evm_note/
ibc.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    DepsMut, Env, HexBinary, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg,
5    IbcChannelOpenMsg, IbcChannelOpenResponse, IbcPacketAckMsg, IbcPacketReceiveMsg,
6    IbcPacketTimeoutMsg, IbcReceiveResponse, Never, Reply, Response, SubMsg,
7};
8use polytone_evm::{accounts, ack::Ack, callbacks, handshake::note, ibc::Packet};
9
10use crate::{
11    error::ContractError,
12    state::{BLOCK_MAX_GAS, CHANNEL, CONNECTION_REMOTE_PORT},
13};
14
15// TODO: Remove before production
16pub(crate) mod temp_test {
17    use cosmwasm_schema::cw_serde;
18    use cosmwasm_std::HexBinary;
19    use cw_storage_plus::Map;
20    use polytone_evm::ibc::ExecuteResponsePacket;
21
22    use super::*;
23
24    #[cw_serde]
25    pub enum SentPacketData {
26        Binary(HexBinary),
27        Request(Packet),
28    }
29
30    #[cw_serde]
31    pub enum ReceivedPacketData {
32        Binary(HexBinary),
33        Result(ExecuteResponsePacket),
34    }
35
36    #[cw_serde]
37    pub struct AckInfo {
38        pub height: u64,
39        pub index: Option<u32>,
40        pub sent: SentPacketData,
41        pub result: Ack,
42    }
43
44    pub const ACK_DATAS: Map<u64, AckInfo> = Map::new("ack_datas");
45}
46
47/// The amount of gas that needs to be reserved for handling a
48/// callback error in the reply method. See `TestNoteOutOfGas` in the
49/// simulation tests for a test that can be used to tune thise.
50pub(crate) const ERR_GAS_NEEDED: u64 = 101_000;
51
52#[cfg_attr(not(feature = "library"), entry_point)]
53pub fn ibc_channel_open(
54    deps: DepsMut,
55    _env: Env,
56    msg: IbcChannelOpenMsg,
57) -> Result<IbcChannelOpenResponse, ContractError> {
58    let response = note::open(&msg, &["EVM"])?;
59    match CONNECTION_REMOTE_PORT.may_load(deps.storage)? {
60        Some((conn, port)) => {
61            if msg.channel().counterparty_endpoint.port_id != port
62                || msg.channel().connection_id != conn
63            {
64                Err(ContractError::AlreadyPaired {
65                    suggested_connection: msg.channel().connection_id.clone(),
66                    suggested_port: msg.channel().counterparty_endpoint.port_id.clone(),
67                    pair_connection: conn,
68                    pair_port: port,
69                })
70            } else {
71                Ok(response)
72            }
73        }
74        None => Ok(response),
75    }
76}
77
78#[cfg_attr(not(feature = "library"), entry_point)]
79pub fn ibc_channel_connect(
80    deps: DepsMut,
81    _env: Env,
82    msg: IbcChannelConnectMsg,
83) -> Result<IbcBasicResponse, ContractError> {
84    note::connect(&msg, &["EvmMsg"])?;
85    CONNECTION_REMOTE_PORT.save(
86        deps.storage,
87        &(
88            msg.channel().connection_id.clone(),
89            msg.channel().counterparty_endpoint.port_id.clone(),
90        ),
91    )?;
92    CHANNEL.save(deps.storage, &msg.channel().endpoint.channel_id)?;
93    Ok(IbcBasicResponse::new()
94        .add_attribute("method", "ibc_channel_connect")
95        .add_attribute("channel_id", &msg.channel().endpoint.channel_id))
96}
97
98#[cfg_attr(not(feature = "library"), entry_point)]
99pub fn ibc_channel_close(
100    deps: DepsMut,
101    _env: Env,
102    msg: IbcChannelCloseMsg,
103) -> Result<IbcBasicResponse, ContractError> {
104    CHANNEL.remove(deps.storage);
105    Ok(IbcBasicResponse::default()
106        .add_attribute("method", "ibc_channel_close")
107        .add_attribute("connection_id", msg.channel().connection_id.clone())
108        .add_attribute(
109            "counterparty_port_id",
110            msg.channel().counterparty_endpoint.port_id.clone(),
111        ))
112}
113
114#[cfg_attr(not(feature = "library"), entry_point)]
115pub fn ibc_packet_receive(
116    _deps: DepsMut,
117    _env: Env,
118    _msg: IbcPacketReceiveMsg,
119) -> Result<IbcReceiveResponse, Never> {
120    unreachable!("voice should never send a packet")
121}
122
123#[cfg_attr(not(feature = "library"), entry_point)]
124pub fn ibc_packet_ack(
125    deps: DepsMut,
126    _env: Env,
127    ack: IbcPacketAckMsg,
128) -> Result<IbcBasicResponse, ContractError> {
129    let maybe_packet = Packet::decode(&ack.original_packet.data);
130
131    // TODO: Remove error catching for production
132    let packet = match maybe_packet {
133        Ok(decoded) => temp_test::SentPacketData::Request(decoded),
134        Err(err) => {
135            deps.api.debug(&format!("Error decoding packet: {:?}", err));
136            temp_test::SentPacketData::Binary(HexBinary::from(ack.original_packet.data.clone()))
137        }
138    };
139
140    let (callback, executed_by, result) = callbacks::on_ack(deps.storage, &ack);
141
142    let callback_msg = callback.map(|callback| {
143        SubMsg::reply_on_error(callback, ack.original_packet.sequence).with_gas_limit(
144            BLOCK_MAX_GAS
145                .load(deps.storage)
146                .expect("set during instantiation")
147                - ERR_GAS_NEEDED,
148        )
149    });
150
151    accounts::on_ack(
152        deps.storage,
153        ack.original_packet.src.channel_id.clone(),
154        ack.original_packet.sequence,
155        executed_by,
156    );
157
158    let ack_info = temp_test::AckInfo {
159        height: _env.block.height,
160        index: _env.transaction.map(|tx| tx.index),
161        sent: packet,
162        result,
163    };
164
165    temp_test::ACK_DATAS
166        .save(deps.storage, ack.original_packet.sequence, &ack_info)
167        .ok();
168
169    Ok(IbcBasicResponse::default()
170        .add_attribute("method", "ibc_packet_ack")
171        .add_attribute("sequence_number", ack.original_packet.sequence.to_string())
172        .add_submessages(callback_msg))
173}
174
175#[cfg_attr(not(feature = "library"), entry_point)]
176pub fn ibc_packet_timeout(
177    deps: DepsMut,
178    _env: Env,
179    msg: IbcPacketTimeoutMsg,
180) -> Result<IbcBasicResponse, ContractError> {
181    let callback = callbacks::on_timeout(deps.storage, &msg).map(|cosmos_msg| {
182        SubMsg::reply_on_error(cosmos_msg, msg.packet.sequence).with_gas_limit(
183            BLOCK_MAX_GAS
184                .load(deps.storage)
185                .expect("set during instantiation")
186                - ERR_GAS_NEEDED,
187        )
188    });
189
190    accounts::on_timeout(deps.storage, msg.packet.src.channel_id, msg.packet.sequence);
191
192    Ok(IbcBasicResponse::default()
193        .add_attribute("method", "ibc_packet_timeout")
194        .add_attribute("sequence_number", msg.packet.sequence.to_string())
195        .add_submessages(callback))
196}
197
198#[cfg_attr(not(feature = "library"), entry_point)]
199pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
200    let sequence = msg.id;
201    Ok(Response::default()
202        .add_attribute("method", "reply_callback_error")
203        .add_attribute("packet_sequence", sequence.to_string())
204        .add_attribute("callback_error", msg.result.unwrap_err()))
205}