From d601ee118dde6ea854e4cdcafc4b6ebf732b6fd4 Mon Sep 17 00:00:00 2001 From: loloicci Date: Fri, 2 Sep 2022 17:44:37 +0900 Subject: [PATCH] feat: make callable point takes deps as the first arg (#233) * feat: make callable_point function takes deps as the first arg * feat: fix dynamic callee contracts' callable points * test: add and format docs for macros * feat: remove functions to get deps in global_api * fix: fix merged dynamic_callee_contract --- .../dynamic-callee-contract/src/contract.rs | 16 ++-- .../dynamic-caller-contract/src/contract.rs | 6 +- contracts/number/src/contract.rs | 20 ++--- packages/derive/src/callable_point.rs | 74 ++++++++++++++++--- packages/derive/src/lib.rs | 27 ++++++- packages/std/src/exports.rs | 2 +- packages/std/src/global_api.rs | 41 +--------- packages/std/src/lib.rs | 4 +- 8 files changed, 113 insertions(+), 77 deletions(-) diff --git a/contracts/dynamic-callee-contract/src/contract.rs b/contracts/dynamic-callee-contract/src/contract.rs index 328f2df31..e071a355e 100644 --- a/contracts/dynamic-callee-contract/src/contract.rs +++ b/contracts/dynamic-callee-contract/src/contract.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - callable_point, dynamic_link, entry_point, Addr, Contract, DepsMut, Env, GlobalApi, + callable_point, dynamic_link, entry_point, Addr, Contract, Deps, DepsMut, Env, GlobalApi, MessageInfo, Response, }; use serde::{Deserialize, Serialize}; @@ -20,7 +20,7 @@ pub fn instantiate( } #[callable_point] -fn pong(x: u64) -> u64 { +fn pong(_deps: Deps, x: u64) -> u64 { x + 1 } @@ -31,7 +31,7 @@ pub struct ExampleStruct { } #[callable_point] -fn pong_with_struct(example: ExampleStruct) -> ExampleStruct { +fn pong_with_struct(_deps: Deps, example: ExampleStruct) -> ExampleStruct { ExampleStruct { str_field: example.str_field + " world", u64_field: example.u64_field + 1, @@ -39,22 +39,22 @@ fn pong_with_struct(example: ExampleStruct) -> ExampleStruct { } #[callable_point] -fn pong_with_tuple(input: (String, i32)) -> (String, i32) { +fn pong_with_tuple(_deps: Deps, input: (String, i32)) -> (String, i32) { (input.0 + " world", input.1 + 1) } #[callable_point] -fn pong_with_tuple_takes_2_args(input1: String, input2: i32) -> (String, i32) { +fn pong_with_tuple_takes_2_args(_deps: Deps, input1: String, input2: i32) -> (String, i32) { (input1 + " world", input2 + 1) } #[callable_point] -fn pong_env() -> Env { +fn pong_env(_deps: Deps) -> Env { GlobalApi::env() } #[callable_point] -fn do_panic() { +fn do_panic(_deps: Deps) { panic!(); } @@ -69,7 +69,7 @@ trait ReEntrance: Contract { } #[callable_point] -fn reentrancy(address: Addr) { +fn reentrancy(_deps: Deps, address: Addr) { let me = Me { address }; me.should_never_be_called() } diff --git a/contracts/dynamic-caller-contract/src/contract.rs b/contracts/dynamic-caller-contract/src/contract.rs index 077863231..345eca010 100644 --- a/contracts/dynamic-caller-contract/src/contract.rs +++ b/contracts/dynamic-caller-contract/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - callable_point, dynamic_link, entry_point, from_slice, to_vec, Addr, Contract, DepsMut, Env, - MessageInfo, Response, Uint128, + callable_point, dynamic_link, entry_point, from_slice, to_vec, Addr, Contract, Deps, DepsMut, + Env, MessageInfo, Response, Uint128, }; use serde::{Deserialize, Serialize}; use std::fmt; @@ -145,7 +145,7 @@ pub fn try_do_panic(deps: DepsMut, _env: Env) -> Result } #[callable_point] -fn should_never_be_called() {} +fn should_never_be_called(_deps: Deps) {} #[cfg(test)] mod tests { diff --git a/contracts/number/src/contract.rs b/contracts/number/src/contract.rs index ada1fb726..f4e73ad44 100644 --- a/contracts/number/src/contract.rs +++ b/contracts/number/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - callable_point, entry_point, to_binary, Binary, Deps, DepsMut, Env, GlobalApi, MessageInfo, - Response, Storage, + callable_point, entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, + Storage, }; use crate::error::ContractError; @@ -77,21 +77,21 @@ fn query_number(deps: Deps) -> Result { } #[callable_point] -fn add(by: i32) { - GlobalApi::with_deps_mut(|deps| handle_add(deps, by)).unwrap(); +fn add(deps: DepsMut, by: i32) { + handle_add(deps, by).unwrap(); } #[callable_point] -fn sub(by: i32) { - GlobalApi::with_deps_mut(|deps| handle_sub(deps, by)).unwrap(); +fn sub(deps: DepsMut, by: i32) { + handle_sub(deps, by).unwrap(); } #[callable_point] -fn mul(by: i32) { - GlobalApi::with_deps_mut(|deps| handle_mul(deps, by)).unwrap(); +fn mul(deps: DepsMut, by: i32) { + handle_mul(deps, by).unwrap(); } #[callable_point] -fn number() -> i32 { - GlobalApi::with_deps(|deps| read(deps.storage).unwrap()) +fn number(deps: Deps) -> i32 { + read(deps.storage).unwrap() } diff --git a/packages/derive/src/callable_point.rs b/packages/derive/src/callable_point.rs index 7fcadac2a..47077e6a8 100644 --- a/packages/derive/src/callable_point.rs +++ b/packages/derive/src/callable_point.rs @@ -1,12 +1,20 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use crate::utils::{collect_available_arg_types, has_return_value, make_typed_return}; +use crate::utils::{abort_by, collect_available_arg_types, has_return_value, make_typed_return}; pub fn make_callable_point(function: syn::ItemFn) -> TokenStream { let function_name_ident = &function.sig.ident; let mod_name_ident = format_ident!("__wasm_export_{}", function_name_ident); - let args_len = function.sig.inputs.len(); + // The first argument is `deps`, the rest is region pointers + if function.sig.inputs.is_empty() { + abort_by!( + function, + "callable_point", + "the first argument of callable_point function needs `deps` typed `Deps` or `DepsMut`" + ) + } + let args_len = function.sig.inputs.len() - 1; let arg_idents: Vec<_> = (0..args_len).map(|i| format_ident!("arg{}", i)).collect(); let vec_arg_idents: Vec<_> = (0..args_len) @@ -14,7 +22,29 @@ pub fn make_callable_point(function: syn::ItemFn) -> TokenStream { .collect(); let ptr_idents: Vec<_> = (0..args_len).map(|i| format_ident!("ptr{}", i)).collect(); - let arg_types = collect_available_arg_types(&function.sig, "callable_point".to_string()); + let orig_arg_types = collect_available_arg_types(&function.sig, "callable_point".to_string()); + let arg_types = &orig_arg_types[1..]; + let is_dep_mutable = match &orig_arg_types[0] { + syn::Type::Path(p) => { + if p.path.is_ident("Deps") { + false + } else if p.path.is_ident("DepsMut") { + true + } else { + abort_by!( + function, "callable_point", + "the first argument of callable_point function needs `deps` typed `Deps` or `DepsMut`" + ) + } + } + _ => { + abort_by!( + function, "callable_point", + "the first argument of callable_point function needs `deps` typed `Deps` or `DepsMut`" + ) + } + }; + let renamed_param_defs: Vec<_> = ptr_idents .iter() .map(|id| { @@ -23,8 +53,18 @@ pub fn make_callable_point(function: syn::ItemFn) -> TokenStream { .collect(); let typed_return = make_typed_return(&function.sig.output); - let call_origin_return = - make_call_origin_and_return(function_name_ident, args_len, &function.sig.output); + let call_origin_return = make_call_origin_and_return( + is_dep_mutable, + function_name_ident, + args_len, + &function.sig.output, + ); + + let deps_def = if is_dep_mutable { + quote! { let mut deps = cosmwasm_std::make_dependencies() } + } else { + quote! { let deps = cosmwasm_std::make_dependencies() } + }; quote! { #[cfg(target_arch = "wasm32")] @@ -35,6 +75,9 @@ pub fn make_callable_point(function: syn::ItemFn) -> TokenStream { extern "C" fn #function_name_ident(#(#renamed_param_defs),*) #typed_return { #(let #vec_arg_idents: Vec = unsafe { cosmwasm_std::memory::consume_region(#ptr_idents as *mut cosmwasm_std::memory::Region)};)* #(let #arg_idents: #arg_types = cosmwasm_std::from_slice(&#vec_arg_idents).unwrap();)* + + #deps_def; + #call_origin_return } } @@ -42,20 +85,27 @@ pub fn make_callable_point(function: syn::ItemFn) -> TokenStream { } fn make_call_origin_and_return( + is_dep_mutable: bool, func_name_ident: &syn::Ident, args_len: usize, return_type: &syn::ReturnType, ) -> TokenStream { let arguments: Vec<_> = (0..args_len).map(|n| format_ident!("arg{}", n)).collect(); + let call_func = if is_dep_mutable { + quote! { super::#func_name_ident(deps.as_mut() #(, #arguments)*) } + } else { + quote! { super::#func_name_ident(deps.as_ref() #(, #arguments)*) } + }; + if has_return_value(return_type) { quote! { - let result = super::#func_name_ident(#(#arguments),*); + let result = #call_func; let vec_result = cosmwasm_std::to_vec(&result).unwrap(); cosmwasm_std::memory::release_buffer(vec_result) as u32 } } else { - quote! { super::#func_name_ident(#(#arguments),*); } + call_func } } @@ -68,11 +118,12 @@ mod tests { fn make_call_origin_and_return_works() { { let function_foo_ret1: ItemFn = parse_quote! { - fn foo() -> u64 { + fn foo(deps: DepsMut) -> u64 { 1 } }; let result_code = make_call_origin_and_return( + true, &function_foo_ret1.sig.ident, 0, &function_foo_ret1.sig.output, @@ -80,7 +131,7 @@ mod tests { .to_string(); let expected: TokenStream = parse_quote! { - let result = super::foo(); + let result = super::foo(deps.as_mut()); let vec_result = cosmwasm_std::to_vec(&result).unwrap(); cosmwasm_std::memory::release_buffer(vec_result) as u32 }; @@ -89,11 +140,12 @@ mod tests { { let function_foo_ret2: ItemFn = parse_quote! { - fn foo() -> (u64, u64) { + fn foo(deps: Deps) -> (u64, u64) { (1, 2) } }; let result_code = make_call_origin_and_return( + false, &function_foo_ret2.sig.ident, 0, &function_foo_ret2.sig.output, @@ -101,7 +153,7 @@ mod tests { .to_string(); let expected: TokenStream = parse_quote! { - let result = super::foo(); + let result = super::foo(deps.as_ref()); let vec_result = cosmwasm_std::to_vec(&result).unwrap(); cosmwasm_std::memory::release_buffer(vec_result) as u32 }; diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 1f9e2c34a..0935aa63f 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -10,6 +10,7 @@ mod callable_point; mod contract; mod dynamic_link; mod utils; + /// This attribute macro generates the boilerplate required to call into the /// contract-specific logic from the entry-points to the Wasm module. /// @@ -93,6 +94,21 @@ pub fn entry_point(_attr: TokenStream, item: TokenStream) -> TokenStream { res } +/// This macro generate callable point function which can be called with dynamic link. +/// +/// Function attributed with this macro must take `deps` typed `Deps` or `DepsMut` +/// as the first argument. +/// +/// example usage: +/// ``` +/// use cosmwasm_std::{Addr, Deps, callable_point}; +/// +/// #[callable_point] +/// fn validate_address_callable_from_other_contracts(deps: Deps) -> Addr { +/// // do something with deps, for example, using api. +/// deps.api.addr_validate("dummy_human_address").unwrap() +/// } +/// ``` #[proc_macro_error] #[proc_macro_attribute] pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { @@ -107,15 +123,17 @@ pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { res } -/// This macro implements dynamic call functions for attributed trait. +/// This macro implements functions to call dynamic linked function for attributed trait. /// /// This macro must take an attribute specifying a struct to implement the traits for. /// The trait must have `cosmwasm_std::Contract` as a supertrait and each /// methods of the trait must have `&self` receiver as its first argument. /// /// This macro can take a bool value as a named attribute `user_defined_mock` -/// When this value is true, this macro generates implement of the trait for specified struct for only `target_arch = "wasm32"`. -/// So, with `user_defined_mock = true`, user can and must write mock implement of the trait for specified struct with `#[cfg(not(target_arch = "wasm32"))]`. +/// When this value is true, this macro generates implement of the trait for +/// specified struct for only `target_arch = "wasm32"`. +/// So, with `user_defined_mock = true`, user can and must write mock implement of +/// the trait for specified struct with `#[cfg(not(target_arch = "wasm32"))]`. /// /// example usage: /// @@ -132,7 +150,8 @@ pub fn callable_point(_attr: TokenStream, item: TokenStream) -> TokenStream { /// fn callable_point_on_another_contract(&self, x: i32) -> i32; /// } /// -/// // When `user_defined_mock = true` is specified, implement is generated only for "wasm32" +/// // When `user_defined_mock = true` is specified, implement is generated +/// // only for "wasm32" /// #[cfg(not(target_arch = "wasm32"))] /// impl TraitName for ContractStruct { /// fn callable_point_on_another_contract(&self, x: i32) -> i32 { diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index d47ca7d62..ef535226c 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -319,7 +319,7 @@ where } /// Makes all bridges to external dependencies (i.e. Wasm imports) that are injected by the VM -pub(crate) fn make_dependencies() -> OwnedDeps { +pub fn make_dependencies() -> OwnedDeps { OwnedDeps { storage: ExternalStorage::new(), api: ExternalApi::new(), diff --git a/packages/std/src/global_api.rs b/packages/std/src/global_api.rs index c7131b4f9..5f4fbab59 100644 --- a/packages/std/src/global_api.rs +++ b/packages/std/src/global_api.rs @@ -1,12 +1,10 @@ #[cfg(target_arch = "wasm32")] -use crate::exports::make_dependencies; -#[cfg(target_arch = "wasm32")] use crate::memory::{consume_region, Region}; #[cfg(not(target_arch = "wasm32"))] -use crate::mock::{mock_dependencies, mock_env}; +use crate::mock::mock_env; #[cfg(target_arch = "wasm32")] use crate::serde::from_slice; -use crate::{Deps, DepsMut, Env}; +use crate::Env; #[cfg(target_arch = "wasm32")] extern "C" { @@ -26,39 +24,4 @@ impl GlobalApi { pub fn env() -> Env { mock_env() } - - // By existing design, ownership of Deps is intended to be held outside the contract logic. - // So, it is not possible to provide a simple getter style without changing the existing design. - pub fn with_deps(callback: C) -> R - where - C: FnOnce(Deps) -> R, - { - /* FIXME: - In actual product execution, deps is a stateless implementation. - so even if we call make_dependencies in multiple places, there are no practical problems other than design violations. - However, mock_dependencies in the test environment is different. - Since it has an individual state, if it is created in several places, the test environment will be inconsistent with each different state. - - Therefore, it is currently not possible to develop logic that tests dynamic_call and the existing message driven. - */ - #[cfg(target_arch = "wasm32")] - let deps = make_dependencies(); - #[cfg(not(target_arch = "wasm32"))] - let deps = mock_dependencies(&[]); - - callback(deps.as_ref()) - } - - pub fn with_deps_mut(callback: C) -> R - where - C: FnOnce(DepsMut) -> R, - { - // FIXME: same above - #[cfg(target_arch = "wasm32")] - let mut deps = make_dependencies(); - #[cfg(not(target_arch = "wasm32"))] - let mut deps = mock_dependencies(&[]); - - callback(deps.as_mut()) - } } diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 82a0ea751..759009e0d 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -79,7 +79,9 @@ mod imports; pub mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines. #[cfg(target_arch = "wasm32")] -pub use crate::exports::{do_execute, do_instantiate, do_migrate, do_query, do_reply, do_sudo}; +pub use crate::exports::{ + do_execute, do_instantiate, do_migrate, do_query, do_reply, do_sudo, make_dependencies, +}; #[cfg(target_arch = "wasm32")] pub use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};