From 99d722afb5013149d8e8f3caacba7fc8ab78efc8 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 16:35:20 -0800 Subject: [PATCH 1/7] Use soroban-rpc repo with Makefile GOLDFLAGS --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 000b2aef..e5ffc3a6 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,10 @@ ifeq ($(strip $(REPOSITORY_VERSION)),) endif REPOSITORY_BRANCH := "$(shell git rev-parse --abbrev-ref HEAD)" BUILD_TIMESTAMP ?= $(shell date '+%Y-%m-%dT%H:%M:%S') -GOLDFLAGS := -X 'github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/config.Version=${REPOSITORY_VERSION}' \ - -X 'github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/config.CommitHash=${REPOSITORY_COMMIT_HASH}' \ - -X 'github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}' \ - -X 'github.com/stellar/soroban-tools/cmd/soroban-rpc/internal/config.Branch=${REPOSITORY_BRANCH}' +GOLDFLAGS := -X 'github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config.Version=${REPOSITORY_VERSION}' \ + -X 'github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config.CommitHash=${REPOSITORY_COMMIT_HASH}' \ + -X 'github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config.BuildTimestamp=${BUILD_TIMESTAMP}' \ + -X 'github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config.Branch=${REPOSITORY_BRANCH}' # The following works around incompatibility between the rust and the go linkers - From 06125e0c36afe9fa770d0d589ecc2b8ffc0881b7 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 16:42:31 -0800 Subject: [PATCH 2/7] Use external soroban-spec-json crate --- .github/workflows/rust.yml | 2 +- Cargo.lock | 353 +++++++++++----------- Cargo.toml | 5 +- cmd/crates/soroban-spec-json/Cargo.toml | 26 -- cmd/crates/soroban-spec-json/README.md | 3 - cmd/crates/soroban-spec-json/src/lib.rs | 229 -------------- cmd/crates/soroban-spec-json/src/types.rs | 245 --------------- 7 files changed, 171 insertions(+), 692 deletions(-) delete mode 100644 cmd/crates/soroban-spec-json/Cargo.toml delete mode 100644 cmd/crates/soroban-spec-json/README.md delete mode 100644 cmd/crates/soroban-spec-json/src/lib.rs delete mode 100644 cmd/crates/soroban-spec-json/src/types.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9028df78..c60b8366 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -118,7 +118,7 @@ jobs: cargo-hack-feature-options: --features opt --ignore-unknown-features uses: stellar/actions/.github/workflows/rust-publish-dry-run-v2.yml@main with: - crates: soroban-spec-tools soroban-spec-json soroban-spec-typescript soroban-test soroban-cli + crates: soroban-spec-tools soroban-spec-typescript soroban-test soroban-cli runs-on: ${{ matrix.os }} target: ${{ matrix.target }} cargo-hack-feature-options: ${{ matrix.cargo-hack-feature-options }} diff --git a/Cargo.lock b/Cargo.lock index 451235fa..f10f9108 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "628a8f9bd1e24b4e0db2b4bc2d000b001e7dd032d54afa60a68836aeec5aa54a" dependencies = [ "anstyle", "anstyle-parse", @@ -91,9 +91,9 @@ dependencies = [ [[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" @@ -106,14 +106,14 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467" dependencies = [ "anstyle", "bstr", "doc-comment", - "predicates 3.0.4", + "predicates 3.1.0", "predicates-core", "predicates-tree", "wait-timeout", @@ -121,14 +121,14 @@ dependencies = [ [[package]] name = "assert_fs" -version = "1.0.13" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48" +checksum = "2cd762e110c8ed629b11b6cde59458cc1c71de78ebbcc30099fc8e0403a2a2ec" dependencies = [ "anstyle", "doc-comment", "globwalk", - "predicates 3.0.4", + "predicates 3.1.0", "predicates-core", "predicates-tree", "tempfile", @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", @@ -186,9 +186,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" @@ -213,9 +213,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "regex-automata 0.4.3", @@ -287,9 +287,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" dependencies = [ "serde", ] @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.4" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +checksum = "dfb0d4825b75ff281318c393e8e1b80c4da9fb75a6b1d98547d389d6fe1f48d2" dependencies = [ "clap", ] @@ -415,9 +415,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" @@ -437,9 +437,9 @@ 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", ] @@ -468,45 +468,37 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.16" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", ] [[package]] name = "crossbeam-utils" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-bigint" @@ -614,9 +606,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.110" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7129e341034ecb940c9072817cd9007974ea696844fc4dd582dc1653a7fbe2e8" +checksum = "58ab30434ea0ff6aa640a08dda5284026a366d47565496fd40b6cbfbdd7e31a2" dependencies = [ "cc", "cxxbridge-flags", @@ -626,9 +618,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.110" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a24f3f5f8eed71936f21e570436f024f5c2e25628f7496aa7ccd03b90109d5" +checksum = "b649d7dfae8268450d53d109388b337b9352c7cba1fc10db4a1bc23c3dc189fb" dependencies = [ "cc", "codespan-reporting", @@ -641,15 +633,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.110" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fdd177fc61050d63f67f5bd6351fac6ab5526694ea8e359cd9cd3b75857f44" +checksum = "42281b20eba5218c539295c667c18e2f50211bb11902419194c6ed1ae808e547" [[package]] name = "cxxbridge-macro" -version = "1.0.110" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f" +checksum = "b45506e3c66512b0a65d291a6b452128b7b1dd9841e20d1e151addbd2c00ea50" dependencies = [ "proc-macro2", "quote", @@ -703,9 +695,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -974,36 +966,36 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-sink", @@ -1063,11 +1055,11 @@ dependencies = [ [[package]] name = "globwalk" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", "ignore", "walkdir", ] @@ -1085,9 +1077,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -1122,9 +1114,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hex" @@ -1162,11 +1154,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1205,9 +1197,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1220,7 +1212,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -1258,9 +1250,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1297,9 +1289,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ "crossbeam-deque", "globset", @@ -1394,9 +1386,9 @@ dependencies = [ [[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", ] @@ -1470,9 +1462,9 @@ 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", ] @@ -1485,9 +1477,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[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" @@ -1501,7 +1493,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall", ] @@ -1517,9 +1509,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1548,18 +1540,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" @@ -1668,9 +1651,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", ] @@ -1689,11 +1672,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.61" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -1730,9 +1713,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.97" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -1841,15 +1824,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[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" @@ -1879,13 +1862,12 @@ dependencies = [ [[package]] name = "predicates" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", - "itertools 0.11.0", "predicates-core", ] @@ -1909,7 +1891,7 @@ dependencies = [ name = "preflight" version = "20.2.0" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "libc", "sha2 0.10.8", "soroban-env-host", @@ -2122,11 +2104,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -2163,7 +2145,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -2199,11 +2181,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2297,9 +2279,9 @@ dependencies = [ [[package]] name = "serde-aux" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184eba62ebddb71658697c8b08822edee89970bf318c5362189f0de27f85b498" +checksum = "a86348501c129f3ad50c2f4635a01971f76974cd8a3f335988a0f1581c082765" dependencies = [ "chrono", "serde", @@ -2334,7 +2316,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", @@ -2453,19 +2435,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - -[[package]] -name = "socket2" -version = "0.4.10" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "socket2" @@ -2494,7 +2466,7 @@ version = "20.2.0" dependencies = [ "assert_cmd", "assert_fs", - "base64 0.21.5", + "base64 0.21.7", "cargo_metadata", "chrono", "clap", @@ -2531,7 +2503,7 @@ dependencies = [ "soroban-env-host", "soroban-ledger-snapshot", "soroban-sdk", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-json", "soroban-spec-rust", "soroban-spec-tools", @@ -2667,7 +2639,7 @@ dependencies = [ "rustc_version", "sha2 0.10.8", "soroban-env-common", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-rust", "stellar-xdr", "syn 2.0.39", @@ -2685,6 +2657,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "soroban-spec" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4550e1498552b464bf8583c16abd0670568285c1513091cf3c0ea82f41a229" +dependencies = [ + "base64 0.13.1", + "stellar-xdr", + "thiserror", + "wasmparser 0.88.0", +] + [[package]] name = "soroban-spec" version = "20.1.0" @@ -2699,13 +2683,14 @@ dependencies = [ [[package]] name = "soroban-spec-json" version = "20.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c200aff4d5c5307e9a4984cc759390ff0d66c32822824cab6cce2da0bb68f11" dependencies = [ - "pretty_assertions", "serde", "serde_derive", "serde_json", "sha2 0.9.9", - "soroban-spec", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "stellar-xdr", "thiserror", ] @@ -2719,7 +2704,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", "syn 2.0.39", "thiserror", @@ -2729,12 +2714,12 @@ dependencies = [ name = "soroban-spec-tools" version = "20.2.0" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "ethnum", "hex", "itertools 0.10.5", "serde_json", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-strkey 0.0.7", "stellar-xdr", "thiserror", @@ -2747,7 +2732,7 @@ dependencies = [ name = "soroban-spec-typescript" version = "20.2.0" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "heck", "include_dir", "itertools 0.10.5", @@ -2757,7 +2742,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.9.9", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", "temp-dir", "thiserror", @@ -2779,7 +2764,7 @@ dependencies = [ "soroban-env-host", "soroban-ledger-snapshot", "soroban-sdk", - "soroban-spec", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-tools", "stellar-strkey 0.0.7", "thiserror", @@ -2924,28 +2909,28 @@ dependencies = [ [[package]] name = "temp-dir" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" +checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -3010,18 +2995,18 @@ dependencies = [ [[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", @@ -3040,9 +3025,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -3060,9 +3045,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -3103,9 +3088,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -3115,7 +3100,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -3289,9 +3274,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3391,9 +3376,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", @@ -3401,9 +3386,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", @@ -3416,9 +3401,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", @@ -3426,9 +3411,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", @@ -3439,9 +3424,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 = "wasm-opt" @@ -3572,11 +3557,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c88cd4ab..89f2c700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,6 @@ git = "https://github.com/stellar/rs-soroban-sdk" rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" # path = "../rs-soroban-sdk/soroban-spec-rust" -[workspace.dependencies.soroban-spec-json] -version = "20.2.0" -path = "./cmd/crates/soroban-spec-json" - [workspace.dependencies.soroban-spec-typescript] version = "20.2.0" path = "./cmd/crates/soroban-spec-typescript" @@ -89,6 +85,7 @@ tracing-subscriber = "0.3.16" tracing-appender = "0.2.2" which = "4.4.0" wasmparser = "0.90.0" +soroban-spec-json = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/cmd/crates/soroban-spec-json/Cargo.toml b/cmd/crates/soroban-spec-json/Cargo.toml deleted file mode 100644 index 04051ea6..00000000 --- a/cmd/crates/soroban-spec-json/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "soroban-spec-json" -description = "Soroban contract spec utilities for generating JSON." -homepage = "https://github.com/stellar/soroban-tools" -repository = "https://github.com/stellar/soroban-tools" -authors = ["Stellar Development Foundation "] -readme = "README.md" -license = "Apache-2.0" -version.workspace = true -edition = "2021" -rust-version.workspace = true - -[dependencies] -soroban-spec = { workspace = true } -thiserror = "1.0.32" -serde = "1.0.82" -serde_derive = "1.0.82" -serde_json = "1.0.82" -sha2 = "0.9.9" - -[dependencies.stellar-xdr] -workspace = true -features = ["curr", "std", "serde"] - -[dev_dependencies] -pretty_assertions = "1.2.1" diff --git a/cmd/crates/soroban-spec-json/README.md b/cmd/crates/soroban-spec-json/README.md deleted file mode 100644 index cf3c38c4..00000000 --- a/cmd/crates/soroban-spec-json/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# soroban-spec-json - -Generation of JSON that describes a Soroban contract specification / interface. diff --git a/cmd/crates/soroban-spec-json/src/lib.rs b/cmd/crates/soroban-spec-json/src/lib.rs deleted file mode 100644 index cdc64665..00000000 --- a/cmd/crates/soroban-spec-json/src/lib.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::{fs, io}; - -pub mod types; - -use sha2::{Digest, Sha256}; - -use stellar_xdr::curr::ScSpecEntry; -use types::Entry; - -use soroban_spec::read::{from_wasm, FromWasmError}; - -#[derive(thiserror::Error, Debug)] -pub enum GenerateFromFileError { - #[error("reading file: {0}")] - Io(io::Error), - #[error("sha256 does not match, expected: {expected}")] - VerifySha256 { expected: String }, - #[error("parsing contract spec: {0}")] - Parse(stellar_xdr::curr::Error), - #[error("getting contract spec: {0}")] - GetSpec(FromWasmError), -} - -/// # Errors -/// -/// Will return an error if the file cannot be read, or the wasm cannot be parsed. -pub fn generate_from_file( - file: &str, - verify_sha256: Option<&str>, -) -> Result { - // Read file. - let wasm = fs::read(file).map_err(GenerateFromFileError::Io)?; - - // Produce hash for file. - let sha256 = Sha256::digest(&wasm); - let sha256 = format!("{sha256:x}"); - - if let Some(verify_sha256) = verify_sha256 { - if verify_sha256 != sha256 { - return Err(GenerateFromFileError::VerifySha256 { expected: sha256 }); - } - } - - // Generate code. - let json = generate_from_wasm(&wasm).map_err(GenerateFromFileError::GetSpec)?; - Ok(json) -} - -/// # Errors -/// -/// Will return an error if the wasm cannot be parsed. -pub fn generate_from_wasm(wasm: &[u8]) -> Result { - let spec = from_wasm(wasm)?; - let json = generate(&spec); - Ok(json) -} - -/// # Panics -/// -/// If `serde_json::to_string_pretty` fails to serialize the spec entries. -pub fn generate(spec: &[ScSpecEntry]) -> String { - let collected: Vec<_> = spec.iter().map(Entry::from).collect(); - serde_json::to_string_pretty(&collected).expect("serialization of the spec entries should not have any failure cases as all keys are strings and the serialize implementations are derived") -} - -#[allow(clippy::too_many_lines)] -#[cfg(test)] -mod test { - use pretty_assertions::assert_eq; - use soroban_spec::read::from_wasm; - - use super::generate; - - const EXAMPLE_WASM: &[u8] = - include_bytes!("../../../../target/wasm32-unknown-unknown/test-wasms/test_udt.wasm"); - - #[test] - fn example() { - let entries = from_wasm(EXAMPLE_WASM).unwrap(); - let json = generate(&entries); - assert_eq!( - json, - r#"[ - { - "type": "enum", - "doc": "", - "name": "UdtEnum2", - "cases": [ - { - "doc": "", - "name": "A", - "value": 10 - }, - { - "doc": "", - "name": "B", - "value": 15 - } - ] - }, - { - "type": "union", - "doc": "", - "name": "UdtEnum", - "cases": [ - { - "doc": "", - "name": "UdtA", - "values": [] - }, - { - "doc": "", - "name": "UdtB", - "values": [ - { - "type": "custom", - "name": "UdtStruct" - } - ] - }, - { - "doc": "", - "name": "UdtC", - "values": [ - { - "type": "custom", - "name": "UdtEnum2" - } - ] - }, - { - "doc": "", - "name": "UdtD", - "values": [ - { - "type": "custom", - "name": "UdtTuple" - } - ] - } - ] - }, - { - "type": "struct", - "doc": "", - "name": "UdtTuple", - "fields": [ - { - "doc": "", - "name": "0", - "value": { - "type": "i64" - } - }, - { - "doc": "", - "name": "1", - "value": { - "type": "vec", - "element": { - "type": "i64" - } - } - } - ] - }, - { - "type": "struct", - "doc": "", - "name": "UdtStruct", - "fields": [ - { - "doc": "", - "name": "a", - "value": { - "type": "i64" - } - }, - { - "doc": "", - "name": "b", - "value": { - "type": "i64" - } - }, - { - "doc": "", - "name": "c", - "value": { - "type": "vec", - "element": { - "type": "i64" - } - } - } - ] - }, - { - "type": "function", - "doc": "", - "name": "add", - "inputs": [ - { - "doc": "", - "name": "a", - "value": { - "type": "custom", - "name": "UdtEnum" - } - }, - { - "doc": "", - "name": "b", - "value": { - "type": "custom", - "name": "UdtEnum" - } - } - ], - "outputs": [ - { - "type": "i64" - } - ] - } -]"#, - ); - } -} diff --git a/cmd/crates/soroban-spec-json/src/types.rs b/cmd/crates/soroban-spec-json/src/types.rs deleted file mode 100644 index 8e3b2177..00000000 --- a/cmd/crates/soroban-spec-json/src/types.rs +++ /dev/null @@ -1,245 +0,0 @@ -use serde::Serialize; -use stellar_xdr::curr::{ - ScSpecEntry, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, - ScSpecUdtErrorEnumCaseV0, ScSpecUdtStructFieldV0, ScSpecUdtUnionCaseV0, -}; - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct StructField { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecUdtStructFieldV0> for StructField { - fn from(f: &ScSpecUdtStructFieldV0) -> Self { - StructField { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FunctionInput { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecFunctionInputV0> for FunctionInput { - fn from(f: &ScSpecFunctionInputV0) -> Self { - FunctionInput { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UnionCase { - pub doc: String, - pub name: String, - pub values: Vec, -} - -impl From<&ScSpecUdtUnionCaseV0> for UnionCase { - fn from(c: &ScSpecUdtUnionCaseV0) -> Self { - let (doc, name, values) = match c { - ScSpecUdtUnionCaseV0::VoidV0(v) => ( - v.doc.to_utf8_string_lossy(), - v.name.to_utf8_string_lossy(), - vec![], - ), - ScSpecUdtUnionCaseV0::TupleV0(t) => ( - t.doc.to_utf8_string_lossy(), - t.name.to_utf8_string_lossy(), - t.type_.iter().map(Type::from).collect(), - ), - }; - UnionCase { doc, name, values } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtEnumCaseV0> for EnumCase { - fn from(c: &ScSpecUdtEnumCaseV0) -> Self { - EnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ErrorEnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtErrorEnumCaseV0> for EnumCase { - fn from(c: &ScSpecUdtErrorEnumCaseV0) -> Self { - EnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Type { - Void, - Val, - U64, - I64, - U32, - I32, - U128, - I128, - U256, - I256, - Bool, - Symbol, - Error, - Bytes, - String, - Address, - Timepoint, - Duration, - Map { key: Box, value: Box }, - Option { value: Box }, - Result { value: Box, error: Box }, - Vec { element: Box }, - BytesN { n: u32 }, - Tuple { elements: Vec }, - Custom { name: String }, -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Entry { - Function { - doc: String, - name: String, - inputs: Vec, - outputs: Vec, - }, - Struct { - doc: String, - name: String, - fields: Vec, - }, - Union { - doc: String, - name: String, - cases: Vec, - }, - Enum { - doc: String, - name: String, - cases: Vec, - }, - ErrorEnum { - doc: String, - name: String, - cases: Vec, - }, -} - -impl From<&ScSpecTypeDef> for Type { - fn from(spec: &ScSpecTypeDef) -> Self { - match spec { - ScSpecTypeDef::Map(map) => Type::Map { - key: Box::new(Type::from(map.key_type.as_ref())), - value: Box::new(Type::from(map.value_type.as_ref())), - }, - ScSpecTypeDef::Option(opt) => Type::Option { - value: Box::new(Type::from(opt.value_type.as_ref())), - }, - ScSpecTypeDef::Result(res) => Type::Result { - value: Box::new(Type::from(res.ok_type.as_ref())), - error: Box::new(Type::from(res.error_type.as_ref())), - }, - ScSpecTypeDef::Tuple(tuple) => Type::Tuple { - elements: tuple.value_types.iter().map(Type::from).collect(), - }, - ScSpecTypeDef::Vec(vec) => Type::Vec { - element: Box::new(Type::from(vec.element_type.as_ref())), - }, - ScSpecTypeDef::Udt(udt) => Type::Custom { - name: udt.name.to_utf8_string_lossy(), - }, - ScSpecTypeDef::BytesN(b) => Type::BytesN { n: b.n }, - ScSpecTypeDef::Val => Type::Val, - ScSpecTypeDef::U64 => Type::U64, - ScSpecTypeDef::I64 => Type::I64, - ScSpecTypeDef::U32 => Type::U32, - ScSpecTypeDef::I32 => Type::I32, - ScSpecTypeDef::U128 => Type::U128, - ScSpecTypeDef::I128 => Type::I128, - ScSpecTypeDef::U256 => Type::U256, - ScSpecTypeDef::I256 => Type::I256, - ScSpecTypeDef::Bool => Type::Bool, - ScSpecTypeDef::Symbol => Type::Symbol, - ScSpecTypeDef::Error => Type::Error, - ScSpecTypeDef::Bytes => Type::Bytes, - ScSpecTypeDef::String => Type::String, - ScSpecTypeDef::Address => Type::Address, - ScSpecTypeDef::Void => Type::Void, - ScSpecTypeDef::Timepoint => Type::Timepoint, - ScSpecTypeDef::Duration => Type::Duration, - } - } -} - -impl From<&ScSpecEntry> for Entry { - fn from(spec: &ScSpecEntry) -> Self { - match spec { - ScSpecEntry::FunctionV0(f) => Entry::Function { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - inputs: f.inputs.iter().map(FunctionInput::from).collect(), - outputs: f.outputs.iter().map(Type::from).collect(), - }, - ScSpecEntry::UdtStructV0(s) => Entry::Struct { - doc: s.doc.to_utf8_string_lossy(), - name: s.name.to_utf8_string_lossy(), - fields: s.fields.iter().map(StructField::from).collect(), - }, - ScSpecEntry::UdtUnionV0(u) => Entry::Union { - doc: u.doc.to_utf8_string_lossy(), - name: u.name.to_utf8_string_lossy(), - cases: u.cases.iter().map(UnionCase::from).collect(), - }, - ScSpecEntry::UdtEnumV0(e) => Entry::Enum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(EnumCase::from).collect(), - }, - ScSpecEntry::UdtErrorEnumV0(e) => Entry::Enum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(EnumCase::from).collect(), - }, - } - } -} From 7dd64b457a25b1cea6eb2553ac379078ebbdd2b5 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 16:45:30 -0800 Subject: [PATCH 3/7] Use external soroban-spec-tools crate --- .github/workflows/rust.yml | 2 +- Cargo.lock | 6 +- Cargo.toml | 5 +- cmd/crates/soroban-spec-tools/Cargo.toml | 37 - cmd/crates/soroban-spec-tools/README.md | 3 - cmd/crates/soroban-spec-tools/src/lib.rs | 1468 -------------------- cmd/crates/soroban-spec-tools/src/utils.rs | 294 ---- 7 files changed, 5 insertions(+), 1810 deletions(-) delete mode 100644 cmd/crates/soroban-spec-tools/Cargo.toml delete mode 100644 cmd/crates/soroban-spec-tools/README.md delete mode 100644 cmd/crates/soroban-spec-tools/src/lib.rs delete mode 100644 cmd/crates/soroban-spec-tools/src/utils.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c60b8366..9df0b413 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -118,7 +118,7 @@ jobs: cargo-hack-feature-options: --features opt --ignore-unknown-features uses: stellar/actions/.github/workflows/rust-publish-dry-run-v2.yml@main with: - crates: soroban-spec-tools soroban-spec-typescript soroban-test soroban-cli + crates: soroban-spec-typescript soroban-test soroban-cli runs-on: ${{ matrix.os }} target: ${{ matrix.target }} cargo-hack-feature-options: ${{ matrix.cargo-hack-feature-options }} diff --git a/Cargo.lock b/Cargo.lock index f10f9108..3795f4ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2713,19 +2713,19 @@ dependencies = [ [[package]] name = "soroban-spec-tools" version = "20.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d624133bbf30971d5a5aa656328d8659c7675fbe71d1f2d95796cd13bf64f5f7" dependencies = [ "base64 0.21.7", "ethnum", "hex", "itertools 0.10.5", "serde_json", - "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "stellar-strkey 0.0.7", "stellar-xdr", "thiserror", - "tokio", "wasmparser 0.90.0", - "which", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 89f2c700..06d3120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,10 +42,6 @@ rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" version = "20.2.0" path = "./cmd/crates/soroban-spec-typescript" -[workspace.dependencies.soroban-spec-tools] -version = "20.2.0" -path = "./cmd/crates/soroban-spec-tools" - [workspace.dependencies.soroban-sdk] version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" @@ -86,6 +82,7 @@ tracing-appender = "0.2.2" which = "4.4.0" wasmparser = "0.90.0" soroban-spec-json = "20.2.0" +soroban-spec-tools = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/cmd/crates/soroban-spec-tools/Cargo.toml b/cmd/crates/soroban-spec-tools/Cargo.toml deleted file mode 100644 index 61a32c13..00000000 --- a/cmd/crates/soroban-spec-tools/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "soroban-spec-tools" -description = "Tools for using a contract's XDR spec" -homepage = "https://github.com/stellar/soroban-tools" -repository = "https://github.com/stellar/soroban-tools" -authors = ["Stellar Development Foundation "] -license = "Apache-2.0" -readme = "README.md" -version.workspace = true -edition = "2021" -rust-version.workspace = true -autobins = false - - -[lib] -crate-type = ["rlib"] - - -[dependencies] -soroban-spec = { workspace = true } -stellar-strkey = { workspace = true } -stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] } -serde_json = { workspace = true } -itertools = { workspace = true } -ethnum = { workspace = true } -hex = { workspace = true } -wasmparser = { workspace = true } -base64 = { workspace = true } -thiserror = "1.0.31" -# soroban-ledger-snapshot = { workspace = true } -# soroban-sdk = { workspace = true } -# sep5 = { workspace = true } - - -[dev-dependencies] -which = { workspace = true } -tokio = "1.28.1" diff --git a/cmd/crates/soroban-spec-tools/README.md b/cmd/crates/soroban-spec-tools/README.md deleted file mode 100644 index d2b00654..00000000 --- a/cmd/crates/soroban-spec-tools/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# soroban-spec-tools - -Tools and utilities for soroban specification / interface. diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs deleted file mode 100644 index 7f310fd9..00000000 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ /dev/null @@ -1,1468 +0,0 @@ -#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)] -use std::str::FromStr; - -use itertools::Itertools; -use serde_json::{json, Value}; -use stellar_xdr::curr::{ - AccountId, BytesM, ContractExecutable, Error as XdrError, Hash, Int128Parts, Int256Parts, - PublicKey, ScAddress, ScBytes, ScContractInstance, ScMap, ScMapEntry, ScNonceKey, ScSpecEntry, - ScSpecFunctionV0, ScSpecTypeDef as ScType, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult, - ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpecUdtEnumV0, ScSpecUdtErrorEnumCaseV0, - ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, - ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, ScString, ScSymbol, ScVal, ScVec, StringM, - UInt128Parts, UInt256Parts, Uint256, VecM, -}; - -pub mod utils; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("an unknown error occurred")] - Unknown, - #[error("Invalid pair {0:#?} {1:#?}")] - InvalidPair(ScVal, ScType), - #[error("value is not parseable to {0:#?}")] - InvalidValue(Option), - #[error("Unknown case {0} for {1}")] - EnumCase(String, String), - #[error("Enum {0} missing value for type {1}")] - EnumMissingSecondValue(String, String), - #[error("Enum {0} is illformed")] - IllFormedEnum(String), - #[error("Unknown const case {0}")] - EnumConst(u32), - #[error("Enum const value must be a u32 or smaller")] - EnumConstTooLarge(u64), - #[error("Missing Entry {0}")] - MissingEntry(String), - #[error("Missing Spec")] - MissingSpec, - #[error(transparent)] - Xdr(XdrError), - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Ethnum(#[from] core::num::ParseIntError), - - #[error("Missing key {0} in map")] - MissingKey(String), - #[error("Failed to convert {0} to number")] - FailedNumConversion(serde_json::Number), - #[error("First argument in an enum must be a sybmol")] - EnumFirstValueNotSymbol, - #[error("Failed to find enum case {0}")] - FailedToFindEnumCase(String), - #[error(transparent)] - FailedSilceToByte(#[from] std::array::TryFromSliceError), - #[error(transparent)] - Infallible(#[from] std::convert::Infallible), - #[error("Missing Error case {0}")] - MissingErrorCase(u32), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - Base64Spec(#[from] soroban_spec::read::ParseSpecBase64Error), -} - -#[derive(Default, Clone)] -pub struct Spec(pub Option>); - -impl TryInto for &[u8] { - type Error = soroban_spec::read::FromWasmError; - - fn try_into(self) -> Result { - let spec = soroban_spec::read::from_wasm(self)?; - Ok(Spec::new(spec)) - } -} - -impl Spec { - pub fn new(entries: Vec) -> Self { - Self(Some(entries)) - } - - pub fn from_wasm(wasm: &[u8]) -> Result { - let spec = soroban_spec::read::from_wasm(wasm)?; - Ok(Spec::new(spec)) - } - - pub fn parse_base64(base64: &str) -> Result { - let spec = soroban_spec::read::parse_base64(base64.as_bytes())?; - Ok(Spec::new(spec)) - } -} - -impl Spec { - /// # Errors - /// Could fail to find User Defined Type - pub fn doc(&self, name: &str, type_: &ScType) -> Result, Error> { - let mut str = match type_ { - ScType::Val - | ScType::U64 - | ScType::I64 - | ScType::U128 - | ScType::I128 - | ScType::U32 - | ScType::I32 - | ScType::Result(_) - | ScType::Vec(_) - | ScType::Map(_) - | ScType::Tuple(_) - | ScType::BytesN(_) - | ScType::Symbol - | ScType::Error - | ScType::Bytes - | ScType::Void - | ScType::Timepoint - | ScType::Duration - | ScType::U256 - | ScType::I256 - | ScType::String - | ScType::Bool => String::new(), - ScType::Address => String::from( - "Can be public key (G13..), a contract hash (6c45307) or an identity (alice), ", - ), - ScType::Option(type_) => return self.doc(name, &type_.value_type), - ScType::Udt(ScSpecTypeUdt { name }) => { - let spec_type = self.find(&name.to_utf8_string_lossy())?; - match spec_type { - ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) - | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) - | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) - | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) - | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) => doc, - } - .to_utf8_string_lossy() - } - }; - if let Some(mut ex) = self.example(type_) { - if ex.contains(' ') { - ex = format!("'{ex}'"); - } else if ex.contains('"') { - ex = ex.replace('"', ""); - } - if matches!(type_, ScType::Bool) { - ex = String::new(); - } - let sep = if str.is_empty() { "" } else { "\n" }; - str = format!("{str}{sep}Example:\n --{name} {ex}"); - if ex.contains('"') {} - } - if str.is_empty() { - Ok(None) - } else { - Ok(Some(Box::leak(str.into_boxed_str()))) - } - } - - /// # Errors - /// - /// Might return errors - pub fn find(&self, name: &str) -> Result<&ScSpecEntry, Error> { - self.0 - .as_ref() - .and_then(|specs| { - specs.iter().find(|e| { - let entry_name = match e { - ScSpecEntry::FunctionV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtStructV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtUnionV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), - }; - name == entry_name - }) - }) - .ok_or_else(|| Error::MissingEntry(name.to_owned())) - } - - /// # Errors - /// - /// Might return errors - pub fn find_function(&self, name: &str) -> Result<&ScSpecFunctionV0, Error> { - match self.find(name)? { - ScSpecEntry::FunctionV0(f) => Ok(f), - _ => Err(Error::MissingEntry(name.to_owned())), - } - } - // - /// # Errors - /// - pub fn find_functions(&self) -> Result, Error> { - Ok(self - .0 - .as_ref() - .ok_or(Error::MissingSpec)? - .iter() - .filter_map(|e| match e { - ScSpecEntry::FunctionV0(x) => Some(x), - _ => None, - })) - } - - /// # Errors - /// - pub fn find_error_type(&self, value: u32) -> Result<&ScSpecUdtErrorEnumCaseV0, Error> { - if let ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { cases, .. }) = - self.find("Error")? - { - if let Some(case) = cases.iter().find(|case| value == case.value) { - return Ok(case); - } - } - Err(Error::MissingErrorCase(value)) - } - - /// # Errors - /// - /// Might return errors - pub fn from_string_primitive(s: &str, t: &ScType) -> Result { - Self::default().from_string(s, t) - } - - /// # Errors - /// - /// Might return errors - #[allow(clippy::wrong_self_convention)] - pub fn from_string(&self, s: &str, t: &ScType) -> Result { - if let ScType::Option(b) = t { - if s == "null" { - return Ok(ScVal::Void); - } - let ScSpecTypeOption { value_type } = b.as_ref().clone(); - let v = value_type.as_ref().clone(); - return self.from_string(s, &v); - } - // Parse as string and for special types assume Value::String - serde_json::from_str(s) - .map_or_else( - |e| match t { - ScType::Symbol - | ScType::String - | ScType::Bytes - | ScType::BytesN(_) - | ScType::U256 - | ScType::I256 - | ScType::U128 - | ScType::I128 - | ScType::Address => Ok(Value::String(s.to_owned())), - ScType::Udt(ScSpecTypeUdt { name }) - if matches!( - self.find(&name.to_utf8_string_lossy())?, - ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) - ) => - { - Ok(Value::String(s.to_owned())) - } - _ => Err(Error::Serde(e)), - }, - |val| match t { - ScType::U128 | ScType::I128 | ScType::U256 | ScType::I256 => { - Ok(Value::String(s.to_owned())) - } - _ => Ok(val), - }, - ) - .and_then(|raw| self.from_json(&raw, t)) - } - - /// # Errors - /// - /// Might return errors - #[allow(clippy::wrong_self_convention)] - pub fn from_json(&self, v: &Value, t: &ScType) -> Result { - let val: ScVal = match (t, v) { - ( - ScType::Bool - | ScType::U128 - | ScType::I128 - | ScType::U256 - | ScType::I256 - | ScType::I32 - | ScType::I64 - | ScType::U32 - | ScType::U64 - | ScType::String - | ScType::Symbol - | ScType::Address - | ScType::Bytes - | ScType::BytesN(_), - _, - ) => from_json_primitives(v, t)?, - - // Vec parsing - (ScType::Vec(elem), Value::Array(raw)) => { - let converted: ScVec = raw - .iter() - .map(|item| self.from_json(item, &elem.element_type)) - .collect::, Error>>()? - .try_into() - .map_err(Error::Xdr)?; - ScVal::Vec(Some(converted)) - } - - // Map parsing - (ScType::Map(map), Value::Object(raw)) => self.parse_map(map, raw)?, - - // Option parsing - (ScType::Option(_), Value::Null) => ScVal::Void, - (ScType::Option(elem), v) => self.from_json(v, &elem.value_type)?, - - // Tuple parsing - (ScType::Tuple(elem), Value::Array(raw)) => self.parse_tuple(t, elem, raw)?, - - // User defined types parsing - (ScType::Udt(ScSpecTypeUdt { name }), _) => self.parse_udt(name, v)?, - - // TODO: Implement the rest of these - (_, raw) => serde_json::from_value(raw.clone()).map_err(Error::Serde)?, - }; - Ok(val) - } - - fn parse_udt(&self, name: &StringM<60>, value: &Value) -> Result { - let name = &name.to_utf8_string_lossy(); - match (self.find(name)?, value) { - (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { - if strukt - .fields - .iter() - .any(|f| f.name.to_utf8_string_lossy() == "0") - { - self.parse_tuple_strukt( - strukt, - &(0..map.len()) - .map(|i| map.get(&i.to_string()).unwrap().clone()) - .collect::>(), - ) - } else { - self.parse_strukt(strukt, map) - } - } - (ScSpecEntry::UdtStructV0(strukt), Value::Array(arr)) => { - self.parse_tuple_strukt(strukt, arr) - } - ( - ScSpecEntry::UdtUnionV0(union), - val @ (Value::Array(_) | Value::String(_) | Value::Object(_)), - ) => self.parse_union(union, val), - (ScSpecEntry::UdtEnumV0(enum_), Value::Number(num)) => parse_const_enum(num, enum_), - (s, v) => todo!("Not implemented for {s:#?} {v:#?}"), - } - } - - fn parse_tuple_strukt( - &self, - strukt: &ScSpecUdtStructV0, - array: &[Value], - ) -> Result { - let items = strukt - .fields - .to_vec() - .iter() - .zip(array.iter()) - .map(|(f, v)| { - let val = self.from_json(v, &f.type_)?; - Ok(val) - }) - .collect::, Error>>()?; - Ok(ScVal::Vec(Some(items.try_into().map_err(Error::Xdr)?))) - } - - fn parse_strukt( - &self, - strukt: &ScSpecUdtStructV0, - map: &serde_json::Map, - ) -> Result { - let items = strukt - .fields - .to_vec() - .iter() - .map(|f| { - let name = &f.name.to_utf8_string_lossy(); - let v = map - .get(name) - .ok_or_else(|| Error::MissingKey(name.clone()))?; - let val = self.from_json(v, &f.type_)?; - let key = StringM::from_str(name).unwrap(); - Ok(ScMapEntry { - key: ScVal::Symbol(key.into()), - val, - }) - }) - .collect::, Error>>()?; - let map = ScMap::sorted_from(items).map_err(Error::Xdr)?; - Ok(ScVal::Map(Some(map))) - } - - fn parse_union(&self, union: &ScSpecUdtUnionV0, value: &Value) -> Result { - let (enum_case, rest) = match value { - Value::String(s) => (s, None), - Value::Object(o) if o.len() == 1 => { - let res = o.values().next().map(|v| match v { - Value::Object(obj) if obj.contains_key("0") => { - let len = obj.len(); - Value::Array( - (0..len) - .map(|i| obj.get(&i.to_string()).unwrap().clone()) - .collect::>(), - ) - } - _ => v.clone(), - }); - (o.keys().next().unwrap(), res) - } - _ => todo!(), - }; - let case = union - .cases - .iter() - .find(|c| { - let name = match c { - ScSpecUdtUnionCaseV0::VoidV0(v) => &v.name, - ScSpecUdtUnionCaseV0::TupleV0(v) => &v.name, - }; - enum_case == &name.to_utf8_string_lossy() - }) - .ok_or_else(|| { - Error::EnumCase(enum_case.to_string(), union.name.to_utf8_string_lossy()) - })?; - - let mut res = vec![ScVal::Symbol(ScSymbol( - enum_case.try_into().map_err(Error::Xdr)?, - ))]; - - match (case, rest) { - (ScSpecUdtUnionCaseV0::VoidV0(_), _) | (ScSpecUdtUnionCaseV0::TupleV0(_), None) => (), - (ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { type_, .. }), Some(arr)) - if type_.len() == 1 => - { - res.push(self.from_json(&arr, &type_[0])?); - } - ( - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { type_, .. }), - Some(Value::Array(arr)), - ) => { - res.extend( - arr.iter() - .zip(type_.iter()) - .map(|(elem, ty)| self.from_json(elem, ty)) - .collect::, _>>()?, - ); - } - (ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { .. }), Some(_)) => {} - }; - Ok(ScVal::Vec(Some(res.try_into().map_err(Error::Xdr)?))) - } - - fn parse_tuple( - &self, - t: &ScType, - tuple: &ScSpecTypeTuple, - items: &[Value], - ) -> Result { - let ScSpecTypeTuple { value_types } = tuple; - if items.len() != value_types.len() { - return Err(Error::InvalidValue(Some(t.clone()))); - }; - let parsed: Result, Error> = items - .iter() - .zip(value_types.iter()) - .map(|(item, t)| self.from_json(item, t)) - .collect(); - let converted: ScVec = parsed?.try_into().map_err(Error::Xdr)?; - Ok(ScVal::Vec(Some(converted))) - } - - fn parse_map( - &self, - map: &ScSpecTypeMap, - value_map: &serde_json::Map, - ) -> Result { - let ScSpecTypeMap { - key_type, - value_type, - } = map; - // TODO: What do we do if the expected key_type is not a string or symbol? - let parsed: Result, Error> = value_map - .iter() - .map(|(k, v)| -> Result { - let key = self.from_string(k, key_type)?; - let val = self.from_json(v, value_type)?; - Ok(ScMapEntry { key, val }) - }) - .collect(); - Ok(ScVal::Map(Some( - ScMap::sorted_from(parsed?).map_err(Error::Xdr)?, - ))) - } -} - -impl Spec { - /// # Errors - /// - /// Might return `Error::InvalidValue` - /// - /// # Panics - /// - /// May panic - pub fn xdr_to_json(&self, val: &ScVal, output: &ScType) -> Result { - Ok(match (val, output) { - (ScVal::Void, ScType::Val | ScType::Option(_) | ScType::Tuple(_)) - | (ScVal::Map(None) | ScVal::Vec(None), ScType::Option(_)) => Value::Null, - (ScVal::Bool(_), ScType::Bool) - | (ScVal::Void, ScType::Void) - | (ScVal::String(_), ScType::String) - | (ScVal::Symbol(_), ScType::Symbol) - | (ScVal::U64(_), ScType::U64) - | (ScVal::I64(_), ScType::I64) - | (ScVal::U32(_), ScType::U32) - | (ScVal::I32(_), ScType::I32) - | (ScVal::U128(_), ScType::U128) - | (ScVal::I128(_), ScType::I128) - | (ScVal::U256(_), ScType::U256) - | (ScVal::I256(_), ScType::I256) - | (ScVal::Duration(_), ScType::Duration) - | (ScVal::Timepoint(_), ScType::Timepoint) - | ( - ScVal::ContractInstance(_) - | ScVal::LedgerKeyContractInstance - | ScVal::LedgerKeyNonce(_), - _, - ) - | (ScVal::Address(_), ScType::Address) - | (ScVal::Bytes(_), ScType::Bytes | ScType::BytesN(_)) => to_json(val)?, - - (val, ScType::Result(inner)) => self.xdr_to_json(val, &inner.ok_type)?, - - (val, ScType::Option(inner)) => self.xdr_to_json(val, &inner.value_type)?, - (ScVal::Map(Some(_)) | ScVal::Vec(Some(_)) | ScVal::U32(_), type_) => { - self.sc_object_to_json(val, type_)? - } - - (ScVal::Error(_), ScType::Error) => todo!(), - (v, typed) => todo!("{v:#?} doesn't have a matching {typed:#?}"), - }) - } - - /// # Errors - /// - /// Might return an error - pub fn vec_m_to_json( - &self, - vec_m: &VecM, - type_: &ScType, - ) -> Result { - Ok(Value::Array( - vec_m - .to_vec() - .iter() - .map(|sc_val| self.xdr_to_json(sc_val, type_)) - .collect::, Error>>()?, - )) - } - - /// # Errors - /// - /// Might return an error - pub fn sc_map_to_json(&self, sc_map: &ScMap, type_: &ScSpecTypeMap) -> Result { - let v = sc_map - .iter() - .map(|ScMapEntry { key, val }| { - let key_s = self.xdr_to_json(key, &type_.key_type)?.to_string(); - let val_value = self.xdr_to_json(val, &type_.value_type)?; - Ok((key_s, val_value)) - }) - .collect::, Error>>()?; - Ok(Value::Object(v)) - } - - /// # Errors - /// - /// Might return an error - /// - /// # Panics - /// - /// May panic - pub fn udt_to_json(&self, name: &StringM<60>, sc_obj: &ScVal) -> Result { - let name = &name.to_utf8_string_lossy(); - let udt = self.find(name)?; - Ok(match (sc_obj, udt) { - (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( - strukt - .fields - .iter() - .zip(map.iter()) - .map(|(field, entry)| { - let val = self.xdr_to_json(&entry.val, &field.type_)?; - Ok((field.name.to_utf8_string_lossy(), val)) - }) - .collect::, Error>>()?, - ), - (ScVal::Vec(Some(vec_)), ScSpecEntry::UdtStructV0(strukt)) => Value::Array( - strukt - .fields - .iter() - .zip(vec_.iter()) - .map(|(field, entry)| self.xdr_to_json(entry, &field.type_)) - .collect::, Error>>()?, - ), - (ScVal::Vec(Some(vec_)), ScSpecEntry::UdtUnionV0(union)) => { - let v = vec_.to_vec(); - // let val = &v[0]; - let (first, rest) = match v.split_at(1) { - ([first], []) => (first, None), - ([first], rest) => (first, Some(rest)), - _ => return Err(Error::IllFormedEnum(union.name.to_utf8_string_lossy())), - }; - - let ScVal::Symbol(case_name) = first else { - return Err(Error::EnumFirstValueNotSymbol); - }; - let case = union - .cases - .iter() - .find(|case| { - let name = match case { - ScSpecUdtUnionCaseV0::VoidV0(v) => &v.name, - ScSpecUdtUnionCaseV0::TupleV0(v) => &v.name, - }; - name.as_vec() == case_name.as_vec() - }) - .ok_or_else(|| Error::FailedToFindEnumCase(case_name.to_utf8_string_lossy()))?; - - let case_name = case_name.to_utf8_string_lossy(); - match case { - ScSpecUdtUnionCaseV0::TupleV0(v) => { - let rest = rest.ok_or_else(|| { - Error::EnumMissingSecondValue( - union.name.to_utf8_string_lossy(), - case_name.clone(), - ) - })?; - let val = if v.type_.len() == 1 { - self.xdr_to_json(&rest[0], &v.type_[0])? - } else { - Value::Array( - v.type_ - .iter() - .zip(rest.iter()) - .map(|(type_, val)| self.xdr_to_json(val, type_)) - .collect::, Error>>()?, - ) - }; - - Value::Object([(case_name, val)].into_iter().collect()) - } - ScSpecUdtUnionCaseV0::VoidV0(_) => Value::String(case_name), - } - } - (ScVal::U32(v), ScSpecEntry::UdtEnumV0(_enum_)) => { - Value::Number(serde_json::Number::from(*v)) - } - (s, v) => todo!("Not implemented for {s:#?} {v:#?}"), - }) - } - - /// # Errors - /// - /// Might return an error - /// - /// # Panics - /// - /// Some types are not yet supported and will cause a panic if supplied - pub fn sc_object_to_json(&self, val: &ScVal, spec_type: &ScType) -> Result { - Ok(match (val, spec_type) { - (ScVal::Vec(Some(ScVec(vec_m))), ScType::Vec(type_)) => { - self.vec_m_to_json(vec_m, &type_.element_type)? - } - (ScVal::Vec(Some(ScVec(vec_m))), ScType::Tuple(tuple_type)) => Value::Array( - vec_m - .iter() - .zip(tuple_type.value_types.iter()) - .map(|(v, t)| self.xdr_to_json(v, t)) - .collect::, _>>()?, - ), - ( - sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), - ScType::Udt(ScSpecTypeUdt { name }), - ) => self.udt_to_json(name, sc_obj)?, - - (ScVal::Map(Some(map)), ScType::Map(map_type)) => self.sc_map_to_json(map, map_type)?, - - (ScVal::U64(u64_), ScType::U64) => Value::Number(serde_json::Number::from(*u64_)), - - (ScVal::I64(i64_), ScType::I64) => Value::Number(serde_json::Number::from(*i64_)), - (int @ ScVal::U128(_), ScType::U128) => { - // Always output u128s as strings - let v: u128 = int - .clone() - .try_into() - .map_err(|()| Error::InvalidValue(Some(ScType::U128)))?; - Value::String(v.to_string()) - } - - (int @ ScVal::I128(_), ScType::I128) => { - // Always output u128s as strings - let v: i128 = int - .clone() - .try_into() - .map_err(|()| Error::InvalidValue(Some(ScType::I128)))?; - Value::String(v.to_string()) - } - - (ScVal::Bytes(v), ScType::Bytes | ScType::BytesN(_)) => { - Value::String(to_lower_hex(v.as_slice())) - } - - (ScVal::Bytes(_), ScType::Udt(_)) => todo!(), - - (ScVal::ContractInstance(_), _) => todo!(), - - (ScVal::Address(v), ScType::Address) => sc_address_to_json(v), - - (ok_val, ScType::Result(result_type)) => { - let ScSpecTypeResult { ok_type, .. } = result_type.as_ref(); - self.xdr_to_json(ok_val, ok_type)? - } - - (x, y) => return Err(Error::InvalidPair(x.clone(), y.clone())), - }) - } -} - -/// # Errors -/// -/// Might return an error -pub fn from_string_primitive(s: &str, t: &ScType) -> Result { - Spec::from_string_primitive(s, t) -} - -fn parse_const_enum(num: &serde_json::Number, enum_: &ScSpecUdtEnumV0) -> Result { - let num = num - .as_u64() - .ok_or_else(|| Error::FailedNumConversion(num.clone()))?; - let num = u32::try_from(num).map_err(|_| Error::EnumConstTooLarge(num))?; - enum_ - .cases - .iter() - .find(|c| c.value == num) - .ok_or(Error::EnumConst(num)) - .map(|c| ScVal::U32(c.value)) -} - -/// # Errors -/// -/// Might return an error -#[allow(clippy::too_many_lines)] -pub fn from_json_primitives(v: &Value, t: &ScType) -> Result { - let val: ScVal = match (t, v) { - // Boolean parsing - (ScType::Bool, Value::Bool(true)) => ScVal::Bool(true), - (ScType::Bool, Value::Bool(false)) => ScVal::Bool(false), - - // Number parsing - (ScType::U128, Value::String(s)) => { - let val: u128 = u128::from_str(s) - .map(Into::into) - .map_err(|_| Error::InvalidValue(Some(t.clone())))?; - let bytes = val.to_be_bytes(); - let (hi, lo) = bytes.split_at(8); - ScVal::U128(UInt128Parts { - hi: u64::from_be_bytes(hi.try_into()?), - lo: u64::from_be_bytes(lo.try_into()?), - }) - } - - (ScType::I128, Value::String(s)) => { - let val: i128 = i128::from_str(s) - .map(Into::into) - .map_err(|_| Error::InvalidValue(Some(t.clone())))?; - let bytes = val.to_be_bytes(); - let (hi, lo) = bytes.split_at(8); - ScVal::I128(Int128Parts { - hi: i64::from_be_bytes(hi.try_into()?), - lo: u64::from_be_bytes(lo.try_into()?), - }) - } - - // Number parsing - (ScType::U256, Value::String(s)) => { - let (hi, lo) = ethnum::U256::from_str_prefixed(s)?.into_words(); - let hi_bytes = hi.to_be_bytes(); - let (hi_hi, hi_lo) = hi_bytes.split_at(8); - let lo_bytes = lo.to_be_bytes(); - let (lo_hi, lo_lo) = lo_bytes.split_at(8); - ScVal::U256(UInt256Parts { - hi_hi: u64::from_be_bytes(hi_hi.try_into()?), - hi_lo: u64::from_be_bytes(hi_lo.try_into()?), - lo_hi: u64::from_be_bytes(lo_hi.try_into()?), - lo_lo: u64::from_be_bytes(lo_lo.try_into()?), - }) - } - (ScType::I256, Value::String(s)) => { - let (hi, lo) = ethnum::I256::from_str_prefixed(s)?.into_words(); - let hi_bytes = hi.to_be_bytes(); - let (hi_hi, hi_lo) = hi_bytes.split_at(8); - let lo_bytes = lo.to_be_bytes(); - let (lo_hi, lo_lo) = lo_bytes.split_at(8); - ScVal::I256(Int256Parts { - hi_hi: i64::from_be_bytes(hi_hi.try_into()?), - hi_lo: u64::from_be_bytes(hi_lo.try_into()?), - lo_hi: u64::from_be_bytes(lo_hi.try_into()?), - lo_lo: u64::from_be_bytes(lo_lo.try_into()?), - }) - } - - (ScType::I32, Value::Number(n)) => ScVal::I32( - n.as_i64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::U32, Value::Number(n)) => ScVal::U32( - n.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::I64, Value::Number(n)) => ScVal::I64( - n.as_i64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::U64 | ScType::Timepoint | ScType::Duration, Value::Number(n)) => ScVal::U64( - n.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))?, - ), - - // Symbol parsing - (ScType::Symbol, Value::String(s)) => ScVal::Symbol(ScSymbol( - s.as_bytes() - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - )), - - (ScType::Address, Value::String(s)) => sc_address_from_json(s)?, - - // Bytes parsing - (bytes @ ScType::BytesN(_), Value::Number(n)) => { - from_json_primitives(&Value::String(format!("{n}")), bytes)? - } - (ScType::BytesN(bytes), Value::String(s)) => ScVal::Bytes(ScBytes({ - if bytes.n == 32 { - // Bytes might be a strkey, try parsing it as one. Contract devs should use the new - // proper Address type, but for backwards compatibility some contracts might use a - // BytesN<32> to represent an Address. - if let Ok(key) = sc_address_from_json(s) { - return Ok(key); - } - } - // Bytes are not an address, just parse as a hex string - utils::padded_hex_from_str(s, bytes.n as usize) - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - })), - (ScType::Bytes, Value::Number(n)) => { - from_json_primitives(&Value::String(format!("{n}")), &ScType::Bytes)? - } - (ScType::Bytes, Value::String(s)) => ScVal::Bytes( - hex::decode(s) - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::Bytes | ScType::BytesN(_), Value::Array(raw)) => { - let b: Result, Error> = raw - .iter() - .map(|item| { - item.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone()))) - }) - .collect(); - let converted: BytesM<{ u32::MAX }> = b?.try_into().map_err(Error::Xdr)?; - ScVal::Bytes(ScBytes(converted)) - } - - (ScType::String, Value::String(s)) => ScVal::String(ScString( - s.try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - )), - // Todo make proper error Which shouldn't exist - (_, raw) => serde_json::from_value(raw.clone())?, - }; - Ok(val) -} - -/// # Errors -/// -/// Might return an error -pub fn to_string(v: &ScVal) -> Result { - #[allow(clippy::match_same_arms)] - Ok(match v { - // If symbols are a top-level thing we omit the wrapping quotes - // TODO: Decide if this is a good idea or not. - ScVal::Symbol(v) => std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ScVal::LedgerKeyContractInstance => "LedgerKeyContractInstance".to_string(), - _ => serde_json::to_string(&to_json(v)?)?, - }) -} - -/// # Errors -/// -/// Might return an error -#[allow(clippy::too_many_lines)] -pub fn to_json(v: &ScVal) -> Result { - #[allow(clippy::match_same_arms)] - let val: Value = match v { - ScVal::Bool(b) => Value::Bool(*b), - ScVal::Void => Value::Null, - ScVal::LedgerKeyContractInstance => Value::String("LedgerKeyContractInstance".to_string()), - ScVal::U64(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::Timepoint(tp) => Value::Number(serde_json::Number::from(tp.0)), - ScVal::Duration(d) => Value::Number(serde_json::Number::from(d.0)), - ScVal::I64(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::U32(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::I32(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::Symbol(v) => Value::String( - std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ), - ScVal::String(v) => Value::String( - std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ), - ScVal::Vec(v) => { - let values: Result, Error> = v.as_ref().map_or_else( - || Ok(vec![]), - |v| { - v.iter() - .map(|item| -> Result { to_json(item) }) - .collect() - }, - ); - Value::Array(values?) - } - ScVal::Map(None) => Value::Object(serde_json::Map::with_capacity(0)), - ScVal::Map(Some(v)) => { - // TODO: What do we do if the key is not a string? - let mut m = serde_json::Map::::with_capacity(v.len()); - for ScMapEntry { key, val } in v.iter() { - let k: String = to_string(key)?; - let v: Value = to_json(val).map_err(|_| Error::InvalidValue(None))?; - m.insert(k, v); - } - Value::Object(m) - } - ScVal::Bytes(v) => Value::String(to_lower_hex(v.as_slice())), - ScVal::Address(v) => sc_address_to_json(v), - ScVal::U128(n) => { - let hi: [u8; 8] = n.hi.to_be_bytes(); - let lo: [u8; 8] = n.lo.to_be_bytes(); - let bytes = [hi, lo].concat(); - // Always output u128s as strings - let v = u128::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I128)))?, - ) - .to_string(); - Value::String(v) - } - ScVal::I128(n) => { - let hi: [u8; 8] = n.hi.to_be_bytes(); - let lo: [u8; 8] = n.lo.to_be_bytes(); - let bytes = [hi, lo].concat(); - // Always output u128s as strings - let v = i128::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I128)))?, - ) - .to_string(); - Value::String(v) - } - ScVal::U256(u256parts) => { - let bytes = [ - u256parts.hi_hi.to_be_bytes(), - u256parts.hi_lo.to_be_bytes(), - u256parts.lo_hi.to_be_bytes(), - u256parts.lo_lo.to_be_bytes(), - ] - .concat(); - let u256 = ethnum::U256::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::U256)))?, - ); - Value::String(u256.to_string()) - } - ScVal::I256(i256parts) => { - let bytes = [ - i256parts.hi_hi.to_be_bytes(), - i256parts.hi_lo.to_be_bytes(), - i256parts.lo_hi.to_be_bytes(), - i256parts.lo_lo.to_be_bytes(), - ] - .concat(); - let i256 = ethnum::I256::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I256)))?, - ); - Value::String(i256.to_string()) - } - ScVal::ContractInstance(ScContractInstance { - executable: ContractExecutable::Wasm(hash), - .. - }) => json!({ "hash": hash }), - ScVal::ContractInstance(ScContractInstance { - executable: ContractExecutable::StellarAsset, - .. - }) => json!({"SAC": true}), - ScVal::LedgerKeyNonce(ScNonceKey { nonce }) => { - Value::Number(serde_json::Number::from(*nonce)) - } - ScVal::Error(e) => serde_json::to_value(e)?, - }; - Ok(val) -} - -fn sc_address_to_json(v: &ScAddress) -> Value { - match v { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(k)))) => { - Value::String(stellar_strkey::ed25519::PublicKey(*k).to_string()) - } - ScAddress::Contract(Hash(h)) => Value::String(stellar_strkey::Contract(*h).to_string()), - } -} - -fn sc_address_from_json(s: &str) -> Result { - stellar_strkey::Strkey::from_string(s) - .map_err(|_| Error::InvalidValue(Some(ScType::Address))) - .map(|parsed| match parsed { - stellar_strkey::Strkey::PublicKeyEd25519(p) => Some(ScVal::Address( - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(p.0)))), - )), - stellar_strkey::Strkey::Contract(c) => { - Some(ScVal::Address(ScAddress::Contract(Hash(c.0)))) - } - _ => None, - })? - .ok_or(Error::InvalidValue(Some(ScType::Address))) -} - -fn to_lower_hex(bytes: &[u8]) -> String { - let mut res = String::with_capacity(bytes.len()); - for b in bytes { - res.push_str(&format!("{b:02x}")); - } - res -} - -impl Spec { - #[must_use] - pub fn arg_value_name(&self, type_: &ScType, depth: usize) -> Option { - match type_ { - ScType::U64 => Some("u64".to_string()), - ScType::I64 => Some("i64".to_string()), - ScType::U128 => Some("u128".to_string()), - ScType::I128 => Some("i128".to_string()), - ScType::U32 => Some("u32".to_string()), - ScType::I32 => Some("i32".to_string()), - ScType::Bool => Some("bool".to_string()), - ScType::Symbol => Some("Symbol".to_string()), - ScType::Error => Some("Error".to_string()), - ScType::Bytes => Some("hex_bytes".to_string()), - ScType::Address => Some("Address".to_string()), - ScType::Void => Some("Null".to_string()), - ScType::Timepoint => Some("Timepoint".to_string()), - ScType::Duration => Some("Duration".to_string()), - ScType::U256 => Some("u256".to_string()), - ScType::I256 => Some("i256".to_string()), - ScType::String => Some("String".to_string()), - ScType::Option(val) => { - let ScSpecTypeOption { value_type } = val.as_ref(); - let inner = self.arg_value_name(value_type.as_ref(), depth + 1)?; - Some(format!("Option<{inner}>")) - } - ScType::Vec(val) => { - let ScSpecTypeVec { element_type } = val.as_ref(); - let inner = self.arg_value_name(element_type.as_ref(), depth + 1)?; - Some(format!("Array<{inner}>")) - } - ScType::Result(val) => { - let ScSpecTypeResult { - ok_type, - error_type, - } = val.as_ref(); - let ok = self.arg_value_name(ok_type.as_ref(), depth + 1)?; - let error = self.arg_value_name(error_type.as_ref(), depth + 1)?; - Some(format!("Result<{ok}, {error}>")) - } - ScType::Tuple(val) => { - let ScSpecTypeTuple { value_types } = val.as_ref(); - let names = value_types - .iter() - .map(|t| self.arg_value_name(t, depth + 1)) - .collect::>>()? - .join(", "); - Some(format!("Tuple<{names}>")) - } - ScType::Map(val) => { - let ScSpecTypeMap { - key_type, - value_type, - } = val.as_ref(); - let (key, val) = ( - self.arg_value_name(key_type.as_ref(), depth + 1)?, - self.arg_value_name(value_type.as_ref(), depth + 1)?, - ); - Some(format!("Map<{key}, {val}>")) - } - ScType::BytesN(t) => Some(format!("{}_hex_bytes", t.n)), - ScType::Udt(ScSpecTypeUdt { name }) => { - match self.find(&name.to_utf8_string_lossy()).ok()? { - ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) - if fields - .first() - .map(|f| f.name.to_utf8_string_lossy() == "0") - .unwrap_or_default() => - { - let fields = fields - .iter() - .map(|t| self.arg_value_name(&t.type_, depth + 1)) - .collect::>>()? - .join(", "); - Some(format!("[{fields}]")) - } - ScSpecEntry::UdtStructV0(strukt) => self.arg_value_udt(strukt, depth), - ScSpecEntry::UdtUnionV0(union) => self.arg_value_union(union, depth), - ScSpecEntry::UdtEnumV0(enum_) => Some(arg_value_enum(enum_)), - ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_) => None, - } - } - // No specific value name for these yet. - ScType::Val => None, - } - } - - fn arg_value_udt(&self, strukt: &ScSpecUdtStructV0, depth: usize) -> Option { - let inner = strukt - .fields - .iter() - .map(|f| (f.name.to_utf8_string_lossy(), &f.type_)) - .map(|(name, type_)| { - let type_ = self.arg_value_name(type_, depth + 1)?; - Some(format!("{name}: {type_}")) - }) - .collect::>>()? - .join(", "); - Some(format!("{{ {inner} }}")) - } - - fn arg_value_union(&self, union: &ScSpecUdtUnionV0, depth: usize) -> Option { - union - .cases - .iter() - .map(|f| { - Some(match f { - ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { name, .. }) => { - name.to_utf8_string_lossy() - } - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { - name, - type_, - .. - }) => format!( - "{}({})", - name.to_utf8_string_lossy(), - type_ - .iter() - .map(|type_| self.arg_value_name(type_, depth + 1)) - .collect::>>()? - .join(",") - ), - }) - }) - .collect::>>() - .map(|v| v.join(" | ")) - } -} - -fn arg_value_enum(enum_: &ScSpecUdtEnumV0) -> String { - enum_ - .cases - .iter() - .map(|case| case.value.to_string()) - .join(" | ") -} - -// Example implementation -impl Spec { - #[must_use] - pub fn example(&self, type_: &ScType) -> Option { - match type_ { - ScType::U64 => Some("42".to_string()), - ScType::I64 => Some("-42".to_string()), - ScType::U128 => Some("\"1000\"".to_string()), - ScType::I128 => Some("\"-100\"".to_string()), - ScType::U32 => Some("1".to_string()), - ScType::I32 => Some("-1".to_string()), - ScType::Bool => Some("true".to_string()), - ScType::Symbol => Some("\"hello\"".to_string()), - ScType::Error => Some("Error".to_string()), - ScType::Bytes => Some("\"beefface123\"".to_string()), - ScType::Address => { - Some("\"GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4\"".to_string()) - } - ScType::Void => Some("null".to_string()), - ScType::Timepoint => Some("1234".to_string()), - ScType::Duration => Some("9999".to_string()), - ScType::U256 => Some("\"2000\"".to_string()), - ScType::I256 => Some("\"-20000\"".to_string()), - ScType::String => Some("\"hello world\"".to_string()), - ScType::Option(val) => { - let ScSpecTypeOption { value_type } = val.as_ref(); - self.example(value_type.as_ref()) - } - ScType::Vec(val) => { - let ScSpecTypeVec { element_type } = val.as_ref(); - let inner = self.example(element_type.as_ref())?; - Some(format!("[ {inner} ]")) - } - ScType::Result(val) => { - let ScSpecTypeResult { - ok_type, - error_type, - } = val.as_ref(); - let ok = self.example(ok_type.as_ref())?; - let error = self.example(error_type.as_ref())?; - Some(format!("Result<{ok}, {error}>")) - } - ScType::Tuple(val) => { - let ScSpecTypeTuple { value_types } = val.as_ref(); - let names = value_types - .iter() - .map(|t| self.example(t)) - .collect::>>()? - .join(", "); - Some(format!("[{names}]")) - } - ScType::Map(map) => { - let ScSpecTypeMap { - key_type, - value_type, - } = map.as_ref(); - let (mut key, val) = ( - self.example(key_type.as_ref())?, - self.example(value_type.as_ref())?, - ); - if !matches!(key_type.as_ref(), ScType::Symbol) { - key = format!("\"{key}\""); - } - Some(format!("{{ {key}: {val} }}")) - } - ScType::BytesN(n) => { - let n = n.n as usize; - let res = if n % 2 == 0 { - "ef".repeat(n) - } else { - let mut s = "ef".repeat(n - 1); - s.push('e'); - s - }; - Some(format!("\"{res}\"")) - } - ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(name.to_utf8_string_lossy().as_ref()) - } - // No specific value name for these yet. - ScType::Val => None, - } - } - - fn example_udts(&self, name: &str) -> Option { - match self.find(name).ok() { - Some(ScSpecEntry::UdtStructV0(strukt)) => { - // Check if a tuple strukt - if !strukt.fields.is_empty() && strukt.fields[0].name.to_utf8_string_lossy() == "0" - { - let value_types = strukt - .fields - .iter() - .map(|f| f.type_.clone()) - .collect::>() - .try_into() - .ok()?; - return self.example(&ScType::Tuple(Box::new(ScSpecTypeTuple { value_types }))); - } - let inner = strukt - .fields - .iter() - .map(|f| (f.name.to_utf8_string_lossy(), &f.type_)) - .map(|(name, type_)| { - let type_ = self.example(type_)?; - let name = format!(r#""{name}""#); - Some(format!("{name}: {type_}")) - }) - .collect::>>()? - .join(", "); - Some(format!(r#"{{ {inner} }}"#)) - } - Some(ScSpecEntry::UdtUnionV0(union)) => self.example_union(union), - Some(ScSpecEntry::UdtEnumV0(enum_)) => { - enum_.cases.iter().next().map(|c| c.value.to_string()) - } - Some(ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_)) | None => None, - } - } - - fn example_union(&self, union: &ScSpecUdtUnionV0) -> Option { - let res = union - .cases - .iter() - .map(|case| match case { - ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { name, .. }) => { - Some(format!("\"{}\"", name.to_utf8_string_lossy())) - } - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { - name, type_, .. - }) => { - if type_.len() == 1 { - let single = self.example(&type_[0])?; - Some(format!("{{\"{}\":{single}}}", name.to_utf8_string_lossy())) - } else { - let names = type_ - .iter() - .map(|t| self.example(t)) - .collect::>>()? - .join(", "); - Some(format!("{{\"{}\":[{names}]}}", name.to_utf8_string_lossy())) - } - } - }) - .collect::>>()?; - Some(res.join("|")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use stellar_xdr::curr::ScSpecTypeBytesN; - - #[test] - fn from_json_primitives_bytesn() { - // TODO: Add test for parsing addresses - - // Check it parses hex-encoded bytes - let b = from_json_primitives( - &Value::String("beefface".to_string()), - &ScType::BytesN(ScSpecTypeBytesN { n: 4 }), - ) - .unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) - ); - - // Check it parses hex-encoded bytes when they are all numbers. Normally the json would - // interpret the CLI arg as a number, so we need a special case there. - let b = from_json_primitives( - &Value::Number(4554.into()), - &ScType::BytesN(ScSpecTypeBytesN { n: 2 }), - ) - .unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) - ); - } - - #[test] - fn from_json_primitives_bytes() { - // Check it parses hex-encoded bytes - let b = - from_json_primitives(&Value::String("beefface".to_string()), &ScType::Bytes).unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) - ); - - // Check it parses hex-encoded bytes when they are all numbers. Normally the json would - // interpret the CLI arg as a number, so we need a special case there. - let b = from_json_primitives(&Value::Number(4554.into()), &ScType::Bytes).unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) - ); - } - - #[test] - fn test_sc_address_from_json_strkey() { - // All zero contract address - match sc_address_from_json("CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4") { - Ok(addr) => assert_eq!(addr, ScVal::Address(ScAddress::Contract(Hash([0; 32])))), - Err(e) => panic!("Unexpected error: {e}"), - } - - // Real contract address - match sc_address_from_json("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Contract( - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, - 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, - 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - .try_into() - .unwrap() - )) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - - // All zero user account address - match sc_address_from_json("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Account(AccountId( - PublicKey::PublicKeyTypeEd25519([0; 32].try_into().unwrap()) - ))) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - - // Real user account address - match sc_address_from_json("GA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQHES5") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Account(AccountId( - PublicKey::PublicKeyTypeEd25519( - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, - 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, - 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - .try_into() - .unwrap() - ) - ))) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - } -} diff --git a/cmd/crates/soroban-spec-tools/src/utils.rs b/cmd/crates/soroban-spec-tools/src/utils.rs deleted file mode 100644 index 66b153a1..00000000 --- a/cmd/crates/soroban-spec-tools/src/utils.rs +++ /dev/null @@ -1,294 +0,0 @@ -use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; -use hex::FromHexError; -use std::{ - fmt::Display, - io::{self, Cursor}, -}; - -use stellar_xdr::curr::{ - Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecFunctionV0, - ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, -}; - -pub struct ContractSpec { - pub env_meta_base64: Option, - pub env_meta: Vec, - pub meta_base64: Option, - pub meta: Vec, - pub spec_base64: Option, - pub spec: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] stellar_xdr::curr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), -} - -impl ContractSpec { - pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(section) = payload { - let out = match section.name() { - "contractenvmetav0" => &mut env_meta, - "contractmetav0" => &mut meta, - "contractspecv0" => &mut spec, - _ => continue, - }; - *out = Some(section.data()); - }; - } - - let mut env_meta_base64 = None; - let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); - let cursor = Cursor::new(env_meta); - let mut read = Limited::new(cursor, Limits::none()); - ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - let mut meta_base64 = None; - let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); - let cursor = Cursor::new(meta); - let mut read = Limited::new(cursor, Limits::none()); - ScMetaEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - let mut spec_base64 = None; - let spec = if let Some(spec) = spec { - spec_base64 = Some(base64.encode(spec)); - let cursor = Cursor::new(spec); - let mut read = Limited::new(cursor, Limits::none()); - ScSpecEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - Ok(ContractSpec { - env_meta_base64, - env_meta, - meta_base64, - meta, - spec_base64, - spec, - }) - } -} - -impl Display for ContractSpec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(env_meta) = &self.env_meta_base64 { - writeln!(f, "Env Meta: {env_meta}")?; - for env_meta_entry in &self.env_meta { - match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - writeln!(f, " • Interface Version: {v}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Env Meta: None\n")?; - } - - if let Some(_meta) = &self.meta_base64 { - writeln!(f, "Contract Meta:")?; - for meta_entry in &self.meta { - match meta_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { - writeln!(f, " • {key}: {val}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Contract Meta: None\n")?; - } - - if let Some(_spec_base64) = &self.spec_base64 { - writeln!(f, "Contract Spec:")?; - for spec_entry in &self.spec { - match spec_entry { - ScSpecEntry::FunctionV0(func) => write_func(f, func)?, - ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, - ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, - ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, - ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, - } - } - } else { - writeln!(f, "Contract Spec: None")?; - } - Ok(()) - } -} - -fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { - writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; - if func.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - &indent(&func.doc.to_utf8_string_lossy(), 11).trim() - )?; - } - writeln!( - f, - " Inputs: {}", - indent(&format!("{:#?}", func.inputs), 5).trim() - )?; - writeln!( - f, - " Output: {}", - indent(&format!("{:#?}", func.outputs), 5).trim() - )?; - writeln!(f)?; - Ok(()) -} - -fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { - writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { - writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Fields:")?; - for field in udt.fields.iter() { - writeln!( - f, - " • {}: {}", - field.name.to_utf8_string_lossy(), - indent(&format!("{:#?}", field.type_), 8).trim() - )?; - if field.doc.len() > 0 { - writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; - } - } - writeln!(f)?; - Ok(()) -} - -fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { - writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { - writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn indent(s: &str, n: usize) -> String { - let pad = " ".repeat(n); - s.lines() - .map(|line| format!("{pad}{line}")) - .collect::>() - .join("\n") -} - -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { - if lib.len() > 0 { - format!( - "{}::{}", - lib.to_utf8_string_lossy(), - name.to_utf8_string_lossy() - ) - } else { - name.to_utf8_string_lossy() - } -} - -/// # Errors -/// -/// Might return an error -pub fn padded_hex_from_str(s: &str, n: usize) -> Result, FromHexError> { - if s.len() > n * 2 { - return Err(FromHexError::InvalidStringLength); - } - let mut decoded = vec![0u8; n]; - let padded = format!("{s:0>width$}", width = n * 2); - hex::decode_to_slice(padded, &mut decoded)?; - Ok(decoded) -} - -/// # Errors -/// -/// Might return an error -pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strkey::DecodeError> { - stellar_strkey::Contract::from_string(contract_id) - .map(|strkey| strkey.0) - .or_else(|_| { - // strkey failed, try to parse it as a hex string, for backwards compatibility. - padded_hex_from_str(contract_id, 32) - .map_err(|_| stellar_strkey::DecodeError::Invalid)? - .try_into() - .map_err(|_| stellar_strkey::DecodeError::Invalid) - }) - .map_err(|_| stellar_strkey::DecodeError::Invalid) -} From 2ad47f9e5a8181f63e7a294706e95178613fe471 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 16:51:13 -0800 Subject: [PATCH 4/7] Use external soroban-spec-typescript crate --- .gitattributes | 1 - .github/workflows/bindings-ts.yml | 43 - .github/workflows/rust.yml | 2 +- Cargo.lock | 35 +- Cargo.toml | 5 +- Makefile | 13 +- cmd/crates/soroban-spec-typescript/Cargo.toml | 33 - cmd/crates/soroban-spec-typescript/README.md | 4 - .../fixtures/test_custom_types/.gitignore | 2 - .../fixtures/test_custom_types/README.md | 54 - .../dist/cjs/assembled-tx.d.ts | 184 - .../dist/cjs/assembled-tx.js | 462 --- .../test_custom_types/dist/cjs/index.d.ts | 424 -- .../test_custom_types/dist/cjs/index.js | 527 --- .../dist/cjs/method-options.d.ts | 47 - .../dist/cjs/method-options.js | 4 - .../dist/esm/assembled-tx.d.ts | 184 - .../dist/esm/assembled-tx.js | 450 --- .../test_custom_types/dist/esm/index.d.ts | 424 -- .../test_custom_types/dist/esm/index.js | 509 --- .../dist/esm/method-options.d.ts | 47 - .../dist/esm/method-options.js | 3 - .../test_custom_types/dist/esm/package.json | 1 - .../dist/types/assembled-tx.d.ts | 184 - .../test_custom_types/dist/types/index.d.ts | 424 -- .../dist/types/method-options.d.ts | 47 - .../test_custom_types/package-lock.json | 328 -- .../fixtures/test_custom_types/package.json | 20 - .../test_custom_types/scripts/build.mjs | 37 - .../scripts/tsconfig.cjs.json | 7 - .../scripts/tsconfig.esm.json | 7 - .../scripts/tsconfig.types.json | 8 - .../test_custom_types/src/assembled-tx.ts | 664 ---- .../fixtures/test_custom_types/src/index.ts | 758 ---- .../test_custom_types/src/method-options.ts | 50 - .../fixtures/test_custom_types/tsconfig.json | 98 - .../src/boilerplate.rs | 218 -- cmd/crates/soroban-spec-typescript/src/lib.rs | 377 -- .../src/project_template/.gitignore | 2 - .../src/project_template/README.md | 54 - .../src/project_template/package.json | 20 - .../src/project_template/scripts/build.mjs | 37 - .../scripts/tsconfig.cjs.json | 7 - .../scripts/tsconfig.esm.json | 7 - .../scripts/tsconfig.types.json | 8 - .../src/project_template/src/assembled-tx.ts | 664 ---- .../src/project_template/src/index.ts | 27 - .../project_template/src/method-options.ts | 50 - .../src/project_template/tsconfig.json | 98 - .../soroban-spec-typescript/src/types.rs | 259 -- .../soroban-spec-typescript/src/wrapper.rs | 55 - .../soroban-spec-typescript/ts-tests/.env | 2 - .../ts-tests/.eslintrc.cjs | 11 - .../ts-tests/.gitignore | 4 - .../ts-tests/initialize.sh | 79 - .../ts-tests/package-lock.json | 3405 ----------------- .../ts-tests/package.json | 31 - .../soroban-spec-typescript/ts-tests/soroban | 3 - .../ts-tests/src/test-custom-types.ts | 187 - .../src/test-deserialized-transaction.ts | 16 - .../ts-tests/src/test-hello-world.ts | 26 - .../ts-tests/src/test-methods-as-args.ts | 12 - .../ts-tests/src/test-swap.ts | 134 - .../ts-tests/src/util.ts | 57 - .../ts-tests/tsconfig.json | 101 - 65 files changed, 6 insertions(+), 12035 deletions(-) delete mode 100644 .github/workflows/bindings-ts.yml delete mode 100644 cmd/crates/soroban-spec-typescript/Cargo.toml delete mode 100644 cmd/crates/soroban-spec-typescript/README.md delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/.gitignore delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/README.md delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.js delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/package.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/assembled-tx.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/index.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/method-options.d.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/build.mjs delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.cjs.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.esm.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.types.json delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/assembled-tx.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/method-options.ts delete mode 100644 cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/tsconfig.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/boilerplate.rs delete mode 100644 cmd/crates/soroban-spec-typescript/src/lib.rs delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/.gitignore delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/README.md delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/package.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/scripts/build.mjs delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.cjs.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.esm.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.types.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/src/index.ts delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/src/method-options.ts delete mode 100644 cmd/crates/soroban-spec-typescript/src/project_template/tsconfig.json delete mode 100644 cmd/crates/soroban-spec-typescript/src/types.rs delete mode 100644 cmd/crates/soroban-spec-typescript/src/wrapper.rs delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/.env delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/.eslintrc.cjs delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/.gitignore delete mode 100755 cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/package.json delete mode 100755 cmd/crates/soroban-spec-typescript/ts-tests/soroban delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/test-custom-types.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/test-deserialized-transaction.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/test-hello-world.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/test-methods-as-args.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/test-swap.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts delete mode 100644 cmd/crates/soroban-spec-typescript/ts-tests/tsconfig.json diff --git a/.gitattributes b/.gitattributes index 39fc29cb..012d1ba8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1 @@ Cargo.lock text -merge eol=lf -cmd/crates/soroban-spec-typescript/fixtures/**/* linguist-generated=true -diff diff --git a/.github/workflows/bindings-ts.yml b/.github/workflows/bindings-ts.yml deleted file mode 100644 index 62469fd7..00000000 --- a/.github/workflows/bindings-ts.yml +++ /dev/null @@ -1,43 +0,0 @@ - -name: bindings typescript - -on: - push: - branches: [main, release/**] - pull_request: - -jobs: - test: - name: test generated libraries - runs-on: ubuntu-22.04 - services: - rpc: - image: stellar/quickstart:soroban-dev@sha256:0ad51035cf7caba2fd99c7c1fad0945df6932be7d5c893e1520ccdef7d6a6ffe - ports: - - 8000:8000 - env: - ENABLE_LOGS: true - NETWORK: local - ENABLE_SOROBAN_RPC: true - options: >- - --health-cmd "curl --no-progress-meter --fail-with-body -X POST \"http://localhost:8000/soroban/rpc\" -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getNetwork\"}' && curl --no-progress-meter \"http://localhost:8000/friendbot\" | grep '\"invalid_field\": \"addr\"'" - --health-interval 10s - --health-timeout 5s - --health-retries 50 - steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - run: rustup update - - run: cargo build - - run: rustup target add wasm32-unknown-unknown - - run: make build-test-wasms - - run: npm ci && npm run test - working-directory: cmd/crates/soroban-spec-typescript/ts-tests diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9df0b413..c96167ad 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -118,7 +118,7 @@ jobs: cargo-hack-feature-options: --features opt --ignore-unknown-features uses: stellar/actions/.github/workflows/rust-publish-dry-run-v2.yml@main with: - crates: soroban-spec-typescript soroban-test soroban-cli + crates: soroban-test soroban-cli runs-on: ${{ matrix.os }} target: ${{ matrix.target }} cargo-hack-feature-options: ${{ matrix.cargo-hack-feature-options }} diff --git a/Cargo.lock b/Cargo.lock index 3795f4ea..45bd18dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,12 +714,6 @@ dependencies = [ "syn 2.0.39", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "difflib" version = "0.4.0" @@ -1898,16 +1892,6 @@ dependencies = [ "soroban-simulation", ] -[[package]] -name = "pretty_assertions" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" -dependencies = [ - "diff", - "yansi", -] - [[package]] name = "prettyplease" version = "0.2.15" @@ -2731,22 +2715,21 @@ dependencies = [ [[package]] name = "soroban-spec-typescript" version = "20.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250120ec096eb536804c0cac981e26a85f4357a9ddd28dcc8a870a2b41a82ac6" dependencies = [ "base64 0.21.7", "heck", "include_dir", "itertools 0.10.5", - "pretty_assertions", "prettyplease", "serde", "serde_derive", "serde_json", "sha2 0.9.9", - "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "stellar-xdr", - "temp-dir", "thiserror", - "walkdir", ] [[package]] @@ -2907,12 +2890,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "temp-dir" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" - [[package]] name = "tempfile" version = "3.9.0" @@ -3696,12 +3673,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 06d3120f..9da4ca95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,6 @@ git = "https://github.com/stellar/rs-soroban-sdk" rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" # path = "../rs-soroban-sdk/soroban-spec-rust" -[workspace.dependencies.soroban-spec-typescript] -version = "20.2.0" -path = "./cmd/crates/soroban-spec-typescript" - [workspace.dependencies.soroban-sdk] version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" @@ -83,6 +79,7 @@ which = "4.4.0" wasmparser = "0.90.0" soroban-spec-json = "20.2.0" soroban-spec-tools = "20.2.0" +soroban-spec-typescript = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/Makefile b/Makefile index e5ffc3a6..23187151 100644 --- a/Makefile +++ b/Makefile @@ -53,9 +53,6 @@ build_rust: Cargo.lock build_go: build-libpreflight go build -ldflags="${GOLDFLAGS}" ${MACOS_MIN_VER} ./... -# regenerate the example lib in `cmd/crates/soroban-spec-typsecript/fixtures/ts` -build-snapshot: typescript-bindings-fixtures - build: build_rust build_go build-libpreflight: Cargo.lock @@ -100,14 +97,6 @@ lint-changes: lint: golangci-lint run ./... -typescript-bindings-fixtures: build-test-wasms - cargo run -- contract bindings typescript \ - --wasm ./target/wasm32-unknown-unknown/test-wasms/test_custom_types.wasm \ - --contract-id CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK \ - --network futurenet \ - --output-dir ./cmd/crates/soroban-spec-typescript/fixtures/test_custom_types \ - --overwrite - # PHONY lists all the targets that aren't file names, so that make would skip the timestamp based check. -.PHONY: publish clean fmt watch check e2e-test test build-test-wasms install build build-soroban-rpc build-libpreflight lint lint-changes build-snapshot typescript-bindings-fixtures +.PHONY: publish clean fmt watch check e2e-test test build-test-wasms install build build-soroban-rpc build-libpreflight lint lint-changes diff --git a/cmd/crates/soroban-spec-typescript/Cargo.toml b/cmd/crates/soroban-spec-typescript/Cargo.toml deleted file mode 100644 index cc17073f..00000000 --- a/cmd/crates/soroban-spec-typescript/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "soroban-spec-typescript" -description = "Soroban contract spec utilities for generating JSON." -homepage = "https://github.com/stellar/soroban-tools" -repository = "https://github.com/stellar/soroban-tools" -authors = ["Stellar Development Foundation "] -readme = "README.md" -license = "Apache-2.0" -version.workspace = true -edition = "2021" -rust-version.workspace = true - -[dependencies] -soroban-spec = { workspace = true } -thiserror = "1.0.32" -serde = "1.0.82" -serde_derive = "1.0.82" -serde_json = "1.0.82" -sha2 = "0.9.9" -prettyplease = "0.2.4" -include_dir = { version = "0.7.3", features = ["glob"] } -heck = "0.4.1" -itertools = { workspace = true } -base64 = { workspace = true } - -[dependencies.stellar-xdr] -workspace = true -features = ["curr", "std", "serde", "base64"] - -[dev_dependencies] -temp-dir = "0.1.11" -pretty_assertions = "1.2.1" -walkdir = "2.3.3" diff --git a/cmd/crates/soroban-spec-typescript/README.md b/cmd/crates/soroban-spec-typescript/README.md deleted file mode 100644 index 4cc3f522..00000000 --- a/cmd/crates/soroban-spec-typescript/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# soroban-spec-json - -Generation of TypeScript client bindings from Soroban contract specification / -interface. diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/.gitignore b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/.gitignore deleted file mode 100644 index 72aae85f..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -out/ diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/README.md b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/README.md deleted file mode 100644 index 03f87f30..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# test_custom_types JS - -JS library for interacting with [Soroban](https://soroban.stellar.org/) smart contract `test_custom_types` via Soroban RPC. - -This library was automatically generated by Soroban CLI using a command similar to: - -```bash -soroban contract bindings ts \ - --rpc-url https://rpc-futurenet.stellar.org:443 \ - --network-passphrase "Test SDF Future Network ; October 2022" \ - --contract-id CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK \ - --output-dir ./path/to/test_custom_types -``` - -The network passphrase and contract ID are exported from [index.ts](./src/index.ts) in the `networks` constant. If you are the one who generated this library and you know that this contract is also deployed to other networks, feel free to update `networks` with other valid options. This will help your contract consumers use this library more easily. - -# To publish or not to publish - -This library is suitable for publishing to NPM. You can publish it to NPM using the `npm publish` command. - -But you don't need to publish this library to NPM to use it. You can add it to your project's `package.json` using a file path: - -```json -"dependencies": { - "test_custom_types": "./path/to/this/folder" -} -``` - -However, we've actually encountered [frustration](https://github.com/stellar/soroban-example-dapp/pull/117#discussion_r1232873560) using local libraries with NPM in this way. Though it seems a bit messy, we suggest generating the library directly to your `node_modules` folder automatically after each install by using a `postinstall` script. We've had the least trouble with this approach. NPM will automatically remove what it sees as erroneous directories during the `install` step, and then regenerate them when it gets to your `postinstall` step, which will keep the library up-to-date with your contract. - -```json -"scripts": { - "postinstall": "soroban contract bindings ts --rpc-url https://rpc-futurenet.stellar.org:443 --network-passphrase \"Test SDF Future Network ; October 2022\" --id CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK --name test_custom_types" -} -``` - -Obviously you need to adjust the above command based on the actual command you used to generate the library. - -# Use it - -Now that you have your library up-to-date and added to your project, you can import it in a file and see inline documentation for all of its exported methods: - -```js -import { Contract, networks } from "test_custom_types" - -const contract = new Contract({ - ...networks.futurenet, // for example; check which networks this library exports - rpcUrl: '...', // use your own, or find one for testing at https://soroban.stellar.org/docs/reference/rpc#public-rpc-providers -}) - -contract.| -``` - -As long as your editor is configured to show JavaScript/TypeScript documentation, you can pause your typing at that `|` to get a list of all exports and inline-documentation for each. It exports a separate [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) function for each method in the smart contract, with documentation for each generated from the comments the contract's author included in the original source code. diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.d.ts deleted file mode 100644 index 1d5e6f5e..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.d.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Account, Address, Operation, SorobanRpc, xdr } from "@stellar/stellar-sdk"; -import type { Memo, MemoType, Transaction } from "@stellar/stellar-sdk"; -import type { ClassOptions, MethodOptions, Wallet, XDR_BASE64 } from "./method-options.js"; -export type Tx = Transaction, Operation[]>; -export declare class ExpiredStateError extends Error { -} -export declare class NeedsMoreSignaturesError extends Error { -} -export declare class WalletDisconnectedError extends Error { -} -export declare class SendResultOnlyError extends Error { -} -export declare class SendFailedError extends Error { -} -export declare class NoUnsignedNonInvokerAuthEntriesError extends Error { -} -type SendTx = SorobanRpc.Api.SendTransactionResponse; -type GetTx = SorobanRpc.Api.GetTransactionResponse; -export type u32 = number; -export type i32 = number; -export type u64 = bigint; -export type i64 = bigint; -export type u128 = bigint; -export type i128 = bigint; -export type u256 = bigint; -export type i256 = bigint; -export type Option = T | undefined; -export type Typepoint = bigint; -export type Duration = bigint; -export { Address }; -export interface Error_ { - message: string; -} -export interface Result { - unwrap(): T; - unwrapErr(): E; - isOk(): boolean; - isErr(): boolean; -} -export declare class Ok implements Result { - readonly value: T; - constructor(value: T); - unwrapErr(): E; - unwrap(): T; - isOk(): boolean; - isErr(): boolean; -} -export declare class Err implements Result { - readonly error: E; - constructor(error: E); - unwrapErr(): E; - unwrap(): never; - isOk(): boolean; - isErr(): boolean; -} -export declare const contractErrorPattern: RegExp; -type AssembledTransactionOptions = MethodOptions & ClassOptions & { - method: string; - args?: any[]; - parseResultXdr: (xdr: string | xdr.ScVal | Err) => T; -}; -export declare const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; -export declare class AssembledTransaction { - options: AssembledTransactionOptions; - raw: Tx; - private simulation?; - private simulationResult?; - private simulationTransactionData?; - private server; - toJSON(): string; - static fromJSON(options: Omit, 'args'>, { tx, simulationResult, simulationTransactionData }: { - tx: XDR_BASE64; - simulationResult: { - auth: XDR_BASE64[]; - retval: XDR_BASE64; - }; - simulationTransactionData: XDR_BASE64; - }): AssembledTransaction; - private constructor(); - static fromSimulation(options: AssembledTransactionOptions): Promise>; - simulate: () => Promise; - get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult; - transactionData: xdr.SorobanTransactionData; - }; - get result(): T; - parseError(errorMessage: string): Err | undefined; - getWallet: () => Promise; - getPublicKey: () => Promise; - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount: () => Promise; - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend: ({ secondsToWait, force }?: { - /** - * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). - */ - secondsToWait?: number | undefined; - /** - * If `true`, sign and send the transaction even if it is a read call. - */ - force?: boolean | undefined; - }) => Promise>; - getStorageExpiration: () => Promise; - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy: ({ includeAlreadySigned, }?: { - /** - * Whether or not to include auth entries that have already been signed. Default: false - */ - includeAlreadySigned?: boolean | undefined; - }) => Promise; - preImageFor(entry: xdr.SorobanAuthorizationEntry, signatureExpirationLedger: number): xdr.HashIdPreimage; - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries: (expiration?: number | Promise) => Promise; - get isReadCall(): boolean; - hasRealInvoker: () => Promise; -} -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -declare class SentTransaction { - options: AssembledTransactionOptions; - assembled: AssembledTransaction; - server: SorobanRpc.Server; - signed: Tx; - sendTransactionResponse?: SendTx; - sendTransactionResponseAll?: SendTx[]; - getTransactionResponse?: GetTx; - getTransactionResponseAll?: GetTx[]; - constructor(options: AssembledTransactionOptions, assembled: AssembledTransaction); - static init: (options: AssembledTransactionOptions, assembled: AssembledTransaction, secondsToWait?: number) => Promise>; - private send; - get result(): T; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.js deleted file mode 100644 index c60a6e5f..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/assembled-tx.js +++ /dev/null @@ -1,462 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AssembledTransaction = exports.NULL_ACCOUNT = exports.contractErrorPattern = exports.Err = exports.Ok = exports.Address = exports.NoUnsignedNonInvokerAuthEntriesError = exports.SendFailedError = exports.SendResultOnlyError = exports.WalletDisconnectedError = exports.NeedsMoreSignaturesError = exports.ExpiredStateError = void 0; -const stellar_sdk_1 = require("@stellar/stellar-sdk"); -Object.defineProperty(exports, "Address", { enumerable: true, get: function () { return stellar_sdk_1.Address; } }); -const buffer_1 = require("buffer"); -class ExpiredStateError extends Error { -} -exports.ExpiredStateError = ExpiredStateError; -class NeedsMoreSignaturesError extends Error { -} -exports.NeedsMoreSignaturesError = NeedsMoreSignaturesError; -class WalletDisconnectedError extends Error { -} -exports.WalletDisconnectedError = WalletDisconnectedError; -class SendResultOnlyError extends Error { -} -exports.SendResultOnlyError = SendResultOnlyError; -class SendFailedError extends Error { -} -exports.SendFailedError = SendFailedError; -class NoUnsignedNonInvokerAuthEntriesError extends Error { -} -exports.NoUnsignedNonInvokerAuthEntriesError = NoUnsignedNonInvokerAuthEntriesError; -; -; -class Ok { - value; - constructor(value) { - this.value = value; - } - unwrapErr() { - throw new Error('No error'); - } - unwrap() { - return this.value; - } - isOk() { - return true; - } - isErr() { - return !this.isOk(); - } -} -exports.Ok = Ok; -class Err { - error; - constructor(error) { - this.error = error; - } - unwrapErr() { - return this.error; - } - unwrap() { - throw new Error(this.error.message); - } - isOk() { - return false; - } - isErr() { - return !this.isOk(); - } -} -exports.Err = Err; -exports.contractErrorPattern = /Error\(Contract, #(\d+)\)/; -exports.NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; -class AssembledTransaction { - options; - raw; - simulation; - simulationResult; - simulationTransactionData; - server; - toJSON() { - return JSON.stringify({ - method: this.options.method, - tx: this.raw?.toXDR(), - simulationResult: { - auth: this.simulationData.result.auth.map(a => a.toXDR('base64')), - retval: this.simulationData.result.retval.toXDR('base64'), - }, - simulationTransactionData: this.simulationData.transactionData.toXDR('base64'), - }); - } - static fromJSON(options, { tx, simulationResult, simulationTransactionData }) { - const txn = new AssembledTransaction(options); - txn.raw = stellar_sdk_1.TransactionBuilder.fromXDR(tx, options.networkPassphrase); - txn.simulationResult = { - auth: simulationResult.auth.map(a => stellar_sdk_1.xdr.SorobanAuthorizationEntry.fromXDR(a, 'base64')), - retval: stellar_sdk_1.xdr.ScVal.fromXDR(simulationResult.retval, 'base64'), - }; - txn.simulationTransactionData = stellar_sdk_1.xdr.SorobanTransactionData.fromXDR(simulationTransactionData, 'base64'); - return txn; - } - constructor(options) { - this.options = options; - this.server = new stellar_sdk_1.SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - } - static async fromSimulation(options) { - const tx = new AssembledTransaction(options); - const contract = new stellar_sdk_1.Contract(options.contractId); - tx.raw = new stellar_sdk_1.TransactionBuilder(await tx.getAccount(), { - fee: options.fee?.toString(10) ?? stellar_sdk_1.BASE_FEE, - networkPassphrase: options.networkPassphrase, - }) - .addOperation(contract.call(options.method, ...(options.args ?? []))) - .setTimeout(stellar_sdk_1.TimeoutInfinite) - .build(); - return await tx.simulate(); - } - simulate = async () => { - if (!this.raw) - throw new Error('Transaction has not yet been assembled'); - this.simulation = await this.server.simulateTransaction(this.raw); - if (stellar_sdk_1.SorobanRpc.Api.isSimulationSuccess(this.simulation)) { - this.raw = stellar_sdk_1.SorobanRpc.assembleTransaction(this.raw, this.simulation).build(); - } - return this; - }; - get simulationData() { - if (this.simulationResult && this.simulationTransactionData) { - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - }; - } - // else, we know we just did the simulation on this machine - const simulation = this.simulation; - if (stellar_sdk_1.SorobanRpc.Api.isSimulationError(simulation)) { - throw new Error(`Transaction simulation failed: "${simulation.error}"`); - } - if (stellar_sdk_1.SorobanRpc.Api.isSimulationRestore(simulation)) { - throw new ExpiredStateError(`You need to restore some contract state before you can invoke this method. ${JSON.stringify(simulation, null, 2)}`); - } - if (!simulation.result) { - throw new Error(`Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify(simulation, null, 2)}`); - } - // add to object for serialization & deserialization - this.simulationResult = simulation.result; - this.simulationTransactionData = simulation.transactionData.build(); - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - }; - } - get result() { - try { - return this.options.parseResultXdr(this.simulationData.result.retval); - } - catch (e) { - let err = this.parseError(e.toString()); - if (err) - return err; - throw e; - } - } - parseError(errorMessage) { - if (!this.options.errorTypes) - return; - const match = errorMessage.match(exports.contractErrorPattern); - if (!match) - return; - let i = parseInt(match[1], 10); - let err = this.options.errorTypes[i]; - if (err) - return new Err(err); - } - getWallet = async () => { - return this.options.wallet ?? (await Promise.resolve().then(() => require("@stellar/freighter-api"))).default; - }; - getPublicKey = async () => { - const wallet = await this.getWallet(); - if (await wallet.isConnected() && await wallet.isAllowed()) { - return (await wallet.getUserInfo()).publicKey; - } - }; - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount = async () => { - const publicKey = await this.getPublicKey(); - return publicKey - ? await this.server.getAccount(publicKey) - : new stellar_sdk_1.Account(exports.NULL_ACCOUNT, "0"); - }; - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend = async ({ secondsToWait = 10, force = false } = {}) => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated'); - } - if (!force && this.isReadCall) { - throw new Error('This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway.'); - } - if (!await this.hasRealInvoker()) { - throw new WalletDisconnectedError('Wallet is not connected'); - } - if (this.raw.source !== (await this.getAccount()).accountId()) { - throw new Error(`You must submit the transaction with the account that originally created it. Please switch to the wallet with "${this.raw.source}" as its public key.`); - } - if ((await this.needsNonInvokerSigningBy()).length) { - throw new NeedsMoreSignaturesError('Transaction requires more signatures. See `needsNonInvokerSigningBy` for details.'); - } - return await SentTransaction.init(this.options, this, secondsToWait); - }; - getStorageExpiration = async () => { - const entryRes = await this.server.getLedgerEntries(new stellar_sdk_1.Contract(this.options.contractId).getFootprint()); - if (!entryRes.entries || - !entryRes.entries.length || - !entryRes.entries[0].liveUntilLedgerSeq) - throw new Error('failed to get ledger entry'); - return entryRes.entries[0].liveUntilLedgerSeq; - }; - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy = async ({ includeAlreadySigned = false, } = {}) => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated'); - } - // We expect that any transaction constructed by these libraries has a - // single operation, which is an InvokeHostFunction operation. The host - // function being invoked is the contract method call. - if (!("operations" in this.raw)) { - throw new Error(`Unexpected Transaction type; no operations: ${JSON.stringify(this.raw)}`); - } - const rawInvokeHostFunctionOp = this.raw - .operations[0]; - return [...new Set((rawInvokeHostFunctionOp.auth ?? []).filter(entry => entry.credentials().switch() === - stellar_sdk_1.xdr.SorobanCredentialsType.sorobanCredentialsAddress() && - (includeAlreadySigned || - entry.credentials().address().signature().switch().name === 'scvVoid')).map(entry => stellar_sdk_1.StrKey.encodeEd25519PublicKey(entry.credentials().address().address().accountId().ed25519())))]; - }; - preImageFor(entry, signatureExpirationLedger) { - const addrAuth = entry.credentials().address(); - return stellar_sdk_1.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(new stellar_sdk_1.xdr.HashIdPreimageSorobanAuthorization({ - networkId: (0, stellar_sdk_1.hash)(buffer_1.Buffer.from(this.options.networkPassphrase)), - nonce: addrAuth.nonce(), - invocation: entry.rootInvocation(), - signatureExpirationLedger, - })); - } - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries = async ( - /** - * When to set each auth entry to expire. Could be any number of blocks in - * the future. Can be supplied as a promise or a raw number. Default: - * contract's current `persistent` storage expiration date/ledger - * number/block. - */ - expiration = this.getStorageExpiration()) => { - if (!this.raw) - throw new Error('Transaction has not yet been assembled or simulated'); - const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy(); - if (!needsNonInvokerSigningBy) - throw new NoUnsignedNonInvokerAuthEntriesError('No unsigned non-invoker auth entries; maybe you already signed?'); - const publicKey = await this.getPublicKey(); - if (!publicKey) - throw new Error('Could not get public key from wallet; maybe Freighter is not signed in?'); - if (needsNonInvokerSigningBy.indexOf(publicKey) === -1) - throw new Error(`No auth entries for public key "${publicKey}"`); - const wallet = await this.getWallet(); - const rawInvokeHostFunctionOp = this.raw - .operations[0]; - const authEntries = rawInvokeHostFunctionOp.auth ?? []; - for (const [i, entry] of authEntries.entries()) { - if (entry.credentials().switch() !== - stellar_sdk_1.xdr.SorobanCredentialsType.sorobanCredentialsAddress()) { - // if the invoker/source account, then the entry doesn't need explicit - // signature, since the tx envelope is already signed by the source - // account, so only check for sorobanCredentialsAddress - continue; - } - const pk = stellar_sdk_1.StrKey.encodeEd25519PublicKey(entry.credentials().address().address().accountId().ed25519()); - // this auth entry needs to be signed by a different account - // (or maybe already was!) - if (pk !== publicKey) - continue; - authEntries[i] = await (0, stellar_sdk_1.authorizeEntry)(entry, async (preimage) => buffer_1.Buffer.from(await wallet.signAuthEntry(preimage.toXDR('base64')), 'base64'), await expiration, this.options.networkPassphrase); - } - }; - get isReadCall() { - const authsCount = this.simulationData.result.auth.length; - const writeLength = this.simulationData.transactionData.resources().footprint().readWrite().length; - return (authsCount === 0) && (writeLength === 0); - } - hasRealInvoker = async () => { - const account = await this.getAccount(); - return account.accountId() !== exports.NULL_ACCOUNT; - }; -} -exports.AssembledTransaction = AssembledTransaction; -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -class SentTransaction { - options; - assembled; - server; - signed; - sendTransactionResponse; - sendTransactionResponseAll; - getTransactionResponse; - getTransactionResponseAll; - constructor(options, assembled) { - this.options = options; - this.assembled = assembled; - this.server = new stellar_sdk_1.SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - this.assembled = assembled; - } - static init = async (options, assembled, secondsToWait = 10) => { - const tx = new SentTransaction(options, assembled); - return await tx.send(secondsToWait); - }; - send = async (secondsToWait = 10) => { - const wallet = await this.assembled.getWallet(); - this.sendTransactionResponseAll = await withExponentialBackoff(async (previousFailure) => { - if (previousFailure) { - // Increment transaction sequence number and resimulate before trying again - // Soroban transaction can only have 1 operation - const op = this.assembled.raw.operations[0]; - this.assembled.raw = new stellar_sdk_1.TransactionBuilder(await this.assembled.getAccount(), { - fee: this.assembled.raw.fee, - networkPassphrase: this.options.networkPassphrase, - }) - .setTimeout(stellar_sdk_1.TimeoutInfinite) - .addOperation(stellar_sdk_1.Operation.invokeHostFunction({ ...op, auth: op.auth ?? [] })) - .build(); - await this.assembled.simulate(); - } - const signature = await wallet.signTransaction(this.assembled.raw.toXDR(), { - networkPassphrase: this.options.networkPassphrase, - }); - this.signed = stellar_sdk_1.TransactionBuilder.fromXDR(signature, this.options.networkPassphrase); - return this.server.sendTransaction(this.signed); - }, resp => resp.status !== "PENDING", secondsToWait); - this.sendTransactionResponse = this.sendTransactionResponseAll[this.sendTransactionResponseAll.length - 1]; - if (this.sendTransactionResponse.status !== "PENDING") { - throw new Error(`Tried to resubmit transaction for ${secondsToWait} seconds, but it's still failing. ` + - `All attempts: ${JSON.stringify(this.sendTransactionResponseAll, null, 2)}`); - } - const { hash } = this.sendTransactionResponse; - this.getTransactionResponseAll = await withExponentialBackoff(() => this.server.getTransaction(hash), resp => resp.status === stellar_sdk_1.SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, secondsToWait); - this.getTransactionResponse = this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1]; - if (this.getTransactionResponse.status === stellar_sdk_1.SorobanRpc.Api.GetTransactionStatus.NOT_FOUND) { - console.error(`Waited ${secondsToWait} seconds for transaction to complete, but it did not. ` + - `Returning anyway. Check the transaction status manually. ` + - `Sent transaction: ${JSON.stringify(this.sendTransactionResponse, null, 2)}\n` + - `All attempts to get the result: ${JSON.stringify(this.getTransactionResponseAll, null, 2)}`); - } - return this; - }; - get result() { - // 1. check if transaction was submitted and awaited with `getTransaction` - if ("getTransactionResponse" in this && - this.getTransactionResponse) { - // getTransactionResponse has a `returnValue` field unless it failed - if ("returnValue" in this.getTransactionResponse) { - return this.options.parseResultXdr(this.getTransactionResponse.returnValue); - } - // if "returnValue" not present, the transaction failed; return without parsing the result - throw new Error("Transaction failed! Cannot parse result."); - } - // 2. otherwise, maybe it was merely sent with `sendTransaction` - if (this.sendTransactionResponse) { - const errorResult = this.sendTransactionResponse.errorResult?.result(); - if (errorResult) { - throw new SendFailedError(`Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResult}`); - } - throw new SendResultOnlyError(`Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\``); - } - // 3. finally, if neither of those are present, throw an error - throw new Error(`Sending transaction failed: ${JSON.stringify(this.assembled)}`); - } -} -/** - * Keep calling a `fn` for `secondsToWait` seconds, if `keepWaitingIf` is true. - * Returns an array of all attempts to call the function. - */ -async function withExponentialBackoff(fn, keepWaitingIf, secondsToWait, exponentialFactor = 1.5, verbose = false) { - const attempts = []; - let count = 0; - attempts.push(await fn()); - if (!keepWaitingIf(attempts[attempts.length - 1])) - return attempts; - const waitUntil = new Date(Date.now() + secondsToWait * 1000).valueOf(); - let waitTime = 1000; - let totalWaitTime = waitTime; - while (Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1])) { - count++; - // Wait a beat - if (verbose) { - console.info(`Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${secondsToWait * 1000}ms)`); - } - await new Promise(res => setTimeout(res, waitTime)); - // Exponential backoff - waitTime = waitTime * exponentialFactor; - if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { - waitTime = waitUntil - Date.now(); - if (verbose) { - console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`); - } - } - totalWaitTime = waitTime + totalWaitTime; - // Try again - attempts.push(await fn(attempts[attempts.length - 1])); - if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { - console.info(`${count}. Called ${fn}; ${attempts.length} prev attempts. Most recent: ${JSON.stringify(attempts[attempts.length - 1], null, 2)}`); - } - } - return attempts; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.d.ts deleted file mode 100644 index 66d3d595..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.d.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { ContractSpec } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -import type { u32, i32, i64, i128, Option, Error_ } from './assembled-tx.js'; -import type { ClassOptions } from './method-options.js'; -export * from './assembled-tx.js'; -export * from './method-options.js'; -export declare const networks: { - readonly futurenet: { - readonly networkPassphrase: "Test SDF Future Network ; October 2022"; - readonly contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK"; - }; -}; -/** - This is from the rust doc above the struct Test - */ -export interface Test { - /** - - */ - a: u32; - /** - - */ - b: boolean; - /** - - */ - c: string; -} -/** - - */ -export type SimpleEnum = { - tag: "First"; - values: void; -} | { - tag: "Second"; - values: void; -} | { - tag: "Third"; - values: void; -}; -/** - - */ -export declare enum RoyalCard { - Jack = 11, - Queen = 12, - King = 13 -} -/** - - */ -export type TupleStruct = readonly [Test, SimpleEnum]; -/** - - */ -export type ComplexEnum = { - tag: "Struct"; - values: readonly [Test]; -} | { - tag: "Tuple"; - values: readonly [TupleStruct]; -} | { - tag: "Enum"; - values: readonly [SimpleEnum]; -} | { - tag: "Asset"; - values: readonly [string, i128]; -} | { - tag: "Void"; - values: void; -}; -/** - - */ -export declare const Errors: { - 1: { - message: string; - }; -}; -export declare class Contract { - readonly options: ClassOptions; - spec: ContractSpec; - constructor(options: ClassOptions); - private readonly parsers; - private txFromJSON; - readonly fromJSON: { - hello: (json: string) => AssembledTransaction; - woid: (json: string) => AssembledTransaction; - val: (json: string) => AssembledTransaction; - u32FailOnEven: (json: string) => AssembledTransaction | Ok>; - u32: (json: string) => AssembledTransaction; - i32: (json: string) => AssembledTransaction; - i64: (json: string) => AssembledTransaction; - struktHel: (json: string) => AssembledTransaction; - strukt: (json: string) => AssembledTransaction; - simple: (json: string) => AssembledTransaction; - complex: (json: string) => AssembledTransaction; - addresse: (json: string) => AssembledTransaction; - bytes: (json: string) => AssembledTransaction; - bytesN: (json: string) => AssembledTransaction; - card: (json: string) => AssembledTransaction; - boolean: (json: string) => AssembledTransaction; - not: (json: string) => AssembledTransaction; - i128: (json: string) => AssembledTransaction; - u128: (json: string) => AssembledTransaction; - multiArgs: (json: string) => AssembledTransaction; - map: (json: string) => AssembledTransaction>; - vec: (json: string) => AssembledTransaction; - tuple: (json: string) => AssembledTransaction; - option: (json: string) => AssembledTransaction>; - u256: (json: string) => AssembledTransaction; - i256: (json: string) => AssembledTransaction; - string: (json: string) => AssembledTransaction; - tupleStrukt: (json: string) => AssembledTransaction; - }; - /** -* Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - hello: ({ hello }: { - hello: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - woid: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - val: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32FailOnEven: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise | Ok>>; - /** -* Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i32: ({ i32_ }: { - i32_: i32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i64: ({ i64_ }: { - i64_: i64; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct -*/ - struktHel: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - strukt: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - simple: ({ simple }: { - simple: SimpleEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - complex: ({ complex }: { - complex: ComplexEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - addresse: ({ addresse }: { - addresse: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytes: ({ bytes }: { - bytes: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytesN: ({ bytes_n }: { - bytes_n: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - card: ({ card }: { - card: RoyalCard; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - boolean: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value -*/ - not: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i128: ({ i128 }: { - i128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u128: ({ u128 }: { - u128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - multiArgs: ({ a, b }: { - a: u32; - b: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - map: ({ map }: { - map: Map; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - vec: ({ vec }: { - vec: Array; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tuple: ({ tuple }: { - tuple: readonly [string, u32]; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument -*/ - option: ({ option }: { - option: Option; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u256: ({ u256 }: { - u256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i256: ({ i256 }: { - i256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - string: ({ string }: { - string: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tupleStrukt: ({ tuple_strukt }: { - tuple_strukt: TupleStruct; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.js deleted file mode 100644 index d6e27aa8..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/index.js +++ /dev/null @@ -1,527 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Contract = exports.Errors = exports.RoyalCard = exports.networks = void 0; -const stellar_sdk_1 = require("@stellar/stellar-sdk"); -const buffer_1 = require("buffer"); -const assembled_tx_js_1 = require("./assembled-tx.js"); -__exportStar(require("./assembled-tx.js"), exports); -__exportStar(require("./method-options.js"), exports); -if (typeof window !== 'undefined') { - //@ts-ignore Buffer exists - window.Buffer = window.Buffer || buffer_1.Buffer; -} -exports.networks = { - futurenet: { - networkPassphrase: "Test SDF Future Network ; October 2022", - contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK", - } -}; -/** - - */ -var RoyalCard; -(function (RoyalCard) { - RoyalCard[RoyalCard["Jack"] = 11] = "Jack"; - RoyalCard[RoyalCard["Queen"] = 12] = "Queen"; - RoyalCard[RoyalCard["King"] = 13] = "King"; -})(RoyalCard || (exports.RoyalCard = RoyalCard = {})); -/** - - */ -exports.Errors = { - 1: { message: "Please provide an odd number" } -}; -class Contract { - options; - spec; - constructor(options) { - this.options = options; - this.spec = new stellar_sdk_1.ContractSpec([ - "AAAAAQAAAC9UaGlzIGlzIGZyb20gdGhlIHJ1c3QgZG9jIGFib3ZlIHRoZSBzdHJ1Y3QgVGVzdAAAAAAAAAAABFRlc3QAAAADAAAAAAAAAAFhAAAAAAAABAAAAAAAAAABYgAAAAAAAAEAAAAAAAAAAWMAAAAAAAAR", - "AAAAAgAAAAAAAAAAAAAAClNpbXBsZUVudW0AAAAAAAMAAAAAAAAAAAAAAAVGaXJzdAAAAAAAAAAAAAAAAAAABlNlY29uZAAAAAAAAAAAAAAAAAAFVGhpcmQAAAA=", - "AAAAAwAAAAAAAAAAAAAACVJveWFsQ2FyZAAAAAAAAAMAAAAAAAAABEphY2sAAAALAAAAAAAAAAVRdWVlbgAAAAAAAAwAAAAAAAAABEtpbmcAAAAN", - "AAAAAQAAAAAAAAAAAAAAC1R1cGxlU3RydWN0AAAAAAIAAAAAAAAAATAAAAAAAAfQAAAABFRlc3QAAAAAAAAAATEAAAAAAAfQAAAAClNpbXBsZUVudW0AAA==", - "AAAAAgAAAAAAAAAAAAAAC0NvbXBsZXhFbnVtAAAAAAUAAAABAAAAAAAAAAZTdHJ1Y3QAAAAAAAEAAAfQAAAABFRlc3QAAAABAAAAAAAAAAVUdXBsZQAAAAAAAAEAAAfQAAAAC1R1cGxlU3RydWN0AAAAAAEAAAAAAAAABEVudW0AAAABAAAH0AAAAApTaW1wbGVFbnVtAAAAAAABAAAAAAAAAAVBc3NldAAAAAAAAAIAAAATAAAACwAAAAAAAAAAAAAABFZvaWQ=", - "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAAQAAABxQbGVhc2UgcHJvdmlkZSBhbiBvZGQgbnVtYmVyAAAAD051bWJlck11c3RCZU9kZAAAAAAB", - "AAAAAAAAAAAAAAAFaGVsbG8AAAAAAAABAAAAAAAAAAVoZWxsbwAAAAAAABEAAAABAAAAEQ==", - "AAAAAAAAAAAAAAAEd29pZAAAAAAAAAAA", - "AAAAAAAAAAAAAAADdmFsAAAAAAAAAAABAAAAAA==", - "AAAAAAAAAAAAAAAQdTMyX2ZhaWxfb25fZXZlbgAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAA+kAAAAEAAAAAw==", - "AAAAAAAAAAAAAAAEdTMyXwAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAAEaTMyXwAAAAEAAAAAAAAABGkzMl8AAAAFAAAAAQAAAAU=", - "AAAAAAAAAAAAAAAEaTY0XwAAAAEAAAAAAAAABGk2NF8AAAAHAAAAAQAAAAc=", - "AAAAAAAAACxFeGFtcGxlIGNvbnRyYWN0IG1ldGhvZCB3aGljaCB0YWtlcyBhIHN0cnVjdAAAAApzdHJ1a3RfaGVsAAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAPqAAAAEQ==", - "AAAAAAAAAAAAAAAGc3RydWt0AAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAfQAAAABFRlc3Q=", - "AAAAAAAAAAAAAAAGc2ltcGxlAAAAAAABAAAAAAAAAAZzaW1wbGUAAAAAB9AAAAAKU2ltcGxlRW51bQAAAAAAAQAAB9AAAAAKU2ltcGxlRW51bQAA", - "AAAAAAAAAAAAAAAHY29tcGxleAAAAAABAAAAAAAAAAdjb21wbGV4AAAAB9AAAAALQ29tcGxleEVudW0AAAAAAQAAB9AAAAALQ29tcGxleEVudW0A", - "AAAAAAAAAAAAAAAIYWRkcmVzc2UAAAABAAAAAAAAAAhhZGRyZXNzZQAAABMAAAABAAAAEw==", - "AAAAAAAAAAAAAAAFYnl0ZXMAAAAAAAABAAAAAAAAAAVieXRlcwAAAAAAAA4AAAABAAAADg==", - "AAAAAAAAAAAAAAAHYnl0ZXNfbgAAAAABAAAAAAAAAAdieXRlc19uAAAAA+4AAAAJAAAAAQAAA+4AAAAJ", - "AAAAAAAAAAAAAAAEY2FyZAAAAAEAAAAAAAAABGNhcmQAAAfQAAAACVJveWFsQ2FyZAAAAAAAAAEAAAfQAAAACVJveWFsQ2FyZAAAAA==", - "AAAAAAAAAAAAAAAHYm9vbGVhbgAAAAABAAAAAAAAAAdib29sZWFuAAAAAAEAAAABAAAAAQ==", - "AAAAAAAAABdOZWdhdGVzIGEgYm9vbGVhbiB2YWx1ZQAAAAADbm90AAAAAAEAAAAAAAAAB2Jvb2xlYW4AAAAAAQAAAAEAAAAB", - "AAAAAAAAAAAAAAAEaTEyOAAAAAEAAAAAAAAABGkxMjgAAAALAAAAAQAAAAs=", - "AAAAAAAAAAAAAAAEdTEyOAAAAAEAAAAAAAAABHUxMjgAAAAKAAAAAQAAAAo=", - "AAAAAAAAAAAAAAAKbXVsdGlfYXJncwAAAAAAAgAAAAAAAAABYQAAAAAAAAQAAAAAAAAAAWIAAAAAAAABAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAADbWFwAAAAAAEAAAAAAAAAA21hcAAAAAPsAAAABAAAAAEAAAABAAAD7AAAAAQAAAAB", - "AAAAAAAAAAAAAAADdmVjAAAAAAEAAAAAAAAAA3ZlYwAAAAPqAAAABAAAAAEAAAPqAAAABA==", - "AAAAAAAAAAAAAAAFdHVwbGUAAAAAAAABAAAAAAAAAAV0dXBsZQAAAAAAA+0AAAACAAAAEQAAAAQAAAABAAAD7QAAAAIAAAARAAAABA==", - "AAAAAAAAAB9FeGFtcGxlIG9mIGFuIG9wdGlvbmFsIGFyZ3VtZW50AAAAAAZvcHRpb24AAAAAAAEAAAAAAAAABm9wdGlvbgAAAAAD6AAAAAQAAAABAAAD6AAAAAQ=", - "AAAAAAAAAAAAAAAEdTI1NgAAAAEAAAAAAAAABHUyNTYAAAAMAAAAAQAAAAw=", - "AAAAAAAAAAAAAAAEaTI1NgAAAAEAAAAAAAAABGkyNTYAAAANAAAAAQAAAA0=", - "AAAAAAAAAAAAAAAGc3RyaW5nAAAAAAABAAAAAAAAAAZzdHJpbmcAAAAAABAAAAABAAAAEA==", - "AAAAAAAAAAAAAAAMdHVwbGVfc3RydWt0AAAAAQAAAAAAAAAMdHVwbGVfc3RydWt0AAAH0AAAAAtUdXBsZVN0cnVjdAAAAAABAAAH0AAAAAtUdXBsZVN0cnVjdAA=" - ]); - } - parsers = { - hello: (result) => this.spec.funcResToNative("hello", result), - woid: () => { }, - val: (result) => this.spec.funcResToNative("val", result), - u32FailOnEven: (result) => { - if (result instanceof assembled_tx_js_1.Err) - return result; - return new assembled_tx_js_1.Ok(this.spec.funcResToNative("u32_fail_on_even", result)); - }, - u32: (result) => this.spec.funcResToNative("u32_", result), - i32: (result) => this.spec.funcResToNative("i32_", result), - i64: (result) => this.spec.funcResToNative("i64_", result), - struktHel: (result) => this.spec.funcResToNative("strukt_hel", result), - strukt: (result) => this.spec.funcResToNative("strukt", result), - simple: (result) => this.spec.funcResToNative("simple", result), - complex: (result) => this.spec.funcResToNative("complex", result), - addresse: (result) => this.spec.funcResToNative("addresse", result), - bytes: (result) => this.spec.funcResToNative("bytes", result), - bytesN: (result) => this.spec.funcResToNative("bytes_n", result), - card: (result) => this.spec.funcResToNative("card", result), - boolean: (result) => this.spec.funcResToNative("boolean", result), - not: (result) => this.spec.funcResToNative("not", result), - i128: (result) => this.spec.funcResToNative("i128", result), - u128: (result) => this.spec.funcResToNative("u128", result), - multiArgs: (result) => this.spec.funcResToNative("multi_args", result), - map: (result) => this.spec.funcResToNative("map", result), - vec: (result) => this.spec.funcResToNative("vec", result), - tuple: (result) => this.spec.funcResToNative("tuple", result), - option: (result) => this.spec.funcResToNative("option", result), - u256: (result) => this.spec.funcResToNative("u256", result), - i256: (result) => this.spec.funcResToNative("i256", result), - string: (result) => this.spec.funcResToNative("string", result), - tupleStrukt: (result) => this.spec.funcResToNative("tuple_strukt", result) - }; - txFromJSON = (json) => { - const { method, ...tx } = JSON.parse(json); - return assembled_tx_js_1.AssembledTransaction.fromJSON({ - ...this.options, - method, - parseResultXdr: this.parsers[method], - }, tx); - }; - fromJSON = { - hello: (this.txFromJSON), - woid: (this.txFromJSON), - val: (this.txFromJSON), - u32FailOnEven: (this.txFromJSON), - u32: (this.txFromJSON), - i32: (this.txFromJSON), - i64: (this.txFromJSON), - struktHel: (this.txFromJSON), - strukt: (this.txFromJSON), - simple: (this.txFromJSON), - complex: (this.txFromJSON), - addresse: (this.txFromJSON), - bytes: (this.txFromJSON), - bytesN: (this.txFromJSON), - card: (this.txFromJSON), - boolean: (this.txFromJSON), - not: (this.txFromJSON), - i128: (this.txFromJSON), - u128: (this.txFromJSON), - multiArgs: (this.txFromJSON), - map: (this.txFromJSON), - vec: (this.txFromJSON), - tuple: (this.txFromJSON), - option: (this.txFromJSON), - u256: (this.txFromJSON), - i256: (this.txFromJSON), - string: (this.txFromJSON), - tupleStrukt: (this.txFromJSON) - }; - /** -* Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - hello = async ({ hello }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'hello', - args: this.spec.funcArgsToScVals("hello", { hello }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['hello'], - }); - }; - /** -* Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - woid = async (options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'woid', - args: this.spec.funcArgsToScVals("woid", {}), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['woid'], - }); - }; - /** -* Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - val = async (options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'val', - args: this.spec.funcArgsToScVals("val", {}), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['val'], - }); - }; - /** -* Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32FailOnEven = async ({ u32_ }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'u32_fail_on_even', - args: this.spec.funcArgsToScVals("u32_fail_on_even", { u32_ }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['u32FailOnEven'], - }); - }; - /** -* Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32 = async ({ u32_ }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'u32_', - args: this.spec.funcArgsToScVals("u32_", { u32_ }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['u32'], - }); - }; - /** -* Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i32 = async ({ i32_ }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'i32_', - args: this.spec.funcArgsToScVals("i32_", { i32_ }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['i32'], - }); - }; - /** -* Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i64 = async ({ i64_ }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'i64_', - args: this.spec.funcArgsToScVals("i64_", { i64_ }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['i64'], - }); - }; - /** -* Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct -*/ - struktHel = async ({ strukt }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'strukt_hel', - args: this.spec.funcArgsToScVals("strukt_hel", { strukt }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['struktHel'], - }); - }; - /** -* Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - strukt = async ({ strukt }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'strukt', - args: this.spec.funcArgsToScVals("strukt", { strukt }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['strukt'], - }); - }; - /** -* Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - simple = async ({ simple }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'simple', - args: this.spec.funcArgsToScVals("simple", { simple }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['simple'], - }); - }; - /** -* Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - complex = async ({ complex }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'complex', - args: this.spec.funcArgsToScVals("complex", { complex }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['complex'], - }); - }; - /** -* Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - addresse = async ({ addresse }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'addresse', - args: this.spec.funcArgsToScVals("addresse", { addresse: new stellar_sdk_1.Address(addresse) }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['addresse'], - }); - }; - /** -* Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytes = async ({ bytes }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'bytes', - args: this.spec.funcArgsToScVals("bytes", { bytes }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['bytes'], - }); - }; - /** -* Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytesN = async ({ bytes_n }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'bytes_n', - args: this.spec.funcArgsToScVals("bytes_n", { bytes_n }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['bytesN'], - }); - }; - /** -* Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - card = async ({ card }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'card', - args: this.spec.funcArgsToScVals("card", { card }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['card'], - }); - }; - /** -* Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - boolean = async ({ boolean }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'boolean', - args: this.spec.funcArgsToScVals("boolean", { boolean }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['boolean'], - }); - }; - /** -* Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value -*/ - not = async ({ boolean }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'not', - args: this.spec.funcArgsToScVals("not", { boolean }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['not'], - }); - }; - /** -* Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i128 = async ({ i128 }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'i128', - args: this.spec.funcArgsToScVals("i128", { i128 }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['i128'], - }); - }; - /** -* Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u128 = async ({ u128 }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'u128', - args: this.spec.funcArgsToScVals("u128", { u128 }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['u128'], - }); - }; - /** -* Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - multiArgs = async ({ a, b }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'multi_args', - args: this.spec.funcArgsToScVals("multi_args", { a, b }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['multiArgs'], - }); - }; - /** -* Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - map = async ({ map }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'map', - args: this.spec.funcArgsToScVals("map", { map }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['map'], - }); - }; - /** -* Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - vec = async ({ vec }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'vec', - args: this.spec.funcArgsToScVals("vec", { vec }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['vec'], - }); - }; - /** -* Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tuple = async ({ tuple }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'tuple', - args: this.spec.funcArgsToScVals("tuple", { tuple }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['tuple'], - }); - }; - /** -* Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument -*/ - option = async ({ option }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'option', - args: this.spec.funcArgsToScVals("option", { option }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['option'], - }); - }; - /** -* Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u256 = async ({ u256 }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'u256', - args: this.spec.funcArgsToScVals("u256", { u256 }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['u256'], - }); - }; - /** -* Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i256 = async ({ i256 }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'i256', - args: this.spec.funcArgsToScVals("i256", { i256 }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['i256'], - }); - }; - /** -* Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - string = async ({ string }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'string', - args: this.spec.funcArgsToScVals("string", { string }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['string'], - }); - }; - /** -* Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tupleStrukt = async ({ tuple_strukt }, options = {}) => { - return await assembled_tx_js_1.AssembledTransaction.fromSimulation({ - method: 'tuple_strukt', - args: this.spec.funcArgsToScVals("tuple_strukt", { tuple_strukt }), - ...options, - ...this.options, - errorTypes: exports.Errors, - parseResultXdr: this.parsers['tupleStrukt'], - }); - }; -} -exports.Contract = Contract; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.d.ts deleted file mode 100644 index fc6b21d5..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -declare let responseTypes: 'simulated' | 'full' | undefined; -export type ResponseTypes = typeof responseTypes; -export type XDR_BASE64 = string; -export interface Wallet { - isConnected: () => Promise; - isAllowed: () => Promise; - getUserInfo: () => Promise<{ - publicKey?: string; - }>; - signTransaction: (tx: XDR_BASE64, opts?: { - network?: string; - networkPassphrase?: string; - accountToSign?: string; - }) => Promise; - signAuthEntry: (entryXdr: XDR_BASE64, opts?: { - accountToSign?: string; - }) => Promise; -} -export type ClassOptions = { - contractId: string; - networkPassphrase: string; - rpcUrl: string; - errorTypes?: Record; - /** - * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: - * - * @example - * ```ts - * import freighter from "@stellar/freighter-api"; - * import { Contract } from "test_custom_types"; - * const contract = new Contract({ - * …, - * wallet: freighter, - * }) - * ``` - */ - wallet?: Wallet; -}; -export type MethodOptions = { - /** - * The fee to pay for the transaction. Default: soroban-sdk's BASE_FEE ('100') - */ - fee?: number; -}; -export {}; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.js deleted file mode 100644 index 6d483ac3..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/cjs/method-options.js +++ /dev/null @@ -1,4 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -// defined this way so typeahead shows full union, not named alias -let responseTypes; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.d.ts deleted file mode 100644 index 1d5e6f5e..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.d.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Account, Address, Operation, SorobanRpc, xdr } from "@stellar/stellar-sdk"; -import type { Memo, MemoType, Transaction } from "@stellar/stellar-sdk"; -import type { ClassOptions, MethodOptions, Wallet, XDR_BASE64 } from "./method-options.js"; -export type Tx = Transaction, Operation[]>; -export declare class ExpiredStateError extends Error { -} -export declare class NeedsMoreSignaturesError extends Error { -} -export declare class WalletDisconnectedError extends Error { -} -export declare class SendResultOnlyError extends Error { -} -export declare class SendFailedError extends Error { -} -export declare class NoUnsignedNonInvokerAuthEntriesError extends Error { -} -type SendTx = SorobanRpc.Api.SendTransactionResponse; -type GetTx = SorobanRpc.Api.GetTransactionResponse; -export type u32 = number; -export type i32 = number; -export type u64 = bigint; -export type i64 = bigint; -export type u128 = bigint; -export type i128 = bigint; -export type u256 = bigint; -export type i256 = bigint; -export type Option = T | undefined; -export type Typepoint = bigint; -export type Duration = bigint; -export { Address }; -export interface Error_ { - message: string; -} -export interface Result { - unwrap(): T; - unwrapErr(): E; - isOk(): boolean; - isErr(): boolean; -} -export declare class Ok implements Result { - readonly value: T; - constructor(value: T); - unwrapErr(): E; - unwrap(): T; - isOk(): boolean; - isErr(): boolean; -} -export declare class Err implements Result { - readonly error: E; - constructor(error: E); - unwrapErr(): E; - unwrap(): never; - isOk(): boolean; - isErr(): boolean; -} -export declare const contractErrorPattern: RegExp; -type AssembledTransactionOptions = MethodOptions & ClassOptions & { - method: string; - args?: any[]; - parseResultXdr: (xdr: string | xdr.ScVal | Err) => T; -}; -export declare const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; -export declare class AssembledTransaction { - options: AssembledTransactionOptions; - raw: Tx; - private simulation?; - private simulationResult?; - private simulationTransactionData?; - private server; - toJSON(): string; - static fromJSON(options: Omit, 'args'>, { tx, simulationResult, simulationTransactionData }: { - tx: XDR_BASE64; - simulationResult: { - auth: XDR_BASE64[]; - retval: XDR_BASE64; - }; - simulationTransactionData: XDR_BASE64; - }): AssembledTransaction; - private constructor(); - static fromSimulation(options: AssembledTransactionOptions): Promise>; - simulate: () => Promise; - get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult; - transactionData: xdr.SorobanTransactionData; - }; - get result(): T; - parseError(errorMessage: string): Err | undefined; - getWallet: () => Promise; - getPublicKey: () => Promise; - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount: () => Promise; - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend: ({ secondsToWait, force }?: { - /** - * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). - */ - secondsToWait?: number | undefined; - /** - * If `true`, sign and send the transaction even if it is a read call. - */ - force?: boolean | undefined; - }) => Promise>; - getStorageExpiration: () => Promise; - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy: ({ includeAlreadySigned, }?: { - /** - * Whether or not to include auth entries that have already been signed. Default: false - */ - includeAlreadySigned?: boolean | undefined; - }) => Promise; - preImageFor(entry: xdr.SorobanAuthorizationEntry, signatureExpirationLedger: number): xdr.HashIdPreimage; - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries: (expiration?: number | Promise) => Promise; - get isReadCall(): boolean; - hasRealInvoker: () => Promise; -} -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -declare class SentTransaction { - options: AssembledTransactionOptions; - assembled: AssembledTransaction; - server: SorobanRpc.Server; - signed: Tx; - sendTransactionResponse?: SendTx; - sendTransactionResponseAll?: SendTx[]; - getTransactionResponse?: GetTx; - getTransactionResponseAll?: GetTx[]; - constructor(options: AssembledTransactionOptions, assembled: AssembledTransaction); - static init: (options: AssembledTransactionOptions, assembled: AssembledTransaction, secondsToWait?: number) => Promise>; - private send; - get result(): T; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.js deleted file mode 100644 index 79b7c01d..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/assembled-tx.js +++ /dev/null @@ -1,450 +0,0 @@ -import { Account, Address, Contract, Operation, SorobanRpc, StrKey, TimeoutInfinite, TransactionBuilder, authorizeEntry, hash, xdr, BASE_FEE, } from "@stellar/stellar-sdk"; -import { Buffer } from "buffer"; -export class ExpiredStateError extends Error { -} -export class NeedsMoreSignaturesError extends Error { -} -export class WalletDisconnectedError extends Error { -} -export class SendResultOnlyError extends Error { -} -export class SendFailedError extends Error { -} -export class NoUnsignedNonInvokerAuthEntriesError extends Error { -} -export { Address }; -; -; -export class Ok { - value; - constructor(value) { - this.value = value; - } - unwrapErr() { - throw new Error('No error'); - } - unwrap() { - return this.value; - } - isOk() { - return true; - } - isErr() { - return !this.isOk(); - } -} -export class Err { - error; - constructor(error) { - this.error = error; - } - unwrapErr() { - return this.error; - } - unwrap() { - throw new Error(this.error.message); - } - isOk() { - return false; - } - isErr() { - return !this.isOk(); - } -} -export const contractErrorPattern = /Error\(Contract, #(\d+)\)/; -export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; -export class AssembledTransaction { - options; - raw; - simulation; - simulationResult; - simulationTransactionData; - server; - toJSON() { - return JSON.stringify({ - method: this.options.method, - tx: this.raw?.toXDR(), - simulationResult: { - auth: this.simulationData.result.auth.map(a => a.toXDR('base64')), - retval: this.simulationData.result.retval.toXDR('base64'), - }, - simulationTransactionData: this.simulationData.transactionData.toXDR('base64'), - }); - } - static fromJSON(options, { tx, simulationResult, simulationTransactionData }) { - const txn = new AssembledTransaction(options); - txn.raw = TransactionBuilder.fromXDR(tx, options.networkPassphrase); - txn.simulationResult = { - auth: simulationResult.auth.map(a => xdr.SorobanAuthorizationEntry.fromXDR(a, 'base64')), - retval: xdr.ScVal.fromXDR(simulationResult.retval, 'base64'), - }; - txn.simulationTransactionData = xdr.SorobanTransactionData.fromXDR(simulationTransactionData, 'base64'); - return txn; - } - constructor(options) { - this.options = options; - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - } - static async fromSimulation(options) { - const tx = new AssembledTransaction(options); - const contract = new Contract(options.contractId); - tx.raw = new TransactionBuilder(await tx.getAccount(), { - fee: options.fee?.toString(10) ?? BASE_FEE, - networkPassphrase: options.networkPassphrase, - }) - .addOperation(contract.call(options.method, ...(options.args ?? []))) - .setTimeout(TimeoutInfinite) - .build(); - return await tx.simulate(); - } - simulate = async () => { - if (!this.raw) - throw new Error('Transaction has not yet been assembled'); - this.simulation = await this.server.simulateTransaction(this.raw); - if (SorobanRpc.Api.isSimulationSuccess(this.simulation)) { - this.raw = SorobanRpc.assembleTransaction(this.raw, this.simulation).build(); - } - return this; - }; - get simulationData() { - if (this.simulationResult && this.simulationTransactionData) { - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - }; - } - // else, we know we just did the simulation on this machine - const simulation = this.simulation; - if (SorobanRpc.Api.isSimulationError(simulation)) { - throw new Error(`Transaction simulation failed: "${simulation.error}"`); - } - if (SorobanRpc.Api.isSimulationRestore(simulation)) { - throw new ExpiredStateError(`You need to restore some contract state before you can invoke this method. ${JSON.stringify(simulation, null, 2)}`); - } - if (!simulation.result) { - throw new Error(`Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify(simulation, null, 2)}`); - } - // add to object for serialization & deserialization - this.simulationResult = simulation.result; - this.simulationTransactionData = simulation.transactionData.build(); - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - }; - } - get result() { - try { - return this.options.parseResultXdr(this.simulationData.result.retval); - } - catch (e) { - let err = this.parseError(e.toString()); - if (err) - return err; - throw e; - } - } - parseError(errorMessage) { - if (!this.options.errorTypes) - return; - const match = errorMessage.match(contractErrorPattern); - if (!match) - return; - let i = parseInt(match[1], 10); - let err = this.options.errorTypes[i]; - if (err) - return new Err(err); - } - getWallet = async () => { - return this.options.wallet ?? (await import("@stellar/freighter-api")).default; - }; - getPublicKey = async () => { - const wallet = await this.getWallet(); - if (await wallet.isConnected() && await wallet.isAllowed()) { - return (await wallet.getUserInfo()).publicKey; - } - }; - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount = async () => { - const publicKey = await this.getPublicKey(); - return publicKey - ? await this.server.getAccount(publicKey) - : new Account(NULL_ACCOUNT, "0"); - }; - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend = async ({ secondsToWait = 10, force = false } = {}) => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated'); - } - if (!force && this.isReadCall) { - throw new Error('This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway.'); - } - if (!await this.hasRealInvoker()) { - throw new WalletDisconnectedError('Wallet is not connected'); - } - if (this.raw.source !== (await this.getAccount()).accountId()) { - throw new Error(`You must submit the transaction with the account that originally created it. Please switch to the wallet with "${this.raw.source}" as its public key.`); - } - if ((await this.needsNonInvokerSigningBy()).length) { - throw new NeedsMoreSignaturesError('Transaction requires more signatures. See `needsNonInvokerSigningBy` for details.'); - } - return await SentTransaction.init(this.options, this, secondsToWait); - }; - getStorageExpiration = async () => { - const entryRes = await this.server.getLedgerEntries(new Contract(this.options.contractId).getFootprint()); - if (!entryRes.entries || - !entryRes.entries.length || - !entryRes.entries[0].liveUntilLedgerSeq) - throw new Error('failed to get ledger entry'); - return entryRes.entries[0].liveUntilLedgerSeq; - }; - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy = async ({ includeAlreadySigned = false, } = {}) => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated'); - } - // We expect that any transaction constructed by these libraries has a - // single operation, which is an InvokeHostFunction operation. The host - // function being invoked is the contract method call. - if (!("operations" in this.raw)) { - throw new Error(`Unexpected Transaction type; no operations: ${JSON.stringify(this.raw)}`); - } - const rawInvokeHostFunctionOp = this.raw - .operations[0]; - return [...new Set((rawInvokeHostFunctionOp.auth ?? []).filter(entry => entry.credentials().switch() === - xdr.SorobanCredentialsType.sorobanCredentialsAddress() && - (includeAlreadySigned || - entry.credentials().address().signature().switch().name === 'scvVoid')).map(entry => StrKey.encodeEd25519PublicKey(entry.credentials().address().address().accountId().ed25519())))]; - }; - preImageFor(entry, signatureExpirationLedger) { - const addrAuth = entry.credentials().address(); - return xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(new xdr.HashIdPreimageSorobanAuthorization({ - networkId: hash(Buffer.from(this.options.networkPassphrase)), - nonce: addrAuth.nonce(), - invocation: entry.rootInvocation(), - signatureExpirationLedger, - })); - } - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries = async ( - /** - * When to set each auth entry to expire. Could be any number of blocks in - * the future. Can be supplied as a promise or a raw number. Default: - * contract's current `persistent` storage expiration date/ledger - * number/block. - */ - expiration = this.getStorageExpiration()) => { - if (!this.raw) - throw new Error('Transaction has not yet been assembled or simulated'); - const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy(); - if (!needsNonInvokerSigningBy) - throw new NoUnsignedNonInvokerAuthEntriesError('No unsigned non-invoker auth entries; maybe you already signed?'); - const publicKey = await this.getPublicKey(); - if (!publicKey) - throw new Error('Could not get public key from wallet; maybe Freighter is not signed in?'); - if (needsNonInvokerSigningBy.indexOf(publicKey) === -1) - throw new Error(`No auth entries for public key "${publicKey}"`); - const wallet = await this.getWallet(); - const rawInvokeHostFunctionOp = this.raw - .operations[0]; - const authEntries = rawInvokeHostFunctionOp.auth ?? []; - for (const [i, entry] of authEntries.entries()) { - if (entry.credentials().switch() !== - xdr.SorobanCredentialsType.sorobanCredentialsAddress()) { - // if the invoker/source account, then the entry doesn't need explicit - // signature, since the tx envelope is already signed by the source - // account, so only check for sorobanCredentialsAddress - continue; - } - const pk = StrKey.encodeEd25519PublicKey(entry.credentials().address().address().accountId().ed25519()); - // this auth entry needs to be signed by a different account - // (or maybe already was!) - if (pk !== publicKey) - continue; - authEntries[i] = await authorizeEntry(entry, async (preimage) => Buffer.from(await wallet.signAuthEntry(preimage.toXDR('base64')), 'base64'), await expiration, this.options.networkPassphrase); - } - }; - get isReadCall() { - const authsCount = this.simulationData.result.auth.length; - const writeLength = this.simulationData.transactionData.resources().footprint().readWrite().length; - return (authsCount === 0) && (writeLength === 0); - } - hasRealInvoker = async () => { - const account = await this.getAccount(); - return account.accountId() !== NULL_ACCOUNT; - }; -} -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -class SentTransaction { - options; - assembled; - server; - signed; - sendTransactionResponse; - sendTransactionResponseAll; - getTransactionResponse; - getTransactionResponseAll; - constructor(options, assembled) { - this.options = options; - this.assembled = assembled; - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - this.assembled = assembled; - } - static init = async (options, assembled, secondsToWait = 10) => { - const tx = new SentTransaction(options, assembled); - return await tx.send(secondsToWait); - }; - send = async (secondsToWait = 10) => { - const wallet = await this.assembled.getWallet(); - this.sendTransactionResponseAll = await withExponentialBackoff(async (previousFailure) => { - if (previousFailure) { - // Increment transaction sequence number and resimulate before trying again - // Soroban transaction can only have 1 operation - const op = this.assembled.raw.operations[0]; - this.assembled.raw = new TransactionBuilder(await this.assembled.getAccount(), { - fee: this.assembled.raw.fee, - networkPassphrase: this.options.networkPassphrase, - }) - .setTimeout(TimeoutInfinite) - .addOperation(Operation.invokeHostFunction({ ...op, auth: op.auth ?? [] })) - .build(); - await this.assembled.simulate(); - } - const signature = await wallet.signTransaction(this.assembled.raw.toXDR(), { - networkPassphrase: this.options.networkPassphrase, - }); - this.signed = TransactionBuilder.fromXDR(signature, this.options.networkPassphrase); - return this.server.sendTransaction(this.signed); - }, resp => resp.status !== "PENDING", secondsToWait); - this.sendTransactionResponse = this.sendTransactionResponseAll[this.sendTransactionResponseAll.length - 1]; - if (this.sendTransactionResponse.status !== "PENDING") { - throw new Error(`Tried to resubmit transaction for ${secondsToWait} seconds, but it's still failing. ` + - `All attempts: ${JSON.stringify(this.sendTransactionResponseAll, null, 2)}`); - } - const { hash } = this.sendTransactionResponse; - this.getTransactionResponseAll = await withExponentialBackoff(() => this.server.getTransaction(hash), resp => resp.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, secondsToWait); - this.getTransactionResponse = this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1]; - if (this.getTransactionResponse.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND) { - console.error(`Waited ${secondsToWait} seconds for transaction to complete, but it did not. ` + - `Returning anyway. Check the transaction status manually. ` + - `Sent transaction: ${JSON.stringify(this.sendTransactionResponse, null, 2)}\n` + - `All attempts to get the result: ${JSON.stringify(this.getTransactionResponseAll, null, 2)}`); - } - return this; - }; - get result() { - // 1. check if transaction was submitted and awaited with `getTransaction` - if ("getTransactionResponse" in this && - this.getTransactionResponse) { - // getTransactionResponse has a `returnValue` field unless it failed - if ("returnValue" in this.getTransactionResponse) { - return this.options.parseResultXdr(this.getTransactionResponse.returnValue); - } - // if "returnValue" not present, the transaction failed; return without parsing the result - throw new Error("Transaction failed! Cannot parse result."); - } - // 2. otherwise, maybe it was merely sent with `sendTransaction` - if (this.sendTransactionResponse) { - const errorResult = this.sendTransactionResponse.errorResult?.result(); - if (errorResult) { - throw new SendFailedError(`Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResult}`); - } - throw new SendResultOnlyError(`Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\``); - } - // 3. finally, if neither of those are present, throw an error - throw new Error(`Sending transaction failed: ${JSON.stringify(this.assembled)}`); - } -} -/** - * Keep calling a `fn` for `secondsToWait` seconds, if `keepWaitingIf` is true. - * Returns an array of all attempts to call the function. - */ -async function withExponentialBackoff(fn, keepWaitingIf, secondsToWait, exponentialFactor = 1.5, verbose = false) { - const attempts = []; - let count = 0; - attempts.push(await fn()); - if (!keepWaitingIf(attempts[attempts.length - 1])) - return attempts; - const waitUntil = new Date(Date.now() + secondsToWait * 1000).valueOf(); - let waitTime = 1000; - let totalWaitTime = waitTime; - while (Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1])) { - count++; - // Wait a beat - if (verbose) { - console.info(`Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${secondsToWait * 1000}ms)`); - } - await new Promise(res => setTimeout(res, waitTime)); - // Exponential backoff - waitTime = waitTime * exponentialFactor; - if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { - waitTime = waitUntil - Date.now(); - if (verbose) { - console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`); - } - } - totalWaitTime = waitTime + totalWaitTime; - // Try again - attempts.push(await fn(attempts[attempts.length - 1])); - if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { - console.info(`${count}. Called ${fn}; ${attempts.length} prev attempts. Most recent: ${JSON.stringify(attempts[attempts.length - 1], null, 2)}`); - } - } - return attempts; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.d.ts deleted file mode 100644 index 66d3d595..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.d.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { ContractSpec } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -import type { u32, i32, i64, i128, Option, Error_ } from './assembled-tx.js'; -import type { ClassOptions } from './method-options.js'; -export * from './assembled-tx.js'; -export * from './method-options.js'; -export declare const networks: { - readonly futurenet: { - readonly networkPassphrase: "Test SDF Future Network ; October 2022"; - readonly contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK"; - }; -}; -/** - This is from the rust doc above the struct Test - */ -export interface Test { - /** - - */ - a: u32; - /** - - */ - b: boolean; - /** - - */ - c: string; -} -/** - - */ -export type SimpleEnum = { - tag: "First"; - values: void; -} | { - tag: "Second"; - values: void; -} | { - tag: "Third"; - values: void; -}; -/** - - */ -export declare enum RoyalCard { - Jack = 11, - Queen = 12, - King = 13 -} -/** - - */ -export type TupleStruct = readonly [Test, SimpleEnum]; -/** - - */ -export type ComplexEnum = { - tag: "Struct"; - values: readonly [Test]; -} | { - tag: "Tuple"; - values: readonly [TupleStruct]; -} | { - tag: "Enum"; - values: readonly [SimpleEnum]; -} | { - tag: "Asset"; - values: readonly [string, i128]; -} | { - tag: "Void"; - values: void; -}; -/** - - */ -export declare const Errors: { - 1: { - message: string; - }; -}; -export declare class Contract { - readonly options: ClassOptions; - spec: ContractSpec; - constructor(options: ClassOptions); - private readonly parsers; - private txFromJSON; - readonly fromJSON: { - hello: (json: string) => AssembledTransaction; - woid: (json: string) => AssembledTransaction; - val: (json: string) => AssembledTransaction; - u32FailOnEven: (json: string) => AssembledTransaction | Ok>; - u32: (json: string) => AssembledTransaction; - i32: (json: string) => AssembledTransaction; - i64: (json: string) => AssembledTransaction; - struktHel: (json: string) => AssembledTransaction; - strukt: (json: string) => AssembledTransaction; - simple: (json: string) => AssembledTransaction; - complex: (json: string) => AssembledTransaction; - addresse: (json: string) => AssembledTransaction; - bytes: (json: string) => AssembledTransaction; - bytesN: (json: string) => AssembledTransaction; - card: (json: string) => AssembledTransaction; - boolean: (json: string) => AssembledTransaction; - not: (json: string) => AssembledTransaction; - i128: (json: string) => AssembledTransaction; - u128: (json: string) => AssembledTransaction; - multiArgs: (json: string) => AssembledTransaction; - map: (json: string) => AssembledTransaction>; - vec: (json: string) => AssembledTransaction; - tuple: (json: string) => AssembledTransaction; - option: (json: string) => AssembledTransaction>; - u256: (json: string) => AssembledTransaction; - i256: (json: string) => AssembledTransaction; - string: (json: string) => AssembledTransaction; - tupleStrukt: (json: string) => AssembledTransaction; - }; - /** -* Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - hello: ({ hello }: { - hello: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - woid: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - val: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32FailOnEven: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise | Ok>>; - /** -* Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i32: ({ i32_ }: { - i32_: i32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i64: ({ i64_ }: { - i64_: i64; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct -*/ - struktHel: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - strukt: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - simple: ({ simple }: { - simple: SimpleEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - complex: ({ complex }: { - complex: ComplexEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - addresse: ({ addresse }: { - addresse: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytes: ({ bytes }: { - bytes: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytesN: ({ bytes_n }: { - bytes_n: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - card: ({ card }: { - card: RoyalCard; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - boolean: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value -*/ - not: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i128: ({ i128 }: { - i128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u128: ({ u128 }: { - u128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - multiArgs: ({ a, b }: { - a: u32; - b: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - map: ({ map }: { - map: Map; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - vec: ({ vec }: { - vec: Array; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tuple: ({ tuple }: { - tuple: readonly [string, u32]; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument -*/ - option: ({ option }: { - option: Option; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u256: ({ u256 }: { - u256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i256: ({ i256 }: { - i256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - string: ({ string }: { - string: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tupleStrukt: ({ tuple_strukt }: { - tuple_strukt: TupleStruct; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.js deleted file mode 100644 index b97cff55..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/index.js +++ /dev/null @@ -1,509 +0,0 @@ -import { ContractSpec, Address } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -export * from './assembled-tx.js'; -export * from './method-options.js'; -if (typeof window !== 'undefined') { - //@ts-ignore Buffer exists - window.Buffer = window.Buffer || Buffer; -} -export const networks = { - futurenet: { - networkPassphrase: "Test SDF Future Network ; October 2022", - contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK", - } -}; -/** - - */ -export var RoyalCard; -(function (RoyalCard) { - RoyalCard[RoyalCard["Jack"] = 11] = "Jack"; - RoyalCard[RoyalCard["Queen"] = 12] = "Queen"; - RoyalCard[RoyalCard["King"] = 13] = "King"; -})(RoyalCard || (RoyalCard = {})); -/** - - */ -export const Errors = { - 1: { message: "Please provide an odd number" } -}; -export class Contract { - options; - spec; - constructor(options) { - this.options = options; - this.spec = new ContractSpec([ - "AAAAAQAAAC9UaGlzIGlzIGZyb20gdGhlIHJ1c3QgZG9jIGFib3ZlIHRoZSBzdHJ1Y3QgVGVzdAAAAAAAAAAABFRlc3QAAAADAAAAAAAAAAFhAAAAAAAABAAAAAAAAAABYgAAAAAAAAEAAAAAAAAAAWMAAAAAAAAR", - "AAAAAgAAAAAAAAAAAAAAClNpbXBsZUVudW0AAAAAAAMAAAAAAAAAAAAAAAVGaXJzdAAAAAAAAAAAAAAAAAAABlNlY29uZAAAAAAAAAAAAAAAAAAFVGhpcmQAAAA=", - "AAAAAwAAAAAAAAAAAAAACVJveWFsQ2FyZAAAAAAAAAMAAAAAAAAABEphY2sAAAALAAAAAAAAAAVRdWVlbgAAAAAAAAwAAAAAAAAABEtpbmcAAAAN", - "AAAAAQAAAAAAAAAAAAAAC1R1cGxlU3RydWN0AAAAAAIAAAAAAAAAATAAAAAAAAfQAAAABFRlc3QAAAAAAAAAATEAAAAAAAfQAAAAClNpbXBsZUVudW0AAA==", - "AAAAAgAAAAAAAAAAAAAAC0NvbXBsZXhFbnVtAAAAAAUAAAABAAAAAAAAAAZTdHJ1Y3QAAAAAAAEAAAfQAAAABFRlc3QAAAABAAAAAAAAAAVUdXBsZQAAAAAAAAEAAAfQAAAAC1R1cGxlU3RydWN0AAAAAAEAAAAAAAAABEVudW0AAAABAAAH0AAAAApTaW1wbGVFbnVtAAAAAAABAAAAAAAAAAVBc3NldAAAAAAAAAIAAAATAAAACwAAAAAAAAAAAAAABFZvaWQ=", - "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAAQAAABxQbGVhc2UgcHJvdmlkZSBhbiBvZGQgbnVtYmVyAAAAD051bWJlck11c3RCZU9kZAAAAAAB", - "AAAAAAAAAAAAAAAFaGVsbG8AAAAAAAABAAAAAAAAAAVoZWxsbwAAAAAAABEAAAABAAAAEQ==", - "AAAAAAAAAAAAAAAEd29pZAAAAAAAAAAA", - "AAAAAAAAAAAAAAADdmFsAAAAAAAAAAABAAAAAA==", - "AAAAAAAAAAAAAAAQdTMyX2ZhaWxfb25fZXZlbgAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAA+kAAAAEAAAAAw==", - "AAAAAAAAAAAAAAAEdTMyXwAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAAEaTMyXwAAAAEAAAAAAAAABGkzMl8AAAAFAAAAAQAAAAU=", - "AAAAAAAAAAAAAAAEaTY0XwAAAAEAAAAAAAAABGk2NF8AAAAHAAAAAQAAAAc=", - "AAAAAAAAACxFeGFtcGxlIGNvbnRyYWN0IG1ldGhvZCB3aGljaCB0YWtlcyBhIHN0cnVjdAAAAApzdHJ1a3RfaGVsAAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAPqAAAAEQ==", - "AAAAAAAAAAAAAAAGc3RydWt0AAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAfQAAAABFRlc3Q=", - "AAAAAAAAAAAAAAAGc2ltcGxlAAAAAAABAAAAAAAAAAZzaW1wbGUAAAAAB9AAAAAKU2ltcGxlRW51bQAAAAAAAQAAB9AAAAAKU2ltcGxlRW51bQAA", - "AAAAAAAAAAAAAAAHY29tcGxleAAAAAABAAAAAAAAAAdjb21wbGV4AAAAB9AAAAALQ29tcGxleEVudW0AAAAAAQAAB9AAAAALQ29tcGxleEVudW0A", - "AAAAAAAAAAAAAAAIYWRkcmVzc2UAAAABAAAAAAAAAAhhZGRyZXNzZQAAABMAAAABAAAAEw==", - "AAAAAAAAAAAAAAAFYnl0ZXMAAAAAAAABAAAAAAAAAAVieXRlcwAAAAAAAA4AAAABAAAADg==", - "AAAAAAAAAAAAAAAHYnl0ZXNfbgAAAAABAAAAAAAAAAdieXRlc19uAAAAA+4AAAAJAAAAAQAAA+4AAAAJ", - "AAAAAAAAAAAAAAAEY2FyZAAAAAEAAAAAAAAABGNhcmQAAAfQAAAACVJveWFsQ2FyZAAAAAAAAAEAAAfQAAAACVJveWFsQ2FyZAAAAA==", - "AAAAAAAAAAAAAAAHYm9vbGVhbgAAAAABAAAAAAAAAAdib29sZWFuAAAAAAEAAAABAAAAAQ==", - "AAAAAAAAABdOZWdhdGVzIGEgYm9vbGVhbiB2YWx1ZQAAAAADbm90AAAAAAEAAAAAAAAAB2Jvb2xlYW4AAAAAAQAAAAEAAAAB", - "AAAAAAAAAAAAAAAEaTEyOAAAAAEAAAAAAAAABGkxMjgAAAALAAAAAQAAAAs=", - "AAAAAAAAAAAAAAAEdTEyOAAAAAEAAAAAAAAABHUxMjgAAAAKAAAAAQAAAAo=", - "AAAAAAAAAAAAAAAKbXVsdGlfYXJncwAAAAAAAgAAAAAAAAABYQAAAAAAAAQAAAAAAAAAAWIAAAAAAAABAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAADbWFwAAAAAAEAAAAAAAAAA21hcAAAAAPsAAAABAAAAAEAAAABAAAD7AAAAAQAAAAB", - "AAAAAAAAAAAAAAADdmVjAAAAAAEAAAAAAAAAA3ZlYwAAAAPqAAAABAAAAAEAAAPqAAAABA==", - "AAAAAAAAAAAAAAAFdHVwbGUAAAAAAAABAAAAAAAAAAV0dXBsZQAAAAAAA+0AAAACAAAAEQAAAAQAAAABAAAD7QAAAAIAAAARAAAABA==", - "AAAAAAAAAB9FeGFtcGxlIG9mIGFuIG9wdGlvbmFsIGFyZ3VtZW50AAAAAAZvcHRpb24AAAAAAAEAAAAAAAAABm9wdGlvbgAAAAAD6AAAAAQAAAABAAAD6AAAAAQ=", - "AAAAAAAAAAAAAAAEdTI1NgAAAAEAAAAAAAAABHUyNTYAAAAMAAAAAQAAAAw=", - "AAAAAAAAAAAAAAAEaTI1NgAAAAEAAAAAAAAABGkyNTYAAAANAAAAAQAAAA0=", - "AAAAAAAAAAAAAAAGc3RyaW5nAAAAAAABAAAAAAAAAAZzdHJpbmcAAAAAABAAAAABAAAAEA==", - "AAAAAAAAAAAAAAAMdHVwbGVfc3RydWt0AAAAAQAAAAAAAAAMdHVwbGVfc3RydWt0AAAH0AAAAAtUdXBsZVN0cnVjdAAAAAABAAAH0AAAAAtUdXBsZVN0cnVjdAA=" - ]); - } - parsers = { - hello: (result) => this.spec.funcResToNative("hello", result), - woid: () => { }, - val: (result) => this.spec.funcResToNative("val", result), - u32FailOnEven: (result) => { - if (result instanceof Err) - return result; - return new Ok(this.spec.funcResToNative("u32_fail_on_even", result)); - }, - u32: (result) => this.spec.funcResToNative("u32_", result), - i32: (result) => this.spec.funcResToNative("i32_", result), - i64: (result) => this.spec.funcResToNative("i64_", result), - struktHel: (result) => this.spec.funcResToNative("strukt_hel", result), - strukt: (result) => this.spec.funcResToNative("strukt", result), - simple: (result) => this.spec.funcResToNative("simple", result), - complex: (result) => this.spec.funcResToNative("complex", result), - addresse: (result) => this.spec.funcResToNative("addresse", result), - bytes: (result) => this.spec.funcResToNative("bytes", result), - bytesN: (result) => this.spec.funcResToNative("bytes_n", result), - card: (result) => this.spec.funcResToNative("card", result), - boolean: (result) => this.spec.funcResToNative("boolean", result), - not: (result) => this.spec.funcResToNative("not", result), - i128: (result) => this.spec.funcResToNative("i128", result), - u128: (result) => this.spec.funcResToNative("u128", result), - multiArgs: (result) => this.spec.funcResToNative("multi_args", result), - map: (result) => this.spec.funcResToNative("map", result), - vec: (result) => this.spec.funcResToNative("vec", result), - tuple: (result) => this.spec.funcResToNative("tuple", result), - option: (result) => this.spec.funcResToNative("option", result), - u256: (result) => this.spec.funcResToNative("u256", result), - i256: (result) => this.spec.funcResToNative("i256", result), - string: (result) => this.spec.funcResToNative("string", result), - tupleStrukt: (result) => this.spec.funcResToNative("tuple_strukt", result) - }; - txFromJSON = (json) => { - const { method, ...tx } = JSON.parse(json); - return AssembledTransaction.fromJSON({ - ...this.options, - method, - parseResultXdr: this.parsers[method], - }, tx); - }; - fromJSON = { - hello: (this.txFromJSON), - woid: (this.txFromJSON), - val: (this.txFromJSON), - u32FailOnEven: (this.txFromJSON), - u32: (this.txFromJSON), - i32: (this.txFromJSON), - i64: (this.txFromJSON), - struktHel: (this.txFromJSON), - strukt: (this.txFromJSON), - simple: (this.txFromJSON), - complex: (this.txFromJSON), - addresse: (this.txFromJSON), - bytes: (this.txFromJSON), - bytesN: (this.txFromJSON), - card: (this.txFromJSON), - boolean: (this.txFromJSON), - not: (this.txFromJSON), - i128: (this.txFromJSON), - u128: (this.txFromJSON), - multiArgs: (this.txFromJSON), - map: (this.txFromJSON), - vec: (this.txFromJSON), - tuple: (this.txFromJSON), - option: (this.txFromJSON), - u256: (this.txFromJSON), - i256: (this.txFromJSON), - string: (this.txFromJSON), - tupleStrukt: (this.txFromJSON) - }; - /** -* Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - hello = async ({ hello }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'hello', - args: this.spec.funcArgsToScVals("hello", { hello }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['hello'], - }); - }; - /** -* Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - woid = async (options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'woid', - args: this.spec.funcArgsToScVals("woid", {}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['woid'], - }); - }; - /** -* Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - val = async (options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'val', - args: this.spec.funcArgsToScVals("val", {}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['val'], - }); - }; - /** -* Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32FailOnEven = async ({ u32_ }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u32_fail_on_even', - args: this.spec.funcArgsToScVals("u32_fail_on_even", { u32_ }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u32FailOnEven'], - }); - }; - /** -* Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32 = async ({ u32_ }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u32_', - args: this.spec.funcArgsToScVals("u32_", { u32_ }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u32'], - }); - }; - /** -* Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i32 = async ({ i32_ }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i32_', - args: this.spec.funcArgsToScVals("i32_", { i32_ }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i32'], - }); - }; - /** -* Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i64 = async ({ i64_ }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i64_', - args: this.spec.funcArgsToScVals("i64_", { i64_ }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i64'], - }); - }; - /** -* Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct -*/ - struktHel = async ({ strukt }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'strukt_hel', - args: this.spec.funcArgsToScVals("strukt_hel", { strukt }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['struktHel'], - }); - }; - /** -* Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - strukt = async ({ strukt }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'strukt', - args: this.spec.funcArgsToScVals("strukt", { strukt }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['strukt'], - }); - }; - /** -* Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - simple = async ({ simple }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'simple', - args: this.spec.funcArgsToScVals("simple", { simple }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['simple'], - }); - }; - /** -* Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - complex = async ({ complex }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'complex', - args: this.spec.funcArgsToScVals("complex", { complex }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['complex'], - }); - }; - /** -* Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - addresse = async ({ addresse }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'addresse', - args: this.spec.funcArgsToScVals("addresse", { addresse: new Address(addresse) }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['addresse'], - }); - }; - /** -* Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytes = async ({ bytes }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'bytes', - args: this.spec.funcArgsToScVals("bytes", { bytes }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['bytes'], - }); - }; - /** -* Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytesN = async ({ bytes_n }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'bytes_n', - args: this.spec.funcArgsToScVals("bytes_n", { bytes_n }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['bytesN'], - }); - }; - /** -* Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - card = async ({ card }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'card', - args: this.spec.funcArgsToScVals("card", { card }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['card'], - }); - }; - /** -* Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - boolean = async ({ boolean }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'boolean', - args: this.spec.funcArgsToScVals("boolean", { boolean }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['boolean'], - }); - }; - /** -* Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value -*/ - not = async ({ boolean }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'not', - args: this.spec.funcArgsToScVals("not", { boolean }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['not'], - }); - }; - /** -* Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i128 = async ({ i128 }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i128', - args: this.spec.funcArgsToScVals("i128", { i128 }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i128'], - }); - }; - /** -* Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u128 = async ({ u128 }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u128', - args: this.spec.funcArgsToScVals("u128", { u128 }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u128'], - }); - }; - /** -* Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - multiArgs = async ({ a, b }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'multi_args', - args: this.spec.funcArgsToScVals("multi_args", { a, b }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['multiArgs'], - }); - }; - /** -* Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - map = async ({ map }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'map', - args: this.spec.funcArgsToScVals("map", { map }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['map'], - }); - }; - /** -* Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - vec = async ({ vec }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'vec', - args: this.spec.funcArgsToScVals("vec", { vec }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['vec'], - }); - }; - /** -* Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tuple = async ({ tuple }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'tuple', - args: this.spec.funcArgsToScVals("tuple", { tuple }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['tuple'], - }); - }; - /** -* Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument -*/ - option = async ({ option }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'option', - args: this.spec.funcArgsToScVals("option", { option }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['option'], - }); - }; - /** -* Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u256 = async ({ u256 }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u256', - args: this.spec.funcArgsToScVals("u256", { u256 }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u256'], - }); - }; - /** -* Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i256 = async ({ i256 }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i256', - args: this.spec.funcArgsToScVals("i256", { i256 }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i256'], - }); - }; - /** -* Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - string = async ({ string }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'string', - args: this.spec.funcArgsToScVals("string", { string }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['string'], - }); - }; - /** -* Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tupleStrukt = async ({ tuple_strukt }, options = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'tuple_strukt', - args: this.spec.funcArgsToScVals("tuple_strukt", { tuple_strukt }), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['tupleStrukt'], - }); - }; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.d.ts deleted file mode 100644 index fc6b21d5..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -declare let responseTypes: 'simulated' | 'full' | undefined; -export type ResponseTypes = typeof responseTypes; -export type XDR_BASE64 = string; -export interface Wallet { - isConnected: () => Promise; - isAllowed: () => Promise; - getUserInfo: () => Promise<{ - publicKey?: string; - }>; - signTransaction: (tx: XDR_BASE64, opts?: { - network?: string; - networkPassphrase?: string; - accountToSign?: string; - }) => Promise; - signAuthEntry: (entryXdr: XDR_BASE64, opts?: { - accountToSign?: string; - }) => Promise; -} -export type ClassOptions = { - contractId: string; - networkPassphrase: string; - rpcUrl: string; - errorTypes?: Record; - /** - * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: - * - * @example - * ```ts - * import freighter from "@stellar/freighter-api"; - * import { Contract } from "test_custom_types"; - * const contract = new Contract({ - * …, - * wallet: freighter, - * }) - * ``` - */ - wallet?: Wallet; -}; -export type MethodOptions = { - /** - * The fee to pay for the transaction. Default: soroban-sdk's BASE_FEE ('100') - */ - fee?: number; -}; -export {}; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.js deleted file mode 100644 index 00ad9d3c..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/method-options.js +++ /dev/null @@ -1,3 +0,0 @@ -// defined this way so typeahead shows full union, not named alias -let responseTypes; -export {}; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/package.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/package.json deleted file mode 100644 index 1632c2c4..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/esm/package.json +++ /dev/null @@ -1 +0,0 @@ -{"type": "module"} \ No newline at end of file diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/assembled-tx.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/assembled-tx.d.ts deleted file mode 100644 index 1d5e6f5e..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/assembled-tx.d.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Account, Address, Operation, SorobanRpc, xdr } from "@stellar/stellar-sdk"; -import type { Memo, MemoType, Transaction } from "@stellar/stellar-sdk"; -import type { ClassOptions, MethodOptions, Wallet, XDR_BASE64 } from "./method-options.js"; -export type Tx = Transaction, Operation[]>; -export declare class ExpiredStateError extends Error { -} -export declare class NeedsMoreSignaturesError extends Error { -} -export declare class WalletDisconnectedError extends Error { -} -export declare class SendResultOnlyError extends Error { -} -export declare class SendFailedError extends Error { -} -export declare class NoUnsignedNonInvokerAuthEntriesError extends Error { -} -type SendTx = SorobanRpc.Api.SendTransactionResponse; -type GetTx = SorobanRpc.Api.GetTransactionResponse; -export type u32 = number; -export type i32 = number; -export type u64 = bigint; -export type i64 = bigint; -export type u128 = bigint; -export type i128 = bigint; -export type u256 = bigint; -export type i256 = bigint; -export type Option = T | undefined; -export type Typepoint = bigint; -export type Duration = bigint; -export { Address }; -export interface Error_ { - message: string; -} -export interface Result { - unwrap(): T; - unwrapErr(): E; - isOk(): boolean; - isErr(): boolean; -} -export declare class Ok implements Result { - readonly value: T; - constructor(value: T); - unwrapErr(): E; - unwrap(): T; - isOk(): boolean; - isErr(): boolean; -} -export declare class Err implements Result { - readonly error: E; - constructor(error: E); - unwrapErr(): E; - unwrap(): never; - isOk(): boolean; - isErr(): boolean; -} -export declare const contractErrorPattern: RegExp; -type AssembledTransactionOptions = MethodOptions & ClassOptions & { - method: string; - args?: any[]; - parseResultXdr: (xdr: string | xdr.ScVal | Err) => T; -}; -export declare const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; -export declare class AssembledTransaction { - options: AssembledTransactionOptions; - raw: Tx; - private simulation?; - private simulationResult?; - private simulationTransactionData?; - private server; - toJSON(): string; - static fromJSON(options: Omit, 'args'>, { tx, simulationResult, simulationTransactionData }: { - tx: XDR_BASE64; - simulationResult: { - auth: XDR_BASE64[]; - retval: XDR_BASE64; - }; - simulationTransactionData: XDR_BASE64; - }): AssembledTransaction; - private constructor(); - static fromSimulation(options: AssembledTransactionOptions): Promise>; - simulate: () => Promise; - get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult; - transactionData: xdr.SorobanTransactionData; - }; - get result(): T; - parseError(errorMessage: string): Err | undefined; - getWallet: () => Promise; - getPublicKey: () => Promise; - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount: () => Promise; - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend: ({ secondsToWait, force }?: { - /** - * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). - */ - secondsToWait?: number | undefined; - /** - * If `true`, sign and send the transaction even if it is a read call. - */ - force?: boolean | undefined; - }) => Promise>; - getStorageExpiration: () => Promise; - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy: ({ includeAlreadySigned, }?: { - /** - * Whether or not to include auth entries that have already been signed. Default: false - */ - includeAlreadySigned?: boolean | undefined; - }) => Promise; - preImageFor(entry: xdr.SorobanAuthorizationEntry, signatureExpirationLedger: number): xdr.HashIdPreimage; - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries: (expiration?: number | Promise) => Promise; - get isReadCall(): boolean; - hasRealInvoker: () => Promise; -} -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -declare class SentTransaction { - options: AssembledTransactionOptions; - assembled: AssembledTransaction; - server: SorobanRpc.Server; - signed: Tx; - sendTransactionResponse?: SendTx; - sendTransactionResponseAll?: SendTx[]; - getTransactionResponse?: GetTx; - getTransactionResponseAll?: GetTx[]; - constructor(options: AssembledTransactionOptions, assembled: AssembledTransaction); - static init: (options: AssembledTransactionOptions, assembled: AssembledTransaction, secondsToWait?: number) => Promise>; - private send; - get result(): T; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/index.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/index.d.ts deleted file mode 100644 index 66d3d595..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/index.d.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { ContractSpec } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -import type { u32, i32, i64, i128, Option, Error_ } from './assembled-tx.js'; -import type { ClassOptions } from './method-options.js'; -export * from './assembled-tx.js'; -export * from './method-options.js'; -export declare const networks: { - readonly futurenet: { - readonly networkPassphrase: "Test SDF Future Network ; October 2022"; - readonly contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK"; - }; -}; -/** - This is from the rust doc above the struct Test - */ -export interface Test { - /** - - */ - a: u32; - /** - - */ - b: boolean; - /** - - */ - c: string; -} -/** - - */ -export type SimpleEnum = { - tag: "First"; - values: void; -} | { - tag: "Second"; - values: void; -} | { - tag: "Third"; - values: void; -}; -/** - - */ -export declare enum RoyalCard { - Jack = 11, - Queen = 12, - King = 13 -} -/** - - */ -export type TupleStruct = readonly [Test, SimpleEnum]; -/** - - */ -export type ComplexEnum = { - tag: "Struct"; - values: readonly [Test]; -} | { - tag: "Tuple"; - values: readonly [TupleStruct]; -} | { - tag: "Enum"; - values: readonly [SimpleEnum]; -} | { - tag: "Asset"; - values: readonly [string, i128]; -} | { - tag: "Void"; - values: void; -}; -/** - - */ -export declare const Errors: { - 1: { - message: string; - }; -}; -export declare class Contract { - readonly options: ClassOptions; - spec: ContractSpec; - constructor(options: ClassOptions); - private readonly parsers; - private txFromJSON; - readonly fromJSON: { - hello: (json: string) => AssembledTransaction; - woid: (json: string) => AssembledTransaction; - val: (json: string) => AssembledTransaction; - u32FailOnEven: (json: string) => AssembledTransaction | Ok>; - u32: (json: string) => AssembledTransaction; - i32: (json: string) => AssembledTransaction; - i64: (json: string) => AssembledTransaction; - struktHel: (json: string) => AssembledTransaction; - strukt: (json: string) => AssembledTransaction; - simple: (json: string) => AssembledTransaction; - complex: (json: string) => AssembledTransaction; - addresse: (json: string) => AssembledTransaction; - bytes: (json: string) => AssembledTransaction; - bytesN: (json: string) => AssembledTransaction; - card: (json: string) => AssembledTransaction; - boolean: (json: string) => AssembledTransaction; - not: (json: string) => AssembledTransaction; - i128: (json: string) => AssembledTransaction; - u128: (json: string) => AssembledTransaction; - multiArgs: (json: string) => AssembledTransaction; - map: (json: string) => AssembledTransaction>; - vec: (json: string) => AssembledTransaction; - tuple: (json: string) => AssembledTransaction; - option: (json: string) => AssembledTransaction>; - u256: (json: string) => AssembledTransaction; - i256: (json: string) => AssembledTransaction; - string: (json: string) => AssembledTransaction; - tupleStrukt: (json: string) => AssembledTransaction; - }; - /** -* Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - hello: ({ hello }: { - hello: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - woid: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - val: (options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32FailOnEven: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise | Ok>>; - /** -* Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u32: ({ u32_ }: { - u32_: u32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i32: ({ i32_ }: { - i32_: i32; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i64: ({ i64_ }: { - i64_: i64; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct -*/ - struktHel: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - strukt: ({ strukt }: { - strukt: Test; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - simple: ({ simple }: { - simple: SimpleEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - complex: ({ complex }: { - complex: ComplexEnum; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - addresse: ({ addresse }: { - addresse: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytes: ({ bytes }: { - bytes: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - bytesN: ({ bytes_n }: { - bytes_n: Buffer; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - card: ({ card }: { - card: RoyalCard; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - boolean: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value -*/ - not: ({ boolean }: { - boolean: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i128: ({ i128 }: { - i128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u128: ({ u128 }: { - u128: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - multiArgs: ({ a, b }: { - a: u32; - b: boolean; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - map: ({ map }: { - map: Map; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - vec: ({ vec }: { - vec: Array; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tuple: ({ tuple }: { - tuple: readonly [string, u32]; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument -*/ - option: ({ option }: { - option: Option; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>>; - /** -* Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - u256: ({ u256 }: { - u256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - i256: ({ i256 }: { - i256: bigint; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - string: ({ string }: { - string: string; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; - /** -* Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. -*/ - tupleStrukt: ({ tuple_strukt }: { - tuple_strukt: TupleStruct; - }, options?: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number; - }) => Promise>; -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/method-options.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/method-options.d.ts deleted file mode 100644 index fc6b21d5..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/types/method-options.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -declare let responseTypes: 'simulated' | 'full' | undefined; -export type ResponseTypes = typeof responseTypes; -export type XDR_BASE64 = string; -export interface Wallet { - isConnected: () => Promise; - isAllowed: () => Promise; - getUserInfo: () => Promise<{ - publicKey?: string; - }>; - signTransaction: (tx: XDR_BASE64, opts?: { - network?: string; - networkPassphrase?: string; - accountToSign?: string; - }) => Promise; - signAuthEntry: (entryXdr: XDR_BASE64, opts?: { - accountToSign?: string; - }) => Promise; -} -export type ClassOptions = { - contractId: string; - networkPassphrase: string; - rpcUrl: string; - errorTypes?: Record; - /** - * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: - * - * @example - * ```ts - * import freighter from "@stellar/freighter-api"; - * import { Contract } from "test_custom_types"; - * const contract = new Contract({ - * …, - * wallet: freighter, - * }) - * ``` - */ - wallet?: Wallet; -}; -export type MethodOptions = { - /** - * The fee to pay for the transaction. Default: soroban-sdk's BASE_FEE ('100') - */ - fee?: number; -}; -export {}; diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json deleted file mode 100644 index 44332a41..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "name": "test_custom_types", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "test_custom_types", - "version": "0.0.0", - "dependencies": { - "@stellar/freighter-api": "1.7.1", - "@stellar/stellar-sdk": "11.2.0", - "buffer": "6.0.3" - }, - "devDependencies": { - "typescript": "5.3.3" - } - }, - "node_modules/@stellar/freighter-api": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@stellar/freighter-api/-/freighter-api-1.7.1.tgz", - "integrity": "sha512-XvPO+XgEbkeP0VhP0U1edOkds+rGS28+y8GRGbCVXeZ9ZslbWqRFQoETAdX8IXGuykk2ib/aPokiLc5ZaWYP7w==" - }, - "node_modules/@stellar/js-xdr": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.0.1.tgz", - "integrity": "sha512-dp5Eh7Nr1YjiIeqpdkj2cQYxfoPudDAH3ck8MWggp48Htw66Z/hUssNYUQG/OftLjEmHT90Z/dtey2Y77DOxIw==" - }, - "node_modules/@stellar/stellar-base": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-10.0.1.tgz", - "integrity": "sha512-BDbx7VHOEQh+4J3Q+gStNXgPaNckVFmD4aOlBBGwxlF6vPFmVnW8IoJdkX7T58zpX55eWI6DXvEhDBlrqTlhAQ==", - "dependencies": { - "@stellar/js-xdr": "^3.0.1", - "base32.js": "^0.1.0", - "bignumber.js": "^9.1.2", - "buffer": "^6.0.3", - "sha.js": "^2.3.6", - "tweetnacl": "^1.0.3" - }, - "optionalDependencies": { - "sodium-native": "^4.0.1" - } - }, - "node_modules/@stellar/stellar-sdk": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-11.2.0.tgz", - "integrity": "sha512-qInRR+mLLl9O/AI6Q+Sr19RZeYJtlNoJQJi3pch5BYoMvVhjO8IU8AhHADP//Zmc2osyogwPuqXBiFdaGlfHWA==", - "dependencies": { - "@stellar/stellar-base": "10.0.1", - "axios": "^1.6.5", - "bignumber.js": "^9.1.2", - "eventsource": "^2.0.2", - "randombytes": "^2.1.0", - "toml": "^3.0.0", - "urijs": "^1.19.1" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/base32.js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", - "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", - "engines": { - "node": "*" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", - "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/sodium-native": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.5.tgz", - "integrity": "sha512-YGimGhy7Ho6pTAAvuNdn3Tv9C2MD7HP89X1omReHat0Fd1mMnapGqwzb5YoHTAbIEh8tQmKP6+uLlwYCkf+EOA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.6.0" - } - }, - "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/urijs": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", - "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" - } - } -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json deleted file mode 100644 index 1f63968a..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.0.0", - "name": "test_custom_types", - "dependencies": { - "@stellar/freighter-api": "1.7.1", - "buffer": "6.0.3", - "@stellar/stellar-sdk": "11.2.0" - }, - "scripts": { - "build": "node ./scripts/build.mjs" - }, - "exports": { - "require": "./dist/cjs/index.js", - "import": "./dist/esm/index.js" - }, - "typings": "dist/types/index.d.ts", - "devDependencies": { - "typescript": "5.3.3" - } -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/build.mjs b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/build.mjs deleted file mode 100644 index 15a17042..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/build.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import { spawnSync } from "node:child_process" -import fs from "node:fs" -import path from "node:path" - -const buildDir = "./dist" - -const { error, stderr } = spawnSync("tsc", ["-b", "./scripts/tsconfig.cjs.json", "./scripts/tsconfig.esm.json", "./scripts/tsconfig.types.json"], { stdio: "inherit" }) - -if (error) { - console.error(stderr) - console.error(error) - throw error -} - -function createEsmModulePackageJson() { - fs.readdir(buildDir, function (err, dirs) { - if (err) { - throw err - } - dirs.forEach(function (dir) { - if (dir === "esm") { - // 1. add package.json file with "type": "module" - var packageJsonFile = path.join(buildDir, dir, "/package.json") - if (!fs.existsSync(packageJsonFile)) { - fs.writeFileSync( - packageJsonFile, - '{"type": "module"}', - 'utf8', - err => { if (err) throw err } - ) - } - } - }) - }) -} - -createEsmModulePackageJson() diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.cjs.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.cjs.json deleted file mode 100644 index 542ea86d..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/cjs", - "module": "commonjs" - } -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.esm.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.esm.json deleted file mode 100644 index 92b45277..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/esm", - "module": "esnext" - } -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.types.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.types.json deleted file mode 100644 index 8a24fc13..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/scripts/tsconfig.types.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/types", - "declaration": true, - "emitDeclarationOnly": true - } -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/assembled-tx.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/assembled-tx.ts deleted file mode 100644 index f4f892d4..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/assembled-tx.ts +++ /dev/null @@ -1,664 +0,0 @@ -import { - Account, - Address, - Contract, - Operation, - SorobanRpc, - StrKey, - TimeoutInfinite, - TransactionBuilder, - authorizeEntry, - hash, - nativeToScVal, - xdr, - BASE_FEE, -} from "@stellar/stellar-sdk"; -import type { Memo, MemoType, Transaction } from "@stellar/stellar-sdk"; -import { Buffer } from "buffer"; -import type { - ClassOptions, - MethodOptions, - Wallet, - XDR_BASE64, -} from "./method-options.js"; - -export type Tx = Transaction, Operation[]> - -export class ExpiredStateError extends Error { } -export class NeedsMoreSignaturesError extends Error { } -export class WalletDisconnectedError extends Error { } -export class SendResultOnlyError extends Error { } -export class SendFailedError extends Error { } -export class NoUnsignedNonInvokerAuthEntriesError extends Error { } - -type SendTx = SorobanRpc.Api.SendTransactionResponse; -type GetTx = SorobanRpc.Api.GetTransactionResponse; - -export type u32 = number; -export type i32 = number; -export type u64 = bigint; -export type i64 = bigint; -export type u128 = bigint; -export type i128 = bigint; -export type u256 = bigint; -export type i256 = bigint; -export type Option = T | undefined; -export type Typepoint = bigint; -export type Duration = bigint; -export {Address}; - -/// Error interface containing the error message -export interface Error_ { message: string }; - -export interface Result { - unwrap(): T, - unwrapErr(): E, - isOk(): boolean, - isErr(): boolean, -}; - -export class Ok implements Result { - constructor(readonly value: T) { } - unwrapErr(): E { - throw new Error('No error'); - } - unwrap(): T { - return this.value; - } - - isOk(): boolean { - return true; - } - - isErr(): boolean { - return !this.isOk() - } -} - -export class Err implements Result { - constructor(readonly error: E) { } - unwrapErr(): E { - return this.error; - } - unwrap(): never { - throw new Error(this.error.message); - } - - isOk(): boolean { - return false; - } - - isErr(): boolean { - return !this.isOk() - } -} - -export const contractErrorPattern = /Error\(Contract, #(\d+)\)/; - -type AssembledTransactionOptions = MethodOptions & - ClassOptions & { - method: string; - args?: any[]; - parseResultXdr: (xdr: string | xdr.ScVal | Err) => T; - }; - -export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" - -export class AssembledTransaction { - public raw: Tx - private simulation?: SorobanRpc.Api.SimulateTransactionResponse - private simulationResult?: SorobanRpc.Api.SimulateHostFunctionResult - private simulationTransactionData?: xdr.SorobanTransactionData - private server: SorobanRpc.Server - - toJSON() { - return JSON.stringify({ - method: this.options.method, - tx: this.raw?.toXDR(), - simulationResult: { - auth: this.simulationData.result.auth.map(a => a.toXDR('base64')), - retval: this.simulationData.result.retval.toXDR('base64'), - }, - simulationTransactionData: this.simulationData.transactionData.toXDR('base64'), - }) - } - - static fromJSON( - options: Omit, 'args'>, - { tx, simulationResult, simulationTransactionData }: - { - tx: XDR_BASE64, - simulationResult: { - auth: XDR_BASE64[], - retval: XDR_BASE64, - }, - simulationTransactionData: XDR_BASE64, - } - ): AssembledTransaction { - const txn = new AssembledTransaction(options) - txn.raw = TransactionBuilder.fromXDR(tx, options.networkPassphrase) as Tx - txn.simulationResult = { - auth: simulationResult.auth.map(a => xdr.SorobanAuthorizationEntry.fromXDR(a, 'base64')), - retval: xdr.ScVal.fromXDR(simulationResult.retval, 'base64'), - } - txn.simulationTransactionData = xdr.SorobanTransactionData.fromXDR(simulationTransactionData, 'base64') - return txn - } - - private constructor(public options: AssembledTransactionOptions) { - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - } - - static async fromSimulation(options: AssembledTransactionOptions): Promise> { - const tx = new AssembledTransaction(options) - const contract = new Contract(options.contractId); - - tx.raw = new TransactionBuilder(await tx.getAccount(), { - fee: options.fee?.toString(10) ?? BASE_FEE, - networkPassphrase: options.networkPassphrase, - }) - .addOperation(contract.call(options.method, ...(options.args ?? []))) - .setTimeout(TimeoutInfinite) - .build(); - - return await tx.simulate() - } - - simulate = async (): Promise => { - if (!this.raw) throw new Error('Transaction has not yet been assembled') - this.simulation = await this.server.simulateTransaction(this.raw); - - if (SorobanRpc.Api.isSimulationSuccess(this.simulation)) { - this.raw = SorobanRpc.assembleTransaction( - this.raw, - this.simulation - ).build() - } - - return this - } - - get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult - transactionData: xdr.SorobanTransactionData - } { - if (this.simulationResult && this.simulationTransactionData) { - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - } - } - // else, we know we just did the simulation on this machine - const simulation = this.simulation! - if (SorobanRpc.Api.isSimulationError(simulation)) { - throw new Error(`Transaction simulation failed: "${simulation.error}"`) - } - - if (SorobanRpc.Api.isSimulationRestore(simulation)) { - throw new ExpiredStateError(`You need to restore some contract state before you can invoke this method. ${JSON.stringify(simulation, null, 2)}`) - } - - if (!simulation.result) { - throw new Error(`Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify(simulation, null, 2)}`) - } - - // add to object for serialization & deserialization - this.simulationResult = simulation.result - this.simulationTransactionData = simulation.transactionData.build() - - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData!, - } - } - - get result(): T { - try { - return this.options.parseResultXdr(this.simulationData.result.retval) - } catch (e) { - let err = this.parseError(e.toString()) - if (err) return err as T - throw e - } - } - - parseError(errorMessage: string): Err | undefined { - if (!this.options.errorTypes) return - const match = errorMessage.match(contractErrorPattern) - if (!match) return - let i = parseInt(match[1], 10) - let err = this.options.errorTypes[i] - if (err) return new Err(err) - } - - getWallet = async (): Promise => { - return this.options.wallet ?? (await import("@stellar/freighter-api")).default - } - - getPublicKey = async (): Promise => { - const wallet = await this.getWallet() - if (await wallet.isConnected() && await wallet.isAllowed()) { - return (await wallet.getUserInfo()).publicKey - } - } - - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount = async (): Promise => { - const publicKey = await this.getPublicKey() - return publicKey - ? await this.server.getAccount(publicKey) - : new Account(NULL_ACCOUNT, "0") - } - - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend = async ({ secondsToWait = 10, force = false }: { - /** - * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). - */ - secondsToWait?: number - /** - * If `true`, sign and send the transaction even if it is a read call. - */ - force?: boolean - } = {}): Promise> => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated') - } - - if (!force && this.isReadCall) { - throw new Error('This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway.') - } - - if (!await this.hasRealInvoker()) { - throw new WalletDisconnectedError('Wallet is not connected') - } - - if (this.raw.source !== (await this.getAccount()).accountId()) { - throw new Error(`You must submit the transaction with the account that originally created it. Please switch to the wallet with "${this.raw.source}" as its public key.`) - } - - if ((await this.needsNonInvokerSigningBy()).length) { - throw new NeedsMoreSignaturesError( - 'Transaction requires more signatures. See `needsNonInvokerSigningBy` for details.' - ) - } - - return await SentTransaction.init(this.options, this, secondsToWait); - } - - getStorageExpiration = async () => { - const entryRes = await this.server.getLedgerEntries( - new Contract(this.options.contractId).getFootprint() - ) - if ( - !entryRes.entries || - !entryRes.entries.length || - !entryRes.entries[0].liveUntilLedgerSeq - ) throw new Error('failed to get ledger entry') - return entryRes.entries[0].liveUntilLedgerSeq - } - - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy = async ({ - includeAlreadySigned = false, - }: { - /** - * Whether or not to include auth entries that have already been signed. Default: false - */ - includeAlreadySigned?: boolean - } = {}): Promise => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated') - } - - // We expect that any transaction constructed by these libraries has a - // single operation, which is an InvokeHostFunction operation. The host - // function being invoked is the contract method call. - if (!("operations" in this.raw)) { - throw new Error( - `Unexpected Transaction type; no operations: ${JSON.stringify(this.raw) - }` - ) - } - const rawInvokeHostFunctionOp = this.raw - .operations[0] as Operation.InvokeHostFunction - - return [...new Set((rawInvokeHostFunctionOp.auth ?? []).filter(entry => - entry.credentials().switch() === - xdr.SorobanCredentialsType.sorobanCredentialsAddress() && - ( - includeAlreadySigned || - entry.credentials().address().signature().switch().name === 'scvVoid' - ) - ).map(entry => StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() - )))] - } - - preImageFor( - entry: xdr.SorobanAuthorizationEntry, - signatureExpirationLedger: number - ): xdr.HashIdPreimage { - const addrAuth = entry.credentials().address() - return xdr.HashIdPreimage.envelopeTypeSorobanAuthorization( - new xdr.HashIdPreimageSorobanAuthorization({ - networkId: hash(Buffer.from(this.options.networkPassphrase)), - nonce: addrAuth.nonce(), - invocation: entry.rootInvocation(), - signatureExpirationLedger, - }), - ) - } - - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries = async ( - /** - * When to set each auth entry to expire. Could be any number of blocks in - * the future. Can be supplied as a promise or a raw number. Default: - * contract's current `persistent` storage expiration date/ledger - * number/block. - */ - expiration: number | Promise = this.getStorageExpiration() - ): Promise => { - if (!this.raw) throw new Error('Transaction has not yet been assembled or simulated') - const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy() - - if (!needsNonInvokerSigningBy) throw new NoUnsignedNonInvokerAuthEntriesError('No unsigned non-invoker auth entries; maybe you already signed?') - const publicKey = await this.getPublicKey() - if (!publicKey) throw new Error('Could not get public key from wallet; maybe Freighter is not signed in?') - if (needsNonInvokerSigningBy.indexOf(publicKey) === -1) throw new Error(`No auth entries for public key "${publicKey}"`) - const wallet = await this.getWallet() - - const rawInvokeHostFunctionOp = this.raw - .operations[0] as Operation.InvokeHostFunction - - const authEntries = rawInvokeHostFunctionOp.auth ?? [] - - for (const [i, entry] of authEntries.entries()) { - if ( - entry.credentials().switch() !== - xdr.SorobanCredentialsType.sorobanCredentialsAddress() - ) { - // if the invoker/source account, then the entry doesn't need explicit - // signature, since the tx envelope is already signed by the source - // account, so only check for sorobanCredentialsAddress - continue - } - const pk = StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() - ) - - // this auth entry needs to be signed by a different account - // (or maybe already was!) - if (pk !== publicKey) continue - - authEntries[i] = await authorizeEntry( - entry, - async preimage => Buffer.from( - await wallet.signAuthEntry(preimage.toXDR('base64')), - 'base64' - ), - await expiration, - this.options.networkPassphrase - ) - } - } - - get isReadCall(): boolean { - const authsCount = this.simulationData.result.auth.length; - const writeLength = this.simulationData.transactionData.resources().footprint().readWrite().length - return (authsCount === 0) && (writeLength === 0); - } - - hasRealInvoker = async (): Promise => { - const account = await this.getAccount() - return account.accountId() !== NULL_ACCOUNT - } -} - -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -class SentTransaction { - public server: SorobanRpc.Server - public signed: Tx - public sendTransactionResponse?: SendTx - public sendTransactionResponseAll?: SendTx[] - public getTransactionResponse?: GetTx - public getTransactionResponseAll?: GetTx[] - - constructor(public options: AssembledTransactionOptions, public assembled: AssembledTransaction) { - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - this.assembled = assembled - } - - static init = async ( - options: AssembledTransactionOptions, - assembled: AssembledTransaction, - secondsToWait: number = 10 - ): Promise> => { - const tx = new SentTransaction(options, assembled) - return await tx.send(secondsToWait) - } - - private send = async (secondsToWait: number = 10): Promise => { - const wallet = await this.assembled.getWallet() - - this.sendTransactionResponseAll = await withExponentialBackoff( - async (previousFailure) => { - if (previousFailure) { - // Increment transaction sequence number and resimulate before trying again - - // Soroban transaction can only have 1 operation - const op = this.assembled.raw.operations[0] as Operation.InvokeHostFunction; - - this.assembled.raw = new TransactionBuilder(await this.assembled.getAccount(), { - fee: this.assembled.raw.fee, - networkPassphrase: this.options.networkPassphrase, - }) - .setTimeout(TimeoutInfinite) - .addOperation( - Operation.invokeHostFunction({ ...op, auth: op.auth ?? [] }), - ) - .build() - - await this.assembled.simulate() - } - - const signature = await wallet.signTransaction(this.assembled.raw.toXDR(), { - networkPassphrase: this.options.networkPassphrase, - }); - - this.signed = TransactionBuilder.fromXDR( - signature, - this.options.networkPassphrase - ) as Tx - - return this.server.sendTransaction(this.signed) - }, - resp => resp.status !== "PENDING", - secondsToWait - ) - - this.sendTransactionResponse = this.sendTransactionResponseAll[this.sendTransactionResponseAll.length - 1] - - if (this.sendTransactionResponse.status !== "PENDING") { - throw new Error( - `Tried to resubmit transaction for ${secondsToWait - } seconds, but it's still failing. ` + - `All attempts: ${JSON.stringify( - this.sendTransactionResponseAll, - null, - 2 - )}` - ); - } - - const { hash } = this.sendTransactionResponse - - this.getTransactionResponseAll = await withExponentialBackoff( - () => this.server.getTransaction(hash), - resp => resp.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, - secondsToWait - ) - - this.getTransactionResponse = this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1] - if (this.getTransactionResponse.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND) { - console.error( - `Waited ${secondsToWait - } seconds for transaction to complete, but it did not. ` + - `Returning anyway. Check the transaction status manually. ` + - `Sent transaction: ${JSON.stringify( - this.sendTransactionResponse, - null, - 2 - )}\n` + - `All attempts to get the result: ${JSON.stringify( - this.getTransactionResponseAll, - null, - 2 - )}` - ); - } - - return this; - } - - get result(): T { - // 1. check if transaction was submitted and awaited with `getTransaction` - if ( - "getTransactionResponse" in this && - this.getTransactionResponse - ) { - // getTransactionResponse has a `returnValue` field unless it failed - if ("returnValue" in this.getTransactionResponse) { - return this.options.parseResultXdr(this.getTransactionResponse.returnValue!) - } - - // if "returnValue" not present, the transaction failed; return without parsing the result - throw new Error("Transaction failed! Cannot parse result.") - } - - // 2. otherwise, maybe it was merely sent with `sendTransaction` - if (this.sendTransactionResponse) { - const errorResult = this.sendTransactionResponse.errorResult?.result() - if (errorResult) { - throw new SendFailedError( - `Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResult}` - ) - } - throw new SendResultOnlyError( - `Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\`` - ) - } - - // 3. finally, if neither of those are present, throw an error - throw new Error(`Sending transaction failed: ${JSON.stringify(this.assembled)}`) - } -} - -/** - * Keep calling a `fn` for `secondsToWait` seconds, if `keepWaitingIf` is true. - * Returns an array of all attempts to call the function. - */ -async function withExponentialBackoff( - fn: (previousFailure?: T) => Promise, - keepWaitingIf: (result: T) => boolean, - secondsToWait: number, - exponentialFactor = 1.5, - verbose = false, -): Promise { - const attempts: T[] = [] - - let count = 0 - attempts.push(await fn()) - if (!keepWaitingIf(attempts[attempts.length - 1])) return attempts - - const waitUntil = new Date(Date.now() + secondsToWait * 1000).valueOf() - let waitTime = 1000 - let totalWaitTime = waitTime - - while (Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1])) { - count++ - // Wait a beat - if (verbose) { - console.info(`Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${secondsToWait * 1000}ms)`) - } - await new Promise(res => setTimeout(res, waitTime)) - // Exponential backoff - waitTime = waitTime * exponentialFactor; - if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { - waitTime = waitUntil - Date.now() - if (verbose) { - console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`) - } - } - totalWaitTime = waitTime + totalWaitTime - // Try again - attempts.push(await fn(attempts[attempts.length - 1])) - if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { - console.info( - `${count}. Called ${fn}; ${attempts.length - } prev attempts. Most recent: ${JSON.stringify(attempts[attempts.length - 1], null, 2) - }` - ) - } - } - - return attempts -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts deleted file mode 100644 index e3119225..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/index.ts +++ /dev/null @@ -1,758 +0,0 @@ -import { ContractSpec, Address } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -import type { - u32, - i32, - u64, - i64, - u128, - i128, - u256, - i256, - Option, - Typepoint, - Duration, - Error_, - Result, -} from './assembled-tx.js'; -import type { ClassOptions, XDR_BASE64 } from './method-options.js'; - -export * from './assembled-tx.js'; -export * from './method-options.js'; - -if (typeof window !== 'undefined') { - //@ts-ignore Buffer exists - window.Buffer = window.Buffer || Buffer; -} - - -export const networks = { - futurenet: { - networkPassphrase: "Test SDF Future Network ; October 2022", - contractId: "CBYMYMSDF6FBDNCFJCRC7KMO4REYFPOH2U4N7FXI3GJO6YXNCQ43CDSK", - } -} as const - -/** - This is from the rust doc above the struct Test - */ -export interface Test { - /** - - */ -a: u32; - /** - - */ -b: boolean; - /** - - */ -c: string; -} - -/** - - */ -export type SimpleEnum = {tag: "First", values: void} | {tag: "Second", values: void} | {tag: "Third", values: void}; - -/** - - */ -export enum RoyalCard { - Jack = 11, - Queen = 12, - King = 13, -} - -/** - - */ -export type TupleStruct = readonly [Test, SimpleEnum]; -/** - - */ -export type ComplexEnum = {tag: "Struct", values: readonly [Test]} | {tag: "Tuple", values: readonly [TupleStruct]} | {tag: "Enum", values: readonly [SimpleEnum]} | {tag: "Asset", values: readonly [string, i128]} | {tag: "Void", values: void}; - -/** - - */ -export const Errors = { -1: {message:"Please provide an odd number"} -} - -export class Contract { - spec: ContractSpec; - constructor(public readonly options: ClassOptions) { - this.spec = new ContractSpec([ - "AAAAAQAAAC9UaGlzIGlzIGZyb20gdGhlIHJ1c3QgZG9jIGFib3ZlIHRoZSBzdHJ1Y3QgVGVzdAAAAAAAAAAABFRlc3QAAAADAAAAAAAAAAFhAAAAAAAABAAAAAAAAAABYgAAAAAAAAEAAAAAAAAAAWMAAAAAAAAR", - "AAAAAgAAAAAAAAAAAAAAClNpbXBsZUVudW0AAAAAAAMAAAAAAAAAAAAAAAVGaXJzdAAAAAAAAAAAAAAAAAAABlNlY29uZAAAAAAAAAAAAAAAAAAFVGhpcmQAAAA=", - "AAAAAwAAAAAAAAAAAAAACVJveWFsQ2FyZAAAAAAAAAMAAAAAAAAABEphY2sAAAALAAAAAAAAAAVRdWVlbgAAAAAAAAwAAAAAAAAABEtpbmcAAAAN", - "AAAAAQAAAAAAAAAAAAAAC1R1cGxlU3RydWN0AAAAAAIAAAAAAAAAATAAAAAAAAfQAAAABFRlc3QAAAAAAAAAATEAAAAAAAfQAAAAClNpbXBsZUVudW0AAA==", - "AAAAAgAAAAAAAAAAAAAAC0NvbXBsZXhFbnVtAAAAAAUAAAABAAAAAAAAAAZTdHJ1Y3QAAAAAAAEAAAfQAAAABFRlc3QAAAABAAAAAAAAAAVUdXBsZQAAAAAAAAEAAAfQAAAAC1R1cGxlU3RydWN0AAAAAAEAAAAAAAAABEVudW0AAAABAAAH0AAAAApTaW1wbGVFbnVtAAAAAAABAAAAAAAAAAVBc3NldAAAAAAAAAIAAAATAAAACwAAAAAAAAAAAAAABFZvaWQ=", - "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAAQAAABxQbGVhc2UgcHJvdmlkZSBhbiBvZGQgbnVtYmVyAAAAD051bWJlck11c3RCZU9kZAAAAAAB", - "AAAAAAAAAAAAAAAFaGVsbG8AAAAAAAABAAAAAAAAAAVoZWxsbwAAAAAAABEAAAABAAAAEQ==", - "AAAAAAAAAAAAAAAEd29pZAAAAAAAAAAA", - "AAAAAAAAAAAAAAADdmFsAAAAAAAAAAABAAAAAA==", - "AAAAAAAAAAAAAAAQdTMyX2ZhaWxfb25fZXZlbgAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAA+kAAAAEAAAAAw==", - "AAAAAAAAAAAAAAAEdTMyXwAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAAEaTMyXwAAAAEAAAAAAAAABGkzMl8AAAAFAAAAAQAAAAU=", - "AAAAAAAAAAAAAAAEaTY0XwAAAAEAAAAAAAAABGk2NF8AAAAHAAAAAQAAAAc=", - "AAAAAAAAACxFeGFtcGxlIGNvbnRyYWN0IG1ldGhvZCB3aGljaCB0YWtlcyBhIHN0cnVjdAAAAApzdHJ1a3RfaGVsAAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAPqAAAAEQ==", - "AAAAAAAAAAAAAAAGc3RydWt0AAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAfQAAAABFRlc3Q=", - "AAAAAAAAAAAAAAAGc2ltcGxlAAAAAAABAAAAAAAAAAZzaW1wbGUAAAAAB9AAAAAKU2ltcGxlRW51bQAAAAAAAQAAB9AAAAAKU2ltcGxlRW51bQAA", - "AAAAAAAAAAAAAAAHY29tcGxleAAAAAABAAAAAAAAAAdjb21wbGV4AAAAB9AAAAALQ29tcGxleEVudW0AAAAAAQAAB9AAAAALQ29tcGxleEVudW0A", - "AAAAAAAAAAAAAAAIYWRkcmVzc2UAAAABAAAAAAAAAAhhZGRyZXNzZQAAABMAAAABAAAAEw==", - "AAAAAAAAAAAAAAAFYnl0ZXMAAAAAAAABAAAAAAAAAAVieXRlcwAAAAAAAA4AAAABAAAADg==", - "AAAAAAAAAAAAAAAHYnl0ZXNfbgAAAAABAAAAAAAAAAdieXRlc19uAAAAA+4AAAAJAAAAAQAAA+4AAAAJ", - "AAAAAAAAAAAAAAAEY2FyZAAAAAEAAAAAAAAABGNhcmQAAAfQAAAACVJveWFsQ2FyZAAAAAAAAAEAAAfQAAAACVJveWFsQ2FyZAAAAA==", - "AAAAAAAAAAAAAAAHYm9vbGVhbgAAAAABAAAAAAAAAAdib29sZWFuAAAAAAEAAAABAAAAAQ==", - "AAAAAAAAABdOZWdhdGVzIGEgYm9vbGVhbiB2YWx1ZQAAAAADbm90AAAAAAEAAAAAAAAAB2Jvb2xlYW4AAAAAAQAAAAEAAAAB", - "AAAAAAAAAAAAAAAEaTEyOAAAAAEAAAAAAAAABGkxMjgAAAALAAAAAQAAAAs=", - "AAAAAAAAAAAAAAAEdTEyOAAAAAEAAAAAAAAABHUxMjgAAAAKAAAAAQAAAAo=", - "AAAAAAAAAAAAAAAKbXVsdGlfYXJncwAAAAAAAgAAAAAAAAABYQAAAAAAAAQAAAAAAAAAAWIAAAAAAAABAAAAAQAAAAQ=", - "AAAAAAAAAAAAAAADbWFwAAAAAAEAAAAAAAAAA21hcAAAAAPsAAAABAAAAAEAAAABAAAD7AAAAAQAAAAB", - "AAAAAAAAAAAAAAADdmVjAAAAAAEAAAAAAAAAA3ZlYwAAAAPqAAAABAAAAAEAAAPqAAAABA==", - "AAAAAAAAAAAAAAAFdHVwbGUAAAAAAAABAAAAAAAAAAV0dXBsZQAAAAAAA+0AAAACAAAAEQAAAAQAAAABAAAD7QAAAAIAAAARAAAABA==", - "AAAAAAAAAB9FeGFtcGxlIG9mIGFuIG9wdGlvbmFsIGFyZ3VtZW50AAAAAAZvcHRpb24AAAAAAAEAAAAAAAAABm9wdGlvbgAAAAAD6AAAAAQAAAABAAAD6AAAAAQ=", - "AAAAAAAAAAAAAAAEdTI1NgAAAAEAAAAAAAAABHUyNTYAAAAMAAAAAQAAAAw=", - "AAAAAAAAAAAAAAAEaTI1NgAAAAEAAAAAAAAABGkyNTYAAAANAAAAAQAAAA0=", - "AAAAAAAAAAAAAAAGc3RyaW5nAAAAAAABAAAAAAAAAAZzdHJpbmcAAAAAABAAAAABAAAAEA==", - "AAAAAAAAAAAAAAAMdHVwbGVfc3RydWt0AAAAAQAAAAAAAAAMdHVwbGVfc3RydWt0AAAH0AAAAAtUdXBsZVN0cnVjdAAAAAABAAAH0AAAAAtUdXBsZVN0cnVjdAA=" - ]); - } - private readonly parsers = { - hello: (result: XDR_BASE64): string => this.spec.funcResToNative("hello", result), - woid: () => {}, - val: (result: XDR_BASE64): any => this.spec.funcResToNative("val", result), - u32FailOnEven: (result: XDR_BASE64 | Err): Ok | Err => { - if (result instanceof Err) return result - return new Ok(this.spec.funcResToNative("u32_fail_on_even", result)) - }, - u32: (result: XDR_BASE64): u32 => this.spec.funcResToNative("u32_", result), - i32: (result: XDR_BASE64): i32 => this.spec.funcResToNative("i32_", result), - i64: (result: XDR_BASE64): i64 => this.spec.funcResToNative("i64_", result), - struktHel: (result: XDR_BASE64): Array => this.spec.funcResToNative("strukt_hel", result), - strukt: (result: XDR_BASE64): Test => this.spec.funcResToNative("strukt", result), - simple: (result: XDR_BASE64): SimpleEnum => this.spec.funcResToNative("simple", result), - complex: (result: XDR_BASE64): ComplexEnum => this.spec.funcResToNative("complex", result), - addresse: (result: XDR_BASE64): string => this.spec.funcResToNative("addresse", result), - bytes: (result: XDR_BASE64): Buffer => this.spec.funcResToNative("bytes", result), - bytesN: (result: XDR_BASE64): Buffer => this.spec.funcResToNative("bytes_n", result), - card: (result: XDR_BASE64): RoyalCard => this.spec.funcResToNative("card", result), - boolean: (result: XDR_BASE64): boolean => this.spec.funcResToNative("boolean", result), - not: (result: XDR_BASE64): boolean => this.spec.funcResToNative("not", result), - i128: (result: XDR_BASE64): i128 => this.spec.funcResToNative("i128", result), - u128: (result: XDR_BASE64): u128 => this.spec.funcResToNative("u128", result), - multiArgs: (result: XDR_BASE64): u32 => this.spec.funcResToNative("multi_args", result), - map: (result: XDR_BASE64): Map => this.spec.funcResToNative("map", result), - vec: (result: XDR_BASE64): Array => this.spec.funcResToNative("vec", result), - tuple: (result: XDR_BASE64): readonly [string, u32] => this.spec.funcResToNative("tuple", result), - option: (result: XDR_BASE64): Option => this.spec.funcResToNative("option", result), - u256: (result: XDR_BASE64): u256 => this.spec.funcResToNative("u256", result), - i256: (result: XDR_BASE64): i256 => this.spec.funcResToNative("i256", result), - string: (result: XDR_BASE64): string => this.spec.funcResToNative("string", result), - tupleStrukt: (result: XDR_BASE64): TupleStruct => this.spec.funcResToNative("tuple_strukt", result) - }; - private txFromJSON = (json: string): AssembledTransaction => { - const { method, ...tx } = JSON.parse(json) - return AssembledTransaction.fromJSON( - { - ...this.options, - method, - parseResultXdr: this.parsers[method], - }, - tx, - ); - } - public readonly fromJSON = { - hello: this.txFromJSON>, - woid: this.txFromJSON>, - val: this.txFromJSON>, - u32FailOnEven: this.txFromJSON>, - u32: this.txFromJSON>, - i32: this.txFromJSON>, - i64: this.txFromJSON>, - struktHel: this.txFromJSON>, - strukt: this.txFromJSON>, - simple: this.txFromJSON>, - complex: this.txFromJSON>, - addresse: this.txFromJSON>, - bytes: this.txFromJSON>, - bytesN: this.txFromJSON>, - card: this.txFromJSON>, - boolean: this.txFromJSON>, - not: this.txFromJSON>, - i128: this.txFromJSON>, - u128: this.txFromJSON>, - multiArgs: this.txFromJSON>, - map: this.txFromJSON>, - vec: this.txFromJSON>, - tuple: this.txFromJSON>, - option: this.txFromJSON>, - u256: this.txFromJSON>, - i256: this.txFromJSON>, - string: this.txFromJSON>, - tupleStrukt: this.txFromJSON> - } - /** - * Construct and simulate a hello transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - hello = async ({hello}: {hello: string}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'hello', - args: this.spec.funcArgsToScVals("hello", {hello}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['hello'], - }); - } - - - /** - * Construct and simulate a woid transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - woid = async (options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'woid', - args: this.spec.funcArgsToScVals("woid", {}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['woid'], - }); - } - - - /** - * Construct and simulate a val transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - val = async (options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'val', - args: this.spec.funcArgsToScVals("val", {}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['val'], - }); - } - - - /** - * Construct and simulate a u32_fail_on_even transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - u32FailOnEven = async ({u32_}: {u32_: u32}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u32_fail_on_even', - args: this.spec.funcArgsToScVals("u32_fail_on_even", {u32_}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u32FailOnEven'], - }); - } - - - /** - * Construct and simulate a u32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - u32 = async ({u32_}: {u32_: u32}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u32_', - args: this.spec.funcArgsToScVals("u32_", {u32_}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u32'], - }); - } - - - /** - * Construct and simulate a i32_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - i32 = async ({i32_}: {i32_: i32}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i32_', - args: this.spec.funcArgsToScVals("i32_", {i32_}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i32'], - }); - } - - - /** - * Construct and simulate a i64_ transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - i64 = async ({i64_}: {i64_: i64}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i64_', - args: this.spec.funcArgsToScVals("i64_", {i64_}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i64'], - }); - } - - - /** - * Construct and simulate a strukt_hel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example contract method which takes a struct - */ - struktHel = async ({strukt}: {strukt: Test}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'strukt_hel', - args: this.spec.funcArgsToScVals("strukt_hel", {strukt}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['struktHel'], - }); - } - - - /** - * Construct and simulate a strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - strukt = async ({strukt}: {strukt: Test}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'strukt', - args: this.spec.funcArgsToScVals("strukt", {strukt}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['strukt'], - }); - } - - - /** - * Construct and simulate a simple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - simple = async ({simple}: {simple: SimpleEnum}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'simple', - args: this.spec.funcArgsToScVals("simple", {simple}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['simple'], - }); - } - - - /** - * Construct and simulate a complex transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - complex = async ({complex}: {complex: ComplexEnum}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'complex', - args: this.spec.funcArgsToScVals("complex", {complex}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['complex'], - }); - } - - - /** - * Construct and simulate a addresse transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - addresse = async ({addresse}: {addresse: string}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'addresse', - args: this.spec.funcArgsToScVals("addresse", {addresse: new Address(addresse)}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['addresse'], - }); - } - - - /** - * Construct and simulate a bytes transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - bytes = async ({bytes}: {bytes: Buffer}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'bytes', - args: this.spec.funcArgsToScVals("bytes", {bytes}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['bytes'], - }); - } - - - /** - * Construct and simulate a bytes_n transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - bytesN = async ({bytes_n}: {bytes_n: Buffer}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'bytes_n', - args: this.spec.funcArgsToScVals("bytes_n", {bytes_n}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['bytesN'], - }); - } - - - /** - * Construct and simulate a card transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - card = async ({card}: {card: RoyalCard}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'card', - args: this.spec.funcArgsToScVals("card", {card}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['card'], - }); - } - - - /** - * Construct and simulate a boolean transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - boolean = async ({boolean}: {boolean: boolean}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'boolean', - args: this.spec.funcArgsToScVals("boolean", {boolean}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['boolean'], - }); - } - - - /** - * Construct and simulate a not transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Negates a boolean value - */ - not = async ({boolean}: {boolean: boolean}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'not', - args: this.spec.funcArgsToScVals("not", {boolean}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['not'], - }); - } - - - /** - * Construct and simulate a i128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - i128 = async ({i128}: {i128: i128}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i128', - args: this.spec.funcArgsToScVals("i128", {i128}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i128'], - }); - } - - - /** - * Construct and simulate a u128 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - u128 = async ({u128}: {u128: u128}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u128', - args: this.spec.funcArgsToScVals("u128", {u128}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u128'], - }); - } - - - /** - * Construct and simulate a multi_args transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - multiArgs = async ({a, b}: {a: u32, b: boolean}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'multi_args', - args: this.spec.funcArgsToScVals("multi_args", {a, b}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['multiArgs'], - }); - } - - - /** - * Construct and simulate a map transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - map = async ({map}: {map: Map}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'map', - args: this.spec.funcArgsToScVals("map", {map}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['map'], - }); - } - - - /** - * Construct and simulate a vec transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - vec = async ({vec}: {vec: Array}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'vec', - args: this.spec.funcArgsToScVals("vec", {vec}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['vec'], - }); - } - - - /** - * Construct and simulate a tuple transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - tuple = async ({tuple}: {tuple: readonly [string, u32]}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'tuple', - args: this.spec.funcArgsToScVals("tuple", {tuple}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['tuple'], - }); - } - - - /** - * Construct and simulate a option transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.Example of an optional argument - */ - option = async ({option}: {option: Option}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'option', - args: this.spec.funcArgsToScVals("option", {option}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['option'], - }); - } - - - /** - * Construct and simulate a u256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - u256 = async ({u256}: {u256: u256}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'u256', - args: this.spec.funcArgsToScVals("u256", {u256}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['u256'], - }); - } - - - /** - * Construct and simulate a i256 transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - i256 = async ({i256}: {i256: i256}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'i256', - args: this.spec.funcArgsToScVals("i256", {i256}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['i256'], - }); - } - - - /** - * Construct and simulate a string transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - string = async ({string}: {string: string}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'string', - args: this.spec.funcArgsToScVals("string", {string}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['string'], - }); - } - - - /** - * Construct and simulate a tuple_strukt transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - */ - tupleStrukt = async ({tuple_strukt}: {tuple_strukt: TupleStruct}, options: { - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - } = {}) => { - return await AssembledTransaction.fromSimulation({ - method: 'tuple_strukt', - args: this.spec.funcArgsToScVals("tuple_strukt", {tuple_strukt}), - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['tupleStrukt'], - }); - } - -} \ No newline at end of file diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/method-options.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/method-options.ts deleted file mode 100644 index 737ae0a0..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/src/method-options.ts +++ /dev/null @@ -1,50 +0,0 @@ -// defined this way so typeahead shows full union, not named alias -let responseTypes: 'simulated' | 'full' | undefined -export type ResponseTypes = typeof responseTypes - -export type XDR_BASE64 = string - -export interface Wallet { - isConnected: () => Promise, - isAllowed: () => Promise, - getUserInfo: () => Promise<{ publicKey?: string }>, - signTransaction: (tx: XDR_BASE64, opts?: { - network?: string, - networkPassphrase?: string, - accountToSign?: string, - }) => Promise, - signAuthEntry: ( - entryXdr: XDR_BASE64, - opts?: { - accountToSign?: string; - } - ) => Promise -} - -export type ClassOptions = { - contractId: string - networkPassphrase: string - rpcUrl: string - errorTypes?: Record - /** - * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: - * - * @example - * ```ts - * import freighter from "@stellar/freighter-api"; - * import { Contract } from "test_custom_types"; - * const contract = new Contract({ - * …, - * wallet: freighter, - * }) - * ``` - */ - wallet?: Wallet -} - -export type MethodOptions = { - /** - * The fee to pay for the transaction. Default: soroban-sdk's BASE_FEE ('100') - */ - fee?: number -} diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/tsconfig.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/tsconfig.json deleted file mode 100644 index efd4c619..00000000 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/tsconfig.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "ESNext", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - // "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - // "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/*" - ] -} diff --git a/cmd/crates/soroban-spec-typescript/src/boilerplate.rs b/cmd/crates/soroban-spec-typescript/src/boilerplate.rs deleted file mode 100644 index 7ffe6418..00000000 --- a/cmd/crates/soroban-spec-typescript/src/boilerplate.rs +++ /dev/null @@ -1,218 +0,0 @@ -#![allow(non_snake_case)] -use heck::{ToLowerCamelCase, ToShoutySnakeCase}; -use include_dir::{include_dir, Dir}; -use std::{ - fs, - io::Write, - path::{Path, PathBuf}, -}; -use stellar_xdr::curr::ScSpecEntry; - -use super::generate; - -static PROJECT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/project_template"); - -const NETWORK_PASSPHRASE_TESTNET: &str = "Test SDF Network ; September 2015"; -const NETWORK_PASSPHRASE_FUTURENET: &str = "Test SDF Future Network ; October 2022"; -const NETWORK_PASSPHRASE_STANDALONE: &str = "Standalone Network ; February 2017"; - -pub struct Project(PathBuf); - -impl TryInto for PathBuf { - type Error = std::io::Error; - - fn try_into(self) -> Result { - PROJECT_DIR.extract(&self)?; - Ok(Project(self)) - } -} - -impl AsRef for Project { - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} - -impl Project { - /// Initialize a new JS client project, updating placeholder strings in the template and - /// appending functions for each method in the contract to the index.ts file. - /// - /// # Arguments - /// - /// * `contract_name` - The colloquial name of this contract that will be used in the README and package.json - /// * `contract_id` - The ID/address of the contract on the network. Will be overridable with environment variables. - /// * `rpc_url` - The RPC URL of the network where this contract is deployed. Will be overridable with environment variables. - /// * `network_passphrase` - The passphrase of the network where this contract is deployed. Will be overridable with environment variables. - /// * `spec` - The contract specification. - pub fn init( - &self, - contract_name: &str, - contract_id: &str, - rpc_url: &str, - network_passphrase: &str, - spec: &[ScSpecEntry], - ) -> std::io::Result<()> { - self.replace_placeholder_patterns(contract_name, contract_id, rpc_url, network_passphrase)?; - self.append_index_ts(spec, contract_id, network_passphrase) - } - - fn replace_placeholder_patterns( - &self, - contract_name: &str, - contract_id: &str, - rpc_url: &str, - network_passphrase: &str, - ) -> std::io::Result<()> { - let replacement_strings = &[ - ("INSERT_CONTRACT_NAME_HERE", contract_name), - ( - "INSERT_SCREAMING_SNAKE_CASE_CONTRACT_NAME_HERE", - &contract_name.to_shouty_snake_case(), - ), - ( - "INSERT_CAMEL_CASE_CONTRACT_NAME_HERE", - &contract_name.to_lower_camel_case(), - ), - ("INSERT_CONTRACT_ID_HERE", contract_id), - ("INSERT_NETWORK_PASSPHRASE_HERE", network_passphrase), - ("INSERT_RPC_URL_HERE", rpc_url), - ]; - let root: &Path = self.as_ref(); - [ - "package.json", - "README.md", - "src/assembled-tx.ts", - "src/index.ts", - "src/method-options.ts", - ] - .into_iter() - .try_for_each(|file_name| { - let file = &root.join(file_name); - let mut contents = fs::read_to_string(file).unwrap(); - for (pattern, replacement) in replacement_strings { - contents = contents.replace(pattern, replacement); - } - fs::write(file, contents) - }) - } - - fn append_index_ts( - &self, - spec: &[ScSpecEntry], - contract_id: &str, - network_passphrase: &str, - ) -> std::io::Result<()> { - let networks = Project::format_networks_object(contract_id, network_passphrase); - let types_and_fns = generate(spec); - fs::OpenOptions::new() - .append(true) - .open(self.0.join("src/index.ts"))? - .write_all(format!("\n\n{networks}\n\n{types_and_fns}").as_bytes()) - } - - fn format_networks_object(contract_id: &str, network_passphrase: &str) -> String { - let network = match network_passphrase { - NETWORK_PASSPHRASE_TESTNET => "testnet", - NETWORK_PASSPHRASE_FUTURENET => "futurenet", - NETWORK_PASSPHRASE_STANDALONE => "standalone", - _ => "unknown", - }; - format!( - r#"export const networks = {{ - {network}: {{ - networkPassphrase: "{network_passphrase}", - contractId: "{contract_id}", - }} -}} as const"# - ) - } -} - -#[cfg(test)] -mod test { - use temp_dir::TempDir; - use walkdir::WalkDir; - - use super::*; - - const EXAMPLE_WASM: &[u8] = include_bytes!( - "../../../../target/wasm32-unknown-unknown/test-wasms/test_custom_types.wasm" - ); - - fn init(root: impl AsRef) -> std::io::Result { - let spec = soroban_spec::read::from_wasm(EXAMPLE_WASM).unwrap(); - let p: Project = root.as_ref().to_path_buf().try_into()?; - p.init( - "test_custom_types", - "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE", - "https://rpc-futurenet.stellar.org:443", - "Test SDF Future Network ; October 2022", - &spec, - ) - .unwrap(); - Ok(p) - } - - // TODO : fix the test below : - // the test below should verify only a certain subset of the files were copied - // rather then the entire directory. - #[ignore] - #[test] - fn test_project_dir_location() { - // TODO: Ensure windows support - if cfg!(windows) { - return; - } - let temp_dir = TempDir::new().unwrap(); - let _: Project = init(temp_dir.path()).unwrap(); - let fixture = PathBuf::from("./fixtures/test_custom_types"); - assert_dirs_equal(temp_dir.path(), &fixture); - } - - #[ignore] - #[test] - fn build_package() { - let root = PathBuf::from("./fixtures/ts"); - std::fs::remove_dir_all(&root).unwrap_or_default(); - std::fs::create_dir_all(&root).unwrap(); - let _: Project = init(&root).unwrap(); - println!("Updated Snapshot!"); - } - - fn assert_dirs_equal>(dir1: P, dir2: P) { - let walker1 = WalkDir::new(&dir1); - let walker2 = WalkDir::new(&dir2); - - let mut paths1: Vec<_> = walker1.into_iter().collect::>().unwrap(); - let mut paths2: Vec<_> = walker2.into_iter().collect::>().unwrap(); - - paths1 - .sort_unstable_by_key(|entry| entry.path().strip_prefix(&dir1).unwrap().to_path_buf()); - paths2 - .sort_unstable_by_key(|entry| entry.path().strip_prefix(&dir2).unwrap().to_path_buf()); - - assert_eq!( - paths1.len(), - paths2.len(), - "{paths1:?}.len() != {paths2:?}.len()" - ); - - for (entry1, entry2) in paths1.iter().zip(paths2.iter()) { - let path1 = entry1.path(); - let path2 = entry2.path(); - - if path1.is_file() && path2.is_file() { - let content1 = fs::read_to_string(path1).unwrap(); - let content2 = fs::read_to_string(path2).unwrap(); - pretty_assertions::assert_eq!(content1, content2, "{:?} != {:?}", path1, path2); - } else if path1.is_dir() && path2.is_dir() { - continue; - } else { - panic!( - "{:?} is not a file", - if path1.is_file() { path2 } else { path1 } - ); - } - } - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/lib.rs b/cmd/crates/soroban-spec-typescript/src/lib.rs deleted file mode 100644 index 790f43b0..00000000 --- a/cmd/crates/soroban-spec-typescript/src/lib.rs +++ /dev/null @@ -1,377 +0,0 @@ -#![allow( - clippy::missing_errors_doc, - clippy::must_use_candidate, - clippy::missing_panics_doc -)] - -use std::{fs, io}; - -use crate::types::Type; -use heck::ToLowerCamelCase; -use itertools::Itertools; -use sha2::{Digest, Sha256}; -use stellar_xdr::curr::{Limits, ScSpecEntry, WriteXdr}; - -use types::Entry; - -use soroban_spec::read::{from_wasm, FromWasmError}; - -pub mod boilerplate; -mod types; -pub mod wrapper; - -#[derive(thiserror::Error, Debug)] -pub enum GenerateFromFileError { - #[error("reading file: {0}")] - Io(io::Error), - #[error("sha256 does not match, expected: {expected}")] - VerifySha256 { expected: String }, - #[error("parsing contract spec: {0}")] - Parse(stellar_xdr::curr::Error), - #[error("getting contract spec: {0}")] - GetSpec(FromWasmError), -} - -pub fn generate_from_file( - file: &str, - verify_sha256: Option<&str>, -) -> Result { - // Read file. - let wasm = fs::read(file).map_err(GenerateFromFileError::Io)?; - - // Produce hash for file. - let sha256 = Sha256::digest(&wasm); - let sha256 = format!("{sha256:x}"); - - if let Some(verify_sha256) = verify_sha256 { - if verify_sha256 != sha256 { - return Err(GenerateFromFileError::VerifySha256 { expected: sha256 }); - } - } - - // Generate code. - let json = generate_from_wasm(&wasm).map_err(GenerateFromFileError::GetSpec)?; - Ok(json) -} - -pub fn generate_from_wasm(wasm: &[u8]) -> Result { - let spec = from_wasm(wasm)?; - let json = generate(&spec); - Ok(json) -} - -fn generate_class(fns: &[Entry], spec: &[ScSpecEntry]) -> String { - let methods = fns.iter().map(entry_to_method).join("\n\n "); - let parsers = fns - .iter() - .filter_map(entry_to_parser) - .map(|(method, parser)| format!("{method}: {parser}")) - .join(",\n "); - let from_jsons = fns - .iter() - .filter_map(entry_to_parser) - .map(|(method, _)| { - format!("{method}: this.txFromJSON>") - }) - .join(",\n "); - let spec = spec - .iter() - .map(|s| format!("\"{}\"", s.to_xdr_base64(Limits::none()).unwrap())) - .join(",\n "); - format!( - r#"export class Contract {{ - spec: ContractSpec; - constructor(public readonly options: ClassOptions) {{ - this.spec = new ContractSpec([ - {spec} - ]); - }} - private readonly parsers = {{ - {parsers} - }}; - private txFromJSON = (json: string): AssembledTransaction => {{ - const {{ method, ...tx }} = JSON.parse(json) - return AssembledTransaction.fromJSON( - {{ - ...this.options, - method, - parseResultXdr: this.parsers[method], - }}, - tx, - ); - }} - public readonly fromJSON = {{ - {from_jsons} - }} - {methods} -}}"#, - ) -} - -pub fn generate(spec: &[ScSpecEntry]) -> String { - let mut collected: Vec<_> = spec.iter().map(Entry::from).collect(); - if !spec.iter().any(is_error_enum) { - collected.push(Entry::ErrorEnum { - doc: String::new(), - name: "Error".to_string(), - cases: vec![], - }); - } - let (fns, other): (Vec<_>, Vec<_>) = collected - .into_iter() - .partition(|entry| matches!(entry, Entry::Function { .. })); - let top = other.iter().map(entry_to_method).join("\n"); - let bottom = generate_class(&fns, spec); - format!("{top}\n\n{bottom}") -} - -fn doc_to_ts_doc(doc: &str, method: Option<&str>) -> String { - let header = if let Some(method) = method { - format!( - r#"/** - * Construct and simulate a {method} transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object."# - ) - } else { - "/**\n ".to_string() - }; - let footer = "\n */\n"; - let body = if doc.is_empty() { - String::new() - } else { - doc.split('\n').join("\n * ") - }; - format!(r#"{header}{body}{footer}"#) -} - -fn is_error_enum(entry: &ScSpecEntry) -> bool { - matches!(entry, ScSpecEntry::UdtErrorEnumV0(_)) -} - -const METHOD_OPTIONS: &str = r"{ - /** - * The fee to pay for the transaction. Default: 100. - */ - fee?: number, - }"; - -fn jsify_name(name: &String) -> String { - name.to_lower_camel_case() -} - -pub fn entry_to_parser(entry: &Entry) -> Option<(String, String)> { - if let Entry::Function { name, outputs, .. } = entry { - let mut is_result = false; - let mut return_type: String; - if outputs.is_empty() { - return_type = "void".to_owned(); - } else if outputs.len() == 1 { - return_type = type_to_ts(&outputs[0]); - is_result = return_type.starts_with("Result<"); - } else { - return_type = format!("readonly [{}]", outputs.iter().map(type_to_ts).join(", ")); - }; - - if is_result { - return_type = return_type - .strip_prefix("Result<") - .unwrap() - .strip_suffix('>') - .unwrap() - .to_owned(); - return_type = format!("Ok<{return_type}> | Err"); - } - - let output = outputs - .first() - .map(|_| format!("this.spec.funcResToNative(\"{name}\", result)")) - .unwrap_or_default(); - let parse_result_xdr = if return_type == "void" { - r"() => {}".to_owned() - } else if is_result { - format!( - r"(result: XDR_BASE64 | Err): {return_type} => {{ - if (result instanceof Err) return result - return new Ok({output}) - }}" - ) - } else { - format!(r"(result: XDR_BASE64): {return_type} => {output}") - }; - let js_name = jsify_name(name); - Some((js_name, parse_result_xdr)) - } else { - None - } -} - -#[allow(clippy::too_many_lines)] -pub fn entry_to_method(entry: &Entry) -> String { - match entry { - Entry::Function { - doc, name, inputs, .. - } => { - let input_vals = inputs.iter().map(func_input_to_arg_name).join(", "); - let input = (!inputs.is_empty()) - .then(|| { - format!( - "{{{input_vals}}}: {{{}}}, ", - inputs.iter().map(func_input_to_ts).join(", ") - ) - }) - .unwrap_or_default(); - let ts_doc = doc_to_ts_doc(doc, Some(name)); - let (js_name, _) = entry_to_parser(entry).unwrap(); - let parsed_scvals = inputs.iter().map(parse_arg_to_scval).join(", "); - let args = - format!("args: this.spec.funcArgsToScVals(\"{name}\", {{{parsed_scvals}}}),"); - let body = format!( - r#"return await AssembledTransaction.fromSimulation({{ - method: '{name}', - {args} - ...options, - ...this.options, - errorTypes: Errors, - parseResultXdr: this.parsers['{js_name}'], - }});"# - ); - format!( - r#" {ts_doc} {js_name} = async ({input}options: {METHOD_OPTIONS} = {{}}) => {{ - {body} - }} -"# - ) - } - Entry::Struct { doc, name, fields } => { - let docs = doc_to_ts_doc(doc, None); - let fields = fields.iter().map(field_to_ts).join("\n "); - format!( - r#"{docs}export interface {name} {{ - {fields} -}} -"# - ) - } - - Entry::TupleStruct { doc, name, fields } => { - let docs = doc_to_ts_doc(doc, None); - let fields = fields.iter().map(type_to_ts).join(", "); - format!("{docs}export type {name} = readonly [{fields}];") - } - - Entry::Union { name, doc, cases } => { - let doc = doc_to_ts_doc(doc, None); - let cases = cases.iter().map(case_to_ts).join(" | "); - - format!( - r#"{doc}export type {name} = {cases}; -"# - ) - } - Entry::Enum { doc, name, cases } => { - let doc = doc_to_ts_doc(doc, None); - let cases = cases.iter().map(enum_case_to_ts).join("\n "); - let name = (name == "Error") - .then(|| format!("{name}s")) - .unwrap_or(name.to_string()); - format!( - r#"{doc}export enum {name} {{ - {cases} -}} -"#, - ) - } - Entry::ErrorEnum { doc, cases, .. } => { - let doc = doc_to_ts_doc(doc, None); - let cases = cases - .iter() - .map(|c| format!("{}: {{message:\"{}\"}}", c.value, c.doc)) - .join(",\n "); - format!( - r#"{doc}export const Errors = {{ -{cases} -}}"# - ) - } - } -} - -fn enum_case_to_ts(case: &types::EnumCase) -> String { - let types::EnumCase { name, value, .. } = case; - format!("{name} = {value},") -} - -fn case_to_ts(case: &types::UnionCase) -> String { - let types::UnionCase { name, values, .. } = case; - format!( - "{{tag: \"{name}\", values: {}}}", - type_to_ts(&Type::Tuple { - elements: values.clone(), - }) - ) -} - -fn field_to_ts(field: &types::StructField) -> String { - let types::StructField { doc, name, value } = field; - let doc = doc_to_ts_doc(doc, None); - let type_ = type_to_ts(value); - format!("{doc}{name}: {type_};") -} - -pub fn func_input_to_ts(input: &types::FunctionInput) -> String { - let types::FunctionInput { name, value, .. } = input; - let type_ = type_to_ts(value); - format!("{name}: {type_}") -} - -pub fn func_input_to_arg_name(input: &types::FunctionInput) -> String { - let types::FunctionInput { name, .. } = input; - name.to_string() -} - -pub fn parse_arg_to_scval(input: &types::FunctionInput) -> String { - let types::FunctionInput { name, value, .. } = input; - match value { - types::Type::Address => format!("{name}: new Address({name})"), - _ => name.to_string(), - } -} - -pub fn type_to_ts(value: &types::Type) -> String { - match value { - types::Type::U64 => "u64".to_owned(), - types::Type::I64 => "i64".to_owned(), - types::Type::U128 => "u128".to_owned(), - types::Type::I128 => "i128".to_owned(), - types::Type::U32 => "u32".to_owned(), - types::Type::I32 => "i32".to_owned(), - types::Type::Bool => "boolean".to_owned(), - types::Type::Symbol | types::Type::String => "string".to_owned(), - types::Type::Map { key, value } => { - format!("Map<{}, {}>", type_to_ts(key), type_to_ts(value)) - } - types::Type::Option { value } => format!("Option<{}>", type_to_ts(value)), - types::Type::Result { value, .. } => { - format!("Result<{}>", type_to_ts(value)) - } - types::Type::Vec { element } => format!("Array<{}>", type_to_ts(element)), - types::Type::Tuple { elements } => { - if elements.is_empty() { - "void".to_owned() - } else { - format!("readonly [{}]", elements.iter().map(type_to_ts).join(", ")) - } - } - types::Type::Custom { name } => name.clone(), - // TODO: Figure out what js type to map this to. There is already an `Error_` one that - // ahalabs have added in the bindings, so.. maybe rename that? - types::Type::Val => "any".to_owned(), - types::Type::Error { .. } => "Error_".to_owned(), - types::Type::Address => "string".to_string(), - types::Type::Bytes | types::Type::BytesN { .. } => "Buffer".to_string(), - types::Type::Void => "void".to_owned(), - types::Type::U256 => "u256".to_string(), - types::Type::I256 => "i256".to_string(), - types::Type::Timepoint => "Timepoint".to_string(), - types::Type::Duration => "Duration".to_string(), - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/.gitignore b/cmd/crates/soroban-spec-typescript/src/project_template/.gitignore deleted file mode 100644 index 72aae85f..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -out/ diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/README.md b/cmd/crates/soroban-spec-typescript/src/project_template/README.md deleted file mode 100644 index 3995e52b..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# INSERT_CONTRACT_NAME_HERE JS - -JS library for interacting with [Soroban](https://soroban.stellar.org/) smart contract `INSERT_CONTRACT_NAME_HERE` via Soroban RPC. - -This library was automatically generated by Soroban CLI using a command similar to: - -```bash -soroban contract bindings ts \ - --rpc-url INSERT_RPC_URL_HERE \ - --network-passphrase "INSERT_NETWORK_PASSPHRASE_HERE" \ - --contract-id INSERT_CONTRACT_ID_HERE \ - --output-dir ./path/to/INSERT_CONTRACT_NAME_HERE -``` - -The network passphrase and contract ID are exported from [index.ts](./src/index.ts) in the `networks` constant. If you are the one who generated this library and you know that this contract is also deployed to other networks, feel free to update `networks` with other valid options. This will help your contract consumers use this library more easily. - -# To publish or not to publish - -This library is suitable for publishing to NPM. You can publish it to NPM using the `npm publish` command. - -But you don't need to publish this library to NPM to use it. You can add it to your project's `package.json` using a file path: - -```json -"dependencies": { - "INSERT_CONTRACT_NAME_HERE": "./path/to/this/folder" -} -``` - -However, we've actually encountered [frustration](https://github.com/stellar/soroban-example-dapp/pull/117#discussion_r1232873560) using local libraries with NPM in this way. Though it seems a bit messy, we suggest generating the library directly to your `node_modules` folder automatically after each install by using a `postinstall` script. We've had the least trouble with this approach. NPM will automatically remove what it sees as erroneous directories during the `install` step, and then regenerate them when it gets to your `postinstall` step, which will keep the library up-to-date with your contract. - -```json -"scripts": { - "postinstall": "soroban contract bindings ts --rpc-url INSERT_RPC_URL_HERE --network-passphrase \"INSERT_NETWORK_PASSPHRASE_HERE\" --id INSERT_CONTRACT_ID_HERE --name INSERT_CONTRACT_NAME_HERE" -} -``` - -Obviously you need to adjust the above command based on the actual command you used to generate the library. - -# Use it - -Now that you have your library up-to-date and added to your project, you can import it in a file and see inline documentation for all of its exported methods: - -```js -import { Contract, networks } from "INSERT_CONTRACT_NAME_HERE" - -const contract = new Contract({ - ...networks.futurenet, // for example; check which networks this library exports - rpcUrl: '...', // use your own, or find one for testing at https://soroban.stellar.org/docs/reference/rpc#public-rpc-providers -}) - -contract.| -``` - -As long as your editor is configured to show JavaScript/TypeScript documentation, you can pause your typing at that `|` to get a list of all exports and inline-documentation for each. It exports a separate [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) function for each method in the smart contract, with documentation for each generated from the comments the contract's author included in the original source code. diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/package.json b/cmd/crates/soroban-spec-typescript/src/project_template/package.json deleted file mode 100644 index e32d0f5e..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.0.0", - "name": "INSERT_CONTRACT_NAME_HERE", - "dependencies": { - "@stellar/freighter-api": "1.7.1", - "buffer": "6.0.3", - "@stellar/stellar-sdk": "11.2.0" - }, - "scripts": { - "build": "node ./scripts/build.mjs" - }, - "exports": { - "require": "./dist/cjs/index.js", - "import": "./dist/esm/index.js" - }, - "typings": "dist/types/index.d.ts", - "devDependencies": { - "typescript": "5.3.3" - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/build.mjs b/cmd/crates/soroban-spec-typescript/src/project_template/scripts/build.mjs deleted file mode 100644 index 15a17042..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/build.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import { spawnSync } from "node:child_process" -import fs from "node:fs" -import path from "node:path" - -const buildDir = "./dist" - -const { error, stderr } = spawnSync("tsc", ["-b", "./scripts/tsconfig.cjs.json", "./scripts/tsconfig.esm.json", "./scripts/tsconfig.types.json"], { stdio: "inherit" }) - -if (error) { - console.error(stderr) - console.error(error) - throw error -} - -function createEsmModulePackageJson() { - fs.readdir(buildDir, function (err, dirs) { - if (err) { - throw err - } - dirs.forEach(function (dir) { - if (dir === "esm") { - // 1. add package.json file with "type": "module" - var packageJsonFile = path.join(buildDir, dir, "/package.json") - if (!fs.existsSync(packageJsonFile)) { - fs.writeFileSync( - packageJsonFile, - '{"type": "module"}', - 'utf8', - err => { if (err) throw err } - ) - } - } - }) - }) -} - -createEsmModulePackageJson() diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.cjs.json b/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.cjs.json deleted file mode 100644 index 542ea86d..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/cjs", - "module": "commonjs" - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.esm.json b/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.esm.json deleted file mode 100644 index 92b45277..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/esm", - "module": "esnext" - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.types.json b/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.types.json deleted file mode 100644 index 8a24fc13..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/scripts/tsconfig.types.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../dist/types", - "declaration": true, - "emitDeclarationOnly": true - } -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts b/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts deleted file mode 100644 index f4f892d4..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/src/assembled-tx.ts +++ /dev/null @@ -1,664 +0,0 @@ -import { - Account, - Address, - Contract, - Operation, - SorobanRpc, - StrKey, - TimeoutInfinite, - TransactionBuilder, - authorizeEntry, - hash, - nativeToScVal, - xdr, - BASE_FEE, -} from "@stellar/stellar-sdk"; -import type { Memo, MemoType, Transaction } from "@stellar/stellar-sdk"; -import { Buffer } from "buffer"; -import type { - ClassOptions, - MethodOptions, - Wallet, - XDR_BASE64, -} from "./method-options.js"; - -export type Tx = Transaction, Operation[]> - -export class ExpiredStateError extends Error { } -export class NeedsMoreSignaturesError extends Error { } -export class WalletDisconnectedError extends Error { } -export class SendResultOnlyError extends Error { } -export class SendFailedError extends Error { } -export class NoUnsignedNonInvokerAuthEntriesError extends Error { } - -type SendTx = SorobanRpc.Api.SendTransactionResponse; -type GetTx = SorobanRpc.Api.GetTransactionResponse; - -export type u32 = number; -export type i32 = number; -export type u64 = bigint; -export type i64 = bigint; -export type u128 = bigint; -export type i128 = bigint; -export type u256 = bigint; -export type i256 = bigint; -export type Option = T | undefined; -export type Typepoint = bigint; -export type Duration = bigint; -export {Address}; - -/// Error interface containing the error message -export interface Error_ { message: string }; - -export interface Result { - unwrap(): T, - unwrapErr(): E, - isOk(): boolean, - isErr(): boolean, -}; - -export class Ok implements Result { - constructor(readonly value: T) { } - unwrapErr(): E { - throw new Error('No error'); - } - unwrap(): T { - return this.value; - } - - isOk(): boolean { - return true; - } - - isErr(): boolean { - return !this.isOk() - } -} - -export class Err implements Result { - constructor(readonly error: E) { } - unwrapErr(): E { - return this.error; - } - unwrap(): never { - throw new Error(this.error.message); - } - - isOk(): boolean { - return false; - } - - isErr(): boolean { - return !this.isOk() - } -} - -export const contractErrorPattern = /Error\(Contract, #(\d+)\)/; - -type AssembledTransactionOptions = MethodOptions & - ClassOptions & { - method: string; - args?: any[]; - parseResultXdr: (xdr: string | xdr.ScVal | Err) => T; - }; - -export const NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF" - -export class AssembledTransaction { - public raw: Tx - private simulation?: SorobanRpc.Api.SimulateTransactionResponse - private simulationResult?: SorobanRpc.Api.SimulateHostFunctionResult - private simulationTransactionData?: xdr.SorobanTransactionData - private server: SorobanRpc.Server - - toJSON() { - return JSON.stringify({ - method: this.options.method, - tx: this.raw?.toXDR(), - simulationResult: { - auth: this.simulationData.result.auth.map(a => a.toXDR('base64')), - retval: this.simulationData.result.retval.toXDR('base64'), - }, - simulationTransactionData: this.simulationData.transactionData.toXDR('base64'), - }) - } - - static fromJSON( - options: Omit, 'args'>, - { tx, simulationResult, simulationTransactionData }: - { - tx: XDR_BASE64, - simulationResult: { - auth: XDR_BASE64[], - retval: XDR_BASE64, - }, - simulationTransactionData: XDR_BASE64, - } - ): AssembledTransaction { - const txn = new AssembledTransaction(options) - txn.raw = TransactionBuilder.fromXDR(tx, options.networkPassphrase) as Tx - txn.simulationResult = { - auth: simulationResult.auth.map(a => xdr.SorobanAuthorizationEntry.fromXDR(a, 'base64')), - retval: xdr.ScVal.fromXDR(simulationResult.retval, 'base64'), - } - txn.simulationTransactionData = xdr.SorobanTransactionData.fromXDR(simulationTransactionData, 'base64') - return txn - } - - private constructor(public options: AssembledTransactionOptions) { - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - } - - static async fromSimulation(options: AssembledTransactionOptions): Promise> { - const tx = new AssembledTransaction(options) - const contract = new Contract(options.contractId); - - tx.raw = new TransactionBuilder(await tx.getAccount(), { - fee: options.fee?.toString(10) ?? BASE_FEE, - networkPassphrase: options.networkPassphrase, - }) - .addOperation(contract.call(options.method, ...(options.args ?? []))) - .setTimeout(TimeoutInfinite) - .build(); - - return await tx.simulate() - } - - simulate = async (): Promise => { - if (!this.raw) throw new Error('Transaction has not yet been assembled') - this.simulation = await this.server.simulateTransaction(this.raw); - - if (SorobanRpc.Api.isSimulationSuccess(this.simulation)) { - this.raw = SorobanRpc.assembleTransaction( - this.raw, - this.simulation - ).build() - } - - return this - } - - get simulationData(): { - result: SorobanRpc.Api.SimulateHostFunctionResult - transactionData: xdr.SorobanTransactionData - } { - if (this.simulationResult && this.simulationTransactionData) { - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData, - } - } - // else, we know we just did the simulation on this machine - const simulation = this.simulation! - if (SorobanRpc.Api.isSimulationError(simulation)) { - throw new Error(`Transaction simulation failed: "${simulation.error}"`) - } - - if (SorobanRpc.Api.isSimulationRestore(simulation)) { - throw new ExpiredStateError(`You need to restore some contract state before you can invoke this method. ${JSON.stringify(simulation, null, 2)}`) - } - - if (!simulation.result) { - throw new Error(`Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify(simulation, null, 2)}`) - } - - // add to object for serialization & deserialization - this.simulationResult = simulation.result - this.simulationTransactionData = simulation.transactionData.build() - - return { - result: this.simulationResult, - transactionData: this.simulationTransactionData!, - } - } - - get result(): T { - try { - return this.options.parseResultXdr(this.simulationData.result.retval) - } catch (e) { - let err = this.parseError(e.toString()) - if (err) return err as T - throw e - } - } - - parseError(errorMessage: string): Err | undefined { - if (!this.options.errorTypes) return - const match = errorMessage.match(contractErrorPattern) - if (!match) return - let i = parseInt(match[1], 10) - let err = this.options.errorTypes[i] - if (err) return new Err(err) - } - - getWallet = async (): Promise => { - return this.options.wallet ?? (await import("@stellar/freighter-api")).default - } - - getPublicKey = async (): Promise => { - const wallet = await this.getWallet() - if (await wallet.isConnected() && await wallet.isAllowed()) { - return (await wallet.getUserInfo()).publicKey - } - } - - /** - * Get account details from the Soroban network for the publicKey currently - * selected in user's wallet. If not connected to Freighter, use placeholder - * null account. - */ - getAccount = async (): Promise => { - const publicKey = await this.getPublicKey() - return publicKey - ? await this.server.getAccount(publicKey) - : new Account(NULL_ACCOUNT, "0") - } - - /** - * Sign the transaction with the `wallet` (default Freighter), then send to - * the network and return a `SentTransaction` that keeps track of all the - * attempts to send and fetch the transaction from the network. - */ - signAndSend = async ({ secondsToWait = 10, force = false }: { - /** - * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). - */ - secondsToWait?: number - /** - * If `true`, sign and send the transaction even if it is a read call. - */ - force?: boolean - } = {}): Promise> => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated') - } - - if (!force && this.isReadCall) { - throw new Error('This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway.') - } - - if (!await this.hasRealInvoker()) { - throw new WalletDisconnectedError('Wallet is not connected') - } - - if (this.raw.source !== (await this.getAccount()).accountId()) { - throw new Error(`You must submit the transaction with the account that originally created it. Please switch to the wallet with "${this.raw.source}" as its public key.`) - } - - if ((await this.needsNonInvokerSigningBy()).length) { - throw new NeedsMoreSignaturesError( - 'Transaction requires more signatures. See `needsNonInvokerSigningBy` for details.' - ) - } - - return await SentTransaction.init(this.options, this, secondsToWait); - } - - getStorageExpiration = async () => { - const entryRes = await this.server.getLedgerEntries( - new Contract(this.options.contractId).getFootprint() - ) - if ( - !entryRes.entries || - !entryRes.entries.length || - !entryRes.entries[0].liveUntilLedgerSeq - ) throw new Error('failed to get ledger entry') - return entryRes.entries[0].liveUntilLedgerSeq - } - - /** - * Get a list of accounts, other than the invoker of the simulation, that - * need to sign auth entries in this transaction. - * - * Soroban allows multiple people to sign a transaction. Someone needs to - * sign the final transaction envelope; this person/account is called the - * _invoker_, or _source_. Other accounts might need to sign individual auth - * entries in the transaction, if they're not also the invoker. - * - * This function returns a list of accounts that need to sign auth entries, - * assuming that the same invoker/source account will sign the final - * transaction envelope as signed the initial simulation. - * - * One at a time, for each public key in this array, you will need to - * serialize this transaction with `toJSON`, send to the owner of that key, - * deserialize the transaction with `txFromJson`, and call - * {@link signAuthEntries}. Then re-serialize and send to the next account - * in this list. - */ - needsNonInvokerSigningBy = async ({ - includeAlreadySigned = false, - }: { - /** - * Whether or not to include auth entries that have already been signed. Default: false - */ - includeAlreadySigned?: boolean - } = {}): Promise => { - if (!this.raw) { - throw new Error('Transaction has not yet been simulated') - } - - // We expect that any transaction constructed by these libraries has a - // single operation, which is an InvokeHostFunction operation. The host - // function being invoked is the contract method call. - if (!("operations" in this.raw)) { - throw new Error( - `Unexpected Transaction type; no operations: ${JSON.stringify(this.raw) - }` - ) - } - const rawInvokeHostFunctionOp = this.raw - .operations[0] as Operation.InvokeHostFunction - - return [...new Set((rawInvokeHostFunctionOp.auth ?? []).filter(entry => - entry.credentials().switch() === - xdr.SorobanCredentialsType.sorobanCredentialsAddress() && - ( - includeAlreadySigned || - entry.credentials().address().signature().switch().name === 'scvVoid' - ) - ).map(entry => StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() - )))] - } - - preImageFor( - entry: xdr.SorobanAuthorizationEntry, - signatureExpirationLedger: number - ): xdr.HashIdPreimage { - const addrAuth = entry.credentials().address() - return xdr.HashIdPreimage.envelopeTypeSorobanAuthorization( - new xdr.HashIdPreimageSorobanAuthorization({ - networkId: hash(Buffer.from(this.options.networkPassphrase)), - nonce: addrAuth.nonce(), - invocation: entry.rootInvocation(), - signatureExpirationLedger, - }), - ) - } - - /** - * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize - * the transaction with `toJSON`, send it to the owner of one of the public keys - * in the map, deserialize with `txFromJSON`, and call this method on their - * machine. Internally, this will use `signAuthEntry` function from connected - * `wallet` for each. - * - * Then, re-serialize the transaction and either send to the next - * `needsNonInvokerSigningBy` owner, or send it back to the original account - * who simulated the transaction so they can {@link sign} the transaction - * envelope and {@link send} it to the network. - * - * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently - * supported! - */ - signAuthEntries = async ( - /** - * When to set each auth entry to expire. Could be any number of blocks in - * the future. Can be supplied as a promise or a raw number. Default: - * contract's current `persistent` storage expiration date/ledger - * number/block. - */ - expiration: number | Promise = this.getStorageExpiration() - ): Promise => { - if (!this.raw) throw new Error('Transaction has not yet been assembled or simulated') - const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy() - - if (!needsNonInvokerSigningBy) throw new NoUnsignedNonInvokerAuthEntriesError('No unsigned non-invoker auth entries; maybe you already signed?') - const publicKey = await this.getPublicKey() - if (!publicKey) throw new Error('Could not get public key from wallet; maybe Freighter is not signed in?') - if (needsNonInvokerSigningBy.indexOf(publicKey) === -1) throw new Error(`No auth entries for public key "${publicKey}"`) - const wallet = await this.getWallet() - - const rawInvokeHostFunctionOp = this.raw - .operations[0] as Operation.InvokeHostFunction - - const authEntries = rawInvokeHostFunctionOp.auth ?? [] - - for (const [i, entry] of authEntries.entries()) { - if ( - entry.credentials().switch() !== - xdr.SorobanCredentialsType.sorobanCredentialsAddress() - ) { - // if the invoker/source account, then the entry doesn't need explicit - // signature, since the tx envelope is already signed by the source - // account, so only check for sorobanCredentialsAddress - continue - } - const pk = StrKey.encodeEd25519PublicKey( - entry.credentials().address().address().accountId().ed25519() - ) - - // this auth entry needs to be signed by a different account - // (or maybe already was!) - if (pk !== publicKey) continue - - authEntries[i] = await authorizeEntry( - entry, - async preimage => Buffer.from( - await wallet.signAuthEntry(preimage.toXDR('base64')), - 'base64' - ), - await expiration, - this.options.networkPassphrase - ) - } - } - - get isReadCall(): boolean { - const authsCount = this.simulationData.result.auth.length; - const writeLength = this.simulationData.transactionData.resources().footprint().readWrite().length - return (authsCount === 0) && (writeLength === 0); - } - - hasRealInvoker = async (): Promise => { - const account = await this.getAccount() - return account.accountId() !== NULL_ACCOUNT - } -} - -/** - * A transaction that has been sent to the Soroban network. This happens in two steps: - * - * 1. `sendTransaction`: initial submission of the transaction to the network. - * This step can run into problems, and will be retried with exponential - * backoff if it does. See all attempts in `sendTransactionResponseAll` and the - * most recent attempt in `sendTransactionResponse`. - * 2. `getTransaction`: once the transaction has been submitted to the network - * successfully, you need to wait for it to finalize to get the results of the - * transaction. This step can also run into problems, and will be retried with - * exponential backoff if it does. See all attempts in - * `getTransactionResponseAll` and the most recent attempt in - * `getTransactionResponse`. - */ -class SentTransaction { - public server: SorobanRpc.Server - public signed: Tx - public sendTransactionResponse?: SendTx - public sendTransactionResponseAll?: SendTx[] - public getTransactionResponse?: GetTx - public getTransactionResponseAll?: GetTx[] - - constructor(public options: AssembledTransactionOptions, public assembled: AssembledTransaction) { - this.server = new SorobanRpc.Server(this.options.rpcUrl, { - allowHttp: this.options.rpcUrl.startsWith("http://"), - }); - this.assembled = assembled - } - - static init = async ( - options: AssembledTransactionOptions, - assembled: AssembledTransaction, - secondsToWait: number = 10 - ): Promise> => { - const tx = new SentTransaction(options, assembled) - return await tx.send(secondsToWait) - } - - private send = async (secondsToWait: number = 10): Promise => { - const wallet = await this.assembled.getWallet() - - this.sendTransactionResponseAll = await withExponentialBackoff( - async (previousFailure) => { - if (previousFailure) { - // Increment transaction sequence number and resimulate before trying again - - // Soroban transaction can only have 1 operation - const op = this.assembled.raw.operations[0] as Operation.InvokeHostFunction; - - this.assembled.raw = new TransactionBuilder(await this.assembled.getAccount(), { - fee: this.assembled.raw.fee, - networkPassphrase: this.options.networkPassphrase, - }) - .setTimeout(TimeoutInfinite) - .addOperation( - Operation.invokeHostFunction({ ...op, auth: op.auth ?? [] }), - ) - .build() - - await this.assembled.simulate() - } - - const signature = await wallet.signTransaction(this.assembled.raw.toXDR(), { - networkPassphrase: this.options.networkPassphrase, - }); - - this.signed = TransactionBuilder.fromXDR( - signature, - this.options.networkPassphrase - ) as Tx - - return this.server.sendTransaction(this.signed) - }, - resp => resp.status !== "PENDING", - secondsToWait - ) - - this.sendTransactionResponse = this.sendTransactionResponseAll[this.sendTransactionResponseAll.length - 1] - - if (this.sendTransactionResponse.status !== "PENDING") { - throw new Error( - `Tried to resubmit transaction for ${secondsToWait - } seconds, but it's still failing. ` + - `All attempts: ${JSON.stringify( - this.sendTransactionResponseAll, - null, - 2 - )}` - ); - } - - const { hash } = this.sendTransactionResponse - - this.getTransactionResponseAll = await withExponentialBackoff( - () => this.server.getTransaction(hash), - resp => resp.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, - secondsToWait - ) - - this.getTransactionResponse = this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1] - if (this.getTransactionResponse.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND) { - console.error( - `Waited ${secondsToWait - } seconds for transaction to complete, but it did not. ` + - `Returning anyway. Check the transaction status manually. ` + - `Sent transaction: ${JSON.stringify( - this.sendTransactionResponse, - null, - 2 - )}\n` + - `All attempts to get the result: ${JSON.stringify( - this.getTransactionResponseAll, - null, - 2 - )}` - ); - } - - return this; - } - - get result(): T { - // 1. check if transaction was submitted and awaited with `getTransaction` - if ( - "getTransactionResponse" in this && - this.getTransactionResponse - ) { - // getTransactionResponse has a `returnValue` field unless it failed - if ("returnValue" in this.getTransactionResponse) { - return this.options.parseResultXdr(this.getTransactionResponse.returnValue!) - } - - // if "returnValue" not present, the transaction failed; return without parsing the result - throw new Error("Transaction failed! Cannot parse result.") - } - - // 2. otherwise, maybe it was merely sent with `sendTransaction` - if (this.sendTransactionResponse) { - const errorResult = this.sendTransactionResponse.errorResult?.result() - if (errorResult) { - throw new SendFailedError( - `Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResult}` - ) - } - throw new SendResultOnlyError( - `Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\`` - ) - } - - // 3. finally, if neither of those are present, throw an error - throw new Error(`Sending transaction failed: ${JSON.stringify(this.assembled)}`) - } -} - -/** - * Keep calling a `fn` for `secondsToWait` seconds, if `keepWaitingIf` is true. - * Returns an array of all attempts to call the function. - */ -async function withExponentialBackoff( - fn: (previousFailure?: T) => Promise, - keepWaitingIf: (result: T) => boolean, - secondsToWait: number, - exponentialFactor = 1.5, - verbose = false, -): Promise { - const attempts: T[] = [] - - let count = 0 - attempts.push(await fn()) - if (!keepWaitingIf(attempts[attempts.length - 1])) return attempts - - const waitUntil = new Date(Date.now() + secondsToWait * 1000).valueOf() - let waitTime = 1000 - let totalWaitTime = waitTime - - while (Date.now() < waitUntil && keepWaitingIf(attempts[attempts.length - 1])) { - count++ - // Wait a beat - if (verbose) { - console.info(`Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${secondsToWait * 1000}ms)`) - } - await new Promise(res => setTimeout(res, waitTime)) - // Exponential backoff - waitTime = waitTime * exponentialFactor; - if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { - waitTime = waitUntil - Date.now() - if (verbose) { - console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`) - } - } - totalWaitTime = waitTime + totalWaitTime - // Try again - attempts.push(await fn(attempts[attempts.length - 1])) - if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { - console.info( - `${count}. Called ${fn}; ${attempts.length - } prev attempts. Most recent: ${JSON.stringify(attempts[attempts.length - 1], null, 2) - }` - ) - } - } - - return attempts -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/src/index.ts b/cmd/crates/soroban-spec-typescript/src/project_template/src/index.ts deleted file mode 100644 index f7ad0b66..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/src/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ContractSpec, Address } from '@stellar/stellar-sdk'; -import { Buffer } from "buffer"; -import { AssembledTransaction, Ok, Err } from './assembled-tx.js'; -import type { - u32, - i32, - u64, - i64, - u128, - i128, - u256, - i256, - Option, - Typepoint, - Duration, - Error_, - Result, -} from './assembled-tx.js'; -import type { ClassOptions, XDR_BASE64 } from './method-options.js'; - -export * from './assembled-tx.js'; -export * from './method-options.js'; - -if (typeof window !== 'undefined') { - //@ts-ignore Buffer exists - window.Buffer = window.Buffer || Buffer; -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/src/method-options.ts b/cmd/crates/soroban-spec-typescript/src/project_template/src/method-options.ts deleted file mode 100644 index d1ff142f..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/src/method-options.ts +++ /dev/null @@ -1,50 +0,0 @@ -// defined this way so typeahead shows full union, not named alias -let responseTypes: 'simulated' | 'full' | undefined -export type ResponseTypes = typeof responseTypes - -export type XDR_BASE64 = string - -export interface Wallet { - isConnected: () => Promise, - isAllowed: () => Promise, - getUserInfo: () => Promise<{ publicKey?: string }>, - signTransaction: (tx: XDR_BASE64, opts?: { - network?: string, - networkPassphrase?: string, - accountToSign?: string, - }) => Promise, - signAuthEntry: ( - entryXdr: XDR_BASE64, - opts?: { - accountToSign?: string; - } - ) => Promise -} - -export type ClassOptions = { - contractId: string - networkPassphrase: string - rpcUrl: string - errorTypes?: Record - /** - * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: - * - * @example - * ```ts - * import freighter from "@stellar/freighter-api"; - * import { Contract } from "INSERT_CONTRACT_NAME_HERE"; - * const contract = new Contract({ - * …, - * wallet: freighter, - * }) - * ``` - */ - wallet?: Wallet -} - -export type MethodOptions = { - /** - * The fee to pay for the transaction. Default: soroban-sdk's BASE_FEE ('100') - */ - fee?: number -} diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/tsconfig.json b/cmd/crates/soroban-spec-typescript/src/project_template/tsconfig.json deleted file mode 100644 index efd4c619..00000000 --- a/cmd/crates/soroban-spec-typescript/src/project_template/tsconfig.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "ESNext", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - // "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - // "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - // "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - }, - "include": [ - "src/*" - ] -} diff --git a/cmd/crates/soroban-spec-typescript/src/types.rs b/cmd/crates/soroban-spec-typescript/src/types.rs deleted file mode 100644 index 02511843..00000000 --- a/cmd/crates/soroban-spec-typescript/src/types.rs +++ /dev/null @@ -1,259 +0,0 @@ -use serde::Serialize; -use stellar_xdr::curr::{ - ScSpecEntry, ScSpecFunctionInputV0, ScSpecTypeDef, ScSpecUdtEnumCaseV0, - ScSpecUdtErrorEnumCaseV0, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseV0, -}; - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct StructField { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecUdtStructFieldV0> for StructField { - fn from(f: &ScSpecUdtStructFieldV0) -> Self { - StructField { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct FunctionInput { - pub doc: String, - pub name: String, - pub value: Type, -} - -impl From<&ScSpecFunctionInputV0> for FunctionInput { - fn from(f: &ScSpecFunctionInputV0) -> Self { - FunctionInput { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - value: (&f.type_).into(), - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct UnionCase { - pub doc: String, - pub name: String, - pub values: Vec, -} - -impl From<&ScSpecUdtUnionCaseV0> for UnionCase { - fn from(c: &ScSpecUdtUnionCaseV0) -> Self { - let (doc, name, values) = match c { - ScSpecUdtUnionCaseV0::VoidV0(v) => ( - v.doc.to_utf8_string_lossy(), - v.name.to_utf8_string_lossy(), - vec![], - ), - ScSpecUdtUnionCaseV0::TupleV0(t) => ( - t.doc.to_utf8_string_lossy(), - t.name.to_utf8_string_lossy(), - t.type_.iter().map(Type::from).collect(), - ), - }; - UnionCase { doc, name, values } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtEnumCaseV0> for EnumCase { - fn from(c: &ScSpecUdtEnumCaseV0) -> Self { - EnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ErrorEnumCase { - pub doc: String, - pub name: String, - pub value: u32, -} - -impl From<&ScSpecUdtErrorEnumCaseV0> for ErrorEnumCase { - fn from(c: &ScSpecUdtErrorEnumCaseV0) -> Self { - ErrorEnumCase { - doc: c.doc.to_utf8_string_lossy(), - name: c.name.to_utf8_string_lossy(), - value: c.value, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Type { - Void, - Val, - U64, - I64, - U32, - I32, - U128, - I128, - U256, - I256, - Bool, - Symbol, - Bytes, - String, - Address, - Timepoint, - Duration, - Map { key: Box, value: Box }, - Option { value: Box }, - Result { value: Box, error: Box }, - Vec { element: Box }, - BytesN { n: u32 }, - Tuple { elements: Vec }, - Error { message: Option }, - Custom { name: String }, -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(tag = "type")] -#[serde(rename_all = "camelCase")] -pub enum Entry { - Function { - doc: String, - name: String, - inputs: Vec, - outputs: Vec, - }, - Struct { - doc: String, - name: String, - fields: Vec, - }, - TupleStruct { - doc: String, - name: String, - fields: Vec, - }, - Union { - doc: String, - name: String, - cases: Vec, - }, - Enum { - doc: String, - name: String, - cases: Vec, - }, - ErrorEnum { - doc: String, - name: String, - cases: Vec, - }, -} - -impl From<&ScSpecTypeDef> for Type { - fn from(spec: &ScSpecTypeDef) -> Self { - match spec { - ScSpecTypeDef::Map(map) => Type::Map { - key: Box::new(Type::from(map.key_type.as_ref())), - value: Box::new(Type::from(map.value_type.as_ref())), - }, - ScSpecTypeDef::Option(opt) => Type::Option { - value: Box::new(Type::from(opt.value_type.as_ref())), - }, - ScSpecTypeDef::Result(res) => Type::Result { - value: Box::new(Type::from(res.ok_type.as_ref())), - error: Box::new(Type::from(res.error_type.as_ref())), - }, - ScSpecTypeDef::Tuple(tuple) => Type::Tuple { - elements: tuple.value_types.iter().map(Type::from).collect(), - }, - ScSpecTypeDef::Vec(vec) => Type::Vec { - element: Box::new(Type::from(vec.element_type.as_ref())), - }, - ScSpecTypeDef::Udt(udt) => Type::Custom { - name: udt.name.to_utf8_string_lossy(), - }, - ScSpecTypeDef::BytesN(b) => Type::BytesN { n: b.n }, - ScSpecTypeDef::Val => Type::Val, - ScSpecTypeDef::U64 => Type::U64, - ScSpecTypeDef::I64 => Type::I64, - ScSpecTypeDef::U32 => Type::U32, - ScSpecTypeDef::I32 => Type::I32, - ScSpecTypeDef::U128 => Type::U128, - ScSpecTypeDef::I128 => Type::I128, - ScSpecTypeDef::U256 => Type::U256, - ScSpecTypeDef::I256 => Type::I256, - ScSpecTypeDef::Bool => Type::Bool, - ScSpecTypeDef::Symbol => Type::Symbol, - ScSpecTypeDef::Error => Type::Error { message: None }, - ScSpecTypeDef::Bytes => Type::Bytes, - ScSpecTypeDef::String => Type::String, - ScSpecTypeDef::Address => Type::Address, - ScSpecTypeDef::Void => Type::Void, - ScSpecTypeDef::Timepoint => Type::Timepoint, - ScSpecTypeDef::Duration => Type::Duration, - } - } -} - -impl From<&ScSpecEntry> for Entry { - fn from(spec: &ScSpecEntry) -> Self { - match spec { - ScSpecEntry::FunctionV0(f) => Entry::Function { - doc: f.doc.to_utf8_string_lossy(), - name: f.name.to_utf8_string_lossy(), - inputs: f.inputs.iter().map(Into::into).collect(), - outputs: f.outputs.iter().map(Into::into).collect(), - }, - ScSpecEntry::UdtStructV0(s) if is_tuple_strukt(s) => Entry::TupleStruct { - doc: s.doc.to_utf8_string_lossy(), - name: s.name.to_utf8_string_lossy(), - fields: s.fields.iter().map(|f| &f.type_).map(Into::into).collect(), - }, - ScSpecEntry::UdtStructV0(s) => Entry::Struct { - doc: s.doc.to_utf8_string_lossy(), - name: s.name.to_utf8_string_lossy(), - fields: s.fields.iter().map(Into::into).collect(), - }, - ScSpecEntry::UdtUnionV0(u) => Entry::Union { - doc: u.doc.to_utf8_string_lossy(), - name: u.name.to_utf8_string_lossy(), - cases: u.cases.iter().map(Into::into).collect(), - }, - ScSpecEntry::UdtEnumV0(e) => Entry::Enum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(Into::into).collect(), - }, - ScSpecEntry::UdtErrorEnumV0(e) => Entry::ErrorEnum { - doc: e.doc.to_utf8_string_lossy(), - name: e.name.to_utf8_string_lossy(), - cases: e.cases.iter().map(Into::into).collect(), - }, - } - } -} - -fn is_tuple_strukt(s: &ScSpecUdtStructV0) -> bool { - !s.fields.is_empty() && s.fields[0].name.to_utf8_string_lossy() == "0" -} diff --git a/cmd/crates/soroban-spec-typescript/src/wrapper.rs b/cmd/crates/soroban-spec-typescript/src/wrapper.rs deleted file mode 100644 index d2a8d60a..00000000 --- a/cmd/crates/soroban-spec-typescript/src/wrapper.rs +++ /dev/null @@ -1,55 +0,0 @@ -use itertools::Itertools; - -use crate::types; - -pub fn type_to_js_xdr(value: &types::Type) -> String { - match value { - types::Type::Val => todo!(), - types::Type::U64 => "xdr.ScVal.scvU64(xdr.Uint64.fromString(i.toString()))".to_string(), - types::Type::I64 => "xdr.ScVal.scvI64(xdr.Int64.fromString(i.toString()))".to_string(), - types::Type::U32 => "xdr.ScVal.scvU32(i)".to_string(), - types::Type::I32 => "xdr.ScVal.scvI32(i)".to_string(), - types::Type::Bool => "xdr.ScVal.scvBool(i)".to_string(), - types::Type::Symbol => "xdr.ScVal.scvSymbol(i)".to_string(), - types::Type::Map { key, value } => format!( - "xdr.ScVal.scvMap(Array.from(i.entries()).map(([key, value]) => {{ - return new xdr.ScMapEntry({{ - key: ((i)=>{})(key), - val: ((i)=>{})(value)}}) - }}))", - type_to_js_xdr(key), - type_to_js_xdr(value) - ), - types::Type::Option { value } => format!( - "(!i) ? {} : {}", - type_to_js_xdr(&types::Type::Void), - type_to_js_xdr(value) - ), - types::Type::Result { value, .. } => type_to_js_xdr(value), - types::Type::Vec { element } => { - format!("xdr.ScVal.scvVec(i.map((i)=>{}))", type_to_js_xdr(element)) - } - types::Type::Tuple { elements } => { - let cases = elements - .iter() - .enumerate() - .map(|(i, e)| format!("((i) => {})(i[{i}])", type_to_js_xdr(e))) - .join(",\n "); - format!("xdr.ScVal.scvVec([{cases}])") - } - - types::Type::Custom { name } => format!("{name}ToXdr(i)"), - types::Type::BytesN { .. } | types::Type::Bytes => "xdr.ScVal.scvBytes(i)".to_owned(), - types::Type::Address => "addressToScVal(i)".to_owned(), - types::Type::Void => "xdr.ScVal.scvVoid()".to_owned(), - types::Type::U128 => "u128ToScVal(i)".to_owned(), - types::Type::I128 => "i128ToScVal(i)".to_owned(), - - types::Type::U256 | types::Type::I256 | types::Type::Timepoint | types::Type::Duration => { - "i".to_owned() - } - // This is case shouldn't happen since we only go xdr -> js for errors - types::Type::Error { .. } => "N/A".to_owned(), - types::Type::String => "xdr.ScVal.scvString(i)".to_owned(), - } -} diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/.env b/cmd/crates/soroban-spec-typescript/ts-tests/.env deleted file mode 100644 index 39b24548..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/.env +++ /dev/null @@ -1,2 +0,0 @@ -SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017" -SOROBAN_RPC_URL="http://localhost:8000/soroban/rpc" diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/.eslintrc.cjs b/cmd/crates/soroban-spec-typescript/ts-tests/.eslintrc.cjs deleted file mode 100644 index 90af706b..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/.eslintrc.cjs +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-env node */ -module.exports = { - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - parser: '@typescript-eslint/parser', - parserOptions: { project: './tsconfig.json' }, - plugins: ['@typescript-eslint'], - root: true, - rules: { - "@typescript-eslint/no-floating-promises": ["error"] - }, -}; diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/.gitignore b/cmd/crates/soroban-spec-typescript/ts-tests/.gitignore deleted file mode 100644 index 878823de..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build -node_modules -yarn.lock -contract-*.txt diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh b/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh deleted file mode 100755 index 621be2bc..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -# read .env file, but prefer explicitly set environment variables -IFS=$'\n' -for l in $(cat .env); do - IFS='=' read -ra VARVAL <<< "$l" - # If variable with such name already exists, preserves its value - eval "export ${VARVAL[0]}=\${${VARVAL[0]}:-${VARVAL[1]}}" -done -unset IFS - -echo Network -echo " RPC: $SOROBAN_RPC_URL" -echo " Passphrase: \"$SOROBAN_NETWORK_PASSPHRASE\"" - -NETWORK_STATUS=$(curl -s -X POST "http://localhost:8000/soroban/rpc" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 8675309, "method": "getHealth" }' | sed 's/.*"status":"\(.*\)".*/\1/') || { echo "Make sure you're running local RPC network on localhost:8000" && exit 1; } -echo " Status: $NETWORK_STATUS" - -if [[ "$NETWORK_STATUS" != "healthy" ]]; then - echo "Network is not healthy (not running?), exiting" - exit 1 -fi - -# Print command before executing, from https://stackoverflow.com/a/23342259/249801 -# Discussion: https://github.com/stellar/soroban-tools/pull/1034#pullrequestreview-1690667116 -exe() { echo"${@/eval/}" ; "$@" ; } - -function fund_all() { - exe eval "./soroban keys generate root" - exe eval "./soroban keys fund root" - exe eval "./soroban keys generate alice" - exe eval "./soroban keys fund alice" - exe eval "./soroban keys generate bob" - exe eval "./soroban keys fund bob" -} -function upload() { - exe eval "(./soroban contract $1 --source root --wasm $2 --ignore-checks) > $3" -} -function deploy() { - exe eval "(./soroban contract deploy --source root --wasm-hash $(cat $1) --ignore-checks) > $2" -} -function deploy_all() { - upload deploy ../../../../target/wasm32-unknown-unknown/test-wasms/test_custom_types.wasm contract-id-custom-types.txt - upload deploy ../../../../target/wasm32-unknown-unknown/test-wasms/test_hello_world.wasm contract-id-hello-world.txt - upload deploy ../../../../target/wasm32-unknown-unknown/test-wasms/test_swap.wasm contract-id-swap.txt - upload install ../../../../target/wasm32-unknown-unknown/test-wasms/test_token.wasm contract-token-hash.txt - deploy contract-token-hash.txt contract-id-token-a.txt - deploy contract-token-hash.txt contract-id-token-b.txt -} -function initialize() { - exe eval "./soroban contract invoke --source root --id $(cat $1) -- initialize --admin $(./soroban keys address root) --decimal 0 --name 'Token $2' --symbol '$2'" -} -function initialize_all() { - initialize contract-id-token-a.txt A - initialize contract-id-token-b.txt B -} -function bind() { - exe eval "./soroban contract bindings typescript --contract-id $(cat $1) --output-dir ./node_modules/$2 --overwrite" -} -function bind_all() { - bind contract-id-custom-types.txt test-custom-types - bind contract-id-hello-world.txt test-hello-world - bind contract-id-swap.txt test-swap - bind contract-id-token-a.txt token -} - -function mint() { - exe eval "./soroban contract invoke --source root --id $(cat $1) -- mint --amount 2000000 --to $(./soroban keys address $2)" -} -function mint_all() { - mint contract-id-token-a.txt alice - mint contract-id-token-b.txt bob -} - -fund_all -deploy_all -initialize_all -mint_all -bind_all diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json deleted file mode 100644 index 36f5cdd6..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json +++ /dev/null @@ -1,3405 +0,0 @@ -{ - "name": "ts-tests", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "hasInstallScript": true, - "devDependencies": { - "@ava/typescript": "^4.1.0", - "@stellar/stellar-sdk": "11.2.0", - "@types/node": "^20.4.9", - "@typescript-eslint/eslint-plugin": "^6.10.0", - "@typescript-eslint/parser": "^6.10.0", - "ava": "^5.3.1", - "dotenv": "^16.3.1", - "eslint": "^8.53.0", - "typescript": "^5.3.3" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@ava/typescript": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ava/typescript/-/typescript-4.1.0.tgz", - "integrity": "sha512-1iWZQ/nr9iflhLK9VN8H+1oDZqe93qxNnyYUz+jTzkYPAHc5fdZXBrqmNIgIfFhWYXK5OaQ5YtC7OmLeTNhVEg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "execa": "^7.1.1" - }, - "engines": { - "node": "^14.19 || ^16.15 || ^18 || ^20" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@stellar/js-xdr": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.0.1.tgz", - "integrity": "sha512-dp5Eh7Nr1YjiIeqpdkj2cQYxfoPudDAH3ck8MWggp48Htw66Z/hUssNYUQG/OftLjEmHT90Z/dtey2Y77DOxIw==", - "dev": true - }, - "node_modules/@stellar/stellar-base": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-10.0.1.tgz", - "integrity": "sha512-BDbx7VHOEQh+4J3Q+gStNXgPaNckVFmD4aOlBBGwxlF6vPFmVnW8IoJdkX7T58zpX55eWI6DXvEhDBlrqTlhAQ==", - "dev": true, - "dependencies": { - "@stellar/js-xdr": "^3.0.1", - "base32.js": "^0.1.0", - "bignumber.js": "^9.1.2", - "buffer": "^6.0.3", - "sha.js": "^2.3.6", - "tweetnacl": "^1.0.3" - }, - "optionalDependencies": { - "sodium-native": "^4.0.1" - } - }, - "node_modules/@stellar/stellar-sdk": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-11.2.0.tgz", - "integrity": "sha512-qInRR+mLLl9O/AI6Q+Sr19RZeYJtlNoJQJi3pch5BYoMvVhjO8IU8AhHADP//Zmc2osyogwPuqXBiFdaGlfHWA==", - "dev": true, - "dependencies": { - "@stellar/stellar-base": "10.0.1", - "axios": "^1.6.5", - "bignumber.js": "^9.1.2", - "eventsource": "^2.0.2", - "randombytes": "^2.1.0", - "toml": "^3.0.0", - "urijs": "^1.19.1" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", - "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", - "integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/type-utils": "6.14.0", - "@typescript-eslint/utils": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", - "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", - "integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", - "integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.14.0", - "@typescript-eslint/utils": "6.14.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", - "integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", - "integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/visitor-keys": "6.14.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", - "integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.14.0", - "@typescript-eslint/types": "6.14.0", - "@typescript-eslint/typescript-estree": "6.14.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", - "integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.14.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", - "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", - "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", - "dev": true, - "dependencies": { - "clean-stack": "^4.0.0", - "indent-string": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/arrgv": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", - "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/arrify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", - "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/ava": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz", - "integrity": "sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.2", - "acorn-walk": "^8.2.0", - "ansi-styles": "^6.2.1", - "arrgv": "^1.0.2", - "arrify": "^3.0.0", - "callsites": "^4.0.0", - "cbor": "^8.1.0", - "chalk": "^5.2.0", - "chokidar": "^3.5.3", - "chunkd": "^2.0.1", - "ci-info": "^3.8.0", - "ci-parallel-vars": "^1.0.1", - "clean-yaml-object": "^0.1.0", - "cli-truncate": "^3.1.0", - "code-excerpt": "^4.0.0", - "common-path-prefix": "^3.0.0", - "concordance": "^5.0.4", - "currently-unhandled": "^0.4.1", - "debug": "^4.3.4", - "emittery": "^1.0.1", - "figures": "^5.0.0", - "globby": "^13.1.4", - "ignore-by-default": "^2.1.0", - "indent-string": "^5.0.0", - "is-error": "^2.2.2", - "is-plain-object": "^5.0.0", - "is-promise": "^4.0.0", - "matcher": "^5.0.0", - "mem": "^9.0.2", - "ms": "^2.1.3", - "p-event": "^5.0.1", - "p-map": "^5.5.0", - "picomatch": "^2.3.1", - "pkg-conf": "^4.0.0", - "plur": "^5.1.0", - "pretty-ms": "^8.0.0", - "resolve-cwd": "^3.0.0", - "stack-utils": "^2.0.6", - "strip-ansi": "^7.0.1", - "supertap": "^3.0.1", - "temp-dir": "^3.0.0", - "write-file-atomic": "^5.0.1", - "yargs": "^17.7.2" - }, - "bin": { - "ava": "entrypoints/cli.mjs" - }, - "engines": { - "node": ">=14.19 <15 || >=16.15 <17 || >=18" - }, - "peerDependencies": { - "@ava/typescript": "*" - }, - "peerDependenciesMeta": { - "@ava/typescript": { - "optional": true - } - } - }, - "node_modules/ava/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ava/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base32.js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", - "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/callsites": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz", - "integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chunkd": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", - "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", - "dev": true - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/ci-parallel-vars": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", - "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", - "dev": true - }, - "node_modules/clean-stack": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", - "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clean-yaml-object": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", - "integrity": "sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", - "dev": true, - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", - "dev": true, - "dependencies": { - "convert-to-spaces": "^2.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", - "dev": true, - "dependencies": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - }, - "engines": { - "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" - } - }, - "node_modules/convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", - "dev": true, - "dependencies": { - "array-find-index": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "dependencies": { - "time-zone": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/emittery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.1.tgz", - "integrity": "sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", - "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", - "dev": true, - "engines": { - "node": ">=10 <11 || >=12 <13 || >=14" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/irregular-plurals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-error": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", - "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/load-json-file": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", - "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/matcher": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", - "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "dependencies": { - "blueimp-md5": "^2.10.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mem": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", - "integrity": "sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==", - "dev": true, - "dependencies": { - "map-age-cleaner": "^0.1.3", - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sindresorhus/mem?sponsor=1" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-gyp-build": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", - "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", - "dev": true, - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-event": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz", - "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==", - "dev": true, - "dependencies": { - "p-timeout": "^5.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", - "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", - "dev": true, - "dependencies": { - "aggregate-error": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-ms": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", - "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-conf": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-4.0.0.tgz", - "integrity": "sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==", - "dev": true, - "dependencies": { - "find-up": "^6.0.0", - "load-json-file": "^7.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/pkg-conf/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/plur": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", - "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", - "dev": true, - "dependencies": { - "irregular-plurals": "^3.3.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-ms": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", - "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", - "dev": true, - "dependencies": { - "parse-ms": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/sodium-native": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.4.tgz", - "integrity": "sha512-faqOKw4WQKK7r/ybn6Lqo1F9+L5T6NlBJJYvpxbZPetpWylUVqz449mvlwIBKBqxEHbWakWuOlUt8J3Qpc4sWw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supertap": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", - "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", - "dev": true, - "dependencies": { - "indent-string": "^5.0.0", - "js-yaml": "^3.14.1", - "serialize-error": "^7.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/supertap/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/supertap/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", - "dev": true - }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urijs": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", - "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", - "dev": true - }, - "node_modules/well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package.json b/cmd/crates/soroban-spec-typescript/ts-tests/package.json deleted file mode 100644 index c06446de..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "private": true, - "type": "module", - "scripts": { - "lint": "eslint src/*", - "postinstall": "./initialize.sh", - "test": "npm run lint && ava" - }, - "devDependencies": { - "@ava/typescript": "^4.1.0", - "@types/node": "^20.4.9", - "@typescript-eslint/eslint-plugin": "^6.10.0", - "@typescript-eslint/parser": "^6.10.0", - "ava": "^5.3.1", - "dotenv": "^16.3.1", - "eslint": "^8.53.0", - "@stellar/stellar-sdk": "11.2.0", - "typescript": "^5.3.3" - }, - "ava": { - "typescript": { - "rewritePaths": { - "src/": "build/" - }, - "compile": "tsc" - }, - "require": [ - "dotenv/config" - ] - } -} diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/soroban b/cmd/crates/soroban-spec-typescript/ts-tests/soroban deleted file mode 100755 index d98f247c..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/soroban +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -cargo run --quiet -p soroban-cli -- "$@" diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-custom-types.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/test-custom-types.ts deleted file mode 100644 index 3b07dc3d..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-custom-types.ts +++ /dev/null @@ -1,187 +0,0 @@ -import test from 'ava' -import { root, rpcUrl, wallet } from './util.js' -import { Contract, Ok, Err, networks } from 'test-custom-types' - -const publicKey = root.keypair.publicKey(); - -const contract = new Contract({ ...networks.standalone, rpcUrl, wallet }); - -test('hello', async t => { - const { result } = await contract.hello({ hello: 'tests' }) - t.is(result, 'tests') -}) - -test('woid', async t => { - t.is((await contract.woid()).result, undefined) -}) - -test('u32_fail_on_even', async t => { - t.deepEqual( - (await contract.u32FailOnEven({ u32_: 1 })).result, - new Ok(1) - ) - t.deepEqual( - (await contract.u32FailOnEven({ u32_: 0 })).result, - new Err({ message: "Please provide an odd number" }) - ) -}) - -test('u32', async t => { - t.is((await contract.u32({ u32_: 1 })).result, 1) -}) - -test('i32', async t => { - t.is((await contract.i32({ i32_: 1 })).result, 1) -}) - -test('i64', async t => { - t.is((await contract.i64({ i64_: 1n })).result, 1n) -}) - -test("strukt_hel", async (t) => { - const test = { a: 0, b: true, c: "world" } - t.deepEqual((await contract.struktHel({ strukt: test })).result, ["Hello", "world"]) -}) - -test("strukt", async (t) => { - const test = { a: 0, b: true, c: "hello" } - t.deepEqual((await contract.strukt({ strukt: test })).result, test) -}) - -test('simple first', async t => { - const arg = { tag: 'First', values: undefined } as const - const ret = { tag: 'First' } - t.deepEqual((await contract.simple({ simple: arg })).result, ret) -}) - -test('simple second', async t => { - const arg = { tag: 'Second', values: undefined } as const - const ret = { tag: 'Second' } - t.deepEqual((await contract.simple({ simple: arg })).result, ret) -}) - -test('simple third', async t => { - const arg = { tag: 'Third', values: undefined } as const - const ret = { tag: 'Third' } - t.deepEqual((await contract.simple({ simple: arg })).result, ret) -}) - -test('complex with struct', async t => { - const arg = { tag: 'Struct', values: [{ a: 0, b: true, c: 'hello' }] } as const - const ret = { tag: 'Struct', values: [{ a: 0, b: true, c: 'hello' }] } - t.deepEqual((await contract.complex({ complex: arg })).result, ret) -}) - -test('complex with tuple', async t => { - const arg = { tag: 'Tuple', values: [[{ a: 0, b: true, c: 'hello' }, { tag: 'First', values: undefined }]] } as const - const ret = { tag: 'Tuple', values: [[{ a: 0, b: true, c: 'hello' }, { tag: 'First' }]] } - t.deepEqual((await contract.complex({ complex: arg })).result, ret) -}) - -test('complex with enum', async t => { - const arg = { tag: 'Enum', values: [{ tag: 'First', values: undefined }] } as const - const ret = { tag: 'Enum', values: [{ tag: 'First' }] } - t.deepEqual((await contract.complex({ complex: arg })).result, ret) -}) - -test('complex with asset', async t => { - const arg = { tag: 'Asset', values: [publicKey, 1n] } as const - const ret = { tag: 'Asset', values: [publicKey, 1n] } - t.deepEqual((await contract.complex({ complex: arg })).result, ret) -}) - -test('complex with void', async t => { - const arg = { tag: 'Void', values: undefined } as const - const ret = { tag: 'Void' } - t.deepEqual((await contract.complex({ complex: arg })).result, ret) -}) - -test('addresse', async t => { - t.deepEqual((await contract.addresse({ addresse: publicKey })).result, publicKey) -}) - -test('bytes', async t => { - const bytes = Buffer.from('hello') - t.deepEqual((await contract.bytes({ bytes })).result, bytes) -}) - -test('bytes_n', async t => { - const bytes_n = Buffer.from('123456789') // what's the correct way to construct bytes_n? - t.deepEqual((await contract.bytesN({ bytes_n })).result, bytes_n) -}) - -test('card', async t => { - const card = 11 - t.is((await contract.card({ card })).result, card) -}) - -test('boolean', async t => { - t.is((await contract.boolean({ boolean: true })).result, true) -}) - -test('not', async t => { - t.is((await contract.not({ boolean: true })).result, false) -}) - -test('i128', async t => { - t.is((await contract.i128({ i128: -1n })).result, -1n) -}) - -test('u128', async t => { - t.is((await contract.u128({ u128: 1n })).result, 1n) -}) - -test('multi_args', async t => { - t.is((await contract.multiArgs({ a: 1, b: true })).result, 1) - t.is((await contract.multiArgs({ a: 1, b: false })).result, 0) -}) - -test('map', async t => { - const map = new Map() - map.set(1, true) - map.set(2, false) - // map.set(3, 'hahaha') // should throw an error - const ret = Array.from(map.entries()) - t.deepEqual((await contract.map({ map })).result, ret) -}) - -test('vec', async t => { - const vec = [1, 2, 3] - t.deepEqual((await contract.vec({ vec })).result, vec) -}) - -test('tuple', async t => { - const tuple = ['hello', 1] as const - t.deepEqual((await contract.tuple({ tuple })).result, tuple) -}) - -test('option', async t => { - // this makes sense - t.deepEqual((await contract.option({ option: 1 })).result, 1) - - // this passes but shouldn't - t.deepEqual((await contract.option({ option: undefined })).result, undefined) - - // this is the behavior we probably want, but fails - // t.deepEqual(await contract.option(), undefined) // typing and implementation require the object - // t.deepEqual((await contract.option({})).result, undefined) // typing requires argument; implementation would be fine with this - // t.deepEqual((await contract.option({ option: undefined })).result, undefined) -}) - -test('u256', async t => { - t.is((await contract.u256({ u256: 1n })).result, 1n) -}) - -test('i256', async t => { - t.is((await contract.i256({ i256: -1n })).result, -1n) -}) - -test('string', async t => { - t.is((await contract.string({ string: 'hello' })).result, 'hello') -}) - -test('tuple_strukt', async t => { - const arg = [{ a: 0, b: true, c: 'hello' }, { tag: 'First', values: undefined }] as const - const res = [{ a: 0, b: true, c: 'hello' }, { tag: 'First' }] - t.deepEqual((await contract.tupleStrukt({ tuple_strukt: arg })).result, res) -}) diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-deserialized-transaction.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/test-deserialized-transaction.ts deleted file mode 100644 index f6152d2c..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-deserialized-transaction.ts +++ /dev/null @@ -1,16 +0,0 @@ -import test from "ava" -import { wallet, rpcUrl } from "./util.js" -import { Contract, networks } from "test-hello-world" - -const contract = new Contract({ ...networks.standalone, rpcUrl, wallet }) - -test("has correctly-typed result", async (t) => { - const initial = await contract.hello({ world: "tests" }) - t.is(initial.result[0], "Hello") - t.is(initial.result[1], "tests") - - const serialized = initial.toJSON() - const deserialized = contract.fromJSON.hello(serialized) - t.is(deserialized.result[0], "Hello") // throws TS error if `result` is of type `unknown` - t.is(deserialized.result[1], "tests") // throws TS error if `result` is of type `unknown` -}); diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-hello-world.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/test-hello-world.ts deleted file mode 100644 index e658c455..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-hello-world.ts +++ /dev/null @@ -1,26 +0,0 @@ -import test from "ava"; -import { root, wallet, rpcUrl } from "./util.js"; -import { Contract, networks } from "test-hello-world"; - -const contract = new Contract({ ...networks.standalone, rpcUrl, wallet }); - -test("hello", async (t) => { - t.deepEqual((await contract.hello({ world: "tests" })).result, ["Hello", "tests"]); -}); - -test("auth", async (t) => { - t.deepEqual( - (await contract.auth({ - addr: root.keypair.publicKey(), - world: 'lol' - })).result, - root.keypair.publicKey() - ) -}); - -test("inc", async (t) => { - const { result: startingBalance } = await contract.getCount() - const inc = await contract.inc() - t.is((await inc.signAndSend()).result, startingBalance + 1) - t.is((await contract.getCount()).result, startingBalance + 1) -}); diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-methods-as-args.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/test-methods-as-args.ts deleted file mode 100644 index afa3b651..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-methods-as-args.ts +++ /dev/null @@ -1,12 +0,0 @@ -import test from "ava"; -import { wallet, rpcUrl } from "./util.js"; -import { Contract, networks } from "test-hello-world"; - -const contract = new Contract({ ...networks.standalone, rpcUrl, wallet }); - -// this test checks that apps can pass methods as arguments to other methods and have them still work -const hello = contract.hello - -test("hello", async (t) => { - t.deepEqual((await hello({ world: "tests" })).result, ["Hello", "tests"]); -}); diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-swap.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/test-swap.ts deleted file mode 100644 index a473cb4d..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/test-swap.ts +++ /dev/null @@ -1,134 +0,0 @@ -import test from "ava" -import { SorobanRpc, xdr } from '@stellar/stellar-sdk' -import { wallet, rpcUrl, alice, bob, networkPassphrase, root, Wallet } from "./util.js" -import { Contract as Token } from "token" -import { Contract as Swap, networks, NeedsMoreSignaturesError } from "test-swap" -import fs from "node:fs" - -const tokenAId = fs.readFileSync(new URL("../contract-id-token-a.txt", import.meta.url), "utf8").trim() -const tokenBId = fs.readFileSync(new URL("../contract-id-token-b.txt", import.meta.url), "utf8").trim() - -// `root` is the invoker of all contracts -const tokenA = new Token({ - contractId: tokenAId, - networkPassphrase, - rpcUrl, - wallet, -}) -const tokenB = new Token({ - contractId: tokenBId, - networkPassphrase, - rpcUrl, - wallet, -}) -function swapContractAs(invoker: typeof root | typeof alice | typeof bob) { - return new Swap({ - ...networks.standalone, - rpcUrl, - wallet: new Wallet(invoker.keypair.publicKey()), - }) -} - -const amountAToSwap = 2n -const amountBToSwap = 1n -const alicePk = alice.keypair.publicKey() -const bobPk = bob.keypair.publicKey() - -test('calling `signAndSend()` too soon throws descriptive error', async t => { - const swapContract = swapContractAs(root) - const tx = await swapContract.swap({ - a: alicePk, - b: bobPk, - token_a: tokenAId, - token_b: tokenBId, - amount_a: amountAToSwap, - min_a_for_b: amountAToSwap, - amount_b: amountBToSwap, - min_b_for_a: amountBToSwap, - }) - const error = await t.throwsAsync(tx.signAndSend()) - t.true(error instanceof NeedsMoreSignaturesError, `error is not of type 'NeedsMoreSignaturesError'; instead it is of type '${error?.constructor.name}'`) - if (error) t.regex(error.message, /needsNonInvokerSigningBy/) -}) - -test('alice swaps bob 10 A for 1 B', async t => { - const swapContractAsRoot = swapContractAs(root) - const [ - { result: aliceStartingABalance }, - { result: aliceStartingBBalance }, - { result: bobStartingABalance }, - { result: bobStartingBBalance }, - ] = await Promise.all([ - tokenA.balance({ id: alicePk }), - tokenB.balance({ id: alicePk }), - tokenA.balance({ id: bobPk }), - tokenB.balance({ id: bobPk }), - ]) - t.true(aliceStartingABalance >= amountAToSwap, `alice does not have enough Token A! aliceStartingABalance: ${aliceStartingABalance}`) - t.true(bobStartingBBalance >= amountBToSwap, `bob does not have enough Token B! bobStartingBBalance: ${bobStartingBBalance}`) - - const tx = await swapContractAsRoot.swap({ - a: alicePk, - b: bobPk, - token_a: tokenAId, - token_b: tokenBId, - amount_a: amountAToSwap, - min_a_for_b: amountAToSwap, - amount_b: amountBToSwap, - min_b_for_a: amountBToSwap, - }) - - const needsNonInvokerSigningBy = await tx.needsNonInvokerSigningBy() - t.is(needsNonInvokerSigningBy.length, 2) - t.is(needsNonInvokerSigningBy.indexOf(alicePk), 0, 'needsNonInvokerSigningBy does not have alice\'s public key!') - t.is(needsNonInvokerSigningBy.indexOf(bobPk), 1, 'needsNonInvokerSigningBy does not have bob\'s public key!') - - - // root serializes & sends to alice - const jsonFromRoot = tx.toJSON() - const txAlice = swapContractAs(alice).fromJSON.swap(jsonFromRoot) - await txAlice.signAuthEntries() - - // alice serializes & sends to bob - const jsonFromAlice = txAlice.toJSON() - const txBob = swapContractAs(bob).fromJSON.swap(jsonFromAlice) - await txBob.signAuthEntries() - - // bob serializes & sends back to root - const jsonFromBob = txBob.toJSON() - const txRoot = swapContractAsRoot.fromJSON.swap(jsonFromBob) - const result = await txRoot.signAndSend() - - t.truthy(result.sendTransactionResponse, `tx failed: ${JSON.stringify(result, null, 2)}`) - t.is(result.sendTransactionResponse!.status, 'PENDING', `tx failed: ${JSON.stringify(result, null, 2)}`) - t.truthy(result.getTransactionResponseAll?.length, `tx failed: ${JSON.stringify(result.getTransactionResponseAll, null, 2)}`) - t.not(result.getTransactionResponse!.status, 'FAILED', `tx failed: ${JSON.stringify( - ((result.getTransactionResponse as SorobanRpc.Api.GetFailedTransactionResponse) - .resultXdr.result().value() as xdr.OperationResult[] - ).map(op => - op.value()?.value().switch() - ), null, 2)}` - ) - t.is( - result.getTransactionResponse!.status, - SorobanRpc.Api.GetTransactionStatus.SUCCESS, - `tx failed: ${JSON.stringify(result.getTransactionResponse, null, 2)}` - ) - - t.is( - (await tokenA.balance({ id: alicePk })).result, - aliceStartingABalance - amountAToSwap - ) - t.is( - (await tokenB.balance({ id: alicePk })).result, - aliceStartingBBalance + amountBToSwap - ) - t.is( - (await tokenA.balance({ id: bobPk })).result, - bobStartingABalance + amountAToSwap - ) - t.is( - (await tokenB.balance({ id: bobPk })).result, - bobStartingBBalance - amountBToSwap - ) -}) diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts deleted file mode 100644 index d5539fd1..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { Keypair, TransactionBuilder, hash } from "@stellar/stellar-sdk"; -import { Address } from 'test-custom-types' - -const rootKeypair = Keypair.fromSecret(spawnSync("./soroban", ["keys", "show", "root"], { shell: true, encoding: "utf8" }).stdout.trim()); -const aliceKeypair = Keypair.fromSecret(spawnSync("./soroban", ["keys", "show", "alice"], { shell: true, encoding: "utf8" }).stdout.trim()); -const bobKeypair = Keypair.fromSecret(spawnSync("./soroban", ["keys", "show", "bob"], { shell: true, encoding: "utf8" }).stdout.trim()); - -export const root = { - keypair: rootKeypair, - address: Address.fromString(rootKeypair.publicKey()), -} - -export const alice = { - keypair: aliceKeypair, - address: Address.fromString(aliceKeypair.publicKey()), -} - -export const bob = { - keypair: bobKeypair, - address: Address.fromString(bobKeypair.publicKey()), -} - -function getKeypair(pk: string): Keypair { - return Keypair.fromSecret({ - [root.keypair.publicKey()]: root.keypair.secret(), - [alice.keypair.publicKey()]: alice.keypair.secret(), - [bob.keypair.publicKey()]: bob.keypair.secret(), - }[pk]) -} - -export const rpcUrl = process.env.SOROBAN_RPC_URL ?? "http://localhost:8000/"; -export const networkPassphrase = process.env.SOROBAN_NETWORK_PASSPHRASE ?? "Standalone Network ; February 2017"; - -export class Wallet { - constructor(private publicKey: string) {} - isConnected = () => Promise.resolve(true) - isAllowed = () => Promise.resolve(true) - getUserInfo = () => Promise.resolve({ publicKey: this.publicKey }) - signTransaction = async (tx: string) => { - const t = TransactionBuilder.fromXDR(tx, networkPassphrase); - t.sign(getKeypair(this.publicKey)); - return t.toXDR(); - } - signAuthEntry = async ( - entryXdr: string, - opts?: { - accountToSign?: string, - } - ): Promise => { - return getKeypair(opts?.accountToSign ?? this.publicKey) - .sign(hash(Buffer.from(entryXdr, "base64"))) - .toString('base64') - } -} - -export const wallet = new Wallet(root.keypair.publicKey()) diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/tsconfig.json b/cmd/crates/soroban-spec-typescript/ts-tests/tsconfig.json deleted file mode 100644 index 119437bc..00000000 --- a/cmd/crates/soroban-spec-typescript/ts-tests/tsconfig.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "nodenext", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./build", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} From a7d2b154f2d1ac6be675faf29f03d5dac90a190b Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 17:16:10 -0800 Subject: [PATCH 5/7] Use external soroban-cli crate --- .github/workflows/rust.yml | 2 +- Cargo.lock | 457 +++---- Cargo.toml | 8 +- Makefile | 1 - cmd/soroban-cli/Cargo.toml | 106 -- cmd/soroban-cli/README.md | 28 - cmd/soroban-cli/build.rs | 3 - cmd/soroban-cli/src/bin/doc-gen.rs | 36 - cmd/soroban-cli/src/bin/main.rs | 51 - cmd/soroban-cli/src/commands/completion.rs | 32 - .../src/commands/config/locator.rs | 358 ------ cmd/soroban-cli/src/commands/config/mod.rs | 95 -- cmd/soroban-cli/src/commands/config/secret.rs | 143 --- .../src/commands/contract/asset.rs | 27 - .../src/commands/contract/bindings.rs | 38 - .../src/commands/contract/bindings/json.rs | 29 - .../src/commands/contract/bindings/rust.rs | 39 - .../commands/contract/bindings/typescript.rs | 131 -- .../src/commands/contract/build.rs | 194 --- .../src/commands/contract/deploy.rs | 28 - .../src/commands/contract/deploy/asset.rs | 155 --- .../src/commands/contract/deploy/wasm.rs | 228 ---- .../src/commands/contract/extend.rs | 192 --- .../src/commands/contract/fetch.rs | 185 --- cmd/soroban-cli/src/commands/contract/id.rs | 28 - .../src/commands/contract/id/asset.rs | 36 - .../src/commands/contract/id/wasm.rs | 68 - .../src/commands/contract/inspect.rs | 49 - .../src/commands/contract/install.rs | 223 ---- .../src/commands/contract/invoke.rs | 484 ------- cmd/soroban-cli/src/commands/contract/mod.rs | 158 --- .../src/commands/contract/optimize.rs | 80 -- cmd/soroban-cli/src/commands/contract/read.rs | 180 --- .../src/commands/contract/restore.rs | 206 --- cmd/soroban-cli/src/commands/events.rs | 221 ---- cmd/soroban-cli/src/commands/global.rs | 61 - cmd/soroban-cli/src/commands/keys/add.rs | 33 - cmd/soroban-cli/src/commands/keys/address.rs | 54 - cmd/soroban-cli/src/commands/keys/fund.rs | 34 - cmd/soroban-cli/src/commands/keys/generate.rs | 75 -- cmd/soroban-cli/src/commands/keys/ls.rs | 45 - cmd/soroban-cli/src/commands/keys/mod.rs | 63 - cmd/soroban-cli/src/commands/keys/rm.rs | 25 - cmd/soroban-cli/src/commands/keys/show.rs | 43 - cmd/soroban-cli/src/commands/lab/mod.rs | 31 - cmd/soroban-cli/src/commands/lab/token/mod.rs | 38 - cmd/soroban-cli/src/commands/mod.rs | 160 --- cmd/soroban-cli/src/commands/network/add.rs | 32 - cmd/soroban-cli/src/commands/network/ls.rs | 44 - cmd/soroban-cli/src/commands/network/mod.rs | 197 --- cmd/soroban-cli/src/commands/network/rm.rs | 24 - cmd/soroban-cli/src/commands/plugin.rs | 96 -- cmd/soroban-cli/src/commands/version.rs | 32 - cmd/soroban-cli/src/fee.rs | 16 - cmd/soroban-cli/src/key.rs | 110 -- cmd/soroban-cli/src/lib.rs | 53 - cmd/soroban-cli/src/log.rs | 13 - cmd/soroban-cli/src/log/auth.rs | 7 - cmd/soroban-cli/src/log/budget.rs | 5 - cmd/soroban-cli/src/log/cost.rs | 27 - cmd/soroban-cli/src/log/diagnostic_event.rs | 11 - cmd/soroban-cli/src/log/footprint.rs | 5 - cmd/soroban-cli/src/log/host_event.rs | 7 - .../src/rpc/fixtures/event_response.json | 39 - cmd/soroban-cli/src/rpc/mod.rs | 1141 ----------------- cmd/soroban-cli/src/rpc/txn.rs | 610 --------- cmd/soroban-cli/src/toid.rs | 69 - cmd/soroban-cli/src/utils.rs | 244 ---- cmd/soroban-cli/src/utils/contract_spec.rs | 276 ---- cmd/soroban-cli/src/wasm.rs | 93 -- 70 files changed, 234 insertions(+), 7878 deletions(-) delete mode 100644 cmd/soroban-cli/Cargo.toml delete mode 100644 cmd/soroban-cli/README.md delete mode 100644 cmd/soroban-cli/build.rs delete mode 100644 cmd/soroban-cli/src/bin/doc-gen.rs delete mode 100644 cmd/soroban-cli/src/bin/main.rs delete mode 100644 cmd/soroban-cli/src/commands/completion.rs delete mode 100644 cmd/soroban-cli/src/commands/config/locator.rs delete mode 100644 cmd/soroban-cli/src/commands/config/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/config/secret.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/asset.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/bindings.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/bindings/json.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/bindings/rust.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/bindings/typescript.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/build.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/deploy.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/deploy/asset.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/deploy/wasm.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/extend.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/fetch.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/id.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/id/asset.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/id/wasm.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/inspect.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/install.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/invoke.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/optimize.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/read.rs delete mode 100644 cmd/soroban-cli/src/commands/contract/restore.rs delete mode 100644 cmd/soroban-cli/src/commands/events.rs delete mode 100644 cmd/soroban-cli/src/commands/global.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/add.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/address.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/fund.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/generate.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/ls.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/rm.rs delete mode 100644 cmd/soroban-cli/src/commands/keys/show.rs delete mode 100644 cmd/soroban-cli/src/commands/lab/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/lab/token/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/network/add.rs delete mode 100644 cmd/soroban-cli/src/commands/network/ls.rs delete mode 100644 cmd/soroban-cli/src/commands/network/mod.rs delete mode 100644 cmd/soroban-cli/src/commands/network/rm.rs delete mode 100644 cmd/soroban-cli/src/commands/plugin.rs delete mode 100644 cmd/soroban-cli/src/commands/version.rs delete mode 100644 cmd/soroban-cli/src/fee.rs delete mode 100644 cmd/soroban-cli/src/key.rs delete mode 100644 cmd/soroban-cli/src/lib.rs delete mode 100644 cmd/soroban-cli/src/log.rs delete mode 100644 cmd/soroban-cli/src/log/auth.rs delete mode 100644 cmd/soroban-cli/src/log/budget.rs delete mode 100644 cmd/soroban-cli/src/log/cost.rs delete mode 100644 cmd/soroban-cli/src/log/diagnostic_event.rs delete mode 100644 cmd/soroban-cli/src/log/footprint.rs delete mode 100644 cmd/soroban-cli/src/log/host_event.rs delete mode 100644 cmd/soroban-cli/src/rpc/fixtures/event_response.json delete mode 100644 cmd/soroban-cli/src/rpc/mod.rs delete mode 100644 cmd/soroban-cli/src/rpc/txn.rs delete mode 100644 cmd/soroban-cli/src/toid.rs delete mode 100644 cmd/soroban-cli/src/utils.rs delete mode 100644 cmd/soroban-cli/src/utils/contract_spec.rs delete mode 100644 cmd/soroban-cli/src/wasm.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c96167ad..e06d913c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -77,7 +77,7 @@ jobs: - if: startsWith(matrix.target, 'x86_64') # specify directories explicitly to avoid building the preflight library (otherwise it will fail with missing symbols) run: | - for I in cmd/soroban-cli cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do + for I in cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do cargo test --target ${{ matrix.target }} --manifest-path $I/Cargo.toml done diff --git a/Cargo.lock b/Cargo.lock index 45bd18dc..432aff82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -273,7 +273,7 @@ dependencies = [ "num-bigint", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -314,7 +314,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -349,15 +348,6 @@ dependencies = [ "clap_derive", ] -[[package]] -name = "clap-markdown" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325f50228f76921784b6d9f2d62de6778d834483248eefecd27279174797e579" -dependencies = [ - "clap", -] - [[package]] name = "clap_builder" version = "4.4.18" @@ -388,7 +378,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -397,16 +387,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "colorchoice" version = "1.0.0" @@ -560,7 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -601,51 +581,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "cxx" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ab30434ea0ff6aa640a08dda5284026a366d47565496fd40b6cbfbdd7e31a2" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b649d7dfae8268450d53d109388b337b9352c7cba1fc10db4a1bc23c3dc189fb" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.39", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42281b20eba5218c539295c667c18e2f50211bb11902419194c6ed1ae808e547" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45506e3c66512b0a65d291a6b452128b7b1dd9841e20d1e151addbd2c00ea50" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -669,7 +605,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn", ] [[package]] @@ -680,7 +616,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -711,7 +647,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1369,15 +1305,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.67" @@ -1492,15 +1419,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "link-cplusplus" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1611,7 +1529,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1687,7 +1605,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1791,7 +1709,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -1888,7 +1806,7 @@ dependencies = [ "base64 0.21.7", "libc", "sha2 0.10.8", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-simulation", ] @@ -1899,7 +1817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn", ] [[package]] @@ -2142,12 +2060,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "ryu" version = "1.0.16" @@ -2178,12 +2090,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scratch" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" - [[package]] name = "sct" version = "0.7.1" @@ -2280,7 +2186,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -2320,7 +2226,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -2433,6 +2339,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa4d0718c6bb74d5b9f77d54dde6cc08a7eb6ef0e76b524f723a48f1cf5db2c" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "soroban-builtin-sdk-macros" version = "20.1.0" @@ -2441,20 +2359,19 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] name = "soroban-cli" version = "20.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e995a047a684547f1962d1064329c291632ed035b67ca7c423c4978cc231bd8" dependencies = [ - "assert_cmd", - "assert_fs", "base64 0.21.7", "cargo_metadata", "chrono", "clap", - "clap-markdown", "clap_complete", "crate-git-revision 0.0.4", "csv", @@ -2473,7 +2390,6 @@ dependencies = [ "num-bigint", "openssl", "pathdiff", - "predicates 2.1.5", "rand", "regex", "rpassword", @@ -2484,12 +2400,12 @@ dependencies = [ "serde_json", "sha2 0.10.8", "shlex", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk", - "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-sdk 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "soroban-spec-json", - "soroban-spec-rust", + "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "soroban-spec-tools", "soroban-spec-typescript", "stellar-strkey 0.0.7", @@ -2503,11 +2419,27 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", - "wasm-opt", "wasmparser 0.90.0", "which", ] +[[package]] +name = "soroban-env-common" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e3c4b5ea7936e814706f2a5da6af574260319bcc66fbf5517b39f19b5fa365" +dependencies = [ + "crate-git-revision 0.0.6", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-xdr", +] + [[package]] name = "soroban-env-common" version = "20.1.0" @@ -2519,21 +2451,57 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros", - "soroban-wasmi", + "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "static_assertions", "stellar-xdr", ] +[[package]] +name = "soroban-env-guest" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff111936103653515b4471b951b6325b966c3bb31c4c1bd5892ea741aa028ca" +dependencies = [ + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", +] + [[package]] name = "soroban-env-guest" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" dependencies = [ - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "static_assertions", ] +[[package]] +name = "soroban-env-host" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3d7de1f83760dddf9302dcc32f8adfebaff41946045ea67196511aca8d9f00" +dependencies = [ + "curve25519-dalek 4.1.1", + "ed25519-dalek 2.0.0", + "getrandom", + "hex-literal", + "hmac 0.12.1", + "k256", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2 0.10.8", + "sha3", + "soroban-builtin-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions", + "stellar-strkey 0.0.8", +] + [[package]] name = "soroban-env-host" version = "20.1.0" @@ -2553,13 +2521,28 @@ dependencies = [ "rand_chacha", "sha2 0.10.8", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", - "soroban-wasmi", + "soroban-builtin-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-wasmi 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "static_assertions", "stellar-strkey 0.0.8", ] +[[package]] +name = "soroban-env-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff774562cca63a173127b0b6679dce9afde49fd34474eec041dc5612134dda7" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-env-macros" version = "20.1.0" @@ -2571,13 +2554,27 @@ dependencies = [ "serde", "serde_json", "stellar-xdr", - "syn 2.0.39", + "syn", ] [[package]] name = "soroban-hello" version = "20.2.0" +[[package]] +name = "soroban-ledger-snapshot" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0f4b117c50bec49f02eab898957f92f13cae78ee3d17d7660821742251b8c2" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + [[package]] name = "soroban-ledger-snapshot" version = "20.1.0" @@ -2586,11 +2583,28 @@ dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "thiserror", ] +[[package]] +name = "soroban-sdk" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6c13c0f657bec67785cb7d204fcaccca553f937a42743c6acedfd993ad69f6" +dependencies = [ + "bytes-lit", + "rand", + "serde", + "serde_json", + "soroban-env-guest 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-strkey 0.0.8", +] + [[package]] name = "soroban-sdk" version = "20.1.0" @@ -2603,13 +2617,33 @@ dependencies = [ "rand", "serde", "serde_json", - "soroban-env-guest", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk-macros", + "soroban-env-guest 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-strkey 0.0.8", ] +[[package]] +name = "soroban-sdk-macros" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7e5d144250bbea143f0863462e58200e2a82e37fa611b09f239f3d8e849a83" +dependencies = [ + "crate-git-revision 0.0.6", + "darling", + "itertools 0.11.0", + "proc-macro2", + "quote", + "rustc_version", + "sha2 0.10.8", + "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-xdr", + "syn", +] + [[package]] name = "soroban-sdk-macros" version = "20.1.0" @@ -2622,11 +2656,11 @@ dependencies = [ "quote", "rustc_version", "sha2 0.10.8", - "soroban-env-common", + "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", - "soroban-spec-rust", + "soroban-spec-rust 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", - "syn 2.0.39", + "syn", ] [[package]] @@ -2636,7 +2670,7 @@ source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a920 dependencies = [ "anyhow", "rand", - "soroban-env-host", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", "static_assertions", "thiserror", ] @@ -2679,6 +2713,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "soroban-spec-rust" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1bf5452f0d97cf280f14b66de98571e819e198b124b33472e49dba1128ed9d" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2 0.10.8", + "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stellar-xdr", + "syn", + "thiserror", +] + [[package]] name = "soroban-spec-rust" version = "20.1.0" @@ -2690,7 +2740,7 @@ dependencies = [ "sha2 0.10.8", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", - "syn 2.0.39", + "syn", "thiserror", ] @@ -2744,9 +2794,9 @@ dependencies = [ "serde_json", "sha2 0.10.8", "soroban-cli", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk", + "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-tools", "stellar-strkey 0.0.7", @@ -2760,7 +2810,20 @@ name = "soroban-token-sdk" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1aaa682a67cbd2173f1d60cb1e7b951d490d7c4e0b7b6f5387cbb952e963c46" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi_core 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser-nostd", ] [[package]] @@ -2770,8 +2833,8 @@ source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84 dependencies = [ "smallvec", "spin", - "wasmi_arena", - "wasmi_core", + "wasmi_arena 0.4.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", + "wasmi_core 0.13.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", "wasmparser-nostd", ] @@ -2843,42 +2906,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.39" @@ -2938,28 +2971,28 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" name = "test_custom_types" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_hello_world" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_swap" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] name = "test_token" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-token-sdk", ] @@ -2967,7 +3000,7 @@ dependencies = [ name = "test_udt" version = "20.2.0" dependencies = [ - "soroban-sdk", + "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", ] [[package]] @@ -2987,7 +3020,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3090,7 +3123,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3195,7 +3228,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] [[package]] @@ -3270,12 +3303,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - [[package]] name = "untrusted" version = "0.9.0" @@ -3372,7 +3399,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn", "wasm-bindgen-shared", ] @@ -3394,7 +3421,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3406,50 +3433,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] -name = "wasm-opt" -version = "0.114.2" +name = "wasmi_arena" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effbef3bd1dde18acb401f73e740a6f3d4a1bc651e9773bddc512fe4d8d68f67" -dependencies = [ - "anyhow", - "libc", - "strum", - "strum_macros", - "tempfile", - "thiserror", - "wasm-opt-cxx-sys", - "wasm-opt-sys", -] +checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" [[package]] -name = "wasm-opt-cxx-sys" -version = "0.114.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09e24eb283919ace2ed5733bda4842a59ce4c8de110ef5c6d98859513d17047" -dependencies = [ - "anyhow", - "cxx", - "cxx-build", - "wasm-opt-sys", -] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" [[package]] -name = "wasm-opt-sys" -version = "0.114.2" +name = "wasmi_core" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f2f817bed2e8d65eb779fa37317e74de15585751f903c9118342d1970703a4" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" dependencies = [ - "anyhow", - "cc", - "cxx", - "cxx-build", + "downcast-rs", + "libm", + "num-traits", + "paste", ] -[[package]] -name = "wasmi_arena" -version = "0.4.0" -source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" - [[package]] name = "wasmi_core" version = "0.13.0" @@ -3690,5 +3695,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 9da4ca95..73dc70c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,12 @@ [workspace] resolver = "2" members = [ - "cmd/soroban-cli", "cmd/crates/*", "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", "cmd/crates/soroban-test/tests/fixtures/hello", "cmd/soroban-rpc/lib/preflight", ] -default-members = ["cmd/soroban-cli", "cmd/crates/soroban-test"] +default-members = ["cmd/crates/soroban-test"] exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] @@ -53,10 +52,6 @@ version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" -[workspace.dependencies.soroban-cli] -version = "20.2.0" -path = "cmd/soroban-cli" - [workspace.dependencies.stellar-xdr] version = "=20.0.2" default-features = true @@ -80,6 +75,7 @@ wasmparser = "0.90.0" soroban-spec-json = "20.2.0" soroban-spec-tools = "20.2.0" soroban-spec-typescript = "20.2.0" +soroban-cli = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/Makefile b/Makefile index 23187151..fdc88efb 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ Cargo.lock: Cargo.toml cargo update --workspace install_rust: Cargo.lock - cargo install --path ./cmd/soroban-cli --debug cargo install --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet install: install_rust build-libpreflight diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml deleted file mode 100644 index ad60c827..00000000 --- a/cmd/soroban-cli/Cargo.toml +++ /dev/null @@ -1,106 +0,0 @@ -[package] -name = "soroban-cli" -description = "Soroban CLI" -homepage = "https://github.com/stellar/soroban-cli" -repository = "https://github.com/stellar/soroban-cli" -authors = ["Stellar Development Foundation "] -license = "Apache-2.0" -readme = "README.md" -version = "20.2.0" -edition = "2021" -rust-version.workspace = true -autobins = false -default-run = "soroban" - -[[bin]] -name = "soroban" -path = "src/bin/main.rs" - -[package.metadata.binstall] -pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" -bin-dir = "{ bin }{ binary-ext }" - -[[bin]] -name = "doc-gen" -path = "src/bin/doc-gen.rs" -required-features = ["clap-markdown"] - -[lib] -name = "soroban_cli" -path = "src/lib.rs" -doctest = false - -[features] -default = [] -opt = ["dep:wasm-opt"] - -[dependencies] -stellar-xdr = { workspace = true, features = ["cli"] } -soroban-env-host = { workspace = true } -soroban-spec = { workspace = true } -soroban-spec-json = { workspace = true } -soroban-spec-rust = { workspace = true } -soroban-spec-tools = { workspace = true } -soroban-spec-typescript = { workspace = true } -soroban-ledger-snapshot = { workspace = true } -stellar-strkey = { workspace = true } -soroban-sdk = { workspace = true } -clap = { version = "4.1.8", features = [ - "derive", - "env", - "deprecated", - "string", -] } -base64 = { workspace = true } -thiserror = { workspace = true } -serde = "1.0.82" -serde_derive = "1.0.82" -serde_json = "1.0.82" -serde-aux = "4.1.2" -hex = { workspace = true } -num-bigint = "0.4" -tokio = { version = "1", features = ["full"] } -termcolor = "1.1.3" -termcolor_output = "1.0.1" -clap_complete = "4.1.4" -rand = "0.8.5" -wasmparser = { workspace = true } -sha2 = { workspace = true } -csv = "1.1.6" -ed25519-dalek = "=2.0.0" -jsonrpsee-http-client = "0.20.1" -jsonrpsee-core = "0.20.1" -hyper = "0.14.27" -hyper-tls = "0.5" -http = "0.2.9" -regex = "1.6.0" -wasm-opt = { version = "0.114.0", optional = true } -chrono = "0.4.27" -rpassword = "7.2.0" -dirs = "4.0.0" -toml = "0.5.9" -itertools = "0.10.5" -shlex = "1.1.0" -sep5 = { workspace = true } -ethnum = { workspace = true } -clap-markdown = { version = "0.1.3", optional = true } -which = { workspace = true, features = ["regex"] } -strsim = "0.10.0" -heck = "0.4.1" -tracing = { workspace = true } -tracing-appender = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter"] } -cargo_metadata = "0.15.4" -pathdiff = "0.2.1" -dotenvy = "0.15.7" -# For hyper-tls -[target.'cfg(unix)'.dependencies] -openssl = { version = "0.10.55", features = ["vendored"] } - -[build-dependencies] -crate-git-revision = "0.0.4" - -[dev-dependencies] -assert_cmd = "2.0.4" -assert_fs = "1.0.7" -predicates = "2.1.5" diff --git a/cmd/soroban-cli/README.md b/cmd/soroban-cli/README.md deleted file mode 100644 index d17261b1..00000000 --- a/cmd/soroban-cli/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# soroban-cli - -CLI for running Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). - -Soroban: https://soroban.stellar.org - -## Install - -``` -cargo install --locked soroban-cli -``` - -To install with the `opt` feature, which includes a WASM optimization feature and wasm-opt built in: - -``` -cargo install --locked soroban-cli --features opt -``` - -## Usage - -Can invoke a contract method as a subcommand with different arguments. Anything after the slop (`--`) is passed to the contract's CLI. You can use `--help` to learn about which methods are available and what their arguments are including an example of the type of the input. - -## Example - -``` -soroban invoke --id --wasm -- --help -soroban invoke --id --network futurenet -- --help -``` diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs deleted file mode 100644 index b6e6dd92..00000000 --- a/cmd/soroban-cli/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - crate_git_revision::init(); -} diff --git a/cmd/soroban-cli/src/bin/doc-gen.rs b/cmd/soroban-cli/src/bin/doc-gen.rs deleted file mode 100644 index 096f9681..00000000 --- a/cmd/soroban-cli/src/bin/doc-gen.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::{ - env, fs, - path::{Path, PathBuf}, -}; - -type DynError = Box; - -fn main() -> Result<(), DynError> { - doc_gen()?; - Ok(()) -} - -fn doc_gen() -> std::io::Result<()> { - let out_dir = docs_dir(); - - fs::create_dir_all(out_dir.clone())?; - - std::fs::write( - out_dir.join("soroban-cli-full-docs.md"), - clap_markdown::help_markdown::(), - )?; - - Ok(()) -} - -fn project_root() -> PathBuf { - Path::new(&env!("CARGO_MANIFEST_DIR")) - .ancestors() - .nth(2) - .unwrap() - .to_path_buf() -} - -fn docs_dir() -> PathBuf { - project_root().join("docs") -} diff --git a/cmd/soroban-cli/src/bin/main.rs b/cmd/soroban-cli/src/bin/main.rs deleted file mode 100644 index 7a87099c..00000000 --- a/cmd/soroban-cli/src/bin/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -use clap::CommandFactory; -use dotenvy::dotenv; -use tracing_subscriber::{fmt, EnvFilter}; - -use soroban_cli::{commands, Root}; - -#[tokio::main] -async fn main() { - let _ = dotenv().unwrap_or_default(); - let mut root = Root::new().unwrap_or_else(|e| match e { - commands::Error::Clap(e) => { - let mut cmd = Root::command(); - e.format(&mut cmd).exit(); - } - e => { - eprintln!("{e}"); - std::process::exit(1); - } - }); - // Now use root to setup the logger - if let Some(level) = root.global_args.log_level() { - let mut e_filter = EnvFilter::from_default_env() - .add_directive("hyper=off".parse().unwrap()) - .add_directive(format!("soroban_cli={level}").parse().unwrap()); - - for filter in &root.global_args.filter_logs { - e_filter = e_filter.add_directive( - filter - .parse() - .map_err(|e| { - eprintln!("{e}: {filter}"); - std::process::exit(1); - }) - .unwrap(), - ); - } - - let builder = fmt::Subscriber::builder() - .with_env_filter(e_filter) - .with_writer(std::io::stderr); - - let subscriber = builder.finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("Failed to set the global tracing subscriber"); - } - - if let Err(e) = root.run().await { - eprintln!("error: {e}"); - std::process::exit(1); - } -} diff --git a/cmd/soroban-cli/src/commands/completion.rs b/cmd/soroban-cli/src/commands/completion.rs deleted file mode 100644 index f64386b4..00000000 --- a/cmd/soroban-cli/src/commands/completion.rs +++ /dev/null @@ -1,32 +0,0 @@ -use clap::{arg, CommandFactory, Parser}; -use clap_complete::{generate, Shell}; -use std::io; - -use crate::commands::Root; - -pub const LONG_ABOUT: &str = "\ -Print shell completion code for the specified shell - -Ensure the completion package for your shell is installed, -e.g., bash-completion for bash. - -To enable autocomplete in the current bash shell, run: - source <(soroban completion --shell bash) - -To enable autocomplete permanently, run: - echo \"source <(soroban completion --shell bash)\" >> ~/.bashrc"; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// The shell type - #[arg(long, value_enum)] - shell: Shell, -} - -impl Cmd { - pub fn run(&self) { - let cmd = &mut Root::command(); - generate(self.shell, cmd, "soroban", &mut io::stdout()); - } -} diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs deleted file mode 100644 index 2688b043..00000000 --- a/cmd/soroban-cli/src/commands/config/locator.rs +++ /dev/null @@ -1,358 +0,0 @@ -use clap::arg; -use serde::de::DeserializeOwned; -use std::{ - ffi::OsStr, - fmt::Display, - fs, io, - path::{Path, PathBuf}, - str::FromStr, -}; - -use crate::{utils::find_config_dir, Pwd}; - -use super::{network::Network, secret::Secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Failed to find home directory")] - HomeDirNotFound, - #[error("Failed read current directory")] - CurrentDirNotFound, - #[error("Failed read current directory and no SOROBAN_CONFIG_HOME is set")] - NoConfigEnvVar, - #[error("Failed to create directory: {path:?}")] - DirCreationFailed { path: PathBuf }, - #[error( - "Failed to read secret's file: {path}.\nProbably need to use `soroban config identity add`" - )] - SecretFileRead { path: PathBuf }, - #[error( - "Failed to read network file: {path};\nProbably need to use `soroban config network add`" - )] - NetworkFileRead { path: PathBuf }, - #[error(transparent)] - Toml(#[from] toml::de::Error), - #[error("Seceret file failed to deserialize")] - Deserialization, - #[error("Failed to write identity file:{filepath}: {error}")] - IdCreationFailed { filepath: PathBuf, error: io::Error }, - #[error("Seceret file failed to deserialize")] - NetworkDeserialization, - #[error("Failed to write network file: {0}")] - NetworkCreationFailed(std::io::Error), - #[error("Error Identity directory is invalid: {name}")] - IdentityList { name: String }, - // #[error("Config file failed to deserialize")] - // CannotReadConfigFile, - #[error("Config file failed to serialize")] - ConfigSerialization, - // #[error("Config file failed write")] - // CannotWriteConfigFile, - #[error("XDG_CONFIG_HOME env variable is not a valid path. Got {0}")] - XdgConfigHome(String), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("Failed to remove {0}: {1}")] - ConfigRemoval(String, String), - #[error("Failed to find config {0} for {1}")] - ConfigMissing(String, String), - #[error(transparent)] - String(#[from] std::string::FromUtf8Error), - #[error(transparent)] - Secret(#[from] crate::commands::config::secret::Error), -} - -#[derive(Debug, clap::Args, Default, Clone)] -#[group(skip)] -pub struct Args { - /// Use global config - #[arg(long)] - pub global: bool, - - /// Location of config directory, default is "." - #[arg(long, help_heading = "TESTING_OPTIONS")] - pub config_dir: Option, -} - -pub enum Location { - Local(PathBuf), - Global(PathBuf), -} - -impl Display for Location { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} {:?}", - match self { - Location::Local(_) => "Local", - Location::Global(_) => "Global", - }, - self.as_ref().parent().unwrap().parent().unwrap() - ) - } -} - -impl AsRef for Location { - fn as_ref(&self) -> &Path { - match self { - Location::Local(p) | Location::Global(p) => p.as_path(), - } - } -} - -impl Location { - #[must_use] - pub fn wrap(&self, p: PathBuf) -> Self { - match self { - Location::Local(_) => Location::Local(p), - Location::Global(_) => Location::Global(p), - } - } -} - -impl Args { - pub fn config_dir(&self) -> Result { - if self.global { - global_config_path() - } else { - self.local_config() - } - } - - pub fn local_and_global(&self) -> Result<[Location; 2], Error> { - Ok([ - Location::Local(self.local_config()?), - Location::Global(global_config_path()?), - ]) - } - - pub fn local_config(&self) -> Result { - let pwd = self.current_dir()?; - Ok(find_config_dir(pwd.clone()).unwrap_or_else(|_| pwd.join(".soroban"))) - } - - pub fn current_dir(&self) -> Result { - self.config_dir.as_ref().map_or_else( - || std::env::current_dir().map_err(|_| Error::CurrentDirNotFound), - |pwd| Ok(pwd.clone()), - ) - } - - pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { - KeyType::Identity.write(name, secret, &self.config_dir()?) - } - - pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> { - KeyType::Network.write(name, network, &self.config_dir()?) - } - - pub fn list_identities(&self) -> Result, Error> { - Ok(KeyType::Identity - .list_paths(&self.local_and_global()?)? - .into_iter() - .map(|(name, _)| name) - .collect()) - } - - pub fn list_identities_long(&self) -> Result, Error> { - Ok(KeyType::Identity - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .map(|(name, location)| { - let path = match location { - Location::Local(path) | Location::Global(path) => path, - }; - (name, format!("{}", path.display())) - }) - .collect()) - } - - pub fn list_networks(&self) -> Result, Error> { - Ok(KeyType::Network - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .map(|x| x.0) - .collect()) - } - - pub fn list_networks_long(&self) -> Result, Error> { - Ok(KeyType::Network - .list_paths(&self.local_and_global()?) - .into_iter() - .flatten() - .filter_map(|(name, location)| { - Some(( - name, - KeyType::read_from_path::(location.as_ref()).ok()?, - location, - )) - }) - .collect::>()) - } - pub fn read_identity(&self, name: &str) -> Result { - KeyType::Identity.read_with_global(name, &self.local_config()?) - } - - pub fn read_network(&self, name: &str) -> Result { - let res = KeyType::Network.read_with_global(name, &self.local_config()?); - if let Err(Error::ConfigMissing(_, _)) = &res { - if name == "futurenet" { - let network = Network::futurenet(); - self.write_network(name, &network)?; - return Ok(network); - } - } - res - } - - pub fn remove_identity(&self, name: &str) -> Result<(), Error> { - KeyType::Identity.remove(name, &self.config_dir()?) - } - - pub fn remove_network(&self, name: &str) -> Result<(), Error> { - KeyType::Network.remove(name, &self.config_dir()?) - } -} - -fn ensure_directory(dir: PathBuf) -> Result { - let parent = dir.parent().ok_or(Error::HomeDirNotFound)?; - std::fs::create_dir_all(parent).map_err(|_| dir_creation_failed(parent))?; - Ok(dir) -} - -fn dir_creation_failed(p: &Path) -> Error { - Error::DirCreationFailed { - path: p.to_path_buf(), - } -} - -fn read_dir(dir: &Path) -> Result, Error> { - let contents = std::fs::read_dir(dir)?; - let mut res = vec![]; - for entry in contents.filter_map(Result::ok) { - let path = entry.path(); - if let Some("toml") = path.extension().and_then(OsStr::to_str) { - if let Some(os_str) = path.file_stem() { - res.push((os_str.to_string_lossy().trim().to_string(), path)); - } - } - } - res.sort(); - Ok(res) -} - -pub enum KeyType { - Identity, - Network, -} - -impl Display for KeyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - KeyType::Identity => "identity", - KeyType::Network => "network", - } - ) - } -} - -impl KeyType { - pub fn read(&self, key: &str, pwd: &Path) -> Result { - let path = self.path(pwd, key); - Self::read_from_path(&path) - } - - pub fn read_from_path(path: &Path) -> Result { - let data = fs::read(path).map_err(|_| Error::NetworkFileRead { - path: path.to_path_buf(), - })?; - let res = toml::from_slice(data.as_slice()); - Ok(res?) - } - - pub fn read_with_global(&self, key: &str, pwd: &Path) -> Result { - for path in [pwd, global_config_path()?.as_path()] { - match self.read(key, path) { - Ok(t) => return Ok(t), - _ => continue, - } - } - Err(Error::ConfigMissing(self.to_string(), key.to_string())) - } - - pub fn write( - &self, - key: &str, - value: &T, - pwd: &Path, - ) -> Result<(), Error> { - let filepath = ensure_directory(self.path(pwd, key))?; - let data = toml::to_string(value).map_err(|_| Error::ConfigSerialization)?; - std::fs::write(&filepath, data).map_err(|error| Error::IdCreationFailed { filepath, error }) - } - - fn root(&self, pwd: &Path) -> PathBuf { - pwd.join(self.to_string()) - } - - fn path(&self, pwd: &Path, key: &str) -> PathBuf { - let mut path = self.root(pwd).join(key); - path.set_extension("toml"); - path - } - - pub fn list_paths(&self, paths: &[Location]) -> Result, Error> { - Ok(paths - .iter() - .flat_map(|p| self.list(p).unwrap_or_default()) - .collect()) - } - - pub fn list(&self, pwd: &Location) -> Result, Error> { - let path = self.root(pwd.as_ref()); - if path.exists() { - let mut files = read_dir(&path)?; - files.sort(); - - Ok(files - .into_iter() - .map(|(name, p)| (name, pwd.wrap(p))) - .collect()) - } else { - Ok(vec![]) - } - } - - pub fn remove(&self, key: &str, pwd: &Path) -> Result<(), Error> { - let path = self.path(pwd, key); - if path.exists() { - std::fs::remove_file(&path) - .map_err(|_| Error::ConfigRemoval(self.to_string(), key.to_string())) - } else { - Ok(()) - } - } -} - -fn global_config_path() -> Result { - Ok(if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { - PathBuf::from_str(&config_home).map_err(|_| Error::XdgConfigHome(config_home))? - } else { - dirs::home_dir() - .ok_or(Error::HomeDirNotFound)? - .join(".config") - } - .join("soroban")) -} - -impl Pwd for Args { - fn set_pwd(&mut self, pwd: &Path) { - self.config_dir = Some(pwd.to_path_buf()); - } -} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs deleted file mode 100644 index be76e77f..00000000 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::path::PathBuf; - -use clap::{arg, command, Parser}; -use serde::{Deserialize, Serialize}; - -use crate::Pwd; - -use self::{network::Network, secret::Secret}; - -use super::{keys, network}; - -pub mod locator; -pub mod secret; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Configure different networks. Depraecated, use `soroban network` instead. - #[command(subcommand)] - Network(network::Cmd), - /// Identity management. Deprecated, use `soroban keys` instead. - #[command(subcommand)] - Identity(keys::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Identity(#[from] keys::Error), - #[error(transparent)] - Network(#[from] network::Error), - #[error(transparent)] - Secret(#[from] secret::Error), - #[error(transparent)] - Config(#[from] locator::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Identity(identity) => identity.run().await?, - Cmd::Network(network) => network.run()?, - } - Ok(()) - } -} - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - #[command(flatten)] - pub network: network::Args, - - #[arg(long, visible_alias = "source", env = "SOROBAN_ACCOUNT")] - /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` - pub source_account: String, - - #[arg(long)] - /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Args { - pub fn key_pair(&self) -> Result { - let key = self.account(&self.source_account)?; - Ok(key.key_pair(self.hd_path)?) - } - - pub fn account(&self, account_str: &str) -> Result { - if let Ok(secret) = self.locator.read_identity(account_str) { - Ok(secret) - } else { - Ok(account_str.parse::()?) - } - } - - pub fn get_network(&self) -> Result { - Ok(self.network.get(&self.locator)?) - } - - pub fn config_dir(&self) -> Result { - Ok(self.locator.config_dir()?) - } -} - -impl Pwd for Args { - fn set_pwd(&mut self, pwd: &std::path::Path) { - self.locator.set_pwd(pwd); - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct Config {} diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs deleted file mode 100644 index 4684e2a8..00000000 --- a/cmd/soroban-cli/src/commands/config/secret.rs +++ /dev/null @@ -1,143 +0,0 @@ -use clap::arg; -use serde::{Deserialize, Serialize}; -use std::{io::Write, str::FromStr}; -use stellar_strkey::ed25519::{PrivateKey, PublicKey}; - -use crate::utils; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("invalid secret key")] - InvalidSecretKey, - // #[error("seed_phrase must be 12 words long, found {len}")] - // InvalidSeedPhrase { len: usize }, - #[error("seceret input error")] - PasswordRead, - #[error(transparent)] - Secret(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - SeedPhrase(#[from] sep5::error::Error), - #[error(transparent)] - Ed25519(#[from] ed25519_dalek::SignatureError), - #[error("Invalid address {0}")] - InvalidAddress(String), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Add using secret_key - /// Can provide with SOROBAN_SECRET_KEY - #[arg(long, conflicts_with = "seed_phrase")] - pub secret_key: bool, - /// Add using 12 word seed phrase to generate secret_key - #[arg(long, conflicts_with = "secret_key")] - pub seed_phrase: bool, -} - -impl Args { - pub fn read_secret(&self) -> Result { - if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) - } else if self.secret_key { - println!("Type a secret key: "); - let secret_key = read_password()?; - let secret_key = PrivateKey::from_string(&secret_key) - .map_err(|_| Error::InvalidSecretKey)? - .to_string(); - Ok(Secret::SecretKey { secret_key }) - } else if self.seed_phrase { - println!("Type a 12 word seed phrase: "); - let seed_phrase = read_password()?; - let seed_phrase: Vec<&str> = seed_phrase.split_whitespace().collect(); - // if seed_phrase.len() != 12 { - // let len = seed_phrase.len(); - // return Err(Error::InvalidSeedPhrase { len }); - // } - Ok(Secret::SeedPhrase { - seed_phrase: seed_phrase - .into_iter() - .map(ToString::to_string) - .collect::>() - .join(" "), - }) - } else { - Err(Error::PasswordRead {}) - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Secret { - SecretKey { secret_key: String }, - SeedPhrase { seed_phrase: String }, -} - -impl FromStr for Secret { - type Err = Error; - - fn from_str(s: &str) -> Result { - if PrivateKey::from_string(s).is_ok() { - Ok(Secret::SecretKey { - secret_key: s.to_string(), - }) - } else if sep5::SeedPhrase::from_str(s).is_ok() { - Ok(Secret::SeedPhrase { - seed_phrase: s.to_string(), - }) - } else { - Err(Error::InvalidAddress(s.to_string())) - } - } -} - -impl From for Secret { - fn from(value: PrivateKey) -> Self { - Secret::SecretKey { - secret_key: value.to_string(), - } - } -} - -impl Secret { - pub fn private_key(&self, index: Option) -> Result { - Ok(match self { - Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, - Secret::SeedPhrase { seed_phrase } => sep5::SeedPhrase::from_str(seed_phrase)? - .from_path_index(index.unwrap_or_default(), None)? - .private(), - }) - } - - pub fn public_key(&self, index: Option) -> Result { - let key = self.key_pair(index)?; - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - key.verifying_key().as_bytes(), - )?) - } - - pub fn key_pair(&self, index: Option) -> Result { - Ok(utils::into_signing_key(&self.private_key(index)?)) - } - - pub fn from_seed(seed: Option<&str>) -> Result { - let seed_phrase = if let Some(seed) = seed.map(str::as_bytes) { - sep5::SeedPhrase::from_entropy(seed) - } else { - sep5::SeedPhrase::random(sep5::MnemonicType::Words12) - }? - .seed_phrase - .into_phrase(); - Ok(Secret::SeedPhrase { seed_phrase }) - } - - pub fn test_seed_phrase() -> Result { - Self::from_seed(Some("0000000000000000")) - } -} - -fn read_password() -> Result { - std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; - rpassword::read_password().map_err(|_| Error::PasswordRead) -} diff --git a/cmd/soroban-cli/src/commands/contract/asset.rs b/cmd/soroban-cli/src/commands/contract/asset.rs deleted file mode 100644 index ad7be020..00000000 --- a/cmd/soroban-cli/src/commands/contract/asset.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::{deploy, id}; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Get Id of builtin Soroban Asset Contract. Deprecated, use `soroban contract id asset` instead - Id(id::asset::Cmd), - /// Deploy builtin Soroban Asset Contract - Deploy(deploy::asset::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Id(#[from] id::asset::Error), - #[error(transparent)] - Deploy(#[from] deploy::asset::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Id(id) => id.run()?, - Cmd::Deploy(asset) => asset.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings.rs b/cmd/soroban-cli/src/commands/contract/bindings.rs deleted file mode 100644 index 1da94697..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod json; -pub mod rust; -pub mod typescript; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Generate Json Bindings - Json(json::Cmd), - - /// Generate Rust bindings - Rust(rust::Cmd), - - /// Generate a TypeScript / JavaScript package - Typescript(typescript::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Json(#[from] json::Error), - - #[error(transparent)] - Rust(#[from] rust::Error), - - #[error(transparent)] - Typescript(#[from] typescript::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Json(json) => json.run()?, - Cmd::Rust(rust) => rust.run()?, - Cmd::Typescript(ts) => ts.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/json.rs b/cmd/soroban-cli/src/commands/contract/bindings/json.rs deleted file mode 100644 index 060f9064..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/json.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::fmt::Debug; - -use clap::{command, Parser}; -use soroban_spec_json; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("generate json from file: {0}")] - GenerateJsonFromFile(soroban_spec_json::GenerateFromFileError), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm_path_str = self.wasm.wasm.to_string_lossy(); - let json = soroban_spec_json::generate_from_file(&wasm_path_str, None) - .map_err(Error::GenerateJsonFromFile)?; - println!("{json}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/rust.rs b/cmd/soroban-cli/src/commands/contract/bindings/rust.rs deleted file mode 100644 index 176732ec..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/rust.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fmt::Debug; - -use clap::{command, Parser}; -use soroban_spec_rust::{self, ToFormattedString}; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("generate rust from file: {0}")] - GenerateRustFromFile(soroban_spec_rust::GenerateFromFileError), - #[error("format rust error: {0}")] - FormatRust(String), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm_path_str = self.wasm.wasm.to_string_lossy(); - let code = soroban_spec_rust::generate_from_file(&wasm_path_str, None) - .map_err(Error::GenerateRustFromFile)?; - match code.to_formatted_string() { - Ok(formatted) => { - println!("{formatted}"); - Ok(()) - } - Err(e) => { - println!("{code}"); - Err(Error::FormatRust(e.to_string())) - } - } - } -} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs deleted file mode 100644 index 19c7eecd..00000000 --- a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::{ffi::OsString, fmt::Debug, path::PathBuf}; - -use clap::{command, Parser}; -use soroban_spec_typescript::{self as typescript, boilerplate::Project}; - -use crate::wasm; -use crate::{ - commands::{ - config::locator, - contract::{self, fetch}, - network::{self, Network}, - }, - utils::contract_spec::{self, ContractSpec}, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Path to optional wasm binary - #[arg(long)] - pub wasm: Option, - /// Where to place generated project - #[arg(long)] - output_dir: PathBuf, - /// Whether to overwrite output directory if it already exists - #[arg(long)] - overwrite: bool, - /// The contract ID/address on the network - #[arg(long, visible_alias = "id")] - contract_id: String, - #[command(flatten)] - locator: locator::Args, - #[command(flatten)] - network: network::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("failed generate TS from file: {0}")] - GenerateTSFromFile(typescript::GenerateFromFileError), - #[error(transparent)] - Io(#[from] std::io::Error), - - #[error("--output-dir cannot be a file: {0:?}")] - IsFile(PathBuf), - - #[error("--output-dir already exists and you did not specify --overwrite: {0:?}")] - OutputDirExists(PathBuf), - - #[error("--output-dir filepath not representable as utf-8: {0:?}")] - NotUtf8(OsString), - - #[error(transparent)] - Network(#[from] network::Error), - - #[error(transparent)] - Locator(#[from] locator::Error), - #[error(transparent)] - Fetch(#[from] fetch::Error), - #[error(transparent)] - Spec(#[from] contract_spec::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("Failed to get file name from path: {0:?}")] - FailedToGetFileName(PathBuf), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let spec = if let Some(wasm) = &self.wasm { - let wasm: wasm::Args = wasm.into(); - wasm.parse()?.spec - } else { - let fetch = contract::fetch::Cmd { - contract_id: self.contract_id.clone(), - out_file: None, - locator: self.locator.clone(), - network: self.network.clone(), - }; - let bytes = fetch.get_bytes().await?; - ContractSpec::new(&bytes)?.spec - }; - if self.output_dir.is_file() { - return Err(Error::IsFile(self.output_dir.clone())); - } - if self.output_dir.exists() { - if self.overwrite { - std::fs::remove_dir_all(&self.output_dir)?; - } else { - return Err(Error::OutputDirExists(self.output_dir.clone())); - } - } - std::fs::create_dir_all(&self.output_dir)?; - let p: Project = self.output_dir.clone().try_into()?; - let Network { - rpc_url, - network_passphrase, - .. - } = self - .network - .get(&self.locator) - .ok() - .unwrap_or_else(Network::futurenet); - let absolute_path = self.output_dir.canonicalize()?; - let file_name = absolute_path - .file_name() - .ok_or_else(|| Error::FailedToGetFileName(absolute_path.clone()))?; - let contract_name = &file_name - .to_str() - .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; - p.init( - contract_name, - &self.contract_id, - &rpc_url, - &network_passphrase, - &spec, - )?; - std::process::Command::new("npm") - .arg("install") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; - std::process::Command::new("npm") - .arg("run") - .arg("build") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs deleted file mode 100644 index ba17bd1b..00000000 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ /dev/null @@ -1,194 +0,0 @@ -use clap::Parser; -use itertools::Itertools; -use std::{ - collections::HashSet, - env, - ffi::OsStr, - fmt::Debug, - fs, io, - path::Path, - process::{Command, ExitStatus, Stdio}, -}; - -use cargo_metadata::{Metadata, MetadataCommand, Package}; - -/// Build a contract from source -/// -/// Builds all crates that are referenced by the cargo manifest (Cargo.toml) -/// that have cdylib as their crate-type. Crates are built for the wasm32 -/// target. Unless configured otherwise, crates are built with their default -/// features and with their release profile. -/// -/// To view the commands that will be executed, without executing them, use the -/// --print-commands-only option. -#[derive(Parser, Debug, Clone)] -pub struct Cmd { - /// Path to Cargo.toml - #[arg(long, default_value = "Cargo.toml")] - pub manifest_path: std::path::PathBuf, - /// Package to build - /// - /// If omitted, all packages that build for crate-type cdylib are built. - #[arg(long)] - pub package: Option, - /// Build with the specified profile - #[arg(long, default_value = "release")] - pub profile: String, - /// Build with the list of features activated, space or comma separated - #[arg(long, help_heading = "Features")] - pub features: Option, - /// Build with the all features activated - #[arg( - long, - conflicts_with = "features", - conflicts_with = "no_default_features", - help_heading = "Features" - )] - pub all_features: bool, - /// Build with the default feature not activated - #[arg(long, help_heading = "Features")] - pub no_default_features: bool, - /// Directory to copy wasm files to - /// - /// If provided, wasm files can be found in the cargo target directory, and - /// the specified directory. - /// - /// If ommitted, wasm files are written only to the cargo target directory. - #[arg(long)] - pub out_dir: Option, - /// Print commands to build without executing them - #[arg(long, conflicts_with = "out_dir", help_heading = "Other")] - pub print_commands_only: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Metadata(#[from] cargo_metadata::Error), - #[error(transparent)] - CargoCmd(io::Error), - #[error("exit status {0}")] - Exit(ExitStatus), - #[error("package {package} not found")] - PackageNotFound { package: String }, - #[error("creating out directory: {0}")] - CreatingOutDir(io::Error), - #[error("copying wasm file: {0}")] - CopyingWasmFile(io::Error), - #[error("getting the current directory: {0}")] - GettingCurrentDir(io::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let working_dir = env::current_dir().map_err(Error::GettingCurrentDir)?; - - let metadata = self.metadata()?; - let packages = self.packages(&metadata); - let target_dir = &metadata.target_directory; - - if let Some(package) = &self.package { - if packages.is_empty() { - return Err(Error::PackageNotFound { - package: package.clone(), - }); - } - } - - for p in packages { - let mut cmd = Command::new("cargo"); - cmd.stdout(Stdio::piped()); - cmd.arg("rustc"); - let manifest_path = pathdiff::diff_paths(&p.manifest_path, &working_dir) - .unwrap_or(p.manifest_path.clone().into()); - cmd.arg(format!( - "--manifest-path={}", - manifest_path.to_string_lossy() - )); - cmd.arg("--crate-type=cdylib"); - cmd.arg("--target=wasm32-unknown-unknown"); - if self.profile == "release" { - cmd.arg("--release"); - } else { - cmd.arg(format!("--profile={}", self.profile)); - } - if self.all_features { - cmd.arg("--all-features"); - } - if self.no_default_features { - cmd.arg("--no-default-features"); - } - if let Some(features) = self.features() { - let requested: HashSet = features.iter().cloned().collect(); - let available = p.features.iter().map(|f| f.0).cloned().collect(); - let activate = requested.intersection(&available).join(","); - if !activate.is_empty() { - cmd.arg(format!("--features={activate}")); - } - } - let cmd_str = format!( - "cargo {}", - cmd.get_args().map(OsStr::to_string_lossy).join(" ") - ); - - if self.print_commands_only { - println!("{cmd_str}"); - } else { - eprintln!("{cmd_str}"); - let status = cmd.status().map_err(Error::CargoCmd)?; - if !status.success() { - return Err(Error::Exit(status)); - } - - if let Some(out_dir) = &self.out_dir { - fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?; - - let file = format!("{}.wasm", p.name.replace('-', "_")); - let target_file_path = Path::new(target_dir) - .join("wasm32-unknown-unknown") - .join(&self.profile) - .join(&file); - let out_file_path = Path::new(out_dir).join(&file); - fs::copy(target_file_path, out_file_path).map_err(Error::CopyingWasmFile)?; - } - } - } - - Ok(()) - } - - fn features(&self) -> Option> { - self.features - .as_ref() - .map(|f| f.split(&[',', ' ']).map(String::from).collect()) - } - - fn packages(&self, metadata: &Metadata) -> Vec { - metadata - .packages - .iter() - .filter(|p| - // Filter by the package name if one is provided. - self.package.is_none() || Some(&p.name) == self.package.as_ref()) - .filter(|p| { - // Filter crates by those that build to cdylib (wasm), unless a - // package is provided. - self.package.is_some() - || p.targets - .iter() - .any(|t| t.crate_types.iter().any(|c| c == "cdylib")) - }) - .cloned() - .collect() - } - - fn metadata(&self) -> Result { - let mut cmd = MetadataCommand::new(); - cmd.no_deps(); - cmd.manifest_path(&self.manifest_path); - // Do not configure features on the metadata command, because we are - // only collecting non-dependency metadata, features have no impact on - // the output. - cmd.exec() - } -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs deleted file mode 100644 index 9baf4459..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod asset; -pub mod wasm; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Deploy builtin Soroban Asset Contract - Asset(asset::Cmd), - /// Deploy normal Wasm Contract - Wasm(wasm::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run().await?, - Cmd::Wasm(wasm) => wasm.run().await?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs deleted file mode 100644 index c10bf816..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ /dev/null @@ -1,155 +0,0 @@ -use clap::{arg, command, Parser}; -use soroban_env_host::{ - xdr::{ - Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, - LedgerKeyContractData, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, Uint256, VecM, - }, - HostError, -}; -use std::convert::Infallible; -use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; - -use crate::{ - commands::config, - rpc::{Client, Error as SorobanRpcError}, - utils::{contract_id_hash_from_asset, parsing::parse_asset}, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error(transparent)] - Client(#[from] SorobanRpcError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - ParseAssetError(#[from] crate::utils::parsing::Error), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" - #[arg(long)] - pub asset: String, - - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - // Parse asset - let asset = parse_asset(&self.asset)?; - - let res_str = self.run_against_rpc_server(asset).await?; - println!("{res_str}"); - Ok(()) - } - - async fn run_against_rpc_server(&self, asset: Asset) -> Result { - let network = self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - // TODO: use symbols for the method names (both here and in serve) - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - let network_passphrase = &network.network_passphrase; - let contract_id = contract_id_hash_from_asset(&asset, network_passphrase)?; - let tx = build_wrap_token_tx( - &asset, - &contract_id, - sequence + 1, - self.fee.fee, - network_passphrase, - &key, - )?; - - client - .prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None) - .await?; - - Ok(stellar_strkey::Contract(contract_id.0).to_string()) - } -} - -fn build_wrap_token_tx( - asset: &Asset, - contract_id: &Hash, - sequence: i64, - fee: u32, - _network_passphrase: &str, - key: &ed25519_dalek::SigningKey, -) -> Result { - let contract = ScAddress::Contract(contract_id.clone()); - let mut read_write = vec![ - ContractData(LedgerKeyContractData { - contract: contract.clone(), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }), - ContractData(LedgerKeyContractData { - contract: contract.clone(), - key: ScVal::Vec(Some( - vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?, - )), - durability: ContractDataDurability::Persistent, - }), - ]; - if asset != &Asset::Native { - read_write.push(ContractData(LedgerKeyContractData { - contract, - key: ScVal::Vec(Some( - vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?, - )), - durability: ContractDataDurability::Persistent, - })); - } - - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::CreateContract(CreateContractArgs { - contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), - executable: ContractExecutable::StellarAsset, - }), - auth: VecM::default(), - }), - }; - - Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }) -} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs deleted file mode 100644 index 76c13017..00000000 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ /dev/null @@ -1,228 +0,0 @@ -use std::array::TryFromSliceError; -use std::fmt::Debug; -use std::num::ParseIntError; - -use clap::{arg, command, Parser}; -use rand::Rng; -use soroban_env_host::{ - xdr::{ - AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, - CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, - MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, - SequenceNumber, Transaction, TransactionExt, Uint256, VecM, - }, - HostError, -}; - -use crate::commands::contract::{self, id::wasm::get_contract_id}; -use crate::{ - commands::{config, contract::install, HEADING_RPC}, - rpc::{self, Client}, - utils, wasm, -}; - -#[derive(Parser, Debug, Clone)] -#[command(group( - clap::ArgGroup::new("wasm_src") - .required(true) - .args(&["wasm", "wasm_hash"]), -))] -#[group(skip)] -pub struct Cmd { - /// WASM file to deploy - #[arg(long, group = "wasm_src")] - wasm: Option, - /// Hash of the already installed/deployed WASM file - #[arg(long = "wasm-hash", conflicts_with = "wasm", group = "wasm_src")] - wasm_hash: Option, - /// Custom salt 32-byte salt for the token id - #[arg( - long, - help_heading = HEADING_RPC, - )] - salt: Option, - #[command(flatten)] - config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, - #[arg(long, short = 'i', default_value = "false")] - /// Whether to ignore safety checks when deploying contracts - pub ignore_checks: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Install(#[from] install::Error), - #[error(transparent)] - Host(#[from] HostError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error("cannot parse salt: {salt}")] - CannotParseSalt { salt: String }, - #[error("cannot parse contract ID {contract_id}: {error}")] - CannotParseContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("cannot parse WASM hash {wasm_hash}: {error}")] - CannotParseWasmHash { - wasm_hash: String, - error: stellar_strkey::DecodeError, - }, - #[error("Must provide either --wasm or --wash-hash")] - WasmNotProvided, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - Infallible(#[from] std::convert::Infallible), - #[error(transparent)] - WasmId(#[from] contract::id::wasm::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let res_str = self.run_and_get_contract_id().await?; - println!("{res_str}"); - Ok(()) - } - - pub async fn run_and_get_contract_id(&self) -> Result { - let wasm_hash = if let Some(wasm) = &self.wasm { - let hash = install::Cmd { - wasm: wasm::Args { wasm: wasm.clone() }, - config: self.config.clone(), - fee: self.fee.clone(), - ignore_checks: self.ignore_checks, - } - .run_and_get_hash() - .await?; - hex::encode(hash) - } else { - self.wasm_hash - .as_ref() - .ok_or(Error::WasmNotProvided)? - .to_string() - }; - - let hash = Hash(utils::contract_id_from_str(&wasm_hash).map_err(|e| { - Error::CannotParseWasmHash { - wasm_hash: wasm_hash.clone(), - error: e, - } - })?); - - self.run_against_rpc_server(hash).await - } - - async fn run_against_rpc_server(&self, wasm_hash: Hash) -> Result { - let network = self.config.get_network()?; - let salt: [u8; 32] = match &self.salt { - Some(h) => soroban_spec_tools::utils::padded_hex_from_str(h, 32) - .map_err(|_| Error::CannotParseSalt { salt: h.clone() })? - .try_into() - .map_err(|_| Error::CannotParseSalt { salt: h.clone() })?, - None => rand::thread_rng().gen::<[u8; 32]>(), - }; - - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - let (tx, contract_id) = build_create_contract_tx( - wasm_hash, - sequence + 1, - self.fee.fee, - &network.network_passphrase, - salt, - &key, - )?; - client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - Ok(stellar_strkey::Contract(contract_id.0).to_string()) - } -} - -fn build_create_contract_tx( - hash: Hash, - sequence: i64, - fee: u32, - network_passphrase: &str, - salt: [u8; 32], - key: &ed25519_dalek::SigningKey, -) -> Result<(Transaction, Hash), Error> { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519( - key.verifying_key().to_bytes().into(), - )); - - let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { - address: ScAddress::Account(source_account), - salt: Uint256(salt), - }); - let contract_id = get_contract_id(contract_id_preimage.clone(), network_passphrase)?; - - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::CreateContract(CreateContractArgs { - contract_id_preimage, - executable: ContractExecutable::Wasm(hash), - }), - auth: VecM::default(), - }), - }; - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }; - - Ok((tx, Hash(contract_id.into()))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_create_contract() { - let hash = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") - .unwrap() - .try_into() - .unwrap(); - let result = build_create_contract_tx( - Hash(hash), - 300, - 1, - "Public Global Stellar Network ; September 2015", - [0u8; 32], - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), - ); - - assert!(result.is_ok()); - } -} diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs deleted file mode 100644 index 7e9f1e98..00000000 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{fmt::Debug, path::Path, str::FromStr}; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, - TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, -}; - -use crate::{ - commands::config, - key, - rpc::{self, Client}, - wasm, Pwd, -}; - -const MAX_LEDGERS_TO_EXTEND: u32 = 535_679; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Number of ledgers to extend the entries - #[arg(long, required = true)] - pub ledgers_to_extend: u32, - /// Only print the new Time To Live ledger - #[arg(long)] - pub ttl_ledger_only: bool, - #[command(flatten)] - pub key: key::Args, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required")] - KeyIsRequired, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("Ledger entry not found")] - LedgerEntryNotFound, - #[error("missing operation result")] - MissingOperationResult, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error(transparent)] - Key(#[from] key::Error), -} - -impl Cmd { - #[allow(clippy::too_many_lines)] - pub async fn run(&self) -> Result<(), Error> { - let ttl_ledger = self.run_against_rpc_server().await?; - if self.ttl_ledger_only { - println!("{ttl_ledger}"); - } else { - println!("New ttl ledger: {ttl_ledger}"); - } - - Ok(()) - } - - fn ledgers_to_extend(&self) -> u32 { - let res = u32::min(self.ledgers_to_extend, MAX_LEDGERS_TO_EXTEND); - if res < self.ledgers_to_extend { - tracing::warn!( - "Ledgers to extend is too large, using max value of {MAX_LEDGERS_TO_EXTEND}" - ); - } - res - } - - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; - let extend_to = self.ledgers_to_extend(); - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee: self.fee.fee, - seq_num: SequenceNumber(sequence + 1), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::ExtendFootprintTtl(ExtendFootprintTtlOp { - ext: ExtensionPoint::V0, - extend_to, - }), - }] - .try_into()?, - ext: TransactionExt::V1(SorobanTransactionData { - ext: ExtensionPoint::V0, - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: keys.clone().try_into()?, - read_write: vec![].try_into()?, - }, - instructions: 0, - read_bytes: 0, - write_bytes: 0, - }, - resource_fee: 0, - }), - }; - - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - - tracing::trace!(?result); - tracing::trace!(?meta); - if !events.is_empty() { - tracing::info!("Events:\n {events:#?}"); - } - - // The transaction from core will succeed regardless of whether it actually found & extended - // the entry, so we have to inspect the result meta to tell if it worked or not. - let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { - return Err(Error::LedgerEntryNotFound); - }; - - // Simply check if there is exactly one entry here. We only support extending a single - // entry via this command (which we should fix separately, but). - if operations.len() == 0 { - return Err(Error::LedgerEntryNotFound); - } - - if operations[0].changes.is_empty() { - let entry = client.get_full_ledger_entries(&keys).await?; - let extension = entry.entries[0].live_until_ledger_seq; - if entry.latest_ledger + i64::from(extend_to) < i64::from(extension) { - return Ok(extension); - } - } - - match (&operations[0].changes[0], &operations[0].changes[1]) { - ( - LedgerEntryChange::State(_), - LedgerEntryChange::Updated(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }), - ) => Ok(*live_until_ledger_seq), - _ => Err(Error::LedgerEntryNotFound), - } - } -} diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs deleted file mode 100644 index 61a82fc4..00000000 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::convert::Infallible; - -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::{fmt::Debug, fs, io}; - -use clap::{arg, command, Parser}; -use soroban_env_host::{ - budget::Budget, - storage::Storage, - xdr::{ - self, ContractCodeEntry, ContractDataDurability, ContractDataEntry, ContractExecutable, - Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractCode, - LedgerKeyContractData, ScAddress, ScContractInstance, ScVal, - }, -}; - -use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; - -use super::super::config::{self, locator}; -use crate::commands::network::{self, Network}; -use crate::{ - rpc::{self, Client}, - utils, Pwd, -}; - -#[derive(Parser, Debug, Default, Clone)] -#[allow(clippy::struct_excessive_bools)] -#[group(skip)] -pub struct Cmd { - /// Contract ID to fetch - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] - pub contract_id: String, - /// Where to write output otherwise stdout is used - #[arg(long, short = 'o')] - pub out_file: Option, - #[command(flatten)] - pub locator: locator::Args, - #[command(flatten)] - pub network: network::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.locator.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Locator(#[from] locator::Error), - #[error(transparent)] - Xdr(#[from] XdrError), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("missing result")] - MissingResult, - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error("reading file {0:?}: {1}")] - CannotWriteContractFile(PathBuf, io::Error), - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error("network details not provided")] - NetworkNotProvided, - #[error(transparent)] - Network(#[from] network::Error), - #[error("cannot create contract directory for {0:?}")] - CannotCreateContractDir(PathBuf), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let bytes = self.get_bytes().await?; - if let Some(out_file) = &self.out_file { - if let Some(parent) = out_file.parent() { - if !parent.exists() { - fs::create_dir_all(parent) - .map_err(|_| Error::CannotCreateContractDir(out_file.clone()))?; - } - } - fs::write(out_file, bytes) - .map_err(|io| Error::CannotWriteContractFile(out_file.clone(), io)) - } else { - let stdout = std::io::stdout(); - let mut handle = stdout.lock(); - handle.write_all(&bytes)?; - handle.flush()?; - Ok(()) - } - } - - pub async fn get_bytes(&self) -> Result, Error> { - self.run_against_rpc_server().await - } - - pub fn network(&self) -> Result { - Ok(self.network.get(&self.locator)?) - } - - pub async fn run_against_rpc_server(&self) -> Result, Error> { - let network = self.network()?; - tracing::trace!(?network); - let contract_id = self.contract_id()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - // async closures are not yet stable - Ok(client.get_remote_wasm(&contract_id).await?) - } - - fn contract_id(&self) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } -} - -pub fn get_contract_wasm_from_storage( - storage: &mut Storage, - contract_id: [u8; 32], -) -> Result, FromWasmError> { - let key = LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(contract_id.into()), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }); - match storage.get(&key.into(), &Budget::default()) { - Ok(rc) => match rc.as_ref() { - xdr::LedgerEntry { - data: - LedgerEntryData::ContractData(ContractDataEntry { - val: ScVal::ContractInstance(ScContractInstance { executable, .. }), - .. - }), - .. - } => match executable { - ContractExecutable::Wasm(hash) => { - if let Ok(rc) = storage.get( - &LedgerKey::ContractCode(LedgerKeyContractCode { hash: hash.clone() }) - .into(), - &Budget::default(), - ) { - match rc.as_ref() { - xdr::LedgerEntry { - data: LedgerEntryData::ContractCode(ContractCodeEntry { code, .. }), - .. - } => Ok(code.to_vec()), - _ => Err(FromWasmError::NotFound), - } - } else { - Err(FromWasmError::NotFound) - } - } - ContractExecutable::StellarAsset => todo!(), - }, - _ => Err(FromWasmError::NotFound), - }, - _ => Err(FromWasmError::NotFound), - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id.rs b/cmd/soroban-cli/src/commands/contract/id.rs deleted file mode 100644 index bb8744d5..00000000 --- a/cmd/soroban-cli/src/commands/contract/id.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod asset; -pub mod wasm; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Deploy builtin Soroban Asset Contract - Asset(asset::Cmd), - /// Deploy normal Wasm Contract - Wasm(wasm::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run()?, - Cmd::Wasm(wasm) => wasm.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs deleted file mode 100644 index 34e5767a..00000000 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ /dev/null @@ -1,36 +0,0 @@ -use clap::{arg, command, Parser}; - -use crate::commands::config; - -use crate::utils::contract_id_hash_from_asset; -use crate::utils::parsing::parse_asset; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" - #[arg(long)] - pub asset: String, - - #[command(flatten)] - pub config: config::Args, -} -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - ParseError(#[from] crate::utils::parsing::Error), - #[error(transparent)] - ConfigError(#[from] crate::commands::config::Error), - #[error(transparent)] - Xdr(#[from] soroban_env_host::xdr::Error), -} -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let asset = parse_asset(&self.asset)?; - let network = self.config.get_network()?; - let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; - let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); - println!("{strkey_contract_id}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs deleted file mode 100644 index 9c02f07d..00000000 --- a/cmd/soroban-cli/src/commands/contract/id/wasm.rs +++ /dev/null @@ -1,68 +0,0 @@ -use clap::{arg, command, Parser}; -use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ - self, AccountId, ContractIdPreimage, ContractIdPreimageFromAddress, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, PublicKey, ScAddress, Uint256, WriteXdr, -}; - -use crate::commands::config; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// ID of the Soroban contract - #[arg(long)] - pub salt: String, - - #[command(flatten)] - pub config: config::Args, -} -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - ParseError(#[from] crate::utils::parsing::Error), - #[error(transparent)] - ConfigError(#[from] crate::commands::config::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error("cannot parse salt {0}")] - CannotParseSalt(String), -} -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let salt: [u8; 32] = soroban_spec_tools::utils::padded_hex_from_str(&self.salt, 32) - .map_err(|_| Error::CannotParseSalt(self.salt.clone()))? - .try_into() - .map_err(|_| Error::CannotParseSalt(self.salt.clone()))?; - let contract_id_preimage = - contract_preimage(&self.config.key_pair()?.verifying_key(), salt); - let contract_id = get_contract_id( - contract_id_preimage.clone(), - &self.config.get_network()?.network_passphrase, - )?; - let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); - println!("{strkey_contract_id}"); - Ok(()) - } -} - -pub fn contract_preimage(key: &ed25519_dalek::VerifyingKey, salt: [u8; 32]) -> ContractIdPreimage { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.to_bytes().into())); - ContractIdPreimage::Address(ContractIdPreimageFromAddress { - address: ScAddress::Account(source_account), - salt: Uint256(salt), - }) -} - -pub fn get_contract_id( - contract_id_preimage: ContractIdPreimage, - network_passphrase: &str, -) -> Result { - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { - network_id, - contract_id_preimage, - }); - let preimage_xdr = preimage.to_xdr(Limits::none())?; - Ok(Hash(Sha256::digest(preimage_xdr).into())) -} diff --git a/cmd/soroban-cli/src/commands/contract/inspect.rs b/cmd/soroban-cli/src/commands/contract/inspect.rs deleted file mode 100644 index 355c18ca..00000000 --- a/cmd/soroban-cli/src/commands/contract/inspect.rs +++ /dev/null @@ -1,49 +0,0 @@ -use clap::{command, Parser}; -use soroban_env_host::xdr; -use std::{fmt::Debug, path::PathBuf}; -use tracing::debug; - -use super::SpecOutput; -use crate::{commands::config::locator, wasm}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, - /// Output just XDR in base64 - #[arg(long, default_value = "docs")] - output: SpecOutput, - - #[clap(flatten)] - locator: locator::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("missing spec for {0:?}")] - MissingSpec(PathBuf), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Spec(#[from] crate::utils::contract_spec::Error), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let wasm = self.wasm.parse()?; - debug!("File: {}", self.wasm.wasm.to_string_lossy()); - let output = match self.output { - SpecOutput::XdrBase64 => wasm - .spec_base64 - .clone() - .ok_or_else(|| Error::MissingSpec(self.wasm.wasm.clone()))?, - SpecOutput::XdrBase64Array => wasm.spec_as_json_array()?, - SpecOutput::Docs => wasm.to_string(), - }; - println!("{output}"); - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs deleted file mode 100644 index 90577529..00000000 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::array::TryFromSliceError; -use std::fmt::Debug; -use std::num::ParseIntError; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, - TransactionExt, TransactionResult, TransactionResultResult, Uint256, VecM, -}; - -use super::restore; -use crate::key; -use crate::rpc::{self, Client}; -use crate::{commands::config, utils, wasm}; - -const CONTRACT_META_SDK_KEY: &str = "rssdkver"; -const PUBLIC_NETWORK_PASSPHRASE: &str = "Public Global Stellar Network ; September 2015"; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, - #[command(flatten)] - pub wasm: wasm::Args, - #[arg(long, short = 'i', default_value = "false")] - /// Whether to ignore safety checks when deploying contracts - pub ignore_checks: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error("internal conversion error: {0}")] - TryFromSliceError(#[from] TryFromSliceError), - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error(transparent)] - Restore(#[from] restore::Error), - #[error("cannot parse WASM file {wasm}: {error}")] - CannotParseWasm { - wasm: std::path::PathBuf, - error: wasm::Error, - }, - #[error("the deployed smart contract {wasm} was built with Soroban Rust SDK v{version}, a release candidate version not intended for use with the Stellar Public Network. To deploy anyway, use --ignore-checks")] - ContractCompiledWithReleaseCandidateSdk { - wasm: std::path::PathBuf, - version: String, - }, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let res_str = hex::encode(self.run_and_get_hash().await?); - println!("{res_str}"); - Ok(()) - } - - pub async fn run_and_get_hash(&self) -> Result { - self.run_against_rpc_server(&self.wasm.read()?).await - } - - async fn run_against_rpc_server(&self, contract: &[u8]) -> Result { - let network = self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let wasm_spec = &self.wasm.parse().map_err(|e| Error::CannotParseWasm { - wasm: self.wasm.wasm.clone(), - error: e, - })?; - // Check Rust SDK version if using the public network. - if let Some(rs_sdk_ver) = get_contract_meta_sdk_version(wasm_spec) { - if rs_sdk_ver.contains("rc") - && !self.ignore_checks - && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE - { - return Err(Error::ContractCompiledWithReleaseCandidateSdk { - wasm: self.wasm.wasm.clone(), - version: rs_sdk_ver, - }); - } else if rs_sdk_ver.contains("rc") - && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE - { - tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); - } - } - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let (tx_without_preflight, hash) = - build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?; - - // Currently internal errors are not returned if the contract code is expired - if let ( - TransactionResult { - result: TransactionResultResult::TxInternalError, - .. - }, - _, - _, - ) = client - .prepare_and_send_transaction( - &tx_without_preflight, - &key, - &[], - &network.network_passphrase, - None, - None, - ) - .await? - { - // Now just need to restore it and don't have to install again - restore::Cmd { - key: key::Args { - contract_id: None, - key: None, - key_xdr: None, - wasm: Some(self.wasm.wasm.clone()), - wasm_hash: None, - durability: super::Durability::Persistent, - }, - config: self.config.clone(), - fee: self.fee.clone(), - ledgers_to_extend: None, - ttl_ledger_only: true, - } - .run_against_rpc_server() - .await?; - } - - Ok(hash) - } -} - -fn get_contract_meta_sdk_version(wasm_spec: &utils::contract_spec::ContractSpec) -> Option { - let rs_sdk_version_option = if let Some(_meta) = &wasm_spec.meta_base64 { - wasm_spec.meta.iter().find(|entry| match entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, .. }) => { - key.to_utf8_string_lossy().contains(CONTRACT_META_SDK_KEY) - } - }) - } else { - None - }; - if let Some(rs_sdk_version_entry) = &rs_sdk_version_option { - match rs_sdk_version_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { val, .. }) => { - return Some(val.to_utf8_string_lossy()); - } - } - } - None -} - -pub(crate) fn build_install_contract_code_tx( - source_code: &[u8], - sequence: i64, - fee: u32, - key: &ed25519_dalek::SigningKey, -) -> Result<(Transaction, Hash), XdrError> { - let hash = utils::contract_hash(source_code)?; - - let op = Operation { - source_account: Some(MuxedAccount::Ed25519(Uint256( - key.verifying_key().to_bytes(), - ))), - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::UploadContractWasm(source_code.try_into()?), - auth: VecM::default(), - }), - }; - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }; - - Ok((tx, hash)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_install_contract_code() { - let result = build_install_contract_code_tx( - b"foo", - 300, - 1, - &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap(), - ); - - assert!(result.is_ok()); - } -} diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs deleted file mode 100644 index 669342b0..00000000 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ /dev/null @@ -1,484 +0,0 @@ -use std::collections::HashMap; -use std::convert::{Infallible, TryInto}; -use std::ffi::OsString; -use std::num::ParseIntError; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::{fmt::Debug, fs, io}; - -use clap::{arg, command, value_parser, Parser}; -use ed25519_dalek::SigningKey; -use heck::ToKebabCase; - -use soroban_env_host::{ - xdr::{ - self, Error as XdrError, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, - LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, - Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, - SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, TransactionExt, - Uint256, VecM, - }, - HostError, -}; - -use soroban_spec::read::FromWasmError; -use stellar_strkey::DecodeError; - -use super::super::{ - config::{self, locator}, - events, -}; -use crate::{ - commands::global, - rpc::{self, Client}, - utils::{self, contract_spec}, - Pwd, -}; -use soroban_spec_tools::Spec; - -#[derive(Parser, Debug, Default, Clone)] -#[allow(clippy::struct_excessive_bools)] -#[group(skip)] -pub struct Cmd { - /// Contract ID to invoke - #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] - pub contract_id: String, - // For testing only - #[arg(skip)] - pub wasm: Option, - /// Output the cost execution to stderr - #[arg(long = "cost")] - pub cost: bool, - /// Function name as subcommand, then arguments for that function as `--arg-name value` - #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] - pub slop: Vec, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing argument {arg}: {error}")] - CannotParseArg { - arg: String, - error: soroban_spec_tools::Error, - }, - #[error("cannot add contract to ledger entries: {0}")] - CannotAddContractToLedgerEntries(XdrError), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("reading file {0:?}: {1}")] - CannotReadContractFile(PathBuf, io::Error), - #[error("committing file {filepath}: {error}")] - CannotCommitEventsFile { - filepath: std::path::PathBuf, - error: events::Error, - }, - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error("function {0} was not found in the contract")] - FunctionNotFoundInContractSpec(String), - #[error("parsing contract spec: {0}")] - CannotParseContractSpec(FromWasmError), - // }, - #[error("function name {0} is too long")] - FunctionNameTooLong(String), - #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] - MaxNumberOfArgumentsReached { current: usize, maximum: usize }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintResult { - result: ScVal, - error: soroban_spec_tools::Error, - }, - #[error(transparent)] - Xdr(#[from] XdrError), - #[error("error parsing int: {0}")] - ParseIntError(#[from] ParseIntError), - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error("missing operation result")] - MissingOperationResult, - #[error("missing result")] - MissingResult, - #[error(transparent)] - StrVal(#[from] soroban_spec_tools::Error), - #[error("error loading signing key: {0}")] - SignatureError(#[from] ed25519_dalek::SignatureError), - #[error(transparent)] - Config(#[from] config::Error), - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error("Missing argument {0}")] - MissingArgument(String), - #[error(transparent)] - Clap(#[from] clap::Error), - #[error(transparent)] - Locator(#[from] locator::Error), - #[error("Contract Error\n{0}: {1}")] - ContractInvoke(String, String), - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), - #[error(transparent)] - ContractSpec(#[from] contract_spec::Error), - #[error("")] - MissingFileArg(PathBuf), -} - -impl From for Error { - fn from(_: Infallible) -> Self { - unreachable!() - } -} - -impl Cmd { - fn build_host_function_parameters( - &self, - contract_id: [u8; 32], - spec_entries: &[ScSpecEntry], - ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { - let spec = Spec(Some(spec_entries.to_vec())); - let mut cmd = clap::Command::new(self.contract_id.clone()) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - - for ScSpecFunctionV0 { name, .. } in spec.find_functions()? { - cmd = cmd.subcommand(build_custom_cmd(&name.to_utf8_string_lossy(), &spec)?); - } - cmd.build(); - let long_help = cmd.render_long_help(); - let mut matches_ = cmd.get_matches_from(&self.slop); - let Some((function, matches_)) = &matches_.remove_subcommand() else { - println!("{long_help}"); - std::process::exit(1); - }; - - let func = spec.find_function(function)?; - // create parsed_args in same order as the inputs to func - let mut signers: Vec = vec![]; - let parsed_args = func - .inputs - .iter() - .map(|i| { - let name = i.name.to_utf8_string()?; - if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(i.type_, ScSpecTypeDef::Address) { - let cmd = crate::commands::keys::address::Cmd { - name: s.clone(), - hd_path: Some(0), - locator: self.config.locator.clone(), - }; - if let Ok(address) = cmd.public_key() { - s = address.to_string(); - } - if let Ok(key) = cmd.private_key() { - signers.push(key); - } - } - spec.from_string(&s, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { - Ok(ScVal::Void) - } else if let Some(arg_path) = - matches_.get_one::(&fmt_arg_file_name(&name)) - { - if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - Ok(ScVal::try_from( - &std::fs::read(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, - ) - .map_err(|()| Error::CannotParseArg { - arg: name.clone(), - error: soroban_spec_tools::Error::Unknown, - })?) - } else { - let file_contents = std::fs::read_to_string(arg_path) - .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; - tracing::debug!( - "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", - i.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &i.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error }) - } - } else { - Err(Error::MissingArgument(name)) - } - }) - .collect::, Error>>()?; - - let contract_address_arg = ScAddress::Contract(Hash(contract_id)); - let function_symbol_arg = function - .try_into() - .map_err(|()| Error::FunctionNameTooLong(function.clone()))?; - - let final_args = - parsed_args - .clone() - .try_into() - .map_err(|_| Error::MaxNumberOfArgumentsReached { - current: parsed_args.len(), - maximum: ScVec::default().max_len(), - })?; - - let invoke_args = InvokeContractArgs { - contract_address: contract_address_arg, - function_name: function_symbol_arg, - args: final_args, - }; - - Ok((function.clone(), spec, invoke_args, signers)) - } - - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - let res = self.invoke(global_args).await?; - println!("{res}"); - Ok(()) - } - - pub async fn invoke(&self, global_args: &global::Args) -> Result { - self.run_against_rpc_server(global_args).await - } - - pub async fn run_against_rpc_server( - &self, - global_args: &global::Args, - ) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let contract_id = self.contract_id()?; - let spec_entries = self.spec_entries()?; - if let Some(spec_entries) = &spec_entries { - // For testing wasm arg parsing - let _ = self.build_host_function_parameters(contract_id, spec_entries)?; - } - let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - // Get the contract - let spec_entries = client.get_remote_contract_spec(&contract_id).await?; - - // Get the ledger footprint - let (function, spec, host_function_params, signers) = - self.build_host_function_parameters(contract_id, &spec_entries)?; - let tx = build_invoke_contract_tx( - host_function_params.clone(), - sequence + 1, - self.fee.fee, - &key, - )?; - - let (result, meta, events) = client - .prepare_and_send_transaction( - &tx, - &key, - &signers, - &network.network_passphrase, - Some(log_events), - (global_args.verbose || global_args.very_verbose || self.cost) - .then_some(log_resources), - ) - .await?; - - tracing::debug!(?result); - crate::log::diagnostic_events(&events, tracing::Level::INFO); - let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { - soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), - .. - }) = meta - else { - return Err(Error::MissingOperationResult); - }; - - output_to_string(&spec, &return_value, &function) - } - - pub fn read_wasm(&self) -> Result>, Error> { - Ok(if let Some(wasm) = self.wasm.as_ref() { - Some(fs::read(wasm).map_err(|e| Error::CannotReadContractFile(wasm.clone(), e))?) - } else { - None - }) - } - - pub fn spec_entries(&self) -> Result>, Error> { - self.read_wasm()? - .map(|wasm| { - soroban_spec::read::from_wasm(&wasm).map_err(Error::CannotParseContractSpec) - }) - .transpose() - } -} - -impl Cmd { - fn contract_id(&self) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(&self.contract_id) - .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) - } -} - -fn log_events( - footprint: &LedgerFootprint, - auth: &[VecM], - events: &[xdr::DiagnosticEvent], -) { - crate::log::auth(auth); - crate::log::diagnostic_events(events, tracing::Level::TRACE); - crate::log::footprint(footprint); -} - -fn log_resources(resources: &SorobanResources) { - crate::log::cost(resources); -} - -pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result { - let mut res_str = String::new(); - if let Some(output) = spec.find_function(function)?.outputs.first() { - res_str = spec - .xdr_to_json(res, output) - .map_err(|e| Error::CannotPrintResult { - result: res.clone(), - error: e, - })? - .to_string(); - } - Ok(res_str) -} - -fn build_invoke_contract_tx( - parameters: InvokeContractArgs, - sequence: i64, - fee: u32, - key: &SigningKey, -) -> Result { - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::InvokeContract(parameters), - auth: VecM::default(), - }), - }; - Ok(Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee, - seq_num: SequenceNumber(sequence), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![op].try_into()?, - ext: TransactionExt::V0, - }) -} - -fn build_custom_cmd(name: &str, spec: &Spec) -> Result { - let func = spec - .find_function(name) - .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; - - // Parse the function arguments - let inputs_map = &func - .inputs - .iter() - .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) - .collect::>(); - let name: &'static str = Box::leak(name.to_string().into_boxed_str()); - let mut cmd = clap::Command::new(name) - .no_binary_name(true) - .term_width(300) - .max_term_width(300); - let kebab_name = name.to_kebab_case(); - if kebab_name != name { - cmd = cmd.alias(kebab_name); - } - let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); - let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); - - cmd = cmd.about(Some(doc)).long_about(long_doc); - for (name, type_) in inputs_map { - let mut arg = clap::Arg::new(name); - let file_arg_name = fmt_arg_file_name(name); - let mut file_arg = clap::Arg::new(&file_arg_name); - arg = arg - .long(name) - .alias(name.to_kebab_case()) - .num_args(1) - .value_parser(clap::builder::NonEmptyStringValueParser::new()) - .long_help(spec.doc(name, type_)?); - - file_arg = file_arg - .long(&file_arg_name) - .alias(file_arg_name.to_kebab_case()) - .num_args(1) - .hide(true) - .value_parser(value_parser!(PathBuf)) - .conflicts_with(name); - - if let Some(value_name) = spec.arg_value_name(type_, 0) { - let value_name: &'static str = Box::leak(value_name.into_boxed_str()); - arg = arg.value_name(value_name); - } - - // Set up special-case arg rules - arg = match type_ { - xdr::ScSpecTypeDef::Bool => arg - .num_args(0..1) - .default_missing_value("true") - .default_value("false") - .num_args(0..=1), - xdr::ScSpecTypeDef::Option(_val) => arg.required(false), - xdr::ScSpecTypeDef::I256 - | xdr::ScSpecTypeDef::I128 - | xdr::ScSpecTypeDef::I64 - | xdr::ScSpecTypeDef::I32 => arg.allow_hyphen_values(true), - _ => arg, - }; - - cmd = cmd.arg(arg); - cmd = cmd.arg(file_arg); - } - Ok(cmd) -} - -fn fmt_arg_file_name(name: &str) -> String { - format!("{name}-file-path") -} - -fn arg_file_help(docs: &str) -> String { - format!( - r#"{docs} -Usage Notes: -Each arg has a corresponding ---file-path which is a path to a file containing the corresponding JSON argument. -Note: The only types which aren't JSON are Bytes and Bytes which are raw bytes"# - ) -} diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs deleted file mode 100644 index 35be97a7..00000000 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ /dev/null @@ -1,158 +0,0 @@ -pub mod asset; -pub mod bindings; -pub mod build; -pub mod deploy; -pub mod extend; -pub mod fetch; -pub mod id; -pub mod inspect; -pub mod install; -pub mod invoke; -pub mod optimize; -pub mod read; -pub mod restore; - -use crate::commands::global; - -#[derive(Debug, clap::Subcommand)] -pub enum Cmd { - /// Utilities to deploy a Stellar Asset Contract or get its id - #[command(subcommand)] - Asset(asset::Cmd), - /// Generate code client bindings for a contract - #[command(subcommand)] - Bindings(bindings::Cmd), - - Build(build::Cmd), - - /// Extend the time to live ledger of a contract-data ledger entry. - /// - /// If no keys are specified the contract itself is extended. - Extend(extend::Cmd), - - /// Deploy a wasm contract - Deploy(deploy::wasm::Cmd), - - /// Fetch a contract's Wasm binary - Fetch(fetch::Cmd), - - /// Generate the contract id for a given contract or asset - #[command(subcommand)] - Id(id::Cmd), - - /// Inspect a WASM file listing contract functions, meta, etc - Inspect(inspect::Cmd), - - /// Install a WASM file to the ledger without creating a contract instance - Install(install::Cmd), - - /// Invoke a contract function - /// - /// Generates an "implicit CLI" for the specified contract on-the-fly using the contract's - /// schema, which gets embedded into every Soroban contract. The "slop" in this command, - /// everything after the `--`, gets passed to this implicit CLI. Get in-depth help for a given - /// contract: - /// - /// soroban contract invoke ... -- --help - Invoke(invoke::Cmd), - - /// Optimize a WASM file - Optimize(optimize::Cmd), - - /// Print the current value of a contract-data ledger entry - Read(read::Cmd), - - /// Restore an evicted value for a contract-data legder entry. - /// - /// If no keys are specificed the contract itself is restored. - Restore(restore::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Asset(#[from] asset::Error), - - #[error(transparent)] - Bindings(#[from] bindings::Error), - - #[error(transparent)] - Build(#[from] build::Error), - - #[error(transparent)] - Extend(#[from] extend::Error), - - #[error(transparent)] - Deploy(#[from] deploy::wasm::Error), - - #[error(transparent)] - Fetch(#[from] fetch::Error), - #[error(transparent)] - Id(#[from] id::Error), - - #[error(transparent)] - Inspect(#[from] inspect::Error), - - #[error(transparent)] - Install(#[from] install::Error), - - #[error(transparent)] - Invoke(#[from] invoke::Error), - - #[error(transparent)] - Optimize(#[from] optimize::Error), - - #[error(transparent)] - Read(#[from] read::Error), - - #[error(transparent)] - Restore(#[from] restore::Error), -} - -impl Cmd { - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { - match &self { - Cmd::Asset(asset) => asset.run().await?, - Cmd::Bindings(bindings) => bindings.run().await?, - Cmd::Build(build) => build.run()?, - Cmd::Extend(extend) => extend.run().await?, - Cmd::Deploy(deploy) => deploy.run().await?, - Cmd::Id(id) => id.run()?, - Cmd::Inspect(inspect) => inspect.run()?, - Cmd::Install(install) => install.run().await?, - Cmd::Invoke(invoke) => invoke.run(global_args).await?, - Cmd::Optimize(optimize) => optimize.run()?, - Cmd::Fetch(fetch) => fetch.run().await?, - Cmd::Read(read) => read.run().await?, - Cmd::Restore(restore) => restore.run().await?, - } - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum Durability { - /// Persistent - Persistent, - /// Temporary - Temporary, -} - -impl From<&Durability> for soroban_env_host::xdr::ContractDataDurability { - fn from(d: &Durability) -> Self { - match d { - Durability::Persistent => soroban_env_host::xdr::ContractDataDurability::Persistent, - Durability::Temporary => soroban_env_host::xdr::ContractDataDurability::Temporary, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum SpecOutput { - /// XDR of array of contract spec entries - XdrBase64, - /// Array of xdr of contract spec entries - XdrBase64Array, - /// Pretty print of contract spec entries - Docs, -} diff --git a/cmd/soroban-cli/src/commands/contract/optimize.rs b/cmd/soroban-cli/src/commands/contract/optimize.rs deleted file mode 100644 index 751dabb1..00000000 --- a/cmd/soroban-cli/src/commands/contract/optimize.rs +++ /dev/null @@ -1,80 +0,0 @@ -use clap::{arg, command, Parser}; -use std::fmt::Debug; -#[cfg(feature = "opt")] -use wasm_opt::{Feature, OptimizationError, OptimizationOptions}; - -use crate::wasm; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - wasm: wasm::Args, - /// Path to write the optimized WASM file to (defaults to same location as --wasm with .optimized.wasm suffix) - #[arg(long)] - wasm_out: Option, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[cfg(feature = "opt")] - #[error("optimization error: {0}")] - OptimizationError(OptimizationError), - #[cfg(not(feature = "opt"))] - #[error("Must install with \"opt\" feature, e.g. `cargo install soroban-cli --features opt")] - Install, -} - -impl Cmd { - #[cfg(not(feature = "opt"))] - pub fn run(&self) -> Result<(), Error> { - Err(Error::Install) - } - - #[cfg(feature = "opt")] - pub fn run(&self) -> Result<(), Error> { - let wasm_size = self.wasm.len()?; - - println!( - "Reading: {} ({} bytes)", - self.wasm.wasm.to_string_lossy(), - wasm_size - ); - - let wasm_out = self.wasm_out.as_ref().cloned().unwrap_or_else(|| { - let mut wasm_out = self.wasm.wasm.clone(); - wasm_out.set_extension("optimized.wasm"); - wasm_out - }); - println!("Writing to: {}...", wasm_out.to_string_lossy()); - - let mut options = OptimizationOptions::new_optimize_for_size_aggressively(); - options.converge = true; - - // Explicitly set to MVP + sign-ext + mutable-globals, which happens to - // also be the default featureset, but just to be extra clear we set it - // explicitly. - // - // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as - // well as Clang generate code with sign-ext + mutable-globals enabled, - // so Soroban has taken a change to support them also. - options.mvp_features_only(); - options.enable_feature(Feature::MutableGlobals); - options.enable_feature(Feature::SignExt); - - options - .run(&self.wasm.wasm, &wasm_out) - .map_err(Error::OptimizationError)?; - - let wasm_out_size = wasm::len(&wasm_out)?; - println!( - "Optimized: {} ({} bytes)", - wasm_out.to_string_lossy(), - wasm_out_size - ); - - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs deleted file mode 100644 index 842832d5..00000000 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::{ - fmt::Debug, - io::{self, stdout}, -}; - -use clap::{command, Parser, ValueEnum}; -use soroban_env_host::{ - xdr::{ - ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, - ScVal, WriteXdr, - }, - HostError, -}; -use soroban_sdk::xdr::Limits; - -use crate::{ - commands::config, - key, - rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// Type of output to generate - #[arg(long, value_enum, default_value("string"))] - pub output: Output, - #[command(flatten)] - pub key: key::Args, - #[command(flatten)] - config: config::Args, -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] -pub enum Output { - /// String - String, - /// Json - Json, - /// XDR - Xdr, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - #[error("cannot parse contract ID {contract_id}: {error}")] - CannotParseContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintResult { - result: ScVal, - error: soroban_spec_tools::Error, - }, - #[error("cannot print result {result:?}: {error}")] - CannotPrintJsonResult { - result: ScVal, - error: serde_json::Error, - }, - #[error("cannot print as csv: {error}")] - CannotPrintAsCsv { error: csv::Error }, - #[error("cannot print: {error}")] - CannotPrintFlush { error: io::Error }, - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required when querying a network")] - KeyIsRequired, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Xdr(#[from] XdrError), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), - #[error("no matching contract data entries were found for the specified contract id")] - NoContractDataEntryFoundForContractID, - #[error(transparent)] - Key(#[from] key::Error), - #[error("Only contract data and code keys are allowed")] - OnlyDataAllowed, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let entries = self.run_against_rpc_server().await?; - self.output_entries(&entries) - } - - async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys()?; - Ok(client.get_full_ledger_entries(&keys).await?) - } - - fn output_entries(&self, entries: &FullLedgerEntries) -> Result<(), Error> { - if entries.entries.is_empty() { - return Err(Error::NoContractDataEntryFoundForContractID); - } - tracing::trace!("{entries:#?}"); - let mut out = csv::Writer::from_writer(stdout()); - for FullLedgerEntry { - key, - val, - live_until_ledger_seq, - last_modified_ledger, - } in &entries.entries - { - let ( - LedgerKey::ContractData(LedgerKeyContractData { key, .. }), - LedgerEntryData::ContractData(ContractDataEntry { val, .. }), - ) = (key, val) - else { - return Err(Error::OnlyDataAllowed); - }; - let output = match self.output { - Output::String => [ - soroban_spec_tools::to_string(key).map_err(|e| Error::CannotPrintResult { - result: key.clone(), - error: e, - })?, - soroban_spec_tools::to_string(val).map_err(|e| Error::CannotPrintResult { - result: val.clone(), - error: e, - })?, - last_modified_ledger.to_string(), - live_until_ledger_seq.to_string(), - ], - Output::Json => [ - serde_json::to_string_pretty(&key).map_err(|error| { - Error::CannotPrintJsonResult { - result: key.clone(), - error, - } - })?, - serde_json::to_string_pretty(&val).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - serde_json::to_string_pretty(&last_modified_ledger).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - serde_json::to_string_pretty(&live_until_ledger_seq).map_err(|error| { - Error::CannotPrintJsonResult { - result: val.clone(), - error, - } - })?, - ], - Output::Xdr => [ - key.to_xdr_base64(Limits::none())?, - val.to_xdr_base64(Limits::none())?, - last_modified_ledger.to_xdr_base64(Limits::none())?, - live_until_ledger_seq.to_xdr_base64(Limits::none())?, - ], - }; - out.write_record(output) - .map_err(|e| Error::CannotPrintAsCsv { error: e })?; - } - out.flush() - .map_err(|e| Error::CannotPrintFlush { error: e })?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs deleted file mode 100644 index 38b8a84a..00000000 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::{fmt::Debug, path::Path, str::FromStr}; - -use clap::{command, Parser}; -use soroban_env_host::xdr::{ - Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, - LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, OperationMeta, Preconditions, - RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, - TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, -}; -use stellar_strkey::DecodeError; - -use crate::{ - commands::{ - config::{self, locator}, - contract::extend, - }, - key, - rpc::{self, Client}, - wasm, Pwd, -}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub key: key::Args, - /// Number of ledgers to extend the entry - #[arg(long)] - pub ledgers_to_extend: Option, - /// Only print the new Time To Live ledger - #[arg(long)] - pub ttl_ledger_only: bool, - #[command(flatten)] - pub config: config::Args, - #[command(flatten)] - pub fee: crate::fee::Args, -} - -impl FromStr for Cmd { - type Err = clap::error::Error; - - fn from_str(s: &str) -> Result { - use clap::{CommandFactory, FromArgMatches}; - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) - } -} - -impl Pwd for Cmd { - fn set_pwd(&mut self, pwd: &Path) { - self.config.set_pwd(pwd); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("parsing key {key}: {error}")] - CannotParseKey { - key: String, - error: soroban_spec_tools::Error, - }, - #[error("parsing XDR key {key}: {error}")] - CannotParseXdrKey { key: String, error: XdrError }, - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, DecodeError), - #[error(transparent)] - Config(#[from] config::Error), - #[error("either `--key` or `--key-xdr` are required")] - KeyIsRequired, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("Ledger entry not found")] - LedgerEntryNotFound, - #[error(transparent)] - Locator(#[from] locator::Error), - #[error("missing operation result")] - MissingOperationResult, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Wasm(#[from] wasm::Error), - #[error(transparent)] - Key(#[from] key::Error), - #[error(transparent)] - Extend(#[from] extend::Error), -} - -impl Cmd { - #[allow(clippy::too_many_lines)] - pub async fn run(&self) -> Result<(), Error> { - let expiration_ledger_seq = self.run_against_rpc_server().await?; - - if let Some(ledgers_to_extend) = self.ledgers_to_extend { - extend::Cmd { - key: self.key.clone(), - ledgers_to_extend, - config: self.config.clone(), - fee: self.fee.clone(), - ttl_ledger_only: false, - } - .run() - .await?; - } else { - println!("New ttl ledger: {expiration_ledger_seq}"); - } - - Ok(()) - } - - pub async fn run_against_rpc_server(&self) -> Result { - let network = self.config.get_network()?; - tracing::trace!(?network); - let entry_keys = self.key.parse_keys()?; - let network = &self.config.get_network()?; - let client = Client::new(&network.rpc_url)?; - let key = self.config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - let account_details = client.get_account(&public_strkey).await?; - let sequence: i64 = account_details.seq_num.into(); - - let tx = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), - fee: self.fee.fee, - seq_num: SequenceNumber(sequence + 1), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::RestoreFootprint(RestoreFootprintOp { - ext: ExtensionPoint::V0, - }), - }] - .try_into()?, - ext: TransactionExt::V1(SorobanTransactionData { - ext: ExtensionPoint::V0, - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: vec![].try_into()?, - read_write: entry_keys.try_into()?, - }, - instructions: 0, - read_bytes: 0, - write_bytes: 0, - }, - resource_fee: 0, - }), - }; - - let (result, meta, events) = client - .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) - .await?; - - tracing::trace!(?result); - tracing::trace!(?meta); - if !events.is_empty() { - tracing::info!("Events:\n {events:#?}"); - } - - // The transaction from core will succeed regardless of whether it actually found & - // restored the entry, so we have to inspect the result meta to tell if it worked or not. - let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { - return Err(Error::LedgerEntryNotFound); - }; - tracing::debug!("Operations:\nlen:{}\n{operations:#?}", operations.len()); - - // Simply check if there is exactly one entry here. We only support extending a single - // entry via this command (which we should fix separately, but). - if operations.len() == 0 { - return Err(Error::LedgerEntryNotFound); - } - - if operations.len() != 1 { - tracing::warn!( - "Unexpected number of operations: {}. Currently only handle one.", - operations[0].changes.len() - ); - } - parse_operations(&operations).ok_or(Error::MissingOperationResult) - } -} - -fn parse_operations(ops: &[OperationMeta]) -> Option { - ops.first().and_then(|op| { - op.changes.iter().find_map(|entry| match entry { - LedgerEntryChange::Updated(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }) - | LedgerEntryChange::Created(LedgerEntry { - data: - LedgerEntryData::Ttl(TtlEntry { - live_until_ledger_seq, - .. - }), - .. - }) => Some(*live_until_ledger_seq), - _ => None, - }) - }) -} diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs deleted file mode 100644 index aa46bbe2..00000000 --- a/cmd/soroban-cli/src/commands/events.rs +++ /dev/null @@ -1,221 +0,0 @@ -use clap::{arg, command, Parser}; -use std::io; - -use soroban_env_host::xdr::{self, Limits, ReadXdr}; - -use super::{config::locator, network}; -use crate::{rpc, utils}; - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd { - /// The first ledger sequence number in the range to pull events - /// https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence - #[arg(long, conflicts_with = "cursor", required_unless_present = "cursor")] - start_ledger: Option, - /// The cursor corresponding to the start of the event range. - #[arg( - long, - conflicts_with = "start_ledger", - required_unless_present = "start_ledger" - )] - cursor: Option, - /// Output formatting options for event stream - #[arg(long, value_enum, default_value = "pretty")] - output: OutputFormat, - /// The maximum number of events to display (defer to the server-defined limit). - #[arg(short, long, default_value = "10")] - count: usize, - /// A set of (up to 5) contract IDs to filter events on. This parameter can - /// be passed multiple times, e.g. `--id C123.. --id C456..`, or passed with - /// multiple parameters, e.g. `--id C123 C456`. - /// - /// Though the specification supports multiple filter objects (i.e. - /// combinations of type, IDs, and topics), only one set can be specified on - /// the command-line today, though that set can have multiple IDs/topics. - #[arg( - long = "id", - num_args = 1..=6, - help_heading = "FILTERS" - )] - contract_ids: Vec, - /// A set of (up to 4) topic filters to filter event topics on. A single - /// topic filter can contain 1-4 different segment filters, separated by - /// commas, with an asterisk (* character) indicating a wildcard segment. - /// - /// For example, this is one topic filter with two segments: - /// - /// --topic "AAAABQAAAAdDT1VOVEVSAA==,*" - /// - /// This is two topic filters with one and two segments each: - /// - /// --topic "AAAABQAAAAdDT1VOVEVSAA==" --topic '*,*' - /// - /// Note that all of these topic filters are combined with the contract IDs - /// into a single filter (i.e. combination of type, IDs, and topics). - #[arg( - long = "topic", - num_args = 1..=5, - help_heading = "FILTERS" - )] - topic_filters: Vec, - /// Specifies which type of contract events to display. - #[arg( - long = "type", - value_enum, - default_value = "all", - help_heading = "FILTERS" - )] - event_type: rpc::EventType, - #[command(flatten)] - locator: locator::Args, - #[command(flatten)] - network: network::Args, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("cursor is not valid")] - InvalidCursor, - #[error("filepath does not exist: {path}")] - InvalidFile { path: String }, - #[error("filepath ({path}) cannot be read: {error}")] - CannotReadFile { path: String, error: String }, - #[error("cannot parse topic filter {topic} into 1-4 segments")] - InvalidTopicFilter { topic: String }, - #[error("invalid segment ({segment}) in topic filter ({topic}): {error}")] - InvalidSegment { - topic: String, - segment: String, - error: xdr::Error, - }, - #[error("cannot parse contract ID {contract_id}: {error}")] - InvalidContractId { - contract_id: String, - error: stellar_strkey::DecodeError, - }, - #[error("invalid JSON string: {error} ({debug})")] - InvalidJson { - debug: String, - error: serde_json::Error, - }, - #[error("invalid timestamp in event: {ts}")] - InvalidTimestamp { ts: String }, - #[error("missing start_ledger and cursor")] - MissingStartLedgerAndCursor, - #[error("missing target")] - MissingTarget, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Generic(#[from] Box), - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Network(#[from] network::Error), - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum OutputFormat { - /// Colorful, human-oriented console output - Pretty, - /// Human-oriented console output without colors - Plain, - /// JSONified console output - Json, -} - -impl Cmd { - pub async fn run(&mut self) -> Result<(), Error> { - // Validate that topics are made up of segments. - for topic in &self.topic_filters { - for (i, segment) in topic.split(',').enumerate() { - if i > 4 { - return Err(Error::InvalidTopicFilter { - topic: topic.to_string(), - }); - } - - if segment != "*" { - if let Err(e) = xdr::ScVal::from_xdr_base64(segment, Limits::none()) { - return Err(Error::InvalidSegment { - topic: topic.to_string(), - segment: segment.to_string(), - error: e, - }); - } - } - } - } - - // Validate contract_ids - for id in &mut self.contract_ids { - utils::contract_id_from_str(id).map_err(|e| Error::InvalidContractId { - contract_id: id.clone(), - error: e, - })?; - } - - let response = self.run_against_rpc_server().await?; - - for event in &response.events { - match self.output { - // Should we pretty-print the JSON like we're doing here or just - // dump an event in raw JSON on each line? The latter is easier - // to consume programmatically. - OutputFormat::Json => { - println!( - "{}", - serde_json::to_string_pretty(&event).map_err(|e| { - Error::InvalidJson { - debug: format!("{event:#?}"), - error: e, - } - })?, - ); - } - OutputFormat::Plain => println!("{event}"), - OutputFormat::Pretty => event.pretty_print()?, - } - } - println!("Latest Ledger: {}", response.latest_ledger); - - Ok(()) - } - - async fn run_against_rpc_server(&self) -> Result { - let start = self.start()?; - let network = self.network.get(&self.locator)?; - - let client = rpc::Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - client - .get_events( - start, - Some(self.event_type), - &self.contract_ids, - &self.topic_filters, - Some(self.count), - ) - .await - .map_err(Error::Rpc) - } - - fn start(&self) -> Result { - let start = match (self.start_ledger, self.cursor.clone()) { - (Some(start), _) => rpc::EventStart::Ledger(start), - (_, Some(c)) => rpc::EventStart::Cursor(c), - // should never happen because of required_unless_present flags - _ => return Err(Error::MissingStartLedgerAndCursor), - }; - Ok(start) - } -} diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs deleted file mode 100644 index c606bd1b..00000000 --- a/cmd/soroban-cli/src/commands/global.rs +++ /dev/null @@ -1,61 +0,0 @@ -use clap::arg; -use std::path::PathBuf; - -use super::config; - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -#[allow(clippy::struct_excessive_bools)] -pub struct Args { - #[clap(flatten)] - pub locator: config::locator::Args, - - /// Filter logs output. To turn on "soroban_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG`. - #[arg(long, short = 'f')] - pub filter_logs: Vec, - - /// Do not write logs to stderr including `INFO` - #[arg(long, short = 'q')] - pub quiet: bool, - - /// Log DEBUG events - #[arg(long, short = 'v')] - pub verbose: bool, - - /// Log DEBUG and TRACE events - #[arg(long, visible_alias = "vv")] - pub very_verbose: bool, - - /// List installed plugins. E.g. `soroban-hello` - #[arg(long)] - pub list: bool, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, - - #[error("committing file {filepath}: {error}")] - CannotCommitLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, -} - -impl Args { - pub fn log_level(&self) -> Option { - if self.quiet { - None - } else if self.very_verbose { - Some(tracing::Level::TRACE) - } else if self.verbose { - Some(tracing::Level::DEBUG) - } else { - Some(tracing::Level::INFO) - } - } -} diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs deleted file mode 100644 index 2868c737..00000000 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ /dev/null @@ -1,33 +0,0 @@ -use clap::command; - -use super::super::config::{locator, secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity - pub name: String, - - #[command(flatten)] - pub secrets: secret::Args, - - #[command(flatten)] - pub config_locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self - .config_locator - .write_identity(&self.name, &self.secrets.read_secret()?)?) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs deleted file mode 100644 index d13381b4..00000000 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::commands::config::secret; - -use super::super::config::locator; -use clap::arg; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity to lookup, default test identity used if not provided - pub name: String, - - /// If identity is a seed phrase use this hd path, default is 0 - #[arg(long)] - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - println!("{}", self.public_key()?); - Ok(()) - } - - pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .key_pair(self.hd_path)?) - } - - pub fn public_key(&self) -> Result { - if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) { - Ok(key) - } else { - Ok(stellar_strkey::ed25519::PublicKey::from_payload( - self.private_key()?.verifying_key().as_bytes(), - )?) - } - } -} diff --git a/cmd/soroban-cli/src/commands/keys/fund.rs b/cmd/soroban-cli/src/commands/keys/fund.rs deleted file mode 100644 index b6c088f1..00000000 --- a/cmd/soroban-cli/src/commands/keys/fund.rs +++ /dev/null @@ -1,34 +0,0 @@ -use clap::command; - -use crate::commands::network; - -use super::address; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - Network(#[from] network::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub network: network::Args, - /// Address to fund - #[command(flatten)] - pub address: address::Cmd, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let addr = self.address.public_key()?; - self.network - .get(&self.address.locator)? - .fund_address(&addr) - .await?; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs deleted file mode 100644 index 07782b21..00000000 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ /dev/null @@ -1,75 +0,0 @@ -use clap::{arg, command}; - -use crate::commands::network; - -use super::super::config::{ - locator, - secret::{self, Secret}, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - #[error(transparent)] - Secret(#[from] secret::Error), - #[error(transparent)] - Network(#[from] network::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity - pub name: String, - /// Do not fund address - #[arg(long)] - pub no_fund: bool, - /// Optional seed to use when generating seed phrase. - /// Random otherwise. - #[arg(long, conflicts_with = "default_seed")] - pub seed: Option, - - /// Output the generated identity as a secret key - #[arg(long, short = 's')] - pub as_secret: bool, - - #[command(flatten)] - pub config_locator: locator::Args, - - /// When generating a secret key, which hd_path should be used from the original seed_phrase. - #[arg(long)] - pub hd_path: Option, - - /// Generate the default seed phrase. Useful for testing. - /// Equivalent to --seed 0000000000000000 - #[arg(long, short = 'd', conflicts_with = "seed")] - pub default_seed: bool, - - #[command(flatten)] - pub network: network::Args, -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - let seed_phrase = if self.default_seed { - Secret::test_seed_phrase() - } else { - Secret::from_seed(self.seed.as_deref()) - }?; - let secret = if self.as_secret { - seed_phrase.private_key(self.hd_path)?.into() - } else { - seed_phrase - }; - self.config_locator.write_identity(&self.name, &secret)?; - if !self.no_fund { - let addr = secret.public_key(self.hd_path)?; - let network = self.network.get(&self.config_locator)?; - network.fund_address(&addr).await.unwrap_or_else(|_| { - tracing::warn!("Failed to fund address: {addr} on at {}", network.rpc_url); - }); - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/ls.rs b/cmd/soroban-cli/src/commands/keys/ls.rs deleted file mode 100644 index bc46ffcd..00000000 --- a/cmd/soroban-cli/src/commands/keys/ls.rs +++ /dev/null @@ -1,45 +0,0 @@ -use clap::command; - -use super::super::config::locator; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config_locator: locator::Args, - - #[arg(long, short = 'l')] - pub long: bool, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); - println!("{res}"); - Ok(()) - } - - pub fn ls(&self) -> Result, Error> { - let mut list = self.config_locator.list_identities()?; - let test = "test".to_string(); - if !list.contains(&test) { - list.push(test); - } - Ok(list) - } - - pub fn ls_l(&self) -> Result, Error> { - Ok(self - .config_locator - .list_identities_long()? - .into_iter() - .map(|(name, location)| format!("{location}\nName: {name}\n")) - .collect::>()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs deleted file mode 100644 index 42814092..00000000 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use clap::Parser; - -pub mod add; -pub mod address; -pub mod fund; -pub mod generate; -pub mod ls; -pub mod rm; -pub mod show; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Add a new identity (keypair, ledger, macOS keychain) - Add(add::Cmd), - /// Given an identity return its address (public key) - Address(address::Cmd), - /// Fund an identity on a test network - Fund(fund::Cmd), - /// Generate a new identity with a seed phrase, currently 12 words - Generate(generate::Cmd), - /// List identities - Ls(ls::Cmd), - /// Remove an identity - Rm(rm::Cmd), - /// Given an identity return its private key - Show(show::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Add(#[from] add::Error), - - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - Fund(#[from] fund::Error), - - #[error(transparent)] - Generate(#[from] generate::Error), - #[error(transparent)] - Rm(#[from] rm::Error), - #[error(transparent)] - Ls(#[from] ls::Error), - - #[error(transparent)] - Show(#[from] show::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match self { - Cmd::Add(cmd) => cmd.run()?, - Cmd::Address(cmd) => cmd.run()?, - Cmd::Fund(cmd) => cmd.run().await?, - Cmd::Generate(cmd) => cmd.run().await?, - Cmd::Ls(cmd) => cmd.run()?, - Cmd::Rm(cmd) => cmd.run()?, - Cmd::Show(cmd) => cmd.run()?, - }; - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs deleted file mode 100644 index df48108d..00000000 --- a/cmd/soroban-cli/src/commands/keys/rm.rs +++ /dev/null @@ -1,25 +0,0 @@ -use clap::command; - -use super::super::config::locator; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Identity to remove - pub name: String, - - #[command(flatten)] - pub config: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self.config.remove_identity(&self.name)?) - } -} diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs deleted file mode 100644 index b99478cb..00000000 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ /dev/null @@ -1,43 +0,0 @@ -use clap::arg; - -use super::super::config::{locator, secret}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), - - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of identity to lookup, default is test identity - pub name: String, - - /// If identity is a seed phrase use this hd path, default is 0 - #[arg(long)] - pub hd_path: Option, - - #[command(flatten)] - pub locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - println!("{}", self.private_key()?.to_string()); - Ok(()) - } - - pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .private_key(self.hd_path)?) - } -} diff --git a/cmd/soroban-cli/src/commands/lab/mod.rs b/cmd/soroban-cli/src/commands/lab/mod.rs deleted file mode 100644 index f405efe6..00000000 --- a/cmd/soroban-cli/src/commands/lab/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clap::Subcommand; -use stellar_xdr::cli as xdr; - -pub mod token; - -#[derive(Debug, Subcommand)] -pub enum Cmd { - /// Wrap, create, and manage token contracts - Token(token::Root), - - /// Decode xdr - Xdr(xdr::Root), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Token(#[from] token::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), -} - -impl Cmd { - pub async fn run(&self) -> Result<(), Error> { - match &self { - Cmd::Token(token) => token.run().await?, - Cmd::Xdr(xdr) => xdr.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/lab/token/mod.rs b/cmd/soroban-cli/src/commands/lab/token/mod.rs deleted file mode 100644 index bd7eacf3..00000000 --- a/cmd/soroban-cli/src/commands/lab/token/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fmt::Debug; - -use crate::commands::contract::{deploy, id}; -use clap::{Parser, Subcommand}; - -#[derive(Parser, Debug)] -pub struct Root { - #[clap(subcommand)] - cmd: Cmd, -} - -#[derive(Subcommand, Debug)] -enum Cmd { - /// Deploy a token contract to wrap an existing Stellar classic asset for smart contract usage - /// Deprecated, use `soroban contract deploy asset` instead - Wrap(deploy::asset::Cmd), - /// Compute the expected contract id for the given asset - /// Deprecated, use `soroban contract id asset` instead - Id(id::asset::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Wrap(#[from] deploy::asset::Error), - #[error(transparent)] - Id(#[from] id::asset::Error), -} - -impl Root { - pub async fn run(&self) -> Result<(), Error> { - match &self.cmd { - Cmd::Wrap(wrap) => wrap.run().await?, - Cmd::Id(id) => id.run()?, - } - Ok(()) - } -} diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs deleted file mode 100644 index 952869af..00000000 --- a/cmd/soroban-cli/src/commands/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::str::FromStr; - -use clap::{command, error::ErrorKind, CommandFactory, FromArgMatches, Parser}; - -pub mod completion; -pub mod config; -pub mod contract; -pub mod events; -pub mod global; -pub mod keys; -pub mod lab; -pub mod network; -pub mod plugin; -pub mod version; - -pub const HEADING_RPC: &str = "Options (RPC)"; -const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. - -Intro: https://soroban.stellar.org -CLI Reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; - -// long_about is shown when someone uses `--help`; short help when using `-h` -const LONG_ABOUT: &str = " - -The easiest way to get started is to generate a new identity: - - soroban config identity generate alice - -You can use identities with the `--source` flag in other commands later. - -Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: - - soroban contract --help - -A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: - - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ - --help - -Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - - soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ - hello --to world - -Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; - -#[derive(Parser, Debug)] -#[command( - name = "soroban", - about = ABOUT, - version = version::long(), - long_about = ABOUT.to_string() + LONG_ABOUT, - disable_help_subcommand = true, -)] -pub struct Root { - #[clap(flatten)] - pub global_args: global::Args, - - #[command(subcommand)] - pub cmd: Cmd, -} - -impl Root { - pub fn new() -> Result { - Self::try_parse().map_err(|e| { - if std::env::args().any(|s| s == "--list") { - let plugins = plugin::list().unwrap_or_default(); - if plugins.is_empty() { - println!("No Plugins installed. E.g. soroban-hello"); - } else { - println!("Installed Plugins:\n {}", plugins.join("\n ")); - } - std::process::exit(0); - } - match e.kind() { - ErrorKind::InvalidSubcommand => match plugin::run() { - Ok(()) => Error::Clap(e), - Err(e) => Error::Plugin(e), - }, - _ => Error::Clap(e), - } - }) - } - - pub fn from_arg_matches(itr: I) -> Result - where - I: IntoIterator, - T: Into + Clone, - { - Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr)) - } - pub async fn run(&mut self) -> Result<(), Error> { - match &mut self.cmd { - Cmd::Completion(completion) => completion.run(), - Cmd::Contract(contract) => contract.run(&self.global_args).await?, - Cmd::Events(events) => events.run().await?, - Cmd::Lab(lab) => lab.run().await?, - Cmd::Network(network) => network.run()?, - Cmd::Version(version) => version.run(), - Cmd::Keys(id) => id.run().await?, - Cmd::Config(c) => c.run().await?, - }; - Ok(()) - } -} - -impl FromStr for Root { - type Err = clap::Error; - - fn from_str(s: &str) -> Result { - Self::from_arg_matches(s.split_whitespace()) - } -} - -#[derive(Parser, Debug)] -pub enum Cmd { - /// Print shell completion code for the specified shell. - #[command(long_about = completion::LONG_ABOUT)] - Completion(completion::Cmd), - /// Deprecated, use `soroban keys` and `soroban network` instead - #[command(subcommand)] - Config(config::Cmd), - /// Tools for smart contract developers - #[command(subcommand)] - Contract(contract::Cmd), - /// Watch the network for contract events - Events(events::Cmd), - /// Create and manage identities including keys and addresses - #[command(subcommand)] - Keys(keys::Cmd), - /// Experiment with early features and expert tools - #[command(subcommand)] - Lab(lab::Cmd), - /// Start and configure networks - #[command(subcommand)] - Network(network::Cmd), - /// Print version information - Version(version::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - // TODO: stop using Debug for displaying errors - #[error(transparent)] - Contract(#[from] contract::Error), - #[error(transparent)] - Events(#[from] events::Error), - #[error(transparent)] - Keys(#[from] keys::Error), - #[error(transparent)] - Lab(#[from] lab::Error), - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - Clap(#[from] clap::error::Error), - #[error(transparent)] - Plugin(#[from] plugin::Error), - #[error(transparent)] - Network(#[from] network::Error), -} diff --git a/cmd/soroban-cli/src/commands/network/add.rs b/cmd/soroban-cli/src/commands/network/add.rs deleted file mode 100644 index b6a2ddd3..00000000 --- a/cmd/soroban-cli/src/commands/network/add.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::super::config::{locator, secret}; -use clap::command; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Name of network - pub name: String, - - #[command(flatten)] - pub network: super::Network, - - #[command(flatten)] - pub config_locator: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self - .config_locator - .write_network(&self.name, &self.network)?) - } -} diff --git a/cmd/soroban-cli/src/commands/network/ls.rs b/cmd/soroban-cli/src/commands/network/ls.rs deleted file mode 100644 index cc542b3e..00000000 --- a/cmd/soroban-cli/src/commands/network/ls.rs +++ /dev/null @@ -1,44 +0,0 @@ -use clap::command; - -use super::locator; -use crate::commands::config::locator::Location; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Config(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - #[command(flatten)] - pub config_locator: locator::Args, - /// Get more info about the networks - #[arg(long, short = 'l')] - pub long: bool, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); - println!("{res}"); - Ok(()) - } - - pub fn ls(&self) -> Result, Error> { - Ok(self.config_locator.list_networks()?) - } - - pub fn ls_l(&self) -> Result, Error> { - Ok(self - .config_locator - .list_networks_long()? - .iter() - .filter_map(|(name, network, location)| { - (!self.config_locator.global || matches!(location, Location::Global(_))) - .then(|| Some(format!("{location}\nName: {name}\n{network:#?}\n")))? - }) - .collect()) - } -} diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs deleted file mode 100644 index 22cba190..00000000 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::str::FromStr; - -use clap::{arg, Parser}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use stellar_strkey::ed25519::PublicKey; - -use crate::{ - commands::HEADING_RPC, - rpc::{self, Client}, -}; - -use super::config::locator; - -pub mod add; -pub mod ls; -pub mod rm; - -#[derive(Debug, Parser)] -pub enum Cmd { - /// Add a new network - Add(add::Cmd), - /// Remove a network - Rm(rm::Cmd), - /// List networks - Ls(ls::Cmd), -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Add(#[from] add::Error), - - #[error(transparent)] - Rm(#[from] rm::Error), - - #[error(transparent)] - Ls(#[from] ls::Error), - - #[error(transparent)] - Config(#[from] locator::Error), - - #[error("network arg or rpc url and network passphrase are required if using the network")] - Network, - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - Hyper(#[from] hyper::Error), - #[error("Failed to parse JSON from {0}, {1}")] - FailedToParseJSON(String, serde_json::Error), - #[error("Invalid URL {0}")] - InvalidUrl(String), - #[error("Inproper response {0}")] - InproperResponse(String), - #[error("Currently not supported on windows. Please visit:\n{0}")] - WindowsNotSupported(String), -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - match self { - Cmd::Add(cmd) => cmd.run()?, - Cmd::Rm(new) => new.run()?, - Cmd::Ls(cmd) => cmd.run()?, - }; - Ok(()) - } -} - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - /// RPC server endpoint - #[arg( - long = "rpc-url", - requires = "network_passphrase", - required_unless_present = "network", - env = "SOROBAN_RPC_URL", - help_heading = HEADING_RPC, - )] - pub rpc_url: Option, - /// Network passphrase to sign the transaction sent to the rpc server - #[arg( - long = "network-passphrase", - requires = "rpc_url", - required_unless_present = "network", - env = "SOROBAN_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, - )] - pub network_passphrase: Option, - /// Name of network to use from config - #[arg( - long, - required_unless_present = "rpc_url", - env = "SOROBAN_NETWORK", - help_heading = HEADING_RPC, - )] - pub network: Option, -} - -impl Args { - pub fn get(&self, locator: &locator::Args) -> Result { - if let Some(name) = self.network.as_deref() { - if let Ok(network) = locator.read_network(name) { - return Ok(network); - } - } - if let (Some(rpc_url), Some(network_passphrase)) = - (self.rpc_url.clone(), self.network_passphrase.clone()) - { - Ok(Network { - rpc_url, - network_passphrase, - }) - } else { - Err(Error::Network) - } - } -} - -#[derive(Debug, clap::Args, Serialize, Deserialize, Clone)] -#[group(skip)] -pub struct Network { - /// RPC server endpoint - #[arg( - long = "rpc-url", - env = "SOROBAN_RPC_URL", - help_heading = HEADING_RPC, - )] - pub rpc_url: String, - /// Network passphrase to sign the transaction sent to the rpc server - #[arg( - long, - env = "SOROBAN_NETWORK_PASSPHRASE", - help_heading = HEADING_RPC, - )] - pub network_passphrase: String, -} - -impl Network { - pub async fn helper_url(&self, addr: &str) -> Result { - tracing::debug!("address {addr:?}"); - let client = Client::new(&self.rpc_url)?; - let helper_url_root = client.friendbot_url().await?; - let uri = http::Uri::from_str(&helper_url_root) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string()))?; - http::Uri::from_str(&format!("{uri:?}?addr={addr}")) - .map_err(|_| Error::InvalidUrl(helper_url_root.to_string())) - } - - #[allow(clippy::similar_names)] - pub async fn fund_address(&self, addr: &PublicKey) -> Result<(), Error> { - let uri = self.helper_url(&addr.to_string()).await?; - tracing::debug!("URL {uri:?}"); - let response = match uri.scheme_str() { - Some("http") => hyper::Client::new().get(uri.clone()).await?, - Some("https") => { - #[cfg(target_os = "windows")] - { - return Err(Error::WindowsNotSupported(uri.to_string())); - } - #[cfg(not(target_os = "windows"))] - { - let https = hyper_tls::HttpsConnector::new(); - hyper::Client::builder() - .build::<_, hyper::Body>(https) - .get(uri.clone()) - .await? - } - } - _ => { - return Err(Error::InvalidUrl(uri.to_string())); - } - }; - let body = hyper::body::to_bytes(response.into_body()).await?; - let res = serde_json::from_slice::(&body) - .map_err(|e| Error::FailedToParseJSON(uri.to_string(), e))?; - tracing::debug!("{res:#?}"); - if let Some(detail) = res.get("detail").and_then(Value::as_str) { - if detail.contains("createAccountAlreadyExist") { - tracing::warn!("Account already exists"); - } - } else if res.get("successful").is_none() { - return Err(Error::InproperResponse(res.to_string())); - } - Ok(()) - } -} - -impl Network { - pub fn futurenet() -> Self { - Network { - rpc_url: "https://rpc-futurenet.stellar.org:443".to_owned(), - network_passphrase: "Test SDF Future Network ; October 2022".to_owned(), - } - } -} diff --git a/cmd/soroban-cli/src/commands/network/rm.rs b/cmd/soroban-cli/src/commands/network/rm.rs deleted file mode 100644 index 7051dc6b..00000000 --- a/cmd/soroban-cli/src/commands/network/rm.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::locator; -use clap::command; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Locator(#[from] locator::Error), -} - -#[derive(Debug, clap::Parser, Clone)] -#[group(skip)] -pub struct Cmd { - /// Network to remove - pub name: String, - - #[command(flatten)] - pub config: locator::Args, -} - -impl Cmd { - pub fn run(&self) -> Result<(), Error> { - Ok(self.config.remove_network(&self.name)?) - } -} diff --git a/cmd/soroban-cli/src/commands/plugin.rs b/cmd/soroban-cli/src/commands/plugin.rs deleted file mode 100644 index 27c191f0..00000000 --- a/cmd/soroban-cli/src/commands/plugin.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::process::Command; - -use clap::CommandFactory; -use which::which; - -use crate::{utils, Root}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Plugin not provided. Should be `soroban plugin` for a binary `soroban-plugin`")] - MissingSubcommand, - #[error(transparent)] - IO(#[from] std::io::Error), - #[error( - r#"error: no such command: `{0}` - - {1}View all installed plugins with `soroban --list`"# - )] - ExecutableNotFound(String, String), - #[error(transparent)] - Which(#[from] which::Error), - #[error(transparent)] - Regex(#[from] regex::Error), -} - -const SUBCOMMAND_TOLERANCE: f64 = 0.75; -const PLUGIN_TOLERANCE: f64 = 0.75; -const MIN_LENGTH: usize = 4; - -/// Tries to run a plugin, if the plugin's name is similar enough to any of the current subcommands return Ok. -/// Otherwise only errors can be returned because this process will exit with the plugin. -pub fn run() -> Result<(), Error> { - let (name, args) = { - let mut args = std::env::args().skip(1); - let name = args.next().ok_or(Error::MissingSubcommand)?; - (name, args) - }; - - if Root::command().get_subcommands().any(|c| { - let sc_name = c.get_name(); - sc_name.starts_with(&name) - || (name.len() >= MIN_LENGTH && strsim::jaro(sc_name, &name) >= SUBCOMMAND_TOLERANCE) - }) { - return Ok(()); - } - - let bin = which(format!("soroban-{name}")).map_err(|_| { - let suggestion = if let Ok(bins) = list() { - let suggested_name = bins - .iter() - .map(|b| (b, strsim::jaro_winkler(&name, b))) - .filter(|(_, i)| *i > PLUGIN_TOLERANCE) - .min_by(|a, b| a.1.total_cmp(&b.1)) - .map(|(a, _)| a.to_string()) - .unwrap_or_default(); - if suggested_name.is_empty() { - suggested_name - } else { - format!( - r#"Did you mean `{suggested_name}`? - "# - ) - } - } else { - String::new() - }; - Error::ExecutableNotFound(name, suggestion) - })?; - std::process::exit( - Command::new(bin) - .args(args) - .spawn()? - .wait()? - .code() - .unwrap(), - ); -} - -const MAX_HEX_LENGTH: usize = 10; - -pub fn list() -> Result, Error> { - let re_str = if cfg!(target_os = "windows") { - r"^soroban-.*.exe$" - } else { - r"^soroban-.*" - }; - let re = regex::Regex::new(re_str)?; - Ok(which::which_re(re)? - .filter_map(|b| { - let s = b.file_name()?.to_str()?; - Some(s.strip_suffix(".exe").unwrap_or(s).to_string()) - }) - .filter(|s| !(utils::is_hex_string(s) && s.len() > MAX_HEX_LENGTH)) - .map(|s| s.replace("soroban-", "")) - .collect()) -} diff --git a/cmd/soroban-cli/src/commands/version.rs b/cmd/soroban-cli/src/commands/version.rs deleted file mode 100644 index d9fc091b..00000000 --- a/cmd/soroban-cli/src/commands/version.rs +++ /dev/null @@ -1,32 +0,0 @@ -use clap::Parser; -use soroban_env_host::meta; -use std::fmt::Debug; - -const GIT_REVISION: &str = env!("GIT_REVISION"); - -#[derive(Parser, Debug, Clone)] -#[group(skip)] -pub struct Cmd; - -impl Cmd { - #[allow(clippy::unused_self)] - pub fn run(&self) { - println!("soroban {}", long()); - } -} - -pub fn long() -> String { - let env = soroban_env_host::VERSION; - let xdr = soroban_env_host::VERSION.xdr; - [ - format!("{} ({GIT_REVISION})", env!("CARGO_PKG_VERSION")), - format!("soroban-env {} ({})", env.pkg, env.rev), - format!("soroban-env interface version {}", meta::INTERFACE_VERSION), - format!( - "stellar-xdr {} ({}) -xdr curr ({})", - xdr.pkg, xdr.rev, xdr.xdr_curr, - ), - ] - .join("\n") -} diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs deleted file mode 100644 index ee8b9614..00000000 --- a/cmd/soroban-cli/src/fee.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::commands::HEADING_RPC; -use clap::arg; - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm - #[arg(long, default_value = "100", env = "SOROBAN_FEE", help_heading = HEADING_RPC)] - pub fee: u32, -} - -impl Default for Args { - fn default() -> Self { - Self { fee: 100 } - } -} diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs deleted file mode 100644 index e9901abd..00000000 --- a/cmd/soroban-cli/src/key.rs +++ /dev/null @@ -1,110 +0,0 @@ -use clap::arg; -use soroban_env_host::xdr::{ - self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, - ScVal, -}; -use std::path::PathBuf; - -use crate::{ - commands::contract::Durability, - utils::{self}, - wasm, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Spec(#[from] soroban_spec_tools::Error), - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error("cannot parse contract ID {0}: {1}")] - CannotParseContractId(String, stellar_strkey::DecodeError), - #[error(transparent)] - Wasm(#[from] wasm::Error), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Contract ID to which owns the data entries. - /// If no keys provided the Contract's instance will be extended - #[arg( - long = "id", - required_unless_present = "wasm", - required_unless_present = "wasm_hash" - )] - pub contract_id: Option, - /// Storage key (symbols only) - #[arg(long = "key", conflicts_with = "key_xdr")] - pub key: Option>, - /// Storage key (base64-encoded XDR) - #[arg(long = "key-xdr", conflicts_with = "key")] - pub key_xdr: Option>, - /// Path to Wasm file of contract code to extend - #[arg( - long, - conflicts_with = "contract_id", - conflicts_with = "key", - conflicts_with = "key_xdr", - conflicts_with = "wasm_hash" - )] - pub wasm: Option, - /// Path to Wasm file of contract code to extend - #[arg( - long, - conflicts_with = "contract_id", - conflicts_with = "key", - conflicts_with = "key_xdr", - conflicts_with = "wasm" - )] - pub wasm_hash: Option, - /// Storage entry durability - #[arg(long, value_enum, required = true)] - pub durability: Durability, -} - -impl Args { - pub fn parse_keys(&self) -> Result, Error> { - let keys = if let Some(keys) = &self.key { - keys.iter() - .map(|key| { - Ok(soroban_spec_tools::from_string_primitive( - key, - &xdr::ScSpecTypeDef::Symbol, - )?) - }) - .collect::, Error>>()? - } else if let Some(keys) = &self.key_xdr { - keys.iter() - .map(|s| Ok(ScVal::from_xdr_base64(s, Limits::none())?)) - .collect::, Error>>()? - } else if let Some(wasm) = &self.wasm { - return Ok(vec![crate::wasm::Args { wasm: wasm.clone() }.try_into()?]); - } else if let Some(wasm_hash) = &self.wasm_hash { - return Ok(vec![LedgerKey::ContractCode(LedgerKeyContractCode { - hash: xdr::Hash( - utils::contract_id_from_str(wasm_hash) - .map_err(|e| Error::CannotParseContractId(wasm_hash.clone(), e))?, - ), - })]); - } else { - vec![ScVal::LedgerKeyContractInstance] - }; - let contract_id = contract_id(self.contract_id.as_ref().unwrap())?; - - Ok(keys - .into_iter() - .map(|key| { - LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(xdr::Hash(contract_id)), - durability: (&self.durability).into(), - key, - }) - }) - .collect()) - } -} - -fn contract_id(s: &str) -> Result<[u8; 32], Error> { - utils::contract_id_from_str(s).map_err(|e| Error::CannotParseContractId(s.to_string(), e)) -} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs deleted file mode 100644 index 3aad487c..00000000 --- a/cmd/soroban-cli/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![allow( - clippy::missing_errors_doc, - clippy::must_use_candidate, - clippy::missing_panics_doc -)] -pub mod commands; -pub mod fee; -pub mod key; -pub mod log; -pub mod rpc; -pub mod toid; -pub mod utils; -pub mod wasm; - -use std::path::Path; - -pub use commands::Root; - -pub fn parse_cmd(s: &str) -> Result -where - T: clap::CommandFactory + clap::FromArgMatches, -{ - let input = shlex::split(s).ok_or_else(|| { - clap::Error::raw( - clap::error::ErrorKind::InvalidValue, - format!("Invalid input for command:\n{s}"), - ) - })?; - T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(input)) -} - -pub trait CommandParser { - fn parse(s: &str) -> Result; - - fn parse_arg_vec(s: &[&str]) -> Result; -} - -impl CommandParser for T -where - T: clap::CommandFactory + clap::FromArgMatches, -{ - fn parse(s: &str) -> Result { - parse_cmd(s) - } - - fn parse_arg_vec(args: &[&str]) -> Result { - T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(args)) - } -} - -pub trait Pwd { - fn set_pwd(&mut self, pwd: &Path); -} diff --git a/cmd/soroban-cli/src/log.rs b/cmd/soroban-cli/src/log.rs deleted file mode 100644 index 16121982..00000000 --- a/cmd/soroban-cli/src/log.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod auth; -pub mod budget; -pub mod cost; -pub mod diagnostic_event; -pub mod footprint; -pub mod host_event; - -pub use auth::*; -pub use budget::*; -pub use cost::*; -pub use diagnostic_event::*; -pub use footprint::*; -pub use host_event::*; diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs deleted file mode 100644 index c37e7ed3..00000000 --- a/cmd/soroban-cli/src/log/auth.rs +++ /dev/null @@ -1,7 +0,0 @@ -use soroban_env_host::xdr::{SorobanAuthorizationEntry, VecM}; - -pub fn auth(auth: &[VecM]) { - if !auth.is_empty() { - tracing::debug!("{auth:#?}"); - } -} diff --git a/cmd/soroban-cli/src/log/budget.rs b/cmd/soroban-cli/src/log/budget.rs deleted file mode 100644 index 59ff4aad..00000000 --- a/cmd/soroban-cli/src/log/budget.rs +++ /dev/null @@ -1,5 +0,0 @@ -use soroban_env_host::budget::Budget; - -pub fn budget(budget: &Budget) { - tracing::debug!("{budget:#?}"); -} diff --git a/cmd/soroban-cli/src/log/cost.rs b/cmd/soroban-cli/src/log/cost.rs deleted file mode 100644 index 3e049a6c..00000000 --- a/cmd/soroban-cli/src/log/cost.rs +++ /dev/null @@ -1,27 +0,0 @@ -use soroban_env_host::xdr::SorobanResources; -use std::fmt::{Debug, Display}; - -struct Cost<'a>(&'a SorobanResources); - -impl Debug for Cost<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Should we output the footprint here? - writeln!(f, "==================== Cost ====================")?; - writeln!(f, "CPU used: {}", self.0.instructions,)?; - writeln!(f, "Bytes read: {}", self.0.read_bytes,)?; - writeln!(f, "Bytes written: {}", self.0.write_bytes,)?; - writeln!(f, "==============================================")?; - Ok(()) - } -} - -impl Display for Cost<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -pub fn cost(resources: &SorobanResources) { - let cost = Cost(resources); - tracing::debug!(?cost); -} diff --git a/cmd/soroban-cli/src/log/diagnostic_event.rs b/cmd/soroban-cli/src/log/diagnostic_event.rs deleted file mode 100644 index 68af67a4..00000000 --- a/cmd/soroban-cli/src/log/diagnostic_event.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub fn diagnostic_events(events: &[impl std::fmt::Debug], level: tracing::Level) { - for (i, event) in events.iter().enumerate() { - if level == tracing::Level::TRACE { - tracing::trace!("{i}: {event:#?}"); - } else if level == tracing::Level::INFO { - tracing::info!("{i}: {event:#?}"); - } else if level == tracing::Level::ERROR { - tracing::error!("{i}: {event:#?}"); - } - } -} diff --git a/cmd/soroban-cli/src/log/footprint.rs b/cmd/soroban-cli/src/log/footprint.rs deleted file mode 100644 index bfbc9f7a..00000000 --- a/cmd/soroban-cli/src/log/footprint.rs +++ /dev/null @@ -1,5 +0,0 @@ -use soroban_env_host::xdr::LedgerFootprint; - -pub fn footprint(footprint: &LedgerFootprint) { - tracing::debug!("{footprint:#?}"); -} diff --git a/cmd/soroban-cli/src/log/host_event.rs b/cmd/soroban-cli/src/log/host_event.rs deleted file mode 100644 index 4238a74c..00000000 --- a/cmd/soroban-cli/src/log/host_event.rs +++ /dev/null @@ -1,7 +0,0 @@ -use soroban_env_host::events::HostEvent; - -pub fn host_events(events: &[HostEvent]) { - for (i, event) in events.iter().enumerate() { - tracing::info!("{i}: {event:#?}"); - } -} diff --git a/cmd/soroban-cli/src/rpc/fixtures/event_response.json b/cmd/soroban-cli/src/rpc/fixtures/event_response.json deleted file mode 100644 index 6f520fdf..00000000 --- a/cmd/soroban-cli/src/rpc/fixtures/event_response.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "events": [{ - "eventType": "system", - "ledger": "43601283", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }, { - "eventType": "contract", - "ledger": "43601284", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }, { - "eventType": "system", - "ledger": "43601285", - "ledgerClosedAt": "2022-11-16T16:10:41Z", - "contractId": "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2", - "id": "0164090849041387521-0000000003", - "pagingToken": "164090849041387521-3", - "topic": [ - "AAAABQAAAAh0cmFuc2Zlcg==", - "AAAAAQB6Mcc=" - ], - "value": "AAAABQAAAApHaWJNb255UGxzAAA=" - }] -} \ No newline at end of file diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs deleted file mode 100644 index 53542629..00000000 --- a/cmd/soroban-cli/src/rpc/mod.rs +++ /dev/null @@ -1,1141 +0,0 @@ -use http::{uri::Authority, Uri}; -use itertools::Itertools; -use jsonrpsee_core::params::ObjectParams; -use jsonrpsee_core::{self, client::ClientT, rpc_params}; -use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder}; -use serde_aux::prelude::{ - deserialize_default_from_null, deserialize_number_from_string, - deserialize_option_number_from_string, -}; -use soroban_env_host::xdr::{ - self, AccountEntry, AccountId, ContractDataEntry, DiagnosticEvent, Error as XdrError, - LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, Limited, PublicKey, ReadXdr, - SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData, Transaction, - TransactionEnvelope, TransactionMeta, TransactionMetaV3, TransactionResult, Uint256, VecM, - WriteXdr, -}; -use soroban_sdk::token; -use soroban_sdk::xdr::Limits; -use std::{ - fmt::Display, - str::FromStr, - time::{Duration, Instant}, -}; -use termcolor::{Color, ColorChoice, StandardStream, WriteColor}; -use termcolor_output::colored; -use tokio::time::sleep; - -use crate::utils::contract_spec; - -mod txn; - -const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); - -pub type LogEvents = fn( - footprint: &LedgerFootprint, - auth: &[VecM], - events: &[DiagnosticEvent], -) -> (); - -pub type LogResources = fn(resources: &SorobanResources) -> (); - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - InvalidAddress(#[from] stellar_strkey::DecodeError), - #[error("invalid response from server")] - InvalidResponse, - #[error("provided network passphrase {expected:?} does not match the server: {server:?}")] - InvalidNetworkPassphrase { expected: String, server: String }, - #[error("xdr processing error: {0}")] - Xdr(#[from] XdrError), - #[error("invalid rpc url: {0}")] - InvalidRpcUrl(http::uri::InvalidUri), - #[error("invalid rpc url: {0}")] - InvalidRpcUrlFromUriParts(http::uri::InvalidUriParts), - #[error("invalid friendbot url: {0}")] - InvalidUrl(String), - #[error("jsonrpc error: {0}")] - JsonRpc(#[from] jsonrpsee_core::Error), - #[error("json decoding error: {0}")] - Serde(#[from] serde_json::Error), - #[error("transaction failed: {0}")] - TransactionFailed(String), - #[error("transaction submission failed: {0}")] - TransactionSubmissionFailed(String), - #[error("expected transaction status: {0}")] - UnexpectedTransactionStatus(String), - #[error("transaction submission timeout")] - TransactionSubmissionTimeout, - #[error("transaction simulation failed: {0}")] - TransactionSimulationFailed(String), - #[error("{0} not found: {1}")] - NotFound(String, String), - #[error("Missing result in successful response")] - MissingResult, - #[error("Failed to read Error response from server")] - MissingError, - #[error("Missing signing key for account {address}")] - MissingSignerForAddress { address: String }, - #[error("cursor is not valid")] - InvalidCursor, - #[error("unexpected ({length}) simulate transaction result length")] - UnexpectedSimulateTransactionResultSize { length: usize }, - #[error("unexpected ({count}) number of operations")] - UnexpectedOperationCount { count: usize }, - #[error("Transaction contains unsupported operation type")] - UnsupportedOperationType, - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), - #[error(transparent)] - CouldNotParseContractSpec(#[from] contract_spec::Error), - #[error("unexpected contract code got token")] - UnexpectedToken(ContractDataEntry), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - SpecBase64(#[from] soroban_spec::read::ParseSpecBase64Error), - #[error("Fee was too large {0}")] - LargeFee(u64), - #[error("Cannot authorize raw transactions")] - CannotAuthorizeRawTransaction, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct SendTransactionResponse { - pub hash: String, - pub status: String, - #[serde( - rename = "errorResultXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub error_result_xdr: Option, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, - #[serde( - rename = "latestLedgerCloseTime", - deserialize_with = "deserialize_number_from_string" - )] - pub latest_ledger_close_time: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetTransactionResponseRaw { - pub status: String, - #[serde( - rename = "envelopeXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub envelope_xdr: Option, - #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)] - pub result_xdr: Option, - #[serde( - rename = "resultMetaXdr", - skip_serializing_if = "Option::is_none", - default - )] - pub result_meta_xdr: Option, - // TODO: add ledger info and application order -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetTransactionResponse { - pub status: String, - pub envelope: Option, - pub result: Option, - pub result_meta: Option, -} - -impl TryInto for GetTransactionResponseRaw { - type Error = xdr::Error; - - fn try_into(self) -> Result { - Ok(GetTransactionResponse { - status: self.status, - envelope: self - .envelope_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - result: self - .result_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - result_meta: self - .result_meta_xdr - .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) - .transpose()?, - }) - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct LedgerEntryResult { - pub key: String, - pub xdr: String, - #[serde(rename = "lastModifiedLedgerSeq")] - pub last_modified_ledger: u32, - #[serde( - rename = "liveUntilLedgerSeq", - skip_serializing_if = "Option::is_none", - deserialize_with = "deserialize_option_number_from_string", - default - )] - pub live_until_ledger_seq_ledger_seq: Option, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetLedgerEntriesResponse { - pub entries: Option>, - #[serde(rename = "latestLedger")] - pub latest_ledger: i64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetNetworkResponse { - #[serde( - rename = "friendbotUrl", - skip_serializing_if = "Option::is_none", - default - )] - pub friendbot_url: Option, - pub passphrase: String, - #[serde(rename = "protocolVersion")] - pub protocol_version: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetLatestLedgerResponse { - pub id: String, - #[serde(rename = "protocolVersion")] - pub protocol_version: u32, - pub sequence: u32, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct Cost { - #[serde( - rename = "cpuInsns", - deserialize_with = "deserialize_number_from_string" - )] - pub cpu_insns: u64, - #[serde( - rename = "memBytes", - deserialize_with = "deserialize_number_from_string" - )] - pub mem_bytes: u64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct SimulateHostFunctionResultRaw { - #[serde(deserialize_with = "deserialize_default_from_null")] - pub auth: Vec, - pub xdr: String, -} - -#[derive(Debug)] -pub struct SimulateHostFunctionResult { - pub auth: Vec, - pub xdr: xdr::ScVal, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct SimulateTransactionResponse { - #[serde( - rename = "minResourceFee", - deserialize_with = "deserialize_number_from_string", - default - )] - pub min_resource_fee: u64, - #[serde(default)] - pub cost: Cost, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub results: Vec, - #[serde(rename = "transactionData", default)] - pub transaction_data: String, - #[serde( - deserialize_with = "deserialize_default_from_null", - skip_serializing_if = "Vec::is_empty", - default - )] - pub events: Vec, - #[serde( - rename = "restorePreamble", - skip_serializing_if = "Option::is_none", - default - )] - pub restore_preamble: Option, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub error: Option, -} - -impl SimulateTransactionResponse { - pub fn results(&self) -> Result, Error> { - self.results - .iter() - .map(|r| { - Ok(SimulateHostFunctionResult { - auth: r - .auth - .iter() - .map(|a| { - Ok(SorobanAuthorizationEntry::from_xdr_base64( - a, - Limits::none(), - )?) - }) - .collect::>()?, - xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?, - }) - }) - .collect() - } - - pub fn events(&self) -> Result, Error> { - self.events - .iter() - .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?)) - .collect() - } - - pub fn transaction_data(&self) -> Result { - Ok(SorobanTransactionData::from_xdr_base64( - &self.transaction_data, - Limits::none(), - )?) - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] -pub struct RestorePreamble { - #[serde(rename = "transactionData")] - pub transaction_data: String, - #[serde( - rename = "minResourceFee", - deserialize_with = "deserialize_number_from_string" - )] - pub min_resource_fee: u64, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug)] -pub struct GetEventsResponse { - #[serde(deserialize_with = "deserialize_default_from_null")] - pub events: Vec, - #[serde(rename = "latestLedger")] - pub latest_ledger: u32, -} - -// Determines whether or not a particular filter matches a topic based on the -// same semantics as the RPC server: -// -// - for an exact segment match, the filter is a base64-encoded ScVal -// - for a wildcard, single-segment match, the string "*" matches exactly one -// segment -// -// The expectation is that a `filter` is a comma-separated list of segments that -// has previously been validated, and `topic` is the list of segments applicable -// for this event. -// -// [API -// Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx) -// [Code -// Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203) -pub fn does_topic_match(topic: &[String], filter: &[String]) -> bool { - filter.len() == topic.len() - && filter - .iter() - .enumerate() - .all(|(i, s)| *s == "*" || topic[i] == *s) -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct Event { - #[serde(rename = "type")] - pub event_type: String, - - pub ledger: u32, - #[serde(rename = "ledgerClosedAt")] - pub ledger_closed_at: String, - - pub id: String, - #[serde(rename = "pagingToken")] - pub paging_token: String, - - #[serde(rename = "contractId")] - pub contract_id: String, - pub topic: Vec, - pub value: String, -} - -impl Display for Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "Event {} [{}]:", - self.paging_token, - self.event_type.to_ascii_uppercase() - )?; - writeln!( - f, - " Ledger: {} (closed at {})", - self.ledger, self.ledger_closed_at - )?; - writeln!(f, " Contract: {}", self.contract_id)?; - writeln!(f, " Topics:")?; - for topic in &self.topic { - let scval = - xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?; - writeln!(f, " {scval:?}")?; - } - let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none()) - .map_err(|_| std::fmt::Error)?; - writeln!(f, " Value: {scval:?}") - } -} - -impl Event { - pub fn parse_cursor(&self) -> Result<(u64, i32), Error> { - parse_cursor(&self.id) - } - - pub fn pretty_print(&self) -> Result<(), Box> { - let mut stdout = StandardStream::stdout(ColorChoice::Auto); - if !stdout.supports_color() { - println!("{self}"); - return Ok(()); - } - - let color = match self.event_type.as_str() { - "system" => Color::Yellow, - _ => Color::Blue, - }; - colored!( - stdout, - "{}Event{} {}{}{} [{}{}{}{}]:\n", - bold!(true), - bold!(false), - fg!(Some(Color::Green)), - self.paging_token, - reset!(), - bold!(true), - fg!(Some(color)), - self.event_type.to_ascii_uppercase(), - reset!(), - )?; - - colored!( - stdout, - " Ledger: {}{}{} (closed at {}{}{})\n", - fg!(Some(Color::Green)), - self.ledger, - reset!(), - fg!(Some(Color::Green)), - self.ledger_closed_at, - reset!(), - )?; - - colored!( - stdout, - " Contract: {}{}{}\n", - fg!(Some(Color::Green)), - self.contract_id, - reset!(), - )?; - - colored!(stdout, " Topics:\n")?; - for topic in &self.topic { - let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?; - colored!( - stdout, - " {}{:?}{}\n", - fg!(Some(Color::Green)), - scval, - reset!(), - )?; - } - - let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?; - colored!( - stdout, - " Value: {}{:?}{}\n", - fg!(Some(Color::Green)), - scval, - reset!(), - )?; - - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] -pub enum EventType { - All, - Contract, - System, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum EventStart { - Ledger(u32), - Cursor(String), -} - -#[derive(Debug)] -pub struct FullLedgerEntry { - pub key: LedgerKey, - pub val: LedgerEntryData, - pub last_modified_ledger: u32, - pub live_until_ledger_seq: u32, -} - -#[derive(Debug)] -pub struct FullLedgerEntries { - pub entries: Vec, - pub latest_ledger: i64, -} - -pub struct Client { - base_url: String, -} - -impl Client { - pub fn new(base_url: &str) -> Result { - // Add the port to the base URL if there is no port explicitly included - // in the URL and the scheme allows us to infer a default port. - // Jsonrpsee requires a port to always be present even if one can be - // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048. - let uri = base_url.parse::().map_err(Error::InvalidRpcUrl)?; - let mut parts = uri.into_parts(); - if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) { - if authority.port().is_none() { - let port = match scheme.as_str() { - "http" => Some(80), - "https" => Some(443), - _ => None, - }; - if let Some(port) = port { - let host = authority.host(); - parts.authority = Some( - Authority::from_str(&format!("{host}:{port}")) - .map_err(Error::InvalidRpcUrl)?, - ); - } - } - } - let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?; - tracing::trace!(?uri); - Ok(Self { - base_url: uri.to_string(), - }) - } - - fn client(&self) -> Result { - let url = self.base_url.clone(); - let mut headers = HeaderMap::new(); - headers.insert("X-Client-Name", "soroban-cli".parse().unwrap()); - let version = VERSION.unwrap_or("devel"); - headers.insert("X-Client-Version", version.parse().unwrap()); - Ok(HttpClientBuilder::default() - .set_headers(headers) - .build(url)?) - } - - pub async fn friendbot_url(&self) -> Result { - let network = self.get_network().await?; - tracing::trace!("{network:#?}"); - network.friendbot_url.ok_or_else(|| { - Error::NotFound( - "Friendbot".to_string(), - "Friendbot is not available on this network".to_string(), - ) - }) - } - - pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result { - let server = self.get_network().await?.passphrase; - if let Some(expected) = expected { - if expected != server { - return Err(Error::InvalidNetworkPassphrase { - expected: expected.to_string(), - server, - }); - } - } - Ok(server) - } - - pub async fn get_network(&self) -> Result { - tracing::trace!("Getting network"); - Ok(self.client()?.request("getNetwork", rpc_params![]).await?) - } - - pub async fn get_latest_ledger(&self) -> Result { - tracing::trace!("Getting latest ledger"); - Ok(self - .client()? - .request("getLatestLedger", rpc_params![]) - .await?) - } - - pub async fn get_account(&self, address: &str) -> Result { - tracing::trace!("Getting address {}", address); - let key = LedgerKey::Account(LedgerKeyAccount { - account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - stellar_strkey::ed25519::PublicKey::from_string(address)?.0, - ))), - }); - let keys = Vec::from([key]); - let response = self.get_ledger_entries(&keys).await?; - let entries = response.entries.unwrap_or_default(); - if entries.is_empty() { - return Err(Error::NotFound( - "Account".to_string(), - format!( - r#"{address} -Might need to fund account like: -soroban config identity fund {address} --network -soroban config identity fund {address} --helper-url "# - ), - )); - } - let ledger_entry = &entries[0]; - let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none()); - if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? { - tracing::trace!(account=?entry); - Ok(entry) - } else { - Err(Error::InvalidResponse) - } - } - - pub async fn send_transaction( - &self, - tx: &TransactionEnvelope, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { - let client = self.client()?; - tracing::trace!("Sending:\n{tx:#?}"); - let SendTransactionResponse { - hash, - error_result_xdr, - status, - .. - } = client - .request( - "sendTransaction", - rpc_params![tx.to_xdr_base64(Limits::none())?], - ) - .await - .map_err(|err| { - Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}")) - })?; - - if status == "ERROR" { - let error = error_result_xdr - .ok_or(Error::MissingError) - .and_then(|x| { - TransactionResult::read_xdr_base64(&mut Limited::new( - x.as_bytes(), - Limits::none(), - )) - .map_err(|_| Error::InvalidResponse) - }) - .map(|r| r.result); - tracing::error!("TXN failed:\n {error:#?}"); - return Err(Error::TransactionSubmissionFailed(format!("{:#?}", error?))); - } - // even if status == "success" we need to query the transaction status in order to get the result - - // Poll the transaction status - let start = Instant::now(); - loop { - let response: GetTransactionResponse = self.get_transaction(&hash).await?.try_into()?; - match response.status.as_str() { - "SUCCESS" => { - // TODO: the caller should probably be printing this - tracing::trace!("{response:#?}"); - let GetTransactionResponse { - result, - result_meta, - .. - } = response; - let meta = result_meta.ok_or(Error::MissingResult)?; - let events = extract_events(&meta); - return Ok((result.ok_or(Error::MissingResult)?, meta, events)); - } - "FAILED" => { - tracing::error!("{response:#?}"); - // TODO: provide a more elaborate error - return Err(Error::TransactionSubmissionFailed(format!( - "{:#?}", - response.result - ))); - } - "NOT_FOUND" => (), - _ => { - return Err(Error::UnexpectedTransactionStatus(response.status)); - } - }; - let duration = start.elapsed(); - // TODO: parameterize the timeout instead of using a magic constant - if duration.as_secs() > 10 { - return Err(Error::TransactionSubmissionTimeout); - } - sleep(Duration::from_secs(1)).await; - } - } - - pub async fn simulate_transaction( - &self, - tx: &TransactionEnvelope, - ) -> Result { - tracing::trace!("Simulating:\n{tx:#?}"); - let base64_tx = tx.to_xdr_base64(Limits::none())?; - let mut builder = ObjectParams::new(); - builder.insert("transaction", base64_tx)?; - let response: SimulateTransactionResponse = self - .client()? - .request("simulateTransaction", builder) - .await?; - tracing::trace!("Simulation response:\n {response:#?}"); - match response.error { - None => Ok(response), - Some(e) => { - crate::log::diagnostic_events(&response.events, tracing::Level::ERROR); - Err(Error::TransactionSimulationFailed(e)) - } - } - } - - pub async fn prepare_and_send_transaction( - &self, - tx_without_preflight: &Transaction, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - network_passphrase: &str, - log_events: Option, - log_resources: Option, - ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { - let txn = txn::Assembled::new(tx_without_preflight, self).await?; - let seq_num = txn.sim_res().latest_ledger + 60; //5 min; - let authorized = txn - .handle_restore(self, source_key, network_passphrase) - .await? - .authorize(self, source_key, signers, seq_num, network_passphrase) - .await?; - authorized.log(log_events, log_resources)?; - let tx = authorized.sign(source_key, network_passphrase)?; - self.send_transaction(&tx).await - } - - pub async fn get_transaction(&self, tx_id: &str) -> Result { - Ok(self - .client()? - .request("getTransaction", rpc_params![tx_id]) - .await?) - } - - pub async fn get_ledger_entries( - &self, - keys: &[LedgerKey], - ) -> Result { - let mut base64_keys: Vec = vec![]; - for k in keys { - let base64_result = k.to_xdr_base64(Limits::none()); - if base64_result.is_err() { - return Err(Error::Xdr(XdrError::Invalid)); - } - base64_keys.push(k.to_xdr_base64(Limits::none()).unwrap()); - } - Ok(self - .client()? - .request("getLedgerEntries", rpc_params![base64_keys]) - .await?) - } - - pub async fn get_full_ledger_entries( - &self, - ledger_keys: &[LedgerKey], - ) -> Result { - let keys = ledger_keys - .iter() - .filter(|key| !matches!(key, LedgerKey::Ttl(_))) - .map(Clone::clone) - .collect::>(); - tracing::trace!("keys: {keys:#?}"); - let GetLedgerEntriesResponse { - entries, - latest_ledger, - } = self.get_ledger_entries(&keys).await?; - tracing::trace!("raw: {entries:#?}"); - let entries = entries - .unwrap_or_default() - .iter() - .map( - |LedgerEntryResult { - key, - xdr, - last_modified_ledger, - live_until_ledger_seq_ledger_seq, - }| { - Ok(FullLedgerEntry { - key: LedgerKey::from_xdr_base64(key, Limits::none())?, - val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?, - live_until_ledger_seq: live_until_ledger_seq_ledger_seq.unwrap_or_default(), - last_modified_ledger: *last_modified_ledger, - }) - }, - ) - .collect::, Error>>()?; - tracing::trace!("parsed: {entries:#?}"); - Ok(FullLedgerEntries { - entries, - latest_ledger, - }) - } - - pub async fn get_events( - &self, - start: EventStart, - event_type: Option, - contract_ids: &[String], - topics: &[String], - limit: Option, - ) -> Result { - let mut filters = serde_json::Map::new(); - - event_type - .and_then(|t| match t { - EventType::All => None, // all is the default, so avoid incl. the param - EventType::Contract => Some("contract"), - EventType::System => Some("system"), - }) - .map(|t| filters.insert("type".to_string(), t.into())); - - filters.insert("topics".to_string(), topics.into()); - filters.insert("contractIds".to_string(), contract_ids.into()); - - let mut pagination = serde_json::Map::new(); - if let Some(limit) = limit { - pagination.insert("limit".to_string(), limit.into()); - } - - let mut oparams = ObjectParams::new(); - match start { - EventStart::Ledger(l) => oparams.insert("startLedger", l)?, - EventStart::Cursor(c) => { - pagination.insert("cursor".to_string(), c.into()); - } - }; - oparams.insert("filters", vec![filters])?; - oparams.insert("pagination", pagination)?; - - Ok(self.client()?.request("getEvents", oparams).await?) - } - - pub async fn get_contract_data( - &self, - contract_id: &[u8; 32], - ) -> Result { - // Get the contract from the network - let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData { - contract: xdr::ScAddress::Contract(xdr::Hash(*contract_id)), - key: xdr::ScVal::LedgerKeyContractInstance, - durability: xdr::ContractDataDurability::Persistent, - }); - let contract_ref = self.get_ledger_entries(&[contract_key]).await?; - let entries = contract_ref.entries.unwrap_or_default(); - if entries.is_empty() { - let contract_address = stellar_strkey::Contract(*contract_id).to_string(); - return Err(Error::NotFound("Contract".to_string(), contract_address)); - } - let contract_ref_entry = &entries[0]; - match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? { - LedgerEntryData::ContractData(contract_data) => Ok(contract_data), - scval => Err(Error::UnexpectedContractCodeDataType(scval)), - } - } - - pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result, Error> { - match self.get_contract_data(contract_id).await? { - xdr::ContractDataEntry { - val: - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::Wasm(hash), - .. - }), - .. - } => self.get_remote_wasm_from_hash(hash).await, - scval => Err(Error::UnexpectedToken(scval)), - } - } - - pub async fn get_remote_wasm_from_hash(&self, hash: xdr::Hash) -> Result, Error> { - let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); - let contract_data = self.get_ledger_entries(&[code_key]).await?; - let entries = contract_data.entries.unwrap_or_default(); - if entries.is_empty() { - return Err(Error::NotFound( - "Contract Code".to_string(), - hex::encode(hash), - )); - } - let contract_data_entry = &entries[0]; - match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? { - LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()), - scval => Err(Error::UnexpectedContractCodeDataType(scval)), - } - } - - pub async fn get_remote_contract_spec( - &self, - contract_id: &[u8; 32], - ) -> Result, Error> { - let contract_data = self.get_contract_data(contract_id).await?; - match contract_data.val { - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::Wasm(hash), - .. - }) => Ok(contract_spec::ContractSpec::new( - &self.get_remote_wasm_from_hash(hash).await?, - ) - .map_err(Error::CouldNotParseContractSpec)? - .spec), - xdr::ScVal::ContractInstance(xdr::ScContractInstance { - executable: xdr::ContractExecutable::StellarAsset, - .. - }) => Ok(soroban_spec::read::parse_raw( - &token::StellarAssetSpec::spec_xdr(), - )?), - _ => Err(Error::Xdr(XdrError::Invalid)), - } - } -} - -fn extract_events(tx_meta: &TransactionMeta) -> Vec { - match tx_meta { - TransactionMeta::V3(TransactionMetaV3 { - soroban_meta: Some(meta), - .. - }) => { - // NOTE: we assume there can only be one operation, since we only send one - if meta.diagnostic_events.len() == 1 { - meta.diagnostic_events.clone().into() - } else if meta.events.len() == 1 { - meta.events - .iter() - .map(|e| DiagnosticEvent { - in_successful_contract_call: true, - event: e.clone(), - }) - .collect() - } else { - Vec::new() - } - } - _ => Vec::new(), - } -} - -pub fn parse_cursor(c: &str) -> Result<(u64, i32), Error> { - let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?; - let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?; - let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?; - Ok((toid_part, start_index)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn simulation_transaction_response_parsing() { - let s = r#"{ - "minResourceFee": "100000000", - "cost": { "cpuInsns": "1000", "memBytes": "1000" }, - "transactionData": "", - "latestLedger": 1234 - }"#; - - let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); - assert_eq!(resp.min_resource_fee, 100_000_000); - } - - #[test] - fn simulation_transaction_response_parsing_mostly_empty() { - let s = r#"{ - "latestLedger": 1234 - }"#; - - let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); - assert_eq!(resp.latest_ledger, 1_234); - } - - #[test] - fn test_rpc_url_default_ports() { - // Default ports are added. - let client = Client::new("http://example.com").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/"); - let client = Client::new("https://example.com").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/"); - - // Ports are not added when already present. - let client = Client::new("http://example.com:8080").unwrap(); - assert_eq!(client.base_url, "http://example.com:8080/"); - let client = Client::new("https://example.com:8080").unwrap(); - assert_eq!(client.base_url, "https://example.com:8080/"); - - // Paths are not modified. - let client = Client::new("http://example.com/a/b/c").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b/c"); - let client = Client::new("https://example.com/a/b/c").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b/c"); - let client = Client::new("http://example.com/a/b/c/").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b/c/"); - let client = Client::new("https://example.com/a/b/c/").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b/c/"); - let client = Client::new("http://example.com/a/b:80/c/").unwrap(); - assert_eq!(client.base_url, "http://example.com:80/a/b:80/c/"); - let client = Client::new("https://example.com/a/b:80/c/").unwrap(); - assert_eq!(client.base_url, "https://example.com:443/a/b:80/c/"); - } - - #[test] - // Taken from [RPC server - // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21). - fn test_does_topic_match() { - struct TestCase<'a> { - name: &'a str, - filter: Vec<&'a str>, - includes: Vec>, - excludes: Vec>, - } - - let xfer = "AAAABQAAAAh0cmFuc2Zlcg=="; - let number = "AAAAAQB6Mcc="; - let star = "*"; - - for tc in vec![ - // No filter means match nothing. - TestCase { - name: "", - filter: vec![], - includes: vec![], - excludes: vec![vec![xfer]], - }, - // "*" should match "transfer/" but not "transfer/transfer" or - // "transfer/amount", because * is specified as a SINGLE segment - // wildcard. - TestCase { - name: "*", - filter: vec![star], - includes: vec![vec![xfer]], - excludes: vec![vec![xfer, xfer], vec![xfer, number]], - }, - // "*/transfer" should match anything preceding "transfer", but - // nothing that isn't exactly two segments long. - TestCase { - name: "*/transfer", - filter: vec![star, xfer], - includes: vec![vec![number, xfer], vec![xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer, number], - vec![xfer], - vec![xfer, number], - vec![xfer, xfer, xfer], - ], - }, - // The inverse case of before: "transfer/*" should match any single - // segment after a segment that is exactly "transfer", but no - // additional segments. - TestCase { - name: "transfer/*", - filter: vec![xfer, star], - includes: vec![vec![xfer, number], vec![xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer, number], - vec![xfer], - vec![number, xfer], - vec![xfer, xfer, xfer], - ], - }, - // Here, we extend to exactly two wild segments after transfer. - TestCase { - name: "transfer/*/*", - filter: vec![xfer, star, star], - includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, xfer], - vec![number, xfer, number, number], - vec![xfer], - vec![xfer, xfer, xfer, xfer], - ], - }, - // Here, we ensure wildcards can be in the middle of a filter: only - // exact matches happen on the ends, while the middle can be - // anything. - TestCase { - name: "transfer/*/number", - filter: vec![xfer, star, number], - includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]], - excludes: vec![ - vec![number], - vec![number, number], - vec![number, number, number], - vec![number, xfer, number], - vec![xfer], - vec![number, xfer], - vec![xfer, xfer, xfer], - vec![xfer, number, xfer], - ], - }, - ] { - for topic in tc.includes { - assert!( - does_topic_match( - &topic - .iter() - .map(std::string::ToString::to_string) - .collect::>(), - &tc.filter - .iter() - .map(std::string::ToString::to_string) - .collect::>() - ), - "test: {}, topic ({:?}) should be matched by filter ({:?})", - tc.name, - topic, - tc.filter - ); - } - - for topic in tc.excludes { - assert!( - !does_topic_match( - // make deep copies of the vecs - &topic - .iter() - .map(std::string::ToString::to_string) - .collect::>(), - &tc.filter - .iter() - .map(std::string::ToString::to_string) - .collect::>() - ), - "test: {}, topic ({:?}) should NOT be matched by filter ({:?})", - tc.name, - topic, - tc.filter - ); - } - } - } -} diff --git a/cmd/soroban-cli/src/rpc/txn.rs b/cmd/soroban-cli/src/rpc/txn.rs deleted file mode 100644 index 9e36938d..00000000 --- a/cmd/soroban-cli/src/rpc/txn.rs +++ /dev/null @@ -1,610 +0,0 @@ -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ - self, AccountId, DecoratedSignature, ExtensionPoint, Hash, HashIdPreimage, - HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Memo, Operation, - OperationBody, Preconditions, PublicKey, ReadXdr, RestoreFootprintOp, ScAddress, ScMap, - ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, - SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, SorobanResources, - SorobanTransactionData, Transaction, TransactionEnvelope, TransactionExt, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, Uint256, VecM, WriteXdr, -}; - -use crate::rpc::{Client, Error, RestorePreamble, SimulateTransactionResponse}; - -use super::{LogEvents, LogResources}; - -pub struct Assembled { - txn: Transaction, - sim_res: SimulateTransactionResponse, -} - -impl Assembled { - pub async fn new(txn: &Transaction, client: &Client) -> Result { - let sim_res = Self::simulate(txn, client).await?; - let txn = assemble(txn, &sim_res)?; - Ok(Self { txn, sim_res }) - } - - pub fn hash(&self, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(self.txn.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) - } - - pub fn sign( - self, - key: &ed25519_dalek::SigningKey, - network_passphrase: &str, - ) -> Result { - let tx = self.txn(); - let tx_hash = self.hash(network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: vec![decorated_signature].try_into()?, - })) - } - - pub async fn simulate( - tx: &Transaction, - client: &Client, - ) -> Result { - client - .simulate_transaction(&TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: VecM::default(), - })) - .await - } - - pub async fn handle_restore( - self, - client: &Client, - source_key: &ed25519_dalek::SigningKey, - network_passphrase: &str, - ) -> Result { - if let Some(restore_preamble) = &self.sim_res.restore_preamble { - // Build and submit the restore transaction - client - .send_transaction( - &Assembled::new(&restore(self.txn(), restore_preamble)?, client) - .await? - .sign(source_key, network_passphrase)?, - ) - .await?; - Ok(self.bump_seq_num()) - } else { - Ok(self) - } - } - - pub fn txn(&self) -> &Transaction { - &self.txn - } - - pub fn sim_res(&self) -> &SimulateTransactionResponse { - &self.sim_res - } - - pub async fn authorize( - self, - client: &Client, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - seq_num: u32, - network_passphrase: &str, - ) -> Result { - if let Some(txn) = sign_soroban_authorizations( - self.txn(), - source_key, - signers, - seq_num, - network_passphrase, - )? { - Self::new(&txn, client).await - } else { - Ok(self) - } - } - - pub fn bump_seq_num(mut self) -> Self { - self.txn.seq_num.0 += 1; - self - } - - pub fn auth(&self) -> VecM { - self.txn - .operations - .first() - .and_then(|op| match op.body { - OperationBody::InvokeHostFunction(ref body) => (matches!( - body.auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - )) - .then_some(body.auth.clone()), - _ => None, - }) - .unwrap_or_default() - } - - pub fn log( - &self, - log_events: Option, - log_resources: Option, - ) -> Result<(), Error> { - if let TransactionExt::V1(SorobanTransactionData { - resources: resources @ SorobanResources { footprint, .. }, - .. - }) = &self.txn.ext - { - if let Some(log) = log_resources { - log(resources); - } - if let Some(log) = log_events { - log(footprint, &[self.auth()], &self.sim_res.events()?); - }; - } - Ok(()) - } -} - -// Apply the result of a simulateTransaction onto a transaction envelope, preparing it for -// submission to the network. -pub fn assemble( - raw: &Transaction, - simulation: &SimulateTransactionResponse, -) -> Result { - let mut tx = raw.clone(); - - // Right now simulate.results is one-result-per-function, and assumes there is only one - // operation in the txn, so we need to enforce that here. I (Paul) think that is a bug - // in soroban-rpc.simulateTransaction design, and we should fix it there. - // TODO: We should to better handling so non-soroban txns can be a passthrough here. - if tx.operations.len() != 1 { - return Err(Error::UnexpectedOperationCount { - count: tx.operations.len(), - }); - } - - let transaction_data = simulation.transaction_data()?; - - let mut op = tx.operations[0].clone(); - if let OperationBody::InvokeHostFunction(ref mut body) = &mut op.body { - if body.auth.is_empty() { - if simulation.results.len() != 1 { - return Err(Error::UnexpectedSimulateTransactionResultSize { - length: simulation.results.len(), - }); - } - - let auths = simulation - .results - .iter() - .map(|r| { - VecM::try_from( - r.auth - .iter() - .map(|v| SorobanAuthorizationEntry::from_xdr_base64(v, Limits::none())) - .collect::, _>>()?, - ) - }) - .collect::, _>>()?; - if !auths.is_empty() { - body.auth = auths[0].clone(); - } - } - } - - // update the fees of the actual transaction to meet the minimum resource fees. - let classic_transaction_fees = crate::fee::Args::default().fee; - // Pad the fees up by 15% for a bit of wiggle room. - tx.fee = (tx.fee.max( - classic_transaction_fees - + u32::try_from(simulation.min_resource_fee) - .map_err(|_| Error::LargeFee(simulation.min_resource_fee))?, - ) * 115) - / 100; - - tx.operations = vec![op].try_into()?; - tx.ext = TransactionExt::V1(transaction_data); - Ok(tx) -} - -// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given -// transaction. If unable to sign, return an error. -fn sign_soroban_authorizations( - raw: &Transaction, - source_key: &ed25519_dalek::SigningKey, - signers: &[ed25519_dalek::SigningKey], - signature_expiration_ledger: u32, - network_passphrase: &str, -) -> Result, Error> { - let mut tx = raw.clone(); - let mut op = match tx.operations.as_slice() { - [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] if matches!( - auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) => - { - op.clone() - } - _ => return Ok(None), - }; - - let Operation { - body: OperationBody::InvokeHostFunction(ref mut body), - .. - } = op - else { - return Ok(None); - }; - - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - - let verification_key = source_key.verifying_key(); - let source_address = verification_key.as_bytes(); - - let signed_auths = body - .auth - .as_slice() - .iter() - .map(|raw_auth| { - let mut auth = raw_auth.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { ref address, .. } = credentials; - - // See if we have a signer for this authorizationEntry - // If not, then we Error - let needle = match address { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, - ScAddress::Contract(Hash(c)) => { - // This address is for a contract. This means we're using a custom - // smart-contract account. Currently the CLI doesn't support that yet. - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) - .to_string(), - }); - } - }; - let signer = if let Some(s) = signers - .iter() - .find(|s| needle == s.verifying_key().as_bytes()) - { - s - } else if needle == source_address { - // This is the source address, so we can sign it - source_key - } else { - // We don't have a signer for this address - return Err(Error::MissingSignerForAddress { - address: stellar_strkey::Strkey::PublicKeyEd25519( - stellar_strkey::ed25519::PublicKey(*needle), - ) - .to_string(), - }); - }; - - sign_soroban_authorization_entry( - raw_auth, - signer, - signature_expiration_ledger, - &network_id, - ) - }) - .collect::, Error>>()?; - - body.auth = signed_auths.try_into()?; - tx.operations = vec![op].try_into()?; - Ok(Some(tx)) -} - -fn sign_soroban_authorization_entry( - raw: &SorobanAuthorizationEntry, - signer: &ed25519_dalek::SigningKey, - signature_expiration_ledger: u32, - network_id: &Hash, -) -> Result { - let mut auth = raw.clone(); - let SorobanAuthorizationEntry { - credentials: SorobanCredentials::Address(ref mut credentials), - .. - } = auth - else { - // Doesn't need special signing - return Ok(auth); - }; - let SorobanAddressCredentials { nonce, .. } = credentials; - - let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { - network_id: network_id.clone(), - invocation: auth.root_invocation.clone(), - nonce: *nonce, - signature_expiration_ledger, - }) - .to_xdr(Limits::none())?; - - let payload = Sha256::digest(preimage); - let signature = signer.sign(&payload); - - let map = ScMap::sorted_from(vec![ - ( - ScVal::Symbol(ScSymbol("public_key".try_into()?)), - ScVal::Bytes( - signer - .verifying_key() - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ( - ScVal::Symbol(ScSymbol("signature".try_into()?)), - ScVal::Bytes( - signature - .to_bytes() - .to_vec() - .try_into() - .map_err(Error::Xdr)?, - ), - ), - ]) - .map_err(Error::Xdr)?; - credentials.signature = ScVal::Vec(Some( - vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, - )); - credentials.signature_expiration_ledger = signature_expiration_ledger; - auth.credentials = SorobanCredentials::Address(credentials.clone()); - Ok(auth) -} - -pub fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result { - let transaction_data = - SorobanTransactionData::from_xdr_base64(&restore.transaction_data, Limits::none())?; - let fee = u32::try_from(restore.min_resource_fee) - .map_err(|_| Error::LargeFee(restore.min_resource_fee))?; - Ok(Transaction { - source_account: parent.source_account.clone(), - fee: parent - .fee - .checked_add(fee) - .ok_or(Error::LargeFee(restore.min_resource_fee))?, - seq_num: parent.seq_num.clone(), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::RestoreFootprint(RestoreFootprintOp { - ext: ExtensionPoint::V0, - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V1(transaction_data), - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - use super::super::SimulateHostFunctionResultRaw; - use soroban_env_host::xdr::{ - self, AccountId, ChangeTrustAsset, ChangeTrustOp, ExtensionPoint, Hash, HostFunction, - InvokeContractArgs, InvokeHostFunctionOp, LedgerFootprint, Memo, MuxedAccount, Operation, - Preconditions, PublicKey, ScAddress, ScSymbol, ScVal, SequenceNumber, - SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanResources, - SorobanTransactionData, Uint256, WriteXdr, - }; - use stellar_strkey::ed25519::PublicKey as Ed25519PublicKey; - - const SOURCE: &str = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"; - - fn transaction_data() -> SorobanTransactionData { - SorobanTransactionData { - resources: SorobanResources { - footprint: LedgerFootprint { - read_only: VecM::default(), - read_write: VecM::default(), - }, - instructions: 0, - read_bytes: 5, - write_bytes: 0, - }, - resource_fee: 0, - ext: ExtensionPoint::V0, - } - } - - fn simulation_response() -> SimulateTransactionResponse { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - let fn_auth = &SorobanAuthorizationEntry { - credentials: xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { - address: ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - source_bytes, - )))), - nonce: 0, - signature_expiration_ledger: 0, - signature: ScVal::Void, - }), - root_invocation: SorobanAuthorizedInvocation { - function: SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { - contract_address: ScAddress::Contract(Hash([0; 32])), - function_name: ScSymbol("fn".try_into().unwrap()), - args: VecM::default(), - }), - sub_invocations: VecM::default(), - }, - }; - - SimulateTransactionResponse { - min_resource_fee: 115, - latest_ledger: 3, - results: vec![SimulateHostFunctionResultRaw { - auth: vec![fn_auth.to_xdr_base64(Limits::none()).unwrap()], - xdr: ScVal::U32(0).to_xdr_base64(Limits::none()).unwrap(), - }], - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - ..Default::default() - } - } - - fn single_contract_fn_transaction() -> Transaction { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), - fee: 100, - seq_num: SequenceNumber(0), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::InvokeContract(InvokeContractArgs { - contract_address: ScAddress::Contract(Hash([0x0; 32])), - function_name: ScSymbol::default(), - args: VecM::default(), - }), - auth: VecM::default(), - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V0, - } - } - - #[test] - fn test_assemble_transaction_updates_tx_data_from_simulation_response() { - let sim = simulation_response(); - let txn = single_contract_fn_transaction(); - let Ok(result) = assemble(&txn, &sim) else { - panic!("assemble failed"); - }; - - // validate it auto updated the tx fees from sim response fees - // since it was greater than tx.fee - assert_eq!(247, result.fee); - - // validate it updated sorobantransactiondata block in the tx ext - assert_eq!(TransactionExt::V1(transaction_data()), result.ext); - } - - #[test] - fn test_assemble_transaction_adds_the_auth_to_the_host_function() { - let sim = simulation_response(); - let txn = single_contract_fn_transaction(); - let Ok(result) = assemble(&txn, &sim) else { - panic!("assemble failed"); - }; - - assert_eq!(1, result.operations.len()); - let OperationBody::InvokeHostFunction(ref op) = result.operations[0].body else { - panic!("unexpected operation type: {:#?}", result.operations[0]); - }; - - assert_eq!(1, op.auth.len()); - let auth = &op.auth[0]; - - let xdr::SorobanAuthorizedFunction::ContractFn(xdr::InvokeContractArgs { - ref function_name, - .. - }) = auth.root_invocation.function - else { - panic!("unexpected function type"); - }; - assert_eq!("fn".to_string(), format!("{}", function_name.0)); - - let xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { - address: - xdr::ScAddress::Account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(address))), - .. - }) = &auth.credentials - else { - panic!("unexpected credentials type"); - }; - assert_eq!( - SOURCE.to_string(), - stellar_strkey::ed25519::PublicKey(address.0).to_string() - ); - } - - #[test] - fn test_assemble_transaction_errors_for_non_invokehostfn_ops() { - let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; - let txn = Transaction { - source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), - fee: 100, - seq_num: SequenceNumber(0), - cond: Preconditions::None, - memo: Memo::None, - operations: vec![Operation { - source_account: None, - body: OperationBody::ChangeTrust(ChangeTrustOp { - line: ChangeTrustAsset::Native, - limit: 0, - }), - }] - .try_into() - .unwrap(), - ext: TransactionExt::V0, - }; - - let result = assemble( - &txn, - &SimulateTransactionResponse { - min_resource_fee: 115, - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - latest_ledger: 3, - ..Default::default() - }, - ); - - match result { - Ok(_) => {} - Err(e) => panic!("expected assembled operation, got: {e:#?}"), - } - } - - #[test] - fn test_assemble_transaction_errors_for_errors_for_mismatched_simulation() { - let txn = single_contract_fn_transaction(); - - let result = assemble( - &txn, - &SimulateTransactionResponse { - min_resource_fee: 115, - transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), - latest_ledger: 3, - ..Default::default() - }, - ); - - match result { - Err(Error::UnexpectedSimulateTransactionResultSize { length }) => { - assert_eq!(0, length); - } - r => panic!("expected UnexpectedSimulateTransactionResultSize error, got: {r:#?}"), - } - } -} diff --git a/cmd/soroban-cli/src/toid.rs b/cmd/soroban-cli/src/toid.rs deleted file mode 100644 index 55c89049..00000000 --- a/cmd/soroban-cli/src/toid.rs +++ /dev/null @@ -1,69 +0,0 @@ -/// A barebones implementation of Total Order IDs (TOIDs) from -/// [SEP-35](https://stellar.org/protocol/sep-35), using the reference -/// implementation from the Go -/// [`stellar/go/toid`](https://github.com/stellar/go/blob/b4ba6f8e67f274bf84d21b0effb01ea8a914b766/toid/main.go#L8-L56) -/// package. -#[derive(Copy, Clone)] -pub struct Toid { - ledger_sequence: u32, - transaction_order: u32, - operation_order: u32, -} - -const LEDGER_MASK: u64 = (1 << 32) - 1; -const TRANSACTION_MASK: u64 = (1 << 20) - 1; -const OPERATION_MASK: u64 = (1 << 12) - 1; -const LEDGER_SHIFT: u64 = 32; -const TRANSACTION_SHIFT: u64 = 12; -const OPERATION_SHIFT: u64 = 0; - -impl Toid { - pub fn new(ledger: u32, tx_order: u32, op_order: u32) -> Toid { - Toid { - ledger_sequence: ledger, - transaction_order: tx_order, - operation_order: op_order, - } - } - - pub fn to_paging_token(self) -> String { - let u: u64 = self.into(); - format!("{u:019}") - } -} - -impl From for Toid { - fn from(item: u64) -> Self { - let ledger: u32 = ((item & LEDGER_MASK) >> LEDGER_SHIFT).try_into().unwrap(); - let tx_order: u32 = ((item & TRANSACTION_MASK) >> TRANSACTION_SHIFT) - .try_into() - .unwrap(); - let op_order: u32 = ((item & OPERATION_MASK) >> OPERATION_SHIFT) - .try_into() - .unwrap(); - - Toid::new(ledger, tx_order, op_order) - } -} - -impl From for u64 { - fn from(item: Toid) -> Self { - let l: u64 = item.ledger_sequence.into(); - let t: u64 = item.transaction_order.into(); - let o: u64 = item.operation_order.into(); - - let mut result: u64 = 0; - result |= (l & LEDGER_MASK) << LEDGER_SHIFT; - result |= (t & TRANSACTION_MASK) << TRANSACTION_SHIFT; - result |= (o & OPERATION_MASK) << OPERATION_SHIFT; - - result - } -} - -impl ToString for Toid { - fn to_string(&self) -> String { - let u: u64 = (*self).into(); - u.to_string() - } -} diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs deleted file mode 100644 index ff0018a9..00000000 --- a/cmd/soroban-cli/src/utils.rs +++ /dev/null @@ -1,244 +0,0 @@ -use ed25519_dalek::Signer; -use sha2::{Digest, Sha256}; -use stellar_strkey::ed25519::PrivateKey; - -use soroban_env_host::xdr::{ - Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, WriteXdr, -}; - -pub mod contract_spec; - -/// # Errors -/// -/// Might return an error -pub fn contract_hash(contract: &[u8]) -> Result { - Ok(Hash(Sha256::digest(contract).into())) -} - -/// # Errors -/// -/// Might return an error -pub fn transaction_hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], XdrError> { - let signature_payload = TransactionSignaturePayload { - network_id: Hash(Sha256::digest(network_passphrase).into()), - tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), - }; - Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) -} - -/// # Errors -/// -/// Might return an error -pub fn sign_transaction( - key: &ed25519_dalek::SigningKey, - tx: &Transaction, - network_passphrase: &str, -) -> Result { - let tx_hash = transaction_hash(tx, network_passphrase)?; - let tx_signature = key.sign(&tx_hash); - - let decorated_signature = DecoratedSignature { - hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), - signature: Signature(tx_signature.to_bytes().try_into()?), - }; - - Ok(TransactionEnvelope::Tx(TransactionV1Envelope { - tx: tx.clone(), - signatures: vec![decorated_signature].try_into()?, - })) -} - -/// # Errors -/// -/// Might return an error -pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strkey::DecodeError> { - stellar_strkey::Contract::from_string(contract_id) - .map(|strkey| strkey.0) - .or_else(|_| { - // strkey failed, try to parse it as a hex string, for backwards compatibility. - soroban_spec_tools::utils::padded_hex_from_str(contract_id, 32) - .map_err(|_| stellar_strkey::DecodeError::Invalid)? - .try_into() - .map_err(|_| stellar_strkey::DecodeError::Invalid) - }) - .map_err(|_| stellar_strkey::DecodeError::Invalid) -} - -/// # Errors -/// May not find a config dir -pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { - let soroban_dir = |p: &std::path::Path| p.join(".soroban"); - while !soroban_dir(&pwd).exists() { - if !pwd.pop() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "soroban directory not found", - )); - } - } - Ok(soroban_dir(&pwd)) -} - -pub(crate) fn into_signing_key(key: &PrivateKey) -> ed25519_dalek::SigningKey { - let secret: ed25519_dalek::SecretKey = key.0; - ed25519_dalek::SigningKey::from_bytes(&secret) -} - -/// Used in tests -#[allow(unused)] -pub(crate) fn parse_secret_key( - s: &str, -) -> Result { - Ok(into_signing_key(&PrivateKey::from_string(s)?)) -} - -pub fn is_hex_string(s: &str) -> bool { - s.chars().all(|s| s.is_ascii_hexdigit()) -} - -pub fn contract_id_hash_from_asset( - asset: &Asset, - network_passphrase: &str, -) -> Result { - let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); - let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { - network_id, - contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), - }); - let preimage_xdr = preimage.to_xdr(Limits::none())?; - Ok(Hash(Sha256::digest(preimage_xdr).into())) -} - -pub mod parsing { - - use regex::Regex; - use soroban_env_host::xdr::{ - AccountId, AlphaNum12, AlphaNum4, Asset, AssetCode12, AssetCode4, PublicKey, - }; - - #[derive(thiserror::Error, Debug)] - pub enum Error { - #[error("invalid asset code: {asset}")] - InvalidAssetCode { asset: String }, - #[error("cannot parse account id: {account_id}")] - CannotParseAccountId { account_id: String }, - #[error("cannot parse asset: {asset}")] - CannotParseAsset { asset: String }, - #[error(transparent)] - Regex(#[from] regex::Error), - } - - pub fn parse_asset(str: &str) -> Result { - if str == "native" { - return Ok(Asset::Native); - } - let split: Vec<&str> = str.splitn(2, ':').collect(); - if split.len() != 2 { - return Err(Error::CannotParseAsset { - asset: str.to_string(), - }); - } - let code = split[0]; - let issuer = split[1]; - let re = Regex::new("^[[:alnum:]]{1,12}$")?; - if !re.is_match(code) { - return Err(Error::InvalidAssetCode { - asset: str.to_string(), - }); - } - if code.len() <= 4 { - let mut asset_code: [u8; 4] = [0; 4]; - for (i, b) in code.as_bytes().iter().enumerate() { - asset_code[i] = *b; - } - Ok(Asset::CreditAlphanum4(AlphaNum4 { - asset_code: AssetCode4(asset_code), - issuer: parse_account_id(issuer)?, - })) - } else { - let mut asset_code: [u8; 12] = [0; 12]; - for (i, b) in code.as_bytes().iter().enumerate() { - asset_code[i] = *b; - } - Ok(Asset::CreditAlphanum12(AlphaNum12 { - asset_code: AssetCode12(asset_code), - issuer: parse_account_id(issuer)?, - })) - } - } - - pub fn parse_account_id(str: &str) -> Result { - let pk_bytes = stellar_strkey::ed25519::PublicKey::from_string(str) - .map_err(|_| Error::CannotParseAccountId { - account_id: str.to_string(), - })? - .0; - Ok(AccountId(PublicKey::PublicKeyTypeEd25519(pk_bytes.into()))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_contract_id_from_str() { - // strkey - match contract_id_from_str("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE") { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, - 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, - 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // hex - match contract_id_from_str( - "363eaa3867841fbad0f4ed88c779e4fe66e56a2470dc98c0ec9c073d05c7b103", - ) { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, - 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, - 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // unpadded-hex - match contract_id_from_str("1") { - Ok(contract_id) => assert_eq!( - contract_id, - [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - ] - ), - Err(err) => panic!("Failed to parse contract id: {err}"), - } - - // invalid hex - match contract_id_from_str("foobar") { - Ok(_) => panic!("Expected parsing to fail"), - Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), - } - - // hex too long (33 bytes) - match contract_id_from_str( - "000000000000000000000000000000000000000000000000000000000000000000", - ) { - Ok(_) => panic!("Expected parsing to fail"), - Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), - } - } -} diff --git a/cmd/soroban-cli/src/utils/contract_spec.rs b/cmd/soroban-cli/src/utils/contract_spec.rs deleted file mode 100644 index b4f24abe..00000000 --- a/cmd/soroban-cli/src/utils/contract_spec.rs +++ /dev/null @@ -1,276 +0,0 @@ -use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; -use std::{ - fmt::Display, - io::{self, Cursor}, -}; - -use soroban_env_host::xdr::{ - self, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, - ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, - StringM, WriteXdr, -}; - -pub struct ContractSpec { - pub env_meta_base64: Option, - pub env_meta: Vec, - pub meta_base64: Option, - pub meta: Vec, - pub spec_base64: Option, - pub spec: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] xdr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), -} - -impl ContractSpec { - pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(section) = payload { - let out = match section.name() { - "contractenvmetav0" => &mut env_meta, - "contractmetav0" => &mut meta, - "contractspecv0" => &mut spec, - _ => continue, - }; - *out = Some(section.data()); - }; - } - - let mut env_meta_base64 = None; - let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); - let cursor = Cursor::new(env_meta); - let mut read = Limited::new(cursor, Limits::none()); - ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut meta_base64 = None; - let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); - let cursor = Cursor::new(meta); - let mut depth_limit_read = Limited::new(cursor, Limits::none()); - ScMetaEntry::read_xdr_iter(&mut depth_limit_read) - .collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut spec_base64 = None; - let spec = if let Some(spec) = spec { - spec_base64 = Some(base64.encode(spec)); - let cursor = Cursor::new(spec); - let mut read = Limited::new(cursor, Limits::none()); - ScSpecEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - Ok(ContractSpec { - env_meta_base64, - env_meta, - meta_base64, - meta, - spec_base64, - spec, - }) - } - - pub fn spec_as_json_array(&self) -> Result { - let spec = self - .spec - .iter() - .map(|e| Ok(format!("\"{}\"", e.to_xdr_base64(Limits::none())?))) - .collect::, Error>>()? - .join(",\n"); - Ok(format!("[{spec}]")) - } -} - -impl Display for ContractSpec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(env_meta) = &self.env_meta_base64 { - writeln!(f, "Env Meta: {env_meta}")?; - for env_meta_entry in &self.env_meta { - match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - writeln!(f, " • Interface Version: {v}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Env Meta: None\n")?; - } - - if let Some(_meta) = &self.meta_base64 { - writeln!(f, "Contract Meta:")?; - for meta_entry in &self.meta { - match meta_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { - writeln!(f, " • {key}: {val}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Contract Meta: None\n")?; - } - - if let Some(_spec_base64) = &self.spec_base64 { - writeln!(f, "Contract Spec:")?; - for spec_entry in &self.spec { - match spec_entry { - ScSpecEntry::FunctionV0(func) => write_func(f, func)?, - ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, - ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, - ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, - ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, - } - } - } else { - writeln!(f, "Contract Spec: None")?; - } - Ok(()) - } -} - -fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { - writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; - if func.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - &indent(&func.doc.to_utf8_string_lossy(), 11).trim() - )?; - } - writeln!( - f, - " Inputs: {}", - indent(&format!("{:#?}", func.inputs), 5).trim() - )?; - writeln!( - f, - " Output: {}", - indent(&format!("{:#?}", func.outputs), 5).trim() - )?; - writeln!(f)?; - Ok(()) -} - -fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { - writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { - writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Fields:")?; - for field in udt.fields.iter() { - writeln!( - f, - " • {}: {}", - field.name.to_utf8_string_lossy(), - indent(&format!("{:#?}", field.type_), 8).trim() - )?; - if field.doc.len() > 0 { - writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; - } - } - writeln!(f)?; - Ok(()) -} - -fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { - writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { - writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn indent(s: &str, n: usize) -> String { - let pad = " ".repeat(n); - s.lines() - .map(|line| format!("{pad}{line}")) - .collect::>() - .join("\n") -} - -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { - if lib.len() > 0 { - format!( - "{}::{}", - lib.to_utf8_string_lossy(), - name.to_utf8_string_lossy() - ) - } else { - name.to_utf8_string_lossy() - } -} diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs deleted file mode 100644 index fce44c7c..00000000 --- a/cmd/soroban-cli/src/wasm.rs +++ /dev/null @@ -1,93 +0,0 @@ -use clap::arg; -use soroban_env_host::xdr::{self, LedgerKey, LedgerKeyContractCode}; -use std::{ - fs, io, - path::{Path, PathBuf}, -}; - -use crate::utils::{self, contract_spec::ContractSpec}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] xdr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), - #[error(transparent)] - ContractSpec(#[from] crate::utils::contract_spec::Error), -} - -#[derive(Debug, clap::Args, Clone)] -#[group(skip)] -pub struct Args { - /// Path to wasm binary - #[arg(long)] - pub wasm: PathBuf, -} - -impl Args { - /// # Errors - /// May fail to read wasm file - pub fn read(&self) -> Result, Error> { - fs::read(&self.wasm).map_err(|e| Error::CannotReadContractFile { - filepath: self.wasm.clone(), - error: e, - }) - } - - /// # Errors - /// May fail to read wasm file - pub fn len(&self) -> Result { - len(&self.wasm) - } - - /// # Errors - /// May fail to read wasm file - pub fn is_empty(&self) -> Result { - self.len().map(|len| len == 0) - } - - /// # Errors - /// May fail to read wasm file or parse xdr section - pub fn parse(&self) -> Result { - let contents = self.read()?; - Ok(ContractSpec::new(&contents)?) - } -} - -impl From<&PathBuf> for Args { - fn from(wasm: &PathBuf) -> Self { - Self { wasm: wasm.clone() } - } -} - -impl TryInto for Args { - type Error = Error; - fn try_into(self) -> Result { - Ok(LedgerKey::ContractCode(LedgerKeyContractCode { - hash: utils::contract_hash(&self.read()?)?, - })) - } -} - -/// # Errors -/// May fail to read wasm file -pub fn len(p: &Path) -> Result { - Ok(std::fs::metadata(p) - .map_err(|e| Error::CannotReadContractFile { - filepath: p.to_path_buf(), - error: e, - })? - .len()) -} From 59fa0107006da3b68c25a87b6141ea8a6dd6d7c0 Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 17:58:23 -0800 Subject: [PATCH 6/7] Revert "Use external soroban-cli crate" This reverts commit a7d2b154f2d1ac6be675faf29f03d5dac90a190b. --- .github/workflows/rust.yml | 2 +- Cargo.lock | 457 ++++--- Cargo.toml | 8 +- Makefile | 1 + cmd/soroban-cli/Cargo.toml | 106 ++ cmd/soroban-cli/README.md | 28 + cmd/soroban-cli/build.rs | 3 + cmd/soroban-cli/src/bin/doc-gen.rs | 36 + cmd/soroban-cli/src/bin/main.rs | 51 + cmd/soroban-cli/src/commands/completion.rs | 32 + .../src/commands/config/locator.rs | 358 ++++++ cmd/soroban-cli/src/commands/config/mod.rs | 95 ++ cmd/soroban-cli/src/commands/config/secret.rs | 143 +++ .../src/commands/contract/asset.rs | 27 + .../src/commands/contract/bindings.rs | 38 + .../src/commands/contract/bindings/json.rs | 29 + .../src/commands/contract/bindings/rust.rs | 39 + .../commands/contract/bindings/typescript.rs | 131 ++ .../src/commands/contract/build.rs | 194 +++ .../src/commands/contract/deploy.rs | 28 + .../src/commands/contract/deploy/asset.rs | 155 +++ .../src/commands/contract/deploy/wasm.rs | 228 ++++ .../src/commands/contract/extend.rs | 192 +++ .../src/commands/contract/fetch.rs | 185 +++ cmd/soroban-cli/src/commands/contract/id.rs | 28 + .../src/commands/contract/id/asset.rs | 36 + .../src/commands/contract/id/wasm.rs | 68 + .../src/commands/contract/inspect.rs | 49 + .../src/commands/contract/install.rs | 223 ++++ .../src/commands/contract/invoke.rs | 484 +++++++ cmd/soroban-cli/src/commands/contract/mod.rs | 158 +++ .../src/commands/contract/optimize.rs | 80 ++ cmd/soroban-cli/src/commands/contract/read.rs | 180 +++ .../src/commands/contract/restore.rs | 206 +++ cmd/soroban-cli/src/commands/events.rs | 221 ++++ cmd/soroban-cli/src/commands/global.rs | 61 + cmd/soroban-cli/src/commands/keys/add.rs | 33 + cmd/soroban-cli/src/commands/keys/address.rs | 54 + cmd/soroban-cli/src/commands/keys/fund.rs | 34 + cmd/soroban-cli/src/commands/keys/generate.rs | 75 ++ cmd/soroban-cli/src/commands/keys/ls.rs | 45 + cmd/soroban-cli/src/commands/keys/mod.rs | 63 + cmd/soroban-cli/src/commands/keys/rm.rs | 25 + cmd/soroban-cli/src/commands/keys/show.rs | 43 + cmd/soroban-cli/src/commands/lab/mod.rs | 31 + cmd/soroban-cli/src/commands/lab/token/mod.rs | 38 + cmd/soroban-cli/src/commands/mod.rs | 160 +++ cmd/soroban-cli/src/commands/network/add.rs | 32 + cmd/soroban-cli/src/commands/network/ls.rs | 44 + cmd/soroban-cli/src/commands/network/mod.rs | 197 +++ cmd/soroban-cli/src/commands/network/rm.rs | 24 + cmd/soroban-cli/src/commands/plugin.rs | 96 ++ cmd/soroban-cli/src/commands/version.rs | 32 + cmd/soroban-cli/src/fee.rs | 16 + cmd/soroban-cli/src/key.rs | 110 ++ cmd/soroban-cli/src/lib.rs | 53 + cmd/soroban-cli/src/log.rs | 13 + cmd/soroban-cli/src/log/auth.rs | 7 + cmd/soroban-cli/src/log/budget.rs | 5 + cmd/soroban-cli/src/log/cost.rs | 27 + cmd/soroban-cli/src/log/diagnostic_event.rs | 11 + cmd/soroban-cli/src/log/footprint.rs | 5 + cmd/soroban-cli/src/log/host_event.rs | 7 + .../src/rpc/fixtures/event_response.json | 39 + cmd/soroban-cli/src/rpc/mod.rs | 1141 +++++++++++++++++ cmd/soroban-cli/src/rpc/txn.rs | 610 +++++++++ cmd/soroban-cli/src/toid.rs | 69 + cmd/soroban-cli/src/utils.rs | 244 ++++ cmd/soroban-cli/src/utils/contract_spec.rs | 276 ++++ cmd/soroban-cli/src/wasm.rs | 93 ++ 70 files changed, 7878 insertions(+), 234 deletions(-) create mode 100644 cmd/soroban-cli/Cargo.toml create mode 100644 cmd/soroban-cli/README.md create mode 100644 cmd/soroban-cli/build.rs create mode 100644 cmd/soroban-cli/src/bin/doc-gen.rs create mode 100644 cmd/soroban-cli/src/bin/main.rs create mode 100644 cmd/soroban-cli/src/commands/completion.rs create mode 100644 cmd/soroban-cli/src/commands/config/locator.rs create mode 100644 cmd/soroban-cli/src/commands/config/mod.rs create mode 100644 cmd/soroban-cli/src/commands/config/secret.rs create mode 100644 cmd/soroban-cli/src/commands/contract/asset.rs create mode 100644 cmd/soroban-cli/src/commands/contract/bindings.rs create mode 100644 cmd/soroban-cli/src/commands/contract/bindings/json.rs create mode 100644 cmd/soroban-cli/src/commands/contract/bindings/rust.rs create mode 100644 cmd/soroban-cli/src/commands/contract/bindings/typescript.rs create mode 100644 cmd/soroban-cli/src/commands/contract/build.rs create mode 100644 cmd/soroban-cli/src/commands/contract/deploy.rs create mode 100644 cmd/soroban-cli/src/commands/contract/deploy/asset.rs create mode 100644 cmd/soroban-cli/src/commands/contract/deploy/wasm.rs create mode 100644 cmd/soroban-cli/src/commands/contract/extend.rs create mode 100644 cmd/soroban-cli/src/commands/contract/fetch.rs create mode 100644 cmd/soroban-cli/src/commands/contract/id.rs create mode 100644 cmd/soroban-cli/src/commands/contract/id/asset.rs create mode 100644 cmd/soroban-cli/src/commands/contract/id/wasm.rs create mode 100644 cmd/soroban-cli/src/commands/contract/inspect.rs create mode 100644 cmd/soroban-cli/src/commands/contract/install.rs create mode 100644 cmd/soroban-cli/src/commands/contract/invoke.rs create mode 100644 cmd/soroban-cli/src/commands/contract/mod.rs create mode 100644 cmd/soroban-cli/src/commands/contract/optimize.rs create mode 100644 cmd/soroban-cli/src/commands/contract/read.rs create mode 100644 cmd/soroban-cli/src/commands/contract/restore.rs create mode 100644 cmd/soroban-cli/src/commands/events.rs create mode 100644 cmd/soroban-cli/src/commands/global.rs create mode 100644 cmd/soroban-cli/src/commands/keys/add.rs create mode 100644 cmd/soroban-cli/src/commands/keys/address.rs create mode 100644 cmd/soroban-cli/src/commands/keys/fund.rs create mode 100644 cmd/soroban-cli/src/commands/keys/generate.rs create mode 100644 cmd/soroban-cli/src/commands/keys/ls.rs create mode 100644 cmd/soroban-cli/src/commands/keys/mod.rs create mode 100644 cmd/soroban-cli/src/commands/keys/rm.rs create mode 100644 cmd/soroban-cli/src/commands/keys/show.rs create mode 100644 cmd/soroban-cli/src/commands/lab/mod.rs create mode 100644 cmd/soroban-cli/src/commands/lab/token/mod.rs create mode 100644 cmd/soroban-cli/src/commands/mod.rs create mode 100644 cmd/soroban-cli/src/commands/network/add.rs create mode 100644 cmd/soroban-cli/src/commands/network/ls.rs create mode 100644 cmd/soroban-cli/src/commands/network/mod.rs create mode 100644 cmd/soroban-cli/src/commands/network/rm.rs create mode 100644 cmd/soroban-cli/src/commands/plugin.rs create mode 100644 cmd/soroban-cli/src/commands/version.rs create mode 100644 cmd/soroban-cli/src/fee.rs create mode 100644 cmd/soroban-cli/src/key.rs create mode 100644 cmd/soroban-cli/src/lib.rs create mode 100644 cmd/soroban-cli/src/log.rs create mode 100644 cmd/soroban-cli/src/log/auth.rs create mode 100644 cmd/soroban-cli/src/log/budget.rs create mode 100644 cmd/soroban-cli/src/log/cost.rs create mode 100644 cmd/soroban-cli/src/log/diagnostic_event.rs create mode 100644 cmd/soroban-cli/src/log/footprint.rs create mode 100644 cmd/soroban-cli/src/log/host_event.rs create mode 100644 cmd/soroban-cli/src/rpc/fixtures/event_response.json create mode 100644 cmd/soroban-cli/src/rpc/mod.rs create mode 100644 cmd/soroban-cli/src/rpc/txn.rs create mode 100644 cmd/soroban-cli/src/toid.rs create mode 100644 cmd/soroban-cli/src/utils.rs create mode 100644 cmd/soroban-cli/src/utils/contract_spec.rs create mode 100644 cmd/soroban-cli/src/wasm.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e06d913c..c96167ad 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -77,7 +77,7 @@ jobs: - if: startsWith(matrix.target, 'x86_64') # specify directories explicitly to avoid building the preflight library (otherwise it will fail with missing symbols) run: | - for I in cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do + for I in cmd/soroban-cli cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do cargo test --target ${{ matrix.target }} --manifest-path $I/Cargo.toml done diff --git a/Cargo.lock b/Cargo.lock index 432aff82..45bd18dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -273,7 +273,7 @@ dependencies = [ "num-bigint", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -314,6 +314,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -348,6 +349,15 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-markdown" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325f50228f76921784b6d9f2d62de6778d834483248eefecd27279174797e579" +dependencies = [ + "clap", +] + [[package]] name = "clap_builder" version = "4.4.18" @@ -378,7 +388,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -387,6 +397,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -540,7 +560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -581,7 +601,51 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", +] + +[[package]] +name = "cxx" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ab30434ea0ff6aa640a08dda5284026a366d47565496fd40b6cbfbdd7e31a2" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b649d7dfae8268450d53d109388b337b9352c7cba1fc10db4a1bc23c3dc189fb" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.39", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42281b20eba5218c539295c667c18e2f50211bb11902419194c6ed1ae808e547" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45506e3c66512b0a65d291a6b452128b7b1dd9841e20d1e151addbd2c00ea50" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] @@ -605,7 +669,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.39", ] [[package]] @@ -616,7 +680,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -647,7 +711,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -1305,6 +1369,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.67" @@ -1419,6 +1492,15 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1529,7 +1611,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -1605,7 +1687,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -1709,7 +1791,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -1806,7 +1888,7 @@ dependencies = [ "base64 0.21.7", "libc", "sha2 0.10.8", - "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host", "soroban-simulation", ] @@ -1817,7 +1899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.39", ] [[package]] @@ -2060,6 +2142,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.16" @@ -2090,6 +2178,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "sct" version = "0.7.1" @@ -2186,7 +2280,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -2226,7 +2320,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -2339,18 +2433,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "soroban-builtin-sdk-macros" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa4d0718c6bb74d5b9f77d54dde6cc08a7eb6ef0e76b524f723a48f1cf5db2c" -dependencies = [ - "itertools 0.11.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "soroban-builtin-sdk-macros" version = "20.1.0" @@ -2359,19 +2441,20 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] name = "soroban-cli" version = "20.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e995a047a684547f1962d1064329c291632ed035b67ca7c423c4978cc231bd8" dependencies = [ + "assert_cmd", + "assert_fs", "base64 0.21.7", "cargo_metadata", "chrono", "clap", + "clap-markdown", "clap_complete", "crate-git-revision 0.0.4", "csv", @@ -2390,6 +2473,7 @@ dependencies = [ "num-bigint", "openssl", "pathdiff", + "predicates 2.1.5", "rand", "regex", "rpassword", @@ -2400,12 +2484,12 @@ dependencies = [ "serde_json", "sha2 0.10.8", "shlex", - "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-sdk 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk", + "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-json", - "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "soroban-spec-rust", "soroban-spec-tools", "soroban-spec-typescript", "stellar-strkey 0.0.7", @@ -2419,27 +2503,11 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "wasm-opt", "wasmparser 0.90.0", "which", ] -[[package]] -name = "soroban-env-common" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e3c4b5ea7936e814706f2a5da6af574260319bcc66fbf5517b39f19b5fa365" -dependencies = [ - "crate-git-revision 0.0.6", - "ethnum", - "num-derive", - "num-traits", - "serde", - "soroban-env-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions", - "stellar-xdr", -] - [[package]] name = "soroban-env-common" version = "20.1.0" @@ -2451,57 +2519,21 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", - "soroban-wasmi 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", + "soroban-env-macros", + "soroban-wasmi", "static_assertions", "stellar-xdr", ] -[[package]] -name = "soroban-env-guest" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ff111936103653515b4471b951b6325b966c3bb31c4c1bd5892ea741aa028ca" -dependencies = [ - "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions", -] - [[package]] name = "soroban-env-guest" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4#36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4" dependencies = [ - "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-common", "static_assertions", ] -[[package]] -name = "soroban-env-host" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3d7de1f83760dddf9302dcc32f8adfebaff41946045ea67196511aca8d9f00" -dependencies = [ - "curve25519-dalek 4.1.1", - "ed25519-dalek 2.0.0", - "getrandom", - "hex-literal", - "hmac 0.12.1", - "k256", - "num-derive", - "num-integer", - "num-traits", - "rand", - "rand_chacha", - "sha2 0.10.8", - "sha3", - "soroban-builtin-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-wasmi 0.31.1-soroban.20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions", - "stellar-strkey 0.0.8", -] - [[package]] name = "soroban-env-host" version = "20.1.0" @@ -2521,28 +2553,13 @@ dependencies = [ "rand_chacha", "sha2 0.10.8", "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 0.31.1-soroban.20.0.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", "static_assertions", "stellar-strkey 0.0.8", ] -[[package]] -name = "soroban-env-macros" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff774562cca63a173127b0b6679dce9afde49fd34474eec041dc5612134dda7" -dependencies = [ - "itertools 0.11.0", - "proc-macro2", - "quote", - "serde", - "serde_json", - "stellar-xdr", - "syn", -] - [[package]] name = "soroban-env-macros" version = "20.1.0" @@ -2554,27 +2571,13 @@ dependencies = [ "serde", "serde_json", "stellar-xdr", - "syn", + "syn 2.0.39", ] [[package]] name = "soroban-hello" version = "20.2.0" -[[package]] -name = "soroban-ledger-snapshot" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0f4b117c50bec49f02eab898957f92f13cae78ee3d17d7660821742251b8c2" -dependencies = [ - "serde", - "serde_json", - "serde_with", - "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror", -] - [[package]] name = "soroban-ledger-snapshot" version = "20.1.0" @@ -2583,28 +2586,11 @@ 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)", + "soroban-env-common", + "soroban-env-host", "thiserror", ] -[[package]] -name = "soroban-sdk" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6c13c0f657bec67785cb7d204fcaccca553f937a42743c6acedfd993ad69f6" -dependencies = [ - "bytes-lit", - "rand", - "serde", - "serde_json", - "soroban-env-guest 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-env-host 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-ledger-snapshot 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-sdk-macros 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stellar-strkey 0.0.8", -] - [[package]] name = "soroban-sdk" version = "20.1.0" @@ -2617,33 +2603,13 @@ dependencies = [ "rand", "serde", "serde_json", - "soroban-env-guest 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", - "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", - "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", - "soroban-sdk-macros 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", "stellar-strkey 0.0.8", ] -[[package]] -name = "soroban-sdk-macros" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7e5d144250bbea143f0863462e58200e2a82e37fa611b09f239f3d8e849a83" -dependencies = [ - "crate-git-revision 0.0.6", - "darling", - "itertools 0.11.0", - "proc-macro2", - "quote", - "rustc_version", - "sha2 0.10.8", - "soroban-env-common 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "soroban-spec-rust 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stellar-xdr", - "syn", -] - [[package]] name = "soroban-sdk-macros" version = "20.1.0" @@ -2656,11 +2622,11 @@ dependencies = [ "quote", "rustc_version", "sha2 0.10.8", - "soroban-env-common 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-common", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", - "soroban-spec-rust 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-spec-rust", "stellar-xdr", - "syn", + "syn 2.0.39", ] [[package]] @@ -2670,7 +2636,7 @@ source = "git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a920 dependencies = [ "anyhow", "rand", - "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", + "soroban-env-host", "static_assertions", "thiserror", ] @@ -2713,22 +2679,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "soroban-spec-rust" -version = "20.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1bf5452f0d97cf280f14b66de98571e819e198b124b33472e49dba1128ed9d" -dependencies = [ - "prettyplease", - "proc-macro2", - "quote", - "sha2 0.10.8", - "soroban-spec 20.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "stellar-xdr", - "syn", - "thiserror", -] - [[package]] name = "soroban-spec-rust" version = "20.1.0" @@ -2740,7 +2690,7 @@ dependencies = [ "sha2 0.10.8", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "stellar-xdr", - "syn", + "syn 2.0.39", "thiserror", ] @@ -2794,9 +2744,9 @@ dependencies = [ "serde_json", "sha2 0.10.8", "soroban-cli", - "soroban-env-host 20.1.0 (git+https://github.com/stellar/rs-soroban-env?rev=36d33cb6c986c9a8a9200b7eb04cf02e2c3f0ef4)", - "soroban-ledger-snapshot 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk", "soroban-spec 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", "soroban-spec-tools", "stellar-strkey 0.0.7", @@ -2810,20 +2760,7 @@ name = "soroban-token-sdk" version = "20.1.0" source = "git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb#e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", -] - -[[package]] -name = "soroban-wasmi" -version = "0.31.1-soroban.20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1aaa682a67cbd2173f1d60cb1e7b951d490d7c4e0b7b6f5387cbb952e963c46" -dependencies = [ - "smallvec", - "spin", - "wasmi_arena 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi_core 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser-nostd", + "soroban-sdk", ] [[package]] @@ -2833,8 +2770,8 @@ source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84 dependencies = [ "smallvec", "spin", - "wasmi_arena 0.4.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", - "wasmi_core 0.13.0 (git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721)", + "wasmi_arena", + "wasmi_core", "wasmparser-nostd", ] @@ -2906,12 +2843,42 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.39" @@ -2971,28 +2938,28 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" name = "test_custom_types" version = "20.2.0" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk", ] [[package]] name = "test_hello_world" version = "20.2.0" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk", ] [[package]] name = "test_swap" version = "20.2.0" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk", ] [[package]] name = "test_token" version = "20.2.0" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk", "soroban-token-sdk", ] @@ -3000,7 +2967,7 @@ dependencies = [ name = "test_udt" version = "20.2.0" dependencies = [ - "soroban-sdk 20.1.0 (git+https://github.com/stellar/rs-soroban-sdk?rev=e6c2c900ab82b5f6eec48f69cb2cb519e19819cb)", + "soroban-sdk", ] [[package]] @@ -3020,7 +2987,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -3123,7 +3090,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -3228,7 +3195,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -3303,6 +3270,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "untrusted" version = "0.9.0" @@ -3399,7 +3372,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -3421,7 +3394,7 @@ checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3433,28 +3406,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] -name = "wasmi_arena" -version = "0.4.0" +name = "wasm-opt" +version = "0.114.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" +checksum = "effbef3bd1dde18acb401f73e740a6f3d4a1bc651e9773bddc512fe4d8d68f67" +dependencies = [ + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] [[package]] -name = "wasmi_arena" -version = "0.4.0" -source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" +name = "wasm-opt-cxx-sys" +version = "0.114.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09e24eb283919ace2ed5733bda4842a59ce4c8de110ef5c6d98859513d17047" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] [[package]] -name = "wasmi_core" -version = "0.13.0" +name = "wasm-opt-sys" +version = "0.114.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +checksum = "36f2f817bed2e8d65eb779fa37317e74de15585751f903c9118342d1970703a4" dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", + "anyhow", + "cc", + "cxx", + "cxx-build", ] +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=ab29800224d85ee64d4ac127bac84cdbb0276721#ab29800224d85ee64d4ac127bac84cdbb0276721" + [[package]] name = "wasmi_core" version = "0.13.0" @@ -3695,5 +3690,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] diff --git a/Cargo.toml b/Cargo.toml index 73dc70c4..9da4ca95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [workspace] resolver = "2" members = [ + "cmd/soroban-cli", "cmd/crates/*", "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", "cmd/crates/soroban-test/tests/fixtures/hello", "cmd/soroban-rpc/lib/preflight", ] -default-members = ["cmd/crates/soroban-test"] +default-members = ["cmd/soroban-cli", "cmd/crates/soroban-test"] exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] @@ -52,6 +53,10 @@ version = "=20.1.0" git = "https://github.com/stellar/rs-soroban-sdk" rev = "e6c2c900ab82b5f6eec48f69cb2cb519e19819cb" +[workspace.dependencies.soroban-cli] +version = "20.2.0" +path = "cmd/soroban-cli" + [workspace.dependencies.stellar-xdr] version = "=20.0.2" default-features = true @@ -75,7 +80,6 @@ wasmparser = "0.90.0" soroban-spec-json = "20.2.0" soroban-spec-tools = "20.2.0" soroban-spec-typescript = "20.2.0" -soroban-cli = "20.2.0" # [patch."https://github.com/stellar/rs-soroban-env"] diff --git a/Makefile b/Makefile index fdc88efb..23187151 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ Cargo.lock: Cargo.toml cargo update --workspace install_rust: Cargo.lock + cargo install --path ./cmd/soroban-cli --debug cargo install --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet install: install_rust build-libpreflight diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml new file mode 100644 index 00000000..ad60c827 --- /dev/null +++ b/cmd/soroban-cli/Cargo.toml @@ -0,0 +1,106 @@ +[package] +name = "soroban-cli" +description = "Soroban CLI" +homepage = "https://github.com/stellar/soroban-cli" +repository = "https://github.com/stellar/soroban-cli" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +readme = "README.md" +version = "20.2.0" +edition = "2021" +rust-version.workspace = true +autobins = false +default-run = "soroban" + +[[bin]] +name = "soroban" +path = "src/bin/main.rs" + +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }{ archive-suffix }" +bin-dir = "{ bin }{ binary-ext }" + +[[bin]] +name = "doc-gen" +path = "src/bin/doc-gen.rs" +required-features = ["clap-markdown"] + +[lib] +name = "soroban_cli" +path = "src/lib.rs" +doctest = false + +[features] +default = [] +opt = ["dep:wasm-opt"] + +[dependencies] +stellar-xdr = { workspace = true, features = ["cli"] } +soroban-env-host = { workspace = true } +soroban-spec = { workspace = true } +soroban-spec-json = { workspace = true } +soroban-spec-rust = { workspace = true } +soroban-spec-tools = { workspace = true } +soroban-spec-typescript = { workspace = true } +soroban-ledger-snapshot = { workspace = true } +stellar-strkey = { workspace = true } +soroban-sdk = { workspace = true } +clap = { version = "4.1.8", features = [ + "derive", + "env", + "deprecated", + "string", +] } +base64 = { workspace = true } +thiserror = { workspace = true } +serde = "1.0.82" +serde_derive = "1.0.82" +serde_json = "1.0.82" +serde-aux = "4.1.2" +hex = { workspace = true } +num-bigint = "0.4" +tokio = { version = "1", features = ["full"] } +termcolor = "1.1.3" +termcolor_output = "1.0.1" +clap_complete = "4.1.4" +rand = "0.8.5" +wasmparser = { workspace = true } +sha2 = { workspace = true } +csv = "1.1.6" +ed25519-dalek = "=2.0.0" +jsonrpsee-http-client = "0.20.1" +jsonrpsee-core = "0.20.1" +hyper = "0.14.27" +hyper-tls = "0.5" +http = "0.2.9" +regex = "1.6.0" +wasm-opt = { version = "0.114.0", optional = true } +chrono = "0.4.27" +rpassword = "7.2.0" +dirs = "4.0.0" +toml = "0.5.9" +itertools = "0.10.5" +shlex = "1.1.0" +sep5 = { workspace = true } +ethnum = { workspace = true } +clap-markdown = { version = "0.1.3", optional = true } +which = { workspace = true, features = ["regex"] } +strsim = "0.10.0" +heck = "0.4.1" +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } +cargo_metadata = "0.15.4" +pathdiff = "0.2.1" +dotenvy = "0.15.7" +# For hyper-tls +[target.'cfg(unix)'.dependencies] +openssl = { version = "0.10.55", features = ["vendored"] } + +[build-dependencies] +crate-git-revision = "0.0.4" + +[dev-dependencies] +assert_cmd = "2.0.4" +assert_fs = "1.0.7" +predicates = "2.1.5" diff --git a/cmd/soroban-cli/README.md b/cmd/soroban-cli/README.md new file mode 100644 index 00000000..d17261b1 --- /dev/null +++ b/cmd/soroban-cli/README.md @@ -0,0 +1,28 @@ +# soroban-cli + +CLI for running Soroban contracts locally in a test VM. Executes WASM files built using the [rs-soroban-sdk](https://github.com/stellar/rs-soroban-sdk). + +Soroban: https://soroban.stellar.org + +## Install + +``` +cargo install --locked soroban-cli +``` + +To install with the `opt` feature, which includes a WASM optimization feature and wasm-opt built in: + +``` +cargo install --locked soroban-cli --features opt +``` + +## Usage + +Can invoke a contract method as a subcommand with different arguments. Anything after the slop (`--`) is passed to the contract's CLI. You can use `--help` to learn about which methods are available and what their arguments are including an example of the type of the input. + +## Example + +``` +soroban invoke --id --wasm -- --help +soroban invoke --id --network futurenet -- --help +``` diff --git a/cmd/soroban-cli/build.rs b/cmd/soroban-cli/build.rs new file mode 100644 index 00000000..b6e6dd92 --- /dev/null +++ b/cmd/soroban-cli/build.rs @@ -0,0 +1,3 @@ +fn main() { + crate_git_revision::init(); +} diff --git a/cmd/soroban-cli/src/bin/doc-gen.rs b/cmd/soroban-cli/src/bin/doc-gen.rs new file mode 100644 index 00000000..096f9681 --- /dev/null +++ b/cmd/soroban-cli/src/bin/doc-gen.rs @@ -0,0 +1,36 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +type DynError = Box; + +fn main() -> Result<(), DynError> { + doc_gen()?; + Ok(()) +} + +fn doc_gen() -> std::io::Result<()> { + let out_dir = docs_dir(); + + fs::create_dir_all(out_dir.clone())?; + + std::fs::write( + out_dir.join("soroban-cli-full-docs.md"), + clap_markdown::help_markdown::(), + )?; + + Ok(()) +} + +fn project_root() -> PathBuf { + Path::new(&env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .to_path_buf() +} + +fn docs_dir() -> PathBuf { + project_root().join("docs") +} diff --git a/cmd/soroban-cli/src/bin/main.rs b/cmd/soroban-cli/src/bin/main.rs new file mode 100644 index 00000000..7a87099c --- /dev/null +++ b/cmd/soroban-cli/src/bin/main.rs @@ -0,0 +1,51 @@ +use clap::CommandFactory; +use dotenvy::dotenv; +use tracing_subscriber::{fmt, EnvFilter}; + +use soroban_cli::{commands, Root}; + +#[tokio::main] +async fn main() { + let _ = dotenv().unwrap_or_default(); + let mut root = Root::new().unwrap_or_else(|e| match e { + commands::Error::Clap(e) => { + let mut cmd = Root::command(); + e.format(&mut cmd).exit(); + } + e => { + eprintln!("{e}"); + std::process::exit(1); + } + }); + // Now use root to setup the logger + if let Some(level) = root.global_args.log_level() { + let mut e_filter = EnvFilter::from_default_env() + .add_directive("hyper=off".parse().unwrap()) + .add_directive(format!("soroban_cli={level}").parse().unwrap()); + + for filter in &root.global_args.filter_logs { + e_filter = e_filter.add_directive( + filter + .parse() + .map_err(|e| { + eprintln!("{e}: {filter}"); + std::process::exit(1); + }) + .unwrap(), + ); + } + + let builder = fmt::Subscriber::builder() + .with_env_filter(e_filter) + .with_writer(std::io::stderr); + + let subscriber = builder.finish(); + tracing::subscriber::set_global_default(subscriber) + .expect("Failed to set the global tracing subscriber"); + } + + if let Err(e) = root.run().await { + eprintln!("error: {e}"); + std::process::exit(1); + } +} diff --git a/cmd/soroban-cli/src/commands/completion.rs b/cmd/soroban-cli/src/commands/completion.rs new file mode 100644 index 00000000..f64386b4 --- /dev/null +++ b/cmd/soroban-cli/src/commands/completion.rs @@ -0,0 +1,32 @@ +use clap::{arg, CommandFactory, Parser}; +use clap_complete::{generate, Shell}; +use std::io; + +use crate::commands::Root; + +pub const LONG_ABOUT: &str = "\ +Print shell completion code for the specified shell + +Ensure the completion package for your shell is installed, +e.g., bash-completion for bash. + +To enable autocomplete in the current bash shell, run: + source <(soroban completion --shell bash) + +To enable autocomplete permanently, run: + echo \"source <(soroban completion --shell bash)\" >> ~/.bashrc"; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// The shell type + #[arg(long, value_enum)] + shell: Shell, +} + +impl Cmd { + pub fn run(&self) { + let cmd = &mut Root::command(); + generate(self.shell, cmd, "soroban", &mut io::stdout()); + } +} diff --git a/cmd/soroban-cli/src/commands/config/locator.rs b/cmd/soroban-cli/src/commands/config/locator.rs new file mode 100644 index 00000000..2688b043 --- /dev/null +++ b/cmd/soroban-cli/src/commands/config/locator.rs @@ -0,0 +1,358 @@ +use clap::arg; +use serde::de::DeserializeOwned; +use std::{ + ffi::OsStr, + fmt::Display, + fs, io, + path::{Path, PathBuf}, + str::FromStr, +}; + +use crate::{utils::find_config_dir, Pwd}; + +use super::{network::Network, secret::Secret}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to find home directory")] + HomeDirNotFound, + #[error("Failed read current directory")] + CurrentDirNotFound, + #[error("Failed read current directory and no SOROBAN_CONFIG_HOME is set")] + NoConfigEnvVar, + #[error("Failed to create directory: {path:?}")] + DirCreationFailed { path: PathBuf }, + #[error( + "Failed to read secret's file: {path}.\nProbably need to use `soroban config identity add`" + )] + SecretFileRead { path: PathBuf }, + #[error( + "Failed to read network file: {path};\nProbably need to use `soroban config network add`" + )] + NetworkFileRead { path: PathBuf }, + #[error(transparent)] + Toml(#[from] toml::de::Error), + #[error("Seceret file failed to deserialize")] + Deserialization, + #[error("Failed to write identity file:{filepath}: {error}")] + IdCreationFailed { filepath: PathBuf, error: io::Error }, + #[error("Seceret file failed to deserialize")] + NetworkDeserialization, + #[error("Failed to write network file: {0}")] + NetworkCreationFailed(std::io::Error), + #[error("Error Identity directory is invalid: {name}")] + IdentityList { name: String }, + // #[error("Config file failed to deserialize")] + // CannotReadConfigFile, + #[error("Config file failed to serialize")] + ConfigSerialization, + // #[error("Config file failed write")] + // CannotWriteConfigFile, + #[error("XDG_CONFIG_HOME env variable is not a valid path. Got {0}")] + XdgConfigHome(String), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("Failed to remove {0}: {1}")] + ConfigRemoval(String, String), + #[error("Failed to find config {0} for {1}")] + ConfigMissing(String, String), + #[error(transparent)] + String(#[from] std::string::FromUtf8Error), + #[error(transparent)] + Secret(#[from] crate::commands::config::secret::Error), +} + +#[derive(Debug, clap::Args, Default, Clone)] +#[group(skip)] +pub struct Args { + /// Use global config + #[arg(long)] + pub global: bool, + + /// Location of config directory, default is "." + #[arg(long, help_heading = "TESTING_OPTIONS")] + pub config_dir: Option, +} + +pub enum Location { + Local(PathBuf), + Global(PathBuf), +} + +impl Display for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {:?}", + match self { + Location::Local(_) => "Local", + Location::Global(_) => "Global", + }, + self.as_ref().parent().unwrap().parent().unwrap() + ) + } +} + +impl AsRef for Location { + fn as_ref(&self) -> &Path { + match self { + Location::Local(p) | Location::Global(p) => p.as_path(), + } + } +} + +impl Location { + #[must_use] + pub fn wrap(&self, p: PathBuf) -> Self { + match self { + Location::Local(_) => Location::Local(p), + Location::Global(_) => Location::Global(p), + } + } +} + +impl Args { + pub fn config_dir(&self) -> Result { + if self.global { + global_config_path() + } else { + self.local_config() + } + } + + pub fn local_and_global(&self) -> Result<[Location; 2], Error> { + Ok([ + Location::Local(self.local_config()?), + Location::Global(global_config_path()?), + ]) + } + + pub fn local_config(&self) -> Result { + let pwd = self.current_dir()?; + Ok(find_config_dir(pwd.clone()).unwrap_or_else(|_| pwd.join(".soroban"))) + } + + pub fn current_dir(&self) -> Result { + self.config_dir.as_ref().map_or_else( + || std::env::current_dir().map_err(|_| Error::CurrentDirNotFound), + |pwd| Ok(pwd.clone()), + ) + } + + pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { + KeyType::Identity.write(name, secret, &self.config_dir()?) + } + + pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> { + KeyType::Network.write(name, network, &self.config_dir()?) + } + + pub fn list_identities(&self) -> Result, Error> { + Ok(KeyType::Identity + .list_paths(&self.local_and_global()?)? + .into_iter() + .map(|(name, _)| name) + .collect()) + } + + pub fn list_identities_long(&self) -> Result, Error> { + Ok(KeyType::Identity + .list_paths(&self.local_and_global()?) + .into_iter() + .flatten() + .map(|(name, location)| { + let path = match location { + Location::Local(path) | Location::Global(path) => path, + }; + (name, format!("{}", path.display())) + }) + .collect()) + } + + pub fn list_networks(&self) -> Result, Error> { + Ok(KeyType::Network + .list_paths(&self.local_and_global()?) + .into_iter() + .flatten() + .map(|x| x.0) + .collect()) + } + + pub fn list_networks_long(&self) -> Result, Error> { + Ok(KeyType::Network + .list_paths(&self.local_and_global()?) + .into_iter() + .flatten() + .filter_map(|(name, location)| { + Some(( + name, + KeyType::read_from_path::(location.as_ref()).ok()?, + location, + )) + }) + .collect::>()) + } + pub fn read_identity(&self, name: &str) -> Result { + KeyType::Identity.read_with_global(name, &self.local_config()?) + } + + pub fn read_network(&self, name: &str) -> Result { + let res = KeyType::Network.read_with_global(name, &self.local_config()?); + if let Err(Error::ConfigMissing(_, _)) = &res { + if name == "futurenet" { + let network = Network::futurenet(); + self.write_network(name, &network)?; + return Ok(network); + } + } + res + } + + pub fn remove_identity(&self, name: &str) -> Result<(), Error> { + KeyType::Identity.remove(name, &self.config_dir()?) + } + + pub fn remove_network(&self, name: &str) -> Result<(), Error> { + KeyType::Network.remove(name, &self.config_dir()?) + } +} + +fn ensure_directory(dir: PathBuf) -> Result { + let parent = dir.parent().ok_or(Error::HomeDirNotFound)?; + std::fs::create_dir_all(parent).map_err(|_| dir_creation_failed(parent))?; + Ok(dir) +} + +fn dir_creation_failed(p: &Path) -> Error { + Error::DirCreationFailed { + path: p.to_path_buf(), + } +} + +fn read_dir(dir: &Path) -> Result, Error> { + let contents = std::fs::read_dir(dir)?; + let mut res = vec![]; + for entry in contents.filter_map(Result::ok) { + let path = entry.path(); + if let Some("toml") = path.extension().and_then(OsStr::to_str) { + if let Some(os_str) = path.file_stem() { + res.push((os_str.to_string_lossy().trim().to_string(), path)); + } + } + } + res.sort(); + Ok(res) +} + +pub enum KeyType { + Identity, + Network, +} + +impl Display for KeyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + KeyType::Identity => "identity", + KeyType::Network => "network", + } + ) + } +} + +impl KeyType { + pub fn read(&self, key: &str, pwd: &Path) -> Result { + let path = self.path(pwd, key); + Self::read_from_path(&path) + } + + pub fn read_from_path(path: &Path) -> Result { + let data = fs::read(path).map_err(|_| Error::NetworkFileRead { + path: path.to_path_buf(), + })?; + let res = toml::from_slice(data.as_slice()); + Ok(res?) + } + + pub fn read_with_global(&self, key: &str, pwd: &Path) -> Result { + for path in [pwd, global_config_path()?.as_path()] { + match self.read(key, path) { + Ok(t) => return Ok(t), + _ => continue, + } + } + Err(Error::ConfigMissing(self.to_string(), key.to_string())) + } + + pub fn write( + &self, + key: &str, + value: &T, + pwd: &Path, + ) -> Result<(), Error> { + let filepath = ensure_directory(self.path(pwd, key))?; + let data = toml::to_string(value).map_err(|_| Error::ConfigSerialization)?; + std::fs::write(&filepath, data).map_err(|error| Error::IdCreationFailed { filepath, error }) + } + + fn root(&self, pwd: &Path) -> PathBuf { + pwd.join(self.to_string()) + } + + fn path(&self, pwd: &Path, key: &str) -> PathBuf { + let mut path = self.root(pwd).join(key); + path.set_extension("toml"); + path + } + + pub fn list_paths(&self, paths: &[Location]) -> Result, Error> { + Ok(paths + .iter() + .flat_map(|p| self.list(p).unwrap_or_default()) + .collect()) + } + + pub fn list(&self, pwd: &Location) -> Result, Error> { + let path = self.root(pwd.as_ref()); + if path.exists() { + let mut files = read_dir(&path)?; + files.sort(); + + Ok(files + .into_iter() + .map(|(name, p)| (name, pwd.wrap(p))) + .collect()) + } else { + Ok(vec![]) + } + } + + pub fn remove(&self, key: &str, pwd: &Path) -> Result<(), Error> { + let path = self.path(pwd, key); + if path.exists() { + std::fs::remove_file(&path) + .map_err(|_| Error::ConfigRemoval(self.to_string(), key.to_string())) + } else { + Ok(()) + } + } +} + +fn global_config_path() -> Result { + Ok(if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { + PathBuf::from_str(&config_home).map_err(|_| Error::XdgConfigHome(config_home))? + } else { + dirs::home_dir() + .ok_or(Error::HomeDirNotFound)? + .join(".config") + } + .join("soroban")) +} + +impl Pwd for Args { + fn set_pwd(&mut self, pwd: &Path) { + self.config_dir = Some(pwd.to_path_buf()); + } +} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs new file mode 100644 index 00000000..be76e77f --- /dev/null +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use clap::{arg, command, Parser}; +use serde::{Deserialize, Serialize}; + +use crate::Pwd; + +use self::{network::Network, secret::Secret}; + +use super::{keys, network}; + +pub mod locator; +pub mod secret; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Configure different networks. Depraecated, use `soroban network` instead. + #[command(subcommand)] + Network(network::Cmd), + /// Identity management. Deprecated, use `soroban keys` instead. + #[command(subcommand)] + Identity(keys::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Identity(#[from] keys::Error), + #[error(transparent)] + Network(#[from] network::Error), + #[error(transparent)] + Secret(#[from] secret::Error), + #[error(transparent)] + Config(#[from] locator::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Identity(identity) => identity.run().await?, + Cmd::Network(network) => network.run()?, + } + Ok(()) + } +} + +#[derive(Debug, clap::Args, Clone, Default)] +#[group(skip)] +pub struct Args { + #[command(flatten)] + pub network: network::Args, + + #[arg(long, visible_alias = "source", env = "SOROBAN_ACCOUNT")] + /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` + pub source_account: String, + + #[arg(long)] + /// If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` + pub hd_path: Option, + + #[command(flatten)] + pub locator: locator::Args, +} + +impl Args { + pub fn key_pair(&self) -> Result { + let key = self.account(&self.source_account)?; + Ok(key.key_pair(self.hd_path)?) + } + + pub fn account(&self, account_str: &str) -> Result { + if let Ok(secret) = self.locator.read_identity(account_str) { + Ok(secret) + } else { + Ok(account_str.parse::()?) + } + } + + pub fn get_network(&self) -> Result { + Ok(self.network.get(&self.locator)?) + } + + pub fn config_dir(&self) -> Result { + Ok(self.locator.config_dir()?) + } +} + +impl Pwd for Args { + fn set_pwd(&mut self, pwd: &std::path::Path) { + self.locator.set_pwd(pwd); + } +} + +#[derive(Default, Serialize, Deserialize)] +pub struct Config {} diff --git a/cmd/soroban-cli/src/commands/config/secret.rs b/cmd/soroban-cli/src/commands/config/secret.rs new file mode 100644 index 00000000..4684e2a8 --- /dev/null +++ b/cmd/soroban-cli/src/commands/config/secret.rs @@ -0,0 +1,143 @@ +use clap::arg; +use serde::{Deserialize, Serialize}; +use std::{io::Write, str::FromStr}; +use stellar_strkey::ed25519::{PrivateKey, PublicKey}; + +use crate::utils; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("invalid secret key")] + InvalidSecretKey, + // #[error("seed_phrase must be 12 words long, found {len}")] + // InvalidSeedPhrase { len: usize }, + #[error("seceret input error")] + PasswordRead, + #[error(transparent)] + Secret(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + SeedPhrase(#[from] sep5::error::Error), + #[error(transparent)] + Ed25519(#[from] ed25519_dalek::SignatureError), + #[error("Invalid address {0}")] + InvalidAddress(String), +} + +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Add using secret_key + /// Can provide with SOROBAN_SECRET_KEY + #[arg(long, conflicts_with = "seed_phrase")] + pub secret_key: bool, + /// Add using 12 word seed phrase to generate secret_key + #[arg(long, conflicts_with = "secret_key")] + pub seed_phrase: bool, +} + +impl Args { + pub fn read_secret(&self) -> Result { + if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { + Ok(Secret::SecretKey { secret_key }) + } else if self.secret_key { + println!("Type a secret key: "); + let secret_key = read_password()?; + let secret_key = PrivateKey::from_string(&secret_key) + .map_err(|_| Error::InvalidSecretKey)? + .to_string(); + Ok(Secret::SecretKey { secret_key }) + } else if self.seed_phrase { + println!("Type a 12 word seed phrase: "); + let seed_phrase = read_password()?; + let seed_phrase: Vec<&str> = seed_phrase.split_whitespace().collect(); + // if seed_phrase.len() != 12 { + // let len = seed_phrase.len(); + // return Err(Error::InvalidSeedPhrase { len }); + // } + Ok(Secret::SeedPhrase { + seed_phrase: seed_phrase + .into_iter() + .map(ToString::to_string) + .collect::>() + .join(" "), + }) + } else { + Err(Error::PasswordRead {}) + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Secret { + SecretKey { secret_key: String }, + SeedPhrase { seed_phrase: String }, +} + +impl FromStr for Secret { + type Err = Error; + + fn from_str(s: &str) -> Result { + if PrivateKey::from_string(s).is_ok() { + Ok(Secret::SecretKey { + secret_key: s.to_string(), + }) + } else if sep5::SeedPhrase::from_str(s).is_ok() { + Ok(Secret::SeedPhrase { + seed_phrase: s.to_string(), + }) + } else { + Err(Error::InvalidAddress(s.to_string())) + } + } +} + +impl From for Secret { + fn from(value: PrivateKey) -> Self { + Secret::SecretKey { + secret_key: value.to_string(), + } + } +} + +impl Secret { + pub fn private_key(&self, index: Option) -> Result { + Ok(match self { + Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?, + Secret::SeedPhrase { seed_phrase } => sep5::SeedPhrase::from_str(seed_phrase)? + .from_path_index(index.unwrap_or_default(), None)? + .private(), + }) + } + + pub fn public_key(&self, index: Option) -> Result { + let key = self.key_pair(index)?; + Ok(stellar_strkey::ed25519::PublicKey::from_payload( + key.verifying_key().as_bytes(), + )?) + } + + pub fn key_pair(&self, index: Option) -> Result { + Ok(utils::into_signing_key(&self.private_key(index)?)) + } + + pub fn from_seed(seed: Option<&str>) -> Result { + let seed_phrase = if let Some(seed) = seed.map(str::as_bytes) { + sep5::SeedPhrase::from_entropy(seed) + } else { + sep5::SeedPhrase::random(sep5::MnemonicType::Words12) + }? + .seed_phrase + .into_phrase(); + Ok(Secret::SeedPhrase { seed_phrase }) + } + + pub fn test_seed_phrase() -> Result { + Self::from_seed(Some("0000000000000000")) + } +} + +fn read_password() -> Result { + std::io::stdout().flush().map_err(|_| Error::PasswordRead)?; + rpassword::read_password().map_err(|_| Error::PasswordRead) +} diff --git a/cmd/soroban-cli/src/commands/contract/asset.rs b/cmd/soroban-cli/src/commands/contract/asset.rs new file mode 100644 index 00000000..ad7be020 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/asset.rs @@ -0,0 +1,27 @@ +use super::{deploy, id}; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Get Id of builtin Soroban Asset Contract. Deprecated, use `soroban contract id asset` instead + Id(id::asset::Cmd), + /// Deploy builtin Soroban Asset Contract + Deploy(deploy::asset::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Id(#[from] id::asset::Error), + #[error(transparent)] + Deploy(#[from] deploy::asset::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Id(id) => id.run()?, + Cmd::Deploy(asset) => asset.run().await?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/bindings.rs b/cmd/soroban-cli/src/commands/contract/bindings.rs new file mode 100644 index 00000000..1da94697 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/bindings.rs @@ -0,0 +1,38 @@ +pub mod json; +pub mod rust; +pub mod typescript; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Generate Json Bindings + Json(json::Cmd), + + /// Generate Rust bindings + Rust(rust::Cmd), + + /// Generate a TypeScript / JavaScript package + Typescript(typescript::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Json(#[from] json::Error), + + #[error(transparent)] + Rust(#[from] rust::Error), + + #[error(transparent)] + Typescript(#[from] typescript::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Json(json) => json.run()?, + Cmd::Rust(rust) => rust.run()?, + Cmd::Typescript(ts) => ts.run().await?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/json.rs b/cmd/soroban-cli/src/commands/contract/bindings/json.rs new file mode 100644 index 00000000..060f9064 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/bindings/json.rs @@ -0,0 +1,29 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; +use soroban_spec_json; + +use crate::wasm; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + wasm: wasm::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("generate json from file: {0}")] + GenerateJsonFromFile(soroban_spec_json::GenerateFromFileError), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let wasm_path_str = self.wasm.wasm.to_string_lossy(); + let json = soroban_spec_json::generate_from_file(&wasm_path_str, None) + .map_err(Error::GenerateJsonFromFile)?; + println!("{json}"); + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/rust.rs b/cmd/soroban-cli/src/commands/contract/bindings/rust.rs new file mode 100644 index 00000000..176732ec --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/bindings/rust.rs @@ -0,0 +1,39 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; +use soroban_spec_rust::{self, ToFormattedString}; + +use crate::wasm; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + wasm: wasm::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("generate rust from file: {0}")] + GenerateRustFromFile(soroban_spec_rust::GenerateFromFileError), + #[error("format rust error: {0}")] + FormatRust(String), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let wasm_path_str = self.wasm.wasm.to_string_lossy(); + let code = soroban_spec_rust::generate_from_file(&wasm_path_str, None) + .map_err(Error::GenerateRustFromFile)?; + match code.to_formatted_string() { + Ok(formatted) => { + println!("{formatted}"); + Ok(()) + } + Err(e) => { + println!("{code}"); + Err(Error::FormatRust(e.to_string())) + } + } + } +} diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs new file mode 100644 index 00000000..19c7eecd --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs @@ -0,0 +1,131 @@ +use std::{ffi::OsString, fmt::Debug, path::PathBuf}; + +use clap::{command, Parser}; +use soroban_spec_typescript::{self as typescript, boilerplate::Project}; + +use crate::wasm; +use crate::{ + commands::{ + config::locator, + contract::{self, fetch}, + network::{self, Network}, + }, + utils::contract_spec::{self, ContractSpec}, +}; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// Path to optional wasm binary + #[arg(long)] + pub wasm: Option, + /// Where to place generated project + #[arg(long)] + output_dir: PathBuf, + /// Whether to overwrite output directory if it already exists + #[arg(long)] + overwrite: bool, + /// The contract ID/address on the network + #[arg(long, visible_alias = "id")] + contract_id: String, + #[command(flatten)] + locator: locator::Args, + #[command(flatten)] + network: network::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("failed generate TS from file: {0}")] + GenerateTSFromFile(typescript::GenerateFromFileError), + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error("--output-dir cannot be a file: {0:?}")] + IsFile(PathBuf), + + #[error("--output-dir already exists and you did not specify --overwrite: {0:?}")] + OutputDirExists(PathBuf), + + #[error("--output-dir filepath not representable as utf-8: {0:?}")] + NotUtf8(OsString), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error(transparent)] + Locator(#[from] locator::Error), + #[error(transparent)] + Fetch(#[from] fetch::Error), + #[error(transparent)] + Spec(#[from] contract_spec::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[error("Failed to get file name from path: {0:?}")] + FailedToGetFileName(PathBuf), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let spec = if let Some(wasm) = &self.wasm { + let wasm: wasm::Args = wasm.into(); + wasm.parse()?.spec + } else { + let fetch = contract::fetch::Cmd { + contract_id: self.contract_id.clone(), + out_file: None, + locator: self.locator.clone(), + network: self.network.clone(), + }; + let bytes = fetch.get_bytes().await?; + ContractSpec::new(&bytes)?.spec + }; + if self.output_dir.is_file() { + return Err(Error::IsFile(self.output_dir.clone())); + } + if self.output_dir.exists() { + if self.overwrite { + std::fs::remove_dir_all(&self.output_dir)?; + } else { + return Err(Error::OutputDirExists(self.output_dir.clone())); + } + } + std::fs::create_dir_all(&self.output_dir)?; + let p: Project = self.output_dir.clone().try_into()?; + let Network { + rpc_url, + network_passphrase, + .. + } = self + .network + .get(&self.locator) + .ok() + .unwrap_or_else(Network::futurenet); + let absolute_path = self.output_dir.canonicalize()?; + let file_name = absolute_path + .file_name() + .ok_or_else(|| Error::FailedToGetFileName(absolute_path.clone()))?; + let contract_name = &file_name + .to_str() + .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; + p.init( + contract_name, + &self.contract_id, + &rpc_url, + &network_passphrase, + &spec, + )?; + std::process::Command::new("npm") + .arg("install") + .current_dir(&self.output_dir) + .spawn()? + .wait()?; + std::process::Command::new("npm") + .arg("run") + .arg("build") + .current_dir(&self.output_dir) + .spawn()? + .wait()?; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs new file mode 100644 index 00000000..ba17bd1b --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/build.rs @@ -0,0 +1,194 @@ +use clap::Parser; +use itertools::Itertools; +use std::{ + collections::HashSet, + env, + ffi::OsStr, + fmt::Debug, + fs, io, + path::Path, + process::{Command, ExitStatus, Stdio}, +}; + +use cargo_metadata::{Metadata, MetadataCommand, Package}; + +/// Build a contract from source +/// +/// Builds all crates that are referenced by the cargo manifest (Cargo.toml) +/// that have cdylib as their crate-type. Crates are built for the wasm32 +/// target. Unless configured otherwise, crates are built with their default +/// features and with their release profile. +/// +/// To view the commands that will be executed, without executing them, use the +/// --print-commands-only option. +#[derive(Parser, Debug, Clone)] +pub struct Cmd { + /// Path to Cargo.toml + #[arg(long, default_value = "Cargo.toml")] + pub manifest_path: std::path::PathBuf, + /// Package to build + /// + /// If omitted, all packages that build for crate-type cdylib are built. + #[arg(long)] + pub package: Option, + /// Build with the specified profile + #[arg(long, default_value = "release")] + pub profile: String, + /// Build with the list of features activated, space or comma separated + #[arg(long, help_heading = "Features")] + pub features: Option, + /// Build with the all features activated + #[arg( + long, + conflicts_with = "features", + conflicts_with = "no_default_features", + help_heading = "Features" + )] + pub all_features: bool, + /// Build with the default feature not activated + #[arg(long, help_heading = "Features")] + pub no_default_features: bool, + /// Directory to copy wasm files to + /// + /// If provided, wasm files can be found in the cargo target directory, and + /// the specified directory. + /// + /// If ommitted, wasm files are written only to the cargo target directory. + #[arg(long)] + pub out_dir: Option, + /// Print commands to build without executing them + #[arg(long, conflicts_with = "out_dir", help_heading = "Other")] + pub print_commands_only: bool, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Metadata(#[from] cargo_metadata::Error), + #[error(transparent)] + CargoCmd(io::Error), + #[error("exit status {0}")] + Exit(ExitStatus), + #[error("package {package} not found")] + PackageNotFound { package: String }, + #[error("creating out directory: {0}")] + CreatingOutDir(io::Error), + #[error("copying wasm file: {0}")] + CopyingWasmFile(io::Error), + #[error("getting the current directory: {0}")] + GettingCurrentDir(io::Error), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let working_dir = env::current_dir().map_err(Error::GettingCurrentDir)?; + + let metadata = self.metadata()?; + let packages = self.packages(&metadata); + let target_dir = &metadata.target_directory; + + if let Some(package) = &self.package { + if packages.is_empty() { + return Err(Error::PackageNotFound { + package: package.clone(), + }); + } + } + + for p in packages { + let mut cmd = Command::new("cargo"); + cmd.stdout(Stdio::piped()); + cmd.arg("rustc"); + let manifest_path = pathdiff::diff_paths(&p.manifest_path, &working_dir) + .unwrap_or(p.manifest_path.clone().into()); + cmd.arg(format!( + "--manifest-path={}", + manifest_path.to_string_lossy() + )); + cmd.arg("--crate-type=cdylib"); + cmd.arg("--target=wasm32-unknown-unknown"); + if self.profile == "release" { + cmd.arg("--release"); + } else { + cmd.arg(format!("--profile={}", self.profile)); + } + if self.all_features { + cmd.arg("--all-features"); + } + if self.no_default_features { + cmd.arg("--no-default-features"); + } + if let Some(features) = self.features() { + let requested: HashSet = features.iter().cloned().collect(); + let available = p.features.iter().map(|f| f.0).cloned().collect(); + let activate = requested.intersection(&available).join(","); + if !activate.is_empty() { + cmd.arg(format!("--features={activate}")); + } + } + let cmd_str = format!( + "cargo {}", + cmd.get_args().map(OsStr::to_string_lossy).join(" ") + ); + + if self.print_commands_only { + println!("{cmd_str}"); + } else { + eprintln!("{cmd_str}"); + let status = cmd.status().map_err(Error::CargoCmd)?; + if !status.success() { + return Err(Error::Exit(status)); + } + + if let Some(out_dir) = &self.out_dir { + fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?; + + let file = format!("{}.wasm", p.name.replace('-', "_")); + let target_file_path = Path::new(target_dir) + .join("wasm32-unknown-unknown") + .join(&self.profile) + .join(&file); + let out_file_path = Path::new(out_dir).join(&file); + fs::copy(target_file_path, out_file_path).map_err(Error::CopyingWasmFile)?; + } + } + } + + Ok(()) + } + + fn features(&self) -> Option> { + self.features + .as_ref() + .map(|f| f.split(&[',', ' ']).map(String::from).collect()) + } + + fn packages(&self, metadata: &Metadata) -> Vec { + metadata + .packages + .iter() + .filter(|p| + // Filter by the package name if one is provided. + self.package.is_none() || Some(&p.name) == self.package.as_ref()) + .filter(|p| { + // Filter crates by those that build to cdylib (wasm), unless a + // package is provided. + self.package.is_some() + || p.targets + .iter() + .any(|t| t.crate_types.iter().any(|c| c == "cdylib")) + }) + .cloned() + .collect() + } + + fn metadata(&self) -> Result { + let mut cmd = MetadataCommand::new(); + cmd.no_deps(); + cmd.manifest_path(&self.manifest_path); + // Do not configure features on the metadata command, because we are + // only collecting non-dependency metadata, features have no impact on + // the output. + cmd.exec() + } +} diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs new file mode 100644 index 00000000..9baf4459 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/deploy.rs @@ -0,0 +1,28 @@ +pub mod asset; +pub mod wasm; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Deploy builtin Soroban Asset Contract + Asset(asset::Cmd), + /// Deploy normal Wasm Contract + Wasm(wasm::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Asset(#[from] asset::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Asset(asset) => asset.run().await?, + Cmd::Wasm(wasm) => wasm.run().await?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs new file mode 100644 index 00000000..c10bf816 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -0,0 +1,155 @@ +use clap::{arg, command, Parser}; +use soroban_env_host::{ + xdr::{ + Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, + Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, + LedgerKeyContractData, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, Uint256, VecM, + }, + HostError, +}; +use std::convert::Infallible; +use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; + +use crate::{ + commands::config, + rpc::{Client, Error as SorobanRpcError}, + utils::{contract_id_hash_from_asset, parsing::parse_asset}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + // TODO: the Display impl of host errors is pretty user-unfriendly + // (it just calls Debug). I think we can do better than that + Host(#[from] HostError), + #[error("error parsing int: {0}")] + ParseIntError(#[from] ParseIntError), + #[error(transparent)] + Client(#[from] SorobanRpcError), + #[error("internal conversion error: {0}")] + TryFromSliceError(#[from] TryFromSliceError), + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + ParseAssetError(#[from] crate::utils::parsing::Error), +} + +impl From for Error { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" + #[arg(long)] + pub asset: String, + + #[command(flatten)] + pub config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + // Parse asset + let asset = parse_asset(&self.asset)?; + + let res_str = self.run_against_rpc_server(asset).await?; + println!("{res_str}"); + Ok(()) + } + + async fn run_against_rpc_server(&self, asset: Asset) -> Result { + let network = self.config.get_network()?; + let client = Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let key = self.config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + // TODO: use symbols for the method names (both here and in serve) + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + let network_passphrase = &network.network_passphrase; + let contract_id = contract_id_hash_from_asset(&asset, network_passphrase)?; + let tx = build_wrap_token_tx( + &asset, + &contract_id, + sequence + 1, + self.fee.fee, + network_passphrase, + &key, + )?; + + client + .prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None) + .await?; + + Ok(stellar_strkey::Contract(contract_id.0).to_string()) + } +} + +fn build_wrap_token_tx( + asset: &Asset, + contract_id: &Hash, + sequence: i64, + fee: u32, + _network_passphrase: &str, + key: &ed25519_dalek::SigningKey, +) -> Result { + let contract = ScAddress::Contract(contract_id.clone()); + let mut read_write = vec![ + ContractData(LedgerKeyContractData { + contract: contract.clone(), + key: ScVal::LedgerKeyContractInstance, + durability: ContractDataDurability::Persistent, + }), + ContractData(LedgerKeyContractData { + contract: contract.clone(), + key: ScVal::Vec(Some( + vec![ScVal::Symbol("Metadata".try_into().unwrap())].try_into()?, + )), + durability: ContractDataDurability::Persistent, + }), + ]; + if asset != &Asset::Native { + read_write.push(ContractData(LedgerKeyContractData { + contract, + key: ScVal::Vec(Some( + vec![ScVal::Symbol("Admin".try_into().unwrap())].try_into()?, + )), + durability: ContractDataDurability::Persistent, + })); + } + + let op = Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::CreateContract(CreateContractArgs { + contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), + executable: ContractExecutable::StellarAsset, + }), + auth: VecM::default(), + }), + }; + + Ok(Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee, + seq_num: SequenceNumber(sequence), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![op].try_into()?, + ext: TransactionExt::V0, + }) +} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs new file mode 100644 index 00000000..76c13017 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -0,0 +1,228 @@ +use std::array::TryFromSliceError; +use std::fmt::Debug; +use std::num::ParseIntError; + +use clap::{arg, command, Parser}; +use rand::Rng; +use soroban_env_host::{ + xdr::{ + AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, + CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, + MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, + SequenceNumber, Transaction, TransactionExt, Uint256, VecM, + }, + HostError, +}; + +use crate::commands::contract::{self, id::wasm::get_contract_id}; +use crate::{ + commands::{config, contract::install, HEADING_RPC}, + rpc::{self, Client}, + utils, wasm, +}; + +#[derive(Parser, Debug, Clone)] +#[command(group( + clap::ArgGroup::new("wasm_src") + .required(true) + .args(&["wasm", "wasm_hash"]), +))] +#[group(skip)] +pub struct Cmd { + /// WASM file to deploy + #[arg(long, group = "wasm_src")] + wasm: Option, + /// Hash of the already installed/deployed WASM file + #[arg(long = "wasm-hash", conflicts_with = "wasm", group = "wasm_src")] + wasm_hash: Option, + /// Custom salt 32-byte salt for the token id + #[arg( + long, + help_heading = HEADING_RPC, + )] + salt: Option, + #[command(flatten)] + config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, + #[arg(long, short = 'i', default_value = "false")] + /// Whether to ignore safety checks when deploying contracts + pub ignore_checks: bool, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Install(#[from] install::Error), + #[error(transparent)] + Host(#[from] HostError), + #[error("error parsing int: {0}")] + ParseIntError(#[from] ParseIntError), + #[error("internal conversion error: {0}")] + TryFromSliceError(#[from] TryFromSliceError), + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error("jsonrpc error: {0}")] + JsonRpc(#[from] jsonrpsee_core::Error), + #[error("cannot parse salt: {salt}")] + CannotParseSalt { salt: String }, + #[error("cannot parse contract ID {contract_id}: {error}")] + CannotParseContractId { + contract_id: String, + error: stellar_strkey::DecodeError, + }, + #[error("cannot parse WASM hash {wasm_hash}: {error}")] + CannotParseWasmHash { + wasm_hash: String, + error: stellar_strkey::DecodeError, + }, + #[error("Must provide either --wasm or --wash-hash")] + WasmNotProvided, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + Infallible(#[from] std::convert::Infallible), + #[error(transparent)] + WasmId(#[from] contract::id::wasm::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res_str = self.run_and_get_contract_id().await?; + println!("{res_str}"); + Ok(()) + } + + pub async fn run_and_get_contract_id(&self) -> Result { + let wasm_hash = if let Some(wasm) = &self.wasm { + let hash = install::Cmd { + wasm: wasm::Args { wasm: wasm.clone() }, + config: self.config.clone(), + fee: self.fee.clone(), + ignore_checks: self.ignore_checks, + } + .run_and_get_hash() + .await?; + hex::encode(hash) + } else { + self.wasm_hash + .as_ref() + .ok_or(Error::WasmNotProvided)? + .to_string() + }; + + let hash = Hash(utils::contract_id_from_str(&wasm_hash).map_err(|e| { + Error::CannotParseWasmHash { + wasm_hash: wasm_hash.clone(), + error: e, + } + })?); + + self.run_against_rpc_server(hash).await + } + + async fn run_against_rpc_server(&self, wasm_hash: Hash) -> Result { + let network = self.config.get_network()?; + let salt: [u8; 32] = match &self.salt { + Some(h) => soroban_spec_tools::utils::padded_hex_from_str(h, 32) + .map_err(|_| Error::CannotParseSalt { salt: h.clone() })? + .try_into() + .map_err(|_| Error::CannotParseSalt { salt: h.clone() })?, + None => rand::thread_rng().gen::<[u8; 32]>(), + }; + + let client = Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let key = self.config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + let (tx, contract_id) = build_create_contract_tx( + wasm_hash, + sequence + 1, + self.fee.fee, + &network.network_passphrase, + salt, + &key, + )?; + client + .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + .await?; + Ok(stellar_strkey::Contract(contract_id.0).to_string()) + } +} + +fn build_create_contract_tx( + hash: Hash, + sequence: i64, + fee: u32, + network_passphrase: &str, + salt: [u8; 32], + key: &ed25519_dalek::SigningKey, +) -> Result<(Transaction, Hash), Error> { + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519( + key.verifying_key().to_bytes().into(), + )); + + let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ScAddress::Account(source_account), + salt: Uint256(salt), + }); + let contract_id = get_contract_id(contract_id_preimage.clone(), network_passphrase)?; + + let op = Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::CreateContract(CreateContractArgs { + contract_id_preimage, + executable: ContractExecutable::Wasm(hash), + }), + auth: VecM::default(), + }), + }; + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee, + seq_num: SequenceNumber(sequence), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![op].try_into()?, + ext: TransactionExt::V0, + }; + + Ok((tx, Hash(contract_id.into()))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_create_contract() { + let hash = hex::decode("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .try_into() + .unwrap(); + let result = build_create_contract_tx( + Hash(hash), + 300, + 1, + "Public Global Stellar Network ; September 2015", + [0u8; 32], + &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") + .unwrap(), + ); + + assert!(result.is_ok()); + } +} diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs new file mode 100644 index 00000000..7e9f1e98 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -0,0 +1,192 @@ +use std::{fmt::Debug, path::Path, str::FromStr}; + +use clap::{command, Parser}; +use soroban_env_host::xdr::{ + Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange, + LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, + TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, +}; + +use crate::{ + commands::config, + key, + rpc::{self, Client}, + wasm, Pwd, +}; + +const MAX_LEDGERS_TO_EXTEND: u32 = 535_679; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// Number of ledgers to extend the entries + #[arg(long, required = true)] + pub ledgers_to_extend: u32, + /// Only print the new Time To Live ledger + #[arg(long)] + pub ttl_ledger_only: bool, + #[command(flatten)] + pub key: key::Args, + #[command(flatten)] + pub config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, +} + +impl FromStr for Cmd { + type Err = clap::error::Error; + + fn from_str(s: &str) -> Result { + use clap::{CommandFactory, FromArgMatches}; + Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) + } +} + +impl Pwd for Cmd { + fn set_pwd(&mut self, pwd: &Path) { + self.config.set_pwd(pwd); + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing key {key}: {error}")] + CannotParseKey { + key: String, + error: soroban_spec_tools::Error, + }, + #[error("parsing XDR key {key}: {error}")] + CannotParseXdrKey { key: String, error: XdrError }, + + #[error(transparent)] + Config(#[from] config::Error), + #[error("either `--key` or `--key-xdr` are required")] + KeyIsRequired, + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error("Ledger entry not found")] + LedgerEntryNotFound, + #[error("missing operation result")] + MissingOperationResult, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[error(transparent)] + Key(#[from] key::Error), +} + +impl Cmd { + #[allow(clippy::too_many_lines)] + pub async fn run(&self) -> Result<(), Error> { + let ttl_ledger = self.run_against_rpc_server().await?; + if self.ttl_ledger_only { + println!("{ttl_ledger}"); + } else { + println!("New ttl ledger: {ttl_ledger}"); + } + + Ok(()) + } + + fn ledgers_to_extend(&self) -> u32 { + let res = u32::min(self.ledgers_to_extend, MAX_LEDGERS_TO_EXTEND); + if res < self.ledgers_to_extend { + tracing::warn!( + "Ledgers to extend is too large, using max value of {MAX_LEDGERS_TO_EXTEND}" + ); + } + res + } + + async fn run_against_rpc_server(&self) -> Result { + let network = self.config.get_network()?; + tracing::trace!(?network); + let keys = self.key.parse_keys()?; + let network = &self.config.get_network()?; + let client = Client::new(&network.rpc_url)?; + let key = self.config.key_pair()?; + let extend_to = self.ledgers_to_extend(); + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee: self.fee.fee, + seq_num: SequenceNumber(sequence + 1), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::ExtendFootprintTtl(ExtendFootprintTtlOp { + ext: ExtensionPoint::V0, + extend_to, + }), + }] + .try_into()?, + ext: TransactionExt::V1(SorobanTransactionData { + ext: ExtensionPoint::V0, + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: keys.clone().try_into()?, + read_write: vec![].try_into()?, + }, + instructions: 0, + read_bytes: 0, + write_bytes: 0, + }, + resource_fee: 0, + }), + }; + + let (result, meta, events) = client + .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + .await?; + + tracing::trace!(?result); + tracing::trace!(?meta); + if !events.is_empty() { + tracing::info!("Events:\n {events:#?}"); + } + + // The transaction from core will succeed regardless of whether it actually found & extended + // the entry, so we have to inspect the result meta to tell if it worked or not. + let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { + return Err(Error::LedgerEntryNotFound); + }; + + // Simply check if there is exactly one entry here. We only support extending a single + // entry via this command (which we should fix separately, but). + if operations.len() == 0 { + return Err(Error::LedgerEntryNotFound); + } + + if operations[0].changes.is_empty() { + let entry = client.get_full_ledger_entries(&keys).await?; + let extension = entry.entries[0].live_until_ledger_seq; + if entry.latest_ledger + i64::from(extend_to) < i64::from(extension) { + return Ok(extension); + } + } + + match (&operations[0].changes[0], &operations[0].changes[1]) { + ( + LedgerEntryChange::State(_), + LedgerEntryChange::Updated(LedgerEntry { + data: + LedgerEntryData::Ttl(TtlEntry { + live_until_ledger_seq, + .. + }), + .. + }), + ) => Ok(*live_until_ledger_seq), + _ => Err(Error::LedgerEntryNotFound), + } + } +} diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs new file mode 100644 index 00000000..61a82fc4 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -0,0 +1,185 @@ +use std::convert::Infallible; + +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::{fmt::Debug, fs, io}; + +use clap::{arg, command, Parser}; +use soroban_env_host::{ + budget::Budget, + storage::Storage, + xdr::{ + self, ContractCodeEntry, ContractDataDurability, ContractDataEntry, ContractExecutable, + Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractCode, + LedgerKeyContractData, ScAddress, ScContractInstance, ScVal, + }, +}; + +use soroban_spec::read::FromWasmError; +use stellar_strkey::DecodeError; + +use super::super::config::{self, locator}; +use crate::commands::network::{self, Network}; +use crate::{ + rpc::{self, Client}, + utils, Pwd, +}; + +#[derive(Parser, Debug, Default, Clone)] +#[allow(clippy::struct_excessive_bools)] +#[group(skip)] +pub struct Cmd { + /// Contract ID to fetch + #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] + pub contract_id: String, + /// Where to write output otherwise stdout is used + #[arg(long, short = 'o')] + pub out_file: Option, + #[command(flatten)] + pub locator: locator::Args, + #[command(flatten)] + pub network: network::Args, +} + +impl FromStr for Cmd { + type Err = clap::error::Error; + + fn from_str(s: &str) -> Result { + use clap::{CommandFactory, FromArgMatches}; + Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) + } +} + +impl Pwd for Cmd { + fn set_pwd(&mut self, pwd: &Path) { + self.locator.set_pwd(pwd); + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + Locator(#[from] locator::Error), + #[error(transparent)] + Xdr(#[from] XdrError), + #[error(transparent)] + Spec(#[from] soroban_spec::read::FromWasmError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("missing result")] + MissingResult, + #[error("unexpected contract code data type: {0:?}")] + UnexpectedContractCodeDataType(LedgerEntryData), + #[error("reading file {0:?}: {1}")] + CannotWriteContractFile(PathBuf, io::Error), + #[error("cannot parse contract ID {0}: {1}")] + CannotParseContractId(String, DecodeError), + #[error("network details not provided")] + NetworkNotProvided, + #[error(transparent)] + Network(#[from] network::Error), + #[error("cannot create contract directory for {0:?}")] + CannotCreateContractDir(PathBuf), +} + +impl From for Error { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let bytes = self.get_bytes().await?; + if let Some(out_file) = &self.out_file { + if let Some(parent) = out_file.parent() { + if !parent.exists() { + fs::create_dir_all(parent) + .map_err(|_| Error::CannotCreateContractDir(out_file.clone()))?; + } + } + fs::write(out_file, bytes) + .map_err(|io| Error::CannotWriteContractFile(out_file.clone(), io)) + } else { + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); + handle.write_all(&bytes)?; + handle.flush()?; + Ok(()) + } + } + + pub async fn get_bytes(&self) -> Result, Error> { + self.run_against_rpc_server().await + } + + pub fn network(&self) -> Result { + Ok(self.network.get(&self.locator)?) + } + + pub async fn run_against_rpc_server(&self) -> Result, Error> { + let network = self.network()?; + tracing::trace!(?network); + let contract_id = self.contract_id()?; + let client = Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + // async closures are not yet stable + Ok(client.get_remote_wasm(&contract_id).await?) + } + + fn contract_id(&self) -> Result<[u8; 32], Error> { + utils::contract_id_from_str(&self.contract_id) + .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) + } +} + +pub fn get_contract_wasm_from_storage( + storage: &mut Storage, + contract_id: [u8; 32], +) -> Result, FromWasmError> { + let key = LedgerKey::ContractData(LedgerKeyContractData { + contract: ScAddress::Contract(contract_id.into()), + key: ScVal::LedgerKeyContractInstance, + durability: ContractDataDurability::Persistent, + }); + match storage.get(&key.into(), &Budget::default()) { + Ok(rc) => match rc.as_ref() { + xdr::LedgerEntry { + data: + LedgerEntryData::ContractData(ContractDataEntry { + val: ScVal::ContractInstance(ScContractInstance { executable, .. }), + .. + }), + .. + } => match executable { + ContractExecutable::Wasm(hash) => { + if let Ok(rc) = storage.get( + &LedgerKey::ContractCode(LedgerKeyContractCode { hash: hash.clone() }) + .into(), + &Budget::default(), + ) { + match rc.as_ref() { + xdr::LedgerEntry { + data: LedgerEntryData::ContractCode(ContractCodeEntry { code, .. }), + .. + } => Ok(code.to_vec()), + _ => Err(FromWasmError::NotFound), + } + } else { + Err(FromWasmError::NotFound) + } + } + ContractExecutable::StellarAsset => todo!(), + }, + _ => Err(FromWasmError::NotFound), + }, + _ => Err(FromWasmError::NotFound), + } +} diff --git a/cmd/soroban-cli/src/commands/contract/id.rs b/cmd/soroban-cli/src/commands/contract/id.rs new file mode 100644 index 00000000..bb8744d5 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/id.rs @@ -0,0 +1,28 @@ +pub mod asset; +pub mod wasm; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Deploy builtin Soroban Asset Contract + Asset(asset::Cmd), + /// Deploy normal Wasm Contract + Wasm(wasm::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Asset(#[from] asset::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Asset(asset) => asset.run()?, + Cmd::Wasm(wasm) => wasm.run()?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs new file mode 100644 index 00000000..34e5767a --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -0,0 +1,36 @@ +use clap::{arg, command, Parser}; + +use crate::commands::config; + +use crate::utils::contract_id_hash_from_asset; +use crate::utils::parsing::parse_asset; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" + #[arg(long)] + pub asset: String, + + #[command(flatten)] + pub config: config::Args, +} +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + ParseError(#[from] crate::utils::parsing::Error), + #[error(transparent)] + ConfigError(#[from] crate::commands::config::Error), + #[error(transparent)] + Xdr(#[from] soroban_env_host::xdr::Error), +} +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let asset = parse_asset(&self.asset)?; + let network = self.config.get_network()?; + let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; + let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); + println!("{strkey_contract_id}"); + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs new file mode 100644 index 00000000..9c02f07d --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/id/wasm.rs @@ -0,0 +1,68 @@ +use clap::{arg, command, Parser}; +use sha2::{Digest, Sha256}; +use soroban_env_host::xdr::{ + self, AccountId, ContractIdPreimage, ContractIdPreimageFromAddress, Hash, HashIdPreimage, + HashIdPreimageContractId, Limits, PublicKey, ScAddress, Uint256, WriteXdr, +}; + +use crate::commands::config; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// ID of the Soroban contract + #[arg(long)] + pub salt: String, + + #[command(flatten)] + pub config: config::Args, +} +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + ParseError(#[from] crate::utils::parsing::Error), + #[error(transparent)] + ConfigError(#[from] crate::commands::config::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("cannot parse salt {0}")] + CannotParseSalt(String), +} +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let salt: [u8; 32] = soroban_spec_tools::utils::padded_hex_from_str(&self.salt, 32) + .map_err(|_| Error::CannotParseSalt(self.salt.clone()))? + .try_into() + .map_err(|_| Error::CannotParseSalt(self.salt.clone()))?; + let contract_id_preimage = + contract_preimage(&self.config.key_pair()?.verifying_key(), salt); + let contract_id = get_contract_id( + contract_id_preimage.clone(), + &self.config.get_network()?.network_passphrase, + )?; + let strkey_contract_id = stellar_strkey::Contract(contract_id.0).to_string(); + println!("{strkey_contract_id}"); + Ok(()) + } +} + +pub fn contract_preimage(key: &ed25519_dalek::VerifyingKey, salt: [u8; 32]) -> ContractIdPreimage { + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.to_bytes().into())); + ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ScAddress::Account(source_account), + salt: Uint256(salt), + }) +} + +pub fn get_contract_id( + contract_id_preimage: ContractIdPreimage, + network_passphrase: &str, +) -> Result { + let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); + let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { + network_id, + contract_id_preimage, + }); + let preimage_xdr = preimage.to_xdr(Limits::none())?; + Ok(Hash(Sha256::digest(preimage_xdr).into())) +} diff --git a/cmd/soroban-cli/src/commands/contract/inspect.rs b/cmd/soroban-cli/src/commands/contract/inspect.rs new file mode 100644 index 00000000..355c18ca --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/inspect.rs @@ -0,0 +1,49 @@ +use clap::{command, Parser}; +use soroban_env_host::xdr; +use std::{fmt::Debug, path::PathBuf}; +use tracing::debug; + +use super::SpecOutput; +use crate::{commands::config::locator, wasm}; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + wasm: wasm::Args, + /// Output just XDR in base64 + #[arg(long, default_value = "docs")] + output: SpecOutput, + + #[clap(flatten)] + locator: locator::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[error("missing spec for {0:?}")] + MissingSpec(PathBuf), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Spec(#[from] crate::utils::contract_spec::Error), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let wasm = self.wasm.parse()?; + debug!("File: {}", self.wasm.wasm.to_string_lossy()); + let output = match self.output { + SpecOutput::XdrBase64 => wasm + .spec_base64 + .clone() + .ok_or_else(|| Error::MissingSpec(self.wasm.wasm.clone()))?, + SpecOutput::XdrBase64Array => wasm.spec_as_json_array()?, + SpecOutput::Docs => wasm.to_string(), + }; + println!("{output}"); + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs new file mode 100644 index 00000000..90577529 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -0,0 +1,223 @@ +use std::array::TryFromSliceError; +use std::fmt::Debug; +use std::num::ParseIntError; + +use clap::{command, Parser}; +use soroban_env_host::xdr::{ + Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Memo, MuxedAccount, Operation, + OperationBody, Preconditions, ScMetaEntry, ScMetaV0, SequenceNumber, Transaction, + TransactionExt, TransactionResult, TransactionResultResult, Uint256, VecM, +}; + +use super::restore; +use crate::key; +use crate::rpc::{self, Client}; +use crate::{commands::config, utils, wasm}; + +const CONTRACT_META_SDK_KEY: &str = "rssdkver"; +const PUBLIC_NETWORK_PASSPHRASE: &str = "Public Global Stellar Network ; September 2015"; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, + #[command(flatten)] + pub wasm: wasm::Args, + #[arg(long, short = 'i', default_value = "false")] + /// Whether to ignore safety checks when deploying contracts + pub ignore_checks: bool, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("error parsing int: {0}")] + ParseIntError(#[from] ParseIntError), + #[error("internal conversion error: {0}")] + TryFromSliceError(#[from] TryFromSliceError), + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error("jsonrpc error: {0}")] + JsonRpc(#[from] jsonrpsee_core::Error), + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[error("unexpected ({length}) simulate transaction result length")] + UnexpectedSimulateTransactionResultSize { length: usize }, + #[error(transparent)] + Restore(#[from] restore::Error), + #[error("cannot parse WASM file {wasm}: {error}")] + CannotParseWasm { + wasm: std::path::PathBuf, + error: wasm::Error, + }, + #[error("the deployed smart contract {wasm} was built with Soroban Rust SDK v{version}, a release candidate version not intended for use with the Stellar Public Network. To deploy anyway, use --ignore-checks")] + ContractCompiledWithReleaseCandidateSdk { + wasm: std::path::PathBuf, + version: String, + }, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let res_str = hex::encode(self.run_and_get_hash().await?); + println!("{res_str}"); + Ok(()) + } + + pub async fn run_and_get_hash(&self) -> Result { + self.run_against_rpc_server(&self.wasm.read()?).await + } + + async fn run_against_rpc_server(&self, contract: &[u8]) -> Result { + let network = self.config.get_network()?; + let client = Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let wasm_spec = &self.wasm.parse().map_err(|e| Error::CannotParseWasm { + wasm: self.wasm.wasm.clone(), + error: e, + })?; + // Check Rust SDK version if using the public network. + if let Some(rs_sdk_ver) = get_contract_meta_sdk_version(wasm_spec) { + if rs_sdk_ver.contains("rc") + && !self.ignore_checks + && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE + { + return Err(Error::ContractCompiledWithReleaseCandidateSdk { + wasm: self.wasm.wasm.clone(), + version: rs_sdk_ver, + }); + } else if rs_sdk_ver.contains("rc") + && network.network_passphrase == PUBLIC_NETWORK_PASSPHRASE + { + tracing::warn!("the deployed smart contract {path} was built with Soroban Rust SDK v{rs_sdk_ver}, a release candidate version not intended for use with the Stellar Public Network", path = self.wasm.wasm.display()); + } + } + let key = self.config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + + let (tx_without_preflight, hash) = + build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?; + + // Currently internal errors are not returned if the contract code is expired + if let ( + TransactionResult { + result: TransactionResultResult::TxInternalError, + .. + }, + _, + _, + ) = client + .prepare_and_send_transaction( + &tx_without_preflight, + &key, + &[], + &network.network_passphrase, + None, + None, + ) + .await? + { + // Now just need to restore it and don't have to install again + restore::Cmd { + key: key::Args { + contract_id: None, + key: None, + key_xdr: None, + wasm: Some(self.wasm.wasm.clone()), + wasm_hash: None, + durability: super::Durability::Persistent, + }, + config: self.config.clone(), + fee: self.fee.clone(), + ledgers_to_extend: None, + ttl_ledger_only: true, + } + .run_against_rpc_server() + .await?; + } + + Ok(hash) + } +} + +fn get_contract_meta_sdk_version(wasm_spec: &utils::contract_spec::ContractSpec) -> Option { + let rs_sdk_version_option = if let Some(_meta) = &wasm_spec.meta_base64 { + wasm_spec.meta.iter().find(|entry| match entry { + ScMetaEntry::ScMetaV0(ScMetaV0 { key, .. }) => { + key.to_utf8_string_lossy().contains(CONTRACT_META_SDK_KEY) + } + }) + } else { + None + }; + if let Some(rs_sdk_version_entry) = &rs_sdk_version_option { + match rs_sdk_version_entry { + ScMetaEntry::ScMetaV0(ScMetaV0 { val, .. }) => { + return Some(val.to_utf8_string_lossy()); + } + } + } + None +} + +pub(crate) fn build_install_contract_code_tx( + source_code: &[u8], + sequence: i64, + fee: u32, + key: &ed25519_dalek::SigningKey, +) -> Result<(Transaction, Hash), XdrError> { + let hash = utils::contract_hash(source_code)?; + + let op = Operation { + source_account: Some(MuxedAccount::Ed25519(Uint256( + key.verifying_key().to_bytes(), + ))), + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::UploadContractWasm(source_code.try_into()?), + auth: VecM::default(), + }), + }; + + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee, + seq_num: SequenceNumber(sequence), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![op].try_into()?, + ext: TransactionExt::V0, + }; + + Ok((tx, hash)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_install_contract_code() { + let result = build_install_contract_code_tx( + b"foo", + 300, + 1, + &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") + .unwrap(), + ); + + assert!(result.is_ok()); + } +} diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs new file mode 100644 index 00000000..669342b0 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -0,0 +1,484 @@ +use std::collections::HashMap; +use std::convert::{Infallible, TryInto}; +use std::ffi::OsString; +use std::num::ParseIntError; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::{fmt::Debug, fs, io}; + +use clap::{arg, command, value_parser, Parser}; +use ed25519_dalek::SigningKey; +use heck::ToKebabCase; + +use soroban_env_host::{ + xdr::{ + self, Error as XdrError, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, + LedgerEntryData, LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, + Preconditions, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, + SequenceNumber, SorobanAuthorizationEntry, SorobanResources, Transaction, TransactionExt, + Uint256, VecM, + }, + HostError, +}; + +use soroban_spec::read::FromWasmError; +use stellar_strkey::DecodeError; + +use super::super::{ + config::{self, locator}, + events, +}; +use crate::{ + commands::global, + rpc::{self, Client}, + utils::{self, contract_spec}, + Pwd, +}; +use soroban_spec_tools::Spec; + +#[derive(Parser, Debug, Default, Clone)] +#[allow(clippy::struct_excessive_bools)] +#[group(skip)] +pub struct Cmd { + /// Contract ID to invoke + #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] + pub contract_id: String, + // For testing only + #[arg(skip)] + pub wasm: Option, + /// Output the cost execution to stderr + #[arg(long = "cost")] + pub cost: bool, + /// Function name as subcommand, then arguments for that function as `--arg-name value` + #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] + pub slop: Vec, + #[command(flatten)] + pub config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, +} + +impl FromStr for Cmd { + type Err = clap::error::Error; + + fn from_str(s: &str) -> Result { + use clap::{CommandFactory, FromArgMatches}; + Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) + } +} + +impl Pwd for Cmd { + fn set_pwd(&mut self, pwd: &Path) { + self.config.set_pwd(pwd); + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing argument {arg}: {error}")] + CannotParseArg { + arg: String, + error: soroban_spec_tools::Error, + }, + #[error("cannot add contract to ledger entries: {0}")] + CannotAddContractToLedgerEntries(XdrError), + #[error(transparent)] + // TODO: the Display impl of host errors is pretty user-unfriendly + // (it just calls Debug). I think we can do better than that + Host(#[from] HostError), + #[error("reading file {0:?}: {1}")] + CannotReadContractFile(PathBuf, io::Error), + #[error("committing file {filepath}: {error}")] + CannotCommitEventsFile { + filepath: std::path::PathBuf, + error: events::Error, + }, + #[error("cannot parse contract ID {0}: {1}")] + CannotParseContractId(String, DecodeError), + #[error("function {0} was not found in the contract")] + FunctionNotFoundInContractSpec(String), + #[error("parsing contract spec: {0}")] + CannotParseContractSpec(FromWasmError), + // }, + #[error("function name {0} is too long")] + FunctionNameTooLong(String), + #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] + MaxNumberOfArgumentsReached { current: usize, maximum: usize }, + #[error("cannot print result {result:?}: {error}")] + CannotPrintResult { + result: ScVal, + error: soroban_spec_tools::Error, + }, + #[error(transparent)] + Xdr(#[from] XdrError), + #[error("error parsing int: {0}")] + ParseIntError(#[from] ParseIntError), + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error("unexpected contract code data type: {0:?}")] + UnexpectedContractCodeDataType(LedgerEntryData), + #[error("missing operation result")] + MissingOperationResult, + #[error("missing result")] + MissingResult, + #[error(transparent)] + StrVal(#[from] soroban_spec_tools::Error), + #[error("error loading signing key: {0}")] + SignatureError(#[from] ed25519_dalek::SignatureError), + #[error(transparent)] + Config(#[from] config::Error), + #[error("unexpected ({length}) simulate transaction result length")] + UnexpectedSimulateTransactionResultSize { length: usize }, + #[error("Missing argument {0}")] + MissingArgument(String), + #[error(transparent)] + Clap(#[from] clap::Error), + #[error(transparent)] + Locator(#[from] locator::Error), + #[error("Contract Error\n{0}: {1}")] + ContractInvoke(String, String), + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), + #[error(transparent)] + ContractSpec(#[from] contract_spec::Error), + #[error("")] + MissingFileArg(PathBuf), +} + +impl From for Error { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +impl Cmd { + fn build_host_function_parameters( + &self, + contract_id: [u8; 32], + spec_entries: &[ScSpecEntry], + ) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { + let spec = Spec(Some(spec_entries.to_vec())); + let mut cmd = clap::Command::new(self.contract_id.clone()) + .no_binary_name(true) + .term_width(300) + .max_term_width(300); + + for ScSpecFunctionV0 { name, .. } in spec.find_functions()? { + cmd = cmd.subcommand(build_custom_cmd(&name.to_utf8_string_lossy(), &spec)?); + } + cmd.build(); + let long_help = cmd.render_long_help(); + let mut matches_ = cmd.get_matches_from(&self.slop); + let Some((function, matches_)) = &matches_.remove_subcommand() else { + println!("{long_help}"); + std::process::exit(1); + }; + + let func = spec.find_function(function)?; + // create parsed_args in same order as the inputs to func + let mut signers: Vec = vec![]; + let parsed_args = func + .inputs + .iter() + .map(|i| { + let name = i.name.to_utf8_string()?; + if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(i.type_, ScSpecTypeDef::Address) { + let cmd = crate::commands::keys::address::Cmd { + name: s.clone(), + hd_path: Some(0), + locator: self.config.locator.clone(), + }; + if let Ok(address) = cmd.public_key() { + s = address.to_string(); + } + if let Ok(key) = cmd.private_key() { + signers.push(key); + } + } + spec.from_string(&s, &i.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error }) + } else if matches!(i.type_, ScSpecTypeDef::Option(_)) { + Ok(ScVal::Void) + } else if let Some(arg_path) = + matches_.get_one::(&fmt_arg_file_name(&name)) + { + if matches!(i.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + Ok(ScVal::try_from( + &std::fs::read(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?, + ) + .map_err(|()| Error::CannotParseArg { + arg: name.clone(), + error: soroban_spec_tools::Error::Unknown, + })?) + } else { + let file_contents = std::fs::read_to_string(arg_path) + .map_err(|_| Error::MissingFileArg(arg_path.clone()))?; + tracing::debug!( + "file {arg_path:?}, has contents:\n{file_contents}\nAnd type {:#?}\n{}", + i.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &i.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error }) + } + } else { + Err(Error::MissingArgument(name)) + } + }) + .collect::, Error>>()?; + + let contract_address_arg = ScAddress::Contract(Hash(contract_id)); + let function_symbol_arg = function + .try_into() + .map_err(|()| Error::FunctionNameTooLong(function.clone()))?; + + let final_args = + parsed_args + .clone() + .try_into() + .map_err(|_| Error::MaxNumberOfArgumentsReached { + current: parsed_args.len(), + maximum: ScVec::default().max_len(), + })?; + + let invoke_args = InvokeContractArgs { + contract_address: contract_address_arg, + function_name: function_symbol_arg, + args: final_args, + }; + + Ok((function.clone(), spec, invoke_args, signers)) + } + + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let res = self.invoke(global_args).await?; + println!("{res}"); + Ok(()) + } + + pub async fn invoke(&self, global_args: &global::Args) -> Result { + self.run_against_rpc_server(global_args).await + } + + pub async fn run_against_rpc_server( + &self, + global_args: &global::Args, + ) -> Result { + let network = self.config.get_network()?; + tracing::trace!(?network); + let contract_id = self.contract_id()?; + let spec_entries = self.spec_entries()?; + if let Some(spec_entries) = &spec_entries { + // For testing wasm arg parsing + let _ = self.build_host_function_parameters(contract_id, spec_entries)?; + } + let client = Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + let key = self.config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + + // Get the contract + let spec_entries = client.get_remote_contract_spec(&contract_id).await?; + + // Get the ledger footprint + let (function, spec, host_function_params, signers) = + self.build_host_function_parameters(contract_id, &spec_entries)?; + let tx = build_invoke_contract_tx( + host_function_params.clone(), + sequence + 1, + self.fee.fee, + &key, + )?; + + let (result, meta, events) = client + .prepare_and_send_transaction( + &tx, + &key, + &signers, + &network.network_passphrase, + Some(log_events), + (global_args.verbose || global_args.very_verbose || self.cost) + .then_some(log_resources), + ) + .await?; + + tracing::debug!(?result); + crate::log::diagnostic_events(&events, tracing::Level::INFO); + let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 { + soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }), + .. + }) = meta + else { + return Err(Error::MissingOperationResult); + }; + + output_to_string(&spec, &return_value, &function) + } + + pub fn read_wasm(&self) -> Result>, Error> { + Ok(if let Some(wasm) = self.wasm.as_ref() { + Some(fs::read(wasm).map_err(|e| Error::CannotReadContractFile(wasm.clone(), e))?) + } else { + None + }) + } + + pub fn spec_entries(&self) -> Result>, Error> { + self.read_wasm()? + .map(|wasm| { + soroban_spec::read::from_wasm(&wasm).map_err(Error::CannotParseContractSpec) + }) + .transpose() + } +} + +impl Cmd { + fn contract_id(&self) -> Result<[u8; 32], Error> { + utils::contract_id_from_str(&self.contract_id) + .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) + } +} + +fn log_events( + footprint: &LedgerFootprint, + auth: &[VecM], + events: &[xdr::DiagnosticEvent], +) { + crate::log::auth(auth); + crate::log::diagnostic_events(events, tracing::Level::TRACE); + crate::log::footprint(footprint); +} + +fn log_resources(resources: &SorobanResources) { + crate::log::cost(resources); +} + +pub fn output_to_string(spec: &Spec, res: &ScVal, function: &str) -> Result { + let mut res_str = String::new(); + if let Some(output) = spec.find_function(function)?.outputs.first() { + res_str = spec + .xdr_to_json(res, output) + .map_err(|e| Error::CannotPrintResult { + result: res.clone(), + error: e, + })? + .to_string(); + } + Ok(res_str) +} + +fn build_invoke_contract_tx( + parameters: InvokeContractArgs, + sequence: i64, + fee: u32, + key: &SigningKey, +) -> Result { + let op = Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::InvokeContract(parameters), + auth: VecM::default(), + }), + }; + Ok(Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee, + seq_num: SequenceNumber(sequence), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![op].try_into()?, + ext: TransactionExt::V0, + }) +} + +fn build_custom_cmd(name: &str, spec: &Spec) -> Result { + let func = spec + .find_function(name) + .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; + + // Parse the function arguments + let inputs_map = &func + .inputs + .iter() + .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) + .collect::>(); + let name: &'static str = Box::leak(name.to_string().into_boxed_str()); + let mut cmd = clap::Command::new(name) + .no_binary_name(true) + .term_width(300) + .max_term_width(300); + let kebab_name = name.to_kebab_case(); + if kebab_name != name { + cmd = cmd.alias(kebab_name); + } + let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); + let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); + + cmd = cmd.about(Some(doc)).long_about(long_doc); + for (name, type_) in inputs_map { + let mut arg = clap::Arg::new(name); + let file_arg_name = fmt_arg_file_name(name); + let mut file_arg = clap::Arg::new(&file_arg_name); + arg = arg + .long(name) + .alias(name.to_kebab_case()) + .num_args(1) + .value_parser(clap::builder::NonEmptyStringValueParser::new()) + .long_help(spec.doc(name, type_)?); + + file_arg = file_arg + .long(&file_arg_name) + .alias(file_arg_name.to_kebab_case()) + .num_args(1) + .hide(true) + .value_parser(value_parser!(PathBuf)) + .conflicts_with(name); + + if let Some(value_name) = spec.arg_value_name(type_, 0) { + let value_name: &'static str = Box::leak(value_name.into_boxed_str()); + arg = arg.value_name(value_name); + } + + // Set up special-case arg rules + arg = match type_ { + xdr::ScSpecTypeDef::Bool => arg + .num_args(0..1) + .default_missing_value("true") + .default_value("false") + .num_args(0..=1), + xdr::ScSpecTypeDef::Option(_val) => arg.required(false), + xdr::ScSpecTypeDef::I256 + | xdr::ScSpecTypeDef::I128 + | xdr::ScSpecTypeDef::I64 + | xdr::ScSpecTypeDef::I32 => arg.allow_hyphen_values(true), + _ => arg, + }; + + cmd = cmd.arg(arg); + cmd = cmd.arg(file_arg); + } + Ok(cmd) +} + +fn fmt_arg_file_name(name: &str) -> String { + format!("{name}-file-path") +} + +fn arg_file_help(docs: &str) -> String { + format!( + r#"{docs} +Usage Notes: +Each arg has a corresponding ---file-path which is a path to a file containing the corresponding JSON argument. +Note: The only types which aren't JSON are Bytes and Bytes which are raw bytes"# + ) +} diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs new file mode 100644 index 00000000..35be97a7 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -0,0 +1,158 @@ +pub mod asset; +pub mod bindings; +pub mod build; +pub mod deploy; +pub mod extend; +pub mod fetch; +pub mod id; +pub mod inspect; +pub mod install; +pub mod invoke; +pub mod optimize; +pub mod read; +pub mod restore; + +use crate::commands::global; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Utilities to deploy a Stellar Asset Contract or get its id + #[command(subcommand)] + Asset(asset::Cmd), + /// Generate code client bindings for a contract + #[command(subcommand)] + Bindings(bindings::Cmd), + + Build(build::Cmd), + + /// Extend the time to live ledger of a contract-data ledger entry. + /// + /// If no keys are specified the contract itself is extended. + Extend(extend::Cmd), + + /// Deploy a wasm contract + Deploy(deploy::wasm::Cmd), + + /// Fetch a contract's Wasm binary + Fetch(fetch::Cmd), + + /// Generate the contract id for a given contract or asset + #[command(subcommand)] + Id(id::Cmd), + + /// Inspect a WASM file listing contract functions, meta, etc + Inspect(inspect::Cmd), + + /// Install a WASM file to the ledger without creating a contract instance + Install(install::Cmd), + + /// Invoke a contract function + /// + /// Generates an "implicit CLI" for the specified contract on-the-fly using the contract's + /// schema, which gets embedded into every Soroban contract. The "slop" in this command, + /// everything after the `--`, gets passed to this implicit CLI. Get in-depth help for a given + /// contract: + /// + /// soroban contract invoke ... -- --help + Invoke(invoke::Cmd), + + /// Optimize a WASM file + Optimize(optimize::Cmd), + + /// Print the current value of a contract-data ledger entry + Read(read::Cmd), + + /// Restore an evicted value for a contract-data legder entry. + /// + /// If no keys are specificed the contract itself is restored. + Restore(restore::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Asset(#[from] asset::Error), + + #[error(transparent)] + Bindings(#[from] bindings::Error), + + #[error(transparent)] + Build(#[from] build::Error), + + #[error(transparent)] + Extend(#[from] extend::Error), + + #[error(transparent)] + Deploy(#[from] deploy::wasm::Error), + + #[error(transparent)] + Fetch(#[from] fetch::Error), + #[error(transparent)] + Id(#[from] id::Error), + + #[error(transparent)] + Inspect(#[from] inspect::Error), + + #[error(transparent)] + Install(#[from] install::Error), + + #[error(transparent)] + Invoke(#[from] invoke::Error), + + #[error(transparent)] + Optimize(#[from] optimize::Error), + + #[error(transparent)] + Read(#[from] read::Error), + + #[error(transparent)] + Restore(#[from] restore::Error), +} + +impl Cmd { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + match &self { + Cmd::Asset(asset) => asset.run().await?, + Cmd::Bindings(bindings) => bindings.run().await?, + Cmd::Build(build) => build.run()?, + Cmd::Extend(extend) => extend.run().await?, + Cmd::Deploy(deploy) => deploy.run().await?, + Cmd::Id(id) => id.run()?, + Cmd::Inspect(inspect) => inspect.run()?, + Cmd::Install(install) => install.run().await?, + Cmd::Invoke(invoke) => invoke.run(global_args).await?, + Cmd::Optimize(optimize) => optimize.run()?, + Cmd::Fetch(fetch) => fetch.run().await?, + Cmd::Read(read) => read.run().await?, + Cmd::Restore(restore) => restore.run().await?, + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] +pub enum Durability { + /// Persistent + Persistent, + /// Temporary + Temporary, +} + +impl From<&Durability> for soroban_env_host::xdr::ContractDataDurability { + fn from(d: &Durability) -> Self { + match d { + Durability::Persistent => soroban_env_host::xdr::ContractDataDurability::Persistent, + Durability::Temporary => soroban_env_host::xdr::ContractDataDurability::Temporary, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] +pub enum SpecOutput { + /// XDR of array of contract spec entries + XdrBase64, + /// Array of xdr of contract spec entries + XdrBase64Array, + /// Pretty print of contract spec entries + Docs, +} diff --git a/cmd/soroban-cli/src/commands/contract/optimize.rs b/cmd/soroban-cli/src/commands/contract/optimize.rs new file mode 100644 index 00000000..751dabb1 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/optimize.rs @@ -0,0 +1,80 @@ +use clap::{arg, command, Parser}; +use std::fmt::Debug; +#[cfg(feature = "opt")] +use wasm_opt::{Feature, OptimizationError, OptimizationOptions}; + +use crate::wasm; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + wasm: wasm::Args, + /// Path to write the optimized WASM file to (defaults to same location as --wasm with .optimized.wasm suffix) + #[arg(long)] + wasm_out: Option, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[cfg(feature = "opt")] + #[error("optimization error: {0}")] + OptimizationError(OptimizationError), + #[cfg(not(feature = "opt"))] + #[error("Must install with \"opt\" feature, e.g. `cargo install soroban-cli --features opt")] + Install, +} + +impl Cmd { + #[cfg(not(feature = "opt"))] + pub fn run(&self) -> Result<(), Error> { + Err(Error::Install) + } + + #[cfg(feature = "opt")] + pub fn run(&self) -> Result<(), Error> { + let wasm_size = self.wasm.len()?; + + println!( + "Reading: {} ({} bytes)", + self.wasm.wasm.to_string_lossy(), + wasm_size + ); + + let wasm_out = self.wasm_out.as_ref().cloned().unwrap_or_else(|| { + let mut wasm_out = self.wasm.wasm.clone(); + wasm_out.set_extension("optimized.wasm"); + wasm_out + }); + println!("Writing to: {}...", wasm_out.to_string_lossy()); + + let mut options = OptimizationOptions::new_optimize_for_size_aggressively(); + options.converge = true; + + // Explicitly set to MVP + sign-ext + mutable-globals, which happens to + // also be the default featureset, but just to be extra clear we set it + // explicitly. + // + // Formerly Soroban supported only the MVP feature set, but Rust 1.70 as + // well as Clang generate code with sign-ext + mutable-globals enabled, + // so Soroban has taken a change to support them also. + options.mvp_features_only(); + options.enable_feature(Feature::MutableGlobals); + options.enable_feature(Feature::SignExt); + + options + .run(&self.wasm.wasm, &wasm_out) + .map_err(Error::OptimizationError)?; + + let wasm_out_size = wasm::len(&wasm_out)?; + println!( + "Optimized: {} ({} bytes)", + wasm_out.to_string_lossy(), + wasm_out_size + ); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs new file mode 100644 index 00000000..842832d5 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -0,0 +1,180 @@ +use std::{ + fmt::Debug, + io::{self, stdout}, +}; + +use clap::{command, Parser, ValueEnum}; +use soroban_env_host::{ + xdr::{ + ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, + ScVal, WriteXdr, + }, + HostError, +}; +use soroban_sdk::xdr::Limits; + +use crate::{ + commands::config, + key, + rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, +}; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// Type of output to generate + #[arg(long, value_enum, default_value("string"))] + pub output: Output, + #[command(flatten)] + pub key: key::Args, + #[command(flatten)] + config: config::Args, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] +pub enum Output { + /// String + String, + /// Json + Json, + /// XDR + Xdr, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing key {key}: {error}")] + CannotParseKey { + key: String, + error: soroban_spec_tools::Error, + }, + #[error("parsing XDR key {key}: {error}")] + CannotParseXdrKey { key: String, error: XdrError }, + #[error("cannot parse contract ID {contract_id}: {error}")] + CannotParseContractId { + contract_id: String, + error: stellar_strkey::DecodeError, + }, + #[error("cannot print result {result:?}: {error}")] + CannotPrintResult { + result: ScVal, + error: soroban_spec_tools::Error, + }, + #[error("cannot print result {result:?}: {error}")] + CannotPrintJsonResult { + result: ScVal, + error: serde_json::Error, + }, + #[error("cannot print as csv: {error}")] + CannotPrintAsCsv { error: csv::Error }, + #[error("cannot print: {error}")] + CannotPrintFlush { error: io::Error }, + #[error(transparent)] + Config(#[from] config::Error), + #[error("either `--key` or `--key-xdr` are required when querying a network")] + KeyIsRequired, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Xdr(#[from] XdrError), + #[error(transparent)] + // TODO: the Display impl of host errors is pretty user-unfriendly + // (it just calls Debug). I think we can do better than that + Host(#[from] HostError), + #[error("no matching contract data entries were found for the specified contract id")] + NoContractDataEntryFoundForContractID, + #[error(transparent)] + Key(#[from] key::Error), + #[error("Only contract data and code keys are allowed")] + OnlyDataAllowed, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let entries = self.run_against_rpc_server().await?; + self.output_entries(&entries) + } + + async fn run_against_rpc_server(&self) -> Result { + let network = self.config.get_network()?; + tracing::trace!(?network); + let network = &self.config.get_network()?; + let client = Client::new(&network.rpc_url)?; + let keys = self.key.parse_keys()?; + Ok(client.get_full_ledger_entries(&keys).await?) + } + + fn output_entries(&self, entries: &FullLedgerEntries) -> Result<(), Error> { + if entries.entries.is_empty() { + return Err(Error::NoContractDataEntryFoundForContractID); + } + tracing::trace!("{entries:#?}"); + let mut out = csv::Writer::from_writer(stdout()); + for FullLedgerEntry { + key, + val, + live_until_ledger_seq, + last_modified_ledger, + } in &entries.entries + { + let ( + LedgerKey::ContractData(LedgerKeyContractData { key, .. }), + LedgerEntryData::ContractData(ContractDataEntry { val, .. }), + ) = (key, val) + else { + return Err(Error::OnlyDataAllowed); + }; + let output = match self.output { + Output::String => [ + soroban_spec_tools::to_string(key).map_err(|e| Error::CannotPrintResult { + result: key.clone(), + error: e, + })?, + soroban_spec_tools::to_string(val).map_err(|e| Error::CannotPrintResult { + result: val.clone(), + error: e, + })?, + last_modified_ledger.to_string(), + live_until_ledger_seq.to_string(), + ], + Output::Json => [ + serde_json::to_string_pretty(&key).map_err(|error| { + Error::CannotPrintJsonResult { + result: key.clone(), + error, + } + })?, + serde_json::to_string_pretty(&val).map_err(|error| { + Error::CannotPrintJsonResult { + result: val.clone(), + error, + } + })?, + serde_json::to_string_pretty(&last_modified_ledger).map_err(|error| { + Error::CannotPrintJsonResult { + result: val.clone(), + error, + } + })?, + serde_json::to_string_pretty(&live_until_ledger_seq).map_err(|error| { + Error::CannotPrintJsonResult { + result: val.clone(), + error, + } + })?, + ], + Output::Xdr => [ + key.to_xdr_base64(Limits::none())?, + val.to_xdr_base64(Limits::none())?, + last_modified_ledger.to_xdr_base64(Limits::none())?, + live_until_ledger_seq.to_xdr_base64(Limits::none())?, + ], + }; + out.write_record(output) + .map_err(|e| Error::CannotPrintAsCsv { error: e })?; + } + out.flush() + .map_err(|e| Error::CannotPrintFlush { error: e })?; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs new file mode 100644 index 00000000..38b8a84a --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -0,0 +1,206 @@ +use std::{fmt::Debug, path::Path, str::FromStr}; + +use clap::{command, Parser}; +use soroban_env_host::xdr::{ + Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, + LedgerFootprint, Memo, MuxedAccount, Operation, OperationBody, OperationMeta, Preconditions, + RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, + TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, Uint256, +}; +use stellar_strkey::DecodeError; + +use crate::{ + commands::{ + config::{self, locator}, + contract::extend, + }, + key, + rpc::{self, Client}, + wasm, Pwd, +}; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub key: key::Args, + /// Number of ledgers to extend the entry + #[arg(long)] + pub ledgers_to_extend: Option, + /// Only print the new Time To Live ledger + #[arg(long)] + pub ttl_ledger_only: bool, + #[command(flatten)] + pub config: config::Args, + #[command(flatten)] + pub fee: crate::fee::Args, +} + +impl FromStr for Cmd { + type Err = clap::error::Error; + + fn from_str(s: &str) -> Result { + use clap::{CommandFactory, FromArgMatches}; + Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace())) + } +} + +impl Pwd for Cmd { + fn set_pwd(&mut self, pwd: &Path) { + self.config.set_pwd(pwd); + } +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing key {key}: {error}")] + CannotParseKey { + key: String, + error: soroban_spec_tools::Error, + }, + #[error("parsing XDR key {key}: {error}")] + CannotParseXdrKey { key: String, error: XdrError }, + #[error("cannot parse contract ID {0}: {1}")] + CannotParseContractId(String, DecodeError), + #[error(transparent)] + Config(#[from] config::Error), + #[error("either `--key` or `--key-xdr` are required")] + KeyIsRequired, + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error("Ledger entry not found")] + LedgerEntryNotFound, + #[error(transparent)] + Locator(#[from] locator::Error), + #[error("missing operation result")] + MissingOperationResult, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Wasm(#[from] wasm::Error), + #[error(transparent)] + Key(#[from] key::Error), + #[error(transparent)] + Extend(#[from] extend::Error), +} + +impl Cmd { + #[allow(clippy::too_many_lines)] + pub async fn run(&self) -> Result<(), Error> { + let expiration_ledger_seq = self.run_against_rpc_server().await?; + + if let Some(ledgers_to_extend) = self.ledgers_to_extend { + extend::Cmd { + key: self.key.clone(), + ledgers_to_extend, + config: self.config.clone(), + fee: self.fee.clone(), + ttl_ledger_only: false, + } + .run() + .await?; + } else { + println!("New ttl ledger: {expiration_ledger_seq}"); + } + + Ok(()) + } + + pub async fn run_against_rpc_server(&self) -> Result { + let network = self.config.get_network()?; + tracing::trace!(?network); + let entry_keys = self.key.parse_keys()?; + let network = &self.config.get_network()?; + let client = Client::new(&network.rpc_url)?; + let key = self.config.key_pair()?; + + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let account_details = client.get_account(&public_strkey).await?; + let sequence: i64 = account_details.seq_num.into(); + + let tx = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(key.verifying_key().to_bytes())), + fee: self.fee.fee, + seq_num: SequenceNumber(sequence + 1), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::RestoreFootprint(RestoreFootprintOp { + ext: ExtensionPoint::V0, + }), + }] + .try_into()?, + ext: TransactionExt::V1(SorobanTransactionData { + ext: ExtensionPoint::V0, + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: vec![].try_into()?, + read_write: entry_keys.try_into()?, + }, + instructions: 0, + read_bytes: 0, + write_bytes: 0, + }, + resource_fee: 0, + }), + }; + + let (result, meta, events) = client + .prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None) + .await?; + + tracing::trace!(?result); + tracing::trace!(?meta); + if !events.is_empty() { + tracing::info!("Events:\n {events:#?}"); + } + + // The transaction from core will succeed regardless of whether it actually found & + // restored the entry, so we have to inspect the result meta to tell if it worked or not. + let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else { + return Err(Error::LedgerEntryNotFound); + }; + tracing::debug!("Operations:\nlen:{}\n{operations:#?}", operations.len()); + + // Simply check if there is exactly one entry here. We only support extending a single + // entry via this command (which we should fix separately, but). + if operations.len() == 0 { + return Err(Error::LedgerEntryNotFound); + } + + if operations.len() != 1 { + tracing::warn!( + "Unexpected number of operations: {}. Currently only handle one.", + operations[0].changes.len() + ); + } + parse_operations(&operations).ok_or(Error::MissingOperationResult) + } +} + +fn parse_operations(ops: &[OperationMeta]) -> Option { + ops.first().and_then(|op| { + op.changes.iter().find_map(|entry| match entry { + LedgerEntryChange::Updated(LedgerEntry { + data: + LedgerEntryData::Ttl(TtlEntry { + live_until_ledger_seq, + .. + }), + .. + }) + | LedgerEntryChange::Created(LedgerEntry { + data: + LedgerEntryData::Ttl(TtlEntry { + live_until_ledger_seq, + .. + }), + .. + }) => Some(*live_until_ledger_seq), + _ => None, + }) + }) +} diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs new file mode 100644 index 00000000..aa46bbe2 --- /dev/null +++ b/cmd/soroban-cli/src/commands/events.rs @@ -0,0 +1,221 @@ +use clap::{arg, command, Parser}; +use std::io; + +use soroban_env_host::xdr::{self, Limits, ReadXdr}; + +use super::{config::locator, network}; +use crate::{rpc, utils}; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + /// The first ledger sequence number in the range to pull events + /// https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence + #[arg(long, conflicts_with = "cursor", required_unless_present = "cursor")] + start_ledger: Option, + /// The cursor corresponding to the start of the event range. + #[arg( + long, + conflicts_with = "start_ledger", + required_unless_present = "start_ledger" + )] + cursor: Option, + /// Output formatting options for event stream + #[arg(long, value_enum, default_value = "pretty")] + output: OutputFormat, + /// The maximum number of events to display (defer to the server-defined limit). + #[arg(short, long, default_value = "10")] + count: usize, + /// A set of (up to 5) contract IDs to filter events on. This parameter can + /// be passed multiple times, e.g. `--id C123.. --id C456..`, or passed with + /// multiple parameters, e.g. `--id C123 C456`. + /// + /// Though the specification supports multiple filter objects (i.e. + /// combinations of type, IDs, and topics), only one set can be specified on + /// the command-line today, though that set can have multiple IDs/topics. + #[arg( + long = "id", + num_args = 1..=6, + help_heading = "FILTERS" + )] + contract_ids: Vec, + /// A set of (up to 4) topic filters to filter event topics on. A single + /// topic filter can contain 1-4 different segment filters, separated by + /// commas, with an asterisk (* character) indicating a wildcard segment. + /// + /// For example, this is one topic filter with two segments: + /// + /// --topic "AAAABQAAAAdDT1VOVEVSAA==,*" + /// + /// This is two topic filters with one and two segments each: + /// + /// --topic "AAAABQAAAAdDT1VOVEVSAA==" --topic '*,*' + /// + /// Note that all of these topic filters are combined with the contract IDs + /// into a single filter (i.e. combination of type, IDs, and topics). + #[arg( + long = "topic", + num_args = 1..=5, + help_heading = "FILTERS" + )] + topic_filters: Vec, + /// Specifies which type of contract events to display. + #[arg( + long = "type", + value_enum, + default_value = "all", + help_heading = "FILTERS" + )] + event_type: rpc::EventType, + #[command(flatten)] + locator: locator::Args, + #[command(flatten)] + network: network::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("cursor is not valid")] + InvalidCursor, + #[error("filepath does not exist: {path}")] + InvalidFile { path: String }, + #[error("filepath ({path}) cannot be read: {error}")] + CannotReadFile { path: String, error: String }, + #[error("cannot parse topic filter {topic} into 1-4 segments")] + InvalidTopicFilter { topic: String }, + #[error("invalid segment ({segment}) in topic filter ({topic}): {error}")] + InvalidSegment { + topic: String, + segment: String, + error: xdr::Error, + }, + #[error("cannot parse contract ID {contract_id}: {error}")] + InvalidContractId { + contract_id: String, + error: stellar_strkey::DecodeError, + }, + #[error("invalid JSON string: {error} ({debug})")] + InvalidJson { + debug: String, + error: serde_json::Error, + }, + #[error("invalid timestamp in event: {ts}")] + InvalidTimestamp { ts: String }, + #[error("missing start_ledger and cursor")] + MissingStartLedgerAndCursor, + #[error("missing target")] + MissingTarget, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Generic(#[from] Box), + #[error(transparent)] + Io(#[from] io::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Serde(#[from] serde_json::Error), + #[error(transparent)] + Network(#[from] network::Error), + #[error(transparent)] + Locator(#[from] locator::Error), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] +pub enum OutputFormat { + /// Colorful, human-oriented console output + Pretty, + /// Human-oriented console output without colors + Plain, + /// JSONified console output + Json, +} + +impl Cmd { + pub async fn run(&mut self) -> Result<(), Error> { + // Validate that topics are made up of segments. + for topic in &self.topic_filters { + for (i, segment) in topic.split(',').enumerate() { + if i > 4 { + return Err(Error::InvalidTopicFilter { + topic: topic.to_string(), + }); + } + + if segment != "*" { + if let Err(e) = xdr::ScVal::from_xdr_base64(segment, Limits::none()) { + return Err(Error::InvalidSegment { + topic: topic.to_string(), + segment: segment.to_string(), + error: e, + }); + } + } + } + } + + // Validate contract_ids + for id in &mut self.contract_ids { + utils::contract_id_from_str(id).map_err(|e| Error::InvalidContractId { + contract_id: id.clone(), + error: e, + })?; + } + + let response = self.run_against_rpc_server().await?; + + for event in &response.events { + match self.output { + // Should we pretty-print the JSON like we're doing here or just + // dump an event in raw JSON on each line? The latter is easier + // to consume programmatically. + OutputFormat::Json => { + println!( + "{}", + serde_json::to_string_pretty(&event).map_err(|e| { + Error::InvalidJson { + debug: format!("{event:#?}"), + error: e, + } + })?, + ); + } + OutputFormat::Plain => println!("{event}"), + OutputFormat::Pretty => event.pretty_print()?, + } + } + println!("Latest Ledger: {}", response.latest_ledger); + + Ok(()) + } + + async fn run_against_rpc_server(&self) -> Result { + let start = self.start()?; + let network = self.network.get(&self.locator)?; + + let client = rpc::Client::new(&network.rpc_url)?; + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + client + .get_events( + start, + Some(self.event_type), + &self.contract_ids, + &self.topic_filters, + Some(self.count), + ) + .await + .map_err(Error::Rpc) + } + + fn start(&self) -> Result { + let start = match (self.start_ledger, self.cursor.clone()) { + (Some(start), _) => rpc::EventStart::Ledger(start), + (_, Some(c)) => rpc::EventStart::Cursor(c), + // should never happen because of required_unless_present flags + _ => return Err(Error::MissingStartLedgerAndCursor), + }; + Ok(start) + } +} diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs new file mode 100644 index 00000000..c606bd1b --- /dev/null +++ b/cmd/soroban-cli/src/commands/global.rs @@ -0,0 +1,61 @@ +use clap::arg; +use std::path::PathBuf; + +use super::config; + +#[derive(Debug, clap::Args, Clone, Default)] +#[group(skip)] +#[allow(clippy::struct_excessive_bools)] +pub struct Args { + #[clap(flatten)] + pub locator: config::locator::Args, + + /// Filter logs output. To turn on "soroban_cli::log::footprint=debug" or off "=off". Can also use env var `RUST_LOG`. + #[arg(long, short = 'f')] + pub filter_logs: Vec, + + /// Do not write logs to stderr including `INFO` + #[arg(long, short = 'q')] + pub quiet: bool, + + /// Log DEBUG events + #[arg(long, short = 'v')] + pub verbose: bool, + + /// Log DEBUG and TRACE events + #[arg(long, visible_alias = "vv")] + pub very_verbose: bool, + + /// List installed plugins. E.g. `soroban-hello` + #[arg(long)] + pub list: bool, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("reading file {filepath}: {error}")] + CannotReadLedgerFile { + filepath: PathBuf, + error: soroban_ledger_snapshot::Error, + }, + + #[error("committing file {filepath}: {error}")] + CannotCommitLedgerFile { + filepath: PathBuf, + error: soroban_ledger_snapshot::Error, + }, +} + +impl Args { + pub fn log_level(&self) -> Option { + if self.quiet { + None + } else if self.very_verbose { + Some(tracing::Level::TRACE) + } else if self.verbose { + Some(tracing::Level::DEBUG) + } else { + Some(tracing::Level::INFO) + } + } +} diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs new file mode 100644 index 00000000..2868c737 --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -0,0 +1,33 @@ +use clap::command; + +use super::super::config::{locator, secret}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Name of identity + pub name: String, + + #[command(flatten)] + pub secrets: secret::Args, + + #[command(flatten)] + pub config_locator: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + Ok(self + .config_locator + .write_identity(&self.name, &self.secrets.read_secret()?)?) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs new file mode 100644 index 00000000..d13381b4 --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -0,0 +1,54 @@ +use crate::commands::config::secret; + +use super::super::config::locator; +use clap::arg; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), + + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Name of identity to lookup, default test identity used if not provided + pub name: String, + + /// If identity is a seed phrase use this hd path, default is 0 + #[arg(long)] + pub hd_path: Option, + + #[command(flatten)] + pub locator: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + println!("{}", self.public_key()?); + Ok(()) + } + + pub fn private_key(&self) -> Result { + Ok(self + .locator + .read_identity(&self.name)? + .key_pair(self.hd_path)?) + } + + pub fn public_key(&self) -> Result { + if let Ok(key) = stellar_strkey::ed25519::PublicKey::from_string(&self.name) { + Ok(key) + } else { + Ok(stellar_strkey::ed25519::PublicKey::from_payload( + self.private_key()?.verifying_key().as_bytes(), + )?) + } + } +} diff --git a/cmd/soroban-cli/src/commands/keys/fund.rs b/cmd/soroban-cli/src/commands/keys/fund.rs new file mode 100644 index 00000000..b6c088f1 --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/fund.rs @@ -0,0 +1,34 @@ +use clap::command; + +use crate::commands::network; + +use super::address; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Address(#[from] address::Error), + #[error(transparent)] + Network(#[from] network::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub network: network::Args, + /// Address to fund + #[command(flatten)] + pub address: address::Cmd, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let addr = self.address.public_key()?; + self.network + .get(&self.address.locator)? + .fund_address(&addr) + .await?; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs new file mode 100644 index 00000000..07782b21 --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -0,0 +1,75 @@ +use clap::{arg, command}; + +use crate::commands::network; + +use super::super::config::{ + locator, + secret::{self, Secret}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), + #[error(transparent)] + Secret(#[from] secret::Error), + #[error(transparent)] + Network(#[from] network::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Name of identity + pub name: String, + /// Do not fund address + #[arg(long)] + pub no_fund: bool, + /// Optional seed to use when generating seed phrase. + /// Random otherwise. + #[arg(long, conflicts_with = "default_seed")] + pub seed: Option, + + /// Output the generated identity as a secret key + #[arg(long, short = 's')] + pub as_secret: bool, + + #[command(flatten)] + pub config_locator: locator::Args, + + /// When generating a secret key, which hd_path should be used from the original seed_phrase. + #[arg(long)] + pub hd_path: Option, + + /// Generate the default seed phrase. Useful for testing. + /// Equivalent to --seed 0000000000000000 + #[arg(long, short = 'd', conflicts_with = "seed")] + pub default_seed: bool, + + #[command(flatten)] + pub network: network::Args, +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + let seed_phrase = if self.default_seed { + Secret::test_seed_phrase() + } else { + Secret::from_seed(self.seed.as_deref()) + }?; + let secret = if self.as_secret { + seed_phrase.private_key(self.hd_path)?.into() + } else { + seed_phrase + }; + self.config_locator.write_identity(&self.name, &secret)?; + if !self.no_fund { + let addr = secret.public_key(self.hd_path)?; + let network = self.network.get(&self.config_locator)?; + network.fund_address(&addr).await.unwrap_or_else(|_| { + tracing::warn!("Failed to fund address: {addr} on at {}", network.rpc_url); + }); + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/ls.rs b/cmd/soroban-cli/src/commands/keys/ls.rs new file mode 100644 index 00000000..bc46ffcd --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/ls.rs @@ -0,0 +1,45 @@ +use clap::command; + +use super::super::config::locator; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[arg(long, short = 'l')] + pub long: bool, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); + println!("{res}"); + Ok(()) + } + + pub fn ls(&self) -> Result, Error> { + let mut list = self.config_locator.list_identities()?; + let test = "test".to_string(); + if !list.contains(&test) { + list.push(test); + } + Ok(list) + } + + pub fn ls_l(&self) -> Result, Error> { + Ok(self + .config_locator + .list_identities_long()? + .into_iter() + .map(|(name, location)| format!("{location}\nName: {name}\n")) + .collect::>()) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs new file mode 100644 index 00000000..42814092 --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/mod.rs @@ -0,0 +1,63 @@ +use clap::Parser; + +pub mod add; +pub mod address; +pub mod fund; +pub mod generate; +pub mod ls; +pub mod rm; +pub mod show; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new identity (keypair, ledger, macOS keychain) + Add(add::Cmd), + /// Given an identity return its address (public key) + Address(address::Cmd), + /// Fund an identity on a test network + Fund(fund::Cmd), + /// Generate a new identity with a seed phrase, currently 12 words + Generate(generate::Cmd), + /// List identities + Ls(ls::Cmd), + /// Remove an identity + Rm(rm::Cmd), + /// Given an identity return its private key + Show(show::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Add(#[from] add::Error), + + #[error(transparent)] + Address(#[from] address::Error), + #[error(transparent)] + Fund(#[from] fund::Error), + + #[error(transparent)] + Generate(#[from] generate::Error), + #[error(transparent)] + Rm(#[from] rm::Error), + #[error(transparent)] + Ls(#[from] ls::Error), + + #[error(transparent)] + Show(#[from] show::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Add(cmd) => cmd.run()?, + Cmd::Address(cmd) => cmd.run()?, + Cmd::Fund(cmd) => cmd.run().await?, + Cmd::Generate(cmd) => cmd.run().await?, + Cmd::Ls(cmd) => cmd.run()?, + Cmd::Rm(cmd) => cmd.run()?, + Cmd::Show(cmd) => cmd.run()?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/rm.rs b/cmd/soroban-cli/src/commands/keys/rm.rs new file mode 100644 index 00000000..df48108d --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/rm.rs @@ -0,0 +1,25 @@ +use clap::command; + +use super::super::config::locator; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Identity to remove + pub name: String, + + #[command(flatten)] + pub config: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + Ok(self.config.remove_identity(&self.name)?) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs new file mode 100644 index 00000000..b99478cb --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/show.rs @@ -0,0 +1,43 @@ +use clap::arg; + +use super::super::config::{locator, secret}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), + + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Name of identity to lookup, default is test identity + pub name: String, + + /// If identity is a seed phrase use this hd path, default is 0 + #[arg(long)] + pub hd_path: Option, + + #[command(flatten)] + pub locator: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + println!("{}", self.private_key()?.to_string()); + Ok(()) + } + + pub fn private_key(&self) -> Result { + Ok(self + .locator + .read_identity(&self.name)? + .private_key(self.hd_path)?) + } +} diff --git a/cmd/soroban-cli/src/commands/lab/mod.rs b/cmd/soroban-cli/src/commands/lab/mod.rs new file mode 100644 index 00000000..f405efe6 --- /dev/null +++ b/cmd/soroban-cli/src/commands/lab/mod.rs @@ -0,0 +1,31 @@ +use clap::Subcommand; +use stellar_xdr::cli as xdr; + +pub mod token; + +#[derive(Debug, Subcommand)] +pub enum Cmd { + /// Wrap, create, and manage token contracts + Token(token::Root), + + /// Decode xdr + Xdr(xdr::Root), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Token(#[from] token::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match &self { + Cmd::Token(token) => token.run().await?, + Cmd::Xdr(xdr) => xdr.run()?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/lab/token/mod.rs b/cmd/soroban-cli/src/commands/lab/token/mod.rs new file mode 100644 index 00000000..bd7eacf3 --- /dev/null +++ b/cmd/soroban-cli/src/commands/lab/token/mod.rs @@ -0,0 +1,38 @@ +use std::fmt::Debug; + +use crate::commands::contract::{deploy, id}; +use clap::{Parser, Subcommand}; + +#[derive(Parser, Debug)] +pub struct Root { + #[clap(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand, Debug)] +enum Cmd { + /// Deploy a token contract to wrap an existing Stellar classic asset for smart contract usage + /// Deprecated, use `soroban contract deploy asset` instead + Wrap(deploy::asset::Cmd), + /// Compute the expected contract id for the given asset + /// Deprecated, use `soroban contract id asset` instead + Id(id::asset::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Wrap(#[from] deploy::asset::Error), + #[error(transparent)] + Id(#[from] id::asset::Error), +} + +impl Root { + pub async fn run(&self) -> Result<(), Error> { + match &self.cmd { + Cmd::Wrap(wrap) => wrap.run().await?, + Cmd::Id(id) => id.run()?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs new file mode 100644 index 00000000..952869af --- /dev/null +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -0,0 +1,160 @@ +use std::str::FromStr; + +use clap::{command, error::ErrorKind, CommandFactory, FromArgMatches, Parser}; + +pub mod completion; +pub mod config; +pub mod contract; +pub mod events; +pub mod global; +pub mod keys; +pub mod lab; +pub mod network; +pub mod plugin; +pub mod version; + +pub const HEADING_RPC: &str = "Options (RPC)"; +const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. + +Intro: https://soroban.stellar.org +CLI Reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; + +// long_about is shown when someone uses `--help`; short help when using `-h` +const LONG_ABOUT: &str = " + +The easiest way to get started is to generate a new identity: + + soroban config identity generate alice + +You can use identities with the `--source` flag in other commands later. + +Commands that relate to smart contract interactions are organized under the `contract` subcommand. List them: + + soroban contract --help + +A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: + + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + --help + +Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: + + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + hello --to world + +Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; + +#[derive(Parser, Debug)] +#[command( + name = "soroban", + about = ABOUT, + version = version::long(), + long_about = ABOUT.to_string() + LONG_ABOUT, + disable_help_subcommand = true, +)] +pub struct Root { + #[clap(flatten)] + pub global_args: global::Args, + + #[command(subcommand)] + pub cmd: Cmd, +} + +impl Root { + pub fn new() -> Result { + Self::try_parse().map_err(|e| { + if std::env::args().any(|s| s == "--list") { + let plugins = plugin::list().unwrap_or_default(); + if plugins.is_empty() { + println!("No Plugins installed. E.g. soroban-hello"); + } else { + println!("Installed Plugins:\n {}", plugins.join("\n ")); + } + std::process::exit(0); + } + match e.kind() { + ErrorKind::InvalidSubcommand => match plugin::run() { + Ok(()) => Error::Clap(e), + Err(e) => Error::Plugin(e), + }, + _ => Error::Clap(e), + } + }) + } + + pub fn from_arg_matches(itr: I) -> Result + where + I: IntoIterator, + T: Into + Clone, + { + Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr)) + } + pub async fn run(&mut self) -> Result<(), Error> { + match &mut self.cmd { + Cmd::Completion(completion) => completion.run(), + Cmd::Contract(contract) => contract.run(&self.global_args).await?, + Cmd::Events(events) => events.run().await?, + Cmd::Lab(lab) => lab.run().await?, + Cmd::Network(network) => network.run()?, + Cmd::Version(version) => version.run(), + Cmd::Keys(id) => id.run().await?, + Cmd::Config(c) => c.run().await?, + }; + Ok(()) + } +} + +impl FromStr for Root { + type Err = clap::Error; + + fn from_str(s: &str) -> Result { + Self::from_arg_matches(s.split_whitespace()) + } +} + +#[derive(Parser, Debug)] +pub enum Cmd { + /// Print shell completion code for the specified shell. + #[command(long_about = completion::LONG_ABOUT)] + Completion(completion::Cmd), + /// Deprecated, use `soroban keys` and `soroban network` instead + #[command(subcommand)] + Config(config::Cmd), + /// Tools for smart contract developers + #[command(subcommand)] + Contract(contract::Cmd), + /// Watch the network for contract events + Events(events::Cmd), + /// Create and manage identities including keys and addresses + #[command(subcommand)] + Keys(keys::Cmd), + /// Experiment with early features and expert tools + #[command(subcommand)] + Lab(lab::Cmd), + /// Start and configure networks + #[command(subcommand)] + Network(network::Cmd), + /// Print version information + Version(version::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + // TODO: stop using Debug for displaying errors + #[error(transparent)] + Contract(#[from] contract::Error), + #[error(transparent)] + Events(#[from] events::Error), + #[error(transparent)] + Keys(#[from] keys::Error), + #[error(transparent)] + Lab(#[from] lab::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + Clap(#[from] clap::error::Error), + #[error(transparent)] + Plugin(#[from] plugin::Error), + #[error(transparent)] + Network(#[from] network::Error), +} diff --git a/cmd/soroban-cli/src/commands/network/add.rs b/cmd/soroban-cli/src/commands/network/add.rs new file mode 100644 index 00000000..b6a2ddd3 --- /dev/null +++ b/cmd/soroban-cli/src/commands/network/add.rs @@ -0,0 +1,32 @@ +use super::super::config::{locator, secret}; +use clap::command; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Secret(#[from] secret::Error), + + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Name of network + pub name: String, + + #[command(flatten)] + pub network: super::Network, + + #[command(flatten)] + pub config_locator: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + Ok(self + .config_locator + .write_network(&self.name, &self.network)?) + } +} diff --git a/cmd/soroban-cli/src/commands/network/ls.rs b/cmd/soroban-cli/src/commands/network/ls.rs new file mode 100644 index 00000000..cc542b3e --- /dev/null +++ b/cmd/soroban-cli/src/commands/network/ls.rs @@ -0,0 +1,44 @@ +use clap::command; + +use super::locator; +use crate::commands::config::locator::Location; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + /// Get more info about the networks + #[arg(long, short = 'l')] + pub long: bool, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + let res = if self.long { self.ls_l() } else { self.ls() }?.join("\n"); + println!("{res}"); + Ok(()) + } + + pub fn ls(&self) -> Result, Error> { + Ok(self.config_locator.list_networks()?) + } + + pub fn ls_l(&self) -> Result, Error> { + Ok(self + .config_locator + .list_networks_long()? + .iter() + .filter_map(|(name, network, location)| { + (!self.config_locator.global || matches!(location, Location::Global(_))) + .then(|| Some(format!("{location}\nName: {name}\n{network:#?}\n")))? + }) + .collect()) + } +} diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs new file mode 100644 index 00000000..22cba190 --- /dev/null +++ b/cmd/soroban-cli/src/commands/network/mod.rs @@ -0,0 +1,197 @@ +use std::str::FromStr; + +use clap::{arg, Parser}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use stellar_strkey::ed25519::PublicKey; + +use crate::{ + commands::HEADING_RPC, + rpc::{self, Client}, +}; + +use super::config::locator; + +pub mod add; +pub mod ls; +pub mod rm; + +#[derive(Debug, Parser)] +pub enum Cmd { + /// Add a new network + Add(add::Cmd), + /// Remove a network + Rm(rm::Cmd), + /// List networks + Ls(ls::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Add(#[from] add::Error), + + #[error(transparent)] + Rm(#[from] rm::Error), + + #[error(transparent)] + Ls(#[from] ls::Error), + + #[error(transparent)] + Config(#[from] locator::Error), + + #[error("network arg or rpc url and network passphrase are required if using the network")] + Network, + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Hyper(#[from] hyper::Error), + #[error("Failed to parse JSON from {0}, {1}")] + FailedToParseJSON(String, serde_json::Error), + #[error("Invalid URL {0}")] + InvalidUrl(String), + #[error("Inproper response {0}")] + InproperResponse(String), + #[error("Currently not supported on windows. Please visit:\n{0}")] + WindowsNotSupported(String), +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + match self { + Cmd::Add(cmd) => cmd.run()?, + Cmd::Rm(new) => new.run()?, + Cmd::Ls(cmd) => cmd.run()?, + }; + Ok(()) + } +} + +#[derive(Debug, clap::Args, Clone, Default)] +#[group(skip)] +pub struct Args { + /// RPC server endpoint + #[arg( + long = "rpc-url", + requires = "network_passphrase", + required_unless_present = "network", + env = "SOROBAN_RPC_URL", + help_heading = HEADING_RPC, + )] + pub rpc_url: Option, + /// Network passphrase to sign the transaction sent to the rpc server + #[arg( + long = "network-passphrase", + requires = "rpc_url", + required_unless_present = "network", + env = "SOROBAN_NETWORK_PASSPHRASE", + help_heading = HEADING_RPC, + )] + pub network_passphrase: Option, + /// Name of network to use from config + #[arg( + long, + required_unless_present = "rpc_url", + env = "SOROBAN_NETWORK", + help_heading = HEADING_RPC, + )] + pub network: Option, +} + +impl Args { + pub fn get(&self, locator: &locator::Args) -> Result { + if let Some(name) = self.network.as_deref() { + if let Ok(network) = locator.read_network(name) { + return Ok(network); + } + } + if let (Some(rpc_url), Some(network_passphrase)) = + (self.rpc_url.clone(), self.network_passphrase.clone()) + { + Ok(Network { + rpc_url, + network_passphrase, + }) + } else { + Err(Error::Network) + } + } +} + +#[derive(Debug, clap::Args, Serialize, Deserialize, Clone)] +#[group(skip)] +pub struct Network { + /// RPC server endpoint + #[arg( + long = "rpc-url", + env = "SOROBAN_RPC_URL", + help_heading = HEADING_RPC, + )] + pub rpc_url: String, + /// Network passphrase to sign the transaction sent to the rpc server + #[arg( + long, + env = "SOROBAN_NETWORK_PASSPHRASE", + help_heading = HEADING_RPC, + )] + pub network_passphrase: String, +} + +impl Network { + pub async fn helper_url(&self, addr: &str) -> Result { + tracing::debug!("address {addr:?}"); + let client = Client::new(&self.rpc_url)?; + let helper_url_root = client.friendbot_url().await?; + let uri = http::Uri::from_str(&helper_url_root) + .map_err(|_| Error::InvalidUrl(helper_url_root.to_string()))?; + http::Uri::from_str(&format!("{uri:?}?addr={addr}")) + .map_err(|_| Error::InvalidUrl(helper_url_root.to_string())) + } + + #[allow(clippy::similar_names)] + pub async fn fund_address(&self, addr: &PublicKey) -> Result<(), Error> { + let uri = self.helper_url(&addr.to_string()).await?; + tracing::debug!("URL {uri:?}"); + let response = match uri.scheme_str() { + Some("http") => hyper::Client::new().get(uri.clone()).await?, + Some("https") => { + #[cfg(target_os = "windows")] + { + return Err(Error::WindowsNotSupported(uri.to_string())); + } + #[cfg(not(target_os = "windows"))] + { + let https = hyper_tls::HttpsConnector::new(); + hyper::Client::builder() + .build::<_, hyper::Body>(https) + .get(uri.clone()) + .await? + } + } + _ => { + return Err(Error::InvalidUrl(uri.to_string())); + } + }; + let body = hyper::body::to_bytes(response.into_body()).await?; + let res = serde_json::from_slice::(&body) + .map_err(|e| Error::FailedToParseJSON(uri.to_string(), e))?; + tracing::debug!("{res:#?}"); + if let Some(detail) = res.get("detail").and_then(Value::as_str) { + if detail.contains("createAccountAlreadyExist") { + tracing::warn!("Account already exists"); + } + } else if res.get("successful").is_none() { + return Err(Error::InproperResponse(res.to_string())); + } + Ok(()) + } +} + +impl Network { + pub fn futurenet() -> Self { + Network { + rpc_url: "https://rpc-futurenet.stellar.org:443".to_owned(), + network_passphrase: "Test SDF Future Network ; October 2022".to_owned(), + } + } +} diff --git a/cmd/soroban-cli/src/commands/network/rm.rs b/cmd/soroban-cli/src/commands/network/rm.rs new file mode 100644 index 00000000..7051dc6b --- /dev/null +++ b/cmd/soroban-cli/src/commands/network/rm.rs @@ -0,0 +1,24 @@ +use super::locator; +use clap::command; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Network to remove + pub name: String, + + #[command(flatten)] + pub config: locator::Args, +} + +impl Cmd { + pub fn run(&self) -> Result<(), Error> { + Ok(self.config.remove_network(&self.name)?) + } +} diff --git a/cmd/soroban-cli/src/commands/plugin.rs b/cmd/soroban-cli/src/commands/plugin.rs new file mode 100644 index 00000000..27c191f0 --- /dev/null +++ b/cmd/soroban-cli/src/commands/plugin.rs @@ -0,0 +1,96 @@ +use std::process::Command; + +use clap::CommandFactory; +use which::which; + +use crate::{utils, Root}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Plugin not provided. Should be `soroban plugin` for a binary `soroban-plugin`")] + MissingSubcommand, + #[error(transparent)] + IO(#[from] std::io::Error), + #[error( + r#"error: no such command: `{0}` + + {1}View all installed plugins with `soroban --list`"# + )] + ExecutableNotFound(String, String), + #[error(transparent)] + Which(#[from] which::Error), + #[error(transparent)] + Regex(#[from] regex::Error), +} + +const SUBCOMMAND_TOLERANCE: f64 = 0.75; +const PLUGIN_TOLERANCE: f64 = 0.75; +const MIN_LENGTH: usize = 4; + +/// Tries to run a plugin, if the plugin's name is similar enough to any of the current subcommands return Ok. +/// Otherwise only errors can be returned because this process will exit with the plugin. +pub fn run() -> Result<(), Error> { + let (name, args) = { + let mut args = std::env::args().skip(1); + let name = args.next().ok_or(Error::MissingSubcommand)?; + (name, args) + }; + + if Root::command().get_subcommands().any(|c| { + let sc_name = c.get_name(); + sc_name.starts_with(&name) + || (name.len() >= MIN_LENGTH && strsim::jaro(sc_name, &name) >= SUBCOMMAND_TOLERANCE) + }) { + return Ok(()); + } + + let bin = which(format!("soroban-{name}")).map_err(|_| { + let suggestion = if let Ok(bins) = list() { + let suggested_name = bins + .iter() + .map(|b| (b, strsim::jaro_winkler(&name, b))) + .filter(|(_, i)| *i > PLUGIN_TOLERANCE) + .min_by(|a, b| a.1.total_cmp(&b.1)) + .map(|(a, _)| a.to_string()) + .unwrap_or_default(); + if suggested_name.is_empty() { + suggested_name + } else { + format!( + r#"Did you mean `{suggested_name}`? + "# + ) + } + } else { + String::new() + }; + Error::ExecutableNotFound(name, suggestion) + })?; + std::process::exit( + Command::new(bin) + .args(args) + .spawn()? + .wait()? + .code() + .unwrap(), + ); +} + +const MAX_HEX_LENGTH: usize = 10; + +pub fn list() -> Result, Error> { + let re_str = if cfg!(target_os = "windows") { + r"^soroban-.*.exe$" + } else { + r"^soroban-.*" + }; + let re = regex::Regex::new(re_str)?; + Ok(which::which_re(re)? + .filter_map(|b| { + let s = b.file_name()?.to_str()?; + Some(s.strip_suffix(".exe").unwrap_or(s).to_string()) + }) + .filter(|s| !(utils::is_hex_string(s) && s.len() > MAX_HEX_LENGTH)) + .map(|s| s.replace("soroban-", "")) + .collect()) +} diff --git a/cmd/soroban-cli/src/commands/version.rs b/cmd/soroban-cli/src/commands/version.rs new file mode 100644 index 00000000..d9fc091b --- /dev/null +++ b/cmd/soroban-cli/src/commands/version.rs @@ -0,0 +1,32 @@ +use clap::Parser; +use soroban_env_host::meta; +use std::fmt::Debug; + +const GIT_REVISION: &str = env!("GIT_REVISION"); + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd; + +impl Cmd { + #[allow(clippy::unused_self)] + pub fn run(&self) { + println!("soroban {}", long()); + } +} + +pub fn long() -> String { + let env = soroban_env_host::VERSION; + let xdr = soroban_env_host::VERSION.xdr; + [ + format!("{} ({GIT_REVISION})", env!("CARGO_PKG_VERSION")), + format!("soroban-env {} ({})", env.pkg, env.rev), + format!("soroban-env interface version {}", meta::INTERFACE_VERSION), + format!( + "stellar-xdr {} ({}) +xdr curr ({})", + xdr.pkg, xdr.rev, xdr.xdr_curr, + ), + ] + .join("\n") +} diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs new file mode 100644 index 00000000..ee8b9614 --- /dev/null +++ b/cmd/soroban-cli/src/fee.rs @@ -0,0 +1,16 @@ +use crate::commands::HEADING_RPC; +use clap::arg; + +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + #[arg(long, default_value = "100", env = "SOROBAN_FEE", help_heading = HEADING_RPC)] + pub fee: u32, +} + +impl Default for Args { + fn default() -> Self { + Self { fee: 100 } + } +} diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs new file mode 100644 index 00000000..e9901abd --- /dev/null +++ b/cmd/soroban-cli/src/key.rs @@ -0,0 +1,110 @@ +use clap::arg; +use soroban_env_host::xdr::{ + self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, + ScVal, +}; +use std::path::PathBuf; + +use crate::{ + commands::contract::Durability, + utils::{self}, + wasm, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Spec(#[from] soroban_spec_tools::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error("cannot parse contract ID {0}: {1}")] + CannotParseContractId(String, stellar_strkey::DecodeError), + #[error(transparent)] + Wasm(#[from] wasm::Error), +} + +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Contract ID to which owns the data entries. + /// If no keys provided the Contract's instance will be extended + #[arg( + long = "id", + required_unless_present = "wasm", + required_unless_present = "wasm_hash" + )] + pub contract_id: Option, + /// Storage key (symbols only) + #[arg(long = "key", conflicts_with = "key_xdr")] + pub key: Option>, + /// Storage key (base64-encoded XDR) + #[arg(long = "key-xdr", conflicts_with = "key")] + pub key_xdr: Option>, + /// Path to Wasm file of contract code to extend + #[arg( + long, + conflicts_with = "contract_id", + conflicts_with = "key", + conflicts_with = "key_xdr", + conflicts_with = "wasm_hash" + )] + pub wasm: Option, + /// Path to Wasm file of contract code to extend + #[arg( + long, + conflicts_with = "contract_id", + conflicts_with = "key", + conflicts_with = "key_xdr", + conflicts_with = "wasm" + )] + pub wasm_hash: Option, + /// Storage entry durability + #[arg(long, value_enum, required = true)] + pub durability: Durability, +} + +impl Args { + pub fn parse_keys(&self) -> Result, Error> { + let keys = if let Some(keys) = &self.key { + keys.iter() + .map(|key| { + Ok(soroban_spec_tools::from_string_primitive( + key, + &xdr::ScSpecTypeDef::Symbol, + )?) + }) + .collect::, Error>>()? + } else if let Some(keys) = &self.key_xdr { + keys.iter() + .map(|s| Ok(ScVal::from_xdr_base64(s, Limits::none())?)) + .collect::, Error>>()? + } else if let Some(wasm) = &self.wasm { + return Ok(vec![crate::wasm::Args { wasm: wasm.clone() }.try_into()?]); + } else if let Some(wasm_hash) = &self.wasm_hash { + return Ok(vec![LedgerKey::ContractCode(LedgerKeyContractCode { + hash: xdr::Hash( + utils::contract_id_from_str(wasm_hash) + .map_err(|e| Error::CannotParseContractId(wasm_hash.clone(), e))?, + ), + })]); + } else { + vec![ScVal::LedgerKeyContractInstance] + }; + let contract_id = contract_id(self.contract_id.as_ref().unwrap())?; + + Ok(keys + .into_iter() + .map(|key| { + LedgerKey::ContractData(LedgerKeyContractData { + contract: ScAddress::Contract(xdr::Hash(contract_id)), + durability: (&self.durability).into(), + key, + }) + }) + .collect()) + } +} + +fn contract_id(s: &str) -> Result<[u8; 32], Error> { + utils::contract_id_from_str(s).map_err(|e| Error::CannotParseContractId(s.to_string(), e)) +} diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs new file mode 100644 index 00000000..3aad487c --- /dev/null +++ b/cmd/soroban-cli/src/lib.rs @@ -0,0 +1,53 @@ +#![allow( + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::missing_panics_doc +)] +pub mod commands; +pub mod fee; +pub mod key; +pub mod log; +pub mod rpc; +pub mod toid; +pub mod utils; +pub mod wasm; + +use std::path::Path; + +pub use commands::Root; + +pub fn parse_cmd(s: &str) -> Result +where + T: clap::CommandFactory + clap::FromArgMatches, +{ + let input = shlex::split(s).ok_or_else(|| { + clap::Error::raw( + clap::error::ErrorKind::InvalidValue, + format!("Invalid input for command:\n{s}"), + ) + })?; + T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(input)) +} + +pub trait CommandParser { + fn parse(s: &str) -> Result; + + fn parse_arg_vec(s: &[&str]) -> Result; +} + +impl CommandParser for T +where + T: clap::CommandFactory + clap::FromArgMatches, +{ + fn parse(s: &str) -> Result { + parse_cmd(s) + } + + fn parse_arg_vec(args: &[&str]) -> Result { + T::from_arg_matches_mut(&mut T::command().no_binary_name(true).get_matches_from(args)) + } +} + +pub trait Pwd { + fn set_pwd(&mut self, pwd: &Path); +} diff --git a/cmd/soroban-cli/src/log.rs b/cmd/soroban-cli/src/log.rs new file mode 100644 index 00000000..16121982 --- /dev/null +++ b/cmd/soroban-cli/src/log.rs @@ -0,0 +1,13 @@ +pub mod auth; +pub mod budget; +pub mod cost; +pub mod diagnostic_event; +pub mod footprint; +pub mod host_event; + +pub use auth::*; +pub use budget::*; +pub use cost::*; +pub use diagnostic_event::*; +pub use footprint::*; +pub use host_event::*; diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs new file mode 100644 index 00000000..c37e7ed3 --- /dev/null +++ b/cmd/soroban-cli/src/log/auth.rs @@ -0,0 +1,7 @@ +use soroban_env_host::xdr::{SorobanAuthorizationEntry, VecM}; + +pub fn auth(auth: &[VecM]) { + if !auth.is_empty() { + tracing::debug!("{auth:#?}"); + } +} diff --git a/cmd/soroban-cli/src/log/budget.rs b/cmd/soroban-cli/src/log/budget.rs new file mode 100644 index 00000000..59ff4aad --- /dev/null +++ b/cmd/soroban-cli/src/log/budget.rs @@ -0,0 +1,5 @@ +use soroban_env_host::budget::Budget; + +pub fn budget(budget: &Budget) { + tracing::debug!("{budget:#?}"); +} diff --git a/cmd/soroban-cli/src/log/cost.rs b/cmd/soroban-cli/src/log/cost.rs new file mode 100644 index 00000000..3e049a6c --- /dev/null +++ b/cmd/soroban-cli/src/log/cost.rs @@ -0,0 +1,27 @@ +use soroban_env_host::xdr::SorobanResources; +use std::fmt::{Debug, Display}; + +struct Cost<'a>(&'a SorobanResources); + +impl Debug for Cost<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO: Should we output the footprint here? + writeln!(f, "==================== Cost ====================")?; + writeln!(f, "CPU used: {}", self.0.instructions,)?; + writeln!(f, "Bytes read: {}", self.0.read_bytes,)?; + writeln!(f, "Bytes written: {}", self.0.write_bytes,)?; + writeln!(f, "==============================================")?; + Ok(()) + } +} + +impl Display for Cost<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +pub fn cost(resources: &SorobanResources) { + let cost = Cost(resources); + tracing::debug!(?cost); +} diff --git a/cmd/soroban-cli/src/log/diagnostic_event.rs b/cmd/soroban-cli/src/log/diagnostic_event.rs new file mode 100644 index 00000000..68af67a4 --- /dev/null +++ b/cmd/soroban-cli/src/log/diagnostic_event.rs @@ -0,0 +1,11 @@ +pub fn diagnostic_events(events: &[impl std::fmt::Debug], level: tracing::Level) { + for (i, event) in events.iter().enumerate() { + if level == tracing::Level::TRACE { + tracing::trace!("{i}: {event:#?}"); + } else if level == tracing::Level::INFO { + tracing::info!("{i}: {event:#?}"); + } else if level == tracing::Level::ERROR { + tracing::error!("{i}: {event:#?}"); + } + } +} diff --git a/cmd/soroban-cli/src/log/footprint.rs b/cmd/soroban-cli/src/log/footprint.rs new file mode 100644 index 00000000..bfbc9f7a --- /dev/null +++ b/cmd/soroban-cli/src/log/footprint.rs @@ -0,0 +1,5 @@ +use soroban_env_host::xdr::LedgerFootprint; + +pub fn footprint(footprint: &LedgerFootprint) { + tracing::debug!("{footprint:#?}"); +} diff --git a/cmd/soroban-cli/src/log/host_event.rs b/cmd/soroban-cli/src/log/host_event.rs new file mode 100644 index 00000000..4238a74c --- /dev/null +++ b/cmd/soroban-cli/src/log/host_event.rs @@ -0,0 +1,7 @@ +use soroban_env_host::events::HostEvent; + +pub fn host_events(events: &[HostEvent]) { + for (i, event) in events.iter().enumerate() { + tracing::info!("{i}: {event:#?}"); + } +} diff --git a/cmd/soroban-cli/src/rpc/fixtures/event_response.json b/cmd/soroban-cli/src/rpc/fixtures/event_response.json new file mode 100644 index 00000000..6f520fdf --- /dev/null +++ b/cmd/soroban-cli/src/rpc/fixtures/event_response.json @@ -0,0 +1,39 @@ +{ + "events": [{ + "eventType": "system", + "ledger": "43601283", + "ledgerClosedAt": "2022-11-16T16:10:41Z", + "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", + "id": "0164090849041387521-0000000003", + "pagingToken": "164090849041387521-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + }, { + "eventType": "contract", + "ledger": "43601284", + "ledgerClosedAt": "2022-11-16T16:10:41Z", + "contractId": "CDR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OO5Z", + "id": "0164090849041387521-0000000003", + "pagingToken": "164090849041387521-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + }, { + "eventType": "system", + "ledger": "43601285", + "ledgerClosedAt": "2022-11-16T16:10:41Z", + "contractId": "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2", + "id": "0164090849041387521-0000000003", + "pagingToken": "164090849041387521-3", + "topic": [ + "AAAABQAAAAh0cmFuc2Zlcg==", + "AAAAAQB6Mcc=" + ], + "value": "AAAABQAAAApHaWJNb255UGxzAAA=" + }] +} \ No newline at end of file diff --git a/cmd/soroban-cli/src/rpc/mod.rs b/cmd/soroban-cli/src/rpc/mod.rs new file mode 100644 index 00000000..53542629 --- /dev/null +++ b/cmd/soroban-cli/src/rpc/mod.rs @@ -0,0 +1,1141 @@ +use http::{uri::Authority, Uri}; +use itertools::Itertools; +use jsonrpsee_core::params::ObjectParams; +use jsonrpsee_core::{self, client::ClientT, rpc_params}; +use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder}; +use serde_aux::prelude::{ + deserialize_default_from_null, deserialize_number_from_string, + deserialize_option_number_from_string, +}; +use soroban_env_host::xdr::{ + self, AccountEntry, AccountId, ContractDataEntry, DiagnosticEvent, Error as XdrError, + LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, Limited, PublicKey, ReadXdr, + SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData, Transaction, + TransactionEnvelope, TransactionMeta, TransactionMetaV3, TransactionResult, Uint256, VecM, + WriteXdr, +}; +use soroban_sdk::token; +use soroban_sdk::xdr::Limits; +use std::{ + fmt::Display, + str::FromStr, + time::{Duration, Instant}, +}; +use termcolor::{Color, ColorChoice, StandardStream, WriteColor}; +use termcolor_output::colored; +use tokio::time::sleep; + +use crate::utils::contract_spec; + +mod txn; + +const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); + +pub type LogEvents = fn( + footprint: &LedgerFootprint, + auth: &[VecM], + events: &[DiagnosticEvent], +) -> (); + +pub type LogResources = fn(resources: &SorobanResources) -> (); + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + InvalidAddress(#[from] stellar_strkey::DecodeError), + #[error("invalid response from server")] + InvalidResponse, + #[error("provided network passphrase {expected:?} does not match the server: {server:?}")] + InvalidNetworkPassphrase { expected: String, server: String }, + #[error("xdr processing error: {0}")] + Xdr(#[from] XdrError), + #[error("invalid rpc url: {0}")] + InvalidRpcUrl(http::uri::InvalidUri), + #[error("invalid rpc url: {0}")] + InvalidRpcUrlFromUriParts(http::uri::InvalidUriParts), + #[error("invalid friendbot url: {0}")] + InvalidUrl(String), + #[error("jsonrpc error: {0}")] + JsonRpc(#[from] jsonrpsee_core::Error), + #[error("json decoding error: {0}")] + Serde(#[from] serde_json::Error), + #[error("transaction failed: {0}")] + TransactionFailed(String), + #[error("transaction submission failed: {0}")] + TransactionSubmissionFailed(String), + #[error("expected transaction status: {0}")] + UnexpectedTransactionStatus(String), + #[error("transaction submission timeout")] + TransactionSubmissionTimeout, + #[error("transaction simulation failed: {0}")] + TransactionSimulationFailed(String), + #[error("{0} not found: {1}")] + NotFound(String, String), + #[error("Missing result in successful response")] + MissingResult, + #[error("Failed to read Error response from server")] + MissingError, + #[error("Missing signing key for account {address}")] + MissingSignerForAddress { address: String }, + #[error("cursor is not valid")] + InvalidCursor, + #[error("unexpected ({length}) simulate transaction result length")] + UnexpectedSimulateTransactionResultSize { length: usize }, + #[error("unexpected ({count}) number of operations")] + UnexpectedOperationCount { count: usize }, + #[error("Transaction contains unsupported operation type")] + UnsupportedOperationType, + #[error("unexpected contract code data type: {0:?}")] + UnexpectedContractCodeDataType(LedgerEntryData), + #[error(transparent)] + CouldNotParseContractSpec(#[from] contract_spec::Error), + #[error("unexpected contract code got token")] + UnexpectedToken(ContractDataEntry), + #[error(transparent)] + Spec(#[from] soroban_spec::read::FromWasmError), + #[error(transparent)] + SpecBase64(#[from] soroban_spec::read::ParseSpecBase64Error), + #[error("Fee was too large {0}")] + LargeFee(u64), + #[error("Cannot authorize raw transactions")] + CannotAuthorizeRawTransaction, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct SendTransactionResponse { + pub hash: String, + pub status: String, + #[serde( + rename = "errorResultXdr", + skip_serializing_if = "Option::is_none", + default + )] + pub error_result_xdr: Option, + #[serde(rename = "latestLedger")] + pub latest_ledger: u32, + #[serde( + rename = "latestLedgerCloseTime", + deserialize_with = "deserialize_number_from_string" + )] + pub latest_ledger_close_time: u32, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetTransactionResponseRaw { + pub status: String, + #[serde( + rename = "envelopeXdr", + skip_serializing_if = "Option::is_none", + default + )] + pub envelope_xdr: Option, + #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)] + pub result_xdr: Option, + #[serde( + rename = "resultMetaXdr", + skip_serializing_if = "Option::is_none", + default + )] + pub result_meta_xdr: Option, + // TODO: add ledger info and application order +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetTransactionResponse { + pub status: String, + pub envelope: Option, + pub result: Option, + pub result_meta: Option, +} + +impl TryInto for GetTransactionResponseRaw { + type Error = xdr::Error; + + fn try_into(self) -> Result { + Ok(GetTransactionResponse { + status: self.status, + envelope: self + .envelope_xdr + .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) + .transpose()?, + result: self + .result_xdr + .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) + .transpose()?, + result_meta: self + .result_meta_xdr + .map(|v| ReadXdr::from_xdr_base64(v, Limits::none())) + .transpose()?, + }) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct LedgerEntryResult { + pub key: String, + pub xdr: String, + #[serde(rename = "lastModifiedLedgerSeq")] + pub last_modified_ledger: u32, + #[serde( + rename = "liveUntilLedgerSeq", + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_option_number_from_string", + default + )] + pub live_until_ledger_seq_ledger_seq: Option, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetLedgerEntriesResponse { + pub entries: Option>, + #[serde(rename = "latestLedger")] + pub latest_ledger: i64, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetNetworkResponse { + #[serde( + rename = "friendbotUrl", + skip_serializing_if = "Option::is_none", + default + )] + pub friendbot_url: Option, + pub passphrase: String, + #[serde(rename = "protocolVersion")] + pub protocol_version: u32, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetLatestLedgerResponse { + pub id: String, + #[serde(rename = "protocolVersion")] + pub protocol_version: u32, + pub sequence: u32, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] +pub struct Cost { + #[serde( + rename = "cpuInsns", + deserialize_with = "deserialize_number_from_string" + )] + pub cpu_insns: u64, + #[serde( + rename = "memBytes", + deserialize_with = "deserialize_number_from_string" + )] + pub mem_bytes: u64, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct SimulateHostFunctionResultRaw { + #[serde(deserialize_with = "deserialize_default_from_null")] + pub auth: Vec, + pub xdr: String, +} + +#[derive(Debug)] +pub struct SimulateHostFunctionResult { + pub auth: Vec, + pub xdr: xdr::ScVal, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] +pub struct SimulateTransactionResponse { + #[serde( + rename = "minResourceFee", + deserialize_with = "deserialize_number_from_string", + default + )] + pub min_resource_fee: u64, + #[serde(default)] + pub cost: Cost, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub results: Vec, + #[serde(rename = "transactionData", default)] + pub transaction_data: String, + #[serde( + deserialize_with = "deserialize_default_from_null", + skip_serializing_if = "Vec::is_empty", + default + )] + pub events: Vec, + #[serde( + rename = "restorePreamble", + skip_serializing_if = "Option::is_none", + default + )] + pub restore_preamble: Option, + #[serde(rename = "latestLedger")] + pub latest_ledger: u32, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub error: Option, +} + +impl SimulateTransactionResponse { + pub fn results(&self) -> Result, Error> { + self.results + .iter() + .map(|r| { + Ok(SimulateHostFunctionResult { + auth: r + .auth + .iter() + .map(|a| { + Ok(SorobanAuthorizationEntry::from_xdr_base64( + a, + Limits::none(), + )?) + }) + .collect::>()?, + xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::none())?, + }) + }) + .collect() + } + + pub fn events(&self) -> Result, Error> { + self.events + .iter() + .map(|e| Ok(DiagnosticEvent::from_xdr_base64(e, Limits::none())?)) + .collect() + } + + pub fn transaction_data(&self) -> Result { + Ok(SorobanTransactionData::from_xdr_base64( + &self.transaction_data, + Limits::none(), + )?) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Default)] +pub struct RestorePreamble { + #[serde(rename = "transactionData")] + pub transaction_data: String, + #[serde( + rename = "minResourceFee", + deserialize_with = "deserialize_number_from_string" + )] + pub min_resource_fee: u64, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct GetEventsResponse { + #[serde(deserialize_with = "deserialize_default_from_null")] + pub events: Vec, + #[serde(rename = "latestLedger")] + pub latest_ledger: u32, +} + +// Determines whether or not a particular filter matches a topic based on the +// same semantics as the RPC server: +// +// - for an exact segment match, the filter is a base64-encoded ScVal +// - for a wildcard, single-segment match, the string "*" matches exactly one +// segment +// +// The expectation is that a `filter` is a comma-separated list of segments that +// has previously been validated, and `topic` is the list of segments applicable +// for this event. +// +// [API +// Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx) +// [Code +// Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203) +pub fn does_topic_match(topic: &[String], filter: &[String]) -> bool { + filter.len() == topic.len() + && filter + .iter() + .enumerate() + .all(|(i, s)| *s == "*" || topic[i] == *s) +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Event { + #[serde(rename = "type")] + pub event_type: String, + + pub ledger: u32, + #[serde(rename = "ledgerClosedAt")] + pub ledger_closed_at: String, + + pub id: String, + #[serde(rename = "pagingToken")] + pub paging_token: String, + + #[serde(rename = "contractId")] + pub contract_id: String, + pub topic: Vec, + pub value: String, +} + +impl Display for Event { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "Event {} [{}]:", + self.paging_token, + self.event_type.to_ascii_uppercase() + )?; + writeln!( + f, + " Ledger: {} (closed at {})", + self.ledger, self.ledger_closed_at + )?; + writeln!(f, " Contract: {}", self.contract_id)?; + writeln!(f, " Topics:")?; + for topic in &self.topic { + let scval = + xdr::ScVal::from_xdr_base64(topic, Limits::none()).map_err(|_| std::fmt::Error)?; + writeln!(f, " {scval:?}")?; + } + let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none()) + .map_err(|_| std::fmt::Error)?; + writeln!(f, " Value: {scval:?}") + } +} + +impl Event { + pub fn parse_cursor(&self) -> Result<(u64, i32), Error> { + parse_cursor(&self.id) + } + + pub fn pretty_print(&self) -> Result<(), Box> { + let mut stdout = StandardStream::stdout(ColorChoice::Auto); + if !stdout.supports_color() { + println!("{self}"); + return Ok(()); + } + + let color = match self.event_type.as_str() { + "system" => Color::Yellow, + _ => Color::Blue, + }; + colored!( + stdout, + "{}Event{} {}{}{} [{}{}{}{}]:\n", + bold!(true), + bold!(false), + fg!(Some(Color::Green)), + self.paging_token, + reset!(), + bold!(true), + fg!(Some(color)), + self.event_type.to_ascii_uppercase(), + reset!(), + )?; + + colored!( + stdout, + " Ledger: {}{}{} (closed at {}{}{})\n", + fg!(Some(Color::Green)), + self.ledger, + reset!(), + fg!(Some(Color::Green)), + self.ledger_closed_at, + reset!(), + )?; + + colored!( + stdout, + " Contract: {}{}{}\n", + fg!(Some(Color::Green)), + self.contract_id, + reset!(), + )?; + + colored!(stdout, " Topics:\n")?; + for topic in &self.topic { + let scval = xdr::ScVal::from_xdr_base64(topic, Limits::none())?; + colored!( + stdout, + " {}{:?}{}\n", + fg!(Some(Color::Green)), + scval, + reset!(), + )?; + } + + let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::none())?; + colored!( + stdout, + " Value: {}{:?}{}\n", + fg!(Some(Color::Green)), + scval, + reset!(), + )?; + + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)] +pub enum EventType { + All, + Contract, + System, +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum EventStart { + Ledger(u32), + Cursor(String), +} + +#[derive(Debug)] +pub struct FullLedgerEntry { + pub key: LedgerKey, + pub val: LedgerEntryData, + pub last_modified_ledger: u32, + pub live_until_ledger_seq: u32, +} + +#[derive(Debug)] +pub struct FullLedgerEntries { + pub entries: Vec, + pub latest_ledger: i64, +} + +pub struct Client { + base_url: String, +} + +impl Client { + pub fn new(base_url: &str) -> Result { + // Add the port to the base URL if there is no port explicitly included + // in the URL and the scheme allows us to infer a default port. + // Jsonrpsee requires a port to always be present even if one can be + // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048. + let uri = base_url.parse::().map_err(Error::InvalidRpcUrl)?; + let mut parts = uri.into_parts(); + if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) { + if authority.port().is_none() { + let port = match scheme.as_str() { + "http" => Some(80), + "https" => Some(443), + _ => None, + }; + if let Some(port) = port { + let host = authority.host(); + parts.authority = Some( + Authority::from_str(&format!("{host}:{port}")) + .map_err(Error::InvalidRpcUrl)?, + ); + } + } + } + let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?; + tracing::trace!(?uri); + Ok(Self { + base_url: uri.to_string(), + }) + } + + fn client(&self) -> Result { + let url = self.base_url.clone(); + let mut headers = HeaderMap::new(); + headers.insert("X-Client-Name", "soroban-cli".parse().unwrap()); + let version = VERSION.unwrap_or("devel"); + headers.insert("X-Client-Version", version.parse().unwrap()); + Ok(HttpClientBuilder::default() + .set_headers(headers) + .build(url)?) + } + + pub async fn friendbot_url(&self) -> Result { + let network = self.get_network().await?; + tracing::trace!("{network:#?}"); + network.friendbot_url.ok_or_else(|| { + Error::NotFound( + "Friendbot".to_string(), + "Friendbot is not available on this network".to_string(), + ) + }) + } + + pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result { + let server = self.get_network().await?.passphrase; + if let Some(expected) = expected { + if expected != server { + return Err(Error::InvalidNetworkPassphrase { + expected: expected.to_string(), + server, + }); + } + } + Ok(server) + } + + pub async fn get_network(&self) -> Result { + tracing::trace!("Getting network"); + Ok(self.client()?.request("getNetwork", rpc_params![]).await?) + } + + pub async fn get_latest_ledger(&self) -> Result { + tracing::trace!("Getting latest ledger"); + Ok(self + .client()? + .request("getLatestLedger", rpc_params![]) + .await?) + } + + pub async fn get_account(&self, address: &str) -> Result { + tracing::trace!("Getting address {}", address); + let key = LedgerKey::Account(LedgerKeyAccount { + account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( + stellar_strkey::ed25519::PublicKey::from_string(address)?.0, + ))), + }); + let keys = Vec::from([key]); + let response = self.get_ledger_entries(&keys).await?; + let entries = response.entries.unwrap_or_default(); + if entries.is_empty() { + return Err(Error::NotFound( + "Account".to_string(), + format!( + r#"{address} +Might need to fund account like: +soroban config identity fund {address} --network +soroban config identity fund {address} --helper-url "# + ), + )); + } + let ledger_entry = &entries[0]; + let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::none()); + if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? { + tracing::trace!(account=?entry); + Ok(entry) + } else { + Err(Error::InvalidResponse) + } + } + + pub async fn send_transaction( + &self, + tx: &TransactionEnvelope, + ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { + let client = self.client()?; + tracing::trace!("Sending:\n{tx:#?}"); + let SendTransactionResponse { + hash, + error_result_xdr, + status, + .. + } = client + .request( + "sendTransaction", + rpc_params![tx.to_xdr_base64(Limits::none())?], + ) + .await + .map_err(|err| { + Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}")) + })?; + + if status == "ERROR" { + let error = error_result_xdr + .ok_or(Error::MissingError) + .and_then(|x| { + TransactionResult::read_xdr_base64(&mut Limited::new( + x.as_bytes(), + Limits::none(), + )) + .map_err(|_| Error::InvalidResponse) + }) + .map(|r| r.result); + tracing::error!("TXN failed:\n {error:#?}"); + return Err(Error::TransactionSubmissionFailed(format!("{:#?}", error?))); + } + // even if status == "success" we need to query the transaction status in order to get the result + + // Poll the transaction status + let start = Instant::now(); + loop { + let response: GetTransactionResponse = self.get_transaction(&hash).await?.try_into()?; + match response.status.as_str() { + "SUCCESS" => { + // TODO: the caller should probably be printing this + tracing::trace!("{response:#?}"); + let GetTransactionResponse { + result, + result_meta, + .. + } = response; + let meta = result_meta.ok_or(Error::MissingResult)?; + let events = extract_events(&meta); + return Ok((result.ok_or(Error::MissingResult)?, meta, events)); + } + "FAILED" => { + tracing::error!("{response:#?}"); + // TODO: provide a more elaborate error + return Err(Error::TransactionSubmissionFailed(format!( + "{:#?}", + response.result + ))); + } + "NOT_FOUND" => (), + _ => { + return Err(Error::UnexpectedTransactionStatus(response.status)); + } + }; + let duration = start.elapsed(); + // TODO: parameterize the timeout instead of using a magic constant + if duration.as_secs() > 10 { + return Err(Error::TransactionSubmissionTimeout); + } + sleep(Duration::from_secs(1)).await; + } + } + + pub async fn simulate_transaction( + &self, + tx: &TransactionEnvelope, + ) -> Result { + tracing::trace!("Simulating:\n{tx:#?}"); + let base64_tx = tx.to_xdr_base64(Limits::none())?; + let mut builder = ObjectParams::new(); + builder.insert("transaction", base64_tx)?; + let response: SimulateTransactionResponse = self + .client()? + .request("simulateTransaction", builder) + .await?; + tracing::trace!("Simulation response:\n {response:#?}"); + match response.error { + None => Ok(response), + Some(e) => { + crate::log::diagnostic_events(&response.events, tracing::Level::ERROR); + Err(Error::TransactionSimulationFailed(e)) + } + } + } + + pub async fn prepare_and_send_transaction( + &self, + tx_without_preflight: &Transaction, + source_key: &ed25519_dalek::SigningKey, + signers: &[ed25519_dalek::SigningKey], + network_passphrase: &str, + log_events: Option, + log_resources: Option, + ) -> Result<(TransactionResult, TransactionMeta, Vec), Error> { + let txn = txn::Assembled::new(tx_without_preflight, self).await?; + let seq_num = txn.sim_res().latest_ledger + 60; //5 min; + let authorized = txn + .handle_restore(self, source_key, network_passphrase) + .await? + .authorize(self, source_key, signers, seq_num, network_passphrase) + .await?; + authorized.log(log_events, log_resources)?; + let tx = authorized.sign(source_key, network_passphrase)?; + self.send_transaction(&tx).await + } + + pub async fn get_transaction(&self, tx_id: &str) -> Result { + Ok(self + .client()? + .request("getTransaction", rpc_params![tx_id]) + .await?) + } + + pub async fn get_ledger_entries( + &self, + keys: &[LedgerKey], + ) -> Result { + let mut base64_keys: Vec = vec![]; + for k in keys { + let base64_result = k.to_xdr_base64(Limits::none()); + if base64_result.is_err() { + return Err(Error::Xdr(XdrError::Invalid)); + } + base64_keys.push(k.to_xdr_base64(Limits::none()).unwrap()); + } + Ok(self + .client()? + .request("getLedgerEntries", rpc_params![base64_keys]) + .await?) + } + + pub async fn get_full_ledger_entries( + &self, + ledger_keys: &[LedgerKey], + ) -> Result { + let keys = ledger_keys + .iter() + .filter(|key| !matches!(key, LedgerKey::Ttl(_))) + .map(Clone::clone) + .collect::>(); + tracing::trace!("keys: {keys:#?}"); + let GetLedgerEntriesResponse { + entries, + latest_ledger, + } = self.get_ledger_entries(&keys).await?; + tracing::trace!("raw: {entries:#?}"); + let entries = entries + .unwrap_or_default() + .iter() + .map( + |LedgerEntryResult { + key, + xdr, + last_modified_ledger, + live_until_ledger_seq_ledger_seq, + }| { + Ok(FullLedgerEntry { + key: LedgerKey::from_xdr_base64(key, Limits::none())?, + val: LedgerEntryData::from_xdr_base64(xdr, Limits::none())?, + live_until_ledger_seq: live_until_ledger_seq_ledger_seq.unwrap_or_default(), + last_modified_ledger: *last_modified_ledger, + }) + }, + ) + .collect::, Error>>()?; + tracing::trace!("parsed: {entries:#?}"); + Ok(FullLedgerEntries { + entries, + latest_ledger, + }) + } + + pub async fn get_events( + &self, + start: EventStart, + event_type: Option, + contract_ids: &[String], + topics: &[String], + limit: Option, + ) -> Result { + let mut filters = serde_json::Map::new(); + + event_type + .and_then(|t| match t { + EventType::All => None, // all is the default, so avoid incl. the param + EventType::Contract => Some("contract"), + EventType::System => Some("system"), + }) + .map(|t| filters.insert("type".to_string(), t.into())); + + filters.insert("topics".to_string(), topics.into()); + filters.insert("contractIds".to_string(), contract_ids.into()); + + let mut pagination = serde_json::Map::new(); + if let Some(limit) = limit { + pagination.insert("limit".to_string(), limit.into()); + } + + let mut oparams = ObjectParams::new(); + match start { + EventStart::Ledger(l) => oparams.insert("startLedger", l)?, + EventStart::Cursor(c) => { + pagination.insert("cursor".to_string(), c.into()); + } + }; + oparams.insert("filters", vec![filters])?; + oparams.insert("pagination", pagination)?; + + Ok(self.client()?.request("getEvents", oparams).await?) + } + + pub async fn get_contract_data( + &self, + contract_id: &[u8; 32], + ) -> Result { + // Get the contract from the network + let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData { + contract: xdr::ScAddress::Contract(xdr::Hash(*contract_id)), + key: xdr::ScVal::LedgerKeyContractInstance, + durability: xdr::ContractDataDurability::Persistent, + }); + let contract_ref = self.get_ledger_entries(&[contract_key]).await?; + let entries = contract_ref.entries.unwrap_or_default(); + if entries.is_empty() { + let contract_address = stellar_strkey::Contract(*contract_id).to_string(); + return Err(Error::NotFound("Contract".to_string(), contract_address)); + } + let contract_ref_entry = &entries[0]; + match LedgerEntryData::from_xdr_base64(&contract_ref_entry.xdr, Limits::none())? { + LedgerEntryData::ContractData(contract_data) => Ok(contract_data), + scval => Err(Error::UnexpectedContractCodeDataType(scval)), + } + } + + pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result, Error> { + match self.get_contract_data(contract_id).await? { + xdr::ContractDataEntry { + val: + xdr::ScVal::ContractInstance(xdr::ScContractInstance { + executable: xdr::ContractExecutable::Wasm(hash), + .. + }), + .. + } => self.get_remote_wasm_from_hash(hash).await, + scval => Err(Error::UnexpectedToken(scval)), + } + } + + pub async fn get_remote_wasm_from_hash(&self, hash: xdr::Hash) -> Result, Error> { + let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); + let contract_data = self.get_ledger_entries(&[code_key]).await?; + let entries = contract_data.entries.unwrap_or_default(); + if entries.is_empty() { + return Err(Error::NotFound( + "Contract Code".to_string(), + hex::encode(hash), + )); + } + let contract_data_entry = &entries[0]; + match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? { + LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()), + scval => Err(Error::UnexpectedContractCodeDataType(scval)), + } + } + + pub async fn get_remote_contract_spec( + &self, + contract_id: &[u8; 32], + ) -> Result, Error> { + let contract_data = self.get_contract_data(contract_id).await?; + match contract_data.val { + xdr::ScVal::ContractInstance(xdr::ScContractInstance { + executable: xdr::ContractExecutable::Wasm(hash), + .. + }) => Ok(contract_spec::ContractSpec::new( + &self.get_remote_wasm_from_hash(hash).await?, + ) + .map_err(Error::CouldNotParseContractSpec)? + .spec), + xdr::ScVal::ContractInstance(xdr::ScContractInstance { + executable: xdr::ContractExecutable::StellarAsset, + .. + }) => Ok(soroban_spec::read::parse_raw( + &token::StellarAssetSpec::spec_xdr(), + )?), + _ => Err(Error::Xdr(XdrError::Invalid)), + } + } +} + +fn extract_events(tx_meta: &TransactionMeta) -> Vec { + match tx_meta { + TransactionMeta::V3(TransactionMetaV3 { + soroban_meta: Some(meta), + .. + }) => { + // NOTE: we assume there can only be one operation, since we only send one + if meta.diagnostic_events.len() == 1 { + meta.diagnostic_events.clone().into() + } else if meta.events.len() == 1 { + meta.events + .iter() + .map(|e| DiagnosticEvent { + in_successful_contract_call: true, + event: e.clone(), + }) + .collect() + } else { + Vec::new() + } + } + _ => Vec::new(), + } +} + +pub fn parse_cursor(c: &str) -> Result<(u64, i32), Error> { + let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?; + let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?; + let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?; + Ok((toid_part, start_index)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simulation_transaction_response_parsing() { + let s = r#"{ + "minResourceFee": "100000000", + "cost": { "cpuInsns": "1000", "memBytes": "1000" }, + "transactionData": "", + "latestLedger": 1234 + }"#; + + let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); + assert_eq!(resp.min_resource_fee, 100_000_000); + } + + #[test] + fn simulation_transaction_response_parsing_mostly_empty() { + let s = r#"{ + "latestLedger": 1234 + }"#; + + let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap(); + assert_eq!(resp.latest_ledger, 1_234); + } + + #[test] + fn test_rpc_url_default_ports() { + // Default ports are added. + let client = Client::new("http://example.com").unwrap(); + assert_eq!(client.base_url, "http://example.com:80/"); + let client = Client::new("https://example.com").unwrap(); + assert_eq!(client.base_url, "https://example.com:443/"); + + // Ports are not added when already present. + let client = Client::new("http://example.com:8080").unwrap(); + assert_eq!(client.base_url, "http://example.com:8080/"); + let client = Client::new("https://example.com:8080").unwrap(); + assert_eq!(client.base_url, "https://example.com:8080/"); + + // Paths are not modified. + let client = Client::new("http://example.com/a/b/c").unwrap(); + assert_eq!(client.base_url, "http://example.com:80/a/b/c"); + let client = Client::new("https://example.com/a/b/c").unwrap(); + assert_eq!(client.base_url, "https://example.com:443/a/b/c"); + let client = Client::new("http://example.com/a/b/c/").unwrap(); + assert_eq!(client.base_url, "http://example.com:80/a/b/c/"); + let client = Client::new("https://example.com/a/b/c/").unwrap(); + assert_eq!(client.base_url, "https://example.com:443/a/b/c/"); + let client = Client::new("http://example.com/a/b:80/c/").unwrap(); + assert_eq!(client.base_url, "http://example.com:80/a/b:80/c/"); + let client = Client::new("https://example.com/a/b:80/c/").unwrap(); + assert_eq!(client.base_url, "https://example.com:443/a/b:80/c/"); + } + + #[test] + // Taken from [RPC server + // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21). + fn test_does_topic_match() { + struct TestCase<'a> { + name: &'a str, + filter: Vec<&'a str>, + includes: Vec>, + excludes: Vec>, + } + + let xfer = "AAAABQAAAAh0cmFuc2Zlcg=="; + let number = "AAAAAQB6Mcc="; + let star = "*"; + + for tc in vec![ + // No filter means match nothing. + TestCase { + name: "", + filter: vec![], + includes: vec![], + excludes: vec![vec![xfer]], + }, + // "*" should match "transfer/" but not "transfer/transfer" or + // "transfer/amount", because * is specified as a SINGLE segment + // wildcard. + TestCase { + name: "*", + filter: vec![star], + includes: vec![vec![xfer]], + excludes: vec![vec![xfer, xfer], vec![xfer, number]], + }, + // "*/transfer" should match anything preceding "transfer", but + // nothing that isn't exactly two segments long. + TestCase { + name: "*/transfer", + filter: vec![star, xfer], + includes: vec![vec![number, xfer], vec![xfer, xfer]], + excludes: vec![ + vec![number], + vec![number, number], + vec![number, xfer, number], + vec![xfer], + vec![xfer, number], + vec![xfer, xfer, xfer], + ], + }, + // The inverse case of before: "transfer/*" should match any single + // segment after a segment that is exactly "transfer", but no + // additional segments. + TestCase { + name: "transfer/*", + filter: vec![xfer, star], + includes: vec![vec![xfer, number], vec![xfer, xfer]], + excludes: vec![ + vec![number], + vec![number, number], + vec![number, xfer, number], + vec![xfer], + vec![number, xfer], + vec![xfer, xfer, xfer], + ], + }, + // Here, we extend to exactly two wild segments after transfer. + TestCase { + name: "transfer/*/*", + filter: vec![xfer, star, star], + includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]], + excludes: vec![ + vec![number], + vec![number, number], + vec![number, xfer], + vec![number, xfer, number, number], + vec![xfer], + vec![xfer, xfer, xfer, xfer], + ], + }, + // Here, we ensure wildcards can be in the middle of a filter: only + // exact matches happen on the ends, while the middle can be + // anything. + TestCase { + name: "transfer/*/number", + filter: vec![xfer, star, number], + includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]], + excludes: vec![ + vec![number], + vec![number, number], + vec![number, number, number], + vec![number, xfer, number], + vec![xfer], + vec![number, xfer], + vec![xfer, xfer, xfer], + vec![xfer, number, xfer], + ], + }, + ] { + for topic in tc.includes { + assert!( + does_topic_match( + &topic + .iter() + .map(std::string::ToString::to_string) + .collect::>(), + &tc.filter + .iter() + .map(std::string::ToString::to_string) + .collect::>() + ), + "test: {}, topic ({:?}) should be matched by filter ({:?})", + tc.name, + topic, + tc.filter + ); + } + + for topic in tc.excludes { + assert!( + !does_topic_match( + // make deep copies of the vecs + &topic + .iter() + .map(std::string::ToString::to_string) + .collect::>(), + &tc.filter + .iter() + .map(std::string::ToString::to_string) + .collect::>() + ), + "test: {}, topic ({:?}) should NOT be matched by filter ({:?})", + tc.name, + topic, + tc.filter + ); + } + } + } +} diff --git a/cmd/soroban-cli/src/rpc/txn.rs b/cmd/soroban-cli/src/rpc/txn.rs new file mode 100644 index 00000000..9e36938d --- /dev/null +++ b/cmd/soroban-cli/src/rpc/txn.rs @@ -0,0 +1,610 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; +use soroban_env_host::xdr::{ + self, AccountId, DecoratedSignature, ExtensionPoint, Hash, HashIdPreimage, + HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Memo, Operation, + OperationBody, Preconditions, PublicKey, ReadXdr, RestoreFootprintOp, ScAddress, ScMap, + ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, + SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, SorobanResources, + SorobanTransactionData, Transaction, TransactionEnvelope, TransactionExt, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, VecM, WriteXdr, +}; + +use crate::rpc::{Client, Error, RestorePreamble, SimulateTransactionResponse}; + +use super::{LogEvents, LogResources}; + +pub struct Assembled { + txn: Transaction, + sim_res: SimulateTransactionResponse, +} + +impl Assembled { + pub async fn new(txn: &Transaction, client: &Client) -> Result { + let sim_res = Self::simulate(txn, client).await?; + let txn = assemble(txn, &sim_res)?; + Ok(Self { txn, sim_res }) + } + + pub fn hash(&self, network_passphrase: &str) -> Result<[u8; 32], xdr::Error> { + let signature_payload = TransactionSignaturePayload { + network_id: Hash(Sha256::digest(network_passphrase).into()), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(self.txn.clone()), + }; + Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) + } + + pub fn sign( + self, + key: &ed25519_dalek::SigningKey, + network_passphrase: &str, + ) -> Result { + let tx = self.txn(); + let tx_hash = self.hash(network_passphrase)?; + let tx_signature = key.sign(&tx_hash); + + let decorated_signature = DecoratedSignature { + hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), + signature: Signature(tx_signature.to_bytes().try_into()?), + }; + + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: tx.clone(), + signatures: vec![decorated_signature].try_into()?, + })) + } + + pub async fn simulate( + tx: &Transaction, + client: &Client, + ) -> Result { + client + .simulate_transaction(&TransactionEnvelope::Tx(TransactionV1Envelope { + tx: tx.clone(), + signatures: VecM::default(), + })) + .await + } + + pub async fn handle_restore( + self, + client: &Client, + source_key: &ed25519_dalek::SigningKey, + network_passphrase: &str, + ) -> Result { + if let Some(restore_preamble) = &self.sim_res.restore_preamble { + // Build and submit the restore transaction + client + .send_transaction( + &Assembled::new(&restore(self.txn(), restore_preamble)?, client) + .await? + .sign(source_key, network_passphrase)?, + ) + .await?; + Ok(self.bump_seq_num()) + } else { + Ok(self) + } + } + + pub fn txn(&self) -> &Transaction { + &self.txn + } + + pub fn sim_res(&self) -> &SimulateTransactionResponse { + &self.sim_res + } + + pub async fn authorize( + self, + client: &Client, + source_key: &ed25519_dalek::SigningKey, + signers: &[ed25519_dalek::SigningKey], + seq_num: u32, + network_passphrase: &str, + ) -> Result { + if let Some(txn) = sign_soroban_authorizations( + self.txn(), + source_key, + signers, + seq_num, + network_passphrase, + )? { + Self::new(&txn, client).await + } else { + Ok(self) + } + } + + pub fn bump_seq_num(mut self) -> Self { + self.txn.seq_num.0 += 1; + self + } + + pub fn auth(&self) -> VecM { + self.txn + .operations + .first() + .and_then(|op| match op.body { + OperationBody::InvokeHostFunction(ref body) => (matches!( + body.auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + )) + .then_some(body.auth.clone()), + _ => None, + }) + .unwrap_or_default() + } + + pub fn log( + &self, + log_events: Option, + log_resources: Option, + ) -> Result<(), Error> { + if let TransactionExt::V1(SorobanTransactionData { + resources: resources @ SorobanResources { footprint, .. }, + .. + }) = &self.txn.ext + { + if let Some(log) = log_resources { + log(resources); + } + if let Some(log) = log_events { + log(footprint, &[self.auth()], &self.sim_res.events()?); + }; + } + Ok(()) + } +} + +// Apply the result of a simulateTransaction onto a transaction envelope, preparing it for +// submission to the network. +pub fn assemble( + raw: &Transaction, + simulation: &SimulateTransactionResponse, +) -> Result { + let mut tx = raw.clone(); + + // Right now simulate.results is one-result-per-function, and assumes there is only one + // operation in the txn, so we need to enforce that here. I (Paul) think that is a bug + // in soroban-rpc.simulateTransaction design, and we should fix it there. + // TODO: We should to better handling so non-soroban txns can be a passthrough here. + if tx.operations.len() != 1 { + return Err(Error::UnexpectedOperationCount { + count: tx.operations.len(), + }); + } + + let transaction_data = simulation.transaction_data()?; + + let mut op = tx.operations[0].clone(); + if let OperationBody::InvokeHostFunction(ref mut body) = &mut op.body { + if body.auth.is_empty() { + if simulation.results.len() != 1 { + return Err(Error::UnexpectedSimulateTransactionResultSize { + length: simulation.results.len(), + }); + } + + let auths = simulation + .results + .iter() + .map(|r| { + VecM::try_from( + r.auth + .iter() + .map(|v| SorobanAuthorizationEntry::from_xdr_base64(v, Limits::none())) + .collect::, _>>()?, + ) + }) + .collect::, _>>()?; + if !auths.is_empty() { + body.auth = auths[0].clone(); + } + } + } + + // update the fees of the actual transaction to meet the minimum resource fees. + let classic_transaction_fees = crate::fee::Args::default().fee; + // Pad the fees up by 15% for a bit of wiggle room. + tx.fee = (tx.fee.max( + classic_transaction_fees + + u32::try_from(simulation.min_resource_fee) + .map_err(|_| Error::LargeFee(simulation.min_resource_fee))?, + ) * 115) + / 100; + + tx.operations = vec![op].try_into()?; + tx.ext = TransactionExt::V1(transaction_data); + Ok(tx) +} + +// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given +// transaction. If unable to sign, return an error. +fn sign_soroban_authorizations( + raw: &Transaction, + source_key: &ed25519_dalek::SigningKey, + signers: &[ed25519_dalek::SigningKey], + signature_expiration_ledger: u32, + network_passphrase: &str, +) -> Result, Error> { + let mut tx = raw.clone(); + let mut op = match tx.operations.as_slice() { + [op @ Operation { + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), + .. + }] if matches!( + auth.first().map(|x| &x.root_invocation.function), + Some(&SorobanAuthorizedFunction::ContractFn(_)) + ) => + { + op.clone() + } + _ => return Ok(None), + }; + + let Operation { + body: OperationBody::InvokeHostFunction(ref mut body), + .. + } = op + else { + return Ok(None); + }; + + let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); + + let verification_key = source_key.verifying_key(); + let source_address = verification_key.as_bytes(); + + let signed_auths = body + .auth + .as_slice() + .iter() + .map(|raw_auth| { + let mut auth = raw_auth.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { ref address, .. } = credentials; + + // See if we have a signer for this authorizationEntry + // If not, then we Error + let needle = match address { + ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(ref a)))) => a, + ScAddress::Contract(Hash(c)) => { + // This address is for a contract. This means we're using a custom + // smart-contract account. Currently the CLI doesn't support that yet. + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::Contract(stellar_strkey::Contract(*c)) + .to_string(), + }); + } + }; + let signer = if let Some(s) = signers + .iter() + .find(|s| needle == s.verifying_key().as_bytes()) + { + s + } else if needle == source_address { + // This is the source address, so we can sign it + source_key + } else { + // We don't have a signer for this address + return Err(Error::MissingSignerForAddress { + address: stellar_strkey::Strkey::PublicKeyEd25519( + stellar_strkey::ed25519::PublicKey(*needle), + ) + .to_string(), + }); + }; + + sign_soroban_authorization_entry( + raw_auth, + signer, + signature_expiration_ledger, + &network_id, + ) + }) + .collect::, Error>>()?; + + body.auth = signed_auths.try_into()?; + tx.operations = vec![op].try_into()?; + Ok(Some(tx)) +} + +fn sign_soroban_authorization_entry( + raw: &SorobanAuthorizationEntry, + signer: &ed25519_dalek::SigningKey, + signature_expiration_ledger: u32, + network_id: &Hash, +) -> Result { + let mut auth = raw.clone(); + let SorobanAuthorizationEntry { + credentials: SorobanCredentials::Address(ref mut credentials), + .. + } = auth + else { + // Doesn't need special signing + return Ok(auth); + }; + let SorobanAddressCredentials { nonce, .. } = credentials; + + let preimage = HashIdPreimage::SorobanAuthorization(HashIdPreimageSorobanAuthorization { + network_id: network_id.clone(), + invocation: auth.root_invocation.clone(), + nonce: *nonce, + signature_expiration_ledger, + }) + .to_xdr(Limits::none())?; + + let payload = Sha256::digest(preimage); + let signature = signer.sign(&payload); + + let map = ScMap::sorted_from(vec![ + ( + ScVal::Symbol(ScSymbol("public_key".try_into()?)), + ScVal::Bytes( + signer + .verifying_key() + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ( + ScVal::Symbol(ScSymbol("signature".try_into()?)), + ScVal::Bytes( + signature + .to_bytes() + .to_vec() + .try_into() + .map_err(Error::Xdr)?, + ), + ), + ]) + .map_err(Error::Xdr)?; + credentials.signature = ScVal::Vec(Some( + vec![ScVal::Map(Some(map))].try_into().map_err(Error::Xdr)?, + )); + credentials.signature_expiration_ledger = signature_expiration_ledger; + auth.credentials = SorobanCredentials::Address(credentials.clone()); + Ok(auth) +} + +pub fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result { + let transaction_data = + SorobanTransactionData::from_xdr_base64(&restore.transaction_data, Limits::none())?; + let fee = u32::try_from(restore.min_resource_fee) + .map_err(|_| Error::LargeFee(restore.min_resource_fee))?; + Ok(Transaction { + source_account: parent.source_account.clone(), + fee: parent + .fee + .checked_add(fee) + .ok_or(Error::LargeFee(restore.min_resource_fee))?, + seq_num: parent.seq_num.clone(), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::RestoreFootprint(RestoreFootprintOp { + ext: ExtensionPoint::V0, + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V1(transaction_data), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + use super::super::SimulateHostFunctionResultRaw; + use soroban_env_host::xdr::{ + self, AccountId, ChangeTrustAsset, ChangeTrustOp, ExtensionPoint, Hash, HostFunction, + InvokeContractArgs, InvokeHostFunctionOp, LedgerFootprint, Memo, MuxedAccount, Operation, + Preconditions, PublicKey, ScAddress, ScSymbol, ScVal, SequenceNumber, + SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanResources, + SorobanTransactionData, Uint256, WriteXdr, + }; + use stellar_strkey::ed25519::PublicKey as Ed25519PublicKey; + + const SOURCE: &str = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"; + + fn transaction_data() -> SorobanTransactionData { + SorobanTransactionData { + resources: SorobanResources { + footprint: LedgerFootprint { + read_only: VecM::default(), + read_write: VecM::default(), + }, + instructions: 0, + read_bytes: 5, + write_bytes: 0, + }, + resource_fee: 0, + ext: ExtensionPoint::V0, + } + } + + fn simulation_response() -> SimulateTransactionResponse { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + let fn_auth = &SorobanAuthorizationEntry { + credentials: xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { + address: ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( + source_bytes, + )))), + nonce: 0, + signature_expiration_ledger: 0, + signature: ScVal::Void, + }), + root_invocation: SorobanAuthorizedInvocation { + function: SorobanAuthorizedFunction::ContractFn(InvokeContractArgs { + contract_address: ScAddress::Contract(Hash([0; 32])), + function_name: ScSymbol("fn".try_into().unwrap()), + args: VecM::default(), + }), + sub_invocations: VecM::default(), + }, + }; + + SimulateTransactionResponse { + min_resource_fee: 115, + latest_ledger: 3, + results: vec![SimulateHostFunctionResultRaw { + auth: vec![fn_auth.to_xdr_base64(Limits::none()).unwrap()], + xdr: ScVal::U32(0).to_xdr_base64(Limits::none()).unwrap(), + }], + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + ..Default::default() + } + } + + fn single_contract_fn_transaction() -> Transaction { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), + fee: 100, + seq_num: SequenceNumber(0), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::InvokeContract(InvokeContractArgs { + contract_address: ScAddress::Contract(Hash([0x0; 32])), + function_name: ScSymbol::default(), + args: VecM::default(), + }), + auth: VecM::default(), + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V0, + } + } + + #[test] + fn test_assemble_transaction_updates_tx_data_from_simulation_response() { + let sim = simulation_response(); + let txn = single_contract_fn_transaction(); + let Ok(result) = assemble(&txn, &sim) else { + panic!("assemble failed"); + }; + + // validate it auto updated the tx fees from sim response fees + // since it was greater than tx.fee + assert_eq!(247, result.fee); + + // validate it updated sorobantransactiondata block in the tx ext + assert_eq!(TransactionExt::V1(transaction_data()), result.ext); + } + + #[test] + fn test_assemble_transaction_adds_the_auth_to_the_host_function() { + let sim = simulation_response(); + let txn = single_contract_fn_transaction(); + let Ok(result) = assemble(&txn, &sim) else { + panic!("assemble failed"); + }; + + assert_eq!(1, result.operations.len()); + let OperationBody::InvokeHostFunction(ref op) = result.operations[0].body else { + panic!("unexpected operation type: {:#?}", result.operations[0]); + }; + + assert_eq!(1, op.auth.len()); + let auth = &op.auth[0]; + + let xdr::SorobanAuthorizedFunction::ContractFn(xdr::InvokeContractArgs { + ref function_name, + .. + }) = auth.root_invocation.function + else { + panic!("unexpected function type"); + }; + assert_eq!("fn".to_string(), format!("{}", function_name.0)); + + let xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { + address: + xdr::ScAddress::Account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(address))), + .. + }) = &auth.credentials + else { + panic!("unexpected credentials type"); + }; + assert_eq!( + SOURCE.to_string(), + stellar_strkey::ed25519::PublicKey(address.0).to_string() + ); + } + + #[test] + fn test_assemble_transaction_errors_for_non_invokehostfn_ops() { + let source_bytes = Ed25519PublicKey::from_string(SOURCE).unwrap().0; + let txn = Transaction { + source_account: MuxedAccount::Ed25519(Uint256(source_bytes)), + fee: 100, + seq_num: SequenceNumber(0), + cond: Preconditions::None, + memo: Memo::None, + operations: vec![Operation { + source_account: None, + body: OperationBody::ChangeTrust(ChangeTrustOp { + line: ChangeTrustAsset::Native, + limit: 0, + }), + }] + .try_into() + .unwrap(), + ext: TransactionExt::V0, + }; + + let result = assemble( + &txn, + &SimulateTransactionResponse { + min_resource_fee: 115, + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + latest_ledger: 3, + ..Default::default() + }, + ); + + match result { + Ok(_) => {} + Err(e) => panic!("expected assembled operation, got: {e:#?}"), + } + } + + #[test] + fn test_assemble_transaction_errors_for_errors_for_mismatched_simulation() { + let txn = single_contract_fn_transaction(); + + let result = assemble( + &txn, + &SimulateTransactionResponse { + min_resource_fee: 115, + transaction_data: transaction_data().to_xdr_base64(Limits::none()).unwrap(), + latest_ledger: 3, + ..Default::default() + }, + ); + + match result { + Err(Error::UnexpectedSimulateTransactionResultSize { length }) => { + assert_eq!(0, length); + } + r => panic!("expected UnexpectedSimulateTransactionResultSize error, got: {r:#?}"), + } + } +} diff --git a/cmd/soroban-cli/src/toid.rs b/cmd/soroban-cli/src/toid.rs new file mode 100644 index 00000000..55c89049 --- /dev/null +++ b/cmd/soroban-cli/src/toid.rs @@ -0,0 +1,69 @@ +/// A barebones implementation of Total Order IDs (TOIDs) from +/// [SEP-35](https://stellar.org/protocol/sep-35), using the reference +/// implementation from the Go +/// [`stellar/go/toid`](https://github.com/stellar/go/blob/b4ba6f8e67f274bf84d21b0effb01ea8a914b766/toid/main.go#L8-L56) +/// package. +#[derive(Copy, Clone)] +pub struct Toid { + ledger_sequence: u32, + transaction_order: u32, + operation_order: u32, +} + +const LEDGER_MASK: u64 = (1 << 32) - 1; +const TRANSACTION_MASK: u64 = (1 << 20) - 1; +const OPERATION_MASK: u64 = (1 << 12) - 1; +const LEDGER_SHIFT: u64 = 32; +const TRANSACTION_SHIFT: u64 = 12; +const OPERATION_SHIFT: u64 = 0; + +impl Toid { + pub fn new(ledger: u32, tx_order: u32, op_order: u32) -> Toid { + Toid { + ledger_sequence: ledger, + transaction_order: tx_order, + operation_order: op_order, + } + } + + pub fn to_paging_token(self) -> String { + let u: u64 = self.into(); + format!("{u:019}") + } +} + +impl From for Toid { + fn from(item: u64) -> Self { + let ledger: u32 = ((item & LEDGER_MASK) >> LEDGER_SHIFT).try_into().unwrap(); + let tx_order: u32 = ((item & TRANSACTION_MASK) >> TRANSACTION_SHIFT) + .try_into() + .unwrap(); + let op_order: u32 = ((item & OPERATION_MASK) >> OPERATION_SHIFT) + .try_into() + .unwrap(); + + Toid::new(ledger, tx_order, op_order) + } +} + +impl From for u64 { + fn from(item: Toid) -> Self { + let l: u64 = item.ledger_sequence.into(); + let t: u64 = item.transaction_order.into(); + let o: u64 = item.operation_order.into(); + + let mut result: u64 = 0; + result |= (l & LEDGER_MASK) << LEDGER_SHIFT; + result |= (t & TRANSACTION_MASK) << TRANSACTION_SHIFT; + result |= (o & OPERATION_MASK) << OPERATION_SHIFT; + + result + } +} + +impl ToString for Toid { + fn to_string(&self) -> String { + let u: u64 = (*self).into(); + u.to_string() + } +} diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs new file mode 100644 index 00000000..ff0018a9 --- /dev/null +++ b/cmd/soroban-cli/src/utils.rs @@ -0,0 +1,244 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; +use stellar_strkey::ed25519::PrivateKey; + +use soroban_env_host::xdr::{ + Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, + HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, WriteXdr, +}; + +pub mod contract_spec; + +/// # Errors +/// +/// Might return an error +pub fn contract_hash(contract: &[u8]) -> Result { + Ok(Hash(Sha256::digest(contract).into())) +} + +/// # Errors +/// +/// Might return an error +pub fn transaction_hash(tx: &Transaction, network_passphrase: &str) -> Result<[u8; 32], XdrError> { + let signature_payload = TransactionSignaturePayload { + network_id: Hash(Sha256::digest(network_passphrase).into()), + tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()), + }; + Ok(Sha256::digest(signature_payload.to_xdr(Limits::none())?).into()) +} + +/// # Errors +/// +/// Might return an error +pub fn sign_transaction( + key: &ed25519_dalek::SigningKey, + tx: &Transaction, + network_passphrase: &str, +) -> Result { + let tx_hash = transaction_hash(tx, network_passphrase)?; + let tx_signature = key.sign(&tx_hash); + + let decorated_signature = DecoratedSignature { + hint: SignatureHint(key.verifying_key().to_bytes()[28..].try_into()?), + signature: Signature(tx_signature.to_bytes().try_into()?), + }; + + Ok(TransactionEnvelope::Tx(TransactionV1Envelope { + tx: tx.clone(), + signatures: vec![decorated_signature].try_into()?, + })) +} + +/// # Errors +/// +/// Might return an error +pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strkey::DecodeError> { + stellar_strkey::Contract::from_string(contract_id) + .map(|strkey| strkey.0) + .or_else(|_| { + // strkey failed, try to parse it as a hex string, for backwards compatibility. + soroban_spec_tools::utils::padded_hex_from_str(contract_id, 32) + .map_err(|_| stellar_strkey::DecodeError::Invalid)? + .try_into() + .map_err(|_| stellar_strkey::DecodeError::Invalid) + }) + .map_err(|_| stellar_strkey::DecodeError::Invalid) +} + +/// # Errors +/// May not find a config dir +pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { + let soroban_dir = |p: &std::path::Path| p.join(".soroban"); + while !soroban_dir(&pwd).exists() { + if !pwd.pop() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "soroban directory not found", + )); + } + } + Ok(soroban_dir(&pwd)) +} + +pub(crate) fn into_signing_key(key: &PrivateKey) -> ed25519_dalek::SigningKey { + let secret: ed25519_dalek::SecretKey = key.0; + ed25519_dalek::SigningKey::from_bytes(&secret) +} + +/// Used in tests +#[allow(unused)] +pub(crate) fn parse_secret_key( + s: &str, +) -> Result { + Ok(into_signing_key(&PrivateKey::from_string(s)?)) +} + +pub fn is_hex_string(s: &str) -> bool { + s.chars().all(|s| s.is_ascii_hexdigit()) +} + +pub fn contract_id_hash_from_asset( + asset: &Asset, + network_passphrase: &str, +) -> Result { + let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); + let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { + network_id, + contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), + }); + let preimage_xdr = preimage.to_xdr(Limits::none())?; + Ok(Hash(Sha256::digest(preimage_xdr).into())) +} + +pub mod parsing { + + use regex::Regex; + use soroban_env_host::xdr::{ + AccountId, AlphaNum12, AlphaNum4, Asset, AssetCode12, AssetCode4, PublicKey, + }; + + #[derive(thiserror::Error, Debug)] + pub enum Error { + #[error("invalid asset code: {asset}")] + InvalidAssetCode { asset: String }, + #[error("cannot parse account id: {account_id}")] + CannotParseAccountId { account_id: String }, + #[error("cannot parse asset: {asset}")] + CannotParseAsset { asset: String }, + #[error(transparent)] + Regex(#[from] regex::Error), + } + + pub fn parse_asset(str: &str) -> Result { + if str == "native" { + return Ok(Asset::Native); + } + let split: Vec<&str> = str.splitn(2, ':').collect(); + if split.len() != 2 { + return Err(Error::CannotParseAsset { + asset: str.to_string(), + }); + } + let code = split[0]; + let issuer = split[1]; + let re = Regex::new("^[[:alnum:]]{1,12}$")?; + if !re.is_match(code) { + return Err(Error::InvalidAssetCode { + asset: str.to_string(), + }); + } + if code.len() <= 4 { + let mut asset_code: [u8; 4] = [0; 4]; + for (i, b) in code.as_bytes().iter().enumerate() { + asset_code[i] = *b; + } + Ok(Asset::CreditAlphanum4(AlphaNum4 { + asset_code: AssetCode4(asset_code), + issuer: parse_account_id(issuer)?, + })) + } else { + let mut asset_code: [u8; 12] = [0; 12]; + for (i, b) in code.as_bytes().iter().enumerate() { + asset_code[i] = *b; + } + Ok(Asset::CreditAlphanum12(AlphaNum12 { + asset_code: AssetCode12(asset_code), + issuer: parse_account_id(issuer)?, + })) + } + } + + pub fn parse_account_id(str: &str) -> Result { + let pk_bytes = stellar_strkey::ed25519::PublicKey::from_string(str) + .map_err(|_| Error::CannotParseAccountId { + account_id: str.to_string(), + })? + .0; + Ok(AccountId(PublicKey::PublicKeyTypeEd25519(pk_bytes.into()))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_contract_id_from_str() { + // strkey + match contract_id_from_str("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE") { + Ok(contract_id) => assert_eq!( + contract_id, + [ + 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, + 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, + 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, + ] + ), + Err(err) => panic!("Failed to parse contract id: {err}"), + } + + // hex + match contract_id_from_str( + "363eaa3867841fbad0f4ed88c779e4fe66e56a2470dc98c0ec9c073d05c7b103", + ) { + Ok(contract_id) => assert_eq!( + contract_id, + [ + 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, 0xc7, + 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, 0xec, 0x9c, + 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, + ] + ), + Err(err) => panic!("Failed to parse contract id: {err}"), + } + + // unpadded-hex + match contract_id_from_str("1") { + Ok(contract_id) => assert_eq!( + contract_id, + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ] + ), + Err(err) => panic!("Failed to parse contract id: {err}"), + } + + // invalid hex + match contract_id_from_str("foobar") { + Ok(_) => panic!("Expected parsing to fail"), + Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), + } + + // hex too long (33 bytes) + match contract_id_from_str( + "000000000000000000000000000000000000000000000000000000000000000000", + ) { + Ok(_) => panic!("Expected parsing to fail"), + Err(err) => assert_eq!(err, stellar_strkey::DecodeError::Invalid), + } + } +} diff --git a/cmd/soroban-cli/src/utils/contract_spec.rs b/cmd/soroban-cli/src/utils/contract_spec.rs new file mode 100644 index 00000000..b4f24abe --- /dev/null +++ b/cmd/soroban-cli/src/utils/contract_spec.rs @@ -0,0 +1,276 @@ +use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; +use std::{ + fmt::Display, + io::{self, Cursor}, +}; + +use soroban_env_host::xdr::{ + self, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, + ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, + StringM, WriteXdr, +}; + +pub struct ContractSpec { + pub env_meta_base64: Option, + pub env_meta: Vec, + pub meta_base64: Option, + pub meta: Vec, + pub spec_base64: Option, + pub spec: Vec, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("reading file {filepath}: {error}")] + CannotReadContractFile { + filepath: std::path::PathBuf, + error: io::Error, + }, + #[error("cannot parse wasm file {file}: {error}")] + CannotParseWasm { + file: std::path::PathBuf, + error: wasmparser::BinaryReaderError, + }, + #[error("xdr processing error: {0}")] + Xdr(#[from] xdr::Error), + + #[error(transparent)] + Parser(#[from] wasmparser::BinaryReaderError), +} + +impl ContractSpec { + pub fn new(bytes: &[u8]) -> Result { + let mut env_meta: Option<&[u8]> = None; + let mut meta: Option<&[u8]> = None; + let mut spec: Option<&[u8]> = None; + for payload in wasmparser::Parser::new(0).parse_all(bytes) { + let payload = payload?; + if let wasmparser::Payload::CustomSection(section) = payload { + let out = match section.name() { + "contractenvmetav0" => &mut env_meta, + "contractmetav0" => &mut meta, + "contractspecv0" => &mut spec, + _ => continue, + }; + *out = Some(section.data()); + }; + } + + let mut env_meta_base64 = None; + let env_meta = if let Some(env_meta) = env_meta { + env_meta_base64 = Some(base64.encode(env_meta)); + let cursor = Cursor::new(env_meta); + let mut read = Limited::new(cursor, Limits::none()); + ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? + } else { + vec![] + }; + + let mut meta_base64 = None; + let meta = if let Some(meta) = meta { + meta_base64 = Some(base64.encode(meta)); + let cursor = Cursor::new(meta); + let mut depth_limit_read = Limited::new(cursor, Limits::none()); + ScMetaEntry::read_xdr_iter(&mut depth_limit_read) + .collect::, xdr::Error>>()? + } else { + vec![] + }; + + let mut spec_base64 = None; + let spec = if let Some(spec) = spec { + spec_base64 = Some(base64.encode(spec)); + let cursor = Cursor::new(spec); + let mut read = Limited::new(cursor, Limits::none()); + ScSpecEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? + } else { + vec![] + }; + + Ok(ContractSpec { + env_meta_base64, + env_meta, + meta_base64, + meta, + spec_base64, + spec, + }) + } + + pub fn spec_as_json_array(&self) -> Result { + let spec = self + .spec + .iter() + .map(|e| Ok(format!("\"{}\"", e.to_xdr_base64(Limits::none())?))) + .collect::, Error>>()? + .join(",\n"); + Ok(format!("[{spec}]")) + } +} + +impl Display for ContractSpec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(env_meta) = &self.env_meta_base64 { + writeln!(f, "Env Meta: {env_meta}")?; + for env_meta_entry in &self.env_meta { + match env_meta_entry { + ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { + writeln!(f, " • Interface Version: {v}")?; + } + } + } + writeln!(f)?; + } else { + writeln!(f, "Env Meta: None\n")?; + } + + if let Some(_meta) = &self.meta_base64 { + writeln!(f, "Contract Meta:")?; + for meta_entry in &self.meta { + match meta_entry { + ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { + writeln!(f, " • {key}: {val}")?; + } + } + } + writeln!(f)?; + } else { + writeln!(f, "Contract Meta: None\n")?; + } + + if let Some(_spec_base64) = &self.spec_base64 { + writeln!(f, "Contract Spec:")?; + for spec_entry in &self.spec { + match spec_entry { + ScSpecEntry::FunctionV0(func) => write_func(f, func)?, + ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, + ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, + ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, + ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, + } + } + } else { + writeln!(f, "Contract Spec: None")?; + } + Ok(()) + } +} + +fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { + writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; + if func.doc.len() > 0 { + writeln!( + f, + " Docs: {}", + &indent(&func.doc.to_utf8_string_lossy(), 11).trim() + )?; + } + writeln!( + f, + " Inputs: {}", + indent(&format!("{:#?}", func.inputs), 5).trim() + )?; + writeln!( + f, + " Output: {}", + indent(&format!("{:#?}", func.outputs), 5).trim() + )?; + writeln!(f)?; + Ok(()) +} + +fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { + writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; + if udt.doc.len() > 0 { + writeln!( + f, + " Docs: {}", + indent(&udt.doc.to_utf8_string_lossy(), 10).trim() + )?; + } + writeln!(f, " Cases:")?; + for case in udt.cases.iter() { + writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; + } + writeln!(f)?; + Ok(()) +} + +fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { + writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; + if udt.doc.len() > 0 { + writeln!( + f, + " Docs: {}", + indent(&udt.doc.to_utf8_string_lossy(), 10).trim() + )?; + } + writeln!(f, " Fields:")?; + for field in udt.fields.iter() { + writeln!( + f, + " • {}: {}", + field.name.to_utf8_string_lossy(), + indent(&format!("{:#?}", field.type_), 8).trim() + )?; + if field.doc.len() > 0 { + writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; + } + } + writeln!(f)?; + Ok(()) +} + +fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { + writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; + if udt.doc.len() > 0 { + writeln!( + f, + " Docs: {}", + indent(&udt.doc.to_utf8_string_lossy(), 10).trim() + )?; + } + writeln!(f, " Cases:")?; + for case in udt.cases.iter() { + writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; + } + writeln!(f)?; + Ok(()) +} + +fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { + writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; + if udt.doc.len() > 0 { + writeln!( + f, + " Docs: {}", + indent(&udt.doc.to_utf8_string_lossy(), 10).trim() + )?; + } + writeln!(f, " Cases:")?; + for case in udt.cases.iter() { + writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; + } + writeln!(f)?; + Ok(()) +} + +fn indent(s: &str, n: usize) -> String { + let pad = " ".repeat(n); + s.lines() + .map(|line| format!("{pad}{line}")) + .collect::>() + .join("\n") +} + +fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { + if lib.len() > 0 { + format!( + "{}::{}", + lib.to_utf8_string_lossy(), + name.to_utf8_string_lossy() + ) + } else { + name.to_utf8_string_lossy() + } +} diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs new file mode 100644 index 00000000..fce44c7c --- /dev/null +++ b/cmd/soroban-cli/src/wasm.rs @@ -0,0 +1,93 @@ +use clap::arg; +use soroban_env_host::xdr::{self, LedgerKey, LedgerKeyContractCode}; +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use crate::utils::{self, contract_spec::ContractSpec}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("reading file {filepath}: {error}")] + CannotReadContractFile { + filepath: std::path::PathBuf, + error: io::Error, + }, + #[error("cannot parse wasm file {file}: {error}")] + CannotParseWasm { + file: std::path::PathBuf, + error: wasmparser::BinaryReaderError, + }, + #[error("xdr processing error: {0}")] + Xdr(#[from] xdr::Error), + + #[error(transparent)] + Parser(#[from] wasmparser::BinaryReaderError), + #[error(transparent)] + ContractSpec(#[from] crate::utils::contract_spec::Error), +} + +#[derive(Debug, clap::Args, Clone)] +#[group(skip)] +pub struct Args { + /// Path to wasm binary + #[arg(long)] + pub wasm: PathBuf, +} + +impl Args { + /// # Errors + /// May fail to read wasm file + pub fn read(&self) -> Result, Error> { + fs::read(&self.wasm).map_err(|e| Error::CannotReadContractFile { + filepath: self.wasm.clone(), + error: e, + }) + } + + /// # Errors + /// May fail to read wasm file + pub fn len(&self) -> Result { + len(&self.wasm) + } + + /// # Errors + /// May fail to read wasm file + pub fn is_empty(&self) -> Result { + self.len().map(|len| len == 0) + } + + /// # Errors + /// May fail to read wasm file or parse xdr section + pub fn parse(&self) -> Result { + let contents = self.read()?; + Ok(ContractSpec::new(&contents)?) + } +} + +impl From<&PathBuf> for Args { + fn from(wasm: &PathBuf) -> Self { + Self { wasm: wasm.clone() } + } +} + +impl TryInto for Args { + type Error = Error; + fn try_into(self) -> Result { + Ok(LedgerKey::ContractCode(LedgerKeyContractCode { + hash: utils::contract_hash(&self.read()?)?, + })) + } +} + +/// # Errors +/// May fail to read wasm file +pub fn len(p: &Path) -> Result { + Ok(std::fs::metadata(p) + .map_err(|e| Error::CannotReadContractFile { + filepath: p.to_path_buf(), + error: e, + })? + .len()) +} From be5879902628ef84b16b83750f60539154844eba Mon Sep 17 00:00:00 2001 From: "Tyler.S" Date: Wed, 17 Jan 2024 18:25:17 -0800 Subject: [PATCH 7/7] Remove CLI help doc workflow --- .github/workflows/full-help-docs.yml | 32 ---------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/full-help-docs.yml diff --git a/.github/workflows/full-help-docs.yml b/.github/workflows/full-help-docs.yml deleted file mode 100644 index c8fbd9e0..00000000 --- a/.github/workflows/full-help-docs.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: CLI Help Doc - -on: [push, pull_request] - -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - pull-requests: read - -jobs: - doc_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: stellar/actions/rust-cache@main - - run: rustup update - - name: Generate help doc - # this looks goofy to get GITHUB_OUTPUT to work with multi-line return values; - # see https://stackoverflow.com/a/74266196/249801 - run: | - cargo md-gen - raw_diff=$(git diff docs/soroban-cli-full-docs.md) - if [ "$raw_diff" != "" ]; then echo ""; echo "Unexpected docs change:"; echo "======================="; echo ""; echo "$raw_diff"; echo ""; echo "======================="; echo ""; fi - echo diff=$raw_diff >> $GITHUB_OUTPUT - id: doc-gen - - - name: Check diff - if: steps.doc-gen.outputs.diff != '' - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Expected `doc-gen` to generate no diffs, but got diff shown in previous step.\n\nUpdate the full help docs:\n\n cargo md-gen\n\nDo this automatically on every commit by installing the pre-commit hook as explained in the README.')