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