diff --git a/Cargo.lock b/Cargo.lock index edab8bb9a..eff278396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -117,6 +128,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -485,6 +502,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -560,6 +589,30 @@ dependencies = [ "serde_with", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.39", + "syn_derive", +] + [[package]] name = "bstr" version = "1.9.1" @@ -586,6 +639,28 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -659,6 +734,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -1503,6 +1584,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -2481,6 +2568,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -3793,6 +3883,38 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -3808,6 +3930,26 @@ version = "28.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.33" @@ -3817,6 +3959,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -3926,6 +4074,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.27" @@ -3995,6 +4152,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rpassword" version = "7.3.1" @@ -4050,6 +4236,22 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4263,6 +4465,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.2" @@ -4556,6 +4764,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "similar" version = "2.5.0" @@ -4700,6 +4914,7 @@ dependencies = [ "regex", "rpassword", "rust-embed", + "rust_decimal", "sep5", "serde", "serde-aux", @@ -5219,6 +5434,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -5246,6 +5473,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "temp-dir" version = "0.1.13" @@ -5834,6 +6067,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + [[package]] name = "valuable" version = "0.1.0" @@ -6305,6 +6544,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index ec0c843db..b6b6f5379 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -35,7 +35,7 @@ async fn invoke() { .arg("fund") .arg("test") .assert() - .stderr(predicates::str::contains("Account already exists")); + .stdout(predicates::str::contains("Nothing to do.")); sandbox .new_assert_cmd("keys") .arg("fund") @@ -138,6 +138,13 @@ async fn invoke() { handles_kebab_case(sandbox, id).await; fetch(sandbox, id).await; invoke_prng_u64_in_range_test(sandbox, id).await; + // test fund will add when account exists + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test") + .assert() + .stdout(predicates::str::contains("New test balance:")); } fn invoke_hello_world(sandbox: &TestEnv, id: &str) { diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 6d40ddd90..0fdfa7494 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -100,6 +100,7 @@ pathdiff = "0.2.1" dotenvy = "0.15.7" directories = { workspace = true } ulid = { workspace = true, features = ["serde"] } +rust_decimal = "1.35.0" strum = "0.17.1" strum_macros = "0.17.1" gix = { version = "0.58.0", default-features = false, features = [ diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index e912d2b98..4d770d107 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -19,7 +19,7 @@ use crate::{ NetworkRunnable, }, rpc::{Client, Error as SorobanRpcError}, - utils::{contract_id_hash_from_asset, parsing::parse_asset}, + utils::{contract_id_hash_from_asset, get_account_details, parsing::parse_asset}, }; #[derive(thiserror::Error, Debug)] @@ -94,16 +94,10 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; let client = Client::new(&network.rpc_url)?; - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; let key = config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); // TODO: use symbols for the method names (both here and in serve) - let account_details = client.get_account(&public_strkey).await?; + let account_details = + get_account_details(false, &client, &network.network_passphrase, &key).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)?; diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 22462cbcb..74be03c0d 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -12,11 +12,10 @@ use heck::ToKebabCase; use soroban_env_host::{ xdr::{ - self, AccountEntry, AccountEntryExt, AccountId, Hash, HostFunction, InvokeContractArgs, - InvokeHostFunctionOp, LedgerEntryData, Limits, Memo, MuxedAccount, Operation, - OperationBody, Preconditions, PublicKey, ScAddress, ScSpecEntry, ScSpecFunctionV0, - ScSpecTypeDef, ScVal, ScVec, SequenceNumber, String32, StringM, Thresholds, Transaction, - TransactionExt, Uint256, VecM, WriteXdr, + self, AccountId, Hash, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, + LedgerEntryData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + PublicKey, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, + SequenceNumber, Transaction, TransactionExt, Uint256, VecM, WriteXdr, }, HostError, }; @@ -30,6 +29,7 @@ use super::super::{ use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; use crate::commands::NetworkRunnable; use crate::get_spec::{self, get_remote_contract_spec}; +use crate::utils::get_account_details; use crate::{ commands::{config::data, global, network}, rpc, Pwd, @@ -321,19 +321,14 @@ impl NetworkRunnable for Cmd { let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?; } let client = rpc::Client::new(&network.rpc_url)?; - let account_details = if self.is_view { - default_account_entry() - } else { - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - let key = config.key_pair()?; - - // Get the account sequence number - let public_strkey = - stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); - client.get_account(&public_strkey).await? - }; + let key = config.key_pair()?; + let account_details = get_account_details( + self.is_view, + &client.clone(), + &network.network_passphrase, + &key, + ) + .await?; let sequence: i64 = account_details.seq_num.into(); let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; @@ -396,8 +391,6 @@ impl NetworkRunnable for Cmd { } } -const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))); - // fn log_auth_cost_and_footprint(resources: Option<&SorobanResources>) { // if let Some(resources) = resources { // crate::log::footprint(&resources.footprint); @@ -426,21 +419,6 @@ const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519( // .unwrap_or_default() // } -fn default_account_entry() -> AccountEntry { - AccountEntry { - account_id: DEFAULT_ACCOUNT_ID, - balance: 0, - seq_num: SequenceNumber(0), - num_sub_entries: 0, - inflation_dest: None, - flags: 0, - home_domain: String32::from(unsafe { StringM::<32>::from_str("TEST").unwrap_unchecked() }), - thresholds: Thresholds([0; 4]), - signers: unsafe { [].try_into().unwrap_unchecked() }, - ext: AccountEntryExt::V0, - } -} - pub fn output_to_string( spec: &Spec, res: &ScVal, diff --git a/cmd/soroban-cli/src/commands/keys/fund.rs b/cmd/soroban-cli/src/commands/keys/fund.rs index b6c088f13..5135d21bb 100644 --- a/cmd/soroban-cli/src/commands/keys/fund.rs +++ b/cmd/soroban-cli/src/commands/keys/fund.rs @@ -1,7 +1,18 @@ use clap::command; +use rand::{thread_rng, Rng}; +use rust_decimal::Decimal; +use soroban_sdk::xdr; +use stellar_strkey::ed25519::{PrivateKey, PublicKey}; -use crate::commands::network; +use crate::commands::config::secret::Secret; +use crate::commands::global; +use crate::commands::network::LOCAL_NETWORK_PASSPHRASE; +use crate::utils::contract_id_hash_from_asset; +use crate::utils::parsing::{self, parse_asset}; +use crate::{commands, rpc, CommandParser}; +use crate::{commands::network, utils::get_account_details}; +use super::super::config::secret; use super::address; #[derive(thiserror::Error, Debug)] @@ -10,6 +21,26 @@ pub enum Error { Address(#[from] address::Error), #[error(transparent)] Network(#[from] network::Error), + #[error(transparent)] + Secret(#[from] secret::Error), + #[error(transparent)] + Parsing(#[from] parsing::Error), + #[error(transparent)] + Rpc(#[from] rpc::Error), + #[error(transparent)] + Xdr(#[from] xdr::Error), + #[error(transparent)] + Clap(#[from] clap::Error), + #[error("RPC URL is missing in the network configuration")] + MissingRpcUrl, + #[error("Network passphrase is missing in the network configuration")] + MissingNetworkPassphrase, + #[error("Asset contract could not be deployed")] + AssetContractError, + #[error("Failed to transfer funds: {0}")] + FundTransferError(String), + #[error("Problem deploying asset contract: {0}")] + AssetDeploymentError(String), } #[derive(Debug, clap::Parser, Clone)] @@ -22,13 +53,159 @@ pub struct Cmd { pub address: address::Cmd, } +const STROOPS_PER_XLM: i64 = 10_000_000; +const DEFAULT_FRIENDBOT_AMOUNT: i64 = 10_000 * STROOPS_PER_XLM; + +fn convert_xlm_rounded(balance: i64) -> Decimal { + let xlm_balance = Decimal::new(balance, 0) / Decimal::new(STROOPS_PER_XLM, 0); + xlm_balance.round_dp(2) +} + impl Cmd { + pub async fn check_balance(&self) -> Result { + let rpc_url = self.network.rpc_url.as_ref().ok_or(Error::MissingRpcUrl)?; + let network_passphrase = self + .network + .network_passphrase + .as_ref() + .ok_or(Error::MissingNetworkPassphrase)?; + let client = rpc::Client::new(rpc_url)?; + let key = self.address.private_key()?; + let account_details = + get_account_details(false, &client.clone(), network_passphrase, &key).await?; + Ok(account_details.balance) + } + + pub async fn create_temp_account(&self) -> Result<(PublicKey, PrivateKey), Error> { + let random_seed: String = thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(16) + .map(char::from) + .collect(); + let secret = Secret::from_seed(Some(&random_seed))?; + let addr = secret.public_key(self.address.hd_path)?; + let private_key = secret.private_key(self.address.hd_path)?; + let network = self.network.get(&self.address.locator)?; + network + .fund_address(&addr) + .await + .map_err(|e| { + tracing::warn!("fund_address failed: {e}"); + }) + .unwrap_or_default(); + Ok((addr, private_key)) + } + + pub async fn get_asset_contract_id(&self) -> Result { + // if network is local, deploy the Stellar Asset contract and retrieve ID + let network = self.network.get(&self.address.locator)?; + let asset = parse_asset("native")?; + let rpc_url = self.network.rpc_url.as_ref().ok_or(Error::MissingRpcUrl)?; + if network.network_passphrase == LOCAL_NETWORK_PASSPHRASE { + let cmd = commands::contract::deploy::asset::Cmd::parse_arg_vec(&[ + "--asset", + "native", + "--source-account", + &self.address.name, + "--rpc-url", + rpc_url, + "--network-passphrase", + &network.network_passphrase, + ])?; + match cmd.run().await { + Ok(()) => { + let contract_id = + contract_id_hash_from_asset(&asset, &network.network_passphrase)?; + Ok(stellar_strkey::Contract(contract_id.0)) + } + Err(err) => { + if err.to_string().contains("ExistingValue") { + let contract_id = + contract_id_hash_from_asset(&asset, &network.network_passphrase)?; + Ok(stellar_strkey::Contract(contract_id.0)) + } else { + Err(Error::AssetDeploymentError(err.to_string())) + } + } + } + } else { + let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; + Ok(stellar_strkey::Contract(contract_id.0)) + } + } + + pub async fn add_funds(&self, amount: i64) -> Result<(), Error> { + let id = self.get_asset_contract_id().await?; + let addr = self.address.public_key()?; + let rpc_url = self.network.rpc_url.as_ref().ok_or(Error::MissingRpcUrl)?; + let network_passphrase = self + .network + .network_passphrase + .as_ref() + .ok_or(Error::MissingNetworkPassphrase)?; + let (from_id, temp_secret) = self.create_temp_account().await?; + let cmd = commands::contract::invoke::Cmd::parse_arg_vec(&[ + "--id", + &id.to_string(), + "--source-account", + &temp_secret.to_string(), + "--rpc-url", + rpc_url, + "--network-passphrase", + network_passphrase, + "--", + "transfer", + "--to", + &addr.to_string(), + "--from", + &from_id.to_string(), + "--amount", + &amount.to_string(), + ])?; + cmd.run(&global::Args { + locator: self.address.locator.clone(), + ..Default::default() + }) + .await + .map_err(|e| Error::FundTransferError(e.to_string())) + } + pub async fn run(&self) -> Result<(), Error> { let addr = self.address.public_key()?; - self.network - .get(&self.address.locator)? - .fund_address(&addr) - .await?; + let balance = self.check_balance().await.unwrap_or_else(|err| { + eprintln!("Failed to check balance: {err}"); + 0 + }); + let rounded_xlm = convert_xlm_rounded(balance); + if balance >= DEFAULT_FRIENDBOT_AMOUNT { + println!( + "Current {} balance: {} XLM. Nothing to do.", + self.address.name, rounded_xlm, + ); + } else if balance == 0 { + self.network + .get(&self.address.locator)? + .fund_address(&addr) + .await?; + } else { + println!( + "Current {} balance: {} XLM. Topping off...", + self.address.name, rounded_xlm, + ); + self.add_funds(DEFAULT_FRIENDBOT_AMOUNT - balance).await?; + let balance = match self.check_balance().await { + Ok(balance) => balance, + Err(err) => { + eprintln!("Failed to check balance: {err}"); + 0 + } + }; + println!( + "New {} balance: {} XLM.", + self.address.name, + convert_xlm_rounded(balance), + ); + } Ok(()) } } diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index 698d66007..0e7ccf580 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -27,11 +27,15 @@ pub struct Args { impl Args { pub fn apply_to_assembled_txn(&self, txn: Assembled) -> Assembled { - if let Some(instructions) = self.instructions { - txn.set_max_instructions(instructions) - } else { - add_padding_to_instructions(txn) - } + apply_max_instructions_to_txn(txn, self.instructions) + } +} + +pub fn apply_max_instructions_to_txn(txn: Assembled, max_instructions: Option) -> Assembled { + if let Some(instructions) = max_instructions { + txn.set_max_instructions(instructions) + } else { + add_padding_to_instructions(txn) } } diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index e74222c2c..4ace5c906 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -1,16 +1,17 @@ -use ed25519_dalek::Signer; +use crate::rpc; +use ed25519_dalek::{Signer, SigningKey}; use sha2::{Digest, Sha256}; +use std::str::FromStr; use stellar_strkey::ed25519::PrivateKey; use soroban_env_host::xdr::{ - Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, WriteXdr, + AccountEntry, AccountEntryExt, AccountId, Asset, ContractIdPreimage, DecoratedSignature, + Error as XdrError, Hash, HashIdPreimage, HashIdPreimageContractId, Limits, PublicKey, + SequenceNumber, Signature, SignatureHint, String32, StringM, Thresholds, Transaction, + TransactionEnvelope, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, Uint256, WriteXdr, }; -pub use soroban_spec_tools::contract as contract_spec; - /// # Errors /// /// Might return an error @@ -129,6 +130,45 @@ pub fn contract_id_hash_from_asset( Ok(Hash(Sha256::digest(preimage_xdr).into())) } +const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))); + +pub fn default_account_entry() -> AccountEntry { + AccountEntry { + account_id: DEFAULT_ACCOUNT_ID, + balance: 0, + seq_num: SequenceNumber(0), + num_sub_entries: 0, + inflation_dest: None, + flags: 0, + home_domain: String32::from(unsafe { StringM::<32>::from_str("TEST").unwrap_unchecked() }), + thresholds: Thresholds([0; 4]), + signers: unsafe { [].try_into().unwrap_unchecked() }, + ext: AccountEntryExt::V0, + } +} + +/// # Errors +/// Error retrieving rpc client from url +pub async fn get_account_details( + is_view: bool, + client: &rpc::Client, + network_passphrase: &str, + key: &SigningKey, +) -> Result { + if is_view { + Ok(default_account_entry()) + } else { + client + .verify_network_passphrase(Some(network_passphrase)) + .await?; + // Get the account sequence number + let public_strkey = + stellar_strkey::ed25519::PublicKey(key.verifying_key().to_bytes()).to_string(); + let account_details = client.get_account(&public_strkey).await?; + Ok(account_details) + } +} + pub mod parsing { use regex::Regex;