soroban_cli/commands/contract/
read.rs1use 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 #[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,
35 Json,
37 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}