odra_casper_codegen/
lib.rs

1//! Set of functions to generate Casper contract.
2
3use crate::call_method::CallMethod;
4
5use self::wasm_entrypoint::WasmEntrypoint;
6use odra_types::contract_def::ContractBlueprint;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::{quote, ToTokens};
9use syn::{punctuated::Punctuated, Path, Token};
10
11mod arg;
12mod call_method;
13mod constructor;
14mod entrypoints_def;
15mod schema;
16mod ty;
17mod wasm_entrypoint;
18
19pub use schema::gen_schema;
20
21pub fn contract_ident() -> proc_macro2::Ident {
22    proc_macro2::Ident::new("_contract", Span::call_site())
23}
24
25/// Given the ContractDef from Odra, generate Casper contract.
26pub fn gen_contract(blueprint: ContractBlueprint) -> TokenStream2 {
27    let keys = generate_storage_keys(&blueprint);
28    let entrypoints = generate_entrypoints(&blueprint);
29    let call_fn = generate_call(&blueprint);
30
31    quote! {
32        #![no_std]
33        #![no_main]
34
35        extern crate alloc;
36        #keys
37
38        #call_fn
39
40        #entrypoints
41    }
42}
43
44fn generate_storage_keys(blueprint: &ContractBlueprint) -> TokenStream2 {
45    let keys_count = blueprint.keys_count as usize;
46    let keys_literals = blueprint
47        .keys
48        .iter()
49        .map(|k| quote!(#k))
50        .collect::<Punctuated<TokenStream2, Token![,]>>();
51    quote! {
52        const KEYS: [&'static str; #keys_count] = [
53            #keys_literals
54        ];
55    }
56}
57
58fn generate_entrypoints(blueprint: &ContractBlueprint) -> TokenStream2 {
59    let path = fqn_to_path(blueprint.fqn);
60    blueprint
61        .entrypoints
62        .iter()
63        .flat_map(|ep| WasmEntrypoint(ep, &path).to_token_stream())
64        .collect::<TokenStream2>()
65}
66
67fn generate_call(blueprint: &ContractBlueprint) -> TokenStream2 {
68    let ref_fqn = blueprint.fqn.to_string() + "Ref";
69
70    CallMethod::new(
71        blueprint.events.to_vec(),
72        blueprint.entrypoints.to_vec(),
73        fqn_to_path(ref_fqn.as_str())
74    )
75    .to_token_stream()
76}
77
78fn fqn_to_path(fqn: &str) -> Path {
79    syn::parse_str(fqn).expect("Invalid fqn")
80}
81
82#[cfg(test)]
83fn assert_eq_tokens<A: ToTokens, B: ToTokens>(left: A, right: B) {
84    let left = left.to_token_stream().to_string().replace(' ', "");
85    let right = right.to_token_stream().to_string().replace(' ', "");
86    pretty_assertions::assert_str_eq!(left, right);
87}
88
89#[macro_export]
90macro_rules! gen_contract {
91    ($contract:path, $name:literal) => {
92        pub fn main() {
93            let ident = <$contract as odra::types::contract_def::HasIdent>::ident();
94            let entrypoints =
95                <$contract as odra::types::contract_def::HasEntrypoints>::entrypoints();
96            let events = <$contract as odra::types::contract_def::HasEvents>::events();
97            for event in &events {
98                if event.has_any() {
99                    panic!("Event {} can't have Type::Any struct in it.", &event.ident);
100                }
101            }
102            let keys = <$contract as odra::types::contract_def::Node>::__keys();
103            let keys_count = <$contract as odra::types::contract_def::Node>::COUNT;
104
105            let blueprint = odra::types::contract_def::ContractBlueprint {
106                keys,
107                keys_count,
108                events: events.clone(),
109                entrypoints: entrypoints.clone(),
110                fqn: stringify!($contract)
111            };
112            let code = odra::casper::codegen::gen_contract(blueprint);
113
114            let schema = odra::casper::codegen::gen_schema(&ident, &entrypoints, &events);
115
116            use std::io::prelude::*;
117            let mut source_file = std::fs::File::create(&format!("src/{}_wasm.rs", $name)).unwrap();
118            source_file
119                .write_all(&code.to_string().into_bytes())
120                .unwrap();
121
122            if !std::path::Path::new("../resources").exists() {
123                std::fs::create_dir("../resources").unwrap();
124            }
125
126            let mut schema_file =
127                std::fs::File::create(&format!("../resources/{}_schema.json", $name)).unwrap();
128            schema_file.write_all(&schema.into_bytes()).unwrap();
129        }
130    };
131}
132
133#[cfg(test)]
134mod tests {
135    use odra_types::contract_def::{Argument, ContractBlueprint, Entrypoint, EntrypointType};
136    use odra_types::CLType;
137    use quote::quote;
138
139    use super::{assert_eq_tokens, gen_contract};
140
141    #[test]
142    fn test_contract_codegen() {
143        let constructor = Entrypoint {
144            ident: String::from("construct_me"),
145            args: vec![Argument {
146                ident: String::from("value"),
147                ty: CLType::I32,
148                is_ref: true,
149                is_slice: false
150            }],
151            ret: CLType::Unit,
152            ty: EntrypointType::Constructor {
153                non_reentrant: false
154            },
155            is_mut: false
156        };
157        let entrypoint = Entrypoint {
158            ident: String::from("call_me"),
159            args: vec![],
160            ret: CLType::Bool,
161            ty: EntrypointType::Public {
162                non_reentrant: false
163            },
164            is_mut: false
165        };
166
167        let blueprint = ContractBlueprint {
168            keys: vec!["key".to_string(), "a_b_c".to_string()],
169            keys_count: 2,
170            events: vec![],
171            entrypoints: vec![constructor, entrypoint],
172            fqn: "my_contract::MyContract"
173        };
174        let result = gen_contract(blueprint);
175
176        assert_eq_tokens(
177            result,
178            quote! {
179                #![no_std]
180                #![no_main]
181
182                extern crate alloc;
183
184                const KEYS: [&'static str; 2usize] = [
185                    "key",
186                    "a_b_c"
187                ];
188
189                #[no_mangle]
190                fn call() {
191                    let schemas = alloc::vec![];
192                    let mut entry_points = odra::types::casper_types::EntryPoints::new();
193                    entry_points.add_entry_point(odra::types::casper_types::EntryPoint::new(
194                        "construct_me",
195                        alloc::vec![
196                            odra::types::casper_types::Parameter::new("value", odra::types::CLType::I32)
197                        ],
198                        odra::types::CLType::Unit,
199                        odra::types::casper_types::EntryPointAccess::Groups(alloc::vec![
200                            odra::types::casper_types::Group::new("constructor_group")
201                        ]),
202                        odra::types::casper_types::EntryPointType::Contract,
203                    ));
204                    entry_points.add_entry_point(odra::types::casper_types::EntryPoint::new(
205                        "call_me",
206                        alloc::vec![],
207                        odra::types::CLType::Bool,
208                        odra::types::casper_types::EntryPointAccess::Public,
209                        odra::types::casper_types::EntryPointType::Contract,
210                    ));
211                    #[allow(unused_variables)]
212                    let contract_package_hash = odra::casper::utils::install_contract(entry_points, schemas);
213                    use odra::casper::casper_contract::unwrap_or_revert::UnwrapOrRevert;
214                    let constructor_access = odra::casper::utils::create_constructor_group(contract_package_hash);
215                    let constructor_name = odra::casper::utils::load_constructor_name_arg();
216                    match constructor_name.as_str() {
217                        "construct_me" => {
218                            let odra_address = odra::types::Address::try_from(contract_package_hash)
219                                .map_err(|err| {
220                                    let code = odra::types::ExecutionError::from(err).code();
221                                    odra::types::casper_types::ApiError::User(code)
222                                })
223                                .unwrap_or_revert();
224                            let contract_ref = my_contract::MyContractRef::at(&odra_address);
225                            let value = odra::casper::casper_contract::contract_api::runtime::get_named_arg("value");
226                            contract_ref.construct_me(&value);
227                        },
228                        _ => odra::casper::utils::revert_on_unknown_constructor()
229                    };
230                    odra::casper::utils::revoke_access_to_constructor_group(
231                        contract_package_hash,
232                        constructor_access
233                    );
234                }
235                #[no_mangle]
236                fn construct_me() {
237                    let (_contract, _): (my_contract::MyContract, _) = odra::StaticInstance::instance(&KEYS);
238                    let value = odra::casper::casper_contract::contract_api::runtime::get_named_arg("value");
239                    _contract.construct_me(&value);
240                }
241                #[no_mangle]
242                fn call_me() {
243                    let (_contract, _): (my_contract::MyContract, _) = odra::StaticInstance::instance(&KEYS);
244                    use odra::casper::casper_contract::unwrap_or_revert::UnwrapOrRevert;
245                    let result = _contract.call_me();
246                    let result = odra::types::casper_types::CLValue::from_t(result).unwrap_or_revert();
247                    odra::casper::casper_contract::contract_api::runtime::ret(result);
248                }
249            }
250        );
251    }
252}