Skip to content

Commit

Permalink
feat: enable dynamic callee returns tuple typed value (#195)
Browse files Browse the repository at this point in the history
* feat: enable dynamic callee function to return tuple typed value

* refactor: refactor how to check returned value of dynamic callee

* refactor: refactor dynamic call macros

* feat: add dynamic call function returns tuple and tests

* chore: cargo fmt

* chore: cargo clippy

* fix: fix attribute format of dynamic caller's pnig

* docs: update README.md of contracts

* fix: refactor dynamic-callee and dynamic-caller

* chore: cargo fmt

* chore: format contract/REAMDE.md 's table
  • Loading branch information
loloicci authored Jun 8, 2022
1 parent d81a930 commit 4a4e155
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 144 deletions.
29 changes: 21 additions & 8 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,34 @@ docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_staking",target=/code/contracts/staking/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.11.0 ./contracts/staking

docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_dynamic_callee_contract",target=/code/contracts/dynamic_callee_contract/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.11.0 ./contracts/dynamic_callee_contract

docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_dynamic_caller_contract",target=/code/contracts/dynamic_caller_contract/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.11.0 ./contracts/dynamic_caller_contract
```

## Entry points

The development contracts in this folder contain a variety of different entry
points in order to demonstrate and test the flexibility we have.

| Contract | Macro | Has `query` | Has `migrate` |
| ----------- | --------------------------------------------- | ----------- | ------------- |
| burner | `#[entry_point]` | no | yes |
| hackatom | [`create_entry_points_with_migration!`][cepm] | yes | yes |
| ibc-reflect | `#[entry_point]` | yes | no |
| queue | mixed<sup>1</sup> | yes | yes |
| reflect | [`create_entry_points!`][cep] | yes | no |
| staking | `#[entry_point]` | yes | no |
| Contract | Macro | Has `query` | Has `migrate` |
| ----------------------- | --------------------------------------------- | ----------- | ------------- |
| burner | `#[entry_point]` | no | yes |
| hackatom | [`create_entry_points_with_migration!`][cepm] | yes | yes |
| ibc-reflect | `#[entry_point]` | yes | no |
| queue | mixed<sup>1</sup> | yes | yes |
| reflect | [`create_entry_points!`][cep] | yes | no |
| staking | `#[entry_point]` | yes | no |
| dynamic_callee_contract | `#[entry_point]` | no | no |
| dynamic_caller_contract | `#[entry_point]` | no | no |


<sup>1</sup> Because we can. Don't try this at home.

Expand Down
31 changes: 18 additions & 13 deletions contracts/dynamic-callee-contract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use cosmwasm_std::{
callable_point, dynamic_link, entry_point, Binary, Deps, DepsMut, Env, GlobalApi, MessageInfo, Response,
StdResult, Addr, to_vec,
callable_point, dynamic_link, entry_point, to_vec, Addr, DepsMut, Env, GlobalApi, MessageInfo,
Response,
};
use serde::{Deserialize, Serialize};

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::msg::{ExecuteMsg, InstantiateMsg};

// Note, you can use StdResult in some functions where you do not
// make use of the custom errors
Expand All @@ -21,7 +21,7 @@ pub fn instantiate(

#[callable_point]
fn pong(x: u64) -> u64 {
return x + 1;
x + 1
}

#[derive(Serialize, Deserialize)]
Expand All @@ -38,6 +38,16 @@ fn pong_with_struct(example: ExampleStruct) -> ExampleStruct {
}
}

#[callable_point]
fn pong_with_tuple(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) {
(input1 + " world", input2 + 1)
}

#[callable_point]
fn pong_env() -> Env {
GlobalApi::env()
Expand All @@ -50,9 +60,9 @@ extern "C" {

#[callable_point]
fn reentrancy(addr: Addr) {
GlobalApi::with_deps_mut(|deps|{
GlobalApi::with_deps_mut(|deps| {
deps.storage
.set(b"dynamic_caller_contract", &to_vec(&addr).unwrap());
.set(b"dynamic_caller_contract", &to_vec(&addr).unwrap());
});
should_never_be_called()
}
Expand All @@ -63,12 +73,7 @@ pub fn execute(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: ExecuteMsg,
_msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {}
}

#[entry_point]
pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {}
Ok(Response::default())
}
4 changes: 0 additions & 4 deletions contracts/dynamic-callee-contract/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,3 @@ pub struct InstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {}
63 changes: 57 additions & 6 deletions contracts/dynamic-callee-contract/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ fn required_exports() -> Vec<(String, FunctionType)> {
String::from("stub_pong_with_struct"),
([Type::I32], [Type::I32]).into(),
),
(
String::from("stub_pong_with_tuple"),
([Type::I32], [Type::I32]).into(),
),
(String::from("stub_pong_env"), ([], [Type::I32]).into()),
]
}
Expand Down Expand Up @@ -111,6 +115,57 @@ fn callable_point_pong_with_struct_works() {
assert_eq!(result.u64_field, 101);
}

#[test]
fn callable_point_pong_with_tuple_works() {
let instance = make_callee_instance();

let serialized_param = to_vec(&(String::from("hello"), 41i32)).unwrap();
let param_region_ptr = write_data_to_mock_env(&instance.env, &serialized_param).unwrap();

let required_exports = required_exports();
let call_result = instance
.call_function_strict(
&required_exports[1].1,
"stub_pong_with_tuple",
&[param_region_ptr.into()],
)
.unwrap();
assert_eq!(call_result.len(), 1);

let serialized_return =
read_data_from_mock_env(&instance.env, &call_result[0], u32::MAX as usize).unwrap();
let result: (String, i32) = from_slice(&serialized_return).unwrap();
assert_eq!(result.0, String::from("hello world"));
assert_eq!(result.1, 42);
}

#[test]
fn callable_point_pong_with_tuple_takes_2_args_works() {
let instance = make_callee_instance();

let serialized_param1 = to_vec(&String::from("hello")).unwrap();
let param_region_ptr1 = write_data_to_mock_env(&instance.env, &serialized_param1).unwrap();

let serialized_param2 = to_vec(&41i32).unwrap();
let param_region_ptr2 = write_data_to_mock_env(&instance.env, &serialized_param2).unwrap();

let required_exports = required_exports();
let call_result = instance
.call_function_strict(
&required_exports[1].1,
"stub_pong_with_tuple_takes_2_args",
&[param_region_ptr1.into(), param_region_ptr2.into()],
)
.unwrap();
assert_eq!(call_result.len(), 1);

let serialized_return =
read_data_from_mock_env(&instance.env, &call_result[0], u32::MAX as usize).unwrap();
let result: (String, i32) = from_slice(&serialized_return).unwrap();
assert_eq!(result.0, String::from("hello world"));
assert_eq!(result.1, 42);
}

#[test]
fn callable_point_pong_env_works() {
let instance = make_callee_instance();
Expand All @@ -136,16 +191,12 @@ fn callable_point_pong_deps_works() {

let required_exports = required_exports();
let call_result = instance
.call_function_strict(
&required_exports[1].1,
"stub_pong_env",
&[],
)
.call_function_strict(&required_exports[1].1, "stub_pong_env", &[])
.unwrap();
assert_eq!(call_result.len(), 1);

let serialized_return =
read_data_from_mock_env(&instance.env, &call_result[0], u32::MAX as usize).unwrap();
let result: Env = from_slice(&serialized_return).unwrap();
assert_eq!(result.contract.address, Addr::unchecked("cosmos2contract"));
}
}
28 changes: 17 additions & 11 deletions contracts/dynamic-caller-contract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use cosmwasm_std::{
dynamic_link, callable_point, entry_point, to_vec, Binary, Deps, DepsMut, Env, MessageInfo, Response,
StdResult, Uint128, Addr,
callable_point, dynamic_link, entry_point, to_vec, Addr, DepsMut, Env, MessageInfo, Response,
Uint128,
};
use serde::{Deserialize, Serialize};
use std::fmt;

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::msg::{ExecuteMsg, InstantiateMsg};

#[derive(Serialize, Deserialize)]
pub struct ExampleStruct {
Expand All @@ -23,6 +23,8 @@ impl fmt::Display for ExampleStruct {
extern "C" {
fn pong(ping_num: u64) -> u64;
fn pong_with_struct(example: ExampleStruct) -> ExampleStruct;
fn pong_with_tuple(input: (String, i32)) -> (String, i32);
fn pong_with_tuple_takes_2_args(input1: String, input2: i32) -> (String, i32);
fn pong_env() -> Env;
fn reentrancy(addr: Addr);
}
Expand Down Expand Up @@ -62,10 +64,20 @@ pub fn try_ping(_deps: DepsMut, ping_num: Uint128) -> Result<Response, ContractE
str_field: String::from("hello"),
u64_field: 100u64,
});
let tuple_ret = pong_with_tuple((String::from("hello"), 41));
let tuple_ret2 = pong_with_tuple_takes_2_args(String::from("hello"), 41);

let mut res = Response::default();
res.add_attribute("returned_pong", pong_ret.to_string());
res.add_attribute("returned_pong_with_struct", struct_ret.to_string());
res.add_attribute(
"returned_pong_with_tuple",
format!("({}, {})", tuple_ret.0, tuple_ret.1),
);
res.add_attribute(
"returned_pong_with_tuple_takes_2_args",
format!("({}, {})", tuple_ret2.0, tuple_ret2.1),
);
res.add_attribute(
"returned_contract_address",
pong_env().contract.address.to_string(),
Expand All @@ -75,16 +87,10 @@ pub fn try_ping(_deps: DepsMut, ping_num: Uint128) -> Result<Response, ContractE

pub fn try_re_entrancy(env: Env) -> Result<Response, ContractError> {
// It will be tried to call the should_never_be_called function below.
// But, should be blocked by VM host side normally because it's reentrancy case.
// But, should be blocked by VM host side normally because it's a reentrancy case.
reentrancy(env.contract.address);
Ok(Response::default())
}

#[callable_point]
fn should_never_be_called() {
}

#[entry_point]
pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {}
}
fn should_never_be_called() {}
4 changes: 0 additions & 4 deletions contracts/dynamic-caller-contract/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,3 @@ pub enum ExecuteMsg {
Ping { ping_num: Uint128 },
TryReEntrancy {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {}
10 changes: 10 additions & 0 deletions contracts/dynamic-caller-contract/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ fn required_imports() -> Vec<(String, String, FunctionType)> {
String::from("dynamic_callee_contract"),
([Type::I32], [Type::I32]).into(),
),
(
String::from("stub_pong_with_tuple"),
String::from("dynamic_callee_contract"),
([Type::I32], [Type::I32]).into(),
),
(
String::from("stub_pong_with_tuple_takes_2_args"),
String::from("dynamic_callee_contract"),
([Type::I32, Type::I32], [Type::I32]).into(),
),
(
String::from("stub_pong_env"),
String::from("dynamic_callee_contract"),
Expand Down
49 changes: 16 additions & 33 deletions packages/derive/src/callable_point.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::utils::{collect_available_arg_types, get_return_len, make_typed_return};
use crate::utils::{collect_available_arg_types, has_return_value, make_typed_return};

pub fn make_callable_point(function: syn::ItemFn) -> TokenStream {
let stub_func_name_ident = format_ident!("stub_{}", function.sig.ident);
Expand All @@ -14,13 +14,13 @@ pub fn make_callable_point(function: syn::ItemFn) -> TokenStream {
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 renamed_param_defs: Vec<_> = (0..args_len)
.map(|i| {
let renamed_param_ident = format_ident!("ptr{}", i);
quote! { #renamed_param_ident: u32 }
let renamed_param_defs: Vec<_> = ptr_idents
.iter()
.map(|id| {
quote! { #id: u32 }
})
.collect();
let typed_return = make_typed_return(&function.sig.output, "callable_point".to_string());
let typed_return = make_typed_return(&function.sig.output);

let call_origin_return =
make_call_origin_and_return(&function.sig.ident, args_len, &function.sig.output);
Expand All @@ -42,31 +42,15 @@ fn make_call_origin_and_return(
return_type: &syn::ReturnType,
) -> TokenStream {
let arguments: Vec<_> = (0..args_len).map(|n| format_ident!("arg{}", n)).collect();
let return_len = get_return_len(return_type);

match return_len {
0 => quote! {#func_name_ident(#(#arguments),*);},
1 => {
quote! {
let result = #func_name_ident(#(#arguments),*);
let vec_result = cosmwasm_std::to_vec(&result).unwrap();
cosmwasm_std::memory::release_buffer(vec_result) as u32
}
}
_ => {
let results: Vec<_> = (0..return_len)
.map(|n| format_ident!("result{}", n))
.collect();
let vec_results: Vec<_> = (0..return_len)
.map(|n| format_ident!("vec_result{}", n))
.collect();

quote! {
let (#(#results),*) = #func_name_ident(#(#arguments),*);
#(let #vec_results = cosmwasm_std::to_vec(&#results).unwrap();)*
(#(cosmwasm_std::memory::release_buffer(#vec_results) as u32),*)
}
if has_return_value(return_type) {
quote! {
let result = #func_name_ident(#(#arguments),*);
let vec_result = cosmwasm_std::to_vec(&result).unwrap();
cosmwasm_std::memory::release_buffer(vec_result) as u32
}
} else {
quote! {#func_name_ident(#(#arguments),*);}
}
}

Expand Down Expand Up @@ -112,10 +96,9 @@ mod tests {
.to_string();

let expected: TokenStream = parse_quote! {
let (result0, result1) = foo();
let vec_result0 = cosmwasm_std::to_vec(&result0).unwrap();
let vec_result1 = cosmwasm_std::to_vec(&result1).unwrap();
(cosmwasm_std::memory::release_buffer(vec_result0) as u32 , cosmwasm_std::memory::release_buffer(vec_result1) as u32)
let result = foo();
let vec_result = cosmwasm_std::to_vec(&result).unwrap();
cosmwasm_std::memory::release_buffer(vec_result) as u32
};
assert_eq!(expected.to_string(), result_code);
}
Expand Down
Loading

0 comments on commit 4a4e155

Please sign in to comment.