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

Implement TransferMsgBuilder #2167

Merged
merged 8 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions contracts/ibc-callbacks/src/contract.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions packages/std/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 23 additions & 1 deletion packages/std/src/ibc/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _transfer = TransferMsgBuilder::new(
chipshort marked this conversation as resolved.
Show resolved Hide resolved
/// "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();
///
Expand Down
288 changes: 288 additions & 0 deletions packages/std/src/ibc/transfer_msg_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
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,
}

impl From<EmptyMemo> for Option<String> {
fn from(_: EmptyMemo) -> Self {
None
}
}

impl From<WithMemo> for Option<String> {
fn from(m: WithMemo) -> Self {
Some(m.memo)
}
}

impl From<WithSrcCallback> for Option<String> {
fn from(s: WithSrcCallback) -> Self {
Some(to_json_string(&IbcCallbackRequest::source(s.src_callback)).unwrap())
}
}

impl From<WithDstCallback> for Option<String> {
fn from(d: WithDstCallback) -> Self {
Some(to_json_string(&IbcCallbackRequest::destination(d.dst_callback)).unwrap())
}
}

impl From<WithCallbacks> for Option<String> {
fn from(c: WithCallbacks) -> Self {
Some(to_json_string(&IbcCallbackRequest::both(c.src_callback, c.dst_callback)).unwrap())
}
}

impl<T: Into<Option<String>>> TransferMsgBuilder<T> {
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(),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TransferMsgBuilder<MemoData> {
channel_id: String,
to_address: String,
amount: Coin,
timeout: IbcTimeout,
memo: MemoData,
chipshort marked this conversation as resolved.
Show resolved Hide resolved
}

impl TransferMsgBuilder<EmptyMemo> {
/// Creates a new transfer message with the given parameters and no memo.
pub fn new(
channel_id: impl Into<String>,
to_address: impl Into<String>,
amount: Coin,
timeout: impl Into<IbcTimeout>,
) -> 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<String>) -> TransferMsgBuilder<WithMemo> {
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<WithSrcCallback> {
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<WithDstCallback> {
TransferMsgBuilder {
channel_id: self.channel_id,
to_address: self.to_address,
amount: self.amount,
timeout: self.timeout,
memo: WithDstCallback { dst_callback },
}
}
}

impl TransferMsgBuilder<WithSrcCallback> {
/// 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<WithCallbacks> {
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<WithDstCallback> {
/// 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<WithCallbacks> {
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);
}
}
1 change: 1 addition & 0 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub use crate::ibc::{
IbcDestinationCallbackMsg, IbcDstCallback, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket,
IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse,
IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
TransferMsgBuilder,
webmaster128 marked this conversation as resolved.
Show resolved Hide resolved
};
#[cfg(feature = "iterator")]
pub use crate::iterator::{Order, Record};
Expand Down