Skip to main content

soroban_cli/commands/contract/
read.rs

1use std::{
2    fmt::Debug,
3    io::{self, stdout},
4};
5
6use crate::xdr::{
7    ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData,
8    Limits, ScVal, WriteXdr,
9};
10use clap::{Parser, ValueEnum};
11
12use crate::{
13    config::{self, locator},
14    key,
15    rpc::{self, FullLedgerEntries, FullLedgerEntry},
16};
17
18#[derive(Parser, Debug, Clone)]
19#[group(skip)]
20pub struct Cmd {
21    /// Type of output to generate
22    #[arg(long, value_enum, default_value("string"))]
23    pub output: Output,
24    #[command(flatten)]
25    pub key: key::Args,
26    #[command(flatten)]
27    config: config::ArgsLocatorAndNetwork,
28}
29
30#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)]
31pub enum Output {
32    /// String
33    String,
34    /// Json
35    Json,
36    /// XDR
37    Xdr,
38}
39
40#[derive(thiserror::Error, Debug)]
41pub enum Error {
42    #[error("parsing key {key}: {error}")]
43    CannotParseKey {
44        key: String,
45        error: soroban_spec_tools::Error,
46    },
47    #[error("parsing XDR key {key}: {error}")]
48    CannotParseXdrKey { key: String, error: XdrError },
49    #[error("cannot parse contract ID {contract_id}: {error}")]
50    CannotParseContractId {
51        contract_id: String,
52        error: stellar_strkey::DecodeError,
53    },
54    #[error("cannot print result {result:?}: {error}")]
55    CannotPrintResult {
56        result: ScVal,
57        error: soroban_spec_tools::Error,
58    },
59    #[error("cannot print result {result:?}: {error}")]
60    CannotPrintJsonResult {
61        result: ScVal,
62        error: serde_json::Error,
63    },
64    #[error("cannot print as csv: {error}")]
65    CannotPrintAsCsv { error: csv::Error },
66    #[error("cannot print: {error}")]
67    CannotPrintFlush { error: io::Error },
68    #[error(transparent)]
69    Config(#[from] config::Error),
70    #[error("either `--key` or `--key-xdr` are required when querying a network")]
71    KeyIsRequired,
72    #[error(transparent)]
73    Rpc(#[from] rpc::Error),
74    #[error(transparent)]
75    Xdr(#[from] XdrError),
76    #[error("no matching contract data entries were found for the specified contract id")]
77    NoContractDataEntryFoundForContractID,
78    #[error(transparent)]
79    Key(#[from] key::Error),
80    #[error("Only contract data and code keys are allowed")]
81    OnlyDataAllowed,
82    #[error(transparent)]
83    Locator(#[from] locator::Error),
84    #[error(transparent)]
85    Network(#[from] config::network::Error),
86}
87
88impl Cmd {
89    pub async fn run(&self) -> Result<(), Error> {
90        let entries = self
91            .execute(&config::Args {
92                locator: self.config.locator.clone(),
93                network: self.config.network.clone(),
94                source_account: config::UnresolvedMuxedAccount::default(),
95                sign_with: config::sign_with::Args::default(),
96                fee: None,
97                inclusion_fee: None,
98            })
99            .await?;
100        self.output_entries(&entries)
101    }
102
103    pub async fn execute(&self, config: &config::Args) -> Result<FullLedgerEntries, Error> {
104        let network = config.get_network()?;
105        tracing::trace!(?network);
106        let client = network.rpc_client()?;
107        let keys = self.key.parse_keys(&config.locator, &network)?;
108        Ok(client.get_full_ledger_entries(&keys).await?)
109    }
110
111    fn output_entries(&self, entries: &FullLedgerEntries) -> Result<(), Error> {
112        if entries.entries.is_empty() {
113            return Err(Error::NoContractDataEntryFoundForContractID);
114        }
115        tracing::trace!("{entries:#?}");
116        let mut out = csv::Writer::from_writer(stdout());
117        for FullLedgerEntry {
118            key,
119            val,
120            live_until_ledger_seq,
121            last_modified_ledger,
122        } in &entries.entries
123        {
124            let (
125                LedgerKey::ContractData(LedgerKeyContractData { key, .. }),
126                LedgerEntryData::ContractData(ContractDataEntry { val, .. }),
127            ) = &(key, val)
128            else {
129                return Err(Error::OnlyDataAllowed);
130            };
131            let output = match self.output {
132                Output::String => [
133                    soroban_spec_tools::to_string(key).map_err(|e| Error::CannotPrintResult {
134                        result: key.clone(),
135                        error: e,
136                    })?,
137                    soroban_spec_tools::to_string(val).map_err(|e| Error::CannotPrintResult {
138                        result: val.clone(),
139                        error: e,
140                    })?,
141                    last_modified_ledger.to_string(),
142                    live_until_ledger_seq.unwrap_or_default().to_string(),
143                ],
144                Output::Json => [
145                    serde_json::to_string_pretty(&key).map_err(|error| {
146                        Error::CannotPrintJsonResult {
147                            result: key.clone(),
148                            error,
149                        }
150                    })?,
151                    serde_json::to_string_pretty(&val).map_err(|error| {
152                        Error::CannotPrintJsonResult {
153                            result: val.clone(),
154                            error,
155                        }
156                    })?,
157                    serde_json::to_string_pretty(&last_modified_ledger).map_err(|error| {
158                        Error::CannotPrintJsonResult {
159                            result: val.clone(),
160                            error,
161                        }
162                    })?,
163                    serde_json::to_string_pretty(&live_until_ledger_seq.unwrap_or_default())
164                        .map_err(|error| Error::CannotPrintJsonResult {
165                            result: val.clone(),
166                            error,
167                        })?,
168                ],
169                Output::Xdr => [
170                    key.to_xdr_base64(Limits::none())?,
171                    val.to_xdr_base64(Limits::none())?,
172                    last_modified_ledger.to_xdr_base64(Limits::none())?,
173                    live_until_ledger_seq
174                        .unwrap_or_default()
175                        .to_xdr_base64(Limits::none())?,
176                ],
177            };
178            out.write_record(output)
179                .map_err(|e| Error::CannotPrintAsCsv { error: e })?;
180        }
181        out.flush()
182            .map_err(|e| Error::CannotPrintFlush { error: e })?;
183        Ok(())
184    }
185}