odra_casper_codegen/
lib.rs1use 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
25pub 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}