diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index e33f5f47f6..3057daaa9a 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -2325,7 +2325,7 @@ Calls the token's Stellar Asset Contract `mint` function. A non-SAC contract wit ###### **Options:** - `--id ` — The token to mint: a contract id or alias, or a classic asset as `CODE:ISSUER` -- `--to ` — Account or contract to mint the tokens to. Accepts a `G…`/`M…` account, a `C…` contract address, or an alias +- `--to ` — Account or contract to mint the tokens to. Accepts a `G…` account, a `C…` contract address, or an alias - `--amount ` — Amount to mint, in the token's smallest unit (stroops for a Stellar Asset Contract) - `--output ` — Format of the output diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs index a2cfb5e522..a3e1893a20 100644 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ b/cmd/crates/soroban-spec-tools/src/lib.rs @@ -2514,7 +2514,7 @@ mod tests { fn test_find_events_empty_spec() { let spec = make_spec_with_events_and_functions(vec![], vec![]); let found_events: Vec<_> = spec.find_events().unwrap().collect(); - assert!(found_events.is_empty()); + assert_eq!(found_events, Vec::<&ScSpecEventV0>::new()); } #[test] diff --git a/cmd/crates/soroban-spec-tools/src/verify.rs b/cmd/crates/soroban-spec-tools/src/verify.rs index b3af7405c7..dc5644b3b3 100644 --- a/cmd/crates/soroban-spec-tools/src/verify.rs +++ b/cmd/crates/soroban-spec-tools/src/verify.rs @@ -273,7 +273,7 @@ mod tests { make_fn_entry("do_thing", vec![("s", make_udt_type("MyStruct"))], vec![]), ]; let spec = Spec::new(&entries); - assert!(spec.verify().is_empty()); + assert_eq!(spec.verify(), Vec::::new()); } #[test] @@ -301,7 +301,7 @@ mod tests { vec![], )]; let spec = Spec::new(&entries); - assert!(spec.verify().is_empty()); + assert_eq!(spec.verify(), Vec::::new()); } #[test] diff --git a/cmd/crates/soroban-test/tests/it/integration/ledger/entry.rs b/cmd/crates/soroban-test/tests/it/integration/ledger/entry.rs index 8211b12179..378d97f03c 100644 --- a/cmd/crates/soroban-test/tests/it/integration/ledger/entry.rs +++ b/cmd/crates/soroban-test/tests/it/integration/ledger/entry.rs @@ -47,7 +47,7 @@ async fn ledger_entry_account_with_alias() { let (_, expected_key) = expected_account_ledger_key(&new_account_addr).await; let parsed: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed.entries.is_empty()); + assert_ne!(parsed.entries.len(), 0); assert_eq!(parsed.entries[0].key, expected_key); assert!(matches!( parsed.entries[0].val, @@ -75,7 +75,7 @@ async fn ledger_entry_account_with_account_addr() { let (_, expected_key) = expected_account_ledger_key(&new_account_addr).await; let parsed: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed.entries.is_empty()); + assert_ne!(parsed.entries.len(), 0); assert_eq!(parsed.entries[0].key, expected_key); assert!(matches!( parsed.entries[0].val, @@ -132,7 +132,7 @@ async fn ledger_entry_trustline_asset_usdc() { }); let parsed: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed.entries.is_empty()); + assert_ne!(parsed.entries.len(), 0); let trustline_entry = &parsed.entries[0]; assert_eq!(trustline_entry.key, expected_trustline_key); @@ -166,7 +166,7 @@ async fn ledger_entry_account_data() { .stdout_as_str(); let parsed: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed.entries.is_empty()); + assert_ne!(parsed.entries.len(), 0); let (account_id, _) = expected_account_ledger_key(&new_account_addr).await; @@ -222,7 +222,7 @@ async fn ledger_entry_contract_data() { .success() .stdout_as_str(); let parsed_key_output: FullLedgerEntries = serde_json::from_str(&key_output).unwrap(); - assert!(!parsed_key_output.entries.is_empty()); + assert_ne!(parsed_key_output.entries.len(), 0); // get entry by key xdr let key_xdr_output = sandbox @@ -240,7 +240,7 @@ async fn ledger_entry_contract_data() { .success() .stdout_as_str(); let parsed_key_xdr_output: FullLedgerEntries = serde_json::from_str(&key_xdr_output).unwrap(); - assert!(!parsed_key_xdr_output.entries.is_empty()); + assert_ne!(parsed_key_xdr_output.entries.len(), 0); let expected_contract_data_key = expected_contract_ledger_key(&contract_id, storage_key).await; @@ -282,7 +282,7 @@ async fn ledger_entry_contract_data() { key: ScVal::LedgerKeyContractInstance, durability: ContractDataDurability::Persistent, }); - assert!(!parsed_instance_output.entries.is_empty()); + assert_ne!(parsed_instance_output.entries.len(), 0); assert_eq!(parsed_instance_output.entries[0].key, expected_instance_key); assert!(matches!( parsed_instance_output.entries[0].val, @@ -331,7 +331,7 @@ async fn ledger_entry_contract_code() { .stdout_as_str(); let parsed_output: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed_output.entries.is_empty()); + assert_ne!(parsed_output.entries.len(), 0); let hash = Hash( padded_hex_from_str(&contract_wasm_hash, 32) @@ -378,7 +378,7 @@ async fn ledger_entry_claimable_balance() { .stdout_as_str(); let parsed_output: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed_output.entries.is_empty()); + assert_ne!(parsed_output.entries.len(), 0); let expected_key = LedgerKey::ClaimableBalance(LedgerKeyClaimableBalance { balance_id: ClaimableBalanceId::ClaimableBalanceIdTypeV0(id), }); @@ -431,7 +431,7 @@ async fn ledger_entry_liquidity_pool() { .stdout_as_str(); let parsed_output: FullLedgerEntries = serde_json::from_str(&output).expect("Failed to parse JSON"); - assert!(!parsed_output.entries.is_empty()); + assert_ne!(parsed_output.entries.len(), 0); let expected_key = LedgerKey::LiquidityPool(LedgerKeyLiquidityPool { liquidity_pool_id: PoolId(Hash(pool_id.0)), }); diff --git a/cmd/crates/soroban-test/tests/it/integration/token/mint.rs b/cmd/crates/soroban-test/tests/it/integration/token/mint.rs index eb2372f112..4134dd02aa 100644 --- a/cmd/crates/soroban-test/tests/it/integration/token/mint.rs +++ b/cmd/crates/soroban-test/tests/it/integration/token/mint.rs @@ -111,6 +111,26 @@ async fn mint_rejects_muxed_source_with_clear_error() { )); } +#[tokio::test] +async fn mint_rejects_muxed_to_with_clear_error() { + let sandbox = &TestEnv::new(); + + // A muxed (M…) recipient isn't a valid `mint` target — the host rejects it + // mid-simulation with an opaque error — so the command rejects it up front + // with a clear message. + let muxed = "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU"; + sandbox + .new_assert_cmd("token") + .args([ + "mint", "--id", "native", "--source", "test", "--to", muxed, "--amount", "1", + ]) + .assert() + .failure() + .stderr(predicates::str::contains( + "muxed (M…) recipient accounts are not yet supported", + )); +} + #[tokio::test] async fn mint_warns_when_target_is_not_a_sac() { let sandbox = &TestEnv::new(); diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs b/cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs index 40dc423310..1e7e89cc5e 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs @@ -368,7 +368,7 @@ async fn tx_fetch_events() { let parsed: GetTransactionEvents = serde_json::from_str(&output).unwrap(); // With diagnostic events enabled on the network, the RPC surfaces the full // diagnostic stream (fn_call/fn_return, the contract log, and core_metrics). - assert!(!parsed.diagnostic_events.is_empty()); + assert_ne!(parsed.diagnostic_events.len(), 0); assert_eq!(parsed.contract_events.len(), 1); assert_eq!(parsed.transaction_events.len(), 2); } diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/set_options.rs b/cmd/crates/soroban-test/tests/it/integration/tx/set_options.rs index 961b35ca46..b11beccbed 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/set_options.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/set_options.rs @@ -173,7 +173,7 @@ async fn set_some_options() { after.home_domain, "".parse::>().unwrap().into() ); - assert!(after.signers.is_empty()); + assert_eq!(after.signers.len(), 0); sandbox .new_assert_cmd("tx") .args(["new", "set-options", "--set-clawback-enabled"]) diff --git a/cmd/crates/stellar-ledger/src/signer.rs b/cmd/crates/stellar-ledger/src/signer.rs index 93ca90db51..cf35316ce5 100644 --- a/cmd/crates/stellar-ledger/src/signer.rs +++ b/cmd/crates/stellar-ledger/src/signer.rs @@ -1,4 +1,7 @@ #[async_trait::async_trait] +// async_trait emits `#[must_use]` on methods that already return a must_use +// `Pin>`, tripping clippy 1.99.0's double_must_use. See #2784. +#[allow(clippy::double_must_use)] pub trait Blob { type Key: Send; type Error; diff --git a/cmd/soroban-cli/src/commands/container/shared.rs b/cmd/soroban-cli/src/commands/container/shared.rs index ba450e1807..c3635cc63a 100644 --- a/cmd/soroban-cli/src/commands/container/shared.rs +++ b/cmd/soroban-cli/src/commands/container/shared.rs @@ -587,7 +587,7 @@ mod test { #[test] fn run_args_flags_emit_only_set_limits() { - assert!(RunArgs::default().flags().is_empty()); + assert_eq!(RunArgs::default().flags().len(), 0); assert_eq!( RunArgs { cpus: Some(1), diff --git a/cmd/soroban-cli/src/commands/contract/build/source_archive.rs b/cmd/soroban-cli/src/commands/contract/build/source_archive.rs index e7fa82dd8c..15870bdd6c 100644 --- a/cmd/soroban-cli/src/commands/contract/build/source_archive.rs +++ b/cmd/soroban-cli/src/commands/contract/build/source_archive.rs @@ -629,7 +629,7 @@ mod tests { let a = build_source_archive(root, &print, true, None).unwrap(); let b = build_source_archive(root, &print, true, None).unwrap(); - assert!(!a.is_empty()); + assert_ne!(a.len(), 0); assert_eq!(a, b, "same tree should produce identical bytes"); // The `.git` dir git_init_commit created is never archived. diff --git a/cmd/soroban-cli/src/commands/token/clawback.rs b/cmd/soroban-cli/src/commands/token/clawback.rs index 5decf2d52a..e6ae33718b 100644 --- a/cmd/soroban-cli/src/commands/token/clawback.rs +++ b/cmd/soroban-cli/src/commands/token/clawback.rs @@ -109,23 +109,24 @@ impl Cmd { if matches!(source_account, crate::xdr::MuxedAccount::MuxedEd25519(_)) { return Err(Error::MuxedSourceNotSupported); } - // `clawback` is a SAC-admin function; warn (in human-readable mode) if the - // target isn't actually a Stellar Asset Contract. - if !output.is_json() { - args::warn_if_not_sac(output.print(), "clawback", &token.contract_id, &network).await; - } // `--from` may be an account (`G…`), a contract (`C…`), or an alias. The // host rejects a muxed (`M…`) holder mid-simulation with an opaque error, - // so reject one up front with a clear message — whether supplied as a - // direct `M…` strkey or an alias resolving to a muxed key. + // so reject one up front — before any network round-trip — with a clear + // message, whether supplied as a direct `M…` strkey or an alias resolving + // to a muxed key. if self .from .is_muxed(&config.locator, &network.network_passphrase) { return Err(Error::MuxedFromNotSupported); } - // Resolve it to an `ScAddress` and hand the strkey to the `clawback` arg, - // which accepts any of these holders. + // `clawback` is a SAC-admin function; warn (in human-readable mode) if the + // target isn't actually a Stellar Asset Contract. + if !output.is_json() { + args::warn_if_not_sac(output.print(), "clawback", &token.contract_id, &network).await; + } + // Resolve `--from` to an `ScAddress` and hand the strkey to the `clawback` + // arg, which accepts any of these holders. let from = self .from .clone() diff --git a/cmd/soroban-cli/src/commands/token/mint.rs b/cmd/soroban-cli/src/commands/token/mint.rs index e8bd627ab0..66dd935174 100644 --- a/cmd/soroban-cli/src/commands/token/mint.rs +++ b/cmd/soroban-cli/src/commands/token/mint.rs @@ -18,8 +18,8 @@ pub struct Cmd { #[arg(long = "id")] pub id: UnresolvedToken, - /// Account or contract to mint the tokens to. Accepts a `G…`/`M…` account, a - /// `C…` contract address, or an alias. + /// Account or contract to mint the tokens to. Accepts a `G…` account, a `C…` + /// contract address, or an alias. #[arg(long)] pub to: UnresolvedScAddress, @@ -55,6 +55,9 @@ pub enum Error { #[error("muxed (M…) source accounts are not yet supported for `token mint`")] MuxedSourceNotSupported, + + #[error("muxed (M…) recipient accounts are not yet supported for `token mint`")] + MuxedToNotSupported, } /// Parse `--amount` as a non-negative `i128`. A negative mint amount is always @@ -82,7 +85,7 @@ impl Error { Error::ScAddress(_) => "invalid_address", Error::Invoke(_) => "invoke", Error::Serde(_) => "internal", - Error::MuxedSourceNotSupported => "unsupported", + Error::MuxedSourceNotSupported | Error::MuxedToNotSupported => "unsupported", } } } @@ -118,13 +121,23 @@ impl Cmd { if matches!(source_account, crate::xdr::MuxedAccount::MuxedEd25519(_)) { return Err(Error::MuxedSourceNotSupported); } + // `--to` may be an account (`G…`), a contract (`C…`), or an alias. The + // host rejects a muxed (`M…`) recipient mid-simulation with an opaque + // error, so reject one up front — before any network round-trip — with a + // clear message, whether supplied as a direct `M…` strkey or an alias + // resolving to a muxed key. + if self + .to + .is_muxed(&config.locator, &network.network_passphrase) + { + return Err(Error::MuxedToNotSupported); + } // `mint` is a SAC-admin function; warn (in human-readable mode) if the // target isn't actually a Stellar Asset Contract. if !output.is_json() { args::warn_if_not_sac(output.print(), "mint", &token.contract_id, &network).await; } - // `--to` may be an account (`G…`/`M…`), a contract (`C…`), or an alias; - // resolve it to an `ScAddress` and hand the strkey to the `mint` arg, + // Resolve `--to` to an `ScAddress` and hand the strkey to the `mint` arg, // which accepts any of these destinations. let to = self .to diff --git a/cmd/soroban-cli/src/commands/token/set_admin.rs b/cmd/soroban-cli/src/commands/token/set_admin.rs index 13a16e73cb..f31820cf4f 100644 --- a/cmd/soroban-cli/src/commands/token/set_admin.rs +++ b/cmd/soroban-cli/src/commands/token/set_admin.rs @@ -104,24 +104,24 @@ impl Cmd { if matches!(source_account, crate::xdr::MuxedAccount::MuxedEd25519(_)) { return Err(Error::MuxedSourceNotSupported); } - // `set_admin` is a SAC-admin function; warn (in human-readable mode) if - // the target isn't actually a Stellar Asset Contract. - if !output.is_json() { - args::warn_if_not_sac(output.print(), "set_admin", &token.contract_id, &network).await; - } // `--new-admin` may be an account (`G…`), a contract (`C…`), or an alias. // A muxed (`M…`) successor would be stranded — it can't sign as a source // (see #2645) and the SAC stores a plain `Address` — so reject one up - // front rather than performing an irreversible transfer to an - // unusable admin. + // front, before any network round-trip, rather than performing an + // irreversible transfer to an unusable admin. if self .new_admin .is_muxed(&config.locator, &network.network_passphrase) { return Err(Error::MuxedNewAdminNotSupported); } - // Resolve it to an `ScAddress` and hand the strkey to the `set_admin` - // arg, which accepts any of these administrators. + // `set_admin` is a SAC-admin function; warn (in human-readable mode) if + // the target isn't actually a Stellar Asset Contract. + if !output.is_json() { + args::warn_if_not_sac(output.print(), "set_admin", &token.contract_id, &network).await; + } + // Resolve `--new-admin` to an `ScAddress` and hand the strkey to the + // `set_admin` arg, which accepts any of these administrators. let new_admin = self .new_admin .clone() diff --git a/cmd/soroban-cli/src/config/network.rs b/cmd/soroban-cli/src/config/network.rs index a089aec2ac..113698091d 100644 --- a/cmd/soroban-cli/src/config/network.rs +++ b/cmd/soroban-cli/src/config/network.rs @@ -582,7 +582,7 @@ mod tests { .expect("passphrase-only network should resolve for signing-only commands"); assert_eq!(network.network_passphrase, "specified manually"); assert_eq!(network.rpc_url, ""); - assert!(network.rpc_headers.is_empty()); + assert_eq!(network.rpc_headers.len(), 0); } #[test]