From 426076c81a6a79ec1fdd6efca6093748cfc03cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Mon, 4 Dec 2023 16:05:58 +0100 Subject: [PATCH] Generate exec parts (#272) * odra reexports odra_macros * 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 * add UnwrapOrRevert --- Cargo.toml | 1 + core/src/contract_context.rs | 1 + core/src/contract_env.rs | 22 ++- core/src/lib.rs | 2 + core/src/unwrap_or_revert.rs | 33 ++++ examples2/Cargo.toml | 1 - examples2/src/counter_pack.rs | 4 +- examples2/src/erc20.rs | 4 +- 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 | 35 ++-- odra-macros/src/ast/wasm_parts.rs | 52 ++--- odra-macros/src/ir/mod.rs | 21 +- 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 +- odra/Cargo.toml | 1 + odra/src/lib.rs | 1 + try-from-macro/Cargo.toml | 17 ++ try-from-macro/src/lib.rs | 91 +++++++++ 30 files changed, 530 insertions(+), 224 deletions(-) create mode 100644 core/src/unwrap_or_revert.rs 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..867adaa1 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_bytes(&self, name: &str) -> Bytes; } diff --git a/core/src/contract_env.rs b/core/src/contract_env.rs index 2047fe6d..d7296cd0 100644 --- a/core/src/contract_env.rs +++ b/core/src/contract_env.rs @@ -1,5 +1,5 @@ use crate::call_def::CallDef; -use crate::prelude::*; +use crate::{prelude::*, UnwrapOrRevert}; use crate::{Address, Bytes, CLTyped, FromBytes, OdraError, ToBytes, U512}; use crate::key_maker; @@ -28,6 +28,22 @@ impl ContractEnv { } } + pub fn get_named_arg(&self, name: &str) -> T { + let bytes = self.backend.borrow().get_named_arg_bytes(name); + + let opt_result = match T::from_bytes(&bytes) { + Ok((value, remainder)) => { + if remainder.is_empty() { + Some(value) + } else { + None + } + } + Err(_) => None + }; + UnwrapOrRevert::unwrap_or_revert(opt_result, self) + } + 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); @@ -53,10 +69,12 @@ impl ContractEnv { self.backend .borrow() .get_value(key) + // TODO: remove unwrap .map(|bytes| T::from_bytes(&bytes).unwrap().0) } pub fn set_value(&self, key: &[u8], value: T) { + // TODO: remove unwrap let bytes = value.to_bytes().unwrap(); self.backend.borrow().set_value(key, Bytes::from(bytes)); } @@ -69,6 +87,7 @@ impl ContractEnv { pub fn call_contract(&self, address: Address, call: CallDef) -> T { let backend = self.backend.borrow(); let bytes = backend.call_contract(address, call); + // TODO: remove unwrap T::from_bytes(&bytes).unwrap().0 } @@ -99,6 +118,7 @@ impl ContractEnv { pub fn emit_event(&self, event: T) { let backend = self.backend.borrow(); + // TODO: remove unwrap backend.emit_event(&event.to_bytes().unwrap().into()) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 092ef13c..a12f37c1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,6 +25,7 @@ mod odra_result; pub mod prelude; pub mod uints; mod unchecked_getter; +mod unwrap_or_revert; pub mod utils; pub mod variable; @@ -41,6 +42,7 @@ pub use item::OdraItem; pub use module::ModuleCaller; pub use odra_result::OdraResult; pub use unchecked_getter::UncheckedGetter; +pub use unwrap_or_revert::UnwrapOrRevert; pub use utils::serialize; pub use casper_types; diff --git a/core/src/unwrap_or_revert.rs b/core/src/unwrap_or_revert.rs new file mode 100644 index 00000000..c45bd07a --- /dev/null +++ b/core/src/unwrap_or_revert.rs @@ -0,0 +1,33 @@ +use crate::{ContractEnv, ExecutionError, OdraError}; + +/// A trait that allows safe unwrapping in the context of a smart contract. +/// On failure the contract does not panic, but reverts calling [`ContractEnv::revert`](crate::ContractEnv::revert()). +/// Works with `Result` and `Option`. +pub trait UnwrapOrRevert { + /// On success, unwraps the value into its inner type, + /// on failure, calls [`ContractEnv::revert`](crate::ContractEnv::revert()) with the passed error. + fn unwrap_or_revert_with>(self, env: &ContractEnv, err: E) -> T; + /// On success, unwraps the value into its inner type, + /// on failure, calls [`ContractEnv::revert`](crate::ContractEnv::revert()) with the default error. + fn unwrap_or_revert(self, env: &ContractEnv) -> T; +} + +impl> UnwrapOrRevert for Result { + fn unwrap_or_revert_with>(self, env: &ContractEnv, err: F) -> T { + self.unwrap_or_else(|_| env.revert(err)) + } + + fn unwrap_or_revert(self, env: &ContractEnv) -> T { + self.unwrap_or_else(|err| env.revert(err)) + } +} + +impl UnwrapOrRevert for Option { + fn unwrap_or_revert_with>(self, env: &ContractEnv, err: E) -> T { + self.unwrap_or_else(|| env.revert(err)) + } + + fn unwrap_or_revert(self, env: &ContractEnv) -> T { + self.unwrap_or_else(|| env.revert(ExecutionError::UnwrapError)) + } +} 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-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..5994e261 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_bytes(&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..240dde00 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"))] @@ -145,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") 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 84279372..53e5fadc 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -162,6 +162,14 @@ impl ModuleIR { )) } + pub fn exec_parts_mod_ident(&self) -> Result { + let module_ident = self.snake_cased_module_ident()?; + Ok(Ident::new( + &format!("__{}_exec_parts", module_ident), + module_ident.span() + )) + } + pub fn functions(&self) -> Vec { self.code .items @@ -196,11 +204,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 { @@ -220,6 +223,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() } @@ -284,10 +291,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..f9bdf0bc 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_bytes(&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..84e19dd7 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); // 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.clone())); 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/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; 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)) +}