mazzaroth_rs_derive/lib.rs
1//! # Mazzaroth Derive Library
2//!
3//! The Mazzaroth Derive Library is a rust library that defines the macros
4//! used to compile Mazzaroth Smart Contracts and generate the JSON ABI.
5//!
6//! ## How to use
7//!
8//! The first step to using this library is to include the necessary dependencies.
9//! The following 3 dependencies should be included in your Cargo.toml:
10//!
11//! mazzaroth-rs
12//! mazzaroth-rs-derive
13//! mazzaroth-xdr
14//!
15//! Every contract will have a similar base layout for the main function and the contract trait definition.
16//! `main()` is used as the entry point and has several important features. It will instantiate the contract,
17//! call a host function to retrieve function input, execute the function, and return a response.
18//!
19//! Here is a basic Hello World contract example:
20//! ```ignore
21//! // must include the ContractInterface and mazzaroth_abi for compiling the macro
22//! extern crate mazzaroth_rs;
23//! extern crate mazzaroth_rs_derive;
24//! use mazzaroth_rs::ContractInterface;
25//! use mazzaroth_rs_derive::mazzaroth_abi;
26//!
27//! // using specific external host modules
28//! use mazzaroth_rs::external::{transaction, log};
29//!
30//! #[no_mangle]
31//! pub fn main() {
32//! // panic hook is set to call the host error log function when a panic occurs
33//! std::panic::set_hook(Box::new(mazzaroth_rs::external::errors::hook));
34//!
35//! // Creates a new instance of the ABI generated around the Contract
36//! let mut contract = HelloWorld::new(Hello {});
37//!
38//! // Use a host function to get arguments
39//! let args = transaction::arguments();
40//!
41//! // Execute calls one of the functions defined in the contract
42//! // Input for the function to call and it's params comes from the Runtime
43//! let response = contract.execute(&args).unwrap();
44//!
45//! // Provide return value through host call
46//! transaction::ret(response);
47//! }
48//!
49//! // mazzaroth_abi used to generate the contract from the trait during compilation
50//! #[mazzaroth_abi(HelloWorld)]
51//! pub trait HelloWorldContract {
52//! // hello() defined as a readonly function
53//! #[readonly]
54//! fn hello(&mut self) -> u32;
55//! }
56//!
57//! // Struct used to implement the contract trait
58//! pub struct Hello {}
59//!
60//! // Actual contract implementation
61//! impl HelloWorldContract for Hello {
62//! fn hello(&mut self) -> u32 {
63//! log("Hello World!".to_string());
64//! 14
65//! }
66//! }
67//! ```
68#![recursion_limit = "256"]
69
70extern crate proc_macro;
71extern crate proc_macro2;
72#[macro_use]
73extern crate syn;
74#[macro_use]
75extern crate quote;
76
77extern crate mazzaroth_xdr;
78extern crate xdr_rs_serialize;
79
80use proc_macro::TokenStream;
81use proc_macro2::Span;
82
83mod contract;
84use contract::{Contract, TraitItem};
85
86mod error;
87use error::{ProcError, Result};
88
89mod json;
90use json::write_json_abi;
91
92/// Macro used to mark the trait that defines the mazzaroth contract
93///
94/// The argument becomes the module name used to construct the contract in main.
95///
96/// Example:
97/// ```ignore
98/// #[mazzaroth_abi(HelloWorld)]
99/// pub trait HelloWorldContract {
100///
101/// }
102/// ```
103#[proc_macro_attribute]
104pub fn mazzaroth_abi(args: TokenStream, input: TokenStream) -> TokenStream {
105 let args_toks = parse_macro_input!(args as syn::AttributeArgs);
106 let input_toks = parse_macro_input!(input as syn::Item);
107
108 let output = match impl_mazzaroth_abi(args_toks, input_toks) {
109 Ok(output) => output,
110 Err(err) => panic!("mazzaroth_abi encountered error: {}", err),
111 };
112
113 output.into()
114}
115
116fn impl_mazzaroth_abi(
117 args: syn::AttributeArgs,
118 input: syn::Item,
119) -> Result<proc_macro2::TokenStream> {
120 // Get the name for the generated Contract from the Arg
121 if args.len() == 0 || args.len() > 1 {
122 return Err(ProcError::invalid_arguments(args.len()));
123 }
124
125 // Get the contract name passed as an argument to the mazzaroth_abi macro
126 let argument_name = if let syn::NestedMeta::Meta(syn::Meta::Word(ident)) = args.get(0).unwrap()
127 {
128 Ok(ident.to_string())
129 } else {
130 Err(ProcError::malformed_argument())
131 }?;
132 let argument_ident = syn::Ident::new(&argument_name, Span::call_site());
133
134 let contract = Contract::from_item(input);
135
136 // Write out a json abi for the functions available
137 write_json_abi(&contract)?;
138
139 // Mod that is created around contract trait
140 let mod_name = format!("mazzaroth_abi_impl_{}", &contract.name().clone());
141 let mod_name_ident = syn::Ident::new(&mod_name, Span::call_site());
142
143 // Tokenize the contract which will have a single entry
144 // to call the contract functions
145 let contract_toks = tokenize_contract(&argument_name, &contract);
146
147 // Note: Imports are included in the generated module here
148 // So if types are added that can be used as function params or returns, they must be included.
149 let result = quote! {
150 #contract
151 mod #mod_name_ident {
152 extern crate mazzaroth_rs;
153 extern crate mazzaroth_xdr;
154 use super::*; // Provide access to the user contract
155 #contract_toks
156 }
157 pub use self::#mod_name_ident::#argument_ident;
158
159 };
160 Ok(result)
161}
162
163// Tokenize contract to an implementation with a callable execute function
164fn tokenize_contract(name: &str, contract: &Contract) -> proc_macro2::TokenStream {
165 // Loop through the trait items of the contract and for Functions build a
166 // quote map of function name to a function wrapper that gets arguments from encoded bytes
167 // and returns bytes. Also includes Readonly functions in contract.
168 let functions: Vec<proc_macro2::TokenStream> = contract.trait_items().iter().filter_map(|item| {
169 match *item {
170 TraitItem::Function(ref function) => {
171 let function_ident = &function.name;
172
173 // Create a matchname string literal that matches name of function
174 let match_name = syn::Lit::Str(syn::LitStr::new(&function_ident.to_string(), Span::call_site()));
175
176 let arg_types = function.arguments.iter().map(|&(_, ref ty)| quote! { #ty });
177 let arg_types2 = function.arguments.iter().map(|&(_, ref ty)| quote! { #ty });
178 let ret_type = function.ret_types.iter().map(|ref ty| quote! {#ty}).next();
179
180 if function.ret_types.is_empty() {
181 Some(quote! {
182 #match_name => {
183 inner.#function_ident(
184 #(decoder.pop::<#arg_types>(stringify!(#arg_types2))?),*
185 );
186 Ok(Vec::new())
187 }
188 })
189 } else {
190 Some(quote! {
191 #match_name => {
192 let result = inner.#function_ident(
193 #(decoder.pop::<#arg_types>(stringify!(#arg_types2))?),*
194 );
195 let mut encoder = mazzaroth_rs::Encoder::default();
196 encoder.push(result, stringify!(#ret_type));
197 Ok(encoder.values())
198 }
199 })
200 }
201 },
202 TraitItem::Readonly(ref function) => {
203 let function_ident = &function.name;
204
205 // Create a matchname string literal that matches name of function
206 let match_name = syn::Lit::Str(syn::LitStr::new(&function_ident.to_string(), Span::call_site()));
207
208 let arg_types = function.arguments.iter().map(|&(_, ref ty)| quote! { #ty });
209 let arg_types2 = function.arguments.iter().map(|&(_, ref ty)| quote! { #ty });
210 let ret_type = function.ret_types.iter().map(|ref ty| quote! {#ty}).next();
211
212 if function.ret_types.is_empty() {
213 Some(quote! {
214 #match_name => {
215 inner.#function_ident(
216 #(decoder.pop::<#arg_types>(stringify!(#arg_types2))?),*
217 );
218 Ok(Vec::new())
219 }
220 })
221 } else {
222 Some(quote! {
223 #match_name => {
224 let result = inner.#function_ident(
225 #(decoder.pop::<#arg_types>(stringify!(#arg_types2))?),*
226 );
227 let mut encoder = mazzaroth_rs::Encoder::default();
228 encoder.push(result, stringify!(#ret_type));
229 Ok(encoder.values())
230 }
231 })
232 }
233 },
234 _ => None,
235 }
236 }).collect();
237
238 let endpoint_ident = syn::Ident::new(name, Span::call_site());
239 let name_ident = syn::Ident::new(&contract.name(), Span::call_site());
240
241 quote! {
242 pub struct #endpoint_ident<T: #name_ident> {
243 pub inner: T,
244 }
245
246 impl<T: #name_ident> From<T> for #endpoint_ident<T> {
247 fn from(inner: T) -> #endpoint_ident<T> {
248 #endpoint_ident {
249 inner: inner,
250 }
251 }
252 }
253
254 impl<T: #name_ident> #endpoint_ident<T> {
255 pub fn new(inner: T) -> Self {
256 #endpoint_ident {
257 inner: inner,
258 }
259 }
260
261 pub fn instance(&self) -> &T {
262 &self.inner
263 }
264 }
265
266 impl<T: #name_ident> mazzaroth_rs::ContractInterface for #endpoint_ident<T> {
267 #[allow(unused_mut)]
268 #[allow(unused_variables)]
269 fn execute(&mut self, payload: &[u8]) -> Result<Vec<u8>, mazzaroth_rs::ContractError> {
270 let inner = &mut self.inner;
271
272 // first decode the call from stream
273 let mut payload_decoder = mazzaroth_rs::Decoder::new(payload);
274 match payload_decoder.pop::<mazzaroth_xdr::Call>() {
275 Ok(call) => {
276 // Then create a decoder for arguments
277 let mut decoder = mazzaroth_rs::InputDecoder::new(&call.arguments);
278
279 match call.function.as_str() {
280 #(#functions,)*
281 _ => Err(mazzaroth_rs::ContractError::invalid_function()),
282 }
283 },
284 _ => Err(mazzaroth_rs::ContractError::invalid_function())
285 }
286 }
287 }
288 }
289}