Skip to content

Commit b2350b1

Browse files
committed
Implement initial version of TransferMsgBuilder
1 parent 3a6613f commit b2350b1

File tree

4 files changed

+269
-2
lines changed

4 files changed

+269
-2
lines changed

packages/std/src/ibc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use crate::StdResult;
1313
use crate::{to_json_binary, Binary};
1414

1515
mod callbacks;
16+
mod transfer_msg_builder;
17+
1618
pub use callbacks::*;
19+
pub use transfer_msg_builder::*;
1720

1821
/// These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts
1922
/// (contracts that directly speak the IBC protocol via 6 entry points)

packages/std/src/ibc/callbacks.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ use crate::{Addr, IbcAcknowledgement, IbcPacket, Uint64};
3939
pub struct IbcCallbackRequest {
4040
// using private fields to force use of the constructors
4141
#[serde(skip_serializing_if = "Option::is_none")]
42-
src_callback: Option<IbcSrcCallback>,
42+
pub(crate) src_callback: Option<IbcSrcCallback>,
4343
#[serde(skip_serializing_if = "Option::is_none")]
44-
dest_callback: Option<IbcDstCallback>,
44+
pub(crate) dest_callback: Option<IbcDstCallback>,
4545
}
4646

4747
impl IbcCallbackRequest {
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
use std::marker::PhantomData;
2+
3+
use crate::{
4+
to_json_string, Coin, IbcCallbackRequest, IbcDstCallback, IbcMsg, IbcSrcCallback, IbcTimeout,
5+
};
6+
7+
// these are the different states the TransferMsgBuilder can be in
8+
#[derive(Clone, Debug, PartialEq, Eq)]
9+
struct EmptyMemo;
10+
#[derive(Clone, Debug, PartialEq, Eq)]
11+
struct WithMemo;
12+
#[derive(Clone, Debug, PartialEq, Eq)]
13+
struct WithSrcCallback;
14+
#[derive(Clone, Debug, PartialEq, Eq)]
15+
struct WithDstCallback;
16+
#[derive(Clone, Debug, PartialEq, Eq)]
17+
struct WithCallbacks;
18+
19+
// TODO: use trait for MemoData and get rid of state?
20+
#[derive(Clone, Debug, PartialEq, Eq)]
21+
enum MemoData {
22+
Empty,
23+
Text(String),
24+
IbcCallbacks(IbcCallbackRequest),
25+
}
26+
27+
impl From<MemoData> for Option<String> {
28+
fn from(memo: MemoData) -> Option<String> {
29+
match memo {
30+
MemoData::Empty => None,
31+
MemoData::Text(text) => Some(text),
32+
MemoData::IbcCallbacks(callbacks) => Some(to_json_string(&callbacks).unwrap()),
33+
}
34+
}
35+
}
36+
37+
impl<T> TransferMsgBuilder<T> {
38+
pub fn build(self) -> IbcMsg {
39+
IbcMsg::Transfer {
40+
channel_id: self.channel_id,
41+
to_address: self.to_address,
42+
amount: self.amount,
43+
timeout: self.timeout,
44+
memo: self.memo.into(),
45+
}
46+
}
47+
}
48+
49+
#[derive(Clone, Debug, PartialEq, Eq)]
50+
pub struct TransferMsgBuilder<State> {
51+
channel_id: String,
52+
to_address: String,
53+
amount: Coin,
54+
timeout: IbcTimeout,
55+
memo: MemoData,
56+
_state: PhantomData<State>,
57+
}
58+
59+
impl TransferMsgBuilder<EmptyMemo> {
60+
pub fn new(
61+
channel_id: impl Into<String>,
62+
to_address: impl Into<String>,
63+
amount: Coin,
64+
timeout: impl Into<IbcTimeout>,
65+
) -> Self {
66+
Self {
67+
channel_id: channel_id.into(),
68+
to_address: to_address.into(),
69+
amount,
70+
timeout: timeout.into(),
71+
memo: MemoData::Empty,
72+
_state: PhantomData,
73+
}
74+
}
75+
76+
pub fn with_memo(self, memo: impl Into<String>) -> TransferMsgBuilder<WithMemo> {
77+
TransferMsgBuilder {
78+
channel_id: self.channel_id,
79+
to_address: self.to_address,
80+
amount: self.amount,
81+
timeout: self.timeout,
82+
memo: MemoData::Text(memo.into()),
83+
_state: PhantomData,
84+
}
85+
}
86+
87+
pub fn with_src_callback(
88+
self,
89+
src_callback: IbcSrcCallback,
90+
) -> TransferMsgBuilder<WithSrcCallback> {
91+
TransferMsgBuilder {
92+
channel_id: self.channel_id,
93+
to_address: self.to_address,
94+
amount: self.amount,
95+
timeout: self.timeout,
96+
memo: MemoData::IbcCallbacks(IbcCallbackRequest::source(src_callback)),
97+
_state: PhantomData,
98+
}
99+
}
100+
101+
pub fn with_dst_callback(
102+
self,
103+
dst_callback: IbcDstCallback,
104+
) -> TransferMsgBuilder<WithDstCallback> {
105+
TransferMsgBuilder {
106+
channel_id: self.channel_id,
107+
to_address: self.to_address,
108+
amount: self.amount,
109+
timeout: self.timeout,
110+
memo: MemoData::IbcCallbacks(IbcCallbackRequest::destination(dst_callback)),
111+
_state: PhantomData,
112+
}
113+
}
114+
}
115+
116+
impl TransferMsgBuilder<WithSrcCallback> {
117+
pub fn with_dst_callback(
118+
self,
119+
dst_callback: IbcDstCallback,
120+
) -> TransferMsgBuilder<WithCallbacks> {
121+
TransferMsgBuilder {
122+
channel_id: self.channel_id,
123+
to_address: self.to_address,
124+
amount: self.amount,
125+
timeout: self.timeout,
126+
memo: match self.memo {
127+
MemoData::IbcCallbacks(IbcCallbackRequest {
128+
src_callback: Some(src_callback),
129+
..
130+
}) => MemoData::IbcCallbacks(IbcCallbackRequest::both(src_callback, dst_callback)),
131+
_ => unreachable!(), // we know this never happens because of the WithSrcCallback state
132+
},
133+
_state: PhantomData,
134+
}
135+
}
136+
}
137+
138+
impl TransferMsgBuilder<WithDstCallback> {
139+
pub fn with_src_callback(
140+
self,
141+
src_callback: IbcSrcCallback,
142+
) -> TransferMsgBuilder<WithCallbacks> {
143+
TransferMsgBuilder {
144+
channel_id: self.channel_id,
145+
to_address: self.to_address,
146+
amount: self.amount,
147+
timeout: self.timeout,
148+
memo: match self.memo {
149+
MemoData::IbcCallbacks(IbcCallbackRequest {
150+
dest_callback: Some(dst_callback),
151+
..
152+
}) => MemoData::IbcCallbacks(IbcCallbackRequest::both(src_callback, dst_callback)),
153+
_ => unreachable!(), // we know this never happens because of the WithDstCallback state
154+
},
155+
_state: PhantomData,
156+
}
157+
}
158+
}
159+
160+
#[cfg(test)]
161+
mod tests {
162+
use crate::{coin, Addr, Timestamp, Uint64};
163+
164+
use super::*;
165+
166+
#[test]
167+
fn test_transfer_msg_builder() {
168+
let src_callback = IbcSrcCallback {
169+
address: Addr::unchecked("src"),
170+
gas_limit: Some(Uint64::new(12345)),
171+
};
172+
let dst_callback = IbcDstCallback {
173+
address: "dst".to_string(),
174+
gas_limit: None,
175+
};
176+
177+
let empty_memo_builder = TransferMsgBuilder::new(
178+
"channel-0",
179+
"cosmos1example",
180+
coin(10, "ucoin"),
181+
Timestamp::from_seconds(12345),
182+
);
183+
184+
let empty = empty_memo_builder.clone().build();
185+
let with_memo = empty_memo_builder.clone().with_memo("memo").build();
186+
187+
let with_src_callback_builder = empty_memo_builder
188+
.clone()
189+
.with_src_callback(src_callback.clone());
190+
let with_src_callback = with_src_callback_builder.clone().build();
191+
let with_dst_callback_builder = empty_memo_builder
192+
.clone()
193+
.with_dst_callback(dst_callback.clone());
194+
let with_dst_callback = with_dst_callback_builder.clone().build();
195+
196+
let with_both_callbacks1 = with_src_callback_builder
197+
.with_dst_callback(dst_callback.clone())
198+
.build();
199+
200+
let with_both_callbacks2 = with_dst_callback_builder
201+
.with_src_callback(src_callback.clone())
202+
.build();
203+
204+
// assert all the different messages
205+
assert_eq!(
206+
empty,
207+
IbcMsg::Transfer {
208+
channel_id: "channel-0".to_string(),
209+
to_address: "cosmos1example".to_string(),
210+
amount: coin(10, "ucoin"),
211+
timeout: Timestamp::from_seconds(12345).into(),
212+
memo: None,
213+
}
214+
);
215+
assert_eq!(
216+
with_memo,
217+
IbcMsg::Transfer {
218+
channel_id: "channel-0".to_string(),
219+
to_address: "cosmos1example".to_string(),
220+
amount: coin(10, "ucoin"),
221+
timeout: Timestamp::from_seconds(12345).into(),
222+
memo: Some("memo".to_string()),
223+
}
224+
);
225+
assert_eq!(
226+
with_src_callback,
227+
IbcMsg::Transfer {
228+
channel_id: "channel-0".to_string(),
229+
to_address: "cosmos1example".to_string(),
230+
amount: coin(10, "ucoin"),
231+
timeout: Timestamp::from_seconds(12345).into(),
232+
memo: Some(
233+
to_json_string(&IbcCallbackRequest::source(src_callback.clone())).unwrap()
234+
),
235+
}
236+
);
237+
assert_eq!(
238+
with_dst_callback,
239+
IbcMsg::Transfer {
240+
channel_id: "channel-0".to_string(),
241+
to_address: "cosmos1example".to_string(),
242+
amount: coin(10, "ucoin"),
243+
timeout: Timestamp::from_seconds(12345).into(),
244+
memo: Some(
245+
to_json_string(&IbcCallbackRequest::destination(dst_callback.clone())).unwrap()
246+
),
247+
}
248+
);
249+
assert_eq!(
250+
with_both_callbacks1,
251+
IbcMsg::Transfer {
252+
channel_id: "channel-0".to_string(),
253+
to_address: "cosmos1example".to_string(),
254+
amount: coin(10, "ucoin"),
255+
timeout: Timestamp::from_seconds(12345).into(),
256+
memo: Some(
257+
to_json_string(&IbcCallbackRequest::both(src_callback, dst_callback)).unwrap()
258+
),
259+
}
260+
);
261+
assert_eq!(with_both_callbacks1, with_both_callbacks2);
262+
}
263+
}

packages/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub use crate::ibc::{
4545
IbcDestinationCallbackMsg, IbcDstCallback, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket,
4646
IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse,
4747
IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
48+
TransferMsgBuilder,
4849
};
4950
#[cfg(feature = "iterator")]
5051
pub use crate::iterator::{Order, Record};

0 commit comments

Comments
 (0)