polytone_note/
ibc.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    DepsMut, Env, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg,
5    IbcChannelOpenResponse, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
6    IbcReceiveResponse, Never, Reply, Response, SubMsg,
7};
8use polytone::{accounts, callbacks, handshake::note};
9
10use crate::{
11    error::ContractError,
12    state::{BLOCK_MAX_GAS, CHANNEL, CONNECTION_REMOTE_PORT},
13};
14
15/// The amount of gas that needs to be reserved for handling a
16/// callback error in the reply method. See `TestNoteOutOfGas` in the
17/// simulation tests for a test that can be used to tune thise.
18pub(crate) const ERR_GAS_NEEDED: u64 = 101_000;
19
20#[cfg_attr(not(feature = "library"), entry_point)]
21pub fn ibc_channel_open(
22    deps: DepsMut,
23    _env: Env,
24    msg: IbcChannelOpenMsg,
25) -> Result<IbcChannelOpenResponse, ContractError> {
26    let response = note::open(&msg, &["JSON-CosmosMsg"])?;
27    match CONNECTION_REMOTE_PORT.may_load(deps.storage)? {
28        Some((conn, port)) => {
29            if msg.channel().counterparty_endpoint.port_id != port
30                || msg.channel().connection_id != conn
31            {
32                Err(ContractError::AlreadyPaired {
33                    suggested_connection: msg.channel().connection_id.clone(),
34                    suggested_port: msg.channel().counterparty_endpoint.port_id.clone(),
35                    pair_connection: conn,
36                    pair_port: port,
37                })
38            } else {
39                Ok(response)
40            }
41        }
42        None => Ok(response),
43    }
44}
45
46#[cfg_attr(not(feature = "library"), entry_point)]
47pub fn ibc_channel_connect(
48    deps: DepsMut,
49    _env: Env,
50    msg: IbcChannelConnectMsg,
51) -> Result<IbcBasicResponse, ContractError> {
52    note::connect(&msg, &["JSON-CosmosMsg"])?;
53    CONNECTION_REMOTE_PORT.save(
54        deps.storage,
55        &(
56            msg.channel().connection_id.clone(),
57            msg.channel().counterparty_endpoint.port_id.clone(),
58        ),
59    )?;
60    CHANNEL.save(deps.storage, &msg.channel().endpoint.channel_id)?;
61    Ok(IbcBasicResponse::new()
62        .add_attribute("method", "ibc_channel_connect")
63        .add_attribute("channel_id", &msg.channel().endpoint.channel_id))
64}
65
66#[cfg_attr(not(feature = "library"), entry_point)]
67pub fn ibc_channel_close(
68    deps: DepsMut,
69    _env: Env,
70    msg: IbcChannelCloseMsg,
71) -> Result<IbcBasicResponse, ContractError> {
72    CHANNEL.remove(deps.storage);
73    Ok(IbcBasicResponse::default()
74        .add_attribute("method", "ibc_channel_close")
75        .add_attribute("connection_id", msg.channel().connection_id.clone())
76        .add_attribute(
77            "counterparty_port_id",
78            msg.channel().counterparty_endpoint.port_id.clone(),
79        ))
80}
81
82#[cfg_attr(not(feature = "library"), entry_point)]
83pub fn ibc_packet_receive(
84    _deps: DepsMut,
85    _env: Env,
86    _msg: IbcPacketReceiveMsg,
87) -> Result<IbcReceiveResponse, Never> {
88    unreachable!("voice should never send a packet")
89}
90
91#[cfg_attr(not(feature = "library"), entry_point)]
92pub fn ibc_packet_ack(
93    deps: DepsMut,
94    _env: Env,
95    ack: IbcPacketAckMsg,
96) -> Result<IbcBasicResponse, ContractError> {
97    let (callback, executed_by) = callbacks::on_ack(deps.storage, &ack);
98    let callback = callback.map(|callback| {
99        SubMsg::reply_on_error(callback, ack.original_packet.sequence).with_gas_limit(
100            BLOCK_MAX_GAS
101                .load(deps.storage)
102                .expect("set during instantiation")
103                - ERR_GAS_NEEDED,
104        )
105    });
106
107    accounts::on_ack(
108        deps.storage,
109        ack.original_packet.src.channel_id.clone(),
110        ack.original_packet.sequence,
111        executed_by,
112    );
113
114    Ok(IbcBasicResponse::default()
115        .add_attribute("method", "ibc_packet_ack")
116        .add_attribute("sequence_number", ack.original_packet.sequence.to_string())
117        .add_submessages(callback))
118}
119
120#[cfg_attr(not(feature = "library"), entry_point)]
121pub fn ibc_packet_timeout(
122    deps: DepsMut,
123    _env: Env,
124    msg: IbcPacketTimeoutMsg,
125) -> Result<IbcBasicResponse, ContractError> {
126    let callback = callbacks::on_timeout(deps.storage, &msg).map(|cosmos_msg| {
127        SubMsg::reply_on_error(cosmos_msg, msg.packet.sequence).with_gas_limit(
128            BLOCK_MAX_GAS
129                .load(deps.storage)
130                .expect("set during instantiation")
131                - ERR_GAS_NEEDED,
132        )
133    });
134
135    accounts::on_timeout(deps.storage, msg.packet.src.channel_id, msg.packet.sequence);
136
137    Ok(IbcBasicResponse::default()
138        .add_attribute("method", "ibc_packet_timeout")
139        .add_attribute("sequence_number", msg.packet.sequence.to_string())
140        .add_submessages(callback))
141}
142
143#[cfg_attr(not(feature = "library"), entry_point)]
144pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
145    let sequence = msg.id;
146    Ok(Response::default()
147        .add_attribute("method", "reply_callback_error")
148        .add_attribute("packet_sequence", sequence.to_string())
149        .add_attribute("callback_error", msg.result.unwrap_err()))
150}