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::{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 #[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,
34 Json,
36 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}