diff --git a/Cargo.lock b/Cargo.lock index 463b5f171d..c75e1087e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4763,7 +4763,7 @@ dependencies = [ [[package]] name = "soroban-cli" -version = "21.3.0" +version = "21.4.1" dependencies = [ "assert_cmd", "assert_fs", @@ -4928,7 +4928,7 @@ dependencies = [ [[package]] name = "soroban-hello" -version = "21.3.0" +version = "21.4.1" [[package]] name = "soroban-ledger-snapshot" @@ -4998,7 +4998,7 @@ dependencies = [ [[package]] name = "soroban-spec-json" -version = "21.3.0" +version = "21.4.1" dependencies = [ "pretty_assertions", "serde", @@ -5028,7 +5028,7 @@ dependencies = [ [[package]] name = "soroban-spec-tools" -version = "21.3.0" +version = "21.4.1" dependencies = [ "base64 0.21.7", "ethnum", @@ -5047,7 +5047,7 @@ dependencies = [ [[package]] name = "soroban-spec-typescript" -version = "21.3.0" +version = "21.4.1" dependencies = [ "base64 0.21.7", "heck 0.4.1", @@ -5068,7 +5068,7 @@ dependencies = [ [[package]] name = "soroban-test" -version = "21.3.0" +version = "21.4.1" dependencies = [ "assert_cmd", "assert_fs", @@ -5140,14 +5140,14 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-cli" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-cli", ] [[package]] name = "stellar-ledger" -version = "21.3.0" +version = "21.4.1" dependencies = [ "async-trait", "bollard", @@ -5453,35 +5453,35 @@ dependencies = [ [[package]] name = "test_custom_account" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_custom_types" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_hello_world" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_swap" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] [[package]] name = "test_token" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5489,7 +5489,7 @@ dependencies = [ [[package]] name = "test_udt" -version = "21.3.0" +version = "21.4.1" dependencies = [ "soroban-sdk", ] diff --git a/Cargo.toml b/Cargo.toml index c4db83a064..d58335a211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ default-members = ["cmd/soroban-cli", "cmd/crates/soroban-spec-tools", "cmd/crat exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] -version = "21.3.0" +version = "21.4.1" rust-version = "1.79.0" [workspace.dependencies.soroban-env-host] @@ -27,15 +27,15 @@ version = "=21.5.0" version = "=21.5.0" [workspace.dependencies.soroban-spec-json] -version = "=21.3.0" +version = "=21.4.1" path = "./cmd/crates/soroban-spec-json" [workspace.dependencies.soroban-spec-typescript] -version = "21.3.0" +version = "21.4.1" path = "./cmd/crates/soroban-spec-typescript" [workspace.dependencies.soroban-spec-tools] -version = "21.3.0" +version = "21.4.1" path = "./cmd/crates/soroban-spec-tools" [workspace.dependencies.soroban-sdk] @@ -48,7 +48,7 @@ version = "=21.2.0" version = "=21.2.0" [workspace.dependencies.soroban-cli] -version = "=21.3.0" +version = "=21.4.1" path = "cmd/soroban-cli" [workspace.dependencies.soroban-rpc] diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 1e6d7428e9..44da543c8e 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -76,6 +76,7 @@ Tools for smart contract developers ###### **Subcommands:** * `asset` — Utilities to deploy a Stellar Asset Contract or get its id +* `alias` — Utilities to manage contract aliases * `bindings` — Generate code client bindings for a contract * `build` — Build a contract from source * `extend` — Extend the time to live ledger of a contract-data ledger entry @@ -116,7 +117,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -137,7 +138,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -155,6 +156,82 @@ Deploy builtin Soroban Asset Contract +## `stellar contract alias` + +Utilities to manage contract aliases + +**Usage:** `stellar contract alias ` + +###### **Subcommands:** + +* `remove` — Remove contract alias +* `add` — Add contract alias +* `show` — Show the contract id associated with a given alias + + + +## `stellar contract alias remove` + +Remove contract alias + +**Usage:** `stellar contract alias remove [OPTIONS] ` + +###### **Arguments:** + +* `` — The contract alias that will be removed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config + + + +## `stellar contract alias add` + +Add contract alias + +**Usage:** `stellar contract alias add [OPTIONS] --id ` + +###### **Arguments:** + +* `` — The contract alias that will be removed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--overwrite` — Overwrite the contract alias if it already exists +* `--id ` — The contract id that will be associated with the alias + + + +## `stellar contract alias show` + +Show the contract id associated with a given alias + +**Usage:** `stellar contract alias show [OPTIONS] ` + +###### **Arguments:** + +* `` — The contract alias that will be displayed + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config + + + ## `stellar contract bindings` Generate code client bindings for a contract @@ -274,7 +351,7 @@ If no keys are specified the contract itself is extended. Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -304,7 +381,7 @@ Deploy a wasm contract * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -367,7 +444,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -388,7 +465,7 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -588,7 +665,7 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -629,7 +706,7 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction. Deprecated use `--send=no` * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -712,7 +789,7 @@ Print the current value of a contract-data ledger entry Temporary * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -751,7 +828,7 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1238,7 +1315,7 @@ Simulate a transaction envelope from stdin ###### **Options:** * `--source-account ` — Account where the final transaction originates from. If no `--sign-with-*` flag is passed, passed key will also be used to sign the transaction. Can be an identity (`--source alice`), a secret key (`--source SC36…`), or a seed phrase (`--source "kite urban…"`) -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint @@ -1271,7 +1348,7 @@ Sign a transaction envolope appending the signature to the envelope ###### **Options:** -* `--sign-with-key ` — Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…") +* `--sign-with-key ` — Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--yes` — If one of `--sign-with-*` flags is provided, don't ask to confirm to sign a transaction * `--rpc-url ` — RPC server endpoint diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts index 0b661d2c35..cd888cc046 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.d.ts @@ -51,6 +51,9 @@ export type ComplexEnum = { values: void; }; export declare const Errors: { + /** + * Please provide an odd number + */ 1: { message: string; }; @@ -186,7 +189,7 @@ export interface Client { simulate?: boolean; }) => 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. * + * 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 */ strukt_hel: ({ strukt }: { @@ -358,7 +361,7 @@ export interface Client { simulate?: boolean; }) => 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. * + * 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 }: { @@ -493,7 +496,7 @@ export interface Client { simulate?: boolean; }) => 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. * + * 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 }: { diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js index 5b9e067c90..4286b2a681 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/dist/index.js @@ -20,7 +20,10 @@ export var RoyalCard; RoyalCard[RoyalCard["King"] = 13] = "King"; })(RoyalCard || (RoyalCard = {})); export const Errors = { - 1: { message: "Please provide an odd number" } + /** + * Please provide an odd number + */ + 1: { message: "NumberMustBeOdd" } }; export class Client extends ContractClient { options; 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 index 11ad043be2..f45484ffbf 100644 --- 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 @@ -17,13 +17,11 @@ }, "node_modules/@stellar/js-xdr": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", - "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==" + "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.0.1.tgz", - "integrity": "sha512-g6c27MNsDgEdUmoNQJn7zCWoCY50WHt0OIIOq3PhWaJRtUaT++qs1Jpb8+1bny2GmhtfRGOfPUFSyQBuHT9Mvg==", + "license": "Apache-2.0", "dependencies": { "@stellar/js-xdr": "^3.1.1", "base32.js": "^0.1.0", @@ -38,8 +36,7 @@ }, "node_modules/@stellar/stellar-sdk": { "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.1.0.tgz", - "integrity": "sha512-Va0hu9SaPezmMbO5eMwL5D15Wrx1AGWRtxayUDRWV2Fr3ynY58mvCZS1vsgNQ4kE8MZe3nBVKv6T9Kzqwgx1PQ==", + "license": "Apache-2.0", "dependencies": { "@stellar/stellar-base": "^12.0.1", "axios": "^1.7.2", @@ -52,13 +49,12 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "license": "MIT" }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -67,16 +63,13 @@ }, "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==", + "license": "MIT", "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", @@ -90,20 +83,18 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "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==", + "license": "MIT", "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", @@ -118,6 +109,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -125,8 +117,7 @@ }, "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==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -136,30 +127,27 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "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==", + "license": "MIT", "engines": { "node": ">=12.0.0" } }, "node_modules/follow-redirects": { "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -171,8 +159,7 @@ }, "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==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -184,8 +171,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", @@ -199,25 +184,23 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "license": "ISC" }, "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==", + "license": "MIT", "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==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -227,8 +210,7 @@ }, "node_modules/node-gyp-build": { "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -238,21 +220,17 @@ }, "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==" + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", "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", @@ -266,12 +244,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "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==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -282,9 +260,8 @@ }, "node_modules/sodium-native": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", - "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { "node-gyp-build": "^4.8.0" @@ -292,13 +269,11 @@ }, "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==" + "license": "MIT" }, "node_modules/tweetnacl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "license": "Unlicense" }, "node_modules/typescript": { "version": "5.3.3", @@ -315,8 +290,7 @@ }, "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==" + "license": "MIT" } } } 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 index b6ee3dbbc1..7f2d5b0000 100644 --- 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 @@ -59,7 +59,10 @@ 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"} + /** + * Please provide an odd number + */ + 1: {message:"NumberMustBeOdd"} } export interface Client { @@ -204,7 +207,7 @@ export interface Client { }) => 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. * + * 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 */ strukt_hel: ({strukt}: {strukt: Test}, options?: { @@ -385,7 +388,7 @@ export interface Client { }) => 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. * + * 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?: { @@ -526,7 +529,7 @@ export interface Client { }) => 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. * + * 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?: { diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json index 8e620a1a31..deee8f09dd 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json @@ -700,9 +700,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -782,12 +782,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1610,9 +1610,9 @@ } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index 48f94ddcae..643db6ec31 100644 --- a/cmd/crates/soroban-test/Cargo.toml +++ b/cmd/crates/soroban-test/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/soroban-test" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.3.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml index 0b28f4ef11..64d861184d 100644 --- a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soroban-hello" -version = "21.3.0" +version = "21.4.1" edition = "2021" publish = false diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml index bfb870d35f..db31464f2a 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_custom_account" -version = "21.3.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml index 8eb1875c6d..61b1cd4c64 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_custom_types" -version = "21.3.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml index dcdae0280c..c2bb8ce802 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_hello_world" -version = "21.3.0" +version = "21.4.1" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" diff --git a/cmd/crates/soroban-test/tests/it/help.rs b/cmd/crates/soroban-test/tests/it/help.rs index a66c449ed3..ef84a361b6 100644 --- a/cmd/crates/soroban-test/tests/it/help.rs +++ b/cmd/crates/soroban-test/tests/it/help.rs @@ -1,4 +1,4 @@ -use soroban_cli::commands::contract; +use soroban_cli::commands::contract::{self, arg_parsing}; use soroban_test::TestEnv; use crate::util::{invoke_custom as invoke, CUSTOM_TYPES, DEFAULT_CONTRACT_ID}; @@ -55,7 +55,7 @@ async fn complex_enum_help() { async fn multi_arg_failure() { assert!(matches!( invoke_custom("multi_args", "--b").await.unwrap_err(), - contract::invoke::Error::MissingArgument(_) + contract::invoke::Error::ArgParsing(arg_parsing::Error::MissingArgument(_)) )); } @@ -64,7 +64,9 @@ async fn handle_arg_larger_than_i32_failure() { let res = invoke_custom("i32_", &format!("--i32_={}", u32::MAX)).await; assert!(matches!( res, - Err(contract::invoke::Error::CannotParseArg { .. }) + Err(contract::invoke::Error::ArgParsing( + arg_parsing::Error::CannotParseArg { .. } + )) )); } @@ -73,7 +75,9 @@ async fn handle_arg_larger_than_i64_failure() { let res = invoke_custom("i64_", &format!("--i64_={}", u64::MAX)).await; assert!(matches!( res, - Err(contract::invoke::Error::CannotParseArg { .. }) + Err(contract::invoke::Error::ArgParsing( + arg_parsing::Error::CannotParseArg { .. } + )) )); } diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index d89d6a860e..611898ecdf 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.3.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/soroban-cli/src/commands/contract/alias.rs b/cmd/soroban-cli/src/commands/contract/alias.rs new file mode 100644 index 0000000000..bea808cb47 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias.rs @@ -0,0 +1,40 @@ +use crate::commands::global; + +pub mod add; +pub mod remove; +pub mod show; + +#[derive(Debug, clap::Subcommand)] +pub enum Cmd { + /// Remove contract alias + Remove(remove::Cmd), + + /// Add contract alias + Add(add::Cmd), + + /// Show the contract id associated with a given alias + Show(show::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Remove(#[from] remove::Error), + + #[error(transparent)] + Add(#[from] add::Error), + + #[error(transparent)] + Show(#[from] show::Error), +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + match &self { + Cmd::Remove(remove) => remove.run(global_args)?, + Cmd::Add(add) => add.run(global_args)?, + Cmd::Show(show) => show.run(global_args)?, + } + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/add.rs b/cmd/soroban-cli/src/commands/contract/alias/add.rs new file mode 100644 index 0000000000..d5c304452a --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/add.rs @@ -0,0 +1,84 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be removed. + pub alias: String, + + /// Overwrite the contract alias if it already exists. + #[arg(long)] + pub overwrite: bool, + + /// The contract id that will be associated with the alias. + #[arg(long = "id")] + pub contract_id: stellar_strkey::Contract, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error( + "alias '{alias}' is already referencing contract '{contract}' on network '{network_passphrase}'" + )] + AlreadyExist { + alias: String, + network_passphrase: String, + contract: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + let contract = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)?; + + if let Some(contract) = contract { + if contract != self.contract_id.to_string() && !self.overwrite { + return Err(Error::AlreadyExist { + alias: alias.to_string(), + network_passphrase: network_passphrase.to_string(), + contract, + }); + } + }; + + print.infoln(format!( + "Contract alias '{alias}' will reference {contract} on network '{network_passphrase}'", + contract = self.contract_id + )); + + self.config_locator.save_contract_id( + &network.network_passphrase, + &self.contract_id.to_string(), + alias, + )?; + + print.checkln(format!("Contract alias '{alias}' has been added")); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/remove.rs b/cmd/soroban-cli/src/commands/contract/alias/remove.rs new file mode 100644 index 0000000000..7a41905958 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/remove.rs @@ -0,0 +1,65 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be removed. + pub alias: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error("no contract found with alias '{alias}' for network '{network_passphrase}'")] + NoContract { + alias: String, + network_passphrase: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + let Some(contract) = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)? + else { + return Err(Error::NoContract { + alias: alias.into(), + network_passphrase: network_passphrase.into(), + }); + }; + + print.infoln(format!( + "Contract alias '{alias}' references {contract} on network '{network_passphrase}'" + )); + + self.config_locator + .remove_contract_id(&network.network_passphrase, alias)?; + + print.checkln(format!("Contract alias '{alias}' has been removed")); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/contract/alias/show.rs b/cmd/soroban-cli/src/commands/contract/alias/show.rs new file mode 100644 index 0000000000..fc233b6130 --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/alias/show.rs @@ -0,0 +1,62 @@ +use std::fmt::Debug; + +use clap::{command, Parser}; + +use crate::commands::{config::network, global}; +use crate::config::locator; +use crate::print::Print; + +#[derive(Parser, Debug, Clone)] +#[group(skip)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, + + #[command(flatten)] + network: network::Args, + + /// The contract alias that will be displayed. + pub alias: String, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), + + #[error(transparent)] + Network(#[from] network::Error), + + #[error("no contract found with alias '{alias}' for network '{network_passphrase}'")] + NoContract { + alias: String, + network_passphrase: String, + }, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let alias = &self.alias; + let network = self.network.get(&self.config_locator)?; + let network_passphrase = &network.network_passphrase; + + if let Some(contract) = self + .config_locator + .get_contract_id(&self.alias, network_passphrase)? + { + print.infoln(format!( + "Contract alias '{alias}' references {contract} on network '{network_passphrase}'" + )); + + println!("{contract}"); + + Ok(()) + } else { + Err(Error::NoContract { + alias: alias.into(), + network_passphrase: network_passphrase.into(), + }) + } + } +} diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs new file mode 100644 index 0000000000..772a3e2f9e --- /dev/null +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -0,0 +1,258 @@ +use std::collections::HashMap; +use std::convert::TryInto; +use std::ffi::OsString; +use std::fmt::Debug; +use std::path::PathBuf; + +use clap::value_parser; +use heck::ToKebabCase; + +use crate::commands::txn_result::TxnResult; +use crate::config::secret::StellarSigner; +use crate::config::{self}; +use crate::signer::{self, Stellar}; +use soroban_env_host::xdr::{ + self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, + ScVec, +}; +use soroban_sdk::xdr::ScSpecFunctionInputV0; +use soroban_spec_tools::Spec; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("parsing argument {arg}: {error}")] + CannotParseArg { + arg: String, + error: soroban_spec_tools::Error, + }, + #[error("cannot print result {result:?}: {error}")] + CannotPrintResult { + result: ScVal, + error: soroban_spec_tools::Error, + }, + #[error("function {0} was not found in the contract")] + FunctionNotFoundInContractSpec(String), + #[error("function name {0} is too long")] + FunctionNameTooLong(String), + #[error("argument count ({current}) surpasses maximum allowed count ({maximum})")] + MaxNumberOfArgumentsReached { current: usize, maximum: usize }, + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + StrVal(#[from] soroban_spec_tools::Error), + #[error("Missing argument {0}")] + MissingArgument(String), + #[error("")] + MissingFileArg(PathBuf), + #[error(transparent)] + Signer(#[from] signer::Error), +} + +pub async fn build_host_function_parameters( + contract_id: &stellar_strkey::Contract, + slop: &[OsString], + spec_entries: &[ScSpecEntry], + config: &config::Args, +) -> Result<(String, Spec, InvokeContractArgs, Vec), Error> { + let spec = Spec(Some(spec_entries.to_vec())); + let mut cmd = clap::Command::new(contract_id.to_string()) + .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(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 mut parsed_args: Vec = Vec::new(); + for i in func.inputs.iter() { + let (val, signer) = parse_arg(i, matches_, config, &spec).await?; + parsed_args.push(val); + if let Some(signer) = signer { + signers.push(signer); + } + } + + let contract_address_arg = ScAddress::Contract(Hash(contract_id.0)); + 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 parse_arg( + input: &ScSpecFunctionInputV0, + matches_: &clap::ArgMatches, + config: &config::Args, + spec: &Spec, +) -> Result<(ScVal, Option), Error> { + let mut signer: Option = None; + let name = input.name.to_utf8_string()?; + let sc_val = if let Some(mut val) = matches_.get_raw(&name) { + let mut s = val.next().unwrap().to_string_lossy().to_string(); + if matches!(input.type_, ScSpecTypeDef::Address) { + // Currently we only support local keys, same as input for --sign-with-key`, for signing auth entries. + if let Ok(signer_) = config + .sign_with + .locator + .account(&s) + .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) + { + s = signer_.get_public_key().await?.to_string(); + signer = Some(signer_); + } + } + spec.from_string(&s, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } else if matches!(input.type_, ScSpecTypeDef::Option(_)) { + ScVal::Void + } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { + if matches!(input.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { + 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{}", + input.type_, + file_contents.len() + ); + spec.from_string(&file_contents, &input.type_) + .map_err(|error| Error::CannotParseArg { arg: name, error })? + } + } else { + return Err(Error::MissingArgument(name)); + }; + Ok((sc_val, signer)) +} + +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_ { + ScSpecTypeDef::Bool => arg + .num_args(0..1) + .default_missing_value("true") + .default_value("false") + .num_args(0..=1), + ScSpecTypeDef::Option(_val) => arg.required(false), + ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | 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 BytesN, which are raw bytes"# + ) +} + +pub fn output_to_string( + spec: &Spec, + res: &ScVal, + function: &str, +) -> Result, Error> { + 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(TxnResult::Res(res_str)) +} diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index bc0283b6d3..15ce97624f 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -190,7 +190,7 @@ impl NetworkRunnable for Cmd { } })?); - print.info(format!("Using wasm hash {wasm_hash}").as_str()); + print.infoln(format!("Using wasm hash {wasm_hash}").as_str()); let network = config.get_network()?; let salt: [u8; 32] = match &self.salt { @@ -219,21 +219,21 @@ impl NetworkRunnable for Cmd { )?; if self.fee.build_only { - print.check("Transaction built!"); + print.checkln("Transaction built!"); return Ok(TxnResult::Txn(txn)); } - print.info("Simulating deploy transaction…"); + print.infoln("Simulating deploy transaction…"); let txn = client.simulate_and_assemble_transaction(&txn).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { - print.check("Done!"); + print.checkln("Done!"); return Ok(TxnResult::Txn(txn)); } - print.globe("Submitting deploy transaction…"); + print.globeln("Submitting deploy transaction…"); print.log_transaction(&txn, &network, true)?; let get_txn_resp = client @@ -248,10 +248,10 @@ impl NetworkRunnable for Cmd { let contract_id = stellar_strkey::Contract(contract_id.0).to_string(); if let Some(url) = utils::explorer_url_for_contract(&network, &contract_id) { - print.link(url); + print.linkln(url); } - print.check("Deployed!"); + print.checkln("Deployed!"); Ok(TxnResult::Res(contract_id)) } diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 18d41d172a..79d0d3bd98 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -130,8 +130,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; - let keys = self.key.parse_keys(contract)?; + let keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let network = &config.get_network()?; let client = Client::new(&network.rpc_url)?; let public_key = config.source_account().await?; diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index a83504d966..0d451f6fe4 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -1,6 +1,6 @@ use clap::{ builder::{PossibleValue, PossibleValuesParser, ValueParser}, - Parser, ValueEnum, + Parser, }; use gix::{clone, create, open, progress, remote}; use rust_embed::RustEmbed; @@ -14,24 +14,20 @@ use std::{ }, io::{self, Read, Write}, num::NonZeroU32, - path::Path, + path::{Path, PathBuf}, str, sync::atomic::AtomicBool, }; use toml_edit::{Document, TomlError}; use ureq::get; +use crate::{commands::global, print}; + const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; const GITHUB_URL: &str = "https://github.com"; const WITH_EXAMPLE_LONG_HELP_TEXT: &str = "An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default."; -#[derive(Clone, Debug, ValueEnum, PartialEq)] -pub enum FrontendTemplate { - Astro, - None, -} - #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { @@ -87,380 +83,415 @@ pub enum Error { impl Cmd { #[allow(clippy::unused_self)] - pub fn run(&self) -> Result<(), Error> { - println!("ℹ️ Initializing project at {}", self.project_path); - let project_path = Path::new(&self.project_path); - - init( - project_path, - &self.frontend_template, - &self.with_example, - self.overwrite, - )?; + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let runner = Runner { + args: self.clone(), + print: print::Print::new(global_args.quiet), + }; - Ok(()) + runner.run() } } #[derive(RustEmbed)] #[folder = "src/utils/contract-init-template"] struct TemplateFiles; +struct Runner { + args: Cmd, + print: print::Print, +} -fn init( - project_path: &Path, - frontend_template: &str, - with_examples: &[String], - overwrite: bool, -) -> Result<(), Error> { - // create a project dir, and copy the contents of the base template (contract-init-template) into it - create_dir_all(project_path).map_err(|e| { - eprintln!("Error creating new project directory: {project_path:?}"); - e - })?; - copy_template_files(project_path, overwrite)?; - - if !check_internet_connection() { - println!("⚠️ It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); - return Ok(()); - } - - if !frontend_template.is_empty() { - // create a temp dir for the template repo - let fe_template_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for frontend template"); - e - })?; - - // clone the template repo into the temp dir - clone_repo(frontend_template, fe_template_dir.path())?; - - // copy the frontend template files into the project - copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; - } +impl Runner { + fn run(&self) -> Result<(), Error> { + let project_path = PathBuf::from(&self.args.project_path); + self.print + .infoln(format!("Initializing project at {project_path:?}")); - // if there are --with-example flags, include the example contracts - if include_example_contracts(with_examples) { - // create an examples temp dir - let examples_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for soroban-examples"); + // create a project dir, and copy the contents of the base template (contract-init-template) into it + create_dir_all(&project_path).map_err(|e| { + self.print + .errorln("Error creating new project directory: {project_path:?}"); e })?; + self.copy_template_files()?; - // clone the soroban-examples repo into the temp dir - clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; + if !Self::check_internet_connection() { + self.print.warnln("It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); + return Ok(()); + } - // copy the example contracts into the project - copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; - } + if !self.args.frontend_template.is_empty() { + // create a temp dir for the template repo + let fe_template_dir = tempfile::tempdir().map_err(|e| { + self.print + .errorln("Error creating temp dir for frontend template"); + e + })?; - Ok(()) -} + // clone the template repo into the temp dir + self.clone_repo(&self.args.frontend_template, fe_template_dir.path())?; -fn copy_template_files(project_path: &Path, overwrite: bool) -> Result<(), Error> { - for item in TemplateFiles::iter() { - let mut to = project_path.join(item.as_ref()); - let exists = file_exists(&to); - if exists && !overwrite { - println!( - "ℹ️ Skipped creating {} as it already exists", - &to.to_string_lossy() - ); - continue; + // copy the frontend template files into the project + self.copy_frontend_files(fe_template_dir.path(), &project_path)?; } - create_dir_all(to.parent().unwrap()).map_err(|e| { - eprintln!("Error creating directory path for: {to:?}"); - e - })?; - let Some(file) = TemplateFiles::get(item.as_ref()) else { - println!("⚠️ Failed to read file: {}", item.as_ref()); - continue; - }; + // if there are --with-example flags, include the example contracts + if self.include_example_contracts() { + // create an examples temp dir + let examples_dir = tempfile::tempdir().map_err(|e| { + self.print + .errorln("Error creating temp dir for soroban-examples"); + e + })?; - let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { - eprintln!( - "Error converting file contents in {:?} to string", - item.as_ref() - ); - e - })?; + // clone the soroban-examples repo into the temp dir + self.clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; - // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. - let item_path = Path::new(item.as_ref()); - if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { - let item_parent_path = item_path.parent().unwrap(); - to = project_path.join(item_parent_path).join("Cargo.toml"); + // copy the example contracts into the project + self.copy_example_contracts( + examples_dir.path(), + &project_path, + &self.args.with_example, + )?; } - if exists { - println!("🔄 Overwriting {}", &to.to_string_lossy()); - } else { - println!("➕ Writing {}", &to.to_string_lossy()); - } - write(&to, file_contents).map_err(|e| { - eprintln!("Error writing file: {to:?}"); - e - })?; + Ok(()) } - Ok(()) -} -fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - let contents_to_exclude_from_copy = [ - ".git", - ".github", - "Makefile", - ".vscode", - "target", - "Cargo.lock", - ]; - for entry in read_dir(from).map_err(|e| { - eprintln!("Error reading directory: {from:?}"); - e - })? { - let entry = entry.map_err(|e| { - eprintln!("Error reading entry in directory: {from:?}"); - e - })?; - let path = entry.path(); - let entry_name = entry.file_name().to_string_lossy().to_string(); - let new_path = to.join(&entry_name); + fn copy_template_files(&self) -> Result<(), Error> { + let project_path = Path::new(&self.args.project_path); + for item in TemplateFiles::iter() { + let mut to = project_path.join(item.as_ref()); + let exists = Self::file_exists(&to); + if exists && !self.args.overwrite { + self.print + .infoln(format!("Skipped creating {to:?} as it already exists")); + continue; + } - if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { - continue; - } + create_dir_all(to.parent().unwrap()).map_err(|e| { + self.print + .errorln(format!("Error creating directory path for: {to:?}")); + e + })?; - if path.is_dir() { - create_dir_all(&new_path).map_err(|e| { - eprintln!("Error creating directory: {new_path:?}"); + let Some(file) = TemplateFiles::get(item.as_ref()) else { + self.print + .warnln(format!("Failed to read file: {}", item.as_ref())); + continue; + }; + + let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { + self.print.errorln(format!( + "Error converting file contents in {:?} to string", + item.as_ref() + )); e })?; - copy_contents(&path, &new_path, overwrite)?; - } else { - let exists = file_exists(&new_path); - let new_path_str = new_path.to_string_lossy(); - if exists { - let append = - new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); - if append { - append_contents(&path, &new_path)?; - } - if overwrite && !append { - println!("🔄 Overwriting {new_path_str}"); - } else { - println!("ℹ️ Skipped creating {new_path_str} as it already exists"); - continue; - } + // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. + let item_path = Path::new(item.as_ref()); + if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { + let item_parent_path = item_path.parent().unwrap(); + to = project_path.join(item_parent_path).join("Cargo.toml"); + } + + if exists { + self.print + .plusln(format!("Writing {to:?} (overwriting existing file)")); } else { - println!("➕ Writing {new_path_str}"); + self.print.plusln(format!("Writing {to:?}")); } - copy(&path, &new_path).map_err(|e| { - eprintln!( - "Error copying from {:?} to {:?}", - path.to_string_lossy(), - new_path - ); + write(&to, file_contents).map_err(|e| { + self.print.errorln(format!("Error writing file: {to:?}")); e })?; } + Ok(()) } - Ok(()) -} - -fn file_exists(file_path: &Path) -> bool { - metadata(file_path) - .as_ref() - .map(Metadata::is_file) - .unwrap_or(false) -} + fn copy_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let contents_to_exclude_from_copy = [ + ".git", + ".github", + "Makefile", + ".vscode", + "target", + "Cargo.lock", + ]; + for entry in read_dir(from).map_err(|e| { + self.print + .errorln(format!("Error reading directory: {from:?}")); + e + })? { + let entry = entry.map_err(|e| { + self.print + .errorln(format!("Error reading entry in directory: {from:?}")); + e + })?; + let path = entry.path(); + let entry_name = entry.file_name().to_string_lossy().to_string(); + let new_path = to.join(&entry_name); -fn include_example_contracts(contracts: &[String]) -> bool { - !contracts.is_empty() -} + if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { + continue; + } -fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { - let mut prepare = clone::PrepareFetch::new( - from_url, - to_path, - create::Kind::WithWorktree, - create::Options { - destination_must_be_empty: false, - fs_capabilities: None, - }, - open::Options::isolated(), - ) - .map_err(|e| { - eprintln!("Error preparing fetch for {from_url:?}"); - Box::new(e) - })? - .with_shallow(remote::fetch::Shallow::DepthAtRemote( - NonZeroU32::new(1).unwrap(), - )); - - let (mut checkout, _outcome) = prepare - .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| { - eprintln!("Error calling fetch_then_checkout with {from_url:?}"); - Box::new(e) - })?; + if path.is_dir() { + create_dir_all(&new_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {new_path:?}")); + e + })?; + self.copy_contents(&path, &new_path)?; + } else { + let exists = Self::file_exists(&new_path); + let new_path_str = new_path.to_string_lossy(); + if exists { + let append = + new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); + if append { + self.append_contents(&path, &new_path)?; + } + + if self.args.overwrite && !append { + self.print.plusln(format!( + "Writing {new_path_str} (overwriting existing file)" + )); + } else { + self.print.infoln(format!( + "Skipped creating {new_path_str} as it already exists" + )); + continue; + } + } else { + self.print.plus(format!("Writing {new_path_str}")); + } + copy(&path, &new_path).map_err(|e| { + self.print.errorln(format!( + "Error copying from {:?} to {:?}", + path.to_string_lossy(), + new_path + )); + e + })?; + } + } - let (_repo, _outcome) = checkout - .main_worktree(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| { - eprintln!("Error calling main_worktree for {from_url:?}"); - e - })?; + Ok(()) + } - Ok(()) -} + fn file_exists(file_path: &Path) -> bool { + metadata(file_path) + .as_ref() + .map(Metadata::is_file) + .unwrap_or(false) + } -fn copy_example_contracts( - from: &Path, - to: &Path, - contracts: &[String], - overwrite: bool, -) -> Result<(), Error> { - let project_contracts_path = to.join("contracts"); - for contract in contracts { - println!("ℹ️ Initializing example contract: {contract}"); - let contract_as_string = contract.to_string(); - let contract_path = Path::new(&contract_as_string); - let from_contract_path = from.join(contract_path); - let to_contract_path = project_contracts_path.join(contract_path); - create_dir_all(&to_contract_path).map_err(|e| { - eprintln!("Error creating directory: {contract_path:?}"); - e - })?; + fn check_internet_connection() -> bool { + if let Ok(_req) = get(GITHUB_URL).call() { + return true; + } - copy_contents(&from_contract_path, &to_contract_path, overwrite)?; - edit_contract_cargo_file(&to_contract_path)?; + false } - Ok(()) -} + fn include_example_contracts(&self) -> bool { + !self.args.with_example.is_empty() + } -fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { - let cargo_path = contract_path.join("Cargo.toml"); - let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { - eprint!("Error reading Cargo.toml file in: {contract_path:?}"); - e - })?; - - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) - .unwrap() - .replace_all( - cargo_toml_str.as_str(), - "soroban-sdk = { workspace = true }", - ); + fn clone_repo(&self, from_url: &str, to_path: &Path) -> Result<(), Error> { + let mut prepare = clone::PrepareFetch::new( + from_url, + to_path, + create::Kind::WithWorktree, + create::Options { + destination_must_be_empty: false, + fs_capabilities: None, + }, + open::Options::isolated(), + ) + .map_err(|e| { + self.print + .errorln(format!("Error preparing fetch for {from_url:?}")); + Box::new(e) + })? + .with_shallow(remote::fetch::Shallow::DepthAtRemote( + NonZeroU32::new(1).unwrap(), + )); + + let (mut checkout, _outcome) = prepare + .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print.errorln(format!( + "Error calling fetch_then_checkout with {from_url:?}" + )); + Box::new(e) + })?; - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) - .unwrap() - .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); + let (_repo, _outcome) = checkout + .main_worktree(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print + .errorln(format!("Error calling main_worktree for {from_url:?}")); + e + })?; - let mut doc = cargo_toml_str.parse::().map_err(|e| { - eprintln!("Error parsing Cargo.toml file in: {contract_path:?}"); - e - })?; - doc.remove("profile"); + Ok(()) + } - write(&cargo_path, doc.to_string()).map_err(|e| { - eprintln!("Error writing to Cargo.toml file in: {contract_path:?}"); - e - })?; + fn copy_example_contracts( + &self, + from: &Path, + to: &Path, + contracts: &[String], + ) -> Result<(), Error> { + let project_contracts_path = to.join("contracts"); + for contract in contracts { + self.print + .infoln(format!("Initializing example contract: {contract}")); + let contract_as_string = contract.to_string(); + let contract_path = Path::new(&contract_as_string); + let from_contract_path = from.join(contract_path); + let to_contract_path = project_contracts_path.join(contract_path); + create_dir_all(&to_contract_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {contract_path:?}")); + e + })?; - Ok(()) -} + self.copy_contents(&from_contract_path, &to_contract_path)?; + self.edit_contract_cargo_file(&to_contract_path)?; + } -fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - println!("ℹ️ Initializing with frontend template"); - copy_contents(from, to, overwrite)?; - edit_package_json_files(to) -} + Ok(()) + } -fn edit_package_json_files(project_path: &Path) -> Result<(), Error> { - let package_name = if let Some(name) = project_path.file_name() { - name.to_owned() - } else { - let current_dir = env::current_dir()?; - let file_name = current_dir - .file_name() - .unwrap_or(OsStr::new("soroban-astro-template")) - .to_os_string(); - file_name - }; + fn edit_contract_cargo_file(&self, contract_path: &Path) -> Result<(), Error> { + let cargo_path = contract_path.join("Cargo.toml"); + let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { + self.print.errorln(format!( + "Error reading Cargo.toml file in: {contract_path:?}" + )); + e + })?; - edit_package_name(project_path, &package_name, "package.json").map_err(|e| { - eprintln!("Error editing package.json file in: {project_path:?}"); - e - })?; - edit_package_name(project_path, &package_name, "package-lock.json") -} + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) + .unwrap() + .replace_all( + cargo_toml_str.as_str(), + "soroban-sdk = { workspace = true }", + ); -fn edit_package_name( - project_path: &Path, - package_name: &OsStr, - file_name: &str, -) -> Result<(), Error> { - let file_path = project_path.join(file_name); - let file_contents = read_to_string(&file_path)?; + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) + .unwrap() + .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); - let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { - eprintln!("Error parsing package.json file in: {project_path:?}"); - e - })?; + let mut doc = cargo_toml_str.parse::().map_err(|e| { + self.print.errorln(format!( + "Error parsing Cargo.toml file in: {contract_path:?}" + )); + e + })?; + doc.remove("profile"); - doc["name"] = json!(package_name.to_string_lossy()); + write(&cargo_path, doc.to_string()).map_err(|e| { + self.print.errorln(format!( + "Error writing to Cargo.toml file in: {contract_path:?}" + )); + e + })?; - let formatted_json = to_string_pretty(&doc)?; + Ok(()) + } - write(&file_path, formatted_json)?; + fn copy_frontend_files(&self, from: &Path, to: &Path) -> Result<(), Error> { + self.print.infoln("ℹ️ Initializing with frontend template"); + self.copy_contents(from, to)?; + self.edit_package_json_files(to) + } - Ok(()) -} + fn edit_package_json_files(&self, project_path: &Path) -> Result<(), Error> { + let package_name = if let Some(name) = project_path.file_name() { + name.to_owned() + } else { + let current_dir = env::current_dir()?; + let file_name = current_dir + .file_name() + .unwrap_or(OsStr::new("soroban-astro-template")) + .to_os_string(); + file_name + }; -fn check_internet_connection() -> bool { - if let Ok(_req) = get(GITHUB_URL).call() { - return true; + self.edit_package_name(project_path, &package_name, "package.json") + .map_err(|e| { + self.print.errorln(format!( + "Error editing package.json file in: {project_path:?}" + )); + e + })?; + self.edit_package_name(project_path, &package_name, "package-lock.json") } - false -} + fn edit_package_name( + &self, + project_path: &Path, + package_name: &OsStr, + file_name: &str, + ) -> Result<(), Error> { + let file_path = project_path.join(file_name); + let file_contents = read_to_string(&file_path)?; + + let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { + self.print.errorln(format!( + "Error parsing {file_name} file in: {project_path:?}" + )); + e + })?; + + doc["name"] = json!(package_name.to_string_lossy()); -// Appends the contents of a file to another file, separated by a delimiter -fn append_contents(from: &Path, to: &Path) -> Result<(), Error> { - let mut from_file = File::open(from)?; - let mut from_content = String::new(); - from_file.read_to_string(&mut from_content)?; + let formatted_json = to_string_pretty(&doc)?; - let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; - let mut to_content = String::new(); - to_file.read_to_string(&mut to_content)?; + write(&file_path, formatted_json)?; - let delimiter = get_merged_file_delimiter(to); - // if the to file already contains the delimiter, we don't need to append the contents again - if to_content.contains(&delimiter) { - return Ok(()); + Ok(()) } - to_file.write_all(delimiter.as_bytes())?; - to_file.write_all(from_content.as_bytes())?; + // Appends the contents of a file to another file, separated by a delimiter + fn append_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let mut from_file = File::open(from)?; + let mut from_content = String::new(); + from_file.read_to_string(&mut from_content)?; - println!("ℹ️ Merging {} contents", &to.to_string_lossy()); - Ok(()) -} + let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; + let mut to_content = String::new(); + to_file.read_to_string(&mut to_content)?; -fn get_merged_file_delimiter(file_path: &Path) -> String { - let comment = if file_path.to_string_lossy().contains("README.md") { - "---\n".to_string() - } else if file_path.to_string_lossy().contains("gitignore") { - "# The following is from the Frontend Template's .gitignore".to_string() - } else { - String::new() - }; + let delimiter = Self::get_merged_file_delimiter(to); + // if the to file already contains the delimiter, we don't need to append the contents again + if to_content.contains(&delimiter) { + return Ok(()); + } + + to_file.write_all(delimiter.as_bytes())?; + to_file.write_all(from_content.as_bytes())?; + + self.print.infoln(format!("Merging {to:?} contents")); + Ok(()) + } + + fn get_merged_file_delimiter(file_path: &Path) -> String { + let comment = if file_path.to_string_lossy().contains("README.md") { + "---\n".to_string() + } else if file_path.to_string_lossy().contains("gitignore") { + "# The following is from the Frontend Template's .gitignore".to_string() + } else { + String::new() + }; - format!("\n\n{comment}\n\n").to_string() + format!("\n\n{comment}\n\n").to_string() + } } #[cfg(test)] @@ -482,9 +513,16 @@ mod tests { fn test_init() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -501,9 +539,16 @@ mod tests { fn test_init_including_example_contract() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = ["alloc".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["alloc".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -525,9 +570,16 @@ mod tests { fn test_init_including_multiple_example_contracts() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); - let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["account".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -550,9 +602,16 @@ mod tests { fn test_init_with_invalid_example_contract() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); - let with_examples = ["invalid_example".to_owned(), "atomic_swap".to_owned()]; - let overwrite = false; - assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["invalid_example".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: String::new(), + overwrite: false, + }, + print: print::Print::new(false), + }; + assert!(runner.run().is_err()); temp_dir.close().unwrap(); } @@ -561,15 +620,16 @@ mod tests { fn test_init_with_frontend_template() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -591,28 +651,33 @@ mod tests { fn test_init_with_overwrite() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; // First initialization - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - false, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // Get initial modification times let initial_mod_times = get_mod_times(&project_dir); // Second initialization with overwrite - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - true, // overwrite = true - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: true, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // Get new modification times let new_mod_times = get_mod_times(&project_dir); @@ -647,15 +712,16 @@ mod tests { fn test_init_from_within_an_existing_project() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("./"); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -679,24 +745,28 @@ mod tests { fn test_init_does_not_duplicate_frontend_readme_contents_when_run_more_than_once() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // call init again to make sure the README.md's contents are not duplicated - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -743,7 +813,6 @@ mod tests { let cargo_toml_path = contract_dir.as_path().join("Cargo.toml"); let cargo_toml_str = read_to_string(cargo_toml_path.clone()).unwrap(); let doc = cargo_toml_str.parse::().unwrap(); - println!("{cargo_toml_path:?} contents:\n{cargo_toml_str}"); assert!( doc.get("dependencies") .unwrap() diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index 48ff44fc40..ce21bf5ba4 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -158,7 +158,7 @@ impl NetworkRunnable for Cmd { // Skip reupload if this isn't V0 because V1 extension already // exists. if code.ext.ne(&ContractCodeEntryExt::V0) { - print.info("Skipping install because wasm already installed"); + print.infoln("Skipping install because wasm already installed"); return Ok(TxnResult::Res(hash)); } } @@ -170,7 +170,7 @@ impl NetworkRunnable for Cmd { } } - print.info("Simulating install transaction…"); + print.infoln("Simulating install transaction…"); let txn = client .simulate_and_assemble_transaction(&tx_without_preflight) @@ -181,7 +181,7 @@ impl NetworkRunnable for Cmd { return Ok(TxnResult::Txn(txn)); } - print.globe("Submitting install transaction…"); + print.globeln("Submitting install transaction…"); let txn_resp = client .send_transaction_polling(&self.config.sign(txn).await?) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 6f5ae326b0..816431c14c 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::convert::{Infallible, TryInto}; use std::ffi::OsString; use std::num::ParseIntError; @@ -6,36 +5,37 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt::Debug, fs, io}; -use clap::{arg, command, value_parser, Parser, ValueEnum}; -use heck::ToKebabCase; +use clap::{arg, command, Parser, ValueEnum}; use soroban_env_host::{ xdr::{ self, AccountEntry, AccountEntryExt, AccountId, ContractEvent, ContractEventType, - DiagnosticEvent, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, - LedgerEntryData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - PublicKey, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, - SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, Uint256, VecM, - WriteXdr, + DiagnosticEvent, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, + Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, + ScSpecEntry, SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, + Uint256, VecM, WriteXdr, }, HostError, }; use soroban_rpc::{SimulateHostFunctionResult, SimulateTransactionResponse}; -use soroban_sdk::xdr::ScSpecFunctionInputV0; use soroban_spec::read::FromWasmError; use super::super::events; -use crate::commands::config::secret::StellarSigner; +use super::arg_parsing; +use crate::commands::contract::arg_parsing::{build_host_function_parameters, output_to_string}; use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; -use crate::config::{self, data, locator, network}; use crate::get_spec::{self, get_remote_contract_spec}; use crate::print; -use crate::signer::auth::sign_soroban_authorizations; -use crate::signer::{self, Stellar}; -use crate::{commands::global, rpc, Pwd}; -use soroban_spec_tools::{contract, Spec}; +use crate::signer::{self, auth::sign_soroban_authorizations}; +// use crate::signer; +use crate::{ + commands::global, + config::{self, data, locator, network}, + rpc, Pwd, +}; +use soroban_spec_tools::contract; #[derive(Parser, Debug, Default, Clone)] #[allow(clippy::struct_excessive_bools)] @@ -81,11 +81,6 @@ impl Pwd for Cmd { #[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(xdr::Error), #[error(transparent)] @@ -99,19 +94,8 @@ pub enum Error { filepath: std::path::PathBuf, error: events::Error, }, - #[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] xdr::Error), #[error("error parsing int: {0}")] @@ -122,16 +106,12 @@ pub enum Error { UnexpectedContractCodeDataType(LedgerEntryData), #[error("missing operation result")] MissingOperationResult, - #[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)] @@ -142,8 +122,6 @@ pub enum Error { StrKey(#[from] stellar_strkey::DecodeError), #[error(transparent)] ContractSpec(#[from] contract::Error), - #[error("")] - MissingFileArg(PathBuf), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] @@ -154,6 +132,8 @@ pub enum Error { GetSpecError(#[from] get_spec::Error), #[error(transparent)] Signer(#[from] signer::Error), + #[error(transparent)] + ArgParsing(#[from] arg_parsing::Error), } impl From for Error { @@ -163,117 +143,6 @@ impl From for Error { } impl Cmd { - async fn build_host_function_parameters( - &self, - contract_id: [u8; 32], - spec_entries: &[ScSpecEntry], - config: &config::Args, - ) -> 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 mut parsed_args: Vec = Vec::new(); - for i in func.inputs.iter() { - let (val, signer) = self.parse_arg(i, matches_, config, &spec).await?; - parsed_args.push(val); - if let Some(signer) = signer { - signers.push(signer); - } - } - - 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 parse_arg( - &self, - input: &ScSpecFunctionInputV0, - matches_: &clap::ArgMatches, - config: &config::Args, - spec: &Spec, - ) -> Result<(ScVal, Option), Error> { - let mut signer: Option = None; - let name = input.name.to_utf8_string()?; - let sc_val = if let Some(mut val) = matches_.get_raw(&name) { - let mut s = val.next().unwrap().to_string_lossy().to_string(); - if matches!(input.type_, ScSpecTypeDef::Address) { - if let Ok(signer_) = config - .sign_with - .locator - .account(&s) - .and_then(|signer| Ok(signer.signer(config.sign_with.hd_path, false)?)) - { - s = signer_.get_public_key().await?.to_string(); - signer = Some(signer_); - } - } - spec.from_string(&s, &input.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error })? - } else if matches!(input.type_, ScSpecTypeDef::Option(_)) { - ScVal::Void - } else if let Some(arg_path) = matches_.get_one::(&fmt_arg_file_name(&name)) { - if matches!(input.type_, ScSpecTypeDef::Bytes | ScSpecTypeDef::BytesN(_)) { - 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{}", - input.type_, - file_contents.len() - ); - spec.from_string(&file_contents, &input.type_) - .map_err(|error| Error::CannotParseArg { arg: name, error })? - } - } else { - return Err(Error::MissingArgument(name)); - }; - Ok((sc_val, signer)) - } - pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { let res = self.invoke(global_args).await?.to_envelope(); match res { @@ -336,12 +205,16 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract_id = config.resolve_contract_id(&self.contract_id)?.0; + let contract_id = self + .config + .sign_with + .locator + .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; + 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, config) + let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config) .await?; } let client = rpc::Client::new(&network.rpc_url)?; @@ -360,7 +233,7 @@ impl NetworkRunnable for Cmd { let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; let spec_entries = get_remote_contract_spec( - &contract_id, + &contract_id.0, &config.sign_with.locator, &config.sign_with.network, global_args, @@ -370,9 +243,8 @@ impl NetworkRunnable for Cmd { .map_err(Error::from)?; // Get the ledger footprint - let (function, spec, host_function_params, signers) = self - .build_host_function_parameters(contract_id, &spec_entries, config) - .await?; + let (function, spec, host_function_params, signers) = + build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config).await?; let tx = build_invoke_contract_tx( host_function_params.clone(), sequence + 1, @@ -427,7 +299,7 @@ impl NetworkRunnable for Cmd { } }; crate::log::events(&events); - output_to_string(&spec, &return_value, &function) + Ok(output_to_string(&spec, &return_value, &function)?) } } @@ -476,24 +348,6 @@ fn default_account_entry() -> AccountEntry { } } -pub fn output_to_string( - spec: &Spec, - res: &ScVal, - function: &str, -) -> Result, Error> { - 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(TxnResult::Res(res_str)) -} - fn build_invoke_contract_tx( parameters: InvokeContractArgs, sequence: i64, @@ -518,87 +372,6 @@ fn build_invoke_contract_tx( }) } -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_ { - ScSpecTypeDef::Bool => arg - .num_args(0..1) - .default_missing_value("true") - .default_value("false") - .num_args(0..=1), - ScSpecTypeDef::Option(_val) => arg.required(false), - ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | 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 BytesN, which are raw bytes"# - ) -} - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum, Default)] pub enum Send { /// Send transaction if simulation indicates there are ledger writes, diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index c15284c07e..2997bcea3e 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -1,3 +1,5 @@ +pub mod alias; +pub mod arg_parsing; pub mod asset; pub mod bindings; pub mod build; @@ -21,6 +23,11 @@ pub enum Cmd { /// Utilities to deploy a Stellar Asset Contract or get its id #[command(subcommand)] Asset(asset::Cmd), + + /// Utilities to manage contract aliases + #[command(subcommand)] + Alias(alias::Cmd), + /// Generate code client bindings for a contract #[command(subcommand)] Bindings(bindings::Cmd), @@ -82,6 +89,9 @@ pub enum Error { #[error(transparent)] Asset(#[from] asset::Error), + #[error(transparent)] + Alias(#[from] alias::Error), + #[error(transparent)] Bindings(#[from] bindings::Error), @@ -132,10 +142,11 @@ impl Cmd { Cmd::Bindings(bindings) => bindings.run().await?, Cmd::Build(build) => build.run()?, Cmd::Extend(extend) => extend.run().await?, + Cmd::Alias(alias) => alias.run(global_args)?, Cmd::Deploy(deploy) => deploy.run(global_args).await?, Cmd::Id(id) => id.run().await?, Cmd::Info(info) => info.run().await?, - Cmd::Init(init) => init.run()?, + Cmd::Init(init) => init.run(global_args)?, Cmd::Inspect(inspect) => inspect.run()?, Cmd::Install(install) => install.run(global_args).await?, Cmd::Invoke(invoke) => invoke.run(global_args).await?, diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index b688a28b86..8398a281b4 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -186,8 +186,7 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let client = Client::new(&network.rpc_url)?; - let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; - let keys = self.key.parse_keys(contract)?; + let keys = self.key.parse_keys(&config.sign_with.locator, &network)?; Ok(client.get_full_ledger_entries(&keys).await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 8ca899328f..d3cdad6d0f 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -133,8 +133,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let network = config.get_network()?; tracing::trace!(?network); - let contract = config.resolve_contract_id(self.key.contract_id.as_ref().unwrap())?; - let entry_keys = self.key.parse_keys(contract)?; + let entry_keys = self.key.parse_keys(&config.sign_with.locator, &network)?; let client = Client::new(&network.rpc_url)?; // Get the account sequence number diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index a755c33d04..5e2bda66f8 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -176,8 +176,6 @@ impl Cmd { OutputFormat::Pretty => event.pretty_print()?, } } - println!("Latest Ledger: {}", response.latest_ledger); - Ok(()) } diff --git a/cmd/soroban-cli/src/commands/network/container/start.rs b/cmd/soroban-cli/src/commands/network/container/start.rs index 73c80893dc..a4d840f0fe 100644 --- a/cmd/soroban-cli/src/commands/network/container/start.rs +++ b/cmd/soroban-cli/src/commands/network/container/start.rs @@ -82,17 +82,34 @@ impl Runner { let docker = self.args.container_args.connect_to_docker().await?; let image = self.get_image_name(); - docker - .create_image( - Some(CreateImageOptions { - from_image: image.clone(), - ..Default::default() - }), - None, - None, - ) - .try_collect::>() - .await?; + let mut stream = docker.create_image( + Some(CreateImageOptions { + from_image: image.clone(), + ..Default::default() + }), + None, + None, + ); + + while let Some(result) = stream.try_next().await.transpose() { + if let Ok(item) = result { + if let Some(status) = item.status { + if status.contains("Pulling from") + || status.contains("Digest") + || status.contains("Status") + { + self.print.infoln(status); + } + } + } else { + self.print + .warnln(format!("Failed to fetch image: {image}.")); + self.print.warnln( + "Attempting to start local quickstart image. The image may be out-of-date.", + ); + break; + } + } let config = Config { image: Some(image), diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 4ac749208d..c29cdfb2ec 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -68,6 +68,8 @@ pub enum Error { Json(#[from] serde_json::Error), #[error("cannot access config dir for alias file")] CannotAccessConfigDir, + #[error("cannot access alias config file (no permission or doesn't exist)")] + CannotAccessAliasConfigFile, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), } @@ -285,6 +287,29 @@ impl Args { Ok(to_file.write_all(content.as_bytes())?) } + pub fn remove_contract_id(&self, network_passphrase: &str, alias: &str) -> Result<(), Error> { + let path = self.alias_path(alias)?; + + if !path.is_file() { + return Err(Error::CannotAccessAliasConfigFile); + } + + let content = fs::read_to_string(&path).unwrap_or_default(); + let mut data: alias::Data = serde_json::from_str(&content).unwrap_or_default(); + + let mut to_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path)?; + + data.ids.remove::(network_passphrase); + + let content = serde_json::to_string(&data)?; + + Ok(to_file.write_all(content.as_bytes())?) + } + pub fn get_contract_id( &self, alias: &str, diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 456e820870..b5e7dc0f0b 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -40,7 +40,7 @@ pub enum Error { #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct Args { - /// Sign with account. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). + /// Sign with a local key. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path. #[arg( long, conflicts_with = "sign_with_lab", diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs index 8852959363..b4fd358aa6 100644 --- a/cmd/soroban-cli/src/key.rs +++ b/cmd/soroban-cli/src/key.rs @@ -1,12 +1,14 @@ +use crate::{ + commands::contract::Durability, + config::{locator, network::Network}, + wasm, +}; use clap::arg; use soroban_env_host::xdr::{ self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, ScVal, }; use std::path::PathBuf; -use stellar_strkey::Contract; - -use crate::{commands::contract::Durability, wasm}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -18,6 +20,8 @@ pub enum Error { CannotParseContractId(String, stellar_strkey::DecodeError), #[error(transparent)] Wasm(#[from] wasm::Error), + #[error(transparent)] + Locator(#[from] locator::Error), } #[derive(Debug, clap::Args, Clone)] @@ -61,7 +65,13 @@ pub struct Args { } impl Args { - pub fn parse_keys(&self, contract: Contract) -> Result, Error> { + pub fn parse_keys( + &self, + locator: &locator::Args, + Network { + network_passphrase, .. + }: &Network, + ) -> Result, Error> { let keys = if let Some(keys) = &self.key { keys.iter() .map(|key| { @@ -87,6 +97,8 @@ impl Args { } else { vec![ScVal::LedgerKeyContractInstance] }; + let contract = + locator.resolve_contract_id(self.contract_id.as_ref().unwrap(), network_passphrase)?; Ok(keys .into_iter() diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index fd1eb3d586..2afa5ec03a 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -86,5 +86,7 @@ create_print_functions!(error, errorln, "❌"); create_print_functions!(globe, globeln, "🌎"); create_print_functions!(info, infoln, "ℹ️"); create_print_functions!(link, linkln, "🔗"); +create_print_functions!(plus, plusln, "➕"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); +create_print_functions!(warn, warnln, "⚠️"); diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs index fb23e50556..f3eb78a360 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs +++ b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs @@ -1,13 +1,13 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, symbol_short, vec, Env, Symbol, Vec}; +use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec}; #[contract] pub struct HelloContract; #[contractimpl] impl HelloContract { - pub fn hello(env: Env, to: Symbol) -> Vec { - vec![&env, symbol_short!("Hello"), to] + pub fn hello(env: Env, to: String) -> Vec { + vec![&env, String::from_str(&env, "Hello"), to] } } diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs index e72c6bb9fa..4b250446ae 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs +++ b/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs @@ -1,7 +1,7 @@ #![cfg(test)] use super::*; -use soroban_sdk::{symbol_short, vec, Env}; +use soroban_sdk::{vec, Env, String}; #[test] fn test() { @@ -9,9 +9,13 @@ fn test() { let contract_id = env.register_contract(None, HelloContract); let client = HelloContractClient::new(&env, &contract_id); - let words = client.hello(&symbol_short!("Dev")); + let words = client.hello(&String::from_str(&env, "Dev")); assert_eq!( words, - vec![&env, symbol_short!("Hello"), symbol_short!("Dev"),] + vec![ + &env, + String::from_str(&env, "Hello"), + String::from_str(&env, "Dev"), + ] ); } diff --git a/cmd/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml index ab87541992..40fa29dcfe 100644 --- a/cmd/stellar-cli/Cargo.toml +++ b/cmd/stellar-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.3.0" +version = "21.4.1" edition = "2021" rust-version.workspace = true autobins = false