Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable dynamic callee returns tuple typed value #195

Merged
merged 11 commits into from
Jun 8, 2022
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