1pub mod r#trait;
2pub mod types;
3
4use std::{fs, io};
5
6use proc_macro2::TokenStream;
7use quote::quote;
8use sha2::{Digest, Sha256};
9use stellar_xdr::curr as stellar_xdr;
10use stellar_xdr::ScSpecEntry;
11use syn::Error;
12
13use soroban_spec::read::{from_wasm, FromWasmError};
14
15use types::{generate_enum, generate_error_enum, generate_event, generate_struct, generate_union};
16
17#[derive(thiserror::Error, Debug)]
18pub enum GenerateFromFileError {
19 #[error("reading file: {0}")]
20 Io(io::Error),
21 #[error("sha256 does not match, expected: {expected}")]
22 VerifySha256 { expected: String },
23 #[error("parsing contract spec: {0}")]
24 Parse(stellar_xdr::Error),
25 #[error("getting contract spec: {0}")]
26 GetSpec(FromWasmError),
27}
28
29pub fn generate_from_file(
30 file: &str,
31 verify_sha256: Option<&str>,
32) -> Result<TokenStream, GenerateFromFileError> {
33 let wasm = fs::read(file).map_err(GenerateFromFileError::Io)?;
35
36 let code = generate_from_wasm(&wasm, file, verify_sha256)?;
38 Ok(code)
39}
40
41pub fn generate_from_wasm(
42 wasm: &[u8],
43 file: &str,
44 verify_sha256: Option<&str>,
45) -> Result<TokenStream, GenerateFromFileError> {
46 let sha256 = Sha256::digest(wasm);
47 let sha256 = format!("{:x}", sha256);
48 if let Some(verify_sha256) = verify_sha256 {
49 if verify_sha256 != sha256 {
50 return Err(GenerateFromFileError::VerifySha256 { expected: sha256 });
51 }
52 }
53
54 let spec = from_wasm(wasm).map_err(GenerateFromFileError::GetSpec)?;
55 let code = generate(&spec, file, &sha256);
56 Ok(code)
57}
58
59pub fn generate(specs: &[ScSpecEntry], file: &str, sha256: &str) -> TokenStream {
60 let generated = generate_without_file(specs);
61 quote! {
62 pub const WASM: &[u8] = soroban_sdk::contractfile!(file = #file, sha256 = #sha256);
63 #generated
64 }
65}
66
67pub fn generate_without_file(specs: &[ScSpecEntry]) -> TokenStream {
68 let mut spec_fns = Vec::new();
69 let mut spec_structs = Vec::new();
70 let mut spec_unions = Vec::new();
71 let mut spec_enums = Vec::new();
72 let mut spec_error_enums = Vec::new();
73 let mut spec_events = Vec::new();
74 for s in specs {
75 match s {
76 ScSpecEntry::FunctionV0(f) => spec_fns.push(f),
77 ScSpecEntry::UdtStructV0(s) => spec_structs.push(s),
78 ScSpecEntry::UdtUnionV0(u) => spec_unions.push(u),
79 ScSpecEntry::UdtEnumV0(e) => spec_enums.push(e),
80 ScSpecEntry::UdtErrorEnumV0(e) => spec_error_enums.push(e),
81 ScSpecEntry::EventV0(e) => spec_events.push(e),
82 }
83 }
84
85 let trait_name = "Contract";
86
87 let trait_ = r#trait::generate_trait(trait_name, &spec_fns);
88 let structs = spec_structs.iter().map(|s| generate_struct(s));
89 let unions = spec_unions.iter().map(|s| generate_union(s));
90 let enums = spec_enums.iter().map(|s| generate_enum(s));
91 let error_enums = spec_error_enums.iter().map(|s| generate_error_enum(s));
92 let events = spec_events.iter().map(|s| generate_event(s));
93
94 quote! {
95 #[soroban_sdk::contractargs(name = "Args")]
96 #[soroban_sdk::contractclient(name = "Client")]
97 #trait_
98
99 #(#structs)*
100 #(#unions)*
101 #(#enums)*
102 #(#error_enums)*
103 #(#events)*
104 }
105}
106
107pub trait ToFormattedString {
110 fn to_formatted_string(&self) -> Result<String, Error>;
114}
115
116impl ToFormattedString for TokenStream {
117 fn to_formatted_string(&self) -> Result<String, Error> {
118 let file = syn::parse2(self.clone())?;
119 Ok(prettyplease::unparse(&file))
120 }
121}
122
123#[cfg(test)]
124mod test {
125 use pretty_assertions::assert_eq;
126
127 use super::{generate, ToFormattedString};
128 use soroban_spec::read::from_wasm;
129
130 const EXAMPLE_WASM: &[u8] = include_bytes!("../../target/wasm32v1-none/release/test_udt.wasm");
131
132 #[test]
133 fn example() {
134 let entries = from_wasm(EXAMPLE_WASM).unwrap();
135 let rust = generate(&entries, "<file>", "<sha256>")
136 .to_formatted_string()
137 .unwrap();
138 assert_eq!(
139 rust,
140 r#"pub const WASM: &[u8] = soroban_sdk::contractfile!(file = "<file>", sha256 = "<sha256>");
141#[soroban_sdk::contractargs(name = "Args")]
142#[soroban_sdk::contractclient(name = "Client")]
143pub trait Contract {
144 fn add(env: soroban_sdk::Env, a: UdtEnum, b: UdtEnum) -> i64;
145}
146#[soroban_sdk::contracttype(export = false)]
147#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
148pub struct UdtTuple(pub i64, pub soroban_sdk::Vec<i64>);
149#[soroban_sdk::contracttype(export = false)]
150#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
151pub struct UdtStruct {
152 pub a: i64,
153 pub b: i64,
154 pub c: soroban_sdk::Vec<i64>,
155}
156#[soroban_sdk::contracttype(export = false)]
157#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
158pub enum UdtEnum {
159 UdtA,
160 UdtB(UdtStruct),
161 UdtC(UdtEnum2),
162 UdtD(UdtTuple),
163}
164#[soroban_sdk::contracttype(export = false)]
165#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
166pub enum UdtEnum2 {
167 A = 10,
168 B = 15,
169}
170"#,
171 );
172 }
173}