soroban_cli/commands/tx/
xdr.rs1use crate::xdr::{
2 Limits, Operation, ReadXdr, Transaction, TransactionEnvelope, TransactionV1Envelope,
3};
4use std::ffi::OsString;
5use std::fs::File;
6use std::io::{stdin, Read};
7use std::io::{Cursor, IsTerminal};
8use std::path::Path;
9use stellar_xdr::curr::Limited;
10
11#[derive(Debug, thiserror::Error)]
12pub enum Error {
13 #[error("failed to decode XDR: {0}")]
14 XDRDecode(#[from] stellar_xdr::curr::Error),
15 #[error(transparent)]
16 Io(#[from] std::io::Error),
17 #[error("only transaction v1 is supported")]
18 OnlyTransactionV1Supported,
19 #[error("too many operations, limited to 100 operations in a transaction")]
20 TooManyOperations,
21 #[error("no transaction provided")]
22 NoStdin,
23}
24
25pub fn tx_envelope_from_input(input: &Option<OsString>) -> Result<TransactionEnvelope, Error> {
26 let read: &mut dyn Read = if let Some(input) = input {
27 let exist = Path::new(input).try_exists();
28 if let Ok(true) = exist {
29 &mut File::open(input)?
30 } else {
31 &mut Cursor::new(input.clone().into_encoded_bytes())
32 }
33 } else {
34 if stdin().is_terminal() {
35 return Err(Error::NoStdin);
36 }
37 &mut stdin()
38 };
39
40 let mut lim = Limited::new(SkipWhitespace::new(read), Limits::none());
41 Ok(TransactionEnvelope::read_xdr_base64_to_end(&mut lim)?)
42}
43
44pub struct SkipWhitespace<R: Read> {
46 pub inner: R,
47}
48
49impl<R: Read> SkipWhitespace<R> {
50 pub fn new(inner: R) -> Self {
51 SkipWhitespace { inner }
52 }
53}
54
55impl<R: Read> Read for SkipWhitespace<R> {
56 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
57 loop {
58 let n = self.inner.read(buf)?;
59 if n == 0 {
60 return Ok(0);
61 }
62
63 let mut written = 0;
64 for read in 0..n {
65 if !buf[read].is_ascii_whitespace() {
66 buf[written] = buf[read];
67 written += 1;
68 }
69 }
70
71 if written > 0 {
72 return Ok(written);
73 }
74 }
75 }
76}
77
78pub fn unwrap_envelope_v1(tx_env: TransactionEnvelope) -> Result<Transaction, Error> {
79 let TransactionEnvelope::Tx(TransactionV1Envelope { tx, .. }) = tx_env else {
80 return Err(Error::OnlyTransactionV1Supported);
81 };
82 Ok(tx)
83}
84
85pub fn add_op(tx_env: TransactionEnvelope, op: Operation) -> Result<TransactionEnvelope, Error> {
86 let mut tx = unwrap_envelope_v1(tx_env)?;
87 let mut ops = tx.operations.to_vec();
88 ops.push(op);
89 tx.operations = ops.try_into().map_err(|_| Error::TooManyOperations)?;
90 Ok(tx.into())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use std::io::Cursor;
97
98 struct ChunkedReader {
99 chunks: Vec<Vec<u8>>,
100 pos: usize,
101 }
102
103 impl ChunkedReader {
104 fn new(chunks: Vec<&[u8]>) -> Self {
105 Self {
106 chunks: chunks.iter().map(|c| c.to_vec()).collect(),
107 pos: 0,
108 }
109 }
110 }
111
112 impl Read for ChunkedReader {
113 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
114 if self.pos >= self.chunks.len() {
115 return Ok(0);
116 }
117 let chunk = &self.chunks[self.pos];
118 let n = chunk.len().min(buf.len());
119 buf[..n].copy_from_slice(&chunk[..n]);
120 self.pos += 1;
121 Ok(n)
122 }
123 }
124
125 #[test]
126 fn skip_whitespace_preserves_content() {
127 let input = Cursor::new(b"helloworld");
128 let mut reader = SkipWhitespace::new(input);
129 let mut result = String::new();
130 reader.read_to_string(&mut result).unwrap();
131 assert_eq!(result, "helloworld");
132 }
133
134 #[test]
135 fn skip_whitespace_strips_all_whitespace_types() {
136 let input = Cursor::new(b"hello \t\n\r world");
137 let mut reader = SkipWhitespace::new(input);
138 let mut result = String::new();
139 reader.read_to_string(&mut result).unwrap();
140 assert_eq!(result, "helloworld");
141 }
142
143 #[test]
144 fn skip_whitespace_handles_only_whitespace() {
145 let input = Cursor::new(b"\n \t \r\n");
146 let mut reader = SkipWhitespace::new(input);
147 let mut result = String::new();
148 reader.read_to_string(&mut result).unwrap();
149 assert_eq!(result, "");
150 }
151
152 #[test]
153 fn skip_whitespace_handles_empty_input() {
154 let input = Cursor::new(b"");
155 let mut reader = SkipWhitespace::new(input);
156 let mut result = String::new();
157 reader.read_to_string(&mut result).unwrap();
158 assert_eq!(result, "");
159 }
160
161 #[test]
162 fn skip_whitespace_loops_past_whitespace_only_chunks() {
163 let reader = ChunkedReader::new(vec![b"\n\n", b"hello", b""]);
167 let mut skipper = SkipWhitespace::new(reader);
168 let mut result = String::new();
169 skipper.read_to_string(&mut result).unwrap();
170 assert_eq!(result, "hello");
171 }
172
173 #[test]
174 fn skip_whitespace_handles_leading_trailing_whitespace() {
175 let input = Cursor::new(b"\n\nhello\n\n");
176 let mut reader = SkipWhitespace::new(input);
177 let mut result = String::new();
178 reader.read_to_string(&mut result).unwrap();
179 assert_eq!(result, "hello");
180 }
181}