From d33a9fc8d1f78310e1b7f16bf8cd8fb9389d09ca Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 5 Nov 2024 11:00:43 -0500 Subject: [PATCH 01/15] feat: Add new `Key` type; allowing public keys to be named keys This will allow users to add public keys with `keys add` so that they can be referenced in commands that need public addresses and contract invoke's that take address arguments. --- Cargo.lock | 9 +- cmd/soroban-cli/Cargo.toml | 1 + cmd/soroban-cli/src/commands/keys/add.rs | 6 +- cmd/soroban-cli/src/commands/keys/address.rs | 18 ++- cmd/soroban-cli/src/commands/keys/show.rs | 7 +- cmd/soroban-cli/src/config/address.rs | 14 +- cmd/soroban-cli/src/config/key.rs | 146 +++++++++++++++++++ cmd/soroban-cli/src/config/locator.rs | 30 +++- cmd/soroban-cli/src/config/mod.rs | 1 + cmd/soroban-cli/src/config/secret.rs | 23 ++- cmd/soroban-cli/src/config/sign_with.rs | 2 +- 11 files changed, 214 insertions(+), 43 deletions(-) create mode 100644 cmd/soroban-cli/src/config/key.rs diff --git a/Cargo.lock b/Cargo.lock index 3f8fcf4ac..609b5be2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4067,9 +4067,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -4085,9 +4085,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", @@ -4336,6 +4336,7 @@ dependencies = [ "serde", "serde-aux", "serde_json", + "serde_with", "sha2 0.10.8", "shell-escape", "shlex", diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 2f2a26255..e6e205248 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -124,6 +124,7 @@ fqdn = "0.3.12" open = "5.3.0" url = "2.5.2" wasm-gen = "0.1.4" +serde_with = "3.11.0" [build-dependencies] crate-git-revision = "0.0.6" diff --git a/cmd/soroban-cli/src/commands/keys/add.rs b/cmd/soroban-cli/src/commands/keys/add.rs index d8f528bae..5e32944e8 100644 --- a/cmd/soroban-cli/src/commands/keys/add.rs +++ b/cmd/soroban-cli/src/commands/keys/add.rs @@ -1,11 +1,11 @@ use clap::command; -use crate::config::{locator, secret}; +use crate::config::{key, locator, secret}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error(transparent)] Config(#[from] locator::Error), @@ -28,6 +28,6 @@ impl Cmd { pub fn run(&self) -> Result<(), Error> { Ok(self .config_locator - .write_identity(&self.name, &self.secrets.read_secret()?)?) + .write_key(&self.name, &self.secrets.read_key()?)?) } } diff --git a/cmd/soroban-cli/src/commands/keys/address.rs b/cmd/soroban-cli/src/commands/keys/address.rs index d13381b49..8eda8dd83 100644 --- a/cmd/soroban-cli/src/commands/keys/address.rs +++ b/cmd/soroban-cli/src/commands/keys/address.rs @@ -1,15 +1,14 @@ -use crate::commands::config::secret; - -use super::super::config::locator; use clap::arg; +use crate::commands::config::{key, locator}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Config(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error(transparent)] StrKey(#[from] stellar_strkey::DecodeError), @@ -36,10 +35,13 @@ impl Cmd { } pub fn private_key(&self) -> Result { - Ok(self - .locator - .read_identity(&self.name)? - .key_pair(self.hd_path)?) + Ok(ed25519_dalek::SigningKey::from_bytes( + &self + .locator + .read_identity(&self.name)? + .private_key(self.hd_path)? + .0, + )) } pub fn public_key(&self) -> Result { diff --git a/cmd/soroban-cli/src/commands/keys/show.rs b/cmd/soroban-cli/src/commands/keys/show.rs index 58c47740c..ae0faab03 100644 --- a/cmd/soroban-cli/src/commands/keys/show.rs +++ b/cmd/soroban-cli/src/commands/keys/show.rs @@ -1,6 +1,6 @@ use clap::arg; -use crate::config::{locator, secret}; +use crate::config::{key, locator}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -8,10 +8,7 @@ pub enum Error { Config(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), - - #[error(transparent)] - StrKey(#[from] stellar_strkey::DecodeError), + Key(#[from] key::Error), } #[derive(Debug, clap::Parser, Clone)] diff --git a/cmd/soroban-cli/src/config/address.rs b/cmd/soroban-cli/src/config/address.rs index 066bc8d91..af1918103 100644 --- a/cmd/soroban-cli/src/config/address.rs +++ b/cmd/soroban-cli/src/config/address.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use crate::xdr; -use super::{locator, secret}; +use super::{key, locator, secret}; /// Address can be either a public key or eventually an alias of a address. #[derive(Clone, Debug)] @@ -22,7 +22,7 @@ pub enum Error { #[error(transparent)] Locator(#[from] locator::Error), #[error(transparent)] - Secret(#[from] secret::Error), + Key(#[from] key::Error), #[error("Address cannot be used to sign {0}")] CannotSign(xdr::MuxedAccount), } @@ -46,18 +46,16 @@ impl Address { ) -> Result { match self { Address::MuxedAccount(muxed_account) => Ok(muxed_account.clone()), - Address::AliasOrSecret(alias) => alias.parse().or_else(|_| { - Ok(xdr::MuxedAccount::Ed25519( - locator.read_identity(alias)?.public_key(hd_path)?.0.into(), - )) - }), + Address::AliasOrSecret(alias) => alias + .parse() + .or_else(|_| Ok(locator.read_identity(alias)?.public_key(hd_path)?)), } } pub fn resolve_secret(&self, locator: &locator::Args) -> Result { match &self { + Address::AliasOrSecret(alias) => Ok(locator.get_private_key(alias)?), Address::MuxedAccount(muxed_account) => Err(Error::CannotSign(muxed_account.clone())), - Address::AliasOrSecret(alias) => Ok(locator.read_identity(alias)?), } } } diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs new file mode 100644 index 000000000..e6adface4 --- /dev/null +++ b/cmd/soroban-cli/src/config/key.rs @@ -0,0 +1,146 @@ +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; + +use super::secret::{self, Secret}; +use crate::xdr; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to extract public key from secret")] + SecretPublicKey, + #[error(transparent)] + Secret(#[from] secret::Error), + #[error(transparent)] + StrKey(#[from] stellar_strkey::DecodeError), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum Key { + Secret(Secret), + PublicKey { public_key: PublicKey }, + MuxedAccount { muxed_account: MuxedAccount }, +} + +impl Key { + pub fn public_key(&self, hd_path: Option) -> Result { + let bytes = match self { + Key::Secret(secret) => secret.public_key(hd_path)?.0, + Key::PublicKey { + public_key: PublicKey(key), + } => key.0, + Key::MuxedAccount { + muxed_account: MuxedAccount(stellar_strkey::ed25519::MuxedAccount { ed25519, id }), + } => { + return Ok(xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 { + ed25519: xdr::Uint256(*ed25519), + id: *id, + })) + } + }; + Ok(xdr::MuxedAccount::Ed25519(xdr::Uint256(bytes))) + } + + pub fn private_key( + &self, + hd_path: Option, + ) -> Result { + match self { + Key::Secret(secret) => Ok(secret.private_key(hd_path)?), + _ => Err(Error::SecretPublicKey), + } + } +} + +impl FromStr for Key { + type Err = Error; + + fn from_str(s: &str) -> Result { + if let Ok(secret) = s.parse() { + return Ok(Key::Secret(secret)); + } + if let Ok(public_key) = s.parse() { + return Ok(Key::PublicKey { public_key }); + } + if let Ok(muxed_account) = s.parse() { + return Ok(Key::MuxedAccount { muxed_account }); + } + todo!("Error handling for invalid key format"); + } +} + +impl From for Key { + fn from(value: stellar_strkey::ed25519::PublicKey) -> Self { + Key::PublicKey { + public_key: PublicKey(value), + } + } +} + +impl From<&stellar_strkey::ed25519::PublicKey> for Key { + fn from(stellar_strkey::ed25519::PublicKey(key): &stellar_strkey::ed25519::PublicKey) -> Self { + stellar_strkey::ed25519::PublicKey(*key).into() + } +} + +#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +pub struct PublicKey(pub stellar_strkey::ed25519::PublicKey); + +impl FromStr for PublicKey { + type Err = stellar_strkey::DecodeError; + + fn from_str(s: &str) -> Result { + Ok(PublicKey(stellar_strkey::ed25519::PublicKey::from_str(s)?)) + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From<&PublicKey> for stellar_strkey::ed25519::MuxedAccount { + fn from(PublicKey(stellar_strkey::ed25519::PublicKey(key)): &PublicKey) -> Self { + stellar_strkey::ed25519::MuxedAccount { + id: 0, + ed25519: *key, + } + } +} + +#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] +pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount); + +impl FromStr for MuxedAccount { + type Err = stellar_strkey::DecodeError; + + fn from_str(s: &str) -> Result { + Ok(MuxedAccount( + stellar_strkey::ed25519::MuxedAccount::from_str(s)?, + )) + } +} + +impl Display for MuxedAccount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn public_key() { + let key = Key::PublicKey { + public_key: PublicKey(stellar_strkey::ed25519::PublicKey([0; 32])), + }; + let serialized = toml::to_string(&key).unwrap(); + println!("{serialized}"); + let deserialized: Key = toml::from_str(&serialized).unwrap(); + assert_eq!(key, deserialized); + } +} diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index b6f5c75c1..07c7ee490 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -16,6 +16,7 @@ use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd}; use super::{ alias, + key::Key, network::{self, Network}, secret::Secret, Config, @@ -163,7 +164,19 @@ impl Args { } pub fn write_identity(&self, name: &str, secret: &Secret) -> Result<(), Error> { - KeyType::Identity.write(name, secret, &self.config_dir()?) + self.write_key(name, &Key::Secret(secret.clone())) + } + + pub fn write_public_key( + &self, + name: &str, + public_key: &stellar_strkey::ed25519::PublicKey, + ) -> Result<(), Error> { + self.write_key(name, &public_key.into()) + } + + pub fn write_key(&self, name: &str, public_key: &Key) -> Result<(), Error> { + KeyType::Identity.write(name, public_key, &self.config_dir()?) } pub fn write_network(&self, name: &str, network: &Network) -> Result<(), Error> { @@ -228,17 +241,20 @@ impl Args { Ok(saved_networks.chain(default_networks).collect()) } - pub fn read_identity(&self, name: &str) -> Result { - Ok(KeyType::Identity - .read_with_global(name, &self.local_config()?) - .or_else(|_| name.parse())?) + pub fn read_identity(&self, name: &str) -> Result { + Ok(KeyType::Identity.read_with_global(name, &self.local_config()?)?) } - pub fn key(&self, key_or_name: &str) -> Result { + pub fn get_private_key(&self, key_or_name: &str) -> Result { if let Ok(signer) = key_or_name.parse::() { Ok(signer) } else { - self.read_identity(key_or_name) + match self.read_identity(key_or_name)? { + Key::Secret(s) => Ok(s), + _ => Err(Error::SecretFileRead { + path: self.alias_path(key_or_name)?, + }), + } } } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index a429ff434..fb4c787fd 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -17,6 +17,7 @@ use network::Network; pub mod address; pub mod alias; pub mod data; +pub mod key; pub mod locator; pub mod network; pub mod secret; diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index a7fd86fda..995b88723 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -9,6 +9,8 @@ use crate::{ utils, }; +use super::key::{self, Key}; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error("invalid secret key")] @@ -39,19 +41,26 @@ pub struct Args { /// Add using 12 word seed phrase to generate `secret_key` #[arg(long, conflicts_with = "secret_key")] pub seed_phrase: bool, + + /// Add a public key, ed25519, or muxed account, e.g. G1.., M2.. + #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")] + pub public_key: Option, } impl Args { - pub fn read_secret(&self) -> Result { + pub fn read_key(&self) -> Result { + if let Some(public_key) = self.public_key.as_ref() { + return Ok(public_key.parse()?); + }; if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { - Ok(Secret::SecretKey { secret_key }) + Ok(Key::Secret(Secret::SecretKey { secret_key })) } else if self.secret_key { println!("Type a secret key: "); let secret_key = read_password()?; let secret_key = PrivateKey::from_string(&secret_key) .map_err(|_| Error::InvalidSecretKey)? .to_string(); - Ok(Secret::SecretKey { secret_key }) + Ok(Key::Secret(Secret::SecretKey { secret_key })) } else if self.seed_phrase { println!("Type a 12 word seed phrase: "); let seed_phrase = read_password()?; @@ -60,20 +69,20 @@ impl Args { // let len = seed_phrase.len(); // return Err(Error::InvalidSeedPhrase { len }); // } - Ok(Secret::SeedPhrase { + Ok(Key::Secret(Secret::SeedPhrase { seed_phrase: seed_phrase .into_iter() .map(ToString::to_string) .collect::>() .join(" "), - }) + })) } else { - Err(Error::PasswordRead {}) + Err(Error::PasswordRead {}.into()) } } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum Secret { SecretKey { secret_key: String }, diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 475013bc8..c9d164914 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -64,7 +64,7 @@ impl Args { } } else { let key_or_name = self.sign_with_key.as_deref().ok_or(Error::NoSignWithKey)?; - let secret = locator.key(key_or_name)?; + let secret = locator.get_private_key(key_or_name)?; secret.signer(self.hd_path, print)? }; Ok(signer.sign_tx_env(tx, network)?) From 2aec046e6f0b875f1c3c819b6eab6e74377f5c94 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 12 Nov 2024 15:40:29 -0500 Subject: [PATCH 02/15] fix: lookup first before parsing raw key --- cmd/soroban-cli/src/config/address.rs | 2 +- cmd/soroban-cli/src/config/key.rs | 24 +++++++++-------- cmd/soroban-cli/src/config/locator.rs | 38 ++++++++++++++++++--------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/cmd/soroban-cli/src/config/address.rs b/cmd/soroban-cli/src/config/address.rs index af1918103..ac2adc762 100644 --- a/cmd/soroban-cli/src/config/address.rs +++ b/cmd/soroban-cli/src/config/address.rs @@ -48,7 +48,7 @@ impl Address { Address::MuxedAccount(muxed_account) => Ok(muxed_account.clone()), Address::AliasOrSecret(alias) => alias .parse() - .or_else(|_| Ok(locator.read_identity(alias)?.public_key(hd_path)?)), + .or_else(|_| Ok(locator.get_public_key(alias, hd_path)?)), } } diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index e6adface4..18f52b876 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -13,13 +13,15 @@ pub enum Error { Secret(#[from] secret::Error), #[error(transparent)] StrKey(#[from] stellar_strkey::DecodeError), + #[error("failed to parse key {0}")] + Parse(String), } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum Key { Secret(Secret), - PublicKey { public_key: PublicKey }, + PublicKey { public_key: Public }, MuxedAccount { muxed_account: MuxedAccount }, } @@ -28,7 +30,7 @@ impl Key { let bytes = match self { Key::Secret(secret) => secret.public_key(hd_path)?.0, Key::PublicKey { - public_key: PublicKey(key), + public_key: Public(key), } => key.0, Key::MuxedAccount { muxed_account: MuxedAccount(stellar_strkey::ed25519::MuxedAccount { ed25519, id }), @@ -66,14 +68,14 @@ impl FromStr for Key { if let Ok(muxed_account) = s.parse() { return Ok(Key::MuxedAccount { muxed_account }); } - todo!("Error handling for invalid key format"); + Err(Error::Parse(s.to_owned())) } } impl From for Key { fn from(value: stellar_strkey::ed25519::PublicKey) -> Self { Key::PublicKey { - public_key: PublicKey(value), + public_key: Public(value), } } } @@ -85,24 +87,24 @@ impl From<&stellar_strkey::ed25519::PublicKey> for Key { } #[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)] -pub struct PublicKey(pub stellar_strkey::ed25519::PublicKey); +pub struct Public(pub stellar_strkey::ed25519::PublicKey); -impl FromStr for PublicKey { +impl FromStr for Public { type Err = stellar_strkey::DecodeError; fn from_str(s: &str) -> Result { - Ok(PublicKey(stellar_strkey::ed25519::PublicKey::from_str(s)?)) + Ok(Public(stellar_strkey::ed25519::PublicKey::from_str(s)?)) } } -impl Display for PublicKey { +impl Display for Public { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } -impl From<&PublicKey> for stellar_strkey::ed25519::MuxedAccount { - fn from(PublicKey(stellar_strkey::ed25519::PublicKey(key)): &PublicKey) -> Self { +impl From<&Public> for stellar_strkey::ed25519::MuxedAccount { + fn from(Public(stellar_strkey::ed25519::PublicKey(key)): &Public) -> Self { stellar_strkey::ed25519::MuxedAccount { id: 0, ed25519: *key, @@ -136,7 +138,7 @@ mod test { #[test] fn public_key() { let key = Key::PublicKey { - public_key: PublicKey(stellar_strkey::ed25519::PublicKey([0; 32])), + public_key: Public(stellar_strkey::ed25519::PublicKey([0; 32])), }; let serialized = toml::to_string(&key).unwrap(); println!("{serialized}"); diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 07c7ee490..68a95f628 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -12,11 +12,11 @@ use std::{ }; use stellar_strkey::{Contract, DecodeError}; -use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd}; +use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, xdr, Pwd}; use super::{ alias, - key::Key, + key::{self, Key}, network::{self, Network}, secret::Secret, Config, @@ -84,6 +84,10 @@ pub enum Error { UpgradeCheckReadFailed { path: PathBuf, error: io::Error }, #[error("Failed to write upgrade check file: {path}: {error}")] UpgradeCheckWriteFailed { path: PathBuf, error: io::Error }, + #[error("Only private keys and seed phrases are supported for getting private keys {0}")] + SecretKeyOnly(String), + #[error(transparent)] + Key(#[from] key::Error), } #[derive(Debug, clap::Args, Default, Clone)] @@ -242,22 +246,32 @@ impl Args { } pub fn read_identity(&self, name: &str) -> Result { - Ok(KeyType::Identity.read_with_global(name, &self.local_config()?)?) + KeyType::Identity.read_with_global(name, &self.local_config()?) } - pub fn get_private_key(&self, key_or_name: &str) -> Result { - if let Ok(signer) = key_or_name.parse::() { - Ok(signer) + pub fn read_key(&self, key_or_name: &str) -> Result { + if let Ok(key) = self.read_identity(key_or_name) { + Ok(key) } else { - match self.read_identity(key_or_name)? { - Key::Secret(s) => Ok(s), - _ => Err(Error::SecretFileRead { - path: self.alias_path(key_or_name)?, - }), - } + Ok(key_or_name.parse()?) + } + } + + pub fn get_private_key(&self, key_or_name: &str) -> Result { + match self.read_key(key_or_name)? { + Key::Secret(s) => Ok(s), + _ => Err(Error::SecretKeyOnly(key_or_name.to_string())), } } + pub fn get_public_key( + &self, + key_or_name: &str, + hd_path: Option, + ) -> Result { + Ok(self.read_key(key_or_name)?.public_key(hd_path)?) + } + pub fn read_network(&self, name: &str) -> Result { let res = KeyType::Network.read_with_global(name, &self.local_config()?); if let Err(Error::ConfigMissing(_, _)) = &res { From 52bd5e83d42340064de0c94d6b394f09cca7c357 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 14 Nov 2024 12:31:22 -0500 Subject: [PATCH 03/15] fix: docs --- FULL_HELP_DOCS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 55e093acc..3580f42a6 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -958,6 +958,7 @@ Add a new identity (keypair, ledger, macOS keychain) * `--secret-key` — Add using `secret_key` Can provide with `SOROBAN_SECRET_KEY` * `--seed-phrase` — Add using 12 word seed phrase to generate `secret_key` +* `--public-key ` — Add a public key, ed25519, or muxed account, e.g. G1.., M2.. * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." From 8f96343e737670663d83172f3d30c72dd5ab56d3 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 14 Nov 2024 12:55:00 -0500 Subject: [PATCH 04/15] fix: clippy --- cmd/soroban-cli/src/config/secret.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 995b88723..82008795b 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -50,7 +50,7 @@ pub struct Args { impl Args { pub fn read_key(&self) -> Result { if let Some(public_key) = self.public_key.as_ref() { - return Ok(public_key.parse()?); + return public_key.parse(); }; if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") { Ok(Key::Secret(Secret::SecretKey { secret_key })) From 0cc57fead531487490dd213044d437810703289b Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Tue, 26 Nov 2024 14:38:43 -0500 Subject: [PATCH 05/15] feat: update tx new commands to use Address type Add tests using --- FULL_HELP_DOCS.md | 4 +- .../tests/it/integration/tx/operations.rs | 92 +++++++++++++++++++ cmd/soroban-cli/src/commands/tx/args.rs | 20 +++- cmd/soroban-cli/src/commands/tx/mod.rs | 2 + .../src/commands/tx/new/account_merge.rs | 15 +-- .../src/commands/tx/new/create_account.rs | 17 ++-- cmd/soroban-cli/src/commands/tx/new/mod.rs | 34 +++++-- .../src/commands/tx/new/payment.rs | 13 +-- .../src/commands/tx/new/set_options.rs | 20 ++-- .../commands/tx/new/set_trustline_flags.rs | 25 ++--- 10 files changed, 192 insertions(+), 50 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 3580f42a6..f8f211f37 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1577,7 +1577,7 @@ Transfers the XLM balance of an account to another account and removes the sourc * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' +* `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' or alias @@ -1792,7 +1792,7 @@ Allows issuing account to configure authorization and trustline flags to an asse * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--trustor ` — Account to set trustline flags for +* `--trustor ` — Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` * `--asset ` — Asset to set trustline flags for * `--set-authorize` — Signifies complete authorization allowing an account to transact freely with the asset to make and receive payments and place orders * `--set-authorize-to-maintain-liabilities` — Denotes limited authorization that allows an account to maintain current orders but not to otherwise transact with the asset diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs index 9988b2cdd..9fc843df1 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs @@ -74,6 +74,71 @@ async fn create_account() { invoke_hello_world(sandbox, &id); } +#[tokio::test] +async fn create_account_with_alias() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .args(["generate", "--no-fund", "new"]) + .assert() + .success(); + let test = test_address(sandbox); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let test_account = client.get_account(&test).await.unwrap(); + println!("test account has a balance of {}", test_account.balance); + let starting_balance = ONE_XLM * 100; + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "create-account", + "--destination", + "new", + "--starting-balance", + starting_balance.to_string().as_str(), + ]) + .assert() + .success(); + let test_account_after = client.get_account(&test).await.unwrap(); + assert!(test_account_after.balance < test_account.balance); + let id = deploy_contract(sandbox, HELLO_WORLD, DeployKind::Normal, Some("new")).await; + println!("{id}"); + invoke_hello_world(sandbox, &id); +} + +#[tokio::test] +async fn payment_with_alias() { + let sandbox = &TestEnv::new(); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let (test, test1) = setup_accounts(sandbox); + let test_account = client.get_account(&test).await.unwrap(); + println!("test account has a balance of {}", test_account.balance); + + let before = client.get_account(&test).await.unwrap(); + let test1_account_entry_before = client.get_account(&test1).await.unwrap(); + + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "payment", + "--destination", + "test1", + "--amount", + ONE_XLM.to_string().as_str(), + ]) + .assert() + .success(); + let test1_account_entry = client.get_account(&test1).await.unwrap(); + assert_eq!( + ONE_XLM, + test1_account_entry.balance - test1_account_entry_before.balance, + "Should have One XLM more" + ); + let after = client.get_account(&test).await.unwrap(); + assert_eq!(before.balance - 10_000_100, after.balance); +} + #[tokio::test] async fn payment() { let sandbox = &TestEnv::new(); @@ -157,6 +222,33 @@ async fn account_merge() { assert_eq!(before.balance + before1.balance - fee, after.balance); } +#[tokio::test] +async fn account_merge_with_alias() { + let sandbox = &TestEnv::new(); + let client = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); + let (test, test1) = setup_accounts(sandbox); + let before = client.get_account(&test).await.unwrap(); + let before1 = client.get_account(&test1).await.unwrap(); + let fee = 100; + sandbox + .new_assert_cmd("tx") + .args([ + "new", + "account-merge", + "--source", + "test1", + "--account", + "test", + "--fee", + fee.to_string().as_str(), + ]) + .assert() + .success(); + let after = client.get_account(&test).await.unwrap(); + assert!(client.get_account(&test1).await.is_err()); + assert_eq!(before.balance + before1.balance - fee, after.balance); +} + #[tokio::test] async fn set_trustline_flags() { let sandbox = &TestEnv::new(); diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index 1da1f230a..c703ead6b 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -1,6 +1,10 @@ use crate::{ commands::{global, txn_result::TxnEnvelopeResult}, - config::{self, data, network, secret}, + config::{ + self, + address::{self, Address}, + data, network, secret, + }, fee, rpc::{self, Client, GetTransactionResponse}, tx::builder::{self, TxExt}, @@ -32,6 +36,8 @@ pub enum Error { Data(#[from] data::Error), #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Address(#[from] address::Error), } impl Args { @@ -64,7 +70,7 @@ impl Args { op: impl Into, global_args: &global::Args, ) -> Result, Error> { - let tx = self.tx(op.into()).await?; + let tx = self.tx(op).await?; self.handle_tx(tx, global_args).await } pub async fn handle_and_print( @@ -104,4 +110,14 @@ impl Args { pub fn source_account(&self) -> Result { Ok(self.config.source_account()?) } + + pub fn reslove_muxed_address(&self, address: &Address) -> Result { + Ok(address.resolve_muxed_account(&self.config.locator, self.config.hd_path)?) + } + + pub fn reslove_account_id(&self, address: &Address) -> Result { + Ok(address + .resolve_muxed_account(&self.config.locator, self.config.hd_path)? + .account_id()) + } } diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index c0390f92e..51a10cb0d 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -37,6 +37,8 @@ pub enum Error { Sign(#[from] sign::Error), #[error(transparent)] Send(#[from] send::Error), + #[error(transparent)] + Args(#[from] args::Error), } impl Cmd { diff --git a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs index ce01f5e1f..5ff834aaa 100644 --- a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs +++ b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs @@ -1,19 +1,22 @@ use clap::{command, Parser}; -use crate::{commands::tx, xdr}; +use crate::{commands::tx, config::address, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub tx: tx::Args, - /// Muxed Account to merge with, e.g. `GBX...`, 'MBX...' + /// Muxed Account to merge with, e.g. `GBX...`, 'MBX...' or alias #[arg(long)] - pub account: xdr::MuxedAccount, + pub account: address::Address, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { - xdr::OperationBody::AccountMerge(cmd.account.clone()) +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(xdr::OperationBody::AccountMerge( + cmd.tx.reslove_muxed_address(&cmd.account)?, + )) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/create_account.rs b/cmd/soroban-cli/src/commands/tx/new/create_account.rs index 2826439e9..280fc75bd 100644 --- a/cmd/soroban-cli/src/commands/tx/new/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/new/create_account.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -9,17 +9,18 @@ pub struct Cmd { pub tx: tx::Args, /// Account Id to create, e.g. `GBX...` #[arg(long)] - pub destination: xdr::AccountId, + pub destination: address::Address, /// Initial balance in stroops of the account, default 1 XLM #[arg(long, default_value = "10_000_000")] pub starting_balance: builder::Amount, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { - xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { - destination: cmd.destination.clone(), - starting_balance: cmd.starting_balance.into(), - }) +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { + destination: cmd.tx.reslove_account_id(&cmd.destination)?, + starting_balance: cmd.starting_balance, + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/mod.rs b/cmd/soroban-cli/src/commands/tx/new/mod.rs index e5923f4ec..1d5682cc0 100644 --- a/cmd/soroban-cli/src/commands/tx/new/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/new/mod.rs @@ -1,4 +1,5 @@ use clap::Parser; +use soroban_sdk::xdr::OperationBody; use super::global; @@ -52,17 +53,34 @@ pub enum Error { Tx(#[from] super::args::Error), } +impl TryFrom<&Cmd> for OperationBody { + type Error = super::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(match cmd { + Cmd::AccountMerge(cmd) => cmd.try_into()?, + Cmd::BumpSequence(cmd) => cmd.into(), + Cmd::ChangeTrust(cmd) => cmd.into(), + Cmd::CreateAccount(cmd) => cmd.try_into()?, + Cmd::ManageData(cmd) => cmd.into(), + Cmd::Payment(cmd) => cmd.try_into()?, + Cmd::SetOptions(cmd) => cmd.try_into()?, + Cmd::SetTrustlineFlags(cmd) => cmd.try_into()?, + }) + } +} + impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let op = OperationBody::try_from(self)?; match self { - Cmd::AccountMerge(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::BumpSequence(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::ChangeTrust(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::CreateAccount(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::ManageData(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::Payment(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, - Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(cmd, global_args).await, + Cmd::AccountMerge(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::BumpSequence(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::ChangeTrust(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::CreateAccount(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::ManageData(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::Payment(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(op, global_args).await, + Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(op, global_args).await, }?; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index 3cebfa532..1ab999ab9 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -9,7 +9,7 @@ pub struct Cmd { pub tx: tx::Args, /// Account to send to, e.g. `GBX...` #[arg(long)] - pub destination: xdr::MuxedAccount, + pub destination: address::Address, /// Asset to send, default native, e.i. XLM #[arg(long, default_value = "native")] pub asset: builder::Asset, @@ -18,10 +18,11 @@ pub struct Cmd { pub amount: builder::Amount, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { - xdr::OperationBody::Payment(xdr::PaymentOp { - destination: cmd.destination.clone(), +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(xdr::OperationBody::Payment(xdr::PaymentOp { + destination: cmd.tx.reslove_muxed_address(&cmd.destination)?, asset: cmd.asset.clone().into(), amount: cmd.amount.into(), }) diff --git a/cmd/soroban-cli/src/commands/tx/new/set_options.rs b/cmd/soroban-cli/src/commands/tx/new/set_options.rs index 69cd10745..e5fce4557 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_options.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, xdr}; +use crate::{commands::tx, config::address, xdr}; #[derive(Parser, Debug, Clone)] #[allow(clippy::struct_excessive_bools, clippy::doc_markdown)] @@ -10,7 +10,7 @@ pub struct Cmd { pub tx: tx::Args, #[arg(long)] /// Account of the inflation destination. - pub inflation_dest: Option, + pub inflation_dest: Option, #[arg(long)] /// A number from 0-255 (inclusive) representing the weight of the master key. If the weight of the master key is updated to 0, it is effectively disabled. pub master_weight: Option, @@ -61,8 +61,9 @@ pub struct Cmd { pub clear_clawback_enabled: bool, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { let mut set_flags = None; let mut set_flag = |flag: xdr::AccountFlags| { *set_flags.get_or_insert(0) |= flag as u32; @@ -108,8 +109,13 @@ impl From<&Cmd> for xdr::OperationBody { } else { None }; - xdr::OperationBody::SetOptions(xdr::SetOptionsOp { - inflation_dest: cmd.inflation_dest.clone().map(Into::into), + let inflation_dest: Option = cmd + .inflation_dest + .as_ref() + .map(|dest| cmd.tx.reslove_account_id(dest)) + .transpose()?; + Ok(xdr::OperationBody::SetOptions(xdr::SetOptionsOp { + inflation_dest, clear_flags, set_flags, master_weight: cmd.master_weight.map(Into::into), @@ -118,6 +124,6 @@ impl From<&Cmd> for xdr::OperationBody { high_threshold: cmd.high_threshold.map(Into::into), home_domain: cmd.home_domain.clone().map(Into::into), signer, - }) + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs index d9b70ecbd..318f3ff1d 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, tx::builder, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[allow(clippy::struct_excessive_bools, clippy::doc_markdown)] #[derive(Parser, Debug, Clone)] @@ -8,9 +8,9 @@ use crate::{commands::tx, tx::builder, xdr}; pub struct Cmd { #[command(flatten)] pub tx: tx::Args, - /// Account to set trustline flags for + /// Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` #[arg(long)] - pub trustor: xdr::AccountId, + pub trustor: address::Address, /// Asset to set trustline flags for #[arg(long)] pub asset: builder::Asset, @@ -32,8 +32,9 @@ pub struct Cmd { pub clear_trustline_clawback_enabled: bool, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { let mut set_flags = 0; let mut set_flag = |flag: xdr::TrustLineFlags| set_flags |= flag as u32; @@ -59,11 +60,13 @@ impl From<&Cmd> for xdr::OperationBody { clear_flag(xdr::TrustLineFlags::TrustlineClawbackEnabledFlag); }; - xdr::OperationBody::SetTrustLineFlags(xdr::SetTrustLineFlagsOp { - trustor: cmd.trustor.clone(), - asset: cmd.asset.clone().into(), - clear_flags, - set_flags, - }) + Ok(xdr::OperationBody::SetTrustLineFlags( + xdr::SetTrustLineFlagsOp { + trustor: cmd.tx.reslove_account_id(&cmd.trustor)?, + asset: cmd.asset.clone().into(), + clear_flags, + set_flags, + }, + )) } } From 31cde547fe055d10eec0c445dbe7af8487848268 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 29 Nov 2024 11:50:54 -0500 Subject: [PATCH 06/15] fix: rebase issue --- cmd/soroban-cli/src/commands/tx/new/create_account.rs | 4 ++-- cmd/soroban-cli/src/commands/tx/new/payment.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/new/create_account.rs b/cmd/soroban-cli/src/commands/tx/new/create_account.rs index 280fc75bd..4ae1c13d2 100644 --- a/cmd/soroban-cli/src/commands/tx/new/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/new/create_account.rs @@ -1,6 +1,6 @@ use clap::{command, Parser}; -use crate::{commands::tx, config::address, xdr}; +use crate::{commands::tx, config::address, tx::builder, xdr}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -20,7 +20,7 @@ impl TryFrom<&Cmd> for xdr::OperationBody { fn try_from(cmd: &Cmd) -> Result { Ok(xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { destination: cmd.tx.reslove_account_id(&cmd.destination)?, - starting_balance: cmd.starting_balance, + starting_balance: cmd.starting_balance.into(), })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index 1ab999ab9..397bf68a7 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -25,6 +25,6 @@ impl TryFrom<&Cmd> for xdr::OperationBody { destination: cmd.tx.reslove_muxed_address(&cmd.destination)?, asset: cmd.asset.clone().into(), amount: cmd.amount.into(), - }) + })) } } From a86a98fcc04d91520fcb2ea718ee3c4c25a90aae Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 2 Dec 2024 08:45:30 -0500 Subject: [PATCH 07/15] Update cmd/soroban-cli/src/config/key.rs Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> --- cmd/soroban-cli/src/config/key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 18f52b876..6c5b99a5d 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -7,7 +7,7 @@ use crate::xdr; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("Failed to extract public key from secret")] + #[error("failed to extract public key from secret")] SecretPublicKey, #[error(transparent)] Secret(#[from] secret::Error), From b36db49696d71dde61607be68c732b09d376fc8e Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 2 Dec 2024 09:57:56 -0500 Subject: [PATCH 08/15] fix: PR review --- cmd/soroban-cli/src/config/key.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index 6c5b99a5d..dcb96775b 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -7,14 +7,14 @@ use crate::xdr; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("failed to extract public key from secret")] + #[error("failed to extract secret from public key ")] SecretPublicKey, #[error(transparent)] Secret(#[from] secret::Error), #[error(transparent)] StrKey(#[from] stellar_strkey::DecodeError), - #[error("failed to parse key {0}")] - Parse(String), + #[error("failed to parse key")] + Parse, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -68,7 +68,7 @@ impl FromStr for Key { if let Ok(muxed_account) = s.parse() { return Ok(Key::MuxedAccount { muxed_account }); } - Err(Error::Parse(s.to_owned())) + Err(Error::Parse) } } From 9226b1b85a009f16b2567f214239ce7962cf9cc5 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 5 Dec 2024 09:43:00 -0500 Subject: [PATCH 09/15] fix: fmt --- cmd/soroban-cli/src/commands/tx/new/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/soroban-cli/src/commands/tx/new/mod.rs b/cmd/soroban-cli/src/commands/tx/new/mod.rs index 58d2b4a3d..c00d01220 100644 --- a/cmd/soroban-cli/src/commands/tx/new/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/new/mod.rs @@ -67,7 +67,6 @@ impl Cmd { Cmd::Payment(cmd) => cmd.tx.handle_and_print(op, global_args).await, Cmd::SetOptions(cmd) => cmd.tx.handle_and_print(op, global_args).await, Cmd::SetTrustlineFlags(cmd) => cmd.tx.handle_and_print(op, global_args).await, - }?; Ok(()) } From 3a25564ab6dadaf73f4613e9d75d8162efe5412d Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 5 Dec 2024 11:47:59 -0500 Subject: [PATCH 10/15] fix: update to work with `op add` --- cmd/soroban-cli/src/commands/tx/mod.rs | 2 - .../src/commands/tx/new/account_merge.rs | 2 +- .../src/commands/tx/new/bump_sequence.rs | 6 +-- .../src/commands/tx/new/change_trust.rs | 8 ++-- .../src/commands/tx/new/create_account.rs | 4 +- .../src/commands/tx/new/manage_data.rs | 8 ++-- .../src/commands/tx/new/payment.rs | 6 +-- .../src/commands/tx/new/set_options.rs | 4 +- .../src/commands/tx/op/add/account_merge.rs | 10 +---- .../src/commands/tx/op/add/bump_sequence.rs | 10 +---- .../src/commands/tx/op/add/change_trust.rs | 10 +---- .../src/commands/tx/op/add/create_account.rs | 10 +---- .../src/commands/tx/op/add/manage_data.rs | 10 +---- cmd/soroban-cli/src/commands/tx/op/add/mod.rs | 43 +++++++++++++------ .../src/commands/tx/op/add/payment.rs | 10 +---- .../src/commands/tx/op/add/set_options.rs | 10 +---- .../commands/tx/op/add/set_trustline_flags.rs | 10 +---- 17 files changed, 67 insertions(+), 96 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 7ea08dea1..02be68bf0 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -43,8 +43,6 @@ pub enum Error { #[error(transparent)] Sign(#[from] sign::Error), #[error(transparent)] - Send(#[from] send::Error), - #[error(transparent)] Args(#[from] args::Error), #[error(transparent)] Simulate(#[from] simulate::Error), diff --git a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs index 9b82f0247..e52e1fc42 100644 --- a/cmd/soroban-cli/src/commands/tx/new/account_merge.rs +++ b/cmd/soroban-cli/src/commands/tx/new/account_merge.rs @@ -22,7 +22,7 @@ impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; fn try_from(cmd: &Cmd) -> Result { Ok(xdr::OperationBody::AccountMerge( - cmd.tx.reslove_muxed_address(&cmd.account)?, + cmd.tx.reslove_muxed_address(&cmd.op.account)?, )) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs b/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs index ff04e96a0..96062bba2 100644 --- a/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs +++ b/cmd/soroban-cli/src/commands/tx/new/bump_sequence.rs @@ -18,10 +18,10 @@ pub struct Args { pub bump_to: i64, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { +impl From<&Cmd> for xdr::OperationBody { + fn from(cmd: &Cmd) -> Self { xdr::OperationBody::BumpSequence(xdr::BumpSequenceOp { - bump_to: cmd.bump_to.into(), + bump_to: cmd.op.bump_to.into(), }) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs index 2013db75b..04f17e87e 100644 --- a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs @@ -20,16 +20,16 @@ pub struct Args { pub limit: i64, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - let line = match cmd.line.0.clone() { +impl From<&Cmd> for xdr::OperationBody { + fn from(cmd: &Cmd) -> Self { + let line = match cmd.op.line.0.clone() { xdr::Asset::CreditAlphanum4(asset) => xdr::ChangeTrustAsset::CreditAlphanum4(asset), xdr::Asset::CreditAlphanum12(asset) => xdr::ChangeTrustAsset::CreditAlphanum12(asset), xdr::Asset::Native => xdr::ChangeTrustAsset::Native, }; xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { line, - limit: cmd.limit, + limit: cmd.op.limit, }) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/create_account.rs b/cmd/soroban-cli/src/commands/tx/new/create_account.rs index efbafcc73..d062a07d5 100644 --- a/cmd/soroban-cli/src/commands/tx/new/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/new/create_account.rs @@ -25,8 +25,8 @@ impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; fn try_from(cmd: &Cmd) -> Result { Ok(xdr::OperationBody::CreateAccount(xdr::CreateAccountOp { - destination: cmd.tx.reslove_account_id(&cmd.destination)?, - starting_balance: cmd.starting_balance.into(), + destination: cmd.tx.reslove_account_id(&cmd.op.destination)?, + starting_balance: cmd.op.starting_balance.into(), })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/manage_data.rs b/cmd/soroban-cli/src/commands/tx/new/manage_data.rs index 30e9a36fd..672d8f66e 100644 --- a/cmd/soroban-cli/src/commands/tx/new/manage_data.rs +++ b/cmd/soroban-cli/src/commands/tx/new/manage_data.rs @@ -25,10 +25,10 @@ pub struct Args { pub data_value: Option>, } -impl From<&Args> for xdr::OperationBody { - fn from(cmd: &Args) -> Self { - let data_value = cmd.data_value.clone().map(Into::into); - let data_name = cmd.data_name.clone().into(); +impl From<&Cmd> for xdr::OperationBody { + fn from(cmd: &Cmd) -> Self { + let data_value = cmd.op.data_value.clone().map(Into::into); + let data_name = cmd.op.data_name.clone().into(); xdr::OperationBody::ManageData(xdr::ManageDataOp { data_name, data_value, diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index 03d137cdc..4b472e141 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -28,9 +28,9 @@ impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; fn try_from(cmd: &Cmd) -> Result { Ok(xdr::OperationBody::Payment(xdr::PaymentOp { - destination: cmd.tx.reslove_muxed_address(&cmd.destination)?, - asset: cmd.asset.clone().into(), - amount: cmd.amount.into(), + destination: cmd.tx.reslove_muxed_address(&cmd.op.destination)?, + asset: cmd.op.asset.clone().into(), + amount: cmd.op.amount.into(), })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_options.rs b/cmd/soroban-cli/src/commands/tx/new/set_options.rs index 272a1689b..42a0767b5 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_options.rs @@ -70,10 +70,12 @@ pub struct Args { impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; fn try_from(cmd: &Cmd) -> Result { + let tx = &cmd.tx; let mut set_flags = None; let mut set_flag = |flag: xdr::AccountFlags| { *set_flags.get_or_insert(0) |= flag as u32; }; + let cmd = &cmd.op; if cmd.set_required { set_flag(xdr::AccountFlags::RequiredFlag); @@ -118,7 +120,7 @@ impl TryFrom<&Cmd> for xdr::OperationBody { let inflation_dest: Option = cmd .inflation_dest .as_ref() - .map(|dest| cmd.tx.reslove_account_id(dest)) + .map(|dest| tx.reslove_account_id(dest)) .transpose()?; Ok(xdr::OperationBody::SetOptions(xdr::SetOptionsOp { inflation_dest, diff --git a/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs b/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs index bd643c199..cf274fd67 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/account_merge.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::account_merge::Args, + pub op: super::new::account_merge::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs b/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs index 907d8d2d6..640b748b3 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/bump_sequence.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::bump_sequence::Args, + pub op: super::new::bump_sequence::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs b/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs index af9afae1b..5647e4cec 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/change_trust.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::change_trust::Args, + pub op: super::new::change_trust::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs b/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs index e30ff20a1..1ca978e0f 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/create_account.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::create_account::Args, + pub op: super::new::create_account::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs b/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs index 962233a84..5758478c3 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/manage_data.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::manage_data::Args, + pub op: super::new::manage_data::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs index b94fc74ce..d8860c1fc 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs @@ -1,7 +1,5 @@ -use clap::Parser; - use super::super::{global, help, xdr::tx_envelope_from_stdin}; -use crate::xdr::WriteXdr; +use crate::xdr::{OperationBody, WriteXdr}; pub(crate) use super::super::{new, xdr}; @@ -15,7 +13,7 @@ mod payment; mod set_options; mod set_trustline_flags; -#[derive(Debug, Parser)] +#[derive(Debug, clap::Parser)] #[allow(clippy::doc_markdown)] pub enum Cmd { #[command(about = help::ACCOUNT_MERGE)] @@ -44,20 +42,41 @@ pub enum Error { TxXdr(#[from] super::super::xdr::Error), #[error(transparent)] Xdr(#[from] crate::xdr::Error), + #[error(transparent)] + New(#[from] super::super::new::Error), + #[error(transparent)] + Tx(#[from] super::super::args::Error), +} + +impl TryFrom<&Cmd> for OperationBody { + type Error = super::super::new::Error; + fn try_from(cmd: &Cmd) -> Result { + Ok(match &cmd { + Cmd::AccountMerge(account_merge::Cmd { op, .. }) => op.try_into()?, + Cmd::BumpSequence(bump_sequence::Cmd { op, .. }) => op.into(), + Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.into(), + Cmd::CreateAccount(create_account::Cmd { op, .. }) => op.try_into()?, + Cmd::ManageData(manage_data::Cmd { op, .. }) => op.into(), + Cmd::Payment(payment::Cmd { op, .. }) => op.try_into()?, + Cmd::SetOptions(set_options::Cmd { op, .. }) => op.try_into()?, + Cmd::SetTrustlineFlags(set_trustline_flags::Cmd { op, .. }) => op.try_into()?, + }) + } } impl Cmd { pub fn run(&self, _: &global::Args) -> Result<(), Error> { let tx_env = tx_envelope_from_stdin()?; + let op = OperationBody::try_from(self)?; let res = match self { - Cmd::AccountMerge(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::BumpSequence(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::ChangeTrust(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::CreateAccount(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::ManageData(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::Payment(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::SetOptions(cmd) => cmd.args.add_op(&cmd.op, tx_env), - Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(&cmd.op, tx_env), + Cmd::AccountMerge(cmd) => cmd.args.add_op(op, tx_env), + Cmd::BumpSequence(cmd) => cmd.args.add_op(op, tx_env), + Cmd::ChangeTrust(cmd) => cmd.args.add_op(op, tx_env), + Cmd::CreateAccount(cmd) => cmd.args.add_op(op, tx_env), + Cmd::ManageData(cmd) => cmd.args.add_op(op, tx_env), + Cmd::Payment(cmd) => cmd.args.add_op(op, tx_env), + Cmd::SetOptions(cmd) => cmd.args.add_op(op, tx_env), + Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(op, tx_env), }?; println!("{}", res.to_xdr_base64(crate::xdr::Limits::none())?); Ok(()) diff --git a/cmd/soroban-cli/src/commands/tx/op/add/payment.rs b/cmd/soroban-cli/src/commands/tx/op/add/payment.rs index d8146c91a..f5f9c5fcc 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/payment.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::payment::Args, + pub op: super::new::payment::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs b/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs index 75b43124a..88323f57c 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/set_options.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::set_options::Args, + pub op: super::new::set_options::Cmd, } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs index 8ffee7a7b..09ee11607 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/set_trustline_flags.rs @@ -1,14 +1,8 @@ -use clap::{command, Parser}; - -use std::fmt::Debug; - -use super::new; - -#[derive(Parser, Debug, Clone)] +#[derive(clap::Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { #[command(flatten)] pub args: super::args::Args, #[command(flatten)] - pub op: new::set_trustline_flags::Args, + pub op: super::new::set_trustline_flags::Cmd, } From 5f55e7c642a8ae28294c06ed14ba4aa548e55796 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 5 Dec 2024 12:25:34 -0500 Subject: [PATCH 11/15] fix: simplify op::add::args::Args Should fix issue with multiple locators --- cmd/soroban-cli/src/commands/tx/args.rs | 20 +++++++++++++ cmd/soroban-cli/src/commands/tx/mod.rs | 2 -- .../src/commands/tx/op/add/args.rs | 30 +++---------------- cmd/soroban-cli/src/commands/tx/op/add/mod.rs | 20 ++++++------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index c703ead6b..23702f4f2 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -11,6 +11,7 @@ use crate::{ xdr::{self, Limits, WriteXdr}, }; + #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { @@ -38,6 +39,8 @@ pub enum Error { Xdr(#[from] xdr::Error), #[error(transparent)] Address(#[from] address::Error), + #[error(transparent)] + TxXdr(#[from] super::xdr::Error), } impl Args { @@ -120,4 +123,21 @@ impl Args { .resolve_muxed_account(&self.config.locator, self.config.hd_path)? .account_id()) } + + + pub fn add_op( + &self, + op_body: impl Into, + tx_env: xdr::TransactionEnvelope, + op_source: Option<&address::Address>, + ) -> Result { + let source_account = op_source + .map(|a| self.reslove_muxed_address(a)) + .transpose()?; + let op = xdr::Operation { + source_account, + body: op_body.into(), + }; + Ok(super::xdr::add_op(tx_env, op)?) + } } diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index 02be68bf0..d9fd79faf 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -43,8 +43,6 @@ pub enum Error { #[error(transparent)] Sign(#[from] sign::Error), #[error(transparent)] - Args(#[from] args::Error), - #[error(transparent)] Simulate(#[from] simulate::Error), } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/args.rs b/cmd/soroban-cli/src/commands/tx/op/add/args.rs index f1858e0b0..9d9b4931b 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/args.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/args.rs @@ -1,22 +1,8 @@ -use super::xdr::add_op; -use crate::{ - config::{address, locator}, - xdr, -}; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Address(#[from] address::Error), - #[error(transparent)] - TxXdr(#[from] super::xdr::Error), -} +use crate::{commands::tx, config::address, xdr}; #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { - #[clap(flatten)] - pub locator: locator::Args, /// Source account used for the operation #[arg( long, @@ -31,16 +17,8 @@ impl Args { &self, op_body: impl Into, tx_env: xdr::TransactionEnvelope, - ) -> Result { - let source_account = self - .operation_source_account - .as_ref() - .map(|a| a.resolve_muxed_account(&self.locator, None)) - .transpose()?; - let op = xdr::Operation { - source_account, - body: op_body.into(), - }; - Ok(add_op(tx_env, op)?) + tx: &tx::args::Args, + ) -> Result { + tx.add_op(op_body, tx_env, self.operation_source_account.as_ref()) } } diff --git a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs index d8860c1fc..5e69cf168 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs @@ -1,7 +1,7 @@ use super::super::{global, help, xdr::tx_envelope_from_stdin}; use crate::xdr::{OperationBody, WriteXdr}; -pub(crate) use super::super::{new, xdr}; +pub(crate) use super::super::new; mod account_merge; mod args; @@ -36,8 +36,6 @@ pub enum Cmd { #[derive(thiserror::Error, Debug)] pub enum Error { - #[error(transparent)] - Args(#[from] args::Error), #[error(transparent)] TxXdr(#[from] super::super::xdr::Error), #[error(transparent)] @@ -69,14 +67,14 @@ impl Cmd { let tx_env = tx_envelope_from_stdin()?; let op = OperationBody::try_from(self)?; let res = match self { - Cmd::AccountMerge(cmd) => cmd.args.add_op(op, tx_env), - Cmd::BumpSequence(cmd) => cmd.args.add_op(op, tx_env), - Cmd::ChangeTrust(cmd) => cmd.args.add_op(op, tx_env), - Cmd::CreateAccount(cmd) => cmd.args.add_op(op, tx_env), - Cmd::ManageData(cmd) => cmd.args.add_op(op, tx_env), - Cmd::Payment(cmd) => cmd.args.add_op(op, tx_env), - Cmd::SetOptions(cmd) => cmd.args.add_op(op, tx_env), - Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(op, tx_env), + Cmd::AccountMerge(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::BumpSequence(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::ChangeTrust(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::CreateAccount(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::ManageData(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::Payment(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::SetOptions(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), + Cmd::SetTrustlineFlags(cmd) => cmd.args.add_op(op, tx_env, &cmd.op.tx), }?; println!("{}", res.to_xdr_base64(crate::xdr::Limits::none())?); Ok(()) From 0e7f08f12a675a6806d3ee8a56c5f8bbb024d303 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 5 Dec 2024 12:53:44 -0500 Subject: [PATCH 12/15] fix: remove untagged and use rename to keep same serialized output --- cmd/soroban-cli/src/commands/tx/args.rs | 2 - cmd/soroban-cli/src/commands/tx/mod.rs | 2 + cmd/soroban-cli/src/config/key.rs | 57 ++++++++++++++++--------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index 23702f4f2..c00da6fea 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -11,7 +11,6 @@ use crate::{ xdr::{self, Limits, WriteXdr}, }; - #[derive(Debug, clap::Args, Clone)] #[group(skip)] pub struct Args { @@ -124,7 +123,6 @@ impl Args { .account_id()) } - pub fn add_op( &self, op_body: impl Into, diff --git a/cmd/soroban-cli/src/commands/tx/mod.rs b/cmd/soroban-cli/src/commands/tx/mod.rs index d9fd79faf..02be68bf0 100644 --- a/cmd/soroban-cli/src/commands/tx/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/mod.rs @@ -43,6 +43,8 @@ pub enum Error { #[error(transparent)] Sign(#[from] sign::Error), #[error(transparent)] + Args(#[from] args::Error), + #[error(transparent)] Simulate(#[from] simulate::Error), } diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index dcb96775b..dab9d8ac9 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -18,23 +18,24 @@ pub enum Error { } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(untagged)] pub enum Key { + #[serde(rename = "public_key")] + PublicKey(Public), + #[serde(rename = "muxed_account")] + MuxedAccount(MuxedAccount), + #[serde(untagged)] Secret(Secret), - PublicKey { public_key: Public }, - MuxedAccount { muxed_account: MuxedAccount }, } impl Key { pub fn public_key(&self, hd_path: Option) -> Result { let bytes = match self { Key::Secret(secret) => secret.public_key(hd_path)?.0, - Key::PublicKey { - public_key: Public(key), - } => key.0, - Key::MuxedAccount { - muxed_account: MuxedAccount(stellar_strkey::ed25519::MuxedAccount { ed25519, id }), - } => { + Key::PublicKey(Public(key)) => key.0, + Key::MuxedAccount(MuxedAccount(stellar_strkey::ed25519::MuxedAccount { + ed25519, + id, + })) => { return Ok(xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 { ed25519: xdr::Uint256(*ed25519), id: *id, @@ -63,10 +64,10 @@ impl FromStr for Key { return Ok(Key::Secret(secret)); } if let Ok(public_key) = s.parse() { - return Ok(Key::PublicKey { public_key }); + return Ok(Key::PublicKey(public_key)); } if let Ok(muxed_account) = s.parse() { - return Ok(Key::MuxedAccount { muxed_account }); + return Ok(Key::MuxedAccount(muxed_account)); } Err(Error::Parse) } @@ -74,9 +75,7 @@ impl FromStr for Key { impl From for Key { fn from(value: stellar_strkey::ed25519::PublicKey) -> Self { - Key::PublicKey { - public_key: Public(value), - } + Key::PublicKey(Public(value)) } } @@ -135,14 +134,34 @@ impl Display for MuxedAccount { mod test { use super::*; - #[test] - fn public_key() { - let key = Key::PublicKey { - public_key: Public(stellar_strkey::ed25519::PublicKey([0; 32])), - }; + fn round_trip(key: Key) { let serialized = toml::to_string(&key).unwrap(); println!("{serialized}"); let deserialized: Key = toml::from_str(&serialized).unwrap(); assert_eq!(key, deserialized); } + + #[test] + fn public_key() { + let key = Key::PublicKey(Public(stellar_strkey::ed25519::PublicKey([0; 32]))); + round_trip(key); + } + #[test] + fn muxed_key() { + let key: stellar_strkey::ed25519::MuxedAccount = + "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU" + .parse() + .unwrap(); + let key = Key::MuxedAccount(MuxedAccount(key)); + round_trip(key); + } + #[test] + fn secret_key() { + let secret_key = stellar_strkey::ed25519::PrivateKey([0; 32]); + let secret = Secret::SecretKey { + secret_key: secret_key.to_string(), + }; + let key = Key::Secret(secret); + round_trip(key); + } } From aa8bb16d7824ee5d8ee8b971fcceadd08e7f28ea Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Thu, 5 Dec 2024 13:14:11 -0500 Subject: [PATCH 13/15] fix: docs and clippy in tests --- FULL_HELP_DOCS.md | 140 ++++++++++++++++++++++++++---- cmd/soroban-cli/src/config/key.rs | 23 +++-- 2 files changed, 136 insertions(+), 27 deletions(-) diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 975845c0b..e453b53d8 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -1536,7 +1536,7 @@ Transfers the XLM balance of an account to another account and removes the sourc * `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' or alias +* `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' @@ -1825,13 +1825,26 @@ https://developers.stellar.org/docs/learn/glossary#flags Transfers the XLM balance of an account to another account and removes the source account from the ledger -**Usage:** `stellar tx operation add account-merge [OPTIONS] --account ` +**Usage:** `stellar tx operation add account-merge [OPTIONS] --source-account --account ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--account ` — Muxed Account to merge with, e.g. `GBX...`, 'MBX...' @@ -1840,13 +1853,26 @@ Transfers the XLM balance of an account to another account and removes the sourc Bumps forward the sequence number of the source account to the given sequence number, invalidating any transaction with a smaller sequence number -**Usage:** `stellar tx operation add bump-sequence [OPTIONS] --bump-to ` +**Usage:** `stellar tx operation add bump-sequence [OPTIONS] --source-account --bump-to ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--bump-to ` — Sequence number to bump to @@ -1857,13 +1883,26 @@ Creates, updates, or deletes a trustline Learn more about trustlines https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#trustlines -**Usage:** `stellar tx operation add change-trust [OPTIONS] --line ` +**Usage:** `stellar tx operation add change-trust [OPTIONS] --source-account --line ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--line ` * `--limit ` — Limit for the trust line, 0 to remove the trust line @@ -1875,13 +1914,26 @@ https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/a Creates and funds a new account with the specified starting balance -**Usage:** `stellar tx operation add create-account [OPTIONS] --destination ` +**Usage:** `stellar tx operation add create-account [OPTIONS] --source-account --destination ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--destination ` — Account Id to create, e.g. `GBX...` * `--starting-balance ` — Initial balance in stroops of the account, default 1 XLM @@ -1895,13 +1947,26 @@ Sets, modifies, or deletes a data entry (name/value pair) that is attached to an Learn more about entries and subentries: https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#subentries -**Usage:** `stellar tx operation add manage-data [OPTIONS] --data-name ` +**Usage:** `stellar tx operation add manage-data [OPTIONS] --source-account --data-name ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--data-name ` — String up to 64 bytes long. If this is a new Name it will add the given name/value pair to the account. If this Name is already present then the associated value will be modified * `--data-value ` — Up to 64 bytes long hex string If not present then the existing Name will be deleted. If present then this value will be set in the `DataEntry` @@ -1911,13 +1976,26 @@ https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/a Sends an amount in a specific asset to a destination account -**Usage:** `stellar tx operation add payment [OPTIONS] --destination --amount ` +**Usage:** `stellar tx operation add payment [OPTIONS] --source-account --destination --amount ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--destination ` — Account to send to, e.g. `GBX...` * `--asset ` — Asset to send, default native, e.i. XLM @@ -1936,13 +2014,26 @@ https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md Learn more about signers operations and key weight: https://developers.stellar.org/docs/learn/encyclopedia/security/signatures-multisig#multisig -**Usage:** `stellar tx operation add set-options [OPTIONS]` +**Usage:** `stellar tx operation add set-options [OPTIONS] --source-account ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation * `--inflation-dest ` — Account of the inflation destination * `--master-weight ` — A number from 0-255 (inclusive) representing the weight of the master key. If the weight of the master key is updated to 0, it is effectively disabled * `--low-threshold ` — A number from 0-255 (inclusive) representing the threshold this account sets on all operations it performs that have a low threshold. https://developers.stellar.org/docs/learn/encyclopedia/security/signatures-multisig#multisig @@ -1970,14 +2061,27 @@ If you are modifying a trustline to a pool share, however, this is composed of t Learn more about flags: https://developers.stellar.org/docs/learn/glossary#flags -**Usage:** `stellar tx operation add set-trustline-flags [OPTIONS] --trustor --asset ` +**Usage:** `stellar tx operation add set-trustline-flags [OPTIONS] --source-account --trustor --asset ` ###### **Options:** +* `--operation-source-account ` — Source account used for the operation +* `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm + + Default value: `100` +* `--cost` — Output the cost execution to stderr +* `--instructions ` — Number of instructions to simulate +* `--build-only` — Build the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout +* `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail +* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." -* `--operation-source-account ` — Source account used for the operation -* `--trustor ` — Account to set trustline flags for +* `--trustor ` — Account to set trustline flags for, e.g. `GBX...`, or alias, or muxed account, `M123...`` * `--asset ` — Asset to set trustline flags for * `--set-authorize` — Signifies complete authorization allowing an account to transact freely with the asset to make and receive payments and place orders * `--set-authorize-to-maintain-liabilities` — Denotes limited authorization that allows an account to maintain current orders but not to otherwise transact with the asset diff --git a/cmd/soroban-cli/src/config/key.rs b/cmd/soroban-cli/src/config/key.rs index dab9d8ac9..cecb83522 100644 --- a/cmd/soroban-cli/src/config/key.rs +++ b/cmd/soroban-cli/src/config/key.rs @@ -134,17 +134,17 @@ impl Display for MuxedAccount { mod test { use super::*; - fn round_trip(key: Key) { + fn round_trip(key: &Key) { let serialized = toml::to_string(&key).unwrap(); println!("{serialized}"); let deserialized: Key = toml::from_str(&serialized).unwrap(); - assert_eq!(key, deserialized); + assert_eq!(key, &deserialized); } #[test] fn public_key() { let key = Key::PublicKey(Public(stellar_strkey::ed25519::PublicKey([0; 32]))); - round_trip(key); + round_trip(&key); } #[test] fn muxed_key() { @@ -153,15 +153,20 @@ mod test { .parse() .unwrap(); let key = Key::MuxedAccount(MuxedAccount(key)); - round_trip(key); + round_trip(&key); } #[test] fn secret_key() { - let secret_key = stellar_strkey::ed25519::PrivateKey([0; 32]); - let secret = Secret::SecretKey { - secret_key: secret_key.to_string(), - }; + let secret_key = stellar_strkey::ed25519::PrivateKey([0; 32]).to_string(); + let secret = Secret::SecretKey { secret_key }; + let key = Key::Secret(secret); + round_trip(&key); + } + #[test] + fn secret_seed_phrase() { + let seed_phrase = "singer swing mango apple singer swing mango apple singer swing mango apple singer swing mango apple".to_string(); + let secret = Secret::SeedPhrase { seed_phrase }; let key = Key::Secret(secret); - round_trip(key); + round_trip(&key); } } From 54ad073a5ca35e78a11ad787940db126d0402488 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 29 Nov 2024 12:31:23 -0500 Subject: [PATCH 14/15] feat: improve asset parsing to use named keys as issuer Previously the issuer had to be a fully resolved `xdr::AccountId`, now it can be an `Address`, which then can be resolved. Thus allowing the following: `USDC:circle` --- .../src/commands/contract/deploy/asset.rs | 6 ++- .../src/commands/contract/id/asset.rs | 7 ++- .../src/commands/snapshot/create.rs | 4 +- cmd/soroban-cli/src/commands/tx/args.rs | 8 ++- .../src/commands/tx/new/change_trust.rs | 14 +++--- cmd/soroban-cli/src/commands/tx/new/mod.rs | 2 +- .../src/commands/tx/new/payment.rs | 15 ++++-- .../commands/tx/new/set_trustline_flags.rs | 4 +- cmd/soroban-cli/src/tx/builder/asset.rs | 50 +++++++++++-------- cmd/soroban-cli/src/utils.rs | 4 +- 10 files changed, 72 insertions(+), 42 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 04a0380ed..9adf1e1be 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -39,6 +39,8 @@ pub enum Error { Network(#[from] network::Error), #[error(transparent)] Builder(#[from] builder::Error), + #[error(transparent)] + Asset(#[from] builder::asset::Error), } impl From for Error { @@ -85,7 +87,7 @@ impl NetworkRunnable for Cmd { ) -> Result { let config = config.unwrap_or(&self.config); // Parse asset - let asset = &self.asset; + let asset = self.asset.resolve(&config.locator)?; let network = config.get_network()?; let client = network.rpc_client()?; @@ -100,7 +102,7 @@ impl NetworkRunnable for Cmd { .await?; let sequence: i64 = account_details.seq_num.into(); let network_passphrase = &network.network_passphrase; - let contract_id = contract_id_hash_from_asset(asset, network_passphrase); + let contract_id = contract_id_hash_from_asset(&asset, network_passphrase); let tx = build_wrap_token_tx( asset, &contract_id, diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs index 921592cf8..ddc03a715 100644 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -21,6 +21,8 @@ pub enum Error { ConfigError(#[from] config::Error), #[error(transparent)] Xdr(#[from] crate::xdr::Error), + #[error(transparent)] + Asset(#[from] builder::asset::Error), } impl Cmd { pub fn run(&self) -> Result<(), Error> { @@ -30,7 +32,10 @@ impl Cmd { pub fn contract_address(&self) -> Result { let network = self.config.get_network()?; - let contract_id = contract_id_hash_from_asset(&self.asset, &network.network_passphrase); + let contract_id = contract_id_hash_from_asset( + &self.asset.resolve(&self.config.locator)?, + &network.network_passphrase, + ); Ok(stellar_strkey::Contract(contract_id.0)) } } diff --git a/cmd/soroban-cli/src/commands/snapshot/create.rs b/cmd/soroban-cli/src/commands/snapshot/create.rs index 6ce48d3f2..9c1736914 100644 --- a/cmd/soroban-cli/src/commands/snapshot/create.rs +++ b/cmd/soroban-cli/src/commands/snapshot/create.rs @@ -322,7 +322,9 @@ impl Cmd { get_name_from_stellar_asset_contract_storage(storage) { let asset: builder::Asset = name.parse()?; - if let Some(issuer) = match asset.into() { + if let Some(issuer) = match asset + .resolve(&global_args.locator)? + { Asset::Native => None, Asset::CreditAlphanum4(a4) => Some(a4.issuer), Asset::CreditAlphanum12(a12) => Some(a12.issuer), diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index c00da6fea..c312f36f4 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -7,7 +7,7 @@ use crate::{ }, fee, rpc::{self, Client, GetTransactionResponse}, - tx::builder::{self, TxExt}, + tx::builder::{self, asset, TxExt}, xdr::{self, Limits, WriteXdr}, }; @@ -39,6 +39,8 @@ pub enum Error { #[error(transparent)] Address(#[from] address::Error), #[error(transparent)] + Asset(#[from] asset::Error), + #[error(transparent)] TxXdr(#[from] super::xdr::Error), } @@ -138,4 +140,8 @@ impl Args { }; Ok(super::xdr::add_op(tx_env, op)?) } + + pub fn resolve_asset(&self, asset: &builder::Asset) -> Result { + Ok(asset.resolve(&self.config.locator)?) + } } diff --git a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs index 04f17e87e..d05a192d4 100644 --- a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs @@ -20,16 +20,18 @@ pub struct Args { pub limit: i64, } -impl From<&Cmd> for xdr::OperationBody { - fn from(cmd: &Cmd) -> Self { - let line = match cmd.op.line.0.clone() { +impl TryFrom<&Cmd> for xdr::OperationBody { + type Error = tx::args::Error; + fn try_from(cmd: &Cmd) -> Result { + let asset = cmd.tx.resolve_asset(&cmd.line)?; + let line = match asset { xdr::Asset::CreditAlphanum4(asset) => xdr::ChangeTrustAsset::CreditAlphanum4(asset), xdr::Asset::CreditAlphanum12(asset) => xdr::ChangeTrustAsset::CreditAlphanum12(asset), xdr::Asset::Native => xdr::ChangeTrustAsset::Native, }; - xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { + Ok(xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { line, - limit: cmd.op.limit, - }) + limit: cmd.limit, + })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/mod.rs b/cmd/soroban-cli/src/commands/tx/new/mod.rs index c00d01220..761773418 100644 --- a/cmd/soroban-cli/src/commands/tx/new/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/new/mod.rs @@ -45,7 +45,7 @@ impl TryFrom<&Cmd> for OperationBody { Ok(match cmd { Cmd::AccountMerge(cmd) => cmd.try_into()?, Cmd::BumpSequence(cmd) => cmd.into(), - Cmd::ChangeTrust(cmd) => cmd.into(), + Cmd::ChangeTrust(cmd) => cmd.try_into()?, Cmd::CreateAccount(cmd) => cmd.try_into()?, Cmd::ManageData(cmd) => cmd.into(), Cmd::Payment(cmd) => cmd.try_into()?, diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index 4b472e141..d7054ed46 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -26,11 +26,18 @@ pub struct Args { impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; - fn try_from(cmd: &Cmd) -> Result { + fn try_from( + Cmd { + tx, + destination, + asset, + amount, + }: &Cmd, + ) -> Result { Ok(xdr::OperationBody::Payment(xdr::PaymentOp { - destination: cmd.tx.reslove_muxed_address(&cmd.op.destination)?, - asset: cmd.op.asset.clone().into(), - amount: cmd.op.amount.into(), + destination: tx.reslove_muxed_address(destination)?, + asset: tx.resolve_asset(asset)?, + amount: amount.into(), })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs index 3e6465115..4406db958 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs @@ -68,8 +68,8 @@ impl TryFrom<&Cmd> for xdr::OperationBody { Ok(xdr::OperationBody::SetTrustLineFlags( xdr::SetTrustLineFlagsOp { - trustor: cmd.tx.reslove_account_id(&cmd.op.trustor)?, - asset: cmd.op.asset.clone().into(), + trustor: cmd.tx.reslove_account_id(&cmd.trustor)?, + asset: cmd.tx.resolve_asset(&cmd.asset)?, clear_flags, set_flags, }, diff --git a/cmd/soroban-cli/src/tx/builder/asset.rs b/cmd/soroban-cli/src/tx/builder/asset.rs index bba39804e..c67e4b42e 100644 --- a/cmd/soroban-cli/src/tx/builder/asset.rs +++ b/cmd/soroban-cli/src/tx/builder/asset.rs @@ -1,17 +1,24 @@ use std::str::FromStr; -use crate::xdr::{self, AlphaNum12, AlphaNum4, AssetCode}; +use crate::{ + config::{address, locator}, + xdr::{self, AlphaNum12, AlphaNum4, AssetCode}, +}; #[derive(Clone, Debug)] -pub struct Asset(pub xdr::Asset); +pub enum Asset { + Asset(AssetCode, address::Address), + Native, +} #[derive(thiserror::Error, Debug)] pub enum Error { #[error("cannot parse asset: {0}, expected format: 'native' or 'code:issuer'")] CannotParseAsset(String), - #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Address(#[from] address::Error), } impl FromStr for Asset { @@ -19,32 +26,31 @@ impl FromStr for Asset { fn from_str(value: &str) -> Result { if value == "native" { - return Ok(Asset(xdr::Asset::Native)); + return Ok(Asset::Native); } let mut iter = value.splitn(2, ':'); let (Some(code), Some(issuer), None) = (iter.next(), iter.next(), iter.next()) else { return Err(Error::CannotParseAsset(value.to_string())); }; - let issuer = issuer.parse()?; - Ok(Asset(match code.parse()? { - AssetCode::CreditAlphanum4(asset_code) => { - xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer }) - } - AssetCode::CreditAlphanum12(asset_code) => { - xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer }) - } - })) + Ok(Asset::Asset(code.parse()?, issuer.parse()?)) } } -impl From for xdr::Asset { - fn from(builder: Asset) -> Self { - builder.0 - } -} - -impl From<&Asset> for xdr::Asset { - fn from(builder: &Asset) -> Self { - builder.clone().into() +impl Asset { + pub fn resolve(&self, locator: &locator::Args) -> Result { + Ok(match self { + Asset::Asset(code, issuer) => { + let issuer = issuer.resolve_muxed_account(locator, None)?.account_id(); + match code.clone() { + AssetCode::CreditAlphanum4(asset_code) => { + xdr::Asset::CreditAlphanum4(AlphaNum4 { asset_code, issuer }) + } + AssetCode::CreditAlphanum12(asset_code) => { + xdr::Asset::CreditAlphanum12(AlphaNum12 { asset_code, issuer }) + } + } + } + Asset::Native => xdr::Asset::Native, + }) } } diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 0c4207a4e..3efe619ef 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -125,13 +125,13 @@ pub fn is_hex_string(s: &str) -> bool { } pub fn contract_id_hash_from_asset( - asset: impl Into, + asset: &Asset, network_passphrase: &str, ) -> stellar_strkey::Contract { let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { network_id, - contract_id_preimage: ContractIdPreimage::Asset(asset.into()), + contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), }); let preimage_xdr = preimage .to_xdr(Limits::none()) From 69fc190e2393894d82a2e062707f59f0ca6456e9 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 29 Nov 2024 14:52:19 -0500 Subject: [PATCH 15/15] fix: add test and fix others --- .../tests/it/integration/tx/operations.rs | 21 +++++++++++++------ .../soroban-test/tests/it/integration/wrap.rs | 14 +++++++------ .../src/commands/tx/new/change_trust.rs | 4 ++-- .../src/commands/tx/new/payment.rs | 9 +++++--- .../commands/tx/new/set_trustline_flags.rs | 4 ++-- cmd/soroban-cli/src/commands/tx/op/add/mod.rs | 2 +- 6 files changed, 34 insertions(+), 20 deletions(-) diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs index a733bf771..9135ae5d2 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs @@ -1,4 +1,5 @@ use soroban_cli::{ + config::locator, tx::{builder, ONE_XLM}, utils::contract_id_hash_from_asset, xdr::{self, ReadXdr, SequenceNumber}, @@ -267,19 +268,23 @@ async fn account_merge_with_alias() { #[tokio::test] async fn set_trustline_flags() { let sandbox = &TestEnv::new(); - let (test, issuer) = setup_accounts(sandbox); - let asset = format!("usdc:{issuer}"); - issue_asset(sandbox, &test, &asset, 100_000, 100).await; + let (test, test1) = setup_accounts(sandbox); + let asset = "usdc:test1"; + issue_asset(sandbox, &test, asset, 100_000, 100).await; sandbox .new_assert_cmd("contract") .arg("asset") .arg("deploy") .arg("--asset") - .arg(&asset) + .arg(asset) .assert() .success(); let id = contract_id_hash_from_asset( - asset.parse::().unwrap(), + &format!("usdc:{test1}") + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(), &sandbox.network_passphrase, ); // sandbox @@ -542,7 +547,11 @@ async fn change_trust() { // wrap_cmd(&asset).run().await.unwrap(); let id = contract_id_hash_from_asset( - asset.parse::().unwrap(), + &asset + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(), &sandbox.network_passphrase, ); sandbox diff --git a/cmd/crates/soroban-test/tests/it/integration/wrap.rs b/cmd/crates/soroban-test/tests/it/integration/wrap.rs index 3fef56ef9..961cd6b4a 100644 --- a/cmd/crates/soroban-test/tests/it/integration/wrap.rs +++ b/cmd/crates/soroban-test/tests/it/integration/wrap.rs @@ -1,4 +1,4 @@ -use soroban_cli::{tx::builder, utils::contract_id_hash_from_asset}; +use soroban_cli::{config::locator, tx::builder, utils::contract_id_hash_from_asset}; use soroban_test::{AssertExt, TestEnv, LOCAL_NETWORK_PASSPHRASE}; #[tokio::test] @@ -12,21 +12,23 @@ async fn burn() { .arg("test") .assert() .stdout_as_str(); - let asset = format!("native:{address}"); + let asset = "native"; sandbox .new_assert_cmd("contract") .arg("asset") .arg("deploy") .arg("--source=test") .arg("--asset") - .arg(&asset) + .arg(asset) .assert() .success(); - // wrap_cmd(&asset).run().await.unwrap(); - let asset: builder::Asset = asset.parse().unwrap(); + let asset = asset + .parse::() + .unwrap() + .resolve(&locator::Args::default()) + .unwrap(); let hash = contract_id_hash_from_asset(&asset, &network_passphrase); let id = stellar_strkey::Contract(hash.0).to_string(); - println!("{id}, {address}"); sandbox .new_assert_cmd("contract") .args([ diff --git a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs index d05a192d4..468291f73 100644 --- a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs @@ -23,7 +23,7 @@ pub struct Args { impl TryFrom<&Cmd> for xdr::OperationBody { type Error = tx::args::Error; fn try_from(cmd: &Cmd) -> Result { - let asset = cmd.tx.resolve_asset(&cmd.line)?; + let asset = cmd.tx.resolve_asset(&cmd.op.line)?; let line = match asset { xdr::Asset::CreditAlphanum4(asset) => xdr::ChangeTrustAsset::CreditAlphanum4(asset), xdr::Asset::CreditAlphanum12(asset) => xdr::ChangeTrustAsset::CreditAlphanum12(asset), @@ -31,7 +31,7 @@ impl TryFrom<&Cmd> for xdr::OperationBody { }; Ok(xdr::OperationBody::ChangeTrust(xdr::ChangeTrustOp { line, - limit: cmd.limit, + limit: cmd.op.limit, })) } } diff --git a/cmd/soroban-cli/src/commands/tx/new/payment.rs b/cmd/soroban-cli/src/commands/tx/new/payment.rs index d7054ed46..9544217d6 100644 --- a/cmd/soroban-cli/src/commands/tx/new/payment.rs +++ b/cmd/soroban-cli/src/commands/tx/new/payment.rs @@ -29,9 +29,12 @@ impl TryFrom<&Cmd> for xdr::OperationBody { fn try_from( Cmd { tx, - destination, - asset, - amount, + op: + Args { + destination, + asset, + amount, + }, }: &Cmd, ) -> Result { Ok(xdr::OperationBody::Payment(xdr::PaymentOp { diff --git a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs index 4406db958..476fd1e7e 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs @@ -68,8 +68,8 @@ impl TryFrom<&Cmd> for xdr::OperationBody { Ok(xdr::OperationBody::SetTrustLineFlags( xdr::SetTrustLineFlagsOp { - trustor: cmd.tx.reslove_account_id(&cmd.trustor)?, - asset: cmd.tx.resolve_asset(&cmd.asset)?, + trustor: cmd.tx.reslove_account_id(&cmd.op.trustor)?, + asset: cmd.tx.resolve_asset(&cmd.op.asset)?, clear_flags, set_flags, }, diff --git a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs index 5e69cf168..1a02d01a2 100644 --- a/cmd/soroban-cli/src/commands/tx/op/add/mod.rs +++ b/cmd/soroban-cli/src/commands/tx/op/add/mod.rs @@ -52,7 +52,7 @@ impl TryFrom<&Cmd> for OperationBody { Ok(match &cmd { Cmd::AccountMerge(account_merge::Cmd { op, .. }) => op.try_into()?, Cmd::BumpSequence(bump_sequence::Cmd { op, .. }) => op.into(), - Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.into(), + Cmd::ChangeTrust(change_trust::Cmd { op, .. }) => op.try_into()?, Cmd::CreateAccount(create_account::Cmd { op, .. }) => op.try_into()?, Cmd::ManageData(manage_data::Cmd { op, .. }) => op.into(), Cmd::Payment(payment::Cmd { op, .. }) => op.try_into()?,