soroban_cli/commands/contract/
restore.rs1use std::{fmt::Debug, path::Path, str::FromStr};
2
3use crate::xdr::{
4 Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData,
5 LedgerFootprint, Limits, Memo, Operation, OperationBody, OperationMeta, Preconditions,
6 RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction,
7 TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, WriteXdr,
8};
9use clap::{command, Parser};
10use stellar_strkey::DecodeError;
11
12use crate::{
13 commands::{
14 contract::extend,
15 global,
16 txn_result::{TxnEnvelopeResult, TxnResult},
17 NetworkRunnable,
18 },
19 config::{self, data, locator, network},
20 key, rpc, wasm, Pwd,
21};
22
23#[derive(Parser, Debug, Clone)]
24#[group(skip)]
25pub struct Cmd {
26 #[command(flatten)]
27 pub key: key::Args,
28 #[arg(long)]
30 pub ledgers_to_extend: Option<u32>,
31 #[arg(long)]
33 pub ttl_ledger_only: bool,
34 #[command(flatten)]
35 pub config: config::Args,
36 #[command(flatten)]
37 pub fee: crate::fee::Args,
38}
39
40impl FromStr for Cmd {
41 type Err = clap::error::Error;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 use clap::{CommandFactory, FromArgMatches};
45 Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace()))
46 }
47}
48
49impl Pwd for Cmd {
50 fn set_pwd(&mut self, pwd: &Path) {
51 self.config.set_pwd(pwd);
52 }
53}
54
55#[derive(thiserror::Error, Debug)]
56pub enum Error {
57 #[error("parsing key {key}: {error}")]
58 CannotParseKey {
59 key: String,
60 error: soroban_spec_tools::Error,
61 },
62 #[error("parsing XDR key {key}: {error}")]
63 CannotParseXdrKey { key: String, error: XdrError },
64 #[error("cannot parse contract ID {0}: {1}")]
65 CannotParseContractId(String, DecodeError),
66 #[error(transparent)]
67 Config(#[from] config::Error),
68 #[error("either `--key` or `--key-xdr` are required")]
69 KeyIsRequired,
70 #[error("xdr processing error: {0}")]
71 Xdr(#[from] XdrError),
72 #[error("Ledger entry not found")]
73 LedgerEntryNotFound,
74 #[error(transparent)]
75 Locator(#[from] locator::Error),
76 #[error("missing operation result")]
77 MissingOperationResult,
78 #[error(transparent)]
79 Rpc(#[from] rpc::Error),
80 #[error(transparent)]
81 Wasm(#[from] wasm::Error),
82 #[error(transparent)]
83 Key(#[from] key::Error),
84 #[error(transparent)]
85 Extend(#[from] extend::Error),
86 #[error(transparent)]
87 Data(#[from] data::Error),
88 #[error(transparent)]
89 Network(#[from] network::Error),
90}
91
92impl Cmd {
93 #[allow(clippy::too_many_lines)]
94 pub async fn run(&self) -> Result<(), Error> {
95 let res = self.run_against_rpc_server(None, None).await?.to_envelope();
96 let expiration_ledger_seq = match res {
97 TxnEnvelopeResult::TxnEnvelope(tx) => {
98 println!("{}", tx.to_xdr_base64(Limits::none())?);
99 return Ok(());
100 }
101 TxnEnvelopeResult::Res(res) => res,
102 };
103 if let Some(ledgers_to_extend) = self.ledgers_to_extend {
104 extend::Cmd {
105 key: self.key.clone(),
106 ledgers_to_extend,
107 config: self.config.clone(),
108 fee: self.fee.clone(),
109 ttl_ledger_only: false,
110 }
111 .run()
112 .await?;
113 } else {
114 println!("New ttl ledger: {expiration_ledger_seq}");
115 }
116
117 Ok(())
118 }
119}
120
121#[async_trait::async_trait]
122impl NetworkRunnable for Cmd {
123 type Error = Error;
124 type Result = TxnResult<u32>;
125
126 async fn run_against_rpc_server(
127 &self,
128 args: Option<&global::Args>,
129 config: Option<&config::Args>,
130 ) -> Result<TxnResult<u32>, Error> {
131 let config = config.unwrap_or(&self.config);
132 let print = crate::print::Print::new(args.map_or(true, |a| a.quiet));
133 let network = config.get_network()?;
134 tracing::trace!(?network);
135 let entry_keys = self.key.parse_keys(&config.locator, &network)?;
136 let client = network.rpc_client()?;
137 let source_account = config.source_account().await?;
138
139 let account_details = client
141 .get_account(&source_account.clone().to_string())
142 .await?;
143 let sequence: i64 = account_details.seq_num.into();
144
145 let tx = Box::new(Transaction {
146 source_account,
147 fee: self.fee.fee,
148 seq_num: SequenceNumber(sequence + 1),
149 cond: Preconditions::None,
150 memo: Memo::None,
151 operations: vec![Operation {
152 source_account: None,
153 body: OperationBody::RestoreFootprint(RestoreFootprintOp {
154 ext: ExtensionPoint::V0,
155 }),
156 }]
157 .try_into()?,
158 ext: TransactionExt::V1(SorobanTransactionData {
159 ext: ExtensionPoint::V0,
160 resources: SorobanResources {
161 footprint: LedgerFootprint {
162 read_only: vec![].try_into()?,
163 read_write: entry_keys.try_into()?,
164 },
165 instructions: self.fee.instructions.unwrap_or_default(),
166 read_bytes: 0,
167 write_bytes: 0,
168 },
169 resource_fee: 0,
170 }),
171 });
172 if self.fee.build_only {
173 return Ok(TxnResult::Txn(tx));
174 }
175 let res = client
176 .send_transaction_polling(&config.sign_with_local_key(*tx).await?)
177 .await?;
178 if args.map_or(true, |a| !a.no_cache) {
179 data::write(res.clone().try_into()?, &network.rpc_uri()?)?;
180 }
181 let meta = res
182 .result_meta
183 .as_ref()
184 .ok_or(Error::MissingOperationResult)?;
185 let events = res.events()?;
186 tracing::trace!(?meta);
187 if !events.is_empty() {
188 crate::log::event::all(&events);
189 crate::log::event::contract(&events, &print);
190 }
191
192 let TransactionMeta::V3(TransactionMetaV3 { operations, .. }) = meta else {
195 return Err(Error::LedgerEntryNotFound);
196 };
197 tracing::debug!("Operations:\nlen:{}\n{operations:#?}", operations.len());
198
199 if operations.len() == 0 {
202 return Err(Error::LedgerEntryNotFound);
203 }
204
205 if operations.len() != 1 {
206 tracing::warn!(
207 "Unexpected number of operations: {}. Currently only handle one.",
208 operations[0].changes.len()
209 );
210 }
211 Ok(TxnResult::Res(
212 parse_operations(&operations.to_vec()).ok_or(Error::MissingOperationResult)?,
213 ))
214 }
215}
216
217fn parse_operations(ops: &[OperationMeta]) -> Option<u32> {
218 ops.first().and_then(|op| {
219 op.changes.iter().find_map(|entry| match entry {
220 LedgerEntryChange::Updated(LedgerEntry {
221 data:
222 LedgerEntryData::Ttl(TtlEntry {
223 live_until_ledger_seq,
224 ..
225 }),
226 ..
227 })
228 | LedgerEntryChange::Created(LedgerEntry {
229 data:
230 LedgerEntryData::Ttl(TtlEntry {
231 live_until_ledger_seq,
232 ..
233 }),
234 ..
235 }) => Some(*live_until_ledger_seq),
236 _ => None,
237 })
238 })
239}