diff --git a/contracts/ibc-reflect-send/src/ibc.rs b/contracts/ibc-reflect-send/src/ibc.rs index 2284c48457..ba6d2fd73f 100644 --- a/contracts/ibc-reflect-send/src/ibc.rs +++ b/contracts/ibc-reflect-send/src/ibc.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - entry_point, from_slice, to_binary, DepsMut, Env, IbcBasicResponse, IbcChannelCloseMsg, - IbcChannelConnectMsg, IbcChannelOpenMsg, IbcMsg, IbcOrder, IbcPacketAckMsg, + entry_point, entry_point_adv, from_slice, to_binary, AdvResult, DepsMut, Env, IbcBasicResponse, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcMsg, IbcOrder, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, StdError, StdResult, }; @@ -89,16 +89,14 @@ pub fn ibc_channel_close( .add_attribute("channel_id", channel_id)) } -#[entry_point] +#[entry_point_adv] /// never should be called as the other side never sends packets pub fn ibc_packet_receive( _deps: DepsMut, _env: Env, _packet: IbcPacketReceiveMsg, -) -> Result { - Ok(IbcReceiveResponse::new() - .set_ack(b"{}") - .add_attribute("action", "ibc_packet_ack")) +) -> AdvResult { + AdvResult::Abort } #[entry_point] diff --git a/packages/derive/Cargo.toml b/packages/derive/Cargo.toml index 236b8bfacf..05d1ef880b 100644 --- a/packages/derive/Cargo.toml +++ b/packages/derive/Cargo.toml @@ -13,6 +13,7 @@ proc-macro = true [features] default = [] +stargate = [ "cosmwasm-std/stargate"] [dependencies] syn = { version = "1.0", features = ["full"] } diff --git a/packages/derive/src/lib.rs b/packages/derive/src/lib.rs index 349f9e3094..aa0cb06c90 100644 --- a/packages/derive/src/lib.rs +++ b/packages/derive/src/lib.rs @@ -82,3 +82,62 @@ pub fn entry_point(_attr: TokenStream, mut item: TokenStream) -> TokenStream { item.extend(entry); item } + +/// This attribute macro generates the boilerplate required to call into the +/// contract-specific logic from the entry-points to the Wasm module. +/// +/// It should be added to the contract's init, handle, migrate and query implementations +/// like this: +/// ``` +/// # use cosmwasm_std::{ +/// # Storage, Api, Querier, DepsMut, Deps, entry_point_adv, Env, IbcPacketReceiveMsg, +/// # IbcReceiveResponse, StdError, MessageInfo, Response, QueryResponse, +/// # }; +/// # +/// # type InstantiateMsg = (); +/// # type ExecuteMsg = (); +/// # type QueryMsg = (); +/// +/// #[entry_point_adv] +/// pub fn ibc_packet_receive( +/// deps: DepsMut, +/// env: Env, +/// msg: IbcPacketReceiveMsg, +/// ) -> Result { +/// # Ok(Default::default()) +/// } +/// ``` +/// +/// This only works for ibc_packet_receive if you want to use new advanced interface +#[cfg(feature = "stargate")] +#[proc_macro_attribute] +pub fn entry_point_adv(_attr: TokenStream, mut item: TokenStream) -> TokenStream { + let cloned = item.clone(); + let function = parse_macro_input!(cloned as syn::ItemFn); + let name = function.sig.ident.to_string(); + // The first argument is `deps`, the rest is region pointers + let args = function.sig.inputs.len() - 1; + + // E.g. "ptr0: u32, ptr1: u32, ptr2: u32, " + let typed_ptrs = (0..args).fold(String::new(), |acc, i| format!("{}ptr{}: u32, ", acc, i)); + // E.g. "ptr0, ptr1, ptr2, " + let ptrs = (0..args).fold(String::new(), |acc, i| format!("{}ptr{}, ", acc, i)); + + let new_code = format!( + r##" + #[cfg(target_arch = "wasm32")] + mod __wasm_export_{name} {{ // new module to avoid conflict of function name + #[no_mangle] + extern "C" fn {name}({typed_ptrs}) -> u32 {{ + cosmwasm_std::do_{name}_adv(&super::{name}, {ptrs}) + }} + }} + "##, + name = name, + typed_ptrs = typed_ptrs, + ptrs = ptrs + ); + let entry = TokenStream::from_str(&new_code).unwrap(); + item.extend(entry); + item +} diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index 93101489d4..1fee25682d 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -29,7 +29,7 @@ staking = [] backtraces = [] # stargate enables stargate-dependent messages and queries, like raw protobuf messages # as well as ibc-related functionality -stargate = [] +stargate = ["cosmwasm-derive/stargate"] # ibc3 extends ibc messages with ibc-v3 only features. This should only be enabled on contracts # that require these types. Without this, they get the smaller ibc-v1 API. ibc3 = ["stargate"] diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index 5b4b30925c..206e634632 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -24,6 +24,8 @@ use crate::memory::{alloc, consume_region, release_buffer, Region}; #[cfg(feature = "abort")] use crate::panic::install_panic_handler; use crate::query::CustomQuery; +#[cfg(feature = "stargate")] +use crate::results::{AdvContractResult, AdvResult}; use crate::results::{ContractResult, QueryResponse, Reply, Response}; use crate::serde::{from_slice, to_vec}; use crate::types::Env; @@ -86,6 +88,22 @@ macro_rules! r#try_into_contract_result { }; } +// TODO: replace with https://doc.rust-lang.org/std/ops/trait.Try.html once stabilized +#[cfg(feature = "stargate")] +macro_rules! r#try_into_adv_contract_result { + ($expr:expr) => { + match $expr { + Ok(val) => val, + Err(err) => { + return AdvContractResult::Err(err.to_string()); + } + } + }; + ($expr:expr,) => { + $crate::try_into_adv_contract_result!($expr) + }; +} + /// This should be wrapped in an external "C" export, containing a contract-specific function as an argument. /// /// - `Q`: custom query type (see QueryRequest) @@ -338,6 +356,39 @@ where release_buffer(v) as u32 } +/// do_ibc_packet_receive is designed for use with #[entry_point_adv] to make a "C" extern +/// +/// This allows us to return a new Abort type not just Ok or Err +/// +/// contract_fn is called when this chain receives an IBC Packet on a channel belonging +/// to this contract +/// +/// - `Q`: custom query type (see QueryRequest) +/// - `C`: custom response message type (see CosmosMsg) +/// - `E`: error type for responses +#[cfg(feature = "stargate")] +pub fn do_ibc_packet_receive_adv( + contract_fn: &dyn Fn( + DepsMut, + Env, + IbcPacketReceiveMsg, + ) -> AdvResult, E>, + env_ptr: u32, + msg_ptr: u32, +) -> u32 +where + Q: CustomQuery, + C: CustomMsg, + E: ToString, +{ + #[cfg(feature = "abort")] + install_panic_handler(); + let res = + _do_ibc_packet_receive_adv(contract_fn, env_ptr as *mut Region, msg_ptr as *mut Region); + let v = to_vec(&res).unwrap(); + release_buffer(v) as u32 +} + /// do_ibc_packet_ack is designed for use with #[entry_point] to make a "C" extern /// /// contract_fn is called when this chain receives an IBC Acknowledgement for a packet @@ -604,6 +655,31 @@ where contract_fn(deps.as_mut(), env, msg).into() } +#[cfg(feature = "stargate")] +fn _do_ibc_packet_receive_adv( + contract_fn: &dyn Fn( + DepsMut, + Env, + IbcPacketReceiveMsg, + ) -> AdvResult, E>, + env_ptr: *mut Region, + msg_ptr: *mut Region, +) -> AdvContractResult> +where + Q: CustomQuery, + C: CustomMsg, + E: ToString, +{ + let env: Vec = unsafe { consume_region(env_ptr) }; + let msg: Vec = unsafe { consume_region(msg_ptr) }; + + let env: Env = try_into_adv_contract_result!(from_slice(&env)); + let msg: IbcPacketReceiveMsg = try_into_adv_contract_result!(from_slice(&msg)); + + let mut deps = make_dependencies(); + contract_fn(deps.as_mut(), env, msg).into() +} + #[cfg(feature = "stargate")] fn _do_ibc_packet_ack( contract_fn: &dyn Fn(DepsMut, Env, IbcPacketAckMsg) -> Result, E>, diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 34a01301f5..066b8f67e8 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -71,9 +71,9 @@ pub use crate::results::SubMsgExecutionResponse; #[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))] pub use crate::results::WeightedVoteOption; pub use crate::results::{ - attr, wasm_execute, wasm_instantiate, Attribute, BankMsg, ContractResult, CosmosMsg, CustomMsg, - Empty, Event, QueryResponse, Reply, ReplyOn, Response, SubMsg, SubMsgResponse, SubMsgResult, - SystemResult, WasmMsg, + attr, wasm_execute, wasm_instantiate, AdvContractResult, AdvResult, Attribute, BankMsg, + ContractResult, CosmosMsg, CustomMsg, Empty, Event, QueryResponse, Reply, ReplyOn, Response, + SubMsg, SubMsgResponse, SubMsgResult, SystemResult, WasmMsg, }; #[cfg(feature = "staking")] pub use crate::results::{DistributionMsg, StakingMsg}; @@ -99,7 +99,7 @@ pub use crate::exports::{do_execute, do_instantiate, do_migrate, do_query, do_re #[cfg(all(feature = "stargate", target_arch = "wasm32"))] pub use crate::exports::{ do_ibc_channel_close, do_ibc_channel_connect, do_ibc_channel_open, do_ibc_packet_ack, - do_ibc_packet_receive, do_ibc_packet_timeout, + do_ibc_packet_receive, do_ibc_packet_receive_adv, do_ibc_packet_timeout, }; #[cfg(target_arch = "wasm32")] pub use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage}; @@ -112,3 +112,5 @@ pub mod testing; // Re-exports pub use cosmwasm_derive::entry_point; +#[cfg(feature = "stargate")] +pub use cosmwasm_derive::entry_point_adv; diff --git a/packages/std/src/results/adv_result.rs b/packages/std/src/results/adv_result.rs new file mode 100644 index 0000000000..4c11b6427c --- /dev/null +++ b/packages/std/src/results/adv_result.rs @@ -0,0 +1,163 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// This is like Result but we add an Abort case +pub enum AdvResult { + Ok(S), + Err(E), + Abort, +} + +impl From> for AdvResult { + fn from(original: Result) -> AdvResult { + match original { + Ok(value) => AdvResult::Ok(value), + Err(err) => AdvResult::Err(err), + } + } +} + +/// This is like ContractResult, but we add one more case. +/// This is only used for ibc-receive-packet-adv +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AdvContractResult { + Ok(S), + /// An error type that every custom error created by contract developers can be converted to. + /// This could potientially have more structure, but String is the easiest. + #[serde(rename = "error")] + Err(String), + /// This will abort the transaction rather than be managed somewhere + Abort {}, +} + +impl AdvContractResult { + pub fn unwrap(self) -> S { + match self { + AdvContractResult::Ok(s) => s, + AdvContractResult::Err(s) => panic!("{}", s), + AdvContractResult::Abort {} => panic!("{}", "abort"), + } + } +} + +impl From> for AdvContractResult { + fn from(original: AdvResult) -> AdvContractResult { + match original { + AdvResult::Ok(value) => AdvContractResult::Ok(value), + AdvResult::Err(err) => AdvContractResult::Err(err.to_string()), + AdvResult::Abort => AdvContractResult::Abort {}, + } + } +} + +impl From> for AdvContractResult { + fn from(original: Result) -> AdvContractResult { + let adv: AdvResult = original.into(); + adv.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{from_slice, to_vec, Response, StdError, StdResult}; + + #[test] + fn adv_result_serialization_works() { + let result = AdvContractResult::Ok(12); + assert_eq!(&to_vec(&result).unwrap(), b"{\"ok\":12}"); + + let result = AdvContractResult::Ok("foo"); + assert_eq!(&to_vec(&result).unwrap(), b"{\"ok\":\"foo\"}"); + + let result: AdvContractResult<()> = AdvContractResult::Abort {}; + assert_eq!(&to_vec(&result).unwrap(), b"{\"abort\":{}}"); + + let result: AdvContractResult = AdvContractResult::Ok(Response::default()); + assert_eq!( + to_vec(&result).unwrap(), + br#"{"ok":{"messages":[],"attributes":[],"events":[],"data":null}}"# + ); + + let result: AdvContractResult = AdvContractResult::Err("broken".to_string()); + assert_eq!(&to_vec(&result).unwrap(), b"{\"error\":\"broken\"}"); + } + + #[test] + fn adv_result_deserialization_works() { + let result: AdvContractResult = from_slice(br#"{"ok":12}"#).unwrap(); + assert_eq!(result, AdvContractResult::Ok(12)); + + let result: AdvContractResult = from_slice(br#"{"ok":"foo"}"#).unwrap(); + assert_eq!(result, AdvContractResult::Ok("foo".to_string())); + + let result: AdvContractResult = from_slice(br#"{"abort":{}}"#).unwrap(); + assert_eq!(result, AdvContractResult::Abort {}); + + let result: AdvContractResult = + from_slice(br#"{"ok":{"messages":[],"attributes":[],"events":[],"data":null}}"#) + .unwrap(); + assert_eq!(result, AdvContractResult::Ok(Response::default())); + + let result: AdvContractResult = from_slice(br#"{"error":"broken"}"#).unwrap(); + assert_eq!(result, AdvContractResult::Err("broken".to_string())); + + // ignores whitespace + let result: AdvContractResult = from_slice(b" {\n\t \"ok\": 5898\n} ").unwrap(); + assert_eq!(result, AdvContractResult::Ok(5898)); + + // fails for additional attributes + let parse: StdResult> = + from_slice(br#"{"unrelated":321,"ok":4554}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + let parse: StdResult> = + from_slice(br#"{"ok":4554,"unrelated":321}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + let parse: StdResult> = + from_slice(br#"{"ok":4554,"error":"What's up now?"}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + } + + #[test] + fn can_convert_from_core_result() { + let original: Result = Ok(Response::default()); + let converted: AdvContractResult = original.into(); + assert_eq!(converted, AdvContractResult::Ok(Response::default())); + + let original: Result = Err(StdError::generic_err("broken")); + let converted: AdvContractResult = original.into(); + assert_eq!( + converted, + AdvContractResult::Err("Generic error: broken".to_string()) + ); + } + + #[test] + fn can_convert_from_adv_result() { + let original: AdvResult = AdvResult::Ok(Response::default()); + let converted: AdvContractResult = original.into(); + assert_eq!(converted, AdvContractResult::Ok(Response::default())); + + let original: AdvResult = + AdvResult::Err(StdError::generic_err("broken")); + let converted: AdvContractResult = original.into(); + assert_eq!( + converted, + AdvContractResult::Err("Generic error: broken".to_string()) + ); + + let original: AdvResult = AdvResult::Abort; + let converted: AdvContractResult = original.into(); + assert_eq!(converted, AdvContractResult::Abort {}); + } +} diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs index 90b8879746..1bde96fc73 100644 --- a/packages/std/src/results/mod.rs +++ b/packages/std/src/results/mod.rs @@ -1,5 +1,6 @@ //! This module contains the messages that are sent from the contract to the VM as an execution result +mod adv_result; mod contract_result; mod cosmos_msg; mod empty; @@ -9,6 +10,7 @@ mod response; mod submessages; mod system_result; +pub use adv_result::{AdvContractResult, AdvResult}; pub use contract_result::ContractResult; #[cfg(all(feature = "stargate", feature = "cosmwasm_1_2"))] pub use cosmos_msg::WeightedVoteOption; diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index 32c7eff3eb..7760e2f829 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -1,13 +1,13 @@ use serde::de::DeserializeOwned; use wasmer::Val; -use cosmwasm_std::{ContractResult, CustomMsg, Env, MessageInfo, QueryResponse, Reply, Response}; #[cfg(feature = "stargate")] use cosmwasm_std::{ - Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, - IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, - IbcReceiveResponse, + AdvContractResult, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, + IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, + IbcPacketTimeoutMsg, IbcReceiveResponse, }; +use cosmwasm_std::{ContractResult, CustomMsg, Env, MessageInfo, QueryResponse, Reply, Response}; use crate::backend::{BackendApi, Querier, Storage}; use crate::conversion::ref_to_u32; @@ -276,7 +276,7 @@ pub fn call_ibc_packet_receive( instance: &mut Instance, env: &Env, msg: &IbcPacketReceiveMsg, -) -> VmResult>> +) -> VmResult>> where A: BackendApi + 'static, S: Storage + 'static, diff --git a/packages/vm/src/testing/calls.rs b/packages/vm/src/testing/calls.rs index 4c11640ed9..8b826f75ac 100644 --- a/packages/vm/src/testing/calls.rs +++ b/packages/vm/src/testing/calls.rs @@ -4,13 +4,13 @@ use schemars::JsonSchema; use serde::{de::DeserializeOwned, Serialize}; -use cosmwasm_std::{ContractResult, CustomMsg, Env, MessageInfo, QueryResponse, Reply, Response}; #[cfg(feature = "stargate")] use cosmwasm_std::{ - Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, - IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, - IbcReceiveResponse, + AdvContractResult, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, + IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, + IbcPacketTimeoutMsg, IbcReceiveResponse, }; +use cosmwasm_std::{ContractResult, CustomMsg, Env, MessageInfo, QueryResponse, Reply, Response}; use crate::calls::{ call_execute, call_instantiate, call_migrate, call_query, call_reply, call_sudo, @@ -198,7 +198,7 @@ pub fn ibc_packet_receive( instance: &mut Instance, env: Env, msg: IbcPacketReceiveMsg, -) -> ContractResult> +) -> AdvContractResult> where A: BackendApi + 'static, S: Storage + 'static,