diff --git a/cmd/soroban-cli/src/commands/config/events_file.rs b/cmd/soroban-cli/src/commands/config/events_file.rs deleted file mode 100644 index 1b8930975e..0000000000 --- a/cmd/soroban-cli/src/commands/config/events_file.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::{ - commands::HEADING_SANDBOX, - rpc::{self, does_topic_match, Event}, - toid, -}; -use chrono::{DateTime, NaiveDateTime, Utc}; -use clap::arg; -use soroban_env_host::{ - events, - xdr::{self, WriteXdr}, -}; -use soroban_ledger_snapshot::LedgerSnapshot; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - /// File to persist events, default is `.soroban/events.json` - #[arg( - long, - value_name = "PATH", - env = "SOROBAN_EVENTS_FILE", - help_heading = HEADING_SANDBOX, - conflicts_with = "rpc_url", - conflicts_with = "network", - )] - pub events_file: Option, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Xdr(#[from] xdr::Error), - #[error(transparent)] - Rpc(#[from] rpc::Error), - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Generic(#[from] Box), - #[error("invalid timestamp in event: {ts}")] - InvalidTimestamp { ts: String }, -} - -impl Args { - /// Returns a list of events from the on-disk event store, which stores events - /// exactly as they'd be returned by an RPC server. - pub fn read(&self, pwd: &Path) -> Result { - let path = self.path(pwd); - let reader = std::fs::OpenOptions::new().read(true).open(path)?; - Ok(serde_json::from_reader(reader)?) - } - - /// Reads the existing event file, appends the new events, and writes it all to - /// disk. Note that this almost certainly isn't safe to call in parallel. - pub fn commit( - &self, - new_events: &[events::HostEvent], - ledger_info: &LedgerSnapshot, - pwd: &Path, - ) -> Result<(), Error> { - let output_file = self.path(pwd); - // Create the directory tree if necessary, since these are unlikely to be - // the first events. - if let Some(dir) = output_file.parent() { - if !dir.exists() { - fs::create_dir_all(dir)?; - } - } - - let mut events: Vec = if output_file.exists() { - let mut file = fs::OpenOptions::new().read(true).open(&output_file)?; - let payload: rpc::GetEventsResponse = serde_json::from_reader(&mut file)?; - payload.events - } else { - vec![] - }; - - for (i, event) in new_events.iter().enumerate() { - let contract_event = &event.event; - let topic = match &contract_event.body { - xdr::ContractEventBody::V0(e) => &e.topics, - } - .iter() - .map(xdr::WriteXdr::to_xdr_base64) - .collect::, _>>()?; - - // stolen from - // https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events.go#L264 - let id = format!( - "{}-{:010}", - toid::Toid::new( - ledger_info.sequence_number, - // we should technically inject the tx order here from the - // ledger info, but the sandbox does one tx/op per ledger - // anyway, so this is a safe assumption - 1, - 1, - ) - .to_paging_token(), - i + 1 - ); - - // Misc. timestamp to RFC 3339-formatted datetime nonsense, with an - // absurd amount of verbosity because every edge case needs its own - // chain of error-handling methods. - // - // Reference: https://stackoverflow.com/a/50072164 - let ts: i64 = - ledger_info - .timestamp - .try_into() - .map_err(|_e| Error::InvalidTimestamp { - ts: ledger_info.timestamp.to_string(), - })?; - let ndt = NaiveDateTime::from_timestamp_opt(ts, 0).ok_or_else(|| { - Error::InvalidTimestamp { - ts: ledger_info.timestamp.to_string(), - } - })?; - - let dt: DateTime = DateTime::from_naive_utc_and_offset(ndt, Utc); - - let cereal_event = rpc::Event { - event_type: match contract_event.type_ { - xdr::ContractEventType::Contract => "contract", - xdr::ContractEventType::System => "system", - xdr::ContractEventType::Diagnostic => "diagnostic", - } - .to_string(), - paging_token: id.clone(), - id, - ledger: ledger_info.sequence_number.to_string(), - ledger_closed_at: dt.format("%Y-%m-%dT%H:%M:%SZ").to_string(), - contract_id: hex::encode( - contract_event - .contract_id - .as_ref() - .unwrap_or(&xdr::Hash([0; 32])), - ), - topic, - value: rpc::EventValue { - xdr: match &contract_event.body { - xdr::ContractEventBody::V0(e) => &e.data, - } - .to_xdr_base64()?, - }, - }; - - events.push(cereal_event); - } - - let mut file = std::fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&output_file)?; - - serde_json::to_writer_pretty( - &mut file, - &rpc::GetEventsResponse { - events, - latest_ledger: ledger_info.sequence_number, - }, - )?; - - Ok(()) - } - - pub fn path(&self, pwd: &Path) -> PathBuf { - if let Some(path) = &self.events_file { - path.clone() - } else { - pwd.join("events.json") - } - } - - pub fn filter_events( - events: &[Event], - path: &Path, - start_cursor: (u64, i32), - contract_ids: &[String], - topic_filters: &[String], - count: usize, - ) -> Vec { - events - .iter() - .filter(|evt| match evt.parse_cursor() { - Ok(event_cursor) => event_cursor > start_cursor, - Err(e) => { - eprintln!("error parsing key 'ledger': {e:?}"); - eprintln!( - "your sandbox events file ('{path:?}') may be corrupt, consider deleting it", - ); - eprintln!("ignoring this event: {evt:#?}"); - - false - } - }) - .filter(|evt| { - // Contract ID filter(s) are optional, so we should render all - // events if they're omitted. - contract_ids.is_empty() || contract_ids.iter().any(|id| *id == evt.contract_id) - }) - .filter(|evt| { - // Like before, no topic filters means pass everything through. - topic_filters.is_empty() || - // Reminder: All of the topic filters are part of a single - // filter object, and each one contains segments, so we need to - // apply all of them to the given event. - topic_filters - .iter() - // quadratic, but both are <= 5 long - .any(|f| { - does_topic_match( - &evt.topic, - // misc. Rust nonsense: make a copy over the given - // split filter, because passing a slice of - // references is too much for this language to - // handle - &f.split(',') - .map(std::string::ToString::to_string) - .collect::>() - ) - }) - }) - .take(count) - .cloned() - .collect() - } -} diff --git a/cmd/soroban-cli/src/commands/config/ledger_file.rs b/cmd/soroban-cli/src/commands/config/ledger_file.rs deleted file mode 100644 index f85cbfdac3..0000000000 --- a/cmd/soroban-cli/src/commands/config/ledger_file.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{commands::HEADING_SANDBOX, utils}; -use clap::arg; -use soroban_ledger_snapshot::LedgerSnapshot; -use std::path::{Path, PathBuf}; - -#[derive(Debug, clap::Args, Clone, Default)] -#[group(skip)] -pub struct Args { - /// File to persist ledger state, default is `.soroban/ledger.json` - #[arg( - long, - env = "SOROBAN_LEDGER_FILE", - help_heading = HEADING_SANDBOX, - )] - pub ledger_file: Option, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, - - #[error("committing file {filepath}: {error}")] - CannotCommitLedgerFile { - filepath: PathBuf, - error: soroban_ledger_snapshot::Error, - }, -} - -impl Args { - pub fn read(&self, pwd: &Path) -> Result { - let filepath = self.path(pwd); - utils::ledger_snapshot_read_or_default(&filepath) - .map_err(|e| Error::CannotReadLedgerFile { filepath, error: e }) - } - - pub fn write(&self, state: &LedgerSnapshot, pwd: &Path) -> Result<(), Error> { - let filepath = self.path(pwd); - - state - .write_file(&filepath) - .map_err(|e| Error::CannotCommitLedgerFile { filepath, error: e }) - } - - pub fn path(&self, pwd: &Path) -> PathBuf { - if let Some(path) = &self.ledger_file { - path.clone() - } else { - pwd.join("ledger.json") - } - } -} diff --git a/cmd/soroban-cli/src/commands/config/mod.rs b/cmd/soroban-cli/src/commands/config/mod.rs index 8b47b32bf8..f4d8ec5544 100644 --- a/cmd/soroban-cli/src/commands/config/mod.rs +++ b/cmd/soroban-cli/src/commands/config/mod.rs @@ -2,15 +2,12 @@ use std::path::PathBuf; use clap::{arg, command, Parser}; use serde::{Deserialize, Serialize}; -use soroban_ledger_snapshot::LedgerSnapshot; use crate::Pwd; use self::{network::Network, secret::Secret}; -pub mod events_file; pub mod identity; -pub mod ledger_file; pub mod locator; pub mod network; pub mod secret; @@ -20,7 +17,6 @@ pub enum Cmd { /// Configure different identities to sign transactions. #[command(subcommand)] Identity(identity::Cmd), - /// Configure different networks #[command(subcommand)] Network(network::Cmd), @@ -30,16 +26,10 @@ pub enum Cmd { pub enum Error { #[error(transparent)] Identity(#[from] identity::Error), - #[error(transparent)] Network(#[from] network::Error), - - #[error(transparent)] - Ledger(#[from] ledger_file::Error), - #[error(transparent)] Secret(#[from] secret::Error), - #[error(transparent)] Config(#[from] locator::Error), } @@ -60,9 +50,6 @@ pub struct Args { #[command(flatten)] pub network: network::Args, - #[command(flatten)] - pub ledger_file: ledger_file::Args, - #[arg(long, visible_alias = "source", env = "SOROBAN_ACCOUNT")] /// Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` pub source_account: Option, @@ -98,18 +85,6 @@ impl Args { Ok(self.network.get(&self.locator)?) } - pub fn is_no_network(&self) -> bool { - self.network.is_no_network() - } - - pub fn get_state(&self) -> Result { - Ok(self.ledger_file.read(&self.locator.config_dir()?)?) - } - - pub fn set_state(&self, state: &LedgerSnapshot) -> Result<(), Error> { - Ok(self.ledger_file.write(state, &self.locator.config_dir()?)?) - } - pub fn config_dir(&self) -> Result { Ok(self.locator.config_dir()?) } diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs index ef5856ea0c..c0a7801535 100644 --- a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs +++ b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs @@ -7,7 +7,7 @@ use crate::wasm; use crate::{ commands::{ config::{ - ledger_file, locator, + locator, network::{self, Network}, }, contract::{self, fetch}, @@ -21,22 +21,17 @@ pub struct Cmd { /// Path to optional wasm binary #[arg(long)] pub wasm: Option, - /// Where to place generated project #[arg(long)] output_dir: PathBuf, - /// Whether to overwrite output directory if it already exists #[arg(long)] overwrite: bool, - /// The contract ID/address on the network #[arg(long, visible_alias = "id")] contract_id: String, - #[command(flatten)] locator: locator::Args, - #[command(flatten)] network: network::Args, } @@ -79,7 +74,6 @@ impl Cmd { out_file: None, locator: self.locator.clone(), network: self.network.clone(), - ledger_file: ledger_file::Args::default(), }; let bytes = fetch.get_bytes().await?; ContractSpec::new(&bytes)?.spec diff --git a/cmd/soroban-cli/src/commands/contract/deploy.rs b/cmd/soroban-cli/src/commands/contract/deploy.rs index abcf47fd13..2b3cffb177 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy.rs @@ -17,7 +17,7 @@ use soroban_env_host::{ }; use crate::{ - commands::{config, contract::install, HEADING_RPC, HEADING_SANDBOX}, + commands::{config, contract::install, HEADING_RPC}, rpc::{self, Client}, utils, wasm, }; @@ -33,22 +33,12 @@ pub struct Cmd { /// WASM file to deploy #[arg(long, group = "wasm_src")] wasm: Option, - /// Hash of the already installed/deployed WASM file #[arg(long = "wasm-hash", conflicts_with = "wasm", group = "wasm_src")] wasm_hash: Option, - - /// Contract ID to deploy to - #[arg( - long = "id", - conflicts_with = "rpc_url", - help_heading = HEADING_SANDBOX, - )] - contract_id: Option, /// Custom salt 32-byte salt for the token id #[arg( long, - conflicts_with_all = &["contract_id", "ledger_file"], help_heading = HEADING_RPC, )] salt: Option, diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 6795771e67..9097ffdea0 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -16,12 +16,10 @@ use soroban_env_host::{ }, }; -use soroban_ledger_snapshot::LedgerSnapshot; use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; use super::super::config::{self, locator}; -use crate::commands::config::ledger_file; use crate::commands::config::network::{self, Network}; use crate::{ rpc::{self, Client}, @@ -42,8 +40,6 @@ pub struct Cmd { pub locator: locator::Args, #[command(flatten)] pub network: network::Args, - #[command(flatten)] - pub ledger_file: ledger_file::Args, } impl FromStr for Cmd { @@ -87,8 +83,6 @@ pub enum Error { NetworkNotProvided, #[error(transparent)] Network(#[from] network::Error), - #[error(transparent)] - Ledger(#[from] ledger_file::Error), #[error("cannot create contract directory for {0:?}")] CannotCreateContractDir(PathBuf), } @@ -140,10 +134,6 @@ impl Cmd { Ok(client.get_remote_wasm(&contract_id).await?) } - pub fn get_state(&self) -> Result { - Ok(self.ledger_file.read(&self.locator.config_dir()?)?) - } - fn contract_id(&self) -> Result<[u8; 32], Error> { utils::contract_id_from_str(&self.contract_id) .map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e)) diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index ed1a8d623c..59c9bf4400 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -25,11 +25,11 @@ use soroban_spec::read::FromWasmError; use stellar_strkey::DecodeError; use super::super::{ - config::{self, events_file, locator}, + config::{self, locator}, events, }; use crate::{ - commands::{global, HEADING_SANDBOX}, + commands::global, rpc::{self, Client}, utils::{self, contract_spec}, Pwd, @@ -43,29 +43,18 @@ pub struct Cmd { /// Contract ID to invoke #[arg(long = "id", env = "SOROBAN_CONTRACT_ID")] pub contract_id: String, - /// WASM file of the contract to invoke (if using sandbox will deploy this file) + // For testing only #[arg(skip)] pub wasm: Option, - /// Output the cost execution to stderr - #[arg(long = "cost", conflicts_with = "rpc_url", conflicts_with="network", help_heading = HEADING_SANDBOX)] + #[arg(long = "cost")] pub cost: bool, - /// Run with an unlimited budget - #[arg(long = "unlimited-budget", - conflicts_with = "rpc_url", - conflicts_with = "network", - help_heading = HEADING_SANDBOX)] - pub unlimited_budget: bool, - /// Function name as subcommand, then arguments for that function as `--arg-name value` #[arg(last = true, id = "CONTRACT_FN_AND_ARGS")] pub slop: Vec, - #[command(flatten)] pub config: config::Args, #[command(flatten)] - pub events_file: events_file::Args, - #[command(flatten)] pub fee: crate::fee::Args, } @@ -145,8 +134,6 @@ pub enum Error { #[error(transparent)] Clap(#[from] clap::Error), #[error(transparent)] - Events(#[from] events_file::Error), - #[error(transparent)] Locator(#[from] locator::Error), #[error("Contract Error\n{0}: {1}")] ContractInvoke(String, String), diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index c2dc1a6775..d9566b6ce8 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -28,7 +28,7 @@ pub enum Cmd { /// Deploy a contract Deploy(deploy::Cmd), - /// Fetch a contract's Wasm binary from a network or local sandbox + /// Fetch a contract's Wasm binary Fetch(fetch::Cmd), /// Inspect a WASM file listing contract functions, meta, etc diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index aa5f11ee66..20a7e0bfa8 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -3,18 +3,16 @@ use std::io; use soroban_env_host::xdr::{self, ReadXdr}; -use super::config::{events_file, locator, network}; +use super::config::{locator, network}; use crate::{rpc, utils}; #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { - /// The first ledger sequence number in the range to pull events (required - /// if not in sandbox mode). + /// The first ledger sequence number in the range to pull events /// https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence #[arg(long, conflicts_with = "cursor", required_unless_present = "cursor")] start_ledger: Option, - /// The cursor corresponding to the start of the event range. #[arg( long, @@ -22,17 +20,12 @@ pub struct Cmd { required_unless_present = "start_ledger" )] cursor: Option, - /// Output formatting options for event stream #[arg(long, value_enum, default_value = "pretty")] output: OutputFormat, - - /// The maximum number of events to display (specify "0" to show all events - /// when using sandbox, or to defer to the server-defined limit if using - /// RPC). + /// The maximum number of events to display (defer to the server-defined limit). #[arg(short, long, default_value = "10")] count: usize, - /// A set of (up to 5) contract IDs to filter events on. This parameter can /// be passed multiple times, e.g. `--id abc --id def`, or passed with /// multiple parameters, e.g. `--id abd def`. @@ -46,7 +39,6 @@ pub struct Cmd { help_heading = "FILTERS" )] contract_ids: Vec, - /// A set of (up to 4) topic filters to filter event topics on. A single /// topic filter can contain 1-4 different segment filters, separated by /// commas, with an asterisk (* character) indicating a wildcard segment. @@ -67,7 +59,6 @@ pub struct Cmd { help_heading = "FILTERS" )] topic_filters: Vec, - /// Specifies which type of contract events to display. #[arg( long = "type", @@ -76,79 +67,56 @@ pub struct Cmd { help_heading = "FILTERS" )] event_type: rpc::EventType, - #[command(flatten)] locator: locator::Args, - #[command(flatten)] network: network::Args, - - #[command(flatten)] - events_file: events_file::Args, } #[derive(thiserror::Error, Debug)] pub enum Error { #[error("cursor is not valid")] InvalidCursor, - #[error("filepath does not exist: {path}")] InvalidFile { path: String }, - #[error("filepath ({path}) cannot be read: {error}")] CannotReadFile { path: String, error: String }, - #[error("cannot parse topic filter {topic} into 1-4 segments")] InvalidTopicFilter { topic: String }, - #[error("invalid segment ({segment}) in topic filter ({topic}): {error}")] InvalidSegment { topic: String, segment: String, error: xdr::Error, }, - #[error("cannot parse contract ID {contract_id}: {error}")] InvalidContractId { contract_id: String, error: stellar_strkey::DecodeError, }, - #[error("invalid JSON string: {error} ({debug})")] InvalidJson { debug: String, error: serde_json::Error, }, - #[error("invalid timestamp in event: {ts}")] InvalidTimestamp { ts: String }, - #[error("missing start_ledger and cursor")] MissingStartLedgerAndCursor, #[error("missing target")] MissingTarget, - #[error(transparent)] Rpc(#[from] rpc::Error), - #[error(transparent)] Generic(#[from] Box), - #[error(transparent)] Io(#[from] io::Error), - #[error(transparent)] Xdr(#[from] xdr::Error), - #[error(transparent)] Serde(#[from] serde_json::Error), - #[error(transparent)] Network(#[from] network::Error), - - #[error(transparent)] - EventsFile(#[from] events_file::Error), - #[error(transparent)] Locator(#[from] locator::Error), } @@ -251,89 +219,3 @@ impl Cmd { Ok(start) } } - -#[cfg(test)] -mod tests { - use std::path; - - use assert_fs::NamedTempFile; - use soroban_env_host::events; - use soroban_sdk::xdr::VecM; - - use super::*; - - use events_file::Args; - #[test] - fn test_does_event_serialization_match() { - let temp = NamedTempFile::new("events.json").unwrap(); - let events_file = Args { - events_file: Some(temp.to_path_buf()), - }; - // Make a couple of fake events with slightly different properties and - // write them to disk, then read the serialized versions from disk and - // ensure the properties match. - - let events: Vec = vec![ - events::HostEvent { - event: xdr::ContractEvent { - ext: xdr::ExtensionPoint::V0, - contract_id: Some(xdr::Hash([0; 32])), - type_: xdr::ContractEventType::Contract, - body: xdr::ContractEventBody::V0(xdr::ContractEventV0 { - topics: VecM::default(), - data: xdr::ScVal::U32(12345), - }), - }, - failed_call: false, - }, - events::HostEvent { - event: xdr::ContractEvent { - ext: xdr::ExtensionPoint::V0, - contract_id: Some(xdr::Hash([0x1; 32])), - type_: xdr::ContractEventType::Contract, - body: xdr::ContractEventBody::V0(xdr::ContractEventV0 { - topics: VecM::default(), - data: xdr::ScVal::I32(67890), - }), - }, - failed_call: false, - }, - ]; - - let ledger_info = soroban_ledger_snapshot::LedgerSnapshot { - protocol_version: 1, - sequence_number: 2, // this is the only value that matters - timestamp: 3, - network_id: [0x1; 32], - base_reserve: 5, - ledger_entries: vec![], - max_entry_expiration: 6, - min_persistent_entry_expiration: 7, - min_temp_entry_expiration: 8, - }; - - events_file.commit(&events, &ledger_info, &temp).unwrap(); - - let file = events_file.read(&std::env::current_dir().unwrap()).unwrap(); - assert_eq!(file.events.len(), 2); - assert_eq!(file.events[0].ledger, "2"); - assert_eq!(file.events[1].ledger, "2"); - assert_eq!(file.events[0].contract_id, "0".repeat(64)); - assert_eq!(file.events[1].contract_id, "01".repeat(32)); - assert_eq!(file.latest_ledger, 2); - } - - #[test] - fn test_does_event_fixture_load() { - // This test ensures that the included JSON fixture file matches the - // correct event format (for the purposes of human readability). - let filename = - path::PathBuf::from("../crates/soroban-test/tests/fixtures/test-jsons/get-events.json"); - let events_file = Args { - events_file: Some(filename), - }; - let result = events_file.read(&std::env::current_dir().unwrap()); - println!("{result:?}"); - assert!(result.is_ok()); - } -} diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 295144f9d0..a9f6680824 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -11,7 +11,6 @@ pub mod lab; pub mod plugin; pub mod version; -pub const HEADING_SANDBOX: &str = "Options (Sandbox)"; pub const HEADING_RPC: &str = "Options (RPC)"; const ABOUT: &str = "Build, deploy, & interact with contracts; set identities to sign with; configure networks; generate keys; and more. @@ -33,11 +32,13 @@ Commands that relate to smart contract interactions are organized under the `con A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: - soroban contract invoke --id 1 --source alice -- --help + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + --help Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - soroban contract invoke --id 1 --source alice -- hello --to world + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- \ + hello --to world Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md"; diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 1589bc4851..3aad487c82 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -7,7 +7,6 @@ pub mod commands; pub mod fee; pub mod key; pub mod log; -pub mod network; pub mod rpc; pub mod toid; pub mod utils; diff --git a/cmd/soroban-cli/src/network.rs b/cmd/soroban-cli/src/network.rs deleted file mode 100644 index d0bc3427fb..0000000000 --- a/cmd/soroban-cli/src/network.rs +++ /dev/null @@ -1,8 +0,0 @@ -use sha2::{Digest, Sha256}; - -pub static SANDBOX_NETWORK_PASSPHRASE: &str = "Local Sandbox Stellar Network ; September 2022"; - -#[must_use] -pub fn sandbox_network_id() -> [u8; 32] { - Sha256::digest(SANDBOX_NETWORK_PASSPHRASE.as_bytes()).into() -} diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 0cbf9dfbc6..fcc3964c98 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -1,28 +1,13 @@ -use std::hash::BuildHasher; -use std::{collections::HashMap, io::ErrorKind, path::Path}; - use ed25519_dalek::Signer; use sha2::{Digest, Sha256}; - -use soroban_env_host::{ - storage::{AccessType, Footprint}, - xdr::{ - AccountEntry, AccountEntryExt, AccountId, Asset, ContractCodeEntry, ContractDataDurability, - ContractDataEntry, ContractExecutable, ContractIdPreimage, DecoratedSignature, - Error as XdrError, ExtensionPoint, Hash, HashIdPreimage, HashIdPreimageContractId, - LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerFootprint, LedgerKey, - LedgerKeyContractCode, LedgerKeyContractData, ScAddress, ScContractInstance, ScSpecEntry, - ScVal, SequenceNumber, Signature, SignatureHint, String32, Thresholds, Transaction, - TransactionEnvelope, TransactionSignaturePayload, - TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, VecM, WriteXdr, - }, -}; -use soroban_ledger_snapshot::LedgerSnapshot; -use soroban_sdk::token; -use soroban_spec::read::FromWasmError; use stellar_strkey::ed25519::PrivateKey; -use crate::network::sandbox_network_id; +use soroban_env_host::xdr::{ + Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, + HashIdPreimageContractId, Signature, SignatureHint, Transaction, TransactionEnvelope, + TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, + TransactionV1Envelope, WriteXdr, +}; pub mod contract_spec; @@ -33,122 +18,6 @@ pub fn contract_hash(contract: &[u8]) -> Result { Ok(Hash(Sha256::digest(contract).into())) } -/// # Errors -/// -/// Might return an error -pub fn ledger_snapshot_read_or_default( - p: impl AsRef, -) -> Result { - match LedgerSnapshot::read_file(p) { - Ok(snapshot) => Ok(snapshot), - Err(soroban_ledger_snapshot::Error::Io(e)) if e.kind() == ErrorKind::NotFound => { - Ok(LedgerSnapshot { - network_id: sandbox_network_id(), - // These three "defaults" are not part of the actual default definition in - // rs-soroban-sdk, but if we don't have them the sandbox doesn't work right. - // Oof. - // TODO: Remove this hacky workaround. - min_persistent_entry_expiration: 4096, - min_temp_entry_expiration: 16, - max_entry_expiration: 6_312_000, - ..Default::default() - }) - } - Err(e) => Err(e), - } -} - -type LedgerSnapshotEntries = Vec<(Box, (Box, Option))>; - -/// # Errors -/// -/// Might return an error -pub fn add_contract_code_to_ledger_entries( - entries: &mut LedgerSnapshotEntries, - contract: Vec, - min_persistent_entry_expiration: u32, -) -> Result { - // Install the code - let hash = contract_hash(contract.as_slice())?; - let code_key = LedgerKey::ContractCode(LedgerKeyContractCode { hash: hash.clone() }); - let code_entry = LedgerEntry { - last_modified_ledger_seq: 0, - data: LedgerEntryData::ContractCode(ContractCodeEntry { - ext: ExtensionPoint::V0, - hash: hash.clone(), - code: contract.try_into()?, - }), - ext: LedgerEntryExt::V0, - }; - for (k, e) in &mut *entries { - if **k == code_key { - *e = (Box::new(code_entry), Some(min_persistent_entry_expiration)); - return Ok(hash); - } - } - entries.push(( - Box::new(code_key), - (Box::new(code_entry), Some(min_persistent_entry_expiration)), - )); - Ok(hash) -} - -pub fn add_contract_to_ledger_entries( - entries: &mut LedgerSnapshotEntries, - contract_id: [u8; 32], - wasm_hash: [u8; 32], - min_persistent_entry_expiration: u32, -) { - // Create the contract - let contract_key = LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(contract_id.into()), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }); - - let contract_entry = LedgerEntry { - last_modified_ledger_seq: 0, - data: LedgerEntryData::ContractData(ContractDataEntry { - contract: ScAddress::Contract(contract_id.into()), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - val: ScVal::ContractInstance(ScContractInstance { - executable: ContractExecutable::Wasm(Hash(wasm_hash)), - storage: None, - }), - ext: ExtensionPoint::V0, - }), - ext: LedgerEntryExt::V0, - }; - for (k, e) in &mut *entries { - if **k == contract_key { - *e = ( - Box::new(contract_entry), - Some(min_persistent_entry_expiration), - ); - return; - } - } - entries.push(( - Box::new(contract_key), - ( - Box::new(contract_entry), - Some(min_persistent_entry_expiration), - ), - )); -} - -pub fn bump_ledger_entry_expirations( - entries: &mut LedgerSnapshotEntries, - lookup: &HashMap, -) { - for (k, (_, expiration)) in &mut *entries { - if let Some(min_expiration) = lookup.get(k.as_ref()) { - *expiration = Some(*min_expiration); - } - } -} - /// # Errors /// /// Might return an error @@ -198,129 +67,6 @@ pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strke .map_err(|_| stellar_strkey::DecodeError::Invalid) } -fn get_entry_from_snapshot( - key: &LedgerKey, - entries: &LedgerSnapshotEntries, -) -> Option<(Box, Option)> { - for (k, result) in entries { - if *key == **k { - return Some((*result).clone()); - } - } - None -} - -/// # Errors -/// -/// Might return an error -pub fn get_contract_spec_from_state( - state: &LedgerSnapshot, - contract_id: [u8; 32], -) -> Result, FromWasmError> { - let current_ledger_seq = state.sequence_number; - let key = LedgerKey::ContractData(LedgerKeyContractData { - contract: ScAddress::Contract(contract_id.into()), - key: ScVal::LedgerKeyContractInstance, - durability: ContractDataDurability::Persistent, - }); - let (entry, expiration_ledger_seq) = match get_entry_from_snapshot(&key, &state.ledger_entries) - { - // It's a contract data entry, so it should have an expiration if present - Some((entry, expiration)) => (entry, expiration.unwrap()), - None => return Err(FromWasmError::NotFound), - }; - if expiration_ledger_seq <= current_ledger_seq { - return Err(FromWasmError::NotFound); - } - - match *entry { - LedgerEntry { - data: - LedgerEntryData::ContractData(ContractDataEntry { - val: ScVal::ContractInstance(ScContractInstance { executable, .. }), - .. - }), - .. - } => match executable { - ContractExecutable::Token => { - // TODO/FIXME: I don't think it will work for token contracts, since we don't store them in the state? - let res = soroban_spec::read::parse_raw(&token::StellarAssetSpec::spec_xdr()); - res.map_err(FromWasmError::Parse) - } - ContractExecutable::Wasm(hash) => { - // It's a contract code entry, so it should have an expiration if present - let (entry, expiration_ledger_seq) = match get_entry_from_snapshot( - &LedgerKey::ContractCode(LedgerKeyContractCode { hash: hash.clone() }), - &state.ledger_entries, - ) { - // It's a contract data entry, so it should have an expiration if present - Some((entry, expiration)) => (entry, expiration.unwrap()), - None => return Err(FromWasmError::NotFound), - }; - if expiration_ledger_seq <= current_ledger_seq { - return Err(FromWasmError::NotFound); - } - match *entry { - LedgerEntry { - data: LedgerEntryData::ContractCode(ContractCodeEntry { code, .. }), - .. - } => soroban_spec::read::from_wasm(code.as_vec()), - _ => Err(FromWasmError::NotFound), - } - } - }, - _ => Err(FromWasmError::NotFound), - } -} - -/// # Panics -/// -/// May panic -#[must_use] -pub fn create_ledger_footprint(footprint: &Footprint) -> LedgerFootprint { - let mut read_only: Vec = vec![]; - let mut read_write: Vec = vec![]; - let Footprint(m) = footprint; - for (k, v) in m { - let dest = match v { - AccessType::ReadOnly => &mut read_only, - AccessType::ReadWrite => &mut read_write, - }; - dest.push((**k).clone()); - } - LedgerFootprint { - read_only: read_only.try_into().unwrap(), - read_write: read_write.try_into().unwrap(), - } -} - -#[must_use] -pub fn default_account_ledger_entry(account_id: AccountId) -> LedgerEntry { - // TODO: Consider moving the definition of a default account ledger entry to - // a location shared by the SDK and CLI. The SDK currently defines the same - // value (see URL below). There's some benefit in only defining this once to - // prevent the two from diverging, which would cause inconsistent test - // behavior between the SDK and CLI. A good home for this is unclear at this - // time. - // https://github.com/stellar/rs-soroban-sdk/blob/b6f9a2c7ec54d2d5b5a1e02d1e38ae3158c22e78/soroban-sdk/src/accounts.rs#L470-L483. - LedgerEntry { - data: LedgerEntryData::Account(AccountEntry { - account_id, - balance: 0, - flags: 0, - home_domain: String32::default(), - inflation_dest: None, - num_sub_entries: 0, - seq_num: SequenceNumber(0), - thresholds: Thresholds([1; 4]), - signers: VecM::default(), - ext: AccountEntryExt::V0, - }), - last_modified_ledger_seq: 0, - ext: LedgerEntryExt::V0, - } -} - /// # Errors /// May not find a config dir pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { diff --git a/docs/soroban-cli-full-docs.md b/docs/soroban-cli-full-docs.md index ce09608ed7..9298e789d8 100644 --- a/docs/soroban-cli-full-docs.md +++ b/docs/soroban-cli-full-docs.md @@ -62,11 +62,11 @@ Commands that relate to smart contract interactions are organized under the `con A Soroban contract has its interface schema types embedded in the binary that gets deployed on-chain, making it possible to dynamically generate a custom CLI for each. `soroban contract invoke` makes use of this: - soroban contract invoke --id 1 --source alice -- --help + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help Anything after the `--` double dash (the "slop") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the embedded schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it: - soroban contract invoke --id 1 --source alice -- hello --to world + soroban contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world Full CLI reference: https://github.com/stellar/soroban-tools/tree/main/docs/soroban-cli-full-docs.md @@ -129,7 +129,7 @@ Tools for smart contract developers * `build` — Build a contract from source * `bump` — Extend the expiry ledger of a contract-data ledger entry * `deploy` — Deploy a contract -* `fetch` — Fetch a contract's Wasm binary from a network or local sandbox +* `fetch` — Fetch a contract's Wasm binary * `inspect` — Inspect a WASM file listing contract functions, meta, etc * `install` — Install a WASM file to the ledger without creating a contract instance * `invoke` — Invoke a contract function @@ -252,7 +252,6 @@ If no keys are specified the contract itself is bumped. * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -273,12 +272,10 @@ Deploy a contract * `--wasm ` — WASM file to deploy * `--wasm-hash ` — Hash of the already installed/deployed WASM file -* `--id ` — Contract ID to deploy to * `--salt ` — Custom salt 32-byte salt for the token id * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -291,7 +288,7 @@ Deploy a contract ## `soroban contract fetch` -Fetch a contract's Wasm binary from a network or local sandbox +Fetch a contract's Wasm binary **Usage:** `soroban contract fetch [OPTIONS] --id ` @@ -304,7 +301,6 @@ Fetch a contract's Wasm binary from a network or local sandbox * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` @@ -345,7 +341,6 @@ Install a WASM file to the ledger without creating a contract instance * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -374,18 +369,14 @@ soroban contract invoke ... -- --help ###### **Options:** * `--id ` — Contract ID to invoke -* `--wasm ` — WASM file of the contract to invoke (if using sandbox will deploy this file) * `--cost` — Output the cost execution to stderr -* `--unlimited-budget` — Run with an unlimited budget * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 ` -* `--events-file ` — File to persist events, default is `.soroban/events.json` * `--fee ` — fee amount for transaction, in stroops. 1 stroop = 0.0000001 xlm Default value: `100` @@ -441,7 +432,6 @@ Print the current value of a contract-data ledger entry * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -476,7 +466,6 @@ If no keys are specificed the contract itself is restored. * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -720,7 +709,7 @@ Watch the network for contract events ###### **Options:** -* `--start-ledger ` — The first ledger sequence number in the range to pull events (required if not in sandbox mode). https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence +* `--start-ledger ` — The first ledger sequence number in the range to pull events https://developers.stellar.org/docs/encyclopedia/ledger-headers#ledger-sequence * `--cursor ` — The cursor corresponding to the start of the event range * `--output ` — Output formatting options for event stream @@ -734,7 +723,7 @@ Watch the network for contract events - `json`: JSONified console output -* `-c`, `--count ` — The maximum number of events to display (specify "0" to show all events when using sandbox, or to defer to the server-defined limit if using RPC) +* `-c`, `--count ` — The maximum number of events to display (defer to the server-defined limit) Default value: `10` * `--id ` — A set of (up to 5) contract IDs to filter events on. This parameter can be passed multiple times, e.g. `--id abc --id def`, or passed with multiple parameters, e.g. `--id abd def` @@ -750,7 +739,6 @@ Watch the network for contract events * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--events-file ` — File to persist events, default is `.soroban/events.json` @@ -792,7 +780,6 @@ Deploy a token contract to wrap an existing Stellar classic asset for smart cont * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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 @@ -815,7 +802,6 @@ Compute the expected contract id for the given asset * `--rpc-url ` — RPC server endpoint * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--ledger-file ` — File to persist ledger state, default is `.soroban/ledger.json` * `--source-account ` — Account that signs the final transaction. Alias `source`. Can be an identity (--source alice), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). Default: `identity generate --default-seed` * `--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