cw_ibc_query/
ibc.rs

1use crate::error::ContractError;
2use cosmwasm_std::{
3    attr, entry_point, DepsMut, Env, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg,
4    IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
5    IbcReceiveResponse, StdError, StdResult,
6};
7
8use crate::relay::{ack_fail, enforce_order_and_version, on_recv_packet};
9use crate::state::{ChannelData, CHANNELS_INFO};
10
11#[entry_point]
12pub fn ibc_channel_open(
13    _deps: DepsMut,
14    _env: Env,
15    msg: IbcChannelOpenMsg,
16) -> Result<(), ContractError> {
17    enforce_order_and_version(msg.channel(), msg.counterparty_version())?;
18
19    Ok(())
20}
21
22#[entry_point]
23pub fn ibc_channel_connect(
24    deps: DepsMut,
25    env: Env,
26    msg: IbcChannelConnectMsg,
27) -> Result<IbcBasicResponse, ContractError> {
28    let channel = msg.channel();
29    enforce_order_and_version(channel, msg.counterparty_version())?;
30
31    let channel_id = &channel.endpoint.channel_id;
32    let data = ChannelData {
33        creation_time: env.block.time,
34    };
35    CHANNELS_INFO.save(deps.storage, channel_id, &data)?;
36
37    Ok(IbcBasicResponse::new()
38        .add_attribute("action", "ibc_connect")
39        .add_attribute("channel_id", channel_id))
40}
41
42#[entry_point]
43pub fn ibc_channel_close(
44    deps: DepsMut,
45    _env: Env,
46    msg: IbcChannelCloseMsg,
47) -> StdResult<IbcBasicResponse> {
48    let channel = msg.channel();
49
50    // remove the channel
51    let channel_id = &channel.endpoint.channel_id;
52    CHANNELS_INFO.remove(deps.storage, channel_id);
53
54    Ok(IbcBasicResponse::new()
55        .add_attribute("action", "ibc_close")
56        .add_attribute("channel_id", channel_id))
57}
58
59#[entry_point]
60pub fn ibc_packet_receive(
61    deps: DepsMut,
62    _env: Env,
63    msg: IbcPacketReceiveMsg,
64) -> StdResult<IbcReceiveResponse> {
65    on_recv_packet(deps, &msg.packet).or_else(|err| {
66        Ok(IbcReceiveResponse::new()
67            .set_ack(ack_fail(err.to_string()))
68            .add_attributes(vec![
69                attr("action", "receive"),
70                attr("error", err.to_string()),
71            ]))
72    })
73}
74
75#[entry_point]
76pub fn ibc_packet_ack(
77    _deps: DepsMut,
78    _env: Env,
79    _msg: IbcPacketAckMsg,
80) -> StdResult<IbcBasicResponse> {
81    Err(StdError::generic_err("cannot receive acknowledgement"))
82}
83
84#[entry_point]
85pub fn ibc_packet_timeout(
86    _deps: DepsMut,
87    _env: Env,
88    _msg: IbcPacketTimeoutMsg,
89) -> StdResult<IbcBasicResponse> {
90    Err(StdError::generic_err("cannot cause a packet timeout"))
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::contract::{instantiate, query};
97    use crate::msg::{ChannelResponse, InstantiateMsg, QueryMsg};
98
99    use crate::ibc_msg::PacketMsg;
100    use crate::relay::QUERY_VERSION;
101    use cosmwasm_std::testing::{
102        mock_dependencies, mock_env, mock_ibc_channel_connect_ack, mock_ibc_channel_open_init,
103        mock_ibc_channel_open_try, mock_ibc_packet_ack, mock_ibc_packet_recv,
104        mock_ibc_packet_timeout, mock_info, MockApi, MockQuerier, MockStorage,
105    };
106    use cosmwasm_std::{from_slice, Binary, IbcAcknowledgement, IbcOrder, OwnedDeps};
107
108    const CREATOR: &str = "creator";
109
110    fn setup() -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
111        let mut deps = mock_dependencies();
112        let msg = InstantiateMsg {};
113        let info = mock_info(CREATOR, &[]);
114        let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
115        assert_eq!(0, res.messages.len());
116        deps
117    }
118
119    // connect will run through the entire handshake to set up a proper connect and
120    // save the account (tested in detail in `proper_handshake_flow`)
121    fn connect(mut deps: DepsMut, channel_id: &str) {
122        let handshake_open =
123            mock_ibc_channel_open_init(channel_id, IbcOrder::Unordered, QUERY_VERSION);
124        // first we try to open with a valid handshake
125        ibc_channel_open(deps.branch(), mock_env(), handshake_open).unwrap();
126
127        // then we connect (with counter-party version set)
128        let handshake_connect =
129            mock_ibc_channel_connect_ack(channel_id, IbcOrder::Unordered, QUERY_VERSION);
130        let res = ibc_channel_connect(deps.branch(), mock_env(), handshake_connect).unwrap();
131
132        assert_eq!(0, res.messages.len());
133    }
134
135    #[test]
136    fn enforce_version_in_handshake() {
137        let mut deps = setup();
138
139        let wrong_order = mock_ibc_channel_open_try("channel-12", IbcOrder::Ordered, QUERY_VERSION);
140        ibc_channel_open(deps.as_mut(), mock_env(), wrong_order).unwrap_err();
141
142        let wrong_version = mock_ibc_channel_open_try("channel-12", IbcOrder::Unordered, "reflect");
143        ibc_channel_open(deps.as_mut(), mock_env(), wrong_version).unwrap_err();
144
145        let valid_handshake =
146            mock_ibc_channel_open_try("channel-12", IbcOrder::Unordered, QUERY_VERSION);
147        ibc_channel_open(deps.as_mut(), mock_env(), valid_handshake).unwrap();
148    }
149
150    #[test]
151    fn proper_handshake_flow() {
152        // setup and connect handshake
153        let mut deps = setup();
154        let channel_id = "channel-1234";
155        connect(deps.as_mut(), channel_id);
156
157        // check for empty account
158        let q = QueryMsg::Channel {
159            id: channel_id.into(),
160        };
161        let r = query(deps.as_ref(), mock_env(), q).unwrap();
162        let acct: ChannelResponse = from_slice(&r).unwrap();
163        assert_eq!(true, acct.creation_time.nanos() > 0);
164
165        // account should be set up
166        let q = QueryMsg::Channel {
167            id: channel_id.into(),
168        };
169        let r = query(deps.as_ref(), mock_env(), q).unwrap();
170        let acct: ChannelResponse = from_slice(&r).unwrap();
171        assert_eq!(true, acct.creation_time.nanos() > 0);
172    }
173
174    #[test]
175    fn no_ack_packet_allowed() {
176        let mut deps = setup();
177        let channel_id = "channel-1234";
178        connect(deps.as_mut(), channel_id);
179
180        let ack_msg =
181            mock_ibc_packet_ack(channel_id, b"{}", IbcAcknowledgement::new(&[1])).unwrap();
182        ibc_packet_ack(deps.as_mut(), mock_env(), ack_msg).unwrap_err();
183
184        let timeout_msg = mock_ibc_packet_timeout(channel_id, b"{}").unwrap();
185        ibc_packet_timeout(deps.as_mut(), mock_env(), timeout_msg).unwrap_err();
186    }
187
188    #[test]
189    fn rcv_query_packet() {
190        let mut deps = setup();
191        let channel_id = "channel-1234";
192        connect(deps.as_mut(), channel_id);
193
194        let packet = PacketMsg {
195            client_id: None,
196            path: "/osmosis.gamm.v1beta1.Query/SpotPrice".to_string(),
197            data: Binary::from(&[1]),
198        };
199        let rcv_msg = mock_ibc_packet_recv(channel_id, &packet).unwrap();
200        let res = ibc_packet_receive(deps.as_mut(), mock_env(), rcv_msg).unwrap();
201
202        let error = res.attributes.iter().find(|r| r.key == "error".to_string());
203        // TODO: Unsupported query type: Stargate
204        assert_eq!("error", error.unwrap().key);
205        assert_eq!(0, res.messages.len());
206    }
207}