diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b514818c2..362cdef46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ and this project adheres to `ibc_source_callback` and `ibc_destination_callback`, as well as the `IbcCallbackRequest` type. ([#2025]) - cosmwasm-vm: Add support for the two new IBC Callbacks entrypoints. ([#2025]) +- cosmwasm-std: Add `TransferMsgBuilder` to more easily create an + `IbcMsg::Transfer` with different kinds of memo values, including IBC + Callbacks memo values. ([#2167]) [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983 [#2025]: https://github.com/CosmWasm/cosmwasm/pull/2025 @@ -64,6 +67,7 @@ and this project adheres to [#2124]: https://github.com/CosmWasm/cosmwasm/pull/2124 [#2129]: https://github.com/CosmWasm/cosmwasm/pull/2129 [#2166]: https://github.com/CosmWasm/cosmwasm/pull/2166 +[#2167]: https://github.com/CosmWasm/cosmwasm/pull/2167 ### Changed diff --git a/contracts/ibc-callbacks/src/contract.rs b/contracts/ibc-callbacks/src/contract.rs index a0f4223de5..cd2f269f26 100644 --- a/contracts/ibc-callbacks/src/contract.rs +++ b/contracts/ibc-callbacks/src/contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ - entry_point, to_json_binary, to_json_string, Binary, Deps, DepsMut, Empty, Env, - IbcBasicResponse, IbcCallbackRequest, IbcDestinationCallbackMsg, IbcDstCallback, IbcMsg, - IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, MessageInfo, Response, StdError, StdResult, + entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, IbcBasicResponse, + IbcDestinationCallbackMsg, IbcDstCallback, IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, + MessageInfo, Response, StdError, StdResult, TransferMsgBuilder, }; use crate::msg::{CallbackType, ExecuteMsg, QueryMsg}; @@ -71,16 +71,19 @@ fn execute_transfer( } }; - let transfer_msg = IbcMsg::Transfer { - to_address, - timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout_seconds as u64)), + let builder = TransferMsgBuilder::new( channel_id, - amount: coin.clone(), - memo: Some(to_json_string(&match callback_type { - CallbackType::Both => IbcCallbackRequest::both(src_callback, dst_callback), - CallbackType::Src => IbcCallbackRequest::source(src_callback), - CallbackType::Dst => IbcCallbackRequest::destination(dst_callback), - })?), + to_address.clone(), + coin.clone(), + IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout_seconds as u64)), + ); + let transfer_msg = match callback_type { + CallbackType::Both => builder + .with_src_callback(src_callback) + .with_dst_callback(dst_callback) + .build(), + CallbackType::Src => builder.with_src_callback(src_callback).build(), + CallbackType::Dst => builder.with_dst_callback(dst_callback).build(), }; Ok(Response::new() diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index cec3b790d7..1b89c92a41 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -13,7 +13,10 @@ use crate::StdResult; use crate::{to_json_binary, Binary}; mod callbacks; +mod transfer_msg_builder; + pub use callbacks::*; +pub use transfer_msg_builder::*; /// These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts /// (contracts that directly speak the IBC protocol via 6 entry points) diff --git a/packages/std/src/ibc/callbacks.rs b/packages/std/src/ibc/callbacks.rs index 86b59bcc0b..d85c374a10 100644 --- a/packages/std/src/ibc/callbacks.rs +++ b/packages/std/src/ibc/callbacks.rs @@ -15,12 +15,34 @@ use crate::{Addr, IbcAcknowledgement, IbcPacket, Uint64}; /// /// # Example /// +/// Using [`TransferMsgBuilder`](crate::TransferMsgBuilder): /// ```rust /// use cosmwasm_std::{ -/// to_json_string, Coin, IbcCallbackRequest, IbcMsg, IbcSrcCallback, IbcTimeout, Response, +/// to_json_string, Coin, IbcCallbackRequest, TransferMsgBuilder, IbcSrcCallback, IbcTimeout, Response, /// Timestamp, /// }; +/// # use cosmwasm_std::testing::mock_env; +/// # let env = mock_env(); +/// +/// let _msg = TransferMsgBuilder::new( +/// "channel-0".to_string(), +/// "cosmos1example".to_string(), +/// Coin::new(10u32, "ucoin"), +/// Timestamp::from_seconds(12345), +/// ) +/// .with_src_callback(IbcSrcCallback { +/// address: env.contract.address, +/// gas_limit: None, +/// }) +/// .build(); +/// ``` /// +/// Manual serialization: +/// ```rust +/// use cosmwasm_std::{ +/// to_json_string, Coin, IbcCallbackRequest, IbcMsg, IbcSrcCallback, IbcTimeout, Response, +/// Timestamp, +/// }; /// # use cosmwasm_std::testing::mock_env; /// # let env = mock_env(); /// diff --git a/packages/std/src/ibc/transfer_msg_builder.rs b/packages/std/src/ibc/transfer_msg_builder.rs new file mode 100644 index 0000000000..484ef4038b --- /dev/null +++ b/packages/std/src/ibc/transfer_msg_builder.rs @@ -0,0 +1,298 @@ +use crate::{ + to_json_string, Coin, IbcCallbackRequest, IbcDstCallback, IbcMsg, IbcSrcCallback, IbcTimeout, +}; + +// these are the different memo types and at the same time the states +// the TransferMsgBuilder can be in +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EmptyMemo; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WithMemo { + memo: String, +} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WithSrcCallback { + src_callback: IbcSrcCallback, +} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WithDstCallback { + dst_callback: IbcDstCallback, +} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WithCallbacks { + src_callback: IbcSrcCallback, + dst_callback: IbcDstCallback, +} + +pub trait MemoSource { + fn into_memo(self) -> Option; +} + +impl MemoSource for EmptyMemo { + fn into_memo(self) -> Option { + None + } +} + +impl MemoSource for WithMemo { + fn into_memo(self) -> Option { + Some(self.memo) + } +} + +impl MemoSource for WithSrcCallback { + fn into_memo(self) -> Option { + Some(to_json_string(&IbcCallbackRequest::source(self.src_callback)).unwrap()) + } +} + +impl MemoSource for WithDstCallback { + fn into_memo(self) -> Option { + Some(to_json_string(&IbcCallbackRequest::destination(self.dst_callback)).unwrap()) + } +} + +impl MemoSource for WithCallbacks { + fn into_memo(self) -> Option { + Some( + to_json_string(&IbcCallbackRequest::both( + self.src_callback, + self.dst_callback, + )) + .unwrap(), + ) + } +} + +impl TransferMsgBuilder { + pub fn build(self) -> IbcMsg { + IbcMsg::Transfer { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: self.memo.into_memo(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransferMsgBuilder { + channel_id: String, + to_address: String, + amount: Coin, + timeout: IbcTimeout, + memo: MemoData, +} + +impl TransferMsgBuilder { + /// Creates a new transfer message with the given parameters and no memo. + pub fn new( + channel_id: impl Into, + to_address: impl Into, + amount: Coin, + timeout: impl Into, + ) -> Self { + Self { + channel_id: channel_id.into(), + to_address: to_address.into(), + amount, + timeout: timeout.into(), + memo: EmptyMemo, + } + } + + /// Adds a memo text to the transfer message. + pub fn with_memo(self, memo: impl Into) -> TransferMsgBuilder { + TransferMsgBuilder { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: WithMemo { memo: memo.into() }, + } + } + + /// Adds an IBC source callback entry to the memo field. + /// Use this if you want to receive IBC callbacks on the source chain. + /// + /// For more info check out [`crate::IbcSourceCallbackMsg`]. + pub fn with_src_callback( + self, + src_callback: IbcSrcCallback, + ) -> TransferMsgBuilder { + TransferMsgBuilder { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: WithSrcCallback { src_callback }, + } + } + + /// Adds an IBC destination callback entry to the memo field. + /// Use this if you want to receive IBC callbacks on the destination chain. + /// + /// For more info check out [`crate::IbcDestinationCallbackMsg`]. + pub fn with_dst_callback( + self, + dst_callback: IbcDstCallback, + ) -> TransferMsgBuilder { + TransferMsgBuilder { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: WithDstCallback { dst_callback }, + } + } +} + +impl TransferMsgBuilder { + /// Adds an IBC destination callback entry to the memo field. + /// Use this if you want to receive IBC callbacks on the destination chain. + /// + /// For more info check out [`crate::IbcDestinationCallbackMsg`]. + pub fn with_dst_callback( + self, + dst_callback: IbcDstCallback, + ) -> TransferMsgBuilder { + TransferMsgBuilder { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: WithCallbacks { + src_callback: self.memo.src_callback, + dst_callback, + }, + } + } +} + +impl TransferMsgBuilder { + /// Adds an IBC source callback entry to the memo field. + /// Use this if you want to receive IBC callbacks on the source chain. + /// + /// For more info check out [`crate::IbcSourceCallbackMsg`]. + pub fn with_src_callback( + self, + src_callback: IbcSrcCallback, + ) -> TransferMsgBuilder { + TransferMsgBuilder { + channel_id: self.channel_id, + to_address: self.to_address, + amount: self.amount, + timeout: self.timeout, + memo: WithCallbacks { + src_callback, + dst_callback: self.memo.dst_callback, + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{coin, Addr, Timestamp, Uint64}; + + use super::*; + + #[test] + fn test_transfer_msg_builder() { + let src_callback = IbcSrcCallback { + address: Addr::unchecked("src"), + gas_limit: Some(Uint64::new(12345)), + }; + let dst_callback = IbcDstCallback { + address: "dst".to_string(), + gas_limit: None, + }; + + let empty_memo_builder = TransferMsgBuilder::new( + "channel-0", + "cosmos1example", + coin(10, "ucoin"), + Timestamp::from_seconds(12345), + ); + + let empty = empty_memo_builder.clone().build(); + let with_memo = empty_memo_builder.clone().with_memo("memo").build(); + + let with_src_callback_builder = empty_memo_builder + .clone() + .with_src_callback(src_callback.clone()); + let with_src_callback = with_src_callback_builder.clone().build(); + let with_dst_callback_builder = empty_memo_builder + .clone() + .with_dst_callback(dst_callback.clone()); + let with_dst_callback = with_dst_callback_builder.clone().build(); + + let with_both_callbacks1 = with_src_callback_builder + .with_dst_callback(dst_callback.clone()) + .build(); + + let with_both_callbacks2 = with_dst_callback_builder + .with_src_callback(src_callback.clone()) + .build(); + + // assert all the different messages + assert_eq!( + empty, + IbcMsg::Transfer { + channel_id: "channel-0".to_string(), + to_address: "cosmos1example".to_string(), + amount: coin(10, "ucoin"), + timeout: Timestamp::from_seconds(12345).into(), + memo: None, + } + ); + assert_eq!( + with_memo, + IbcMsg::Transfer { + channel_id: "channel-0".to_string(), + to_address: "cosmos1example".to_string(), + amount: coin(10, "ucoin"), + timeout: Timestamp::from_seconds(12345).into(), + memo: Some("memo".to_string()), + } + ); + assert_eq!( + with_src_callback, + IbcMsg::Transfer { + channel_id: "channel-0".to_string(), + to_address: "cosmos1example".to_string(), + amount: coin(10, "ucoin"), + timeout: Timestamp::from_seconds(12345).into(), + memo: Some( + to_json_string(&IbcCallbackRequest::source(src_callback.clone())).unwrap() + ), + } + ); + assert_eq!( + with_dst_callback, + IbcMsg::Transfer { + channel_id: "channel-0".to_string(), + to_address: "cosmos1example".to_string(), + amount: coin(10, "ucoin"), + timeout: Timestamp::from_seconds(12345).into(), + memo: Some( + to_json_string(&IbcCallbackRequest::destination(dst_callback.clone())).unwrap() + ), + } + ); + assert_eq!( + with_both_callbacks1, + IbcMsg::Transfer { + channel_id: "channel-0".to_string(), + to_address: "cosmos1example".to_string(), + amount: coin(10, "ucoin"), + timeout: Timestamp::from_seconds(12345).into(), + memo: Some( + to_json_string(&IbcCallbackRequest::both(src_callback, dst_callback)).unwrap() + ), + } + ); + assert_eq!(with_both_callbacks1, with_both_callbacks2); + } +} diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index d83774e750..e3d86c12de 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -45,6 +45,7 @@ pub use crate::ibc::{ IbcDestinationCallbackMsg, IbcDstCallback, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg, + TransferMsgBuilder, }; #[cfg(feature = "iterator")] pub use crate::iterator::{Order, Record};