1use base64::{engine::general_purpose::STANDARD as base64, Engine as _};
2use std::{
3 fmt::Display,
4 io::{self, Cursor},
5};
6
7use stellar_xdr::curr::{
8 self as xdr, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion,
9 ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0,
10 ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, WriteXdr,
11};
12
13pub struct Spec {
14 pub env_meta_base64: Option<String>,
15 pub env_meta: Vec<ScEnvMetaEntry>,
16 pub meta_base64: Option<String>,
17 pub meta: Vec<ScMetaEntry>,
18 pub spec_base64: Option<String>,
19 pub spec: Vec<ScSpecEntry>,
20}
21
22#[derive(thiserror::Error, Debug)]
23pub enum Error {
24 #[error("reading file {filepath}: {error}")]
25 CannotReadContractFile {
26 filepath: std::path::PathBuf,
27 error: io::Error,
28 },
29 #[error("cannot parse wasm file {file}: {error}")]
30 CannotParseWasm {
31 file: std::path::PathBuf,
32 error: wasmparser::BinaryReaderError,
33 },
34 #[error("xdr processing error: {0}")]
35 Xdr(#[from] xdr::Error),
36
37 #[error(transparent)]
38 Parser(#[from] wasmparser::BinaryReaderError),
39}
40
41impl Spec {
42 pub fn new(bytes: &[u8]) -> Result<Self, Error> {
43 let mut env_meta: Option<Vec<u8>> = None;
44 let mut meta: Option<Vec<u8>> = None;
45 let mut spec: Option<Vec<u8>> = None;
46 for payload in wasmparser::Parser::new(0).parse_all(bytes) {
47 let payload = payload?;
48 if let wasmparser::Payload::CustomSection(section) = payload {
49 let out = match section.name() {
50 "contractenvmetav0" => &mut env_meta,
51 "contractmetav0" => &mut meta,
52 "contractspecv0" => &mut spec,
53 _ => continue,
54 };
55
56 if let Some(existing_data) = out {
57 let combined_data = [existing_data, section.data()].concat();
58 *out = Some(combined_data);
59 } else {
60 *out = Some(section.data().to_vec());
61 }
62 };
63 }
64
65 let mut env_meta_base64 = None;
66 let env_meta = if let Some(env_meta) = env_meta {
67 env_meta_base64 = Some(base64.encode(&env_meta));
68 let cursor = Cursor::new(env_meta);
69 let mut read = Limited::new(cursor, Limits::none());
70 ScEnvMetaEntry::read_xdr_iter(&mut read).collect::<Result<Vec<_>, xdr::Error>>()?
71 } else {
72 vec![]
73 };
74
75 let mut meta_base64 = None;
76 let meta = if let Some(meta) = meta {
77 meta_base64 = Some(base64.encode(&meta));
78 let cursor = Cursor::new(meta);
79 let mut depth_limit_read = Limited::new(cursor, Limits::none());
80 ScMetaEntry::read_xdr_iter(&mut depth_limit_read)
81 .collect::<Result<Vec<_>, xdr::Error>>()?
82 } else {
83 vec![]
84 };
85
86 let (spec_base64, spec) = if let Some(spec) = spec {
87 let (spec_base64, spec) = Spec::spec_to_base64(&spec)?;
88 (Some(spec_base64), spec)
89 } else {
90 (None, vec![])
91 };
92
93 Ok(Spec {
94 env_meta_base64,
95 env_meta,
96 meta_base64,
97 meta,
98 spec_base64,
99 spec,
100 })
101 }
102
103 pub fn spec_as_json_array(&self) -> Result<String, Error> {
104 let spec = self
105 .spec
106 .iter()
107 .map(|e| Ok(format!("\"{}\"", e.to_xdr_base64(Limits::none())?)))
108 .collect::<Result<Vec<_>, Error>>()?
109 .join(",\n");
110 Ok(format!("[{spec}]"))
111 }
112
113 pub fn spec_to_base64(spec: &[u8]) -> Result<(String, Vec<ScSpecEntry>), Error> {
114 let spec_base64 = base64.encode(spec);
115 let cursor = Cursor::new(spec);
116 let mut read = Limited::new(cursor, Limits::none());
117 Ok((
118 spec_base64,
119 ScSpecEntry::read_xdr_iter(&mut read).collect::<Result<Vec<_>, xdr::Error>>()?,
120 ))
121 }
122}
123
124impl Display for Spec {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 if let Some(env_meta) = &self.env_meta_base64 {
127 writeln!(f, "Env Meta: {env_meta}")?;
128 for env_meta_entry in &self.env_meta {
129 match env_meta_entry {
130 ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(
131 ScEnvMetaEntryInterfaceVersion {
132 protocol,
133 pre_release,
134 },
135 ) => {
136 writeln!(f, " • Protocol Version: {protocol}")?;
137 if pre_release != &0 {
138 writeln!(f, " • Pre-release Version: {pre_release})")?;
139 }
140 }
141 }
142 }
143 writeln!(f)?;
144 } else {
145 writeln!(f, "Env Meta: None\n")?;
146 }
147
148 if let Some(_meta) = &self.meta_base64 {
149 writeln!(f, "Contract Meta:")?;
150 for meta_entry in &self.meta {
151 match meta_entry {
152 ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => {
153 writeln!(f, " • {key}: {val}")?;
154 }
155 }
156 }
157 writeln!(f)?;
158 } else {
159 writeln!(f, "Contract Meta: None\n")?;
160 }
161
162 if let Some(_spec_base64) = &self.spec_base64 {
163 writeln!(f, "Contract Spec:")?;
164 for spec_entry in &self.spec {
165 match spec_entry {
166 ScSpecEntry::FunctionV0(func) => write_func(f, func)?,
167 ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?,
168 ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?,
169 ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?,
170 ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?,
171 }
172 }
173 } else {
174 writeln!(f, "Contract Spec: None")?;
175 }
176 Ok(())
177 }
178}
179
180fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result {
181 writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?;
182 if func.doc.len() > 0 {
183 writeln!(
184 f,
185 " Docs: {}",
186 &indent(&func.doc.to_utf8_string_lossy(), 11).trim()
187 )?;
188 }
189 writeln!(
190 f,
191 " Inputs: {}",
192 indent(&format!("{:#?}", func.inputs), 5).trim()
193 )?;
194 writeln!(
195 f,
196 " Output: {}",
197 indent(&format!("{:#?}", func.outputs), 5).trim()
198 )?;
199 writeln!(f)?;
200 Ok(())
201}
202
203fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result {
204 writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?;
205 if udt.doc.len() > 0 {
206 writeln!(
207 f,
208 " Docs: {}",
209 indent(&udt.doc.to_utf8_string_lossy(), 10).trim()
210 )?;
211 }
212 writeln!(f, " Cases:")?;
213 for case in udt.cases.iter() {
214 writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?;
215 }
216 writeln!(f)?;
217 Ok(())
218}
219
220fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result {
221 writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?;
222 if udt.doc.len() > 0 {
223 writeln!(
224 f,
225 " Docs: {}",
226 indent(&udt.doc.to_utf8_string_lossy(), 10).trim()
227 )?;
228 }
229 writeln!(f, " Fields:")?;
230 for field in udt.fields.iter() {
231 writeln!(
232 f,
233 " • {}: {}",
234 field.name.to_utf8_string_lossy(),
235 indent(&format!("{:#?}", field.type_), 8).trim()
236 )?;
237 if field.doc.len() > 0 {
238 writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?;
239 }
240 }
241 writeln!(f)?;
242 Ok(())
243}
244
245fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result {
246 writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?;
247 if udt.doc.len() > 0 {
248 writeln!(
249 f,
250 " Docs: {}",
251 indent(&udt.doc.to_utf8_string_lossy(), 10).trim()
252 )?;
253 }
254 writeln!(f, " Cases:")?;
255 for case in udt.cases.iter() {
256 writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?;
257 }
258 writeln!(f)?;
259 Ok(())
260}
261
262fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result {
263 writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?;
264 if udt.doc.len() > 0 {
265 writeln!(
266 f,
267 " Docs: {}",
268 indent(&udt.doc.to_utf8_string_lossy(), 10).trim()
269 )?;
270 }
271 writeln!(f, " Cases:")?;
272 for case in udt.cases.iter() {
273 writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?;
274 }
275 writeln!(f)?;
276 Ok(())
277}
278
279fn indent(s: &str, n: usize) -> String {
280 let pad = " ".repeat(n);
281 s.lines()
282 .map(|line| format!("{pad}{line}"))
283 .collect::<Vec<_>>()
284 .join("\n")
285}
286
287fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String {
288 if lib.len() > 0 {
289 format!(
290 "{}::{}",
291 lib.to_utf8_string_lossy(),
292 name.to_utf8_string_lossy()
293 )
294 } else {
295 name.to_utf8_string_lossy()
296 }
297}