From 059f59b2cc9c2e55af63fb97dba49a6fbbf844fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Pobiar=C5=BCyn?= Date: Tue, 21 Nov 2023 11:36:29 +0100 Subject: [PATCH] Generate HostRef (#261) * HostRefItem * add HostRefItem * extract common part of ContractRef and HostRef * change name `method` to `function` * add test_wasm_parts module * add HostRefItem * refactor items --- core/src/host_env.rs | 4 +- examples2/src/counter_pack.rs | 4 +- examples2/src/erc20.rs | 218 ++---------------- odra-macros/Cargo.toml | 3 +- odra-macros/src/{ir => ast}/deployer_item.rs | 43 ++-- odra-macros/src/ast/host_ref_item.rs | 200 ++++++++++++++++ odra-macros/src/ast/mod.rs | 9 + odra-macros/src/ast/parts_utils.rs | 21 ++ odra-macros/src/ast/ref_item.rs | 145 ++++++++++++ odra-macros/src/ast/ref_utils.rs | 90 ++++++++ odra-macros/src/ast/test_parts.rs | 124 ++++++++++ odra-macros/src/ir/mod.rs | 61 +++-- odra-macros/src/ir/ref_item.rs | 152 ------------ odra-macros/src/lib.rs | 38 +-- odra-macros/src/utils/mod.rs | 1 + .../src/{syn_utils/mod.rs => utils/syn.rs} | 14 +- utils/src/lib.rs | 5 +- 17 files changed, 709 insertions(+), 423 deletions(-) rename odra-macros/src/{ir => ast}/deployer_item.rs (56%) create mode 100644 odra-macros/src/ast/host_ref_item.rs create mode 100644 odra-macros/src/ast/mod.rs create mode 100644 odra-macros/src/ast/parts_utils.rs create mode 100644 odra-macros/src/ast/ref_item.rs create mode 100644 odra-macros/src/ast/ref_utils.rs create mode 100644 odra-macros/src/ast/test_parts.rs delete mode 100644 odra-macros/src/ir/ref_item.rs create mode 100644 odra-macros/src/utils/mod.rs rename odra-macros/src/{syn_utils/mod.rs => utils/syn.rs} (83%) diff --git a/core/src/host_env.rs b/core/src/host_env.rs index e21646eb..b5ec1f3d 100644 --- a/core/src/host_env.rs +++ b/core/src/host_env.rs @@ -63,13 +63,13 @@ impl HostEnv { pub fn call_contract( &self, - address: &Address, + address: Address, call_def: CallDef ) -> Result { let backend = self.backend.borrow(); let use_proxy = T::cl_type() != <()>::cl_type() || !call_def.attached_value().is_zero(); - let call_result = backend.call_contract(address, call_def, use_proxy); + let call_result = backend.call_contract(&address, call_def, use_proxy); let mut events_map: BTreeMap> = BTreeMap::new(); let mut binding = self.events_count.borrow_mut(); diff --git a/examples2/src/counter_pack.rs b/examples2/src/counter_pack.rs index b8ca5200..c5288262 100644 --- a/examples2/src/counter_pack.rs +++ b/examples2/src/counter_pack.rs @@ -198,7 +198,7 @@ mod __counter_pack_test_parts { pub fn get_count(&self, index_a: u8, index_b: u8) -> u32 { self.env .call_contract( - &self.address, + self.address, CallDef::new( String::from("get_count"), runtime_args! { @@ -213,7 +213,7 @@ mod __counter_pack_test_parts { pub fn increment(&self, index_a: u8, index_b: u8) { self.env .call_contract( - &self.address, + self.address, CallDef::new( String::from("increment"), runtime_args! { diff --git a/examples2/src/erc20.rs b/examples2/src/erc20.rs index aee36cad..73995686 100644 --- a/examples2/src/erc20.rs +++ b/examples2/src/erc20.rs @@ -426,241 +426,67 @@ mod __erc20_wasm_parts { } #[cfg(not(target_arch = "wasm32"))] -mod __erc20_test_parts { - use crate::erc20::Erc20; - use core::panic; - use core::panic::AssertUnwindSafe; - use odra::casper_event_standard::EventInstance; - use odra::event::EventError; - use odra::prelude::*; - use odra::types::casper_types::EntryPoints; - use odra::types::{ - runtime_args, Address, Bytes, FromBytes, OdraError, RuntimeArgs, ToBytes, U256, U512 - }; - use odra::{CallDef, CallResult, ContractCallResult, ContractEnv, EntryPointsCaller, HostEnv}; - - pub struct Erc20HostRef { - pub address: Address, - pub env: HostEnv, - pub attached_value: U512 - } - - impl Erc20HostRef { - pub fn with_tokens(&self, tokens: U512) -> Self { - Self { - address: self.address, - env: self.env.clone(), - attached_value: tokens - } - } - - pub fn try_total_supply(&self) -> Result { - self.env.call_contract( - &self.address, - CallDef::new(String::from("total_supply"), RuntimeArgs::new()) - ) - } - - pub fn total_supply(&self) -> U256 { - self.try_total_supply().unwrap() - } - - pub fn try_balance_of(&self, owner: Address) -> Result { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("balance_of"), - runtime_args! { - "owner" => owner - } - ) - ) - } - - pub fn balance_of(&self, owner: Address) -> U256 { - self.try_balance_of(owner).unwrap() - } - - pub fn try_transfer(&self, to: Address, value: U256) -> Result<(), OdraError> { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("transfer"), - runtime_args! { - "to" => to, - "value" => value - } - ) - ) - } - - pub fn transfer(&self, to: Address, value: U256) { - self.try_transfer(to, value).unwrap(); - } - - pub fn try_cross_total(&self, other: Address) -> Result { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("cross_total"), - runtime_args! { - "other" => other - } - ) - ) - } - - pub fn cross_total(&self, other: Address) -> U256 { - self.try_cross_total(other).unwrap() - } - - pub fn try_pay_to_mint(&self) -> Result<(), OdraError> { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("pay_to_mint"), - runtime_args! { - "amount" => self.attached_value - } - ) - .with_amount(self.attached_value) - ) - } - - pub fn pay_to_mint(&self) { - self.try_pay_to_mint().unwrap() - } - - pub fn try_get_current_block_time(&self) -> Result { - self.env.call_contract( - &self.address, - CallDef::new(String::from("get_current_block_time"), runtime_args! {}) - ) - } - - pub fn get_current_block_time(&self) -> u64 { - self.try_get_current_block_time().unwrap() - } - - pub fn try_burn_and_get_paid(&self, amount: U256) -> Result<(), OdraError> { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("burn_and_get_paid"), - runtime_args! { - "amount" => amount - } - ) - ) - } - - pub fn burn_and_get_paid(&self, amount: U256) { - self.try_burn_and_get_paid(amount).unwrap() - } - - pub fn try_approve(&self, to: Address, amount: U256) -> Result<(), OdraError> { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("approve"), - runtime_args! { - "to" => to, - "amount" => amount - } - ) - ) - } - - pub fn approve(&self, to: Address, amount: U256) { - self.try_approve(to, amount).unwrap() - } - - pub fn try_cross_transfer( - &self, - other: Address, - to: Address, - value: U256 - ) -> Result<(), OdraError> { - self.env.call_contract( - &self.address, - CallDef::new( - String::from("cross_transfer"), - runtime_args! { - "other" => other, - "to" => to, - "value" => value - } - ) - ) - } - - pub fn cross_transfer(&self, other: Address, to: Address, value: U256) { - self.try_cross_transfer(other, to, value).unwrap() - } - - pub fn last_call(&self) -> ContractCallResult { - self.env.last_call().contract_last_call(self.address) - } - } +mod __erc20_test_parts2 { + use super::*; pub struct Erc20Deployer; impl Erc20Deployer { - pub fn init(env: &HostEnv, total_supply: Option) -> Erc20HostRef { - let epc = EntryPointsCaller::new(env.clone(), |contract_env, call_def| { + pub fn init(env: &odra::HostEnv, total_supply: Option) -> Erc20HostRef { + let epc = odra::EntryPointsCaller::new(env.clone(), |contract_env, call_def| { use odra::types::ToBytes; let mut erc20 = Erc20::new(Rc::new(contract_env)); match call_def.method() { "init" => { let total_supply: Option = call_def.get("total_supply").unwrap(); let result = erc20.init(total_supply); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "total_supply" => { let result = erc20.total_supply(); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "balance_of" => { let owner: Address = call_def.get("owner").unwrap(); let result = erc20.balance_of(owner); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "transfer" => { let to: Address = call_def.get("to").unwrap(); let value: U256 = call_def.get("value").unwrap(); let result = erc20.transfer(to, value); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "cross_total" => { let other: Address = call_def.get("other").unwrap(); let result = erc20.cross_total(other); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "pay_to_mint" => { let result = erc20.pay_to_mint(); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "get_current_block_time" => { let result = erc20.get_current_block_time(); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "burn_and_get_paid" => { let amount: U256 = call_def.get("amount").unwrap(); let result = erc20.burn_and_get_paid(amount); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "approve" => { let to: Address = call_def.get("to").unwrap(); let amount: U256 = call_def.get("amount").unwrap(); let result = erc20.approve(to, amount); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } "cross_transfer" => { let other: Address = call_def.get("other").unwrap(); let to: Address = call_def.get("to").unwrap(); let value: U256 = call_def.get("value").unwrap(); let result = erc20.cross_transfer(other, to, value); - Bytes::from(result.to_bytes().unwrap()) + odra::types::Bytes::from(result.to_bytes().unwrap()) } _ => panic!("Unknown method") } @@ -685,6 +511,8 @@ mod __erc20_test_parts { #[cfg(not(target_arch = "wasm32"))] pub use __erc20_test_parts::*; +#[cfg(not(target_arch = "wasm32"))] +pub use __erc20_test_parts2::*; use odra::types::{runtime_args, ExecutionError, OdraError, RuntimeArgs}; #[cfg(not(target_arch = "wasm32"))] @@ -705,7 +533,7 @@ mod tests { let bob = env.get_account(1); // Deploy the contract as Alice. - let erc20 = Erc20Deployer::init(&env, Some(100.into())); + let mut erc20 = Erc20Deployer::init(&env, Some(100.into())); assert_eq!(erc20.total_supply(), 100.into()); assert_eq!(erc20.balance_of(alice), 100.into()); assert_eq!(erc20.balance_of(bob), 0.into()); @@ -722,7 +550,7 @@ mod tests { assert_eq!(erc20.balance_of(bob), 0.into()); // Test cross calls - let pobcoin = Erc20Deployer::init(&env, Some(100.into())); + let mut pobcoin = Erc20Deployer::init(&env, Some(100.into())); assert_eq!(erc20.cross_total(pobcoin.address.clone()), 200.into()); // Test attaching value and balances @@ -762,8 +590,8 @@ mod tests { let bob = env.get_account(1); // Deploy the contract as Alice. - let erc20 = Erc20Deployer::init(&env, Some(100.into())); - let pobcoin = Erc20Deployer::init(&env, Some(100.into())); + let mut erc20 = Erc20Deployer::init(&env, Some(100.into())); + let mut pobcoin = Erc20Deployer::init(&env, Some(100.into())); // Make a call or two erc20.transfer(bob, 10.into()); @@ -838,7 +666,7 @@ mod tests { let charlie = env.get_account(2); // Deploy the contract as Alice. - let erc20 = Erc20Deployer::init(&env, Some(100.into())); + let mut erc20 = Erc20Deployer::init(&env, Some(100.into())); // Emit some events erc20.transfer(bob, 10.into()); @@ -896,7 +724,7 @@ mod tests { let bob = env.get_account(1); // Deploy the contract as Alice. - let erc20 = Erc20Deployer::init(&env, Some(100.into())); + let mut erc20 = Erc20Deployer::init(&env, Some(100.into())); // When event is emitted erc20.approve(bob, 10.into()); @@ -972,7 +800,7 @@ mod tests { let alice = env.get_account(0); // Deploy the contract as Alice. - let erc20 = Erc20Deployer::init(&env, Some(100.into())); + let mut erc20 = Erc20Deployer::init(&env, Some(100.into())); // Test errors let result = erc20.try_transfer(alice, 1_000_000.into()); diff --git a/odra-macros/Cargo.toml b/odra-macros/Cargo.toml index 3b1f6d30..2c38ed37 100644 --- a/odra-macros/Cargo.toml +++ b/odra-macros/Cargo.toml @@ -11,10 +11,11 @@ homepage = { workspace = true } repository = { workspace = true } [dependencies] +odra-utils = { path = "../utils", features = ["std"] } proc-macro2 = "1.0.69" -proc-macro-error = "1" quote = "1.0.33" syn = { version = "2.0.29", features = ["full"] } +syn_derive = "0.1.8" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/odra-macros/src/ir/deployer_item.rs b/odra-macros/src/ast/deployer_item.rs similarity index 56% rename from odra-macros/src/ir/deployer_item.rs rename to odra-macros/src/ast/deployer_item.rs index 3b764e3c..8f94f20a 100644 --- a/odra-macros/src/ir/deployer_item.rs +++ b/odra-macros/src/ast/deployer_item.rs @@ -1,5 +1,6 @@ -use super::{checked_unwrap, ModuleIR}; -use quote::{quote, ToTokens}; +use quote::ToTokens; + +use crate::ir::ModuleIR; pub struct DeployerItem<'a> { module: &'a ModuleIR @@ -13,25 +14,25 @@ impl<'a> DeployerItem<'a> { impl<'a> ToTokens for DeployerItem<'a> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let module = checked_unwrap!(self.module.module_ident()); - let module_ref = checked_unwrap!(self.module.host_ref_ident()); - let module_deployer = checked_unwrap!(self.module.deployer_ident()); - tokens.extend(quote!( - pub struct #module_deployer; - impl #module_deployer { - pub fn deploy(env: &mut Env) -> #module_ref { - let caller = odra::ModuleCaller(|env: Env, call_def: odra::types::CallDef| { - let contract = #module; - odra::Callable::call(&contract, env, call_def) - }); - let addr = env.new_contract(caller); - #module_ref { - env: env.clone_empty(), - address: addr, - } - } - } - )); + // let module = checked_unwrap!(self.module.module_ident()); + // let module_ref = checked_unwrap!(self.module.host_ref_ident()); + // let module_deployer = checked_unwrap!(self.module.deployer_ident()); + // tokens.extend(quote!( + // pub struct #module_deployer; + // impl #module_deployer { + // pub fn deploy(env: &mut Env) -> #module_ref { + // let caller = odra::ModuleCaller(|env: Env, call_def: odra::types::CallDef| { + // let contract = #module; + // odra::Callable::call(&contract, env, call_def) + // }); + // let addr = env.new_contract(caller); + // #module_ref { + // env: env.clone_empty(), + // address: addr, + // } + // } + // } + // )); } } diff --git a/odra-macros/src/ast/host_ref_item.rs b/odra-macros/src/ast/host_ref_item.rs new file mode 100644 index 00000000..89f53c6b --- /dev/null +++ b/odra-macros/src/ast/host_ref_item.rs @@ -0,0 +1,200 @@ +use crate::{ir::ModuleIR, utils}; +use proc_macro2::Ident; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::parse_quote; + +use super::ref_utils; + +const CONSTRUCTOR_NAME: &str = "init"; + +#[derive(syn_derive::ToTokens)] +struct HostRefStructItem { + vis: syn::Visibility, + struct_token: syn::token::Struct, + ident: syn::Ident, + fields: syn::Fields +} + +impl TryFrom<&'_ ModuleIR> for HostRefStructItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + let named_fields: syn::FieldsNamed = parse_quote!({ + pub address: odra::types::Address, + pub env: odra::HostEnv, + pub attached_value: odra::types::U512 + }); + Ok(Self { + vis: utils::syn::visibility_pub(), + struct_token: Default::default(), + ident: value.host_ref_ident()?, + fields: named_fields.into() + }) + } +} + +#[derive(syn_derive::ToTokens)] +struct HostRefImplItem { + impl_token: syn::token::Impl, + ref_ident: Ident, + #[syn(braced)] + brace_token: syn::token::Brace, + #[syn(in = brace_token)] + with_tokens_fn: WithTokensFnItem, + #[syn(in = brace_token)] + get_event_fn: GetEventFnItem, + #[syn(in = brace_token)] + last_call_fn: LastCallFnItem, + #[syn(in = brace_token)] + #[to_tokens(|tokens, f| tokens.append_all(f))] + functions: Vec +} + +impl TryFrom<&'_ ModuleIR> for HostRefImplItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + Ok(Self { + impl_token: Default::default(), + ref_ident: value.host_ref_ident()?, + brace_token: Default::default(), + with_tokens_fn: WithTokensFnItem, + get_event_fn: GetEventFnItem, + last_call_fn: LastCallFnItem, + functions: value + .functions() + .iter() + .filter(|f| f.name_str() != CONSTRUCTOR_NAME) + .map(|f| { + vec![ + ref_utils::host_try_function_item(f), + ref_utils::host_function_item(f), + ] + }) + .flatten() + .collect() + }) + } +} + +struct WithTokensFnItem; + +impl ToTokens for WithTokensFnItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + pub fn with_tokens(&self, tokens: odra::types::U512) -> Self { + Self { + address: self.address, + env: self.env.clone(), + attached_value: tokens + } + } + )); + } +} + +struct GetEventFnItem; + +impl ToTokens for GetEventFnItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + pub fn get_event(&self, index: i32) -> Result + where + T: odra::types::FromBytes + odra::casper_event_standard::EventInstance + { + self.env.get_event(&self.address, index) + } + )); + } +} + +struct LastCallFnItem; + +impl ToTokens for LastCallFnItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + pub fn last_call(&self) -> odra::ContractCallResult { + self.env.last_call().contract_last_call(self.address) + } + )) + } +} + +#[derive(syn_derive::ToTokens)] +pub struct HostRefItem { + struct_item: HostRefStructItem, + impl_item: HostRefImplItem +} + +impl<'a> TryFrom<&'a ModuleIR> for HostRefItem { + type Error = syn::Error; + + fn try_from(value: &'a ModuleIR) -> Result { + Ok(Self { + struct_item: value.try_into()?, + impl_item: value.try_into()? + }) + } +} + +#[cfg(test)] +mod ref_item_tests { + use super::HostRefItem; + use crate::test_utils; + use quote::quote; + + #[test] + fn host_ref() { + let module = test_utils::mock_module(); + let expected = quote! { + pub struct Erc20HostRef { + pub address: odra::types::Address, + pub env: odra::HostEnv, + pub attached_value: odra::types::U512 + } + + impl Erc20HostRef { + pub fn with_tokens(&self, tokens: odra::types::U512) -> Self { + Self { + address: self.address, + env: self.env.clone(), + attached_value: tokens + } + } + + pub fn get_event(&self, index: i32) -> Result + where + T: odra::types::FromBytes + odra::casper_event_standard::EventInstance, + { + self.env.get_event(&self.address, index) + } + + pub fn last_call(&self) -> odra::ContractCallResult { + self.env.last_call().contract_last_call(self.address) + } + + pub fn try_total_supply(&self) -> Result { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("total_supply"), + { + let mut named_args = odra::types::RuntimeArgs::new(); + if self.attached_value > odra::types::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + named_args + } + ).with_amount(self.attached_value), + ) + } + + pub fn total_supply(&self) -> U256 { + self.try_total_supply().unwrap() + } + } + }; + let actual = HostRefItem::try_from(&module).unwrap(); + test_utils::assert_eq(actual, expected); + } +} diff --git a/odra-macros/src/ast/mod.rs b/odra-macros/src/ast/mod.rs new file mode 100644 index 00000000..a0357a13 --- /dev/null +++ b/odra-macros/src/ast/mod.rs @@ -0,0 +1,9 @@ +mod deployer_item; +mod host_ref_item; +mod parts_utils; +mod ref_item; +mod ref_utils; +mod test_parts; + +pub(crate) use ref_item::RefItem; +pub(crate) use test_parts::TestParts; diff --git a/odra-macros/src/ast/parts_utils.rs b/odra-macros/src/ast/parts_utils.rs new file mode 100644 index 00000000..9affd46f --- /dev/null +++ b/odra-macros/src/ast/parts_utils.rs @@ -0,0 +1,21 @@ +use quote::{quote, ToTokens}; + +pub struct UsePreludeItem; + +impl ToTokens for UsePreludeItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + use odra::prelude::*; + )); + } +} + +pub struct UseSuperItem; + +impl ToTokens for UseSuperItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + use super::*; + )); + } +} diff --git a/odra-macros/src/ast/ref_item.rs b/odra-macros/src/ast/ref_item.rs new file mode 100644 index 00000000..0095c39a --- /dev/null +++ b/odra-macros/src/ast/ref_item.rs @@ -0,0 +1,145 @@ +use crate::{ast::ref_utils, ir::ModuleIR, utils}; +use quote::{quote, ToTokens, TokenStreamExt}; +use syn::parse_quote; + +#[derive(syn_derive::ToTokens)] +struct ContractRefStructItem { + vis: syn::Visibility, + struct_token: syn::token::Struct, + ident: syn::Ident, + fields: syn::Fields +} + +impl TryFrom<&'_ ModuleIR> for ContractRefStructItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + let ty_address = utils::syn::type_address(); + let ty_contract_env = utils::syn::type_contract_env(); + let named_fields: syn::FieldsNamed = parse_quote!({ + env: Rc<#ty_contract_env>, + address: #ty_address, + }); + + Ok(Self { + vis: utils::syn::visibility_pub(), + struct_token: Default::default(), + ident: value.contract_ref_ident()?, + fields: named_fields.into() + }) + } +} + +struct AddressFnItem; + +impl ToTokens for AddressFnItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(quote!( + pub fn address(&self) -> &odra::types::Address { + &self.address + } + )) + } +} + +#[derive(syn_derive::ToTokens)] +struct ContractRefImplItem { + impl_token: syn::token::Impl, + ref_ident: syn::Ident, + #[syn(braced)] + brace_token: syn::token::Brace, + #[syn(in = brace_token)] + address_fn: AddressFnItem, + #[syn(in = brace_token)] + #[to_tokens(|tokens, val| tokens.append_all(val))] + functions: Vec +} + +impl TryFrom<&'_ ModuleIR> for ContractRefImplItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + Ok(Self { + impl_token: Default::default(), + ref_ident: value.contract_ref_ident()?, + brace_token: Default::default(), + address_fn: AddressFnItem, + functions: value + .functions() + .iter() + .map(ref_utils::contract_function_item) + .collect() + }) + } +} + +#[derive(syn_derive::ToTokens)] +pub struct RefItem { + struct_item: ContractRefStructItem, + impl_item: ContractRefImplItem +} + +impl TryFrom<&'_ ModuleIR> for RefItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + Ok(Self { + struct_item: value.try_into()?, + impl_item: value.try_into()? + }) + } +} + +#[cfg(test)] +mod ref_item_tests { + use super::RefItem; + use crate::test_utils; + use quote::quote; + + #[test] + fn contract_ref() { + let module = test_utils::mock_module(); + let expected = quote! { + pub struct Erc20ContractRef { + env: Rc, + address: odra::types::Address, + } + + impl Erc20ContractRef { + // TODO: this means "address", can't be entrypoint name. + pub fn address(&self) -> &odra::types::Address { + &self.address + } + + pub fn init(&mut self, total_supply: Option) { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("init"), + { + let mut named_args = odra::types::RuntimeArgs::new(); + let _ = named_args.insert(stringify!(total_supply), total_supply); + named_args + } + ), + ) + } + + pub fn total_supply(&self) -> U256 { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("total_supply"), + { + let mut named_args = odra::types::RuntimeArgs::new(); + named_args + } + ), + ) + } + } + }; + let actual = RefItem::try_from(&module).unwrap(); + test_utils::assert_eq(actual, expected); + } +} diff --git a/odra-macros/src/ast/ref_utils.rs b/odra-macros/src/ast/ref_utils.rs new file mode 100644 index 00000000..fe51d418 --- /dev/null +++ b/odra-macros/src/ast/ref_utils.rs @@ -0,0 +1,90 @@ +use crate::ir::FnIR; +use proc_macro2::TokenStream; +use quote::quote; + +pub fn host_try_function_item(fun: &FnIR) -> syn::ItemFn { + let signature = try_function_signature(fun); + let call_def_expr = call_def_with_amount(fun); + + env_call(signature, call_def_expr) +} + +pub fn host_function_item(fun: &FnIR) -> syn::ItemFn { + let signature = function_signature(fun); + let try_func_name = fun.try_name(); + let args = fun.arg_names(); + syn::parse_quote!( + pub #signature { + self.#try_func_name(#(#args),*).unwrap() + } + ) +} + +pub fn contract_function_item(fun: &FnIR) -> syn::ItemFn { + let signature = function_signature(fun); + let call_def_expr = call_def(fun); + + env_call(signature, call_def_expr) +} + +fn env_call(sig: syn::Signature, call_def_expr: syn::Expr) -> syn::ItemFn { + syn::parse_quote!( + pub #sig { + self.env.call_contract( + self.address, + #call_def_expr + ) + } + ) +} + +fn call_def(fun: &FnIR) -> syn::Expr { + let fun_name_str = fun.name_str(); + let args = args_token_stream(fun); + let runtime_args = quote!({ + let mut named_args = odra::types::RuntimeArgs::new(); + #args + named_args + }); + syn::parse_quote!(odra::CallDef::new(String::from(#fun_name_str), #runtime_args)) +} + +fn call_def_with_amount(fun: &FnIR) -> syn::Expr { + let fun_name_str = fun.name_str(); + let args = args_token_stream(fun); + let runtime_args = quote!({ + let mut named_args = odra::types::RuntimeArgs::new(); + if self.attached_value > odra::types::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + #args + named_args + }); + syn::parse_quote!(odra::CallDef::new(String::from(#fun_name_str), #runtime_args).with_amount(self.attached_value)) +} + +fn function_signature(fun: &FnIR) -> syn::Signature { + let fun_name = fun.name(); + let args = fun.typed_args(); + let return_type = fun.return_type(); + let mutability = fun.is_mut().then(|| quote::quote!(mut)); + + syn::parse_quote!(fn #fun_name(& #mutability self #(, #args)*) #return_type) +} + +fn try_function_signature(fun: &FnIR) -> syn::Signature { + let fun_name = fun.try_name(); + let args = fun.typed_args(); + let return_type = fun.try_return_type(); + let mutability = fun.is_mut().then(|| quote::quote!(mut)); + + syn::parse_quote!(fn #fun_name(& #mutability self #(, #args)*) #return_type) +} + +fn args_token_stream(fun: &FnIR) -> TokenStream { + fun + .arg_names() + .iter() + .map(|i| quote!(let _ = named_args.insert(stringify!(#i), #i);)) + .collect::() +} diff --git a/odra-macros/src/ast/test_parts.rs b/odra-macros/src/ast/test_parts.rs new file mode 100644 index 00000000..ead3848f --- /dev/null +++ b/odra-macros/src/ast/test_parts.rs @@ -0,0 +1,124 @@ +use crate::ir::ModuleIR; +use syn::parse_quote; + +use super::{ + host_ref_item::HostRefItem, + parts_utils::{UsePreludeItem, UseSuperItem} +}; + +#[derive(syn_derive::ToTokens)] +pub struct PartsModuleItem { + attr: syn::Attribute, + mod_token: syn::token::Mod, + ident: syn::Ident +} + +impl TryFrom<&'_ ModuleIR> for PartsModuleItem { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + let ident = value.test_parts_mod_ident()?; + let attr = parse_quote!(#[cfg(not(target_arch = "wasm32"))]); + Ok(Self { + attr, + mod_token: Default::default(), + ident + }) + } +} + +#[derive(syn_derive::ToTokens)] +pub struct TestParts { + parts_module: PartsModuleItem, + #[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)] + host_ref: HostRefItem +} + +impl TryFrom<&'_ ModuleIR> for TestParts { + type Error = syn::Error; + + fn try_from(value: &'_ ModuleIR) -> Result { + Ok(TestParts { + parts_module: value.try_into()?, + brace_token: Default::default(), + use_prelude: UsePreludeItem, + use_super: UseSuperItem, + host_ref: value.try_into()? + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_utils::{self, mock_module}; + + #[test] + fn test_parts() { + let module = mock_module(); + let actual = TestParts::try_from(&module).unwrap(); + + let expected = quote::quote! { + #[cfg(not(target_arch = "wasm32"))] + mod __erc20_test_parts { + use super::*; + use odra::prelude::*; + + pub struct Erc20HostRef { + pub address: odra::types::Address, + pub env: odra::HostEnv, + pub attached_value: odra::types::U512 + } + + impl Erc20HostRef { + pub fn with_tokens(&self, tokens: odra::types::U512) -> Self { + Self { + address: self.address, + env: self.env.clone(), + attached_value: tokens + } + } + + pub fn get_event(&self, index: i32) -> Result + where + T: odra::types::FromBytes + odra::casper_event_standard::EventInstance, + { + self.env.get_event(&self.address, index) + } + + pub fn last_call(&self) -> odra::ContractCallResult { + self.env.last_call().contract_last_call(self.address) + } + + pub fn try_total_supply(&self) -> Result { + self.env.call_contract( + self.address, + odra::CallDef::new( + String::from("total_supply"), + { + let mut named_args = odra::types::RuntimeArgs::new(); + if self.attached_value > odra::types::U512::zero() { + let _ = named_args.insert("amount", self.attached_value); + } + named_args + } + ).with_amount(self.attached_value), + ) + } + + pub fn total_supply(&self) -> U256 { + self.try_total_supply().unwrap() + } + } + } + }; + + test_utils::assert_eq(actual, expected); + } +} diff --git a/odra-macros/src/ir/mod.rs b/odra-macros/src/ir/mod.rs index 3e3db8be..a3403e72 100644 --- a/odra-macros/src/ir/mod.rs +++ b/odra-macros/src/ir/mod.rs @@ -1,9 +1,7 @@ -use crate::syn_utils; +use crate::utils; use proc_macro2::{Ident, TokenStream}; -use syn::ItemImpl; - -pub mod deployer_item; -pub mod ref_item; +use quote::format_ident; +use syn::{parse_quote, ItemImpl}; pub struct ModuleIR { code: ItemImpl @@ -25,7 +23,7 @@ impl ModuleIR { } pub fn module_ident(&self) -> Result { - syn_utils::ident_from_impl(&self.code) + utils::syn::ident_from_impl(&self.code) } pub fn host_ref_ident(&self) -> Result { @@ -51,17 +49,21 @@ impl ModuleIR { )) } - pub fn methods(&self) -> Vec { - let methods = self - .code + pub fn test_parts_mod_ident(&self) -> Result { + self.module_ident() + .map(odra_utils::camel_to_snake) + .map(|ident| format_ident!("__{}_test_parts", ident)) + } + + pub fn functions(&self) -> Vec { + self.code .items .iter() .filter_map(|item| match item { - syn::ImplItem::Fn(method) => Some(FnIR::new(method.clone())), + syn::ImplItem::Fn(func) => Some(FnIR::new(func.clone())), _ => None }) - .collect::>(); - methods + .collect::>() } } @@ -78,42 +80,39 @@ impl FnIR { self.code.sig.ident.clone() } + pub fn try_name(&self) -> Ident { + format_ident!("try_{}", self.name()) + } + pub fn name_str(&self) -> String { self.name().to_string() } pub fn arg_names(&self) -> Vec { - syn_utils::function_arg_names(&self.code) + utils::syn::function_arg_names(&self.code) } pub fn args_len(&self) -> usize { - syn_utils::function_args(&self.code).len() + utils::syn::function_args(&self.code).len() } pub fn return_type(&self) -> syn::ReturnType { - syn_utils::function_return_type(&self.code) + utils::syn::function_return_type(&self.code) + } + + pub fn try_return_type(&self) -> syn::ReturnType { + match self.return_type() { + syn::ReturnType::Default => parse_quote!(-> Result<(), OdraError>), + syn::ReturnType::Type(_, box ty) => parse_quote!(-> Result<#ty, OdraError>) + } } pub fn typed_args(&self) -> Vec { - syn_utils::function_args(&self.code) + utils::syn::function_args(&self.code) } pub fn is_mut(&self) -> bool { - let receiver = syn_utils::receiver_arg(&self.code); + let receiver = utils::syn::receiver_arg(&self.code); receiver.map(|r| r.mutability.is_some()).unwrap_or_default() } } - -/// Intended to be used in [quote::ToTokens]. Emits error and ends item tokenization. -macro_rules! checked_unwrap { - ($value:expr) => { - match $value { - Ok(result) => result, - Err(e) => { - proc_macro_error::emit_error!(e.span(), e.to_string()); - return; - } - } - }; -} -pub(crate) use checked_unwrap; diff --git a/odra-macros/src/ir/ref_item.rs b/odra-macros/src/ir/ref_item.rs deleted file mode 100644 index 85d6b7a9..00000000 --- a/odra-macros/src/ir/ref_item.rs +++ /dev/null @@ -1,152 +0,0 @@ -use super::checked_unwrap; -use crate::ir::{FnIR, ModuleIR}; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::parse_quote; - -pub struct RefItem<'a> { - module: &'a ModuleIR -} - -impl<'a> RefItem<'a> { - pub fn new(module: &'a ModuleIR) -> Self { - RefItem { module } - } - - pub fn method(&self, fun: &FnIR) -> syn::ItemFn { - let fun_name = fun.name(); - let fun_name_str = fun.name_str(); - let args = fun.arg_names(); - let typed_args = { - let args = fun.typed_args(); - quote!(#(, #args)*) - }; - - let args = if fun.args_len() == 0 { - quote!(odra::types::RuntimeArgs::new()) - } else { - let args = args - .iter() - .map(|i| quote!(let _ = named_args.insert(stringify!(#i), #i);)) - .collect::(); - quote!({ - let mut named_args = odra::types::RuntimeArgs::new(); - #args - named_args - }) - }; - - let return_type = fun.return_type(); - let mutability = fun.is_mut().then(|| quote!(mut)); - parse_quote!( - pub fn #fun_name(& #mutability self #typed_args) #return_type { - self.env.call_contract( - self.address, - CallDef::new(String::from(#fun_name_str), #args), - ) - } - ) - } - - pub fn methods(&self) -> Vec { - self.module - .methods() - .iter() - .map(|f| self.method(f)) - .collect() - } -} - -impl<'a> ToTokens for RefItem<'a> { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let module_ref = checked_unwrap!(self.module.contract_ref_ident()); - let methods = self.methods(); - tokens.extend(quote!( - pub struct #module_ref { - env: Rc, - address: odra::types::Address, - } - - impl #module_ref { - pub fn address(&self) -> &Address { - &self.address - } - - #(#methods)* - } - )); - } -} - -#[cfg(test)] -mod ref_item_tests { - use super::RefItem; - use crate::test_utils; - use quote::quote; - - #[test] - fn contract_ref() { - let module = test_utils::mock_module(); - let expected = quote! { - pub struct Erc20ContractRef { - env: Rc, - address: odra::types::Address, - } - - impl Erc20ContractRef { - // TODO: this means "address", can't be entrypoint name. - pub fn address(&self) -> &Address { - &self.address - } - - pub fn init(&mut self, total_supply: Option) { - self.env.call_contract( - self.address, - CallDef::new( - String::from("init"), - { - let mut named_args = odra::types::RuntimeArgs::new(); - let _ = named_args.insert(stringify!(total_supply), total_supply); - named_args - } - ), - ) - } - - pub fn total_supply(&self) -> U256 { - self.env.call_contract( - self.address, - CallDef::new( - String::from("total_supply"), - odra::types::RuntimeArgs::new(), - ), - ) - } - } - }; - let actual = RefItem::new(&module); - test_utils::assert_eq(actual, expected); - } - - #[test] - fn method() { - let module = test_utils::mock_module(); - let expected = quote! { - pub fn init(&mut self, total_supply: Option) { - self.env.call_contract( - self.address, - CallDef::new( - String::from("init"), - { - let mut named_args = odra::types::RuntimeArgs::new(); - let _ = named_args.insert(stringify!(total_supply), total_supply); - named_args - } - ), - ) - } - }; - let actual = RefItem::new(&module).method(&module.methods()[0]); - test_utils::assert_eq(actual, expected); - } -} diff --git a/odra-macros/src/lib.rs b/odra-macros/src/lib.rs index 3afdda39..7d3c46fa 100644 --- a/odra-macros/src/lib.rs +++ b/odra-macros/src/lib.rs @@ -1,28 +1,34 @@ -use ir::{ref_item::RefItem, ModuleIR}; +#![feature(box_patterns)] + +use ast::*; +use ir::ModuleIR; use proc_macro::TokenStream; -use proc_macro_error::{abort_if_dirty, proc_macro_error}; +mod ast; mod ir; -mod syn_utils; #[cfg(test)] mod test_utils; +mod utils; #[proc_macro_attribute] -#[proc_macro_error] pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream { - match ModuleIR::try_from(&item.into()) { - Ok(module) => { - let code = module.self_code(); - let ref_item = RefItem::new(&module); - - let result = quote::quote! { - #code - #ref_item - }; - abort_if_dirty(); - result - } + match module_impl(item) { + Ok(result) => result, Err(e) => e.to_compile_error() } .into() } + +fn module_impl(item: TokenStream) -> Result { + let module_ir = ModuleIR::try_from(&item.into())?; + + let code = module_ir.self_code(); + let ref_item = RefItem::try_from(&module_ir)?; + let test_parts = TestParts::try_from(&module_ir)?; + + Ok(quote::quote! { + #code + #ref_item + #test_parts + }) +} diff --git a/odra-macros/src/utils/mod.rs b/odra-macros/src/utils/mod.rs new file mode 100644 index 00000000..3c99ecac --- /dev/null +++ b/odra-macros/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod syn; diff --git a/odra-macros/src/syn_utils/mod.rs b/odra-macros/src/utils/syn.rs similarity index 83% rename from odra-macros/src/syn_utils/mod.rs rename to odra-macros/src/utils/syn.rs index 1af4ce1e..2209a280 100644 --- a/odra-macros/src/syn_utils/mod.rs +++ b/odra-macros/src/utils/syn.rs @@ -1,4 +1,4 @@ -use syn::spanned::Spanned; +use syn::{parse_quote, spanned::Spanned}; pub fn ident_from_impl(impl_code: &syn::ItemImpl) -> Result { match &*impl_code.self_ty { @@ -49,3 +49,15 @@ pub fn receiver_arg(function: &syn::ImplItemFn) -> Option { pub fn function_return_type(function: &syn::ImplItemFn) -> syn::ReturnType { function.sig.output.clone() } + +pub fn type_address() -> syn::Type { + parse_quote!(odra::types::Address) +} + +pub fn type_contract_env() -> syn::Type { + parse_quote!(odra::ContractEnv) +} + +pub fn visibility_pub() -> syn::Visibility { + parse_quote!(pub) +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 247a84d3..e3f4a80a 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -21,8 +21,9 @@ use convert_case::{Boundary, Case, Casing}; /// assert_eq!(&result, "contract_name"); /// ``` #[cfg(feature = "std")] -pub fn camel_to_snake(text: &str) -> String { - text.from_case(Case::UpperCamel) +pub fn camel_to_snake(text: T) -> String { + text.to_string() + .from_case(Case::UpperCamel) .without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit]) .to_case(Case::Snake) }