From 24ea0ed10052d5feda614e3f3267023a2f5e883e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 24 Nov 2023 14:08:32 +0100 Subject: [PATCH 01/12] Add WasmPartsModuleItem --- odra-macros/src/ast/mod.rs | 1 + odra-macros/src/ast/wasm_parts.rs | 62 +++++++++++++++++++++++++++++++ odra-macros/src/ir/mod.rs | 6 +++ odra-macros/src/utils/attr.rs | 12 ++++++ 4 files changed, 81 insertions(+) create mode 100644 odra-macros/src/ast/wasm_parts.rs diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index bff0073c..d6b7d2a0 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -6,6 +6,7 @@ mod parts_utils; mod ref_item; mod ref_utils; mod test_parts; +mod wasm_parts; pub(crate) use module_item::ModuleModItem; pub(crate) use ref_item::RefItem; diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs new file mode 100644 index 00000000..e0b7861d --- /dev/null +++ b/odra-macros/src/ast/wasm_parts.rs @@ -0,0 +1,62 @@ +use quote::{quote, ToTokens, TokenStreamExt}; + +use crate::{ir::ModuleIR, utils}; + +use super::parts_utils::{UseSuperItem, UsePreludeItem}; + +#[derive(syn_derive::ToTokens)] +pub struct WasmPartsModuleItem { + #[to_tokens(|tokens, f| tokens.append_all(f))] + attrs: Vec, + mod_token: syn::token::Mod, + ident: syn::Ident, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + use_super: UseSuperItem, + #[syn(in = braces)] + use_prelude: UsePreludeItem, +} + +impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleIR) -> Result { + let module_str = module.module_str()?; + let ident = module.wasm_parts_mod_ident()?; + Ok(Self { + attrs: vec![utils::attr::wasm32(), utils::attr::odra_module(&module_str)], + mod_token: Default::default(), + ident, + braces: Default::default(), + use_super: UseSuperItem, + use_prelude: UsePreludeItem, + + }) + } +} + + + +#[cfg(test)] +mod test { + use crate::test_utils; + use super::WasmPartsModuleItem; + + #[test] + fn test() { + let module = test_utils::mock_module(); + let actual = WasmPartsModuleItem::try_from(&module).unwrap(); + + let expected = quote::quote! { + #[cfg(target_arch = "wasm32")] + #[cfg(odra_module = "Erc20")] + mod __erc20_wasm_parts { + use super::*; + use odra::prelude::*; + } + }; + + test_utils::assert_eq(actual, expected); + } +} \ No newline at end of file diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 58236c8c..f9ba4b41 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -149,6 +149,12 @@ impl ModuleIR { )) } + pub fn wasm_parts_mod_ident(&self) -> Result { + self.module_ident() + .map(crate::utils::string::camel_to_snake) + .map(|ident| format_ident!("__{}_wasm_parts", ident)) + } + pub fn functions(&self) -> Vec { self.code .items diff --git a/odra-macros/src/utils/attr.rs b/odra-macros/src/utils/attr.rs index e1ea9065..80d5dbda 100644 --- a/odra-macros/src/utils/attr.rs +++ b/odra-macros/src/utils/attr.rs @@ -3,3 +3,15 @@ use syn::parse_quote; pub fn not_wasm32() -> syn::Attribute { parse_quote!(#[cfg(not(target_arch = "wasm32"))]) } + +pub fn wasm32() -> syn::Attribute { + parse_quote!(#[cfg(target_arch = "wasm32")]) +} + +pub fn odra_module(name: &str) -> syn::Attribute { + parse_quote!(#[cfg(odra_module = #name)]) +} + +pub fn no_mangle() -> syn::Attribute { + parse_quote!(#[no_mangle]) +} \ No newline at end of file From 92b54890fe546153262e223b7994bbade28fd7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 28 Nov 2023 11:33:09 +0100 Subject: [PATCH 02/12] generate entry_points() fn --- odra-macros/src/ast/wasm_parts.rs | 161 +++++++++++++++++++++++++++++- odra-macros/src/ir/mod.rs | 15 +++ odra-macros/src/utils/expr.rs | 47 +++++++++ odra-macros/src/utils/ident.rs | 12 +++ odra-macros/src/utils/ty.rs | 28 ++++++ 5 files changed, 260 insertions(+), 3 deletions(-) diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index e0b7861d..3ac6978e 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -1,6 +1,7 @@ -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::TokenStreamExt; +use syn::parse_quote; -use crate::{ir::ModuleIR, utils}; +use crate::{ir::{ModuleIR, FnIR}, utils}; use super::parts_utils::{UseSuperItem, UsePreludeItem}; @@ -16,6 +17,8 @@ pub struct WasmPartsModuleItem { use_super: UseSuperItem, #[syn(in = braces)] use_prelude: UsePreludeItem, + #[syn(in = braces)] + entry_points_fn: EntryPointsFnItem } impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { @@ -31,12 +34,141 @@ impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { braces: Default::default(), use_super: UseSuperItem, use_prelude: UsePreludeItem, + entry_points_fn: module.try_into()? + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct EntryPointsFnItem { + sig: syn::Signature, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + var_declaration: syn::Stmt, + #[syn(in = braces)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + items: Vec, + #[syn(in = braces)] + ret: syn::Expr +} + +impl TryFrom<&'_ ModuleIR> for EntryPointsFnItem { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleIR) -> Result { + let ty_entry_points = utils::ty::entry_points(); + let ident_entry_points = utils::ident::entry_points(); + let expr_entry_points = utils::expr::new_entry_points(); + + Ok(Self { + sig: parse_quote!(fn entry_points() -> #ty_entry_points), + braces: Default::default(), + var_declaration: parse_quote!(let mut #ident_entry_points = #expr_entry_points;), + items: module.functions().iter().map(TryInto::try_into).collect::, _>>()?, + ret: parse_quote!(#ident_entry_points), + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct AddEntryPointStmtItem { + var_ident: syn::Ident, + dot_token: syn::token::Dot, + fn_ident: syn::Ident, + #[syn(parenthesized)] + paren: syn::token::Paren, + #[syn(in = paren)] + new_entry_point_expr: NewEntryPointItem, + semi_token: syn::token::Semi +} + +impl TryFrom<&'_ FnIR> for AddEntryPointStmtItem { + type Error = syn::Error; + fn try_from(func: &'_ FnIR) -> Result { + let var_ident = utils::ident::entry_points(); + let fn_ident = utils::ident::add_entry_point(); + Ok(Self { + var_ident, + dot_token: Default::default(), + fn_ident, + paren: Default::default(), + new_entry_point_expr: func.try_into()?, + semi_token: Default::default(), }) } } +#[derive(syn_derive::ToTokens)] +struct NewEntryPointItem { + ty: syn::Type, + colon_colon_token: syn::token::PathSep, + new_ident: syn::Ident, + #[syn(parenthesized)] + paren: syn::token::Paren, + #[syn(in = paren)] + params: syn::punctuated::Punctuated +} + +impl TryFrom<&'_ FnIR> for NewEntryPointItem { + type Error = syn::Error; + + fn try_from(func: &'_ FnIR) -> Result { + let func_name = func.name_str(); + let param_name = parse_quote!(#func_name); + let param_parameters = Self::param_parameters(func); + let param_ret_ty = Self::param_ret_ty(func); + let param_access = Self::param_access(func); + + let mut params = syn::punctuated::Punctuated::new(); + params.extend(vec![ + param_name, + param_parameters, + param_ret_ty, + param_access, + utils::expr::entry_point_contract(), + ]); + Ok(Self { + ty: utils::ty::entry_point(), + colon_colon_token: Default::default(), + new_ident: utils::ident::new(), + paren: Default::default(), + params + }) + } +} + + +impl NewEntryPointItem { + fn param_parameters(func: &FnIR) -> syn::Expr { + let params = func.named_args() + .iter() + .map(|arg| arg.name_and_ty()) + .filter_map(|result| match result { + Ok(data) => Some(data), + Err(_) => None, + }) + .map(|(name, ty)| utils::expr::new_parameter(name, ty)) + .collect::>(); + parse_quote!(vec![#(#params),*]) + } + + fn param_access(func: &FnIR) -> syn::Expr { + match func.is_constructor() { + true => utils::expr::entry_point_group("constructor_group"), + false => utils::expr::entry_point_public() + } + } + + fn param_ret_ty(func: &FnIR) -> syn::Expr { + match func.return_type() { + syn::ReturnType::Default => utils::expr::unit_cl_type(), + syn::ReturnType::Type(_, ty) => utils::expr::as_cl_type(&ty), + } + } +} #[cfg(test)] mod test { @@ -54,9 +186,32 @@ mod test { mod __erc20_wasm_parts { use super::*; use odra::prelude::*; + + fn entry_points() -> odra::casper_types::EntryPoints { + let mut entry_points = odra::casper_types::EntryPoints::new(); + + entry_points.add_entry_point(odra::casper_types::EntryPoint::new( + "init", + vec![odra::casper_types::Parameter::new( + "total_supply", + as odra::casper_types::CLTyped>::cl_type() + )], + <() as odra::casper_types::CLTyped>::cl_type(), + odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new("constructor_group")]), + odra::casper_types::EntryPointType::Contract + )); + entry_points.add_entry_point(odra::casper_types::EntryPoint::new( + "total_supply", + vec![], + ::cl_type(), + odra::casper_types::EntryPointAccess::Public, + odra::casper_types::EntryPointType::Contract + )); + entry_points + } } }; - + test_utils::assert_eq(actual, expected); } } \ No newline at end of file diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index f9ba4b41..ecd4b641 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -248,6 +248,11 @@ impl FnIR { let receiver = utils::syn::receiver_arg(&self.code); receiver.map(|r| r.mutability.is_some()).unwrap_or_default() } + + pub fn is_constructor(&self) -> bool { + self.name_str() == CONSTRUCTOR_NAME + } + } pub struct FnArgIR { @@ -276,4 +281,14 @@ impl FnArgIR { pub fn name_str(&self) -> Result { self.name().map(|i| i.to_string()) } + + pub fn name_and_ty(&self) -> Result<(String, syn::Type), syn::Error> { + match &self.code { + syn::FnArg::Typed(syn::PatType { + box ty, + pat: box syn::Pat::Ident(pat), .. + }) => Ok((pat.ident.to_string(), ty.clone())), + _ => Err(syn::Error::new_spanned(&self.code, "Unnamed arg")) + } + } } diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index 00315c8c..bcf33b6d 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -20,3 +20,50 @@ pub fn new_type(ty: &syn::Type, env_ident: &syn::Ident, idx: u8) -> syn::Expr { fn rc_clone(ident: &syn::Ident) -> syn::Expr { parse_quote!(Rc::clone(&#ident)) } + +pub fn new_entry_points() -> syn::Expr { + parse_quote!(odra::casper_types::EntryPoints::new()) +} + +pub fn entry_point_contract() -> syn::Expr { + parse_quote!(odra::casper_types::EntryPointType::Contract) +} + +pub fn entry_point_public() -> syn::Expr { + parse_quote!(odra::casper_types::EntryPointAccess::Public) +} + +pub fn entry_point_group(name: &str) -> syn::Expr { + parse_quote!(odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new(#name)])) +} + +pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { + let cl_type = as_cl_type(&ty); + parse_quote!(odra::casper_types::Parameter::new(#name, #cl_type)) +} + +pub fn as_cl_type(ty: &syn::Type) -> syn::Expr { + let ty = match ty { + syn::Type::Path(type_path) => { + let mut segments: syn::punctuated::Punctuated = type_path.path.segments.clone(); + // the syntax as odra::casper_types::CLTyped>::cl_type() is invalid + // it should be as odra::casper_types::CLTyped>::cl_type() + segments + .first_mut() + .map(|ps| if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments { + let generic_arg: syn::AngleBracketedGenericArguments = parse_quote!(::#ab); + ps.arguments = syn::PathArguments::AngleBracketed(generic_arg); + }); + syn::Type::Path(syn::TypePath { + path: syn::Path { leading_colon: None, segments }, + ..type_path.clone() + }) + }, + _ => ty.clone(), + }; + parse_quote!(<#ty as odra::casper_types::CLTyped>::cl_type()) +} + +pub fn unit_cl_type() -> syn::Expr { + parse_quote!(<() as odra::casper_types::CLTyped>::cl_type()) +} \ No newline at end of file diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index 8c2cbb33..323c3488 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -35,3 +35,15 @@ pub fn address() -> syn::Ident { pub fn attached_value() -> syn::Ident { format_ident!("attached_value") } + +pub fn entry_points() -> syn::Ident { + format_ident!("entry_points") +} + +pub fn add_entry_point() -> syn::Ident { + format_ident!("add_entry_point") +} + +pub fn new() -> syn::Ident { + format_ident!("new") +} \ No newline at end of file diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index a6fdf1a6..6b8b6c99 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -59,3 +59,31 @@ pub fn variable() -> syn::Type { pub fn mapping() -> syn::Type { parse_quote!(odra::Mapping) } + +pub fn entry_points() -> syn::Type { + parse_quote!(odra::casper_types::EntryPoints) +} + +pub fn entry_point() -> syn::Type { + parse_quote!(odra::casper_types::EntryPoint) +} + +pub fn entry_point_access() -> syn::Type { + parse_quote!(odra::casper_types::EntryPointAccess) +} + +pub fn entry_point_type() -> syn::Type { + parse_quote!(odra::casper_types::EntryPointType) +} + +pub fn parameter() -> syn::Type { + parse_quote!(odra::casper_types::Parameter) +} + +pub fn group() -> syn::Type { + parse_quote!(odra::casper_types::Group) +} + +pub fn cl_type() -> syn::Type { + parse_quote!(odra::casper_types::CLType) +} From 172c887f30cb7c97ea75b85a1d072673197fb46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 28 Nov 2023 13:18:45 +0100 Subject: [PATCH 03/12] add call() fn implementation --- odra-macros/src/ast/deployer_utils.rs | 4 +- odra-macros/src/ast/fn_utils.rs | 26 ++++ odra-macros/src/ast/mod.rs | 2 + odra-macros/src/ast/ref_utils.rs | 45 +++---- odra-macros/src/ast/wasm_parts.rs | 152 ++++++++++++++++-------- odra-macros/src/ast/wasm_parts_utils.rs | 40 +++++++ odra-macros/src/utils/expr.rs | 40 +++++-- odra-macros/src/utils/ident.rs | 6 +- odra-macros/src/utils/ty.rs | 4 + 9 files changed, 223 insertions(+), 96 deletions(-) create mode 100644 odra-macros/src/ast/fn_utils.rs create mode 100644 odra-macros/src/ast/wasm_parts_utils.rs diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index 6bf278aa..0570c169 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -1,4 +1,4 @@ -use super::ref_utils; +use super::{fn_utils, ref_utils}; use crate::{ ir::{FnArgIR, FnIR, ModuleIR}, utils @@ -104,7 +104,7 @@ impl TryFrom<&'_ ModuleIR> for NewContractExpr { let env_ident = utils::ident::env(); let args = module .constructor() - .map(|f| ref_utils::runtime_args_block(&f)) + .map(|f| fn_utils::runtime_args_block(&f, ref_utils::insert_arg_stmt)) .unwrap_or({ let args = utils::expr::new_runtime_args(); parse_quote!({#args}) diff --git a/odra-macros/src/ast/fn_utils.rs b/odra-macros/src/ast/fn_utils.rs new file mode 100644 index 00000000..90f0ea3b --- /dev/null +++ b/odra-macros/src/ast/fn_utils.rs @@ -0,0 +1,26 @@ +use crate::{ir::FnIR, utils}; + +pub fn runtime_args_block syn::Stmt>( + fun: &FnIR, + insert_arg_fn: F +) -> syn::Block { + let runtime_args = utils::expr::new_runtime_args(); + let args = utils::ident::named_args(); + let insert_args = insert_args_stmts(fun, insert_arg_fn); + + syn::parse_quote!({ + let mut #args = #runtime_args; + #(#insert_args)* + #args + }) +} + +pub fn insert_args_stmts syn::Stmt>( + fun: &FnIR, + insert_arg_fn: F +) -> Vec { + fun.arg_names() + .iter() + .map(insert_arg_fn) + .collect::>() +} diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index d6b7d2a0..c06df045 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -1,5 +1,6 @@ mod deployer_item; mod deployer_utils; +mod fn_utils; mod host_ref_item; mod module_item; mod parts_utils; @@ -7,6 +8,7 @@ mod ref_item; mod ref_utils; mod test_parts; mod wasm_parts; +mod wasm_parts_utils; pub(crate) use module_item::ModuleModItem; pub(crate) use ref_item::RefItem; diff --git a/odra-macros/src/ast/ref_utils.rs b/odra-macros/src/ast/ref_utils.rs index 9dc0747f..40cba30c 100644 --- a/odra-macros/src/ast/ref_utils.rs +++ b/odra-macros/src/ast/ref_utils.rs @@ -1,4 +1,5 @@ use crate::{ + ast::fn_utils, ir::FnIR, utils::{self, syn::visibility_pub} }; @@ -47,14 +48,14 @@ fn env_call(sig: syn::Signature, call_def_expr: syn::Expr) -> syn::ItemFn { fn call_def(fun: &FnIR) -> syn::Expr { let ty_call_def = utils::ty::call_def(); let fun_name_str = fun.name_str(); - let args_block = runtime_args_block(fun); + let args_block = fn_utils::runtime_args_block(fun, insert_arg_stmt); syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #args_block)) } fn call_def_with_amount(fun: &FnIR) -> syn::Expr { let ty_call_def = utils::ty::call_def(); let fun_name_str = fun.name_str(); - let args_block = runtime_args_with_amount_block(fun); + let args_block = runtime_args_with_amount_block(fun, insert_arg_stmt); let attached_value = utils::member::attached_value(); syn::parse_quote!(#ty_call_def::new(String::from(#fun_name_str), #args_block).with_amount(#attached_value)) @@ -78,23 +79,14 @@ fn try_function_signature(fun: &FnIR) -> syn::Signature { syn::parse_quote!(fn #fun_name(& #mutability self #(, #args)*) #return_type) } -pub fn runtime_args_block(fun: &FnIR) -> syn::Block { - let runtime_args = utils::expr::new_runtime_args(); - let args = utils::ident::named_args(); - let insert_args = insert_args_stmts(fun); - - syn::parse_quote!({ - let mut #args = #runtime_args; - #(#insert_args)* - #args - }) -} - -pub fn runtime_args_with_amount_block(fun: &FnIR) -> syn::Block { +fn runtime_args_with_amount_block syn::Stmt>( + fun: &FnIR, + insert_arg_fn: F +) -> syn::Block { let runtime_args = utils::expr::new_runtime_args(); let args = utils::ident::named_args(); let insert_amount = insert_amount_arg_stmt(); - let insert_args = insert_args_stmts(fun); + let insert_args = fn_utils::insert_args_stmts(fun, insert_arg_fn); syn::parse_quote!({ let mut #args = #runtime_args; @@ -104,20 +96,6 @@ pub fn runtime_args_with_amount_block(fun: &FnIR) -> syn::Block { }) } -fn insert_args_stmts(fun: &FnIR) -> Vec { - fun.arg_names() - .iter() - .map(insert_arg_stmt) - .collect::>() -} - -fn insert_arg_stmt(ident: &syn::Ident) -> syn::Stmt { - let name = ident.to_string(); - let args = utils::ident::named_args(); - - syn::parse_quote!(let _ = #args.insert(#name, #ident);) -} - fn insert_amount_arg_stmt() -> syn::Stmt { let ident = utils::ident::named_args(); let zero = utils::expr::u512_zero(); @@ -129,3 +107,10 @@ fn insert_amount_arg_stmt() -> syn::Stmt { } ) } + +pub fn insert_arg_stmt(ident: &syn::Ident) -> syn::Stmt { + let name = ident.to_string(); + let args = utils::ident::named_args(); + + syn::parse_quote!(let _ = #args.insert(#name, #ident);) +} diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index 3ac6978e..efbe5852 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -1,9 +1,16 @@ use quote::TokenStreamExt; use syn::parse_quote; -use crate::{ir::{ModuleIR, FnIR}, utils}; +use crate::{ + ast::fn_utils, + ir::{FnIR, ModuleIR}, + utils +}; -use super::parts_utils::{UseSuperItem, UsePreludeItem}; +use super::{ + parts_utils::{UsePreludeItem, UseSuperItem}, + wasm_parts_utils +}; #[derive(syn_derive::ToTokens)] pub struct WasmPartsModuleItem { @@ -18,7 +25,9 @@ pub struct WasmPartsModuleItem { #[syn(in = braces)] use_prelude: UsePreludeItem, #[syn(in = braces)] - entry_points_fn: EntryPointsFnItem + entry_points_fn: EntryPointsFnItem, + #[syn(in = braces)] + call_fn: CallFnItem } impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { @@ -26,15 +35,16 @@ impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { fn try_from(module: &'_ ModuleIR) -> Result { let module_str = module.module_str()?; - let ident = module.wasm_parts_mod_ident()?; - Ok(Self { - attrs: vec![utils::attr::wasm32(), utils::attr::odra_module(&module_str)], - mod_token: Default::default(), - ident, + let ident = module.wasm_parts_mod_ident()?; + Ok(Self { + attrs: vec![utils::attr::wasm32(), utils::attr::odra_module(&module_str)], + mod_token: Default::default(), + ident, braces: Default::default(), use_super: UseSuperItem, use_prelude: UsePreludeItem, - entry_points_fn: module.try_into()? + entry_points_fn: module.try_into()?, + call_fn: module.try_into()? }) } } @@ -62,11 +72,62 @@ impl TryFrom<&'_ ModuleIR> for EntryPointsFnItem { let expr_entry_points = utils::expr::new_entry_points(); Ok(Self { - sig: parse_quote!(fn entry_points() -> #ty_entry_points), - braces: Default::default(), + sig: parse_quote!(fn #ident_entry_points() -> #ty_entry_points), + braces: Default::default(), var_declaration: parse_quote!(let mut #ident_entry_points = #expr_entry_points;), - items: module.functions().iter().map(TryInto::try_into).collect::, _>>()?, - ret: parse_quote!(#ident_entry_points), + items: module + .functions() + .iter() + .map(TryInto::try_into) + .collect::, _>>()?, + ret: parse_quote!(#ident_entry_points) + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct CallFnItem { + attr: syn::Attribute, + sig: syn::Signature, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + schemas_init_stmt: syn::Stmt, + #[syn(in = braces)] + runtime_args_stmt: syn::Stmt, + #[syn(in = braces)] + install_contract_stmt: syn::Stmt +} + +impl TryFrom<&'_ ModuleIR> for CallFnItem { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleIR) -> Result { + let ident_args = utils::ident::named_args(); + let ident_schemas = utils::ident::schemas(); + let ty_args = utils::ty::runtime_args(); + let ident_entry_points = utils::ident::entry_points(); + let runtime_args_expr: syn::Expr = match module.constructor() { + Some(f) => { + let arg_block = fn_utils::runtime_args_block(&f, wasm_parts_utils::insert_arg_stmt); + parse_quote!(let #ident_args = Some(#arg_block)) + } + None => parse_quote!(let #ident_args = Option::<#ty_args>::None) + }; + let expr_new_schemas = utils::expr::new_schemas(); + let expr_install = utils::expr::install_contract( + parse_quote!(#ident_entry_points()), + parse_quote!(#ident_schemas), + parse_quote!(#ident_args) + ); + + Ok(Self { + attr: utils::attr::no_mangle(), + sig: parse_quote!(fn call()), + braces: Default::default(), + schemas_init_stmt: parse_quote!(let #ident_schemas = #expr_new_schemas;), + runtime_args_stmt: parse_quote!(#runtime_args_expr;), + install_contract_stmt: parse_quote!(#expr_install;) }) } } @@ -95,7 +156,7 @@ impl TryFrom<&'_ FnIR> for AddEntryPointStmtItem { fn_ident, paren: Default::default(), new_entry_point_expr: func.try_into()?, - semi_token: Default::default(), + semi_token: Default::default() }) } } @@ -117,9 +178,9 @@ impl TryFrom<&'_ FnIR> for NewEntryPointItem { fn try_from(func: &'_ FnIR) -> Result { let func_name = func.name_str(); let param_name = parse_quote!(#func_name); - let param_parameters = Self::param_parameters(func); - let param_ret_ty = Self::param_ret_ty(func); - let param_access = Self::param_access(func); + let param_parameters = wasm_parts_utils::param_parameters(func); + let param_ret_ty = wasm_parts_utils::param_ret_ty(func); + let param_access = wasm_parts_utils::param_access(func); let mut params = syn::punctuated::Punctuated::new(); params.extend(vec![ @@ -137,43 +198,12 @@ impl TryFrom<&'_ FnIR> for NewEntryPointItem { params }) } - -} - - -impl NewEntryPointItem { - fn param_parameters(func: &FnIR) -> syn::Expr { - let params = func.named_args() - .iter() - .map(|arg| arg.name_and_ty()) - .filter_map(|result| match result { - Ok(data) => Some(data), - Err(_) => None, - }) - .map(|(name, ty)| utils::expr::new_parameter(name, ty)) - .collect::>(); - parse_quote!(vec![#(#params),*]) - } - - fn param_access(func: &FnIR) -> syn::Expr { - match func.is_constructor() { - true => utils::expr::entry_point_group("constructor_group"), - false => utils::expr::entry_point_public() - } - } - - fn param_ret_ty(func: &FnIR) -> syn::Expr { - match func.return_type() { - syn::ReturnType::Default => utils::expr::unit_cl_type(), - syn::ReturnType::Type(_, ty) => utils::expr::as_cl_type(&ty), - } - } } #[cfg(test)] mod test { - use crate::test_utils; use super::WasmPartsModuleItem; + use crate::test_utils; #[test] fn test() { @@ -193,7 +223,7 @@ mod test { entry_points.add_entry_point(odra::casper_types::EntryPoint::new( "init", vec![odra::casper_types::Parameter::new( - "total_supply", + "total_supply", as odra::casper_types::CLTyped>::cl_type() )], <() as odra::casper_types::CLTyped>::cl_type(), @@ -209,9 +239,27 @@ mod test { )); entry_points } + + #[no_mangle] + fn call() { + let schemas = odra::casper_event_standard::Schemas::new(); + let named_args = Some({ + let mut named_args = odra::RuntimeArgs::new(); + let _ = named_args.insert( + "total_supply", + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg("total_supply") + ); + named_args + }); + odra::odra_casper_wasm_env::host_functions::install_contract( + entry_points(), + schemas, + named_args + ); + } } }; - + test_utils::assert_eq(actual, expected); } -} \ No newline at end of file +} diff --git a/odra-macros/src/ast/wasm_parts_utils.rs b/odra-macros/src/ast/wasm_parts_utils.rs new file mode 100644 index 00000000..89759ce0 --- /dev/null +++ b/odra-macros/src/ast/wasm_parts_utils.rs @@ -0,0 +1,40 @@ +use crate::{ir::FnIR, utils}; +use syn::parse_quote; + +pub fn param_parameters(func: &FnIR) -> syn::Expr { + let params = func + .named_args() + .iter() + .map(|arg| arg.name_and_ty()) + .filter_map(|result| match result { + Ok(data) => Some(data), + Err(_) => None + }) + .map(|(name, ty)| utils::expr::new_parameter(name, ty)) + .collect::>(); + parse_quote!(vec![#(#params),*]) +} + +pub fn param_access(func: &FnIR) -> syn::Expr { + match func.is_constructor() { + true => utils::expr::entry_point_group("constructor_group"), + false => utils::expr::entry_point_public() + } +} + +pub fn param_ret_ty(func: &FnIR) -> syn::Expr { + match func.return_type() { + syn::ReturnType::Default => utils::expr::unit_cl_type(), + syn::ReturnType::Type(_, ty) => utils::expr::as_cl_type(&ty) + } +} + +pub fn insert_arg_stmt(ident: &syn::Ident) -> syn::Stmt { + let name = ident.to_string(); + let args = utils::ident::named_args(); + + syn::parse_quote!(let _ = #args.insert( + #name, + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg(#name) + );) +} diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index bcf33b6d..9ba58808 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -34,7 +34,9 @@ pub fn entry_point_public() -> syn::Expr { } pub fn entry_point_group(name: &str) -> syn::Expr { - parse_quote!(odra::casper_types::EntryPointAccess::Groups(vec![odra::casper_types::Group::new(#name)])) + parse_quote!(odra::casper_types::EntryPointAccess::Groups( + vec![odra::casper_types::Group::new(#name)] + )) } pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { @@ -45,25 +47,41 @@ pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { pub fn as_cl_type(ty: &syn::Type) -> syn::Expr { let ty = match ty { syn::Type::Path(type_path) => { - let mut segments: syn::punctuated::Punctuated = type_path.path.segments.clone(); + let mut segments: syn::punctuated::Punctuated = + type_path.path.segments.clone(); // the syntax as odra::casper_types::CLTyped>::cl_type() is invalid // it should be as odra::casper_types::CLTyped>::cl_type() - segments - .first_mut() - .map(|ps| if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments { + segments.first_mut().map(|ps| { + if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments { let generic_arg: syn::AngleBracketedGenericArguments = parse_quote!(::#ab); ps.arguments = syn::PathArguments::AngleBracketed(generic_arg); - }); - syn::Type::Path(syn::TypePath { - path: syn::Path { leading_colon: None, segments }, + } + }); + syn::Type::Path(syn::TypePath { + path: syn::Path { + leading_colon: None, + segments + }, ..type_path.clone() }) - }, - _ => ty.clone(), + } + _ => ty.clone() }; parse_quote!(<#ty as odra::casper_types::CLTyped>::cl_type()) } pub fn unit_cl_type() -> syn::Expr { parse_quote!(<() as odra::casper_types::CLTyped>::cl_type()) -} \ No newline at end of file +} + +pub fn new_schemas() -> syn::Expr { + parse_quote!(odra::casper_event_standard::Schemas::new()) +} + +pub fn install_contract(entry_points: syn::Expr, schemas: syn::Expr, args: syn::Expr) -> syn::Expr { + parse_quote!(odra::odra_casper_wasm_env::host_functions::install_contract( + #entry_points, + #schemas, + #args + )) +} diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index 323c3488..75fcd004 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -46,4 +46,8 @@ pub fn add_entry_point() -> syn::Ident { pub fn new() -> syn::Ident { format_ident!("new") -} \ No newline at end of file +} + +pub fn schemas() -> syn::Ident { + format_ident!("schemas") +} diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 6b8b6c99..22a48cca 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -87,3 +87,7 @@ pub fn group() -> syn::Type { pub fn cl_type() -> syn::Type { parse_quote!(odra::casper_types::CLType) } + +pub fn runtime_args() -> syn::Type { + parse_quote!(odra::RuntimeArgs) +} From a8a945ad5289b20eb4ce0b4dc15f3f7c7b63e6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Wed, 29 Nov 2023 13:36:13 +0100 Subject: [PATCH 04/12] generate no_mangle functions --- examples2/src/counter_pack.rs | 85 -------- examples2/src/erc20.rs | 252 +----------------------- odra-macros/src/ast/fn_utils.rs | 11 +- odra-macros/src/ast/mod.rs | 1 + odra-macros/src/ast/ref_utils.rs | 7 +- odra-macros/src/ast/wasm_parts.rs | 116 +++++++++-- odra-macros/src/ast/wasm_parts_utils.rs | 13 +- odra-macros/src/ir/mod.rs | 10 +- odra-macros/src/lib.rs | 2 + odra-macros/src/utils/attr.rs | 6 +- odra-macros/src/utils/expr.rs | 64 +++--- odra-macros/src/utils/ident.rs | 4 + odra-macros/src/utils/mod.rs | 1 + odra-macros/src/utils/stmt.rs | 52 +++++ odra-macros/src/utils/syn.rs | 29 +++ odra-macros/src/utils/ty.rs | 8 +- 16 files changed, 252 insertions(+), 409 deletions(-) create mode 100644 odra-macros/src/utils/stmt.rs diff --git a/examples2/src/counter_pack.rs b/examples2/src/counter_pack.rs index 6afb1360..168f3636 100644 --- a/examples2/src/counter_pack.rs +++ b/examples2/src/counter_pack.rs @@ -62,91 +62,6 @@ impl CounterPack { } } -#[cfg(odra_module = "CounterPack")] -#[cfg(target_arch = "wasm32")] -mod __counter_pack_wasm_parts { - use odra::casper_event_standard::Schemas; - use odra::odra_casper_wasm_env; - use odra::odra_casper_wasm_env::casper_contract::contract_api::runtime; - use odra::odra_casper_wasm_env::casper_contract::unwrap_or_revert::UnwrapOrRevert; - use odra::odra_casper_wasm_env::WasmContractEnv; - use odra::types::casper_types::{ - CLType, CLTyped, CLValue, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Group, - Parameter, RuntimeArgs - }; - use odra::types::{runtime_args, Address, U256}; - use odra::{prelude::*, ContractEnv}; - - use super::CounterPack; - - extern crate alloc; - - pub fn entry_points() -> EntryPoints { - let mut entry_points = EntryPoints::new(); - entry_points.add_entry_point(EntryPoint::new( - "get_count", - alloc::vec![ - Parameter::new("index_a", CLType::U8), - Parameter::new("index_b", CLType::U8), - ], - CLType::U32, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "increment", - alloc::vec![ - Parameter::new("index_a", CLType::U8), - Parameter::new("index_b", CLType::U8), - ], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points - } - - pub fn execute_call() { - odra::odra_casper_wasm_env::host_functions::install_contract( - entry_points(), - Schemas::new(), - None - ); - } - - pub fn execute_get_count() { - let index_a: u8 = runtime::get_named_arg("index_a"); - let index_b: u8 = runtime::get_named_arg("index_b"); - let env = WasmContractEnv::new(); - let contract: CounterPack = CounterPack::new(Rc::new(env)); - let result = contract.get_count(index_a, index_b); - runtime::ret(CLValue::from_t(result).unwrap_or_revert()); - } - - pub fn execute_increment() { - let index_a: u8 = runtime::get_named_arg("index_a"); - let index_b: u8 = runtime::get_named_arg("index_b"); - let env = WasmContractEnv::new(); - let mut contract: CounterPack = CounterPack::new(Rc::new(env)); - contract.increment(index_a, index_b); - } - - #[no_mangle] - fn call() { - execute_call(); - } - - #[no_mangle] - fn get_count() { - execute_get_count(); - } - - #[no_mangle] - fn increment() { - execute_increment(); - } -} - #[cfg(test)] mod tests { pub use super::*; diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index 4d824988..b6b2d139 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -151,257 +151,6 @@ mod __erc20_schema { } } -// autogenerated for the WasmContractEnv. -#[cfg(odra_module = "Erc20")] -#[cfg(target_arch = "wasm32")] -mod __erc20_wasm_parts { - use super::{Approval, Erc20, Transfer}; - use odra::casper_event_standard::Schemas; - use odra::casper_types::{ - CLType, CLTyped, CLValue, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Group, - Parameter, RuntimeArgs - }; - use odra::odra_casper_wasm_env; - use odra::odra_casper_wasm_env::casper_contract::contract_api::runtime; - use odra::odra_casper_wasm_env::casper_contract::unwrap_or_revert::UnwrapOrRevert; - use odra::odra_casper_wasm_env::WasmContractEnv; - use odra::Module; - use odra::{prelude::*, ContractEnv}; - use odra::{runtime_args, Address, U256}; - - extern crate alloc; - - fn entry_points() -> EntryPoints { - let mut entry_points = EntryPoints::new(); - entry_points.add_entry_point(EntryPoint::new( - "init", - alloc::vec![Parameter::new("total_supply", Option::::cl_type()),], - CLType::Unit, - EntryPointAccess::Groups(alloc::vec![Group::new("constructor_group")]), - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "total_supply", - alloc::vec![], - U256::cl_type(), - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "balance_of", - alloc::vec![Parameter::new("owner", Address::cl_type()),], - U256::cl_type(), - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "transfer", - alloc::vec![ - Parameter::new("to", Address::cl_type()), - Parameter::new("value", U256::cl_type()), - ], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "cross_total", - alloc::vec![Parameter::new("other", Address::cl_type()),], - U256::cl_type(), - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "pay_to_mint", - alloc::vec![], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "get_current_block_time", - alloc::vec![], - u64::cl_type(), - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "burn_and_get_paid", - alloc::vec![Parameter::new("amount", U256::cl_type()),], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "approve", - alloc::vec![ - Parameter::new("to", Address::cl_type()), - Parameter::new("amount", U256::cl_type()), - ], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points.add_entry_point(EntryPoint::new( - "cross_transfer", - alloc::vec![ - Parameter::new("other", Address::cl_type()), - Parameter::new("to", Address::cl_type()), - Parameter::new("value", U256::cl_type()), - ], - CLType::Unit, - EntryPointAccess::Public, - EntryPointType::Contract - )); - entry_points - } - - pub fn execute_call() { - let schemas = Schemas::new(); //.with::().with::(); - let total_supply: Option = runtime::get_named_arg("total_supply"); - let init_args = runtime_args! { - "total_supply" => total_supply - }; - odra::odra_casper_wasm_env::host_functions::install_contract( - entry_points(), - schemas, - Some(init_args) - ); - } - - pub fn execute_init() { - let total_supply: Option = runtime::get_named_arg("total_supply"); - let env = WasmContractEnv::new_env(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.init(total_supply); - } - - pub fn execute_total_supply() { - let env = WasmContractEnv::new_env(); - let contract: Erc20 = Erc20::new(Rc::new(env)); - let result = contract.total_supply(); - runtime::ret(CLValue::from_t(result).unwrap_or_revert()) - } - - pub fn execute_balance_of() { - let owner: Address = runtime::get_named_arg("owner"); - let env = WasmContractEnv::new_env(); - let contract: Erc20 = Erc20::new(Rc::new(env)); - let result = contract.balance_of(owner); - runtime::ret(CLValue::from_t(result).unwrap_or_revert()) - } - - pub fn execute_transfer() { - let to: Address = runtime::get_named_arg("to"); - let value: U256 = runtime::get_named_arg("value"); - let env = WasmContractEnv::new_env(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.transfer(to, value); - } - - pub fn execute_cross_total() { - let other: Address = runtime::get_named_arg("other"); - let env = WasmContractEnv::new_env(); - let contract: Erc20 = Erc20::new(Rc::new(env)); - let result = contract.cross_total(other); - runtime::ret(CLValue::from_t(result).unwrap_or_revert()) - } - - pub fn execute_pay_to_mint() { - let env = WasmContractEnv::new_env(); - odra::odra_casper_wasm_env::host_functions::handle_attached_value(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.pay_to_mint(); - odra::odra_casper_wasm_env::host_functions::clear_attached_value(); - } - - pub fn execute_get_current_block_time() { - let env = WasmContractEnv::new_env(); - let contract: Erc20 = Erc20::new(Rc::new(env)); - let result = contract.get_current_block_time(); - runtime::ret(CLValue::from_t(result).unwrap_or_revert()) - } - - pub fn execute_burn_and_get_paid() { - let amount: U256 = runtime::get_named_arg("amount"); - let env = WasmContractEnv::new_env(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.burn_and_get_paid(amount); - } - - pub fn execute_approve() { - let to: Address = runtime::get_named_arg("to"); - let amount: U256 = runtime::get_named_arg("amount"); - let env = WasmContractEnv::new_env(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.approve(to, amount); - } - - pub fn execute_cross_transfer() { - let other: Address = runtime::get_named_arg("other"); - let to: Address = runtime::get_named_arg("to"); - let value: U256 = runtime::get_named_arg("value"); - let env = WasmContractEnv::new_env(); - let mut contract: Erc20 = Erc20::new(Rc::new(env)); - contract.cross_transfer(other, to, value); - } - - #[no_mangle] - fn call() { - execute_call(); - } - - #[no_mangle] - fn init() { - execute_init(); - } - - #[no_mangle] - fn total_supply() { - execute_total_supply(); - } - - #[no_mangle] - fn balance_of() { - execute_balance_of(); - } - - #[no_mangle] - fn transfer() { - execute_transfer(); - } - - #[no_mangle] - fn cross_total() { - execute_cross_total() - } - - #[no_mangle] - fn pay_to_mint() { - execute_pay_to_mint(); - } - - #[no_mangle] - fn get_current_block_time() { - execute_get_current_block_time(); - } - - #[no_mangle] - fn burn_and_get_paid() { - execute_burn_and_get_paid(); - } - - #[no_mangle] - fn approve() { - execute_approve(); - } - - #[no_mangle] - fn cross_transfer() { - execute_cross_transfer(); - } -} - use odra::{runtime_args, ExecutionError, RuntimeArgs}; #[cfg(not(target_arch = "wasm32"))] @@ -449,6 +198,7 @@ mod tests { env.set_caller(alice); pobcoin.with_tokens(100.into()).pay_to_mint(); + // TODO: fails cause the generated code does not handle the attached value - more specifically the `payable` attribute assert_eq!(env.balance_of(&pobcoin.address), 100.into()); assert_eq!(pobcoin.total_supply(), 200.into()); assert_eq!(pobcoin.balance_of(alice), 100.into()); diff --git a/odra-macros/src/ast/fn_utils.rs b/odra-macros/src/ast/fn_utils.rs index 90f0ea3b..94c4f901 100644 --- a/odra-macros/src/ast/fn_utils.rs +++ b/odra-macros/src/ast/fn_utils.rs @@ -1,6 +1,9 @@ -use crate::{ir::FnIR, utils}; +use crate::{ + ir::{FnArgIR, FnIR}, + utils +}; -pub fn runtime_args_block syn::Stmt>( +pub fn runtime_args_block syn::Stmt>( fun: &FnIR, insert_arg_fn: F ) -> syn::Block { @@ -15,11 +18,11 @@ pub fn runtime_args_block syn::Stmt>( }) } -pub fn insert_args_stmts syn::Stmt>( +pub fn insert_args_stmts syn::Stmt>( fun: &FnIR, insert_arg_fn: F ) -> Vec { - fun.arg_names() + fun.named_args() .iter() .map(insert_arg_fn) .collect::>() diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index c06df045..7c659e2e 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -13,3 +13,4 @@ mod wasm_parts_utils; pub(crate) use module_item::ModuleModItem; pub(crate) use ref_item::RefItem; pub(crate) use test_parts::{TestParts, TestPartsReexport}; +pub(crate) use wasm_parts::WasmPartsModuleItem; diff --git a/odra-macros/src/ast/ref_utils.rs b/odra-macros/src/ast/ref_utils.rs index 40cba30c..df1766a4 100644 --- a/odra-macros/src/ast/ref_utils.rs +++ b/odra-macros/src/ast/ref_utils.rs @@ -1,6 +1,6 @@ use crate::{ ast::fn_utils, - ir::FnIR, + ir::{FnArgIR, FnIR}, utils::{self, syn::visibility_pub} }; @@ -79,7 +79,7 @@ fn try_function_signature(fun: &FnIR) -> syn::Signature { syn::parse_quote!(fn #fun_name(& #mutability self #(, #args)*) #return_type) } -fn runtime_args_with_amount_block syn::Stmt>( +fn runtime_args_with_amount_block syn::Stmt>( fun: &FnIR, insert_arg_fn: F ) -> syn::Block { @@ -108,7 +108,8 @@ fn insert_amount_arg_stmt() -> syn::Stmt { ) } -pub fn insert_arg_stmt(ident: &syn::Ident) -> syn::Stmt { +pub fn insert_arg_stmt(arg: &FnArgIR) -> syn::Stmt { + let ident = arg.name().unwrap(); let name = ident.to_string(); let args = utils::ident::named_args(); diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index efbe5852..d54b72a2 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -27,7 +27,10 @@ pub struct WasmPartsModuleItem { #[syn(in = braces)] entry_points_fn: EntryPointsFnItem, #[syn(in = braces)] - call_fn: CallFnItem + call_fn: CallFnItem, + #[syn(in = braces)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + entry_points: Vec } impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { @@ -35,22 +38,28 @@ impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { fn try_from(module: &'_ ModuleIR) -> Result { let module_str = module.module_str()?; - let ident = module.wasm_parts_mod_ident()?; Ok(Self { attrs: vec![utils::attr::wasm32(), utils::attr::odra_module(&module_str)], mod_token: Default::default(), - ident, + ident: module.wasm_parts_mod_ident()?, braces: Default::default(), use_super: UseSuperItem, use_prelude: UsePreludeItem, entry_points_fn: module.try_into()?, - call_fn: module.try_into()? + call_fn: module.try_into()?, + entry_points: module + .functions() + .iter() + .map(|f| (module, f)) + .map(TryInto::try_into) + .collect::, _>>()? }) } } #[derive(syn_derive::ToTokens)] struct EntryPointsFnItem { + inline_attr: syn::Attribute, sig: syn::Signature, #[syn(braced)] braces: syn::token::Brace, @@ -72,6 +81,7 @@ impl TryFrom<&'_ ModuleIR> for EntryPointsFnItem { let expr_entry_points = utils::expr::new_entry_points(); Ok(Self { + inline_attr: utils::attr::inline(), sig: parse_quote!(fn #ident_entry_points() -> #ty_entry_points), braces: Default::default(), var_declaration: parse_quote!(let mut #ident_entry_points = #expr_entry_points;), @@ -115,7 +125,7 @@ impl TryFrom<&'_ ModuleIR> for CallFnItem { None => parse_quote!(let #ident_args = Option::<#ty_args>::None) }; let expr_new_schemas = utils::expr::new_schemas(); - let expr_install = utils::expr::install_contract( + let install_contract_stmt = utils::stmt::install_contract( parse_quote!(#ident_entry_points()), parse_quote!(#ident_schemas), parse_quote!(#ident_args) @@ -127,7 +137,72 @@ impl TryFrom<&'_ ModuleIR> for CallFnItem { braces: Default::default(), schemas_init_stmt: parse_quote!(let #ident_schemas = #expr_new_schemas;), runtime_args_stmt: parse_quote!(#runtime_args_expr;), - install_contract_stmt: parse_quote!(#expr_install;) + install_contract_stmt + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct NoMangleFnItem { + attr: syn::Attribute, + sig: syn::Signature, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + read_args_stmts: Vec, + #[syn(in = braces)] + instantiate_env_stmt: syn::Stmt, + #[syn(in = braces)] + instantiate_module_stmt: syn::Stmt, + #[syn(in = braces)] + call_module_stmt: syn::Stmt, + #[syn(in = braces)] + ret_stmt: Option +} + +impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for NoMangleFnItem { + type Error = syn::Error; + + fn try_from(value: (&'_ ModuleIR, &'_ FnIR)) -> Result { + let (module, func) = value; + let module_ident = module.module_ident()?; + let contract_ident = utils::ident::contract(); + let env_ident = utils::ident::env(); + let fn_ident = func.name(); + let result_ident = utils::ident::result(); + let fn_args = func.arg_names(); + + let instantiate_module_stmt = match func.is_mut() { + true => utils::stmt::new_mut_module(&contract_ident, &module_ident, &env_ident), + false => utils::stmt::new_module(&contract_ident, &module_ident, &env_ident) + }; + + let call_module_stmt = match func.return_type() { + syn::ReturnType::Default => parse_quote!(#contract_ident.#fn_ident( #(#fn_args),* );), + syn::ReturnType::Type(_, _) => { + parse_quote!(let #result_ident = #contract_ident.#fn_ident( #(#fn_args),* );) + } + }; + + let ret_stmt = match func.return_type() { + syn::ReturnType::Default => None, + syn::ReturnType::Type(_, _) => Some(utils::stmt::runtime_return(&result_ident)) + }; + + Ok(Self { + attr: utils::attr::no_mangle(), + sig: parse_quote!(fn #fn_ident()), + braces: Default::default(), + read_args_stmts: func + .arg_names() + .iter() + .map(utils::stmt::read_runtime_arg) + .collect(), + instantiate_env_stmt: utils::stmt::new_wasm_contract_env(&env_ident), + instantiate_module_stmt, + call_module_stmt, + ret_stmt }) } } @@ -148,12 +223,10 @@ impl TryFrom<&'_ FnIR> for AddEntryPointStmtItem { type Error = syn::Error; fn try_from(func: &'_ FnIR) -> Result { - let var_ident = utils::ident::entry_points(); - let fn_ident = utils::ident::add_entry_point(); Ok(Self { - var_ident, + var_ident: utils::ident::entry_points(), dot_token: Default::default(), - fn_ident, + fn_ident: utils::ident::add_entry_point(), paren: Default::default(), new_entry_point_expr: func.try_into()?, semi_token: Default::default() @@ -217,6 +290,7 @@ mod test { use super::*; use odra::prelude::*; + #[inline] fn entry_points() -> odra::casper_types::EntryPoints { let mut entry_points = odra::casper_types::EntryPoints::new(); @@ -247,7 +321,7 @@ mod test { let mut named_args = odra::RuntimeArgs::new(); let _ = named_args.insert( "total_supply", - odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg("total_supply") + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg::>("total_supply") ); named_args }); @@ -257,6 +331,26 @@ mod test { named_args ); } + + #[no_mangle] + fn init() { + let total_supply = odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg("total_supply"); + let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); + let mut contract = Erc20::new(Rc::new(env)); + contract.init(total_supply); + } + + #[no_mangle] + fn total_supply() { + let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); + let contract = Erc20::new(Rc::new(env)); + let result = contract.total_supply(); + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::ret( + odra::odra_casper_wasm_env::casper_contract::unwrap_or_revert::UnwrapOrRevert::unwrap_or_revert( + odra::casper_types::CLValue::from_t(result) + ) + ); + } } }; diff --git a/odra-macros/src/ast/wasm_parts_utils.rs b/odra-macros/src/ast/wasm_parts_utils.rs index 89759ce0..8eb03cae 100644 --- a/odra-macros/src/ast/wasm_parts_utils.rs +++ b/odra-macros/src/ast/wasm_parts_utils.rs @@ -1,4 +1,7 @@ -use crate::{ir::FnIR, utils}; +use crate::{ + ir::{FnArgIR, FnIR}, + utils +}; use syn::parse_quote; pub fn param_parameters(func: &FnIR) -> syn::Expr { @@ -29,12 +32,12 @@ pub fn param_ret_ty(func: &FnIR) -> syn::Expr { } } -pub fn insert_arg_stmt(ident: &syn::Ident) -> syn::Stmt { - let name = ident.to_string(); +pub fn insert_arg_stmt(arg: &FnArgIR) -> syn::Stmt { + let (name, ty) = arg.name_and_ty().unwrap(); let args = utils::ident::named_args(); syn::parse_quote!(let _ = #args.insert( - #name, - odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg(#name) + #name, + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg::<#ty>(#name) );) } diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index ecd4b641..a19c87b9 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -252,7 +252,6 @@ impl FnIR { pub fn is_constructor(&self) -> bool { self.name_str() == CONSTRUCTOR_NAME } - } pub struct FnArgIR { @@ -281,12 +280,13 @@ impl FnArgIR { pub fn name_str(&self) -> Result { self.name().map(|i| i.to_string()) } - + pub fn name_and_ty(&self) -> Result<(String, syn::Type), syn::Error> { match &self.code { - syn::FnArg::Typed(syn::PatType { - box ty, - pat: box syn::Pat::Ident(pat), .. + syn::FnArg::Typed(syn::PatType { + box ty, + pat: box syn::Pat::Ident(pat), + .. }) => Ok((pat.ident.to_string(), ty.clone())), _ => Err(syn::Error::new_spanned(&self.code, "Unnamed arg")) } diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index 5f4527aa..6f434138 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -32,12 +32,14 @@ fn module_impl(ir: ModuleIR) -> Result { let ref_item = RefItem::try_from(&ir)?; let test_parts = TestParts::try_from(&ir)?; let test_parts_reexport = TestPartsReexport::try_from(&ir)?; + let wasm_parts = WasmPartsModuleItem::try_from(&ir)?; Ok(quote::quote! { #code #ref_item #test_parts #test_parts_reexport + #wasm_parts }) } diff --git a/odra-macros/src/utils/attr.rs b/odra-macros/src/utils/attr.rs index 80d5dbda..e07da0bc 100644 --- a/odra-macros/src/utils/attr.rs +++ b/odra-macros/src/utils/attr.rs @@ -14,4 +14,8 @@ pub fn odra_module(name: &str) -> syn::Attribute { pub fn no_mangle() -> syn::Attribute { parse_quote!(#[no_mangle]) -} \ No newline at end of file +} + +pub fn inline() -> syn::Attribute { + parse_quote!(#[inline]) +} diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index 9ba58808..abda0452 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -1,11 +1,13 @@ use syn::parse_quote; pub fn new_runtime_args() -> syn::Expr { - parse_quote!(odra::RuntimeArgs::new()) + let ty = super::ty::runtime_args(); + parse_quote!(#ty::new()) } pub fn u512_zero() -> syn::Expr { - parse_quote!(odra::U512::zero()) + let ty = super::ty::u512(); + parse_quote!(#ty::zero()) } pub fn parse_bytes(data_ident: &syn::Ident) -> syn::Expr { @@ -22,66 +24,44 @@ fn rc_clone(ident: &syn::Ident) -> syn::Expr { } pub fn new_entry_points() -> syn::Expr { - parse_quote!(odra::casper_types::EntryPoints::new()) + let ty = super::ty::entry_points(); + parse_quote!(#ty::new()) } pub fn entry_point_contract() -> syn::Expr { - parse_quote!(odra::casper_types::EntryPointType::Contract) + let ty = super::ty::entry_point_type(); + parse_quote!(#ty::Contract) } pub fn entry_point_public() -> syn::Expr { - parse_quote!(odra::casper_types::EntryPointAccess::Public) + let ty = super::ty::entry_point_access(); + parse_quote!(#ty::Public) } pub fn entry_point_group(name: &str) -> syn::Expr { - parse_quote!(odra::casper_types::EntryPointAccess::Groups( - vec![odra::casper_types::Group::new(#name)] - )) + let ty = super::ty::entry_point_access(); + let ty_group = super::ty::group(); + parse_quote!(#ty::Groups(vec![#ty_group::new(#name)])) } pub fn new_parameter(name: String, ty: syn::Type) -> syn::Expr { + let ty_param = super::ty::parameter(); let cl_type = as_cl_type(&ty); - parse_quote!(odra::casper_types::Parameter::new(#name, #cl_type)) + parse_quote!(#ty_param::new(#name, #cl_type)) } pub fn as_cl_type(ty: &syn::Type) -> syn::Expr { - let ty = match ty { - syn::Type::Path(type_path) => { - let mut segments: syn::punctuated::Punctuated = - type_path.path.segments.clone(); - // the syntax as odra::casper_types::CLTyped>::cl_type() is invalid - // it should be as odra::casper_types::CLTyped>::cl_type() - segments.first_mut().map(|ps| { - if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments { - let generic_arg: syn::AngleBracketedGenericArguments = parse_quote!(::#ab); - ps.arguments = syn::PathArguments::AngleBracketed(generic_arg); - } - }); - syn::Type::Path(syn::TypePath { - path: syn::Path { - leading_colon: None, - segments - }, - ..type_path.clone() - }) - } - _ => ty.clone() - }; - parse_quote!(<#ty as odra::casper_types::CLTyped>::cl_type()) + let ty_cl_typed = super::ty::cl_typed(); + let ty = super::syn::as_casted_ty_stream(ty, ty_cl_typed); + parse_quote!(#ty::cl_type()) } pub fn unit_cl_type() -> syn::Expr { - parse_quote!(<() as odra::casper_types::CLTyped>::cl_type()) + let ty_cl_typed = super::ty::cl_typed(); + parse_quote!(<() as #ty_cl_typed>::cl_type()) } pub fn new_schemas() -> syn::Expr { - parse_quote!(odra::casper_event_standard::Schemas::new()) -} - -pub fn install_contract(entry_points: syn::Expr, schemas: syn::Expr, args: syn::Expr) -> syn::Expr { - parse_quote!(odra::odra_casper_wasm_env::host_functions::install_contract( - #entry_points, - #schemas, - #args - )) + let ty = super::ty::schemas(); + parse_quote!(#ty::new()) } diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index 75fcd004..8498356d 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -51,3 +51,7 @@ pub fn new() -> syn::Ident { pub fn schemas() -> syn::Ident { format_ident!("schemas") } + +pub fn contract() -> syn::Ident { + format_ident!("contract") +} diff --git a/odra-macros/src/utils/mod.rs b/odra-macros/src/utils/mod.rs index be488c57..8dcd9a58 100644 --- a/odra-macros/src/utils/mod.rs +++ b/odra-macros/src/utils/mod.rs @@ -2,6 +2,7 @@ pub mod attr; pub mod expr; pub mod ident; pub mod member; +pub mod stmt; pub mod string; pub mod syn; pub mod ty; diff --git a/odra-macros/src/utils/stmt.rs b/odra-macros/src/utils/stmt.rs new file mode 100644 index 00000000..10d33f63 --- /dev/null +++ b/odra-macros/src/utils/stmt.rs @@ -0,0 +1,52 @@ +use syn::parse_quote; + +pub fn read_runtime_arg(ident: &syn::Ident) -> syn::Stmt { + let name_str = ident.to_string(); + parse_quote!( + let #ident = odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg(#name_str); + ) +} + +pub fn runtime_return(result_ident: &syn::Ident) -> syn::Stmt { + parse_quote!( + odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::ret( + odra::odra_casper_wasm_env::casper_contract::unwrap_or_revert::UnwrapOrRevert::unwrap_or_revert( + odra::casper_types::CLValue::from_t(#result_ident) + ) + ); + ) +} + +pub fn new_wasm_contract_env(ident: &syn::Ident) -> syn::Stmt { + parse_quote!( + let #ident = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); + ) +} + +pub fn new_module( + contract_ident: &syn::Ident, + module_ident: &syn::Ident, + env_ident: &syn::Ident +) -> syn::Stmt { + parse_quote!( + let #contract_ident = #module_ident::new(Rc::new(#env_ident)); + ) +} + +pub fn new_mut_module( + contract_ident: &syn::Ident, + module_ident: &syn::Ident, + env_ident: &syn::Ident +) -> syn::Stmt { + parse_quote!( + let mut #contract_ident = #module_ident::new(Rc::new(#env_ident)); + ) +} + +pub fn install_contract(entry_points: syn::Expr, schemas: syn::Expr, args: syn::Expr) -> syn::Stmt { + parse_quote!(odra::odra_casper_wasm_env::host_functions::install_contract( + #entry_points, + #schemas, + #args + );) +} diff --git a/odra-macros/src/utils/syn.rs b/odra-macros/src/utils/syn.rs index e741b0c8..90f1837d 100644 --- a/odra-macros/src/utils/syn.rs +++ b/odra-macros/src/utils/syn.rs @@ -1,3 +1,4 @@ +use proc_macro2::TokenStream; use syn::{parse_quote, spanned::Spanned}; pub fn ident_from_impl(impl_code: &syn::ItemImpl) -> Result { @@ -129,6 +130,34 @@ pub fn clear_generics(ty: &syn::Type) -> Result { } } +// A path like as odra::casper_types::CLTyped> +pub fn as_casted_ty_stream(ty: &syn::Type, as_ty: syn::Type) -> TokenStream { + let ty = match ty { + syn::Type::Path(type_path) => { + let mut segments: syn::punctuated::Punctuated = + type_path.path.segments.clone(); + // the syntax as odra::casper_types::CLTyped>::cl_type() is invalid + // it should be as odra::casper_types::CLTyped>::cl_type() + if let Some(ps) = segments.first_mut() { + if let syn::PathArguments::AngleBracketed(ab) = &ps.arguments { + let generic_arg: syn::AngleBracketedGenericArguments = parse_quote!(::#ab); + ps.arguments = syn::PathArguments::AngleBracketed(generic_arg); + } + } + syn::Type::Path(syn::TypePath { + path: syn::Path { + leading_colon: None, + segments + }, + ..type_path.clone() + }) + } + _ => ty.clone() + }; + + parse_quote!(<#ty as #as_ty>) +} + fn clear_path(ty: &syn::TypePath) -> Result { let mut owned_ty = ty.to_owned(); diff --git a/odra-macros/src/utils/ty.rs b/odra-macros/src/utils/ty.rs index 22a48cca..3255844d 100644 --- a/odra-macros/src/utils/ty.rs +++ b/odra-macros/src/utils/ty.rs @@ -84,8 +84,12 @@ pub fn group() -> syn::Type { parse_quote!(odra::casper_types::Group) } -pub fn cl_type() -> syn::Type { - parse_quote!(odra::casper_types::CLType) +pub fn schemas() -> syn::Type { + parse_quote!(odra::casper_event_standard::Schemas) +} + +pub fn cl_typed() -> syn::Type { + parse_quote!(odra::casper_types::CLTyped) } pub fn runtime_args() -> syn::Type { From b25b816f32fdeedfa7efa388a80d5db834dbecd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Wed, 29 Nov 2023 13:43:21 +0100 Subject: [PATCH 05/12] update ModuleIR --- odra-macros/src/ir/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index a19c87b9..ea1eba2a 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -150,9 +150,14 @@ impl ModuleIR { } pub fn wasm_parts_mod_ident(&self) -> Result { - self.module_ident() - .map(crate::utils::string::camel_to_snake) - .map(|ident| format_ident!("__{}_wasm_parts", ident)) + let module_ident = self.module_ident()?; + Ok(Ident::new( + &format!( + "__{}_wasm_parts", + crate::utils::string::camel_to_snake(&module_ident) + ), + module_ident.span() + )) } pub fn functions(&self) -> Vec { From 59a1d2d30547f0f8fa2e314b9eb50263b963868e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Thu, 30 Nov 2023 15:36:53 +0100 Subject: [PATCH 06/12] odra reexports odra_macros --- examples2/Cargo.toml | 1 - examples2/src/counter_pack.rs | 4 ++-- examples2/src/erc20.rs | 4 ++-- odra/Cargo.toml | 1 + odra/src/lib.rs | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples2/Cargo.toml b/examples2/Cargo.toml index f8b98249..0727900b 100644 --- a/examples2/Cargo.toml +++ b/examples2/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] odra = { path = "../odra" } -odra-macros = { path = "../odra-macros" } [[bin]] name = "contract" diff --git a/examples2/src/counter_pack.rs b/examples2/src/counter_pack.rs index 168f3636..7e9de3a3 100644 --- a/examples2/src/counter_pack.rs +++ b/examples2/src/counter_pack.rs @@ -5,7 +5,7 @@ use odra::Mapping; use odra::Module; use odra::ModuleWrapper; -#[odra_macros::module] +#[odra::module] pub struct CounterPack { env: Rc, counter0: ModuleWrapper, @@ -22,7 +22,7 @@ pub struct CounterPack { counters_map: Mapping } -#[odra_macros::module] +#[odra::module] impl CounterPack { pub fn get_count(&self, index_a: u8, index_b: u8) -> u32 { match index_a { diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index b6b2d139..2a8d295c 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -36,14 +36,14 @@ impl From for OdraError { } } -#[odra_macros::module] +#[odra::module] pub struct Erc20 { env: Rc, total_supply: Variable, balances: Mapping } -#[odra_macros::module] +#[odra::module] impl Erc20 { pub fn init(&mut self, total_supply: Option) { if let Some(total_supply) = total_supply { diff --git a/odra/Cargo.toml b/odra/Cargo.toml index 7282072b..2f97b509 100644 --- a/odra/Cargo.toml +++ b/odra/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [dependencies] odra-core = { path = "../core" } +odra-macros = { path = "../odra-macros" } [target.'cfg(target_arch = "wasm32")'.dependencies] odra-casper-wasm-env = { path = "../odra-casper/wasm-env" } diff --git a/odra/src/lib.rs b/odra/src/lib.rs index 72292b9b..03b65d72 100644 --- a/odra/src/lib.rs +++ b/odra/src/lib.rs @@ -6,6 +6,7 @@ pub use odra_core::mapping::*; pub use odra_core::module::*; pub use odra_core::variable::*; pub use odra_core::*; +pub use odra_macros::*; #[cfg(target_arch = "wasm32")] pub use odra_casper_wasm_env; From fd668e2f61430faf8cc35ee7ef60805a5c7a9cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Thu, 30 Nov 2023 17:22:07 +0100 Subject: [PATCH 07/12] Generate execute_* functions * generate a module with execute_* functions * add get_name_arg() function `ContractContext` * update odra-vm * update odra-casper * add try-from-macro crate * add TryFromRef derive macro that generate TryFrom<&'_ SourceStruct> implementation --- Cargo.toml | 1 + core/src/contract_context.rs | 1 + core/src/contract_env.rs | 5 + odra-casper/wasm-env/src/host_functions.rs | 36 ++++ odra-casper/wasm-env/src/wasm_contract_env.rs | 4 + odra-macros/Cargo.toml | 2 + odra-macros/src/ast/deployer_item.rs | 41 +--- odra-macros/src/ast/deployer_utils.rs | 44 ++--- odra-macros/src/ast/exec_parts.rs | 181 ++++++++++++++++++ odra-macros/src/ast/host_ref_item.rs | 15 +- odra-macros/src/ast/mod.rs | 4 +- odra-macros/src/ast/ref_item.rs | 15 +- odra-macros/src/ast/test_parts.rs | 30 +-- odra-macros/src/ast/wasm_parts.rs | 52 ++--- odra-macros/src/ir/mod.rs | 24 ++- odra-macros/src/lib.rs | 66 +++---- odra-macros/src/utils/expr.rs | 4 + odra-macros/src/utils/stmt.rs | 18 +- odra-vm/src/odra_vm_contract_env.rs | 4 + odra-vm/src/vm/callstack.rs | 17 +- odra-vm/src/vm/odra_vm.rs | 17 +- try-from-macro/Cargo.toml | 17 ++ try-from-macro/src/lib.rs | 91 +++++++++ 23 files changed, 474 insertions(+), 215 deletions(-) create mode 100644 odra-macros/src/ast/exec_parts.rs create mode 100644 try-from-macro/Cargo.toml create mode 100644 try-from-macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 91e0b4e3..c3fabe5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "odra-macros", "odra-casper/wasm-env", "odra-casper/test-vm", + "try-from-macro" # needs a refactor # "odra-casper/livenet" ] diff --git a/core/src/contract_context.rs b/core/src/contract_context.rs index 15ccc805..d2bae2e0 100644 --- a/core/src/contract_context.rs +++ b/core/src/contract_context.rs @@ -12,4 +12,5 @@ pub trait ContractContext { fn emit_event(&self, event: &Bytes); fn transfer_tokens(&self, to: &Address, amount: &U512); fn revert(&self, error: OdraError) -> !; + fn get_named_arg(&self, name: &str) -> Bytes; } diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index 2047fe6d..bfc70361 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -28,6 +28,11 @@ impl ContractEnv { } } + pub fn get_named_arg(&self, name: &str) -> T { + let bytes = self.backend.borrow().get_named_arg(name); + T::from_bytes(&bytes).unwrap().0 + } + pub fn current_key(&self) -> Vec { let index_bytes = key_maker::u32_to_hex(self.index); let mapping_data_bytes = key_maker::bytes_to_hex(&self.mapping_data); diff --git a/odra-casper/wasm-env/src/host_functions.rs b/odra-casper/wasm-env/src/host_functions.rs index 673228b7..57a24146 100644 --- a/odra-casper/wasm-env/src/host_functions.rs +++ b/odra-casper/wasm-env/src/host_functions.rs @@ -127,6 +127,42 @@ pub fn revert(error: u16) -> ! { runtime::revert(ApiError::User(error)) } +pub fn get_named_arg(name: &str) -> Vec { + let arg_size = get_named_arg_size(name); + if arg_size > 0 { + let data_non_null_ptr = contract_api::alloc_bytes(arg_size); + let ret = unsafe { + ext_ffi::casper_get_named_arg( + name.as_bytes().as_ptr(), + name.len(), + data_non_null_ptr.as_ptr(), + arg_size + ) + }; + if ret != 0 { + runtime::revert(ApiError::from(ret as u32)) + } + unsafe { Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) } + } else { + Vec::new() + } +} + +fn get_named_arg_size(name: &str) -> usize { + let mut arg_size: usize = 0; + let ret = unsafe { + ext_ffi::casper_get_named_arg_size( + name.as_bytes().as_ptr(), + name.len(), + &mut arg_size as *mut usize + ) + }; + match ret { + 0 => arg_size, + _ => runtime::revert(ApiError::from(ret as u32)) + } +} + pub fn get_block_time() -> u64 { runtime::get_blocktime().into() } diff --git a/odra-casper/wasm-env/src/wasm_contract_env.rs b/odra-casper/wasm-env/src/wasm_contract_env.rs index 1e3d80bd..b3da7615 100644 --- a/odra-casper/wasm-env/src/wasm_contract_env.rs +++ b/odra-casper/wasm-env/src/wasm_contract_env.rs @@ -49,6 +49,10 @@ impl ContractContext for WasmContractEnv { fn revert(&self, error: OdraError) -> ! { host_functions::revert(error.code()) } + + fn get_named_arg(&self, name: &str) -> Bytes { + host_functions::get_named_arg(name).into() + } } impl WasmContractEnv { diff --git a/odra-macros/Cargo.toml b/odra-macros/Cargo.toml index 8f0906e3..be17943b 100644 --- a/odra-macros/Cargo.toml +++ b/odra-macros/Cargo.toml @@ -16,6 +16,8 @@ quote = "1.0.33" syn = { version = "2.0.29", features = ["full"] } syn_derive = "0.1.8" convert_case = { version = "0.5.0" } +derive_convert = "0.4.0" +derive-try-from ={ path = "../try-from-macro" } [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index 40d1a399..d250079d 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -1,3 +1,5 @@ +use derive_try_from::TryFromRef; + use crate::{ir::ModuleIR, utils}; use super::deployer_utils::{ @@ -48,11 +50,14 @@ impl TryFrom<&'_ ModuleIR> for DeployImplItem { } } -#[derive(syn_derive::ToTokens)] +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] struct ContractInitFn { + #[expr(utils::syn::visibility_pub())] vis: syn::Visibility, sig: DeployerInitSignature, #[syn(braced)] + #[default] braces: syn::token::Brace, #[syn(in = braces)] caller: EntrypointCallerExpr, @@ -62,38 +67,13 @@ struct ContractInitFn { host_ref_instance: HostRefInstanceExpr } -impl TryFrom<&'_ ModuleIR> for ContractInitFn { - type Error = syn::Error; - - fn try_from(module: &'_ ModuleIR) -> Result { - Ok(Self { - vis: utils::syn::visibility_pub(), - sig: module.try_into()?, - braces: Default::default(), - caller: module.try_into()?, - new_contract: module.try_into()?, - host_ref_instance: module.try_into()? - }) - } -} - -#[derive(syn_derive::ToTokens)] +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] pub struct DeployerItem { struct_item: DeployStructItem, impl_item: DeployImplItem } -impl TryFrom<&'_ ModuleIR> for DeployerItem { - type Error = syn::Error; - - fn try_from(module: &'_ ModuleIR) -> Result { - Ok(Self { - struct_item: module.try_into()?, - impl_item: module.try_into()? - }) - } -} - #[cfg(test)] mod deployer_impl { use super::DeployerItem; @@ -111,12 +91,11 @@ mod deployer_impl { let caller = odra::EntryPointsCaller::new(env.clone(), |contract_env, call_def| { match call_def.method() { "init" => { - let result = Erc20::new(Rc::new(contract_env)) - .init(call_def.get("total_supply").expect("arg not found")); + let result = execute_init(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } "total_supply" => { - let result = Erc20::new(Rc::new(contract_env)).total_supply(); + let result = execute_total_supply(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } _ => panic!("Unknown method") diff --git a/odra-macros/src/ast/deployer_utils.rs b/odra-macros/src/ast/deployer_utils.rs index 0570c169..cf416150 100644 --- a/odra-macros/src/ast/deployer_utils.rs +++ b/odra-macros/src/ast/deployer_utils.rs @@ -1,6 +1,6 @@ use super::{fn_utils, ref_utils}; use crate::{ - ir::{FnArgIR, FnIR, ModuleIR}, + ir::{FnIR, ModuleIR}, utils }; use proc_macro2::TokenStream; @@ -72,8 +72,8 @@ impl EntrypointCallerExpr { let mut branches: Vec = module .functions() .iter() - .map(|f| Ok(CallerBranch::Function(FunctionCallBranch::new(module, f)?))) - .collect::>()?; + .map(|f| CallerBranch::Function(FunctionCallBranch::from(f))) + .collect(); branches.push(CallerBranch::Default(DefaultBranch)); Ok(parse_quote!( @@ -178,38 +178,24 @@ struct FunctionCallBranch { result_expr: syn::Expr } -impl<'a> FunctionCallBranch { - pub fn new(module: &'a ModuleIR, func: &'a FnIR) -> Result { - let call_stmt = Self::call_stmt(module, func)?; - let result_expr = utils::expr::parse_bytes(&utils::ident::result()); - - Ok(Self { +impl From<&'_ FnIR> for FunctionCallBranch { + fn from(func: &'_ FnIR) -> Self { + Self { function_name: func.name_str(), arrow_token: Default::default(), brace_token: Default::default(), - call_stmt, - result_expr - }) - } - - fn read_call_def_arg_expr(arg: &FnArgIR) -> Result { - let call_def_ident = utils::ident::call_def(); - let name = arg.name_str()?; - Ok(parse_quote!(#call_def_ident.get(#name).expect("arg not found"))) + call_stmt: Self::call_stmt(func), + result_expr: utils::expr::parse_bytes(&utils::ident::result()) + } } +} - fn call_stmt(module: &'a ModuleIR, func: &'a FnIR) -> Result { - let args = func - .named_args() - .iter() - .map(Self::read_call_def_arg_expr) - .collect::, syn::Error>>( - )?; - +impl<'a> FunctionCallBranch { + fn call_stmt(func: &'a FnIR) -> syn::Stmt { let result_ident = utils::ident::result(); - let module_instance = module.module_instance_expr(utils::ident::contract_env())?; - let function_ident = func.name(); - Ok(parse_quote!(let #result_ident = #module_instance.#function_ident(#args);)) + let function_ident = func.execute_name(); + let contract_env_ident = utils::ident::contract_env(); + parse_quote!(let #result_ident = #function_ident(#contract_env_ident);) } } diff --git a/odra-macros/src/ast/exec_parts.rs b/odra-macros/src/ast/exec_parts.rs new file mode 100644 index 00000000..10a4ed1b --- /dev/null +++ b/odra-macros/src/ast/exec_parts.rs @@ -0,0 +1,181 @@ +use super::parts_utils::{UsePreludeItem, UseSuperItem}; +use crate::{ + ir::{FnIR, ModuleIR}, + utils +}; +use derive_try_from::TryFromRef; +use quote::TokenStreamExt; +use syn::parse_quote; + +#[derive(syn_derive::ToTokens)] +pub struct ExecPartsReexportItem { + reexport_stmt: syn::Stmt +} + +impl TryFrom<&'_ ModuleIR> for ExecPartsReexportItem { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleIR) -> Result { + let test_parts_ident = module.exec_parts_mod_ident()?; + Ok(Self { + reexport_stmt: parse_quote!(pub use #test_parts_ident::*;) + }) + } +} + +#[derive(syn_derive::ToTokens)] +pub struct ExecPartsItem { + parts_module: ExecPartsModuleItem, + #[syn(braced)] + brace_token: syn::token::Brace, + #[syn(in = brace_token)] + use_super: UseSuperItem, + #[syn(in = brace_token)] + use_prelude: UsePreludeItem, + #[syn(in = brace_token)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + exec_functions: Vec +} + +impl TryFrom<&'_ ModuleIR> for ExecPartsItem { + type Error = syn::Error; + + fn try_from(module: &'_ ModuleIR) -> Result { + Ok(Self { + parts_module: module.try_into()?, + brace_token: Default::default(), + use_prelude: UsePreludeItem, + use_super: UseSuperItem, + exec_functions: module + .functions() + .iter() + .map(|f| (module, f)) + .map(TryInto::try_into) + .collect::, _>>()? + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct ExecFunctionItem { + inline_attr: syn::Attribute, + sig: ExecFnSignature, + #[syn(braced)] + braces: syn::token::Brace, + #[syn(in = braces)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + args: Vec, + #[syn(in = braces)] + init_contract_stmt: syn::Stmt, + #[syn(in = braces)] + return_stmt: syn::Stmt +} + +impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for ExecFunctionItem { + type Error = syn::Error; + + fn try_from(value: (&'_ ModuleIR, &'_ FnIR)) -> Result { + let (module, func) = value; + let fn_ident = func.name(); + let env_ident = utils::ident::env(); + let contract_ident = utils::ident::contract(); + let module_ident = module.module_ident()?; + let fn_args = func.arg_names(); + + let args = func + .arg_names() + .iter() + .map(|ident| utils::stmt::get_named_arg(ident, &env_ident)) + .collect(); + + let init_contract_stmt = match func.is_mut() { + true => utils::stmt::new_mut_module(&contract_ident, &module_ident, &env_ident), + false => utils::stmt::new_module(&contract_ident, &module_ident, &env_ident) + }; + + Ok(Self { + inline_attr: utils::attr::inline(), + sig: func.try_into()?, + braces: Default::default(), + args, + init_contract_stmt, + return_stmt: parse_quote!(return #contract_ident.#fn_ident( #(#fn_args),* );) + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct ExecFnSignature { + vis: syn::Visibility, + fn_token: syn::token::Fn, + ident: syn::Ident, + #[syn(parenthesized)] + paren: syn::token::Paren, + #[syn(in = paren)] + env_ident: syn::Ident, + #[syn(in = paren)] + colon_token: syn::token::Colon, + #[syn(in = paren)] + env_type: syn::Type, + ret_ty: syn::ReturnType +} + +impl TryFrom<&'_ FnIR> for ExecFnSignature { + type Error = syn::Error; + + fn try_from(func: &'_ FnIR) -> Result { + Ok(Self { + vis: utils::syn::visibility_pub(), + fn_token: Default::default(), + ident: func.execute_name(), + paren: Default::default(), + env_ident: utils::ident::env(), + colon_token: Default::default(), + env_type: utils::ty::contract_env(), + ret_ty: func.return_type() + }) + } +} + +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] +struct ExecPartsModuleItem { + #[default] + mod_token: syn::token::Mod, + #[expr(item.exec_parts_mod_ident()?)] + ident: syn::Ident +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_utils::{self, mock_module}; + + #[test] + fn test_parts() { + let module = mock_module(); + let actual = ExecPartsItem::try_from(&module).unwrap(); + + let expected = quote::quote! { + mod __erc20_exec_parts { + use super::*; + use odra::prelude::*; + + #[inline] + pub fn execute_init(env: odra::ContractEnv) { + let total_supply = env.get_named_arg("total_supply"); + let mut contract = Erc20::new(Rc::new(env)); + return contract.init(total_supply); + } + + #[inline] + pub fn execute_total_supply(env: odra::ContractEnv) -> U256 { + let contract = Erc20::new(Rc::new(env)); + return contract.total_supply(); + } + } + }; + + test_utils::assert_eq(actual, expected); + } +} diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index 00095518..954ebe1a 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -1,4 +1,5 @@ use crate::{ir::ModuleIR, utils}; +use derive_try_from::TryFromRef; use proc_macro2::Ident; use quote::{quote, ToTokens, TokenStreamExt}; use syn::parse_quote; @@ -145,23 +146,13 @@ impl ToTokens for LastCallFnItem { } } -#[derive(syn_derive::ToTokens)] +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] pub struct HostRefItem { struct_item: HostRefStructItem, impl_item: HostRefImplItem } -impl TryFrom<&'_ ModuleIR> for HostRefItem { - type Error = syn::Error; - - fn try_from(module: &'_ ModuleIR) -> Result { - Ok(Self { - struct_item: module.try_into()?, - impl_item: module.try_into()? - }) - } -} - #[cfg(test)] mod ref_item_tests { use super::HostRefItem; diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs index 7c659e2e..58205fd7 100644 --- a/odra-macros/src/ast/mod.rs +++ b/odra-macros/src/ast/mod.rs @@ -1,5 +1,6 @@ mod deployer_item; mod deployer_utils; +mod exec_parts; mod fn_utils; mod host_ref_item; mod module_item; @@ -10,7 +11,8 @@ mod test_parts; mod wasm_parts; mod wasm_parts_utils; +pub(crate) use exec_parts::{ExecPartsItem, ExecPartsReexportItem}; pub(crate) use module_item::ModuleModItem; pub(crate) use ref_item::RefItem; -pub(crate) use test_parts::{TestParts, TestPartsReexport}; +pub(crate) use test_parts::{TestPartsItem, TestPartsReexportItem}; pub(crate) use wasm_parts::WasmPartsModuleItem; diff --git a/odra-macros/src/ast/ref_item.rs b/odra-macros/src/ast/ref_item.rs index 015a181c..852f6357 100644 --- a/odra-macros/src/ast/ref_item.rs +++ b/odra-macros/src/ast/ref_item.rs @@ -1,4 +1,5 @@ use crate::{ast::ref_utils, ir::ModuleIR, utils}; +use derive_try_from::TryFromRef; use quote::{quote, ToTokens, TokenStreamExt}; use syn::parse_quote; @@ -78,23 +79,13 @@ impl TryFrom<&'_ ModuleIR> for ContractRefImplItem { } } -#[derive(syn_derive::ToTokens)] +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] pub struct RefItem { struct_item: ContractRefStructItem, impl_item: ContractRefImplItem } -impl TryFrom<&'_ ModuleIR> for RefItem { - type Error = syn::Error; - - fn try_from(module: &'_ ModuleIR) -> Result { - Ok(Self { - struct_item: module.try_into()?, - impl_item: module.try_into()? - }) - } -} - #[cfg(test)] mod ref_item_tests { use super::RefItem; diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 5de2637d..6c17cab6 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -1,3 +1,4 @@ +use derive_try_from::TryFromRef; use syn::parse_quote; use crate::{ir::ModuleIR, utils}; @@ -9,12 +10,12 @@ use super::{ }; #[derive(syn_derive::ToTokens)] -pub struct TestPartsReexport { +pub struct TestPartsReexportItem { attr: syn::Attribute, reexport_stmt: syn::Stmt } -impl TryFrom<&'_ ModuleIR> for TestPartsReexport { +impl TryFrom<&'_ ModuleIR> for TestPartsReexportItem { type Error = syn::Error; fn try_from(module: &'_ ModuleIR) -> Result { @@ -45,14 +46,18 @@ impl TryFrom<&'_ ModuleIR> for PartsModuleItem { } } -#[derive(syn_derive::ToTokens)] -pub struct TestParts { +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] +pub struct TestPartsItem { parts_module: PartsModuleItem, #[syn(braced)] + #[default] brace_token: syn::token::Brace, #[syn(in = brace_token)] + #[expr(UseSuperItem)] use_super: UseSuperItem, #[syn(in = brace_token)] + #[expr(UsePreludeItem)] use_prelude: UsePreludeItem, #[syn(in = brace_token)] host_ref: HostRefItem, @@ -60,21 +65,6 @@ pub struct TestParts { deployer: DeployerItem } -impl TryFrom<&'_ ModuleIR> for TestParts { - type Error = syn::Error; - - fn try_from(module: &'_ ModuleIR) -> Result { - Ok(TestParts { - parts_module: module.try_into()?, - brace_token: Default::default(), - use_prelude: UsePreludeItem, - use_super: UseSuperItem, - host_ref: module.try_into()?, - deployer: module.try_into()? - }) - } -} - #[cfg(test)] mod test { use super::*; @@ -83,7 +73,7 @@ mod test { #[test] fn test_parts() { let module = mock_module(); - let actual = TestParts::try_from(&module).unwrap(); + let actual = TestPartsItem::try_from(&module).unwrap(); let expected = quote::quote! { #[cfg(not(target_arch = "wasm32"))] diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index d54b72a2..c788c3ab 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -50,7 +50,6 @@ impl TryFrom<&'_ ModuleIR> for WasmPartsModuleItem { entry_points: module .functions() .iter() - .map(|f| (module, f)) .map(TryInto::try_into) .collect::, _>>()? }) @@ -149,40 +148,23 @@ struct NoMangleFnItem { #[syn(braced)] braces: syn::token::Brace, #[syn(in = braces)] - #[to_tokens(|tokens, f| tokens.append_all(f))] - read_args_stmts: Vec, - #[syn(in = braces)] - instantiate_env_stmt: syn::Stmt, - #[syn(in = braces)] - instantiate_module_stmt: syn::Stmt, - #[syn(in = braces)] - call_module_stmt: syn::Stmt, + execute_stmt: syn::Stmt, #[syn(in = braces)] ret_stmt: Option } -impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for NoMangleFnItem { +impl TryFrom<&'_ FnIR> for NoMangleFnItem { type Error = syn::Error; - fn try_from(value: (&'_ ModuleIR, &'_ FnIR)) -> Result { - let (module, func) = value; - let module_ident = module.module_ident()?; - let contract_ident = utils::ident::contract(); - let env_ident = utils::ident::env(); + fn try_from(func: &'_ FnIR) -> Result { let fn_ident = func.name(); let result_ident = utils::ident::result(); - let fn_args = func.arg_names(); + let exec_fn = func.execute_name(); + let new_env = utils::expr::new_wasm_contract_env(); - let instantiate_module_stmt = match func.is_mut() { - true => utils::stmt::new_mut_module(&contract_ident, &module_ident, &env_ident), - false => utils::stmt::new_module(&contract_ident, &module_ident, &env_ident) - }; - - let call_module_stmt = match func.return_type() { - syn::ReturnType::Default => parse_quote!(#contract_ident.#fn_ident( #(#fn_args),* );), - syn::ReturnType::Type(_, _) => { - parse_quote!(let #result_ident = #contract_ident.#fn_ident( #(#fn_args),* );) - } + let execute_stmt = match func.return_type() { + syn::ReturnType::Default => parse_quote!(#exec_fn(#new_env);), + syn::ReturnType::Type(_, _) => parse_quote!(let #result_ident = #exec_fn(#new_env);) }; let ret_stmt = match func.return_type() { @@ -194,14 +176,7 @@ impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for NoMangleFnItem { attr: utils::attr::no_mangle(), sig: parse_quote!(fn #fn_ident()), braces: Default::default(), - read_args_stmts: func - .arg_names() - .iter() - .map(utils::stmt::read_runtime_arg) - .collect(), - instantiate_env_stmt: utils::stmt::new_wasm_contract_env(&env_ident), - instantiate_module_stmt, - call_module_stmt, + execute_stmt, ret_stmt }) } @@ -334,17 +309,12 @@ mod test { #[no_mangle] fn init() { - let total_supply = odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg("total_supply"); - let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); - let mut contract = Erc20::new(Rc::new(env)); - contract.init(total_supply); + execute_init(odra::odra_casper_wasm_env::WasmContractEnv::new_env()); } #[no_mangle] fn total_supply() { - let env = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); - let contract = Erc20::new(Rc::new(env)); - let result = contract.total_supply(); + let result = execute_total_supply(odra::odra_casper_wasm_env::WasmContractEnv::new_env()); odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::ret( odra::odra_casper_wasm_env::casper_contract::unwrap_or_revert::UnwrapOrRevert::unwrap_or_revert( odra::casper_types::CLValue::from_t(result) diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index ea1eba2a..c42fb9e5 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -160,6 +160,17 @@ impl ModuleIR { )) } + pub fn exec_parts_mod_ident(&self) -> Result { + let module_ident = self.module_ident()?; + Ok(Ident::new( + &format!( + "__{}_exec_parts", + crate::utils::string::camel_to_snake(&module_ident) + ), + module_ident.span() + )) + } + pub fn functions(&self) -> Vec { self.code .items @@ -194,11 +205,6 @@ impl ModuleIR { }) .unwrap_or_default() } - - pub fn module_instance_expr(&self, env_ident: syn::Ident) -> Result { - let module_ident = self.module_ident()?; - Ok(parse_quote!(#module_ident::new(Rc::new(#env_ident)))) - } } pub struct FnIR { @@ -218,6 +224,10 @@ impl FnIR { format_ident!("try_{}", self.name()) } + pub fn execute_name(&self) -> Ident { + format_ident!("execute_{}", self.name()) + } + pub fn name_str(&self) -> String { self.name().to_string() } @@ -282,10 +292,6 @@ impl FnArgIR { } } - pub fn name_str(&self) -> Result { - self.name().map(|i| i.to_string()) - } - pub fn name_and_ty(&self) -> Result<(String, syn::Type), syn::Error> { match &self.code { syn::FnArg::Typed(syn::PatType { diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index 6f434138..a6fddecc 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -1,9 +1,11 @@ #![feature(box_patterns, result_flattening)] use ast::*; +use derive_try_from::TryFromRef; use ir::{ModuleIR, StructIR}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; use syn::spanned::Spanned; mod ast; @@ -16,47 +18,47 @@ mod utils; pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream { let stream: TokenStream2 = item.into(); if let Ok(ir) = ModuleIR::try_from(&stream) { - return handle_result(module_impl(ir)); + return ModuleImpl::try_from(&ir).into_code(); } if let Ok(ir) = StructIR::try_from(&stream) { - return handle_result(module_struct(ir)); + return ModuleStruct::try_from(&ir).into_code(); } - handle_result(Err(syn::Error::new( - stream.span(), - "Struct or impl block expected" - ))) + syn::Error::new(stream.span(), "Struct or impl block expected") + .to_compile_error() + .into() } -fn module_impl(ir: ModuleIR) -> Result { - let code = ir.self_code(); - let ref_item = RefItem::try_from(&ir)?; - let test_parts = TestParts::try_from(&ir)?; - let test_parts_reexport = TestPartsReexport::try_from(&ir)?; - let wasm_parts = WasmPartsModuleItem::try_from(&ir)?; - - Ok(quote::quote! { - #code - #ref_item - #test_parts - #test_parts_reexport - #wasm_parts - }) +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(ModuleIR)] +struct ModuleImpl { + #[expr(item.self_code().clone())] + self_code: syn::ItemImpl, + ref_item: RefItem, + test_parts: TestPartsItem, + test_parts_reexport: TestPartsReexportItem, + exec_parts: ExecPartsItem, + exec_parts_reexport: ExecPartsReexportItem, + wasm_parts: WasmPartsModuleItem } -fn module_struct(ir: StructIR) -> Result { - let code = ir.self_code(); - let module_mod = ModuleModItem::try_from(&ir)?; +#[derive(syn_derive::ToTokens, TryFromRef)] +#[source(StructIR)] +struct ModuleStruct { + #[expr(item.self_code().clone())] + self_code: syn::ItemStruct, + mod_item: ModuleModItem +} - Ok(quote::quote!( - #code - #module_mod - )) +trait IntoCode { + fn into_code(self) -> TokenStream; } -fn handle_result(result: Result) -> TokenStream { - match result { - Ok(stream) => stream, - Err(e) => e.to_compile_error() +impl IntoCode for Result { + fn into_code(self) -> TokenStream { + match self { + Ok(data) => data.to_token_stream(), + Err(e) => e.to_compile_error() + } + .into() } - .into() } diff --git a/odra-macros/src/utils/expr.rs b/odra-macros/src/utils/expr.rs index abda0452..72672256 100644 --- a/odra-macros/src/utils/expr.rs +++ b/odra-macros/src/utils/expr.rs @@ -65,3 +65,7 @@ pub fn new_schemas() -> syn::Expr { let ty = super::ty::schemas(); parse_quote!(#ty::new()) } + +pub fn new_wasm_contract_env() -> syn::Expr { + parse_quote!(odra::odra_casper_wasm_env::WasmContractEnv::new_env()) +} diff --git a/odra-macros/src/utils/stmt.rs b/odra-macros/src/utils/stmt.rs index 10d33f63..ee300dd5 100644 --- a/odra-macros/src/utils/stmt.rs +++ b/odra-macros/src/utils/stmt.rs @@ -1,12 +1,5 @@ use syn::parse_quote; -pub fn read_runtime_arg(ident: &syn::Ident) -> syn::Stmt { - let name_str = ident.to_string(); - parse_quote!( - let #ident = odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::get_named_arg(#name_str); - ) -} - pub fn runtime_return(result_ident: &syn::Ident) -> syn::Stmt { parse_quote!( odra::odra_casper_wasm_env::casper_contract::contract_api::runtime::ret( @@ -17,12 +10,6 @@ pub fn runtime_return(result_ident: &syn::Ident) -> syn::Stmt { ) } -pub fn new_wasm_contract_env(ident: &syn::Ident) -> syn::Stmt { - parse_quote!( - let #ident = odra::odra_casper_wasm_env::WasmContractEnv::new_env(); - ) -} - pub fn new_module( contract_ident: &syn::Ident, module_ident: &syn::Ident, @@ -50,3 +37,8 @@ pub fn install_contract(entry_points: syn::Expr, schemas: syn::Expr, args: syn:: #args );) } + +pub fn get_named_arg(arg_ident: &syn::Ident, env_ident: &syn::Ident) -> syn::Stmt { + let arg_name = arg_ident.to_string(); + parse_quote!(let #arg_ident = #env_ident.get_named_arg(#arg_name);) +} diff --git a/odra-vm/src/odra_vm_contract_env.rs b/odra-vm/src/odra_vm_contract_env.rs index 53e391bb..346d41b3 100644 --- a/odra-vm/src/odra_vm_contract_env.rs +++ b/odra-vm/src/odra_vm_contract_env.rs @@ -48,6 +48,10 @@ impl ContractContext for OdraVmContractEnv { fn revert(&self, error: OdraError) -> ! { self.vm.borrow().revert(error) } + + fn get_named_arg(&self, name: &str) -> Bytes { + self.vm.borrow().get_named_arg(name).into() + } } impl OdraVmContractEnv { diff --git a/odra-vm/src/vm/callstack.rs b/odra-vm/src/vm/callstack.rs index cb1de717..25cc1bc2 100644 --- a/odra-vm/src/vm/callstack.rs +++ b/odra-vm/src/vm/callstack.rs @@ -1,4 +1,4 @@ -use odra_core::{casper_types::U512, Address}; +use odra_core::{casper_types::U512, Address, CallDef}; #[derive(Clone)] pub enum CallstackElement { @@ -18,17 +18,12 @@ impl CallstackElement { #[derive(Clone)] pub struct Entrypoint { pub address: Address, - pub entrypoint: String, - pub attached_value: U512 + pub call_def: CallDef } impl Entrypoint { - pub fn new(address: Address, entrypoint: &str, value: U512) -> Self { - Self { - address, - entrypoint: entrypoint.to_string(), - attached_value: value - } + pub fn new(address: Address, call_def: CallDef) -> Self { + Self { address, call_def } } } @@ -48,13 +43,13 @@ impl Callstack { let ce = self.0.last().unwrap(); match ce { CallstackElement::Account(_) => U512::zero(), - CallstackElement::Entrypoint(e) => e.attached_value + CallstackElement::Entrypoint(e) => e.call_def.amount } } pub fn attach_value(&mut self, amount: U512) { if let Some(CallstackElement::Entrypoint(entrypoint)) = self.0.last_mut() { - entrypoint.attached_value = amount; + entrypoint.call_def.amount = amount; } } diff --git a/odra-vm/src/vm/odra_vm.rs b/odra-vm/src/vm/odra_vm.rs index 6a6212bc..d745f37c 100644 --- a/odra-vm/src/vm/odra_vm.rs +++ b/odra-vm/src/vm/odra_vm.rs @@ -58,7 +58,7 @@ impl OdraVm { } pub fn call_contract(&self, address: Address, call_def: CallDef) -> Bytes { - self.prepare_call(address, &call_def.entry_point, call_def.amount); + self.prepare_call(address, call_def.clone()); // Call contract from register. if call_def.amount > U512::zero() { let status = self.checked_transfer_tokens(&self.caller(), &address, &call_def.amount); @@ -84,7 +84,7 @@ impl OdraVm { // self.handle_call_result(result) // } - fn prepare_call(&self, address: Address, entrypoint: &str, amount: U512) { + fn prepare_call(&self, address: Address, call_def: CallDef) { let mut state = self.state.write().unwrap(); // If only one address on the call_stack, record snapshot. if state.is_in_caller_context() { @@ -93,7 +93,7 @@ impl OdraVm { } // Put the address on stack. - let element = CallstackElement::Entrypoint(Entrypoint::new(address, entrypoint, amount)); + let element = CallstackElement::Entrypoint(Entrypoint::new(address, call_def)); state.push_callstack_element(element); } @@ -128,7 +128,7 @@ impl OdraVm { pub fn revert(&self, error: OdraError) -> ! { let mut revert_msg = String::from(""); if let CallstackElement::Entrypoint(ep) = self.callstack_tip() { - revert_msg = format!("{:?}::{}", ep.address, ep.entrypoint); + revert_msg = format!("{:?}::{}", ep.address, ep.call_def.entry_point); } let mut state = self.state.write().unwrap(); @@ -163,6 +163,15 @@ impl OdraVm { self.state.read().unwrap().callstack_tip().clone() } + pub fn get_named_arg(&self, name: &str) -> Vec { + match self.state.read().unwrap().callstack_tip() { + CallstackElement::Account(_) => todo!(), + CallstackElement::Entrypoint(ep) => { + ep.call_def.args.get(name).unwrap().inner_bytes().to_vec() + } + } + } + pub fn set_caller(&self, caller: Address) { self.state.write().unwrap().set_caller(caller); } diff --git a/try-from-macro/Cargo.toml b/try-from-macro/Cargo.toml new file mode 100644 index 00000000..ce8e68f4 --- /dev/null +++ b/try-from-macro/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "derive-try-from" +edition = "2021" +version = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } + +[dependencies] +proc-macro2 = "1.0.69" +proc-macro-error = "1.0.4" +quote = "1.0.33" +syn = { version = "2.0.29", features = ["full"] } + +[lib] +proc-macro = true diff --git a/try-from-macro/src/lib.rs b/try-from-macro/src/lib.rs new file mode 100644 index 00000000..97db03b7 --- /dev/null +++ b/try-from-macro/src/lib.rs @@ -0,0 +1,91 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use proc_macro_error::{abort, proc_macro_error}; + +#[proc_macro_derive(TryFromRef, attributes(source, default, expr))] +#[proc_macro_error] +pub fn derive_try_from(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + if !input.generics.params.is_empty() { + abort! { + input.generics, + "Generics are not supported!" + } + } + let from_ident = match parse_attrs(&input.attrs) { + Ok(Attr::Source(ident)) => ident, + _ => abort! { + input.ident, + "Missing source attribute"; + help = "Add #[source(YourStruct)]"; + } + }; + let to_ident = &input.ident; + + match &input.data { + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(named), + .. + }) => derive_convert_struct(&from_ident, to_ident, named), + _ => abort! { + input, + "Unions and Enums are not supported!" + } + } + .into() +} + +fn derive_convert_struct( + from_ident: &proc_macro2::Ident, + to_ident: &proc_macro2::Ident, + fields: &syn::FieldsNamed +) -> proc_macro2::TokenStream { + let fields = fields + .named + .iter() + .map(to_field_definition) + .collect::>(); + quote::quote!( + impl TryFrom<&'_ #from_ident> for #to_ident { + type Error = syn::Error; + + fn try_from(item: &'_ #from_ident) -> Result { + Ok(Self { + #( #fields ),* + }) + } + } + ) +} + +fn to_field_definition(field: &syn::Field) -> proc_macro2::TokenStream { + let ident = &field.ident; + match parse_attrs(&field.attrs) { + Ok(Attr::Default) => quote::quote!(#ident: Default::default()), + Ok(Attr::Expr(expr)) => quote::quote!(#ident: #expr), + _ => quote::quote!(#ident: item.try_into()?) + } +} + +enum Attr { + Source(syn::Ident), + Default, + Expr(syn::Expr) +} + +fn parse_attrs(attrs: &[syn::Attribute]) -> syn::Result { + if let Some(attr) = find_attr(attrs, "source") { + return Ok(Attr::Source(attr.parse_args()?)); + } + if find_attr(attrs, "default").is_some() { + return Ok(Attr::Default); + } + if let Some(attr) = find_attr(attrs, "expr") { + return Ok(Attr::Expr(attr.parse_args()?)); + } + Err(syn::Error::new(Span::call_site(), "No attr found")) +} + +fn find_attr<'a>(attrs: &'a [syn::Attribute], ident: &str) -> Option<&'a syn::Attribute> { + attrs.iter().find(|attr| attr.path().is_ident(ident)) +} From 34eb74fdb3ee9d74a70413cd74c9b6211ef0062c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 1 Dec 2023 10:47:33 +0100 Subject: [PATCH 08/12] fix tests --- odra-macros/src/ast/test_parts.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 6c17cab6..240dde00 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -135,12 +135,11 @@ mod test { let caller = odra::EntryPointsCaller::new(env.clone(), |contract_env, call_def| { match call_def.method() { "init" => { - let result = Erc20::new(Rc::new(contract_env)) - .init(call_def.get("total_supply").expect("arg not found")); + let result = execute_init(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } "total_supply" => { - let result = Erc20::new(Rc::new(contract_env)).total_supply(); + let result = execute_total_supply(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } _ => panic!("Unknown method") From 2c5c3fe0aa05761451f383b908cde9d625398ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Fri, 1 Dec 2023 15:47:39 +0100 Subject: [PATCH 09/12] Add odra attributes --- core/src/contract_context.rs | 2 + core/src/contract_env.rs | 58 +++- core/src/lib.rs | 2 +- core/src/mapping.rs | 2 +- examples2/src/erc20.rs | 1 + odra-casper/wasm-env/src/wasm_contract_env.rs | 8 + odra-macros/Cargo.toml | 3 +- odra-macros/src/ast/deployer_item.rs | 8 + odra-macros/src/ast/exec_parts.rs | 112 ++++++- odra-macros/src/ast/host_ref_item.rs | 50 +++ odra-macros/src/ast/ref_item.rs | 30 ++ odra-macros/src/ast/test_parts.rs | 50 +++ odra-macros/src/ast/wasm_parts.rs | 35 +++ odra-macros/src/ir/attr.rs | 286 ++++++++++++++++++ odra-macros/src/ir/mod.rs | 30 +- odra-macros/src/lib.rs | 2 +- odra-macros/src/test_utils.rs | 17 ++ odra-macros/src/utils/ident.rs | 8 + odra-macros/src/utils/stmt.rs | 16 +- odra-vm/src/odra_vm_contract_env.rs | 8 + odra-vm/src/odra_vm_host.rs | 2 +- 21 files changed, 698 insertions(+), 32 deletions(-) create mode 100644 odra-macros/src/ir/attr.rs diff --git a/core/src/contract_context.rs b/core/src/contract_context.rs index d2bae2e0..8335f068 100644 --- a/core/src/contract_context.rs +++ b/core/src/contract_context.rs @@ -13,4 +13,6 @@ pub trait ContractContext { fn transfer_tokens(&self, to: &Address, amount: &U512); fn revert(&self, error: OdraError) -> !; fn get_named_arg(&self, name: &str) -> Bytes; + fn handle_attached_value(&self); + fn clear_attached_value(&self); } diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index bfc70361..435fe59c 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -1,9 +1,8 @@ use crate::call_def::CallDef; -use crate::prelude::*; -use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512}; - use crate::key_maker; pub use crate::ContractContext; +use crate::{prelude::*, ExecutionError}; +use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512}; pub struct ContractEnv { index: u32, @@ -20,7 +19,7 @@ impl ContractEnv { } } - pub fn duplicate(&self) -> Self { + pub fn __duplicate(&self) -> Self { Self { index: self.index, mapping_data: self.mapping_data.clone(), @@ -28,12 +27,7 @@ impl ContractEnv { } } - pub fn get_named_arg(&self, name: &str) -> T { - let bytes = self.backend.borrow().get_named_arg(name); - T::from_bytes(&bytes).unwrap().0 - } - - pub fn current_key(&self) -> Vec { + pub(crate) fn current_key(&self) -> Vec { let index_bytes = key_maker::u32_to_hex(self.index); let mapping_data_bytes = key_maker::bytes_to_hex(&self.mapping_data); let mut key = Vec::new(); @@ -42,11 +36,11 @@ impl ContractEnv { key } - pub fn add_to_mapping_data(&mut self, data: &[u8]) { + pub(crate) fn add_to_mapping_data(&mut self, data: &[u8]) { self.mapping_data.extend_from_slice(data); } - pub fn child(&self, index: u8) -> Self { + pub(crate) fn child(&self, index: u8) -> Self { Self { index: (self.index << 4) + index as u32, mapping_data: self.mapping_data.clone(), @@ -107,3 +101,43 @@ impl ContractEnv { backend.emit_event(&event.to_bytes().unwrap().into()) } } + +pub struct ExecutionEnv { + env: Rc +} + +impl ExecutionEnv { + pub fn new(env: Rc) -> Self { + Self { env } + } + + pub fn non_reentrant_before(&self) { + let status: bool = self + .env + .get_value(crate::consts::REENTRANCY_GUARD.as_slice()) + .unwrap_or_default(); + if status { + self.env.revert(ExecutionError::ReentrantCall); + } + self.env + .set_value(crate::consts::REENTRANCY_GUARD.as_slice(), true); + } + + pub fn non_reentrant_after(&self) { + self.env + .set_value(crate::consts::REENTRANCY_GUARD.as_slice(), false); + } + + pub fn handle_attached_value(&self) { + self.env.backend.borrow().handle_attached_value(); + } + + pub fn clear_attached_value(&self) { + self.env.backend.borrow().clear_attached_value(); + } + + pub fn get_named_arg(&self, name: &str) -> T { + let bytes = self.env.backend.borrow().get_named_arg(name); + T::from_bytes(&bytes).unwrap().0 + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 092ef13c..f3c09fe2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -32,7 +32,7 @@ pub use address::{Address, OdraAddress}; pub use call_def::CallDef; pub use casper_event_standard; pub use contract_context::ContractContext; -pub use contract_env::ContractEnv; +pub use contract_env::{ContractEnv, ExecutionEnv}; pub use entry_point_callback::EntryPointsCaller; pub use error::{AddressError, CollectionError, ExecutionError, OdraError, VmError}; pub use host_context::HostContext; diff --git a/core/src/mapping.rs b/core/src/mapping.rs index 8977826c..8cb60e9f 100644 --- a/core/src/mapping.rs +++ b/core/src/mapping.rs @@ -24,7 +24,7 @@ impl Mapping { impl Mapping { fn env_for_key(&self, key: K) -> ContractEnv { - let mut env = self.parent_env.duplicate(); + let mut env = self.parent_env.__duplicate(); let key = key.to_bytes().unwrap_or_default(); env.add_to_mapping_data(&key); env diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index 2a8d295c..653e35d9 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -94,6 +94,7 @@ impl Erc20 { self.total_supply() + other_erc20.total_supply() } + #[odra(payable)] pub fn pay_to_mint(&mut self) { let attached_value = self.env().attached_value(); let caller = self.env().caller(); diff --git a/odra-casper/wasm-env/src/wasm_contract_env.rs b/odra-casper/wasm-env/src/wasm_contract_env.rs index b3da7615..3be6b984 100644 --- a/odra-casper/wasm-env/src/wasm_contract_env.rs +++ b/odra-casper/wasm-env/src/wasm_contract_env.rs @@ -53,6 +53,14 @@ impl ContractContext for WasmContractEnv { fn get_named_arg(&self, name: &str) -> Bytes { host_functions::get_named_arg(name).into() } + + fn handle_attached_value(&self) { + host_functions::handle_attached_value(); + } + + fn clear_attached_value(&self) { + host_functions::clear_attached_value(); + } } impl WasmContractEnv { diff --git a/odra-macros/Cargo.toml b/odra-macros/Cargo.toml index be17943b..a37db091 100644 --- a/odra-macros/Cargo.toml +++ b/odra-macros/Cargo.toml @@ -16,8 +16,9 @@ quote = "1.0.33" syn = { version = "2.0.29", features = ["full"] } syn_derive = "0.1.8" convert_case = { version = "0.5.0" } -derive_convert = "0.4.0" derive-try-from ={ path = "../try-from-macro" } +derive_more = "0.99.17" +itertools = "0.12.0" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/odra-macros/src/ast/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs index d250079d..0cb460a8 100644 --- a/odra-macros/src/ast/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -98,6 +98,14 @@ mod deployer_impl { let result = execute_total_supply(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } + "pay_to_mint" => { + let result = execute_pay_to_mint(contract_env); + odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() + } + "approve" => { + let result = execute_approve(contract_env); + odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() + } _ => panic!("Unknown method") } }); diff --git a/odra-macros/src/ast/exec_parts.rs b/odra-macros/src/ast/exec_parts.rs index 10a4ed1b..56760200 100644 --- a/odra-macros/src/ast/exec_parts.rs +++ b/odra-macros/src/ast/exec_parts.rs @@ -63,11 +63,25 @@ struct ExecFunctionItem { #[syn(braced)] braces: syn::token::Brace, #[syn(in = braces)] + env_rc_stmt: syn::Stmt, + #[syn(in = braces)] + exec_env_stmt: Option, + #[syn(in = braces)] + non_reentrant_before_stmt: Option, + #[syn(in = braces)] + handle_attached_value_stmt: Option, + #[syn(in = braces)] #[to_tokens(|tokens, f| tokens.append_all(f))] args: Vec, #[syn(in = braces)] init_contract_stmt: syn::Stmt, #[syn(in = braces)] + call_contract_stmt: syn::Stmt, + #[syn(in = braces)] + clear_attached_value_stmt: Option, + #[syn(in = braces)] + non_reentrant_after_stmt: Option, + #[syn(in = braces)] return_stmt: syn::Stmt } @@ -77,7 +91,12 @@ impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for ExecFunctionItem { fn try_from(value: (&'_ ModuleIR, &'_ FnIR)) -> Result { let (module, func) = value; let fn_ident = func.name(); + let result_ident = utils::ident::result(); + let env_rc_ident = utils::ident::env_rc(); let env_ident = utils::ident::env(); + let exec_env_ident = utils::ident::exec_env(); + let exec_env_stmt = (func.is_payable() || func.is_non_reentrant() || func.has_args()) + .then(|| utils::stmt::new_execution_env(&exec_env_ident, &env_rc_ident)); let contract_ident = utils::ident::contract(); let module_ident = module.module_ident()?; let fn_args = func.arg_names(); @@ -85,21 +104,32 @@ impl TryFrom<(&'_ ModuleIR, &'_ FnIR)> for ExecFunctionItem { let args = func .arg_names() .iter() - .map(|ident| utils::stmt::get_named_arg(ident, &env_ident)) + .map(|ident| utils::stmt::get_named_arg(ident, &exec_env_ident)) .collect(); let init_contract_stmt = match func.is_mut() { - true => utils::stmt::new_mut_module(&contract_ident, &module_ident, &env_ident), - false => utils::stmt::new_module(&contract_ident, &module_ident, &env_ident) + true => utils::stmt::new_mut_module(&contract_ident, &module_ident, &env_rc_ident), + false => utils::stmt::new_module(&contract_ident, &module_ident, &env_rc_ident) }; Ok(Self { inline_attr: utils::attr::inline(), sig: func.try_into()?, braces: Default::default(), + env_rc_stmt: utils::stmt::new_rc(&env_rc_ident, &env_ident), + exec_env_stmt, + non_reentrant_before_stmt: func + .is_non_reentrant() + .then(ExecEnvStmt::non_reentrant_before), + handle_attached_value_stmt: func.is_payable().then(ExecEnvStmt::handle_attached_value), args, init_contract_stmt, - return_stmt: parse_quote!(return #contract_ident.#fn_ident( #(#fn_args),* );) + call_contract_stmt: parse_quote!(let #result_ident = #contract_ident.#fn_ident(#(#fn_args),*);), + clear_attached_value_stmt: func.is_payable().then(ExecEnvStmt::clear_attached_value), + non_reentrant_after_stmt: func + .is_non_reentrant() + .then(ExecEnvStmt::non_reentrant_after), + return_stmt: parse_quote!(return #result_ident;) }) } } @@ -146,6 +176,41 @@ struct ExecPartsModuleItem { ident: syn::Ident } +#[derive(syn_derive::ToTokens)] +struct ExecEnvStmt { + ident: syn::Ident, + dot_token: syn::token::Dot, + call_expr: syn::ExprCall, + semi_token: syn::token::Semi +} + +impl ExecEnvStmt { + fn new(call_expr: syn::ExprCall) -> Self { + Self { + ident: utils::ident::exec_env(), + dot_token: Default::default(), + call_expr, + semi_token: Default::default() + } + } + + fn non_reentrant_before() -> Self { + Self::new(parse_quote!(non_reentrant_before())) + } + + fn non_reentrant_after() -> Self { + Self::new(parse_quote!(non_reentrant_after())) + } + + fn handle_attached_value() -> Self { + Self::new(parse_quote!(handle_attached_value())) + } + + fn clear_attached_value() -> Self { + Self::new(parse_quote!(clear_attached_value())) + } +} + #[cfg(test)] mod test { use super::*; @@ -163,15 +228,44 @@ mod test { #[inline] pub fn execute_init(env: odra::ContractEnv) { - let total_supply = env.get_named_arg("total_supply"); - let mut contract = Erc20::new(Rc::new(env)); - return contract.init(total_supply); + let env_rc = Rc::new(env); + let exec_env = odra::ExecutionEnv::new(env_rc.clone()); + let total_supply = exec_env.get_named_arg("total_supply"); + let mut contract = Erc20::new(env_rc); + let result = contract.init(total_supply); + return result; } #[inline] pub fn execute_total_supply(env: odra::ContractEnv) -> U256 { - let contract = Erc20::new(Rc::new(env)); - return contract.total_supply(); + let env_rc = Rc::new(env); + let contract = Erc20::new(env_rc); + let result = contract.total_supply(); + return result; + } + + #[inline] + pub fn execute_pay_to_mint(env: odra::ContractEnv) { + let env_rc = Rc::new(env); + let exec_env = odra::ExecutionEnv::new(env_rc.clone()); + exec_env.handle_attached_value(); + let mut contract = Erc20::new(env_rc); + let result = contract.pay_to_mint(); + exec_env.clear_attached_value(); + return result; + } + + #[inline] + pub fn execute_approve(env: odra::ContractEnv) { + let env_rc = Rc::new(env); + let exec_env = odra::ExecutionEnv::new(env_rc.clone()); + exec_env.non_reentrant_before(); + let to = exec_env.get_named_arg("to"); + let amount = exec_env.get_named_arg("amount"); + let mut contract = Erc20::new(env_rc); + let result = contract.approve(to, amount); + exec_env.non_reentrant_after(); + return result; } } }; diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs index 954ebe1a..48caa319 100644 --- a/odra-macros/src/ast/host_ref_item.rs +++ b/odra-macros/src/ast/host_ref_item.rs @@ -208,6 +208,56 @@ mod ref_item_tests { pub fn total_supply(&self) -> U256 { self.try_total_supply().unwrap() } + + pub fn try_pay_to_mint(&mut self) -> Result<(), odra::OdraError> { + self.env + .call_contract( + self.address, + odra::CallDef::new( + String::from("pay_to_mint"), + { + let mut named_args = odra::RuntimeArgs::new(); + if self.attached_value > odra::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + named_args + }, + ) + .with_amount(self.attached_value), + ) + } + + pub fn pay_to_mint(&mut self) { + self.try_pay_to_mint().unwrap() + } + + pub fn try_approve( + &mut self, + to: Address, + amount: U256, + ) -> Result<(), odra::OdraError> { + self.env + .call_contract( + self.address, + odra::CallDef::new( + String::from("approve"), + { + let mut named_args = odra::RuntimeArgs::new(); + if self.attached_value > odra::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + let _ = named_args.insert("to", to); + let _ = named_args.insert("amount", amount); + named_args + }, + ) + .with_amount(self.attached_value), + ) + } + + pub fn approve(&mut self, to: Address, amount: U256) { + self.try_approve(to, amount).unwrap() + } } }; let actual = HostRefItem::try_from(&module).unwrap(); diff --git a/odra-macros/src/ast/ref_item.rs b/odra-macros/src/ast/ref_item.rs index 852f6357..ec107461 100644 --- a/odra-macros/src/ast/ref_item.rs +++ b/odra-macros/src/ast/ref_item.rs @@ -133,6 +133,36 @@ mod ref_item_tests { ), ) } + + pub fn pay_to_mint(&mut self) { + self.env + .call_contract( + self.address, + odra::CallDef::new( + String::from("pay_to_mint"), + { + let mut named_args = odra::RuntimeArgs::new(); + named_args + }, + ), + ) + } + + pub fn approve(&mut self, to: Address, amount: U256) { + self.env + .call_contract( + self.address, + odra::CallDef::new( + String::from("approve"), + { + let mut named_args = odra::RuntimeArgs::new(); + let _ = named_args.insert("to", to); + let _ = named_args.insert("amount", amount); + named_args + }, + ), + ) + } } }; let actual = RefItem::try_from(&module).unwrap(); diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs index 240dde00..d427c7ab 100644 --- a/odra-macros/src/ast/test_parts.rs +++ b/odra-macros/src/ast/test_parts.rs @@ -126,6 +126,48 @@ mod test { pub fn total_supply(&self) -> U256 { self.try_total_supply().unwrap() } + + pub fn try_pay_to_mint(&mut self) -> Result<(), odra::OdraError> { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("pay_to_mint"), + { + let mut named_args = odra::RuntimeArgs::new(); + if self.attached_value > odra::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + named_args + } + ).with_amount(self.attached_value), + ) + } + + pub fn pay_to_mint(&mut self) { + self.try_pay_to_mint().unwrap() + } + + pub fn try_approve(&mut self, to: Address, amount: U256) -> Result<(), odra::OdraError> { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("approve"), + { + let mut named_args = odra::RuntimeArgs::new(); + if self.attached_value > odra::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + let _ = named_args.insert("to", to); + let _ = named_args.insert("amount", amount); + named_args + } + ).with_amount(self.attached_value), + ) + } + + pub fn approve(&mut self, to: Address, amount: U256) { + self.try_approve(to, amount).unwrap() + } } pub struct Erc20Deployer; @@ -142,6 +184,14 @@ mod test { let result = execute_total_supply(contract_env); odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() } + "pay_to_mint" => { + let result = execute_pay_to_mint(contract_env); + odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() + } + "approve" => { + let result = execute_approve(contract_env); + odra::ToBytes::to_bytes(&result).map(Into::into).unwrap() + } _ => panic!("Unknown method") } }); diff --git a/odra-macros/src/ast/wasm_parts.rs b/odra-macros/src/ast/wasm_parts.rs index c788c3ab..c5c6ccbd 100644 --- a/odra-macros/src/ast/wasm_parts.rs +++ b/odra-macros/src/ast/wasm_parts.rs @@ -287,6 +287,31 @@ mod test { odra::casper_types::EntryPointType::Contract )); entry_points + .add_entry_point( + odra::casper_types::EntryPoint::new( + "pay_to_mint", + vec![], + <() as odra::casper_types::CLTyped>::cl_type(), + odra::casper_types::EntryPointAccess::Public, + odra::casper_types::EntryPointType::Contract, + ), + ); + entry_points + .add_entry_point( + odra::casper_types::EntryPoint::new( + "approve", + vec![ + odra::casper_types::Parameter::new("to", < Address as + odra::casper_types::CLTyped > ::cl_type()), + odra::casper_types::Parameter::new("amount", < U256 as + odra::casper_types::CLTyped > ::cl_type()) + ], + <() as odra::casper_types::CLTyped>::cl_type(), + odra::casper_types::EntryPointAccess::Public, + odra::casper_types::EntryPointType::Contract, + ), + ); + entry_points } #[no_mangle] @@ -321,6 +346,16 @@ mod test { ) ); } + + #[no_mangle] + fn pay_to_mint() { + execute_pay_to_mint(odra::odra_casper_wasm_env::WasmContractEnv::new_env()); + } + + #[no_mangle] + fn approve() { + execute_approve(odra::odra_casper_wasm_env::WasmContractEnv::new_env()); + } } }; diff --git a/odra-macros/src/ir/attr.rs b/odra-macros/src/ir/attr.rs new file mode 100644 index 00000000..3c6de0fa --- /dev/null +++ b/odra-macros/src/ir/attr.rs @@ -0,0 +1,286 @@ +use std::{ + collections::HashSet, + convert::TryFrom, + fmt::{self, Formatter} +}; + +use itertools::{Either, Itertools}; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use syn::{punctuated::Punctuated, Meta, Path, Token}; + +#[derive(Clone)] +pub enum Attribute { + Odra(OdraAttribute), + Other(syn::Attribute) +} + +impl PartialEq for Attribute { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Attribute::Odra(a), Attribute::Odra(b)) => a == b, + (Attribute::Other(a), Attribute::Other(b)) => { + a.to_token_stream().to_string() == b.to_token_stream().to_string() + } + _ => false + } + } +} + +impl std::fmt::Debug for Attribute { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Attribute::Odra(odra_attr) => odra_attr.fmt(f), + Attribute::Other(other_attr) => f + .debug_struct("syn::Attribute") + .field("value", &other_attr.to_token_stream().to_string()) + .finish() + } + } +} + +impl TryFrom for Attribute { + type Error = syn::Error; + + fn try_from(attr: syn::Attribute) -> Result { + if attr.path().is_ident("odra") { + return >::try_from(attr).map(Into::into); + } + Ok(Attribute::Other(attr)) + } +} + +impl From for Attribute { + fn from(odra_attribute: OdraAttribute) -> Self { + Attribute::Odra(odra_attribute) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct OdraAttribute { + types: Vec +} + +impl OdraAttribute { + pub fn is_payable(&self) -> bool { + self.types + .iter() + .any(|attr_kind| matches!(attr_kind, &AttrType::Payable)) + } + + pub fn is_non_reentrant(&self) -> bool { + self.types + .iter() + .any(|attr_kind| matches!(attr_kind, &AttrType::NonReentrant)) + } +} + +impl TryFrom for OdraAttribute { + type Error = syn::Error; + + fn try_from(attr: syn::Attribute) -> Result { + let attrs = attr.parse_args_with(Punctuated::::parse_terminated)?; + let types = attrs + .iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + ensure_no_duplicates(&types)?; + + Ok(OdraAttribute { types }) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +enum AttrType { + Payable, + NonReentrant +} + +impl TryFrom<&'_ syn::Meta> for AttrType { + type Error = syn::Error; + + fn try_from(meta: &'_ syn::Meta) -> Result { + match meta { + Meta::Path(path) => match path.try_to_string(path)?.as_str() { + "payable" => Ok(AttrType::Payable), + "non_reentrant" => Ok(AttrType::NonReentrant), + _ => Err(AttrTypeError::Path(meta).into()) + }, + Meta::List(_) => Err(AttrTypeError::List(meta).into()), + Meta::NameValue(_) => Err(AttrTypeError::NameValue(meta).into()) + } + } +} + +trait TryToString { + fn try_to_string(&self, span: &T) -> Result; +} + +impl TryToString for Path { + fn try_to_string(&self, span: &T) -> Result { + self.get_ident() + .map(Ident::to_string) + .ok_or_else(|| syn::Error::new_spanned(span, "unknown Odra attribute argument (path)")) + } +} + +enum AttrTypeError<'a, T> { + List(&'a T), + Path(&'a T), + NameValue(&'a T) +} + +impl AttrTypeError<'_, T> { + fn span(&self) -> &T { + match self { + AttrTypeError::List(span) => span, + AttrTypeError::Path(span) => span, + AttrTypeError::NameValue(span) => span + } + } +} + +impl From> for syn::Error { + fn from(value: AttrTypeError<'_, T>) -> Self { + let ty = match value { + AttrTypeError::List(_) => "list", + AttrTypeError::Path(_) => "path", + AttrTypeError::NameValue(_) => "name = value" + }; + syn::Error::new_spanned( + value.span(), + format!("unknown Odra attribute argument ({})", ty) + ) + } +} + +fn ensure_no_duplicates(types: &[AttrType]) -> Result<(), syn::Error> { + let mut set: HashSet<&AttrType> = HashSet::new(); + + let contains_duplicate = types.iter().any(|attr| !set.insert(attr)); + match contains_duplicate { + true => Err(syn::Error::new( + Span::call_site(), + "attr duplicate encountered".to_string() + )), + false => Ok(()) + } +} + +pub fn partition_attributes( + attrs: I +) -> Result<(Vec, Vec), syn::Error> +where + I: IntoIterator +{ + let (odra_attrs, other_attrs): (Vec, Vec) = attrs + .into_iter() + .map(>::try_from) + .collect::, syn::Error>>()? + .into_iter() + .partition_map(|attr| match attr { + Attribute::Odra(odra_attr) => Either::Left(odra_attr), + Attribute::Other(other_attr) => Either::Right(other_attr) + }); + + let attrs = odra_attrs + .clone() + .into_iter() + .flat_map(|attr| attr.types) + .collect::>(); + ensure_no_duplicates(&attrs)?; + Ok((odra_attrs, other_attrs)) +} + +pub fn other_attributes(attrs: I) -> Vec +where + I: IntoIterator +{ + let (_, other_attrs) = partition_attributes(attrs).unwrap_or_default(); + other_attrs +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn payable_attr_works() { + assert_attribute_try_from( + syn::parse_quote! { + #[odra(payable)] + }, + Ok(Attribute::Odra(OdraAttribute { + types: vec![AttrType::Payable] + })) + ); + } + + #[test] + fn non_reentrant_attr_works() { + assert_attribute_try_from( + syn::parse_quote! { + #[odra(non_reentrant)] + }, + Ok(Attribute::Odra(OdraAttribute { + types: vec![AttrType::NonReentrant] + })) + ); + } + + #[test] + fn non_odra_attr_works() { + let expected_value: syn::Attribute = syn::parse_quote! { + #[yoyo(abc)] + }; + assert_attribute_try_from(expected_value.clone(), Ok(Attribute::Other(expected_value))); + } + + #[test] + fn duplicated_attrs_fail() { + assert_attribute_try_from( + syn::parse_quote! { + #[odra(payable, payable)] + }, + Err("attr duplicate encountered") + ); + + assert_attributes_try_from( + vec![ + syn::parse_quote! { #[odra(payable)] }, + syn::parse_quote! { #[odra(payable)] }, + ], + Err("attr duplicate encountered") + ) + } + + fn assert_attribute_try_from(input: syn::Attribute, expected: Result) { + assert_eq!( + >::try_from(input).map_err(|err| err.to_string()), + expected.map_err(ToString::to_string), + ); + } + + fn assert_attributes_try_from( + inputs: Vec, + expected: Result<(Vec, Vec), &'static str> + ) { + let result: Result<(Vec, Vec), String> = partition_attributes(inputs) + .map(|(odra_attrs, other_attrs)| { + ( + odra_attrs + .into_iter() + .map(Attribute::from) + .collect::>(), + other_attrs + .into_iter() + .map(|a| Attribute::Other(a)) + .collect::>() + ) + }) + .map_err(|err| err.to_string()); + assert_eq!(result, expected.map_err(ToString::to_string)); + } +} diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index c42fb9e5..88db2450 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -3,6 +3,10 @@ use proc_macro2::Ident; use quote::format_ident; use syn::{parse_quote, spanned::Spanned}; +use self::attr::OdraAttribute; + +mod attr; + const CONSTRUCTOR_NAME: &str = "init"; macro_rules! try_parse { @@ -103,8 +107,14 @@ pub struct EnumeratedTypedField { try_parse!(syn::ItemImpl => ModuleIR); impl ModuleIR { - pub fn self_code(&self) -> &syn::ItemImpl { - &self.code + pub fn self_code(&self) -> syn::ItemImpl { + let mut code = self.code.clone(); + code.items.iter_mut().for_each(|item| { + if let syn::ImplItem::Fn(func) = item { + func.attrs = attr::other_attributes(func.attrs.clone()); + } + }); + code } pub fn module_ident(&self) -> Result { @@ -232,10 +242,26 @@ impl FnIR { self.name().to_string() } + pub fn is_payable(&self) -> bool { + let (odra_attrs, _) = + attr::partition_attributes(self.code.attrs.clone()).unwrap_or_default(); + odra_attrs.iter().any(OdraAttribute::is_payable) + } + + pub fn is_non_reentrant(&self) -> bool { + let (odra_attrs, _) = + attr::partition_attributes(self.code.attrs.clone()).unwrap_or_default(); + odra_attrs.iter().any(OdraAttribute::is_non_reentrant) + } + pub fn arg_names(&self) -> Vec { utils::syn::function_arg_names(&self.code) } + pub fn has_args(&self) -> bool { + !self.arg_names().is_empty() + } + pub fn named_args(&self) -> Vec { utils::syn::function_named_args(&self.code) .into_iter() diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index a6fddecc..6f222a3c 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -31,7 +31,7 @@ pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream { #[derive(syn_derive::ToTokens, TryFromRef)] #[source(ModuleIR)] struct ModuleImpl { - #[expr(item.self_code().clone())] + #[expr(item.self_code())] self_code: syn::ItemImpl, ref_item: RefItem, test_parts: TestPartsItem, diff --git a/odra-macros/src/test_utils.rs b/odra-macros/src/test_utils.rs index e91525b2..45e7a77e 100644 --- a/odra-macros/src/test_utils.rs +++ b/odra-macros/src/test_utils.rs @@ -15,6 +15,22 @@ pub fn mock_module() -> ModuleIR { pub fn total_supply(&self) -> U256 { self.total_supply.get_or_default() } + + #[odra(payable)] + pub fn pay_to_mint(&mut self) { + let attached_value = self.env().attached_value(); + self.total_supply + .set(self.total_supply() + U256::from(attached_value.as_u64())); + } + + #[odra(non_reentrant)] + pub fn approve(&mut self, to: Address, amount: U256) { + self.env.emit_event(Approval { + owner: self.env.caller(), + spender: to, + value: amount + }); + } } }; ModuleIR::try_from(&module).unwrap() @@ -34,6 +50,7 @@ pub fn mock_module_definition() -> StructIR { StructIR::try_from(&module).unwrap() } +#[track_caller] pub fn assert_eq(a: A, b: B) { fn parse(e: T) -> String { let e = e.to_token_stream().to_string(); diff --git a/odra-macros/src/utils/ident.rs b/odra-macros/src/utils/ident.rs index 8498356d..3a8cdf85 100644 --- a/odra-macros/src/utils/ident.rs +++ b/odra-macros/src/utils/ident.rs @@ -24,6 +24,10 @@ pub fn env() -> syn::Ident { format_ident!("env") } +pub fn exec_env() -> syn::Ident { + format_ident!("exec_env") +} + pub fn init() -> syn::Ident { format_ident!("init") } @@ -55,3 +59,7 @@ pub fn schemas() -> syn::Ident { pub fn contract() -> syn::Ident { format_ident!("contract") } + +pub fn env_rc() -> syn::Ident { + format_ident!("env_rc") +} diff --git a/odra-macros/src/utils/stmt.rs b/odra-macros/src/utils/stmt.rs index ee300dd5..7578b339 100644 --- a/odra-macros/src/utils/stmt.rs +++ b/odra-macros/src/utils/stmt.rs @@ -13,20 +13,20 @@ pub fn runtime_return(result_ident: &syn::Ident) -> syn::Stmt { pub fn new_module( contract_ident: &syn::Ident, module_ident: &syn::Ident, - env_ident: &syn::Ident + env_rc_ident: &syn::Ident ) -> syn::Stmt { parse_quote!( - let #contract_ident = #module_ident::new(Rc::new(#env_ident)); + let #contract_ident = #module_ident::new(#env_rc_ident); ) } pub fn new_mut_module( contract_ident: &syn::Ident, module_ident: &syn::Ident, - env_ident: &syn::Ident + env_rc_ident: &syn::Ident ) -> syn::Stmt { parse_quote!( - let mut #contract_ident = #module_ident::new(Rc::new(#env_ident)); + let mut #contract_ident = #module_ident::new(#env_rc_ident); ) } @@ -42,3 +42,11 @@ pub fn get_named_arg(arg_ident: &syn::Ident, env_ident: &syn::Ident) -> syn::Stm let arg_name = arg_ident.to_string(); parse_quote!(let #arg_ident = #env_ident.get_named_arg(#arg_name);) } + +pub fn new_execution_env(ident: &syn::Ident, env_rc_ident: &syn::Ident) -> syn::Stmt { + parse_quote!(let #ident = odra::ExecutionEnv::new(#env_rc_ident.clone());) +} + +pub fn new_rc(var_ident: &syn::Ident, env_ident: &syn::Ident) -> syn::Stmt { + parse_quote!(let #var_ident = Rc::new(#env_ident);) +} diff --git a/odra-vm/src/odra_vm_contract_env.rs b/odra-vm/src/odra_vm_contract_env.rs index 346d41b3..e255f09a 100644 --- a/odra-vm/src/odra_vm_contract_env.rs +++ b/odra-vm/src/odra_vm_contract_env.rs @@ -52,6 +52,14 @@ impl ContractContext for OdraVmContractEnv { fn get_named_arg(&self, name: &str) -> Bytes { self.vm.borrow().get_named_arg(name).into() } + + fn handle_attached_value(&self) { + // todo!() + } + + fn clear_attached_value(&self) { + // todo!() + } } impl OdraVmContractEnv { diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 61dd8eee..2cf3fd8c 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -84,7 +84,7 @@ impl HostContext for OdraVmHost { } fn contract_env(&self) -> ContractEnv { - self.contract_env.duplicate() + self.contract_env.__duplicate() } fn print_gas_report(&self) { From acaf8c1834ddf49dd91dcdbe56d009923a88140c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 4 Dec 2023 12:22:19 +0100 Subject: [PATCH 10/12] Add reentrancy guard example --- examples2/src/lib.rs | 1 + examples2/src/reentrancy_guard.rs | 78 +++++++++++++++++++++++++++++++ odra-macros/src/utils/stmt.rs | 4 +- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 examples2/src/reentrancy_guard.rs diff --git a/examples2/src/lib.rs b/examples2/src/lib.rs index fca3c4d3..681c3f82 100644 --- a/examples2/src/lib.rs +++ b/examples2/src/lib.rs @@ -9,3 +9,4 @@ extern crate alloc; pub mod counter; pub mod counter_pack; pub mod erc20; +pub mod reentrancy_guard; diff --git a/examples2/src/reentrancy_guard.rs b/examples2/src/reentrancy_guard.rs new file mode 100644 index 00000000..30c4981c --- /dev/null +++ b/examples2/src/reentrancy_guard.rs @@ -0,0 +1,78 @@ +use odra::{prelude::*, ContractEnv, Variable}; + +#[odra::module] +pub struct ReentrancyMock { + env: Rc, + counter: Variable +} + +#[odra::module] +impl ReentrancyMock { + #[odra(non_reentrant)] + pub fn count_local_recursive(&mut self, n: u32) { + if n > 0 { + self.count(); + self.count_local_recursive(n - 1); + } + } + + #[odra(non_reentrant)] + pub fn count_ref_recursive(&mut self, n: u32) { + if n > 0 { + self.count(); + let other_erc20 = ReentrancyMockContractRef { + address: self.env.self_address(), + env: self.env.clone() + } + .count_ref_recursive(n - 1); + } + } + + #[odra(non_reentrant)] + pub fn non_reentrant_count(&mut self) { + self.count(); + } + + pub fn get_value(&self) -> u32 { + self.counter.get_or_default() + } +} + +impl ReentrancyMock { + fn count(&mut self) { + let c = self.counter.get_or_default(); + self.counter.set(c + 1); + } +} + +#[cfg(test)] +mod test { + use odra::test_env; + + use super::ReentrancyMockDeployer; + + #[test] + fn non_reentrant_function_can_be_called() { + let env = odra::test_env(); + + let mut contract = ReentrancyMockDeployer::init(&env); + assert_eq!(contract.get_value(), 0); + contract.non_reentrant_count(); + assert_eq!(contract.get_value(), 1); + } + + #[test] + fn ref_recursion_not_allowed() { + let env = odra::test_env(); + let mut contract = ReentrancyMockDeployer::init(&env); + assert!(contract.try_count_ref_recursive(11).is_err()); + } + + #[test] + fn local_recursion_allowed() { + let env = odra::test_env(); + let mut contract = ReentrancyMockDeployer::init(&env); + contract.count_local_recursive(11); + assert_eq!(contract.get_value(), 11); + } +} diff --git a/odra-macros/src/utils/stmt.rs b/odra-macros/src/utils/stmt.rs index 7578b339..5fd7ca72 100644 --- a/odra-macros/src/utils/stmt.rs +++ b/odra-macros/src/utils/stmt.rs @@ -16,7 +16,7 @@ pub fn new_module( env_rc_ident: &syn::Ident ) -> syn::Stmt { parse_quote!( - let #contract_ident = #module_ident::new(#env_rc_ident); + let #contract_ident = <#module_ident as odra::Module>::new(#env_rc_ident); ) } @@ -26,7 +26,7 @@ pub fn new_mut_module( env_rc_ident: &syn::Ident ) -> syn::Stmt { parse_quote!( - let mut #contract_ident = #module_ident::new(#env_rc_ident); + let mut #contract_ident = <#module_ident as odra::Module>::new(#env_rc_ident); ) } From 2502eefcb7fc7f2b7a299126ccd78b610f573901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 4 Dec 2023 12:32:49 +0100 Subject: [PATCH 11/12] Remove __duplicate() func, replace with derive(Clone) --- core/src/contract_env.rs | 9 +-------- core/src/mapping.rs | 2 +- odra-vm/src/odra_vm_host.rs | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index 435fe59c..ec96dbe3 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -4,6 +4,7 @@ pub use crate::ContractContext; use crate::{prelude::*, ExecutionError}; use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512}; +#[derive(Clone)] pub struct ContractEnv { index: u32, mapping_data: Vec, @@ -19,14 +20,6 @@ impl ContractEnv { } } - pub fn __duplicate(&self) -> Self { - Self { - index: self.index, - mapping_data: self.mapping_data.clone(), - backend: self.backend.clone() - } - } - pub(crate) fn current_key(&self) -> Vec { let index_bytes = key_maker::u32_to_hex(self.index); let mapping_data_bytes = key_maker::bytes_to_hex(&self.mapping_data); diff --git a/core/src/mapping.rs b/core/src/mapping.rs index 8cb60e9f..c884a127 100644 --- a/core/src/mapping.rs +++ b/core/src/mapping.rs @@ -24,7 +24,7 @@ impl Mapping { impl Mapping { fn env_for_key(&self, key: K) -> ContractEnv { - let mut env = self.parent_env.__duplicate(); + let mut env = (*self.parent_env).clone(); let key = key.to_bytes().unwrap_or_default(); env.add_to_mapping_data(&key); env diff --git a/odra-vm/src/odra_vm_host.rs b/odra-vm/src/odra_vm_host.rs index 2cf3fd8c..e45d40aa 100644 --- a/odra-vm/src/odra_vm_host.rs +++ b/odra-vm/src/odra_vm_host.rs @@ -84,7 +84,7 @@ impl HostContext for OdraVmHost { } fn contract_env(&self) -> ContractEnv { - self.contract_env.__duplicate() + (*self.contract_env).clone() } fn print_gas_report(&self) { From 324864f88dce953087efbcd8448c00d1badf8d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 5 Dec 2023 16:58:29 +0100 Subject: [PATCH 12/12] update deserialize_bytes() --- core/src/contract_env.rs | 22 ++++++++-------------- core/src/error.rs | 23 ++++++++++++++++++----- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index c9cdde34..3877afd7 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -3,7 +3,6 @@ pub use crate::ContractContext; use crate::{key_maker, UnwrapOrRevert}; use crate::{prelude::*, ExecutionError}; use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512}; -use crate::ExecutionError::CouldntDeserializeSignature; use casper_types::crypto::PublicKey; #[derive(Clone)] @@ -51,9 +50,7 @@ impl ContractEnv { } pub fn set_value(&self, key: &[u8], value: T) { - let result = value - .to_bytes() - .map_err(|_| ExecutionError::SerializationFailed); + let result = value.to_bytes().map_err(ExecutionError::from); let bytes = result.unwrap_or_revert(self); self.backend.borrow().set_value(key, bytes.into()); } @@ -96,9 +93,7 @@ impl ContractEnv { pub fn emit_event(&self, event: T) { let backend = self.backend.borrow(); - let result = event - .to_bytes() - .map_err(|_| ExecutionError::SerializationFailed); + let result = event.to_bytes().map_err(ExecutionError::from); let bytes = result.unwrap_or_revert(self); backend.emit_event(&bytes.into()) } @@ -110,7 +105,7 @@ impl ContractEnv { public_key: &PublicKey ) -> bool { let (signature, _) = casper_types::crypto::Signature::from_bytes(signature.as_slice()) - .unwrap_or_else(|_| self.revert(CouldntDeserializeSignature)); + .unwrap_or_else(|_| self.revert(ExecutionError::CouldNotDeserializeSignature)); casper_types::crypto::verify(message.as_slice(), &signature, public_key).is_ok() } } @@ -156,15 +151,14 @@ impl ExecutionEnv { } fn deserialize_bytes(bytes: Bytes, env: &ContractEnv) -> T { - let opt_result = match T::from_bytes(&bytes) { + match T::from_bytes(&bytes) { Ok((value, remainder)) => { if remainder.is_empty() { - Some(value) + value } else { - None + env.revert(ExecutionError::LeftOverBytes) } } - Err(_) => None - }; - UnwrapOrRevert::unwrap_or_revert(opt_result, env) + Err(err) => env.revert(ExecutionError::from(err)) + } } diff --git a/core/src/error.rs b/core/src/error.rs index 72632437..6fd7f4bd 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -47,8 +47,16 @@ impl From> for OdraError { } impl From for ExecutionError { - fn from(_: casper_types::bytesrepr::Error) -> Self { - Self::SerializationFailed + fn from(error: casper_types::bytesrepr::Error) -> Self { + match error { + casper_types::bytesrepr::Error::EarlyEndOfStream => Self::EarlyEndOfStream, + casper_types::bytesrepr::Error::Formatting => Self::Formatting, + casper_types::bytesrepr::Error::LeftOverBytes => Self::LeftOverBytes, + casper_types::bytesrepr::Error::OutOfMemory => Self::OutOfMemory, + casper_types::bytesrepr::Error::NotRepresentable => Self::NotRepresentable, + casper_types::bytesrepr::Error::ExceededRecursionDepth => Self::ExceededRecursionDepth, + _ => Self::Formatting + } } } @@ -79,9 +87,14 @@ pub enum ExecutionError { IndexOutOfBounds = 108, ZeroAddress = 109, AddressCreationFailed = 110, - SerializationFailed = 111, - KeyNotFound = 112, - CouldntDeserializeSignature = 113, + EarlyEndOfStream = 111, + Formatting = 112, + LeftOverBytes = 113, + OutOfMemory = 114, + NotRepresentable = 115, + ExceededRecursionDepth = 116, + KeyNotFound = 117, + CouldNotDeserializeSignature = 118, MaxUserError = 32767, /// User error too high. The code should be in range 0..32767. UserErrorTooHigh = 32768,