ibc_app_nft_transfer/handler/
send_transfer.rs

1use ibc_core::channel::context::{SendPacketExecutionContext, SendPacketValidationContext};
2use ibc_core::channel::handler::{send_packet_execute, send_packet_validate};
3use ibc_core::channel::types::packet::Packet;
4use ibc_core::handler::types::events::MessageEvent;
5use ibc_core::host::types::path::{ChannelEndPath, SeqSendPath};
6use ibc_core::primitives::prelude::*;
7use ibc_core::router::types::event::ModuleEvent;
8
9use crate::context::{
10    NftClassContext, NftContext, NftTransferExecutionContext, NftTransferValidationContext,
11};
12use crate::types::error::NftTransferError;
13use crate::types::events::TransferEvent;
14use crate::types::msgs::transfer::MsgTransfer;
15use crate::types::{is_sender_chain_source, MODULE_ID_STR};
16
17/// Initiate a token transfer. Equivalent to calling [`send_nft_transfer_validate`], followed by [`send_nft_transfer_execute`].
18pub fn send_nft_transfer<SendPacketCtx, TransferCtx>(
19    send_packet_ctx_a: &mut SendPacketCtx,
20    transfer_ctx: &mut TransferCtx,
21    msg: MsgTransfer,
22) -> Result<(), NftTransferError>
23where
24    SendPacketCtx: SendPacketExecutionContext,
25    TransferCtx: NftTransferExecutionContext,
26{
27    send_nft_transfer_validate(send_packet_ctx_a, transfer_ctx, msg.clone())?;
28    send_nft_transfer_execute(send_packet_ctx_a, transfer_ctx, msg)
29}
30
31/// Validates the NFT transfer
32pub fn send_nft_transfer_validate<SendPacketCtx, TransferCtx>(
33    send_packet_ctx_a: &SendPacketCtx,
34    transfer_ctx: &TransferCtx,
35    msg: MsgTransfer,
36) -> Result<(), NftTransferError>
37where
38    SendPacketCtx: SendPacketValidationContext,
39    TransferCtx: NftTransferValidationContext,
40{
41    transfer_ctx.can_send_nft()?;
42
43    let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
44    let chan_end_on_a = send_packet_ctx_a.channel_end(&chan_end_path_on_a)?;
45
46    let port_id_on_b = chan_end_on_a.counterparty().port_id().clone();
47    let chan_id_on_b = chan_end_on_a
48        .counterparty()
49        .channel_id()
50        .ok_or_else(|| NftTransferError::MissingDestinationChannel {
51            port_id: msg.port_id_on_a.clone(),
52            channel_id: msg.chan_id_on_a.clone(),
53        })?
54        .clone();
55
56    let seq_send_path_on_a = SeqSendPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
57    let sequence = send_packet_ctx_a.get_next_sequence_send(&seq_send_path_on_a)?;
58
59    let sender: TransferCtx::AccountId = transfer_ctx.sender_account(&msg.packet_data.sender)?;
60
61    let mut packet_data = msg.packet_data;
62    let class_id = &packet_data.class_id;
63    let token_ids = &packet_data.token_ids;
64    // overwrite even if they are set in MsgTransfer
65    if let Some(uris) = &mut packet_data.token_uris {
66        uris.clear();
67    }
68    if let Some(data) = &mut packet_data.token_data {
69        data.clear();
70    }
71    for token_id in token_ids.as_ref() {
72        if is_sender_chain_source(msg.port_id_on_a.clone(), msg.chan_id_on_a.clone(), class_id) {
73            transfer_ctx.escrow_nft_validate(
74                &sender,
75                &msg.port_id_on_a,
76                &msg.chan_id_on_a,
77                class_id,
78                token_id,
79                &packet_data.memo.clone().unwrap_or("".into()),
80            )?;
81        } else {
82            transfer_ctx.burn_nft_validate(
83                &sender,
84                class_id,
85                token_id,
86                &packet_data.memo.clone().unwrap_or("".into()),
87            )?;
88        }
89        let nft = transfer_ctx.get_nft(class_id, token_id)?;
90        // Set the URI and the data if both exists
91        if let (Some(uri), Some(data)) = (nft.get_uri(), nft.get_data()) {
92            match &mut packet_data.token_uris {
93                Some(uris) => uris.push(uri.clone()),
94                None => packet_data.token_uris = Some(vec![uri.clone()]),
95            }
96            match &mut packet_data.token_data {
97                Some(token_data) => token_data.push(data.clone()),
98                None => packet_data.token_data = Some(vec![data.clone()]),
99            }
100        }
101    }
102
103    packet_data.validate_basic()?;
104
105    let nft_class = transfer_ctx.get_nft_class(class_id)?;
106    packet_data.class_uri = nft_class.get_uri().cloned();
107    packet_data.class_data = nft_class.get_data().cloned();
108
109    let packet = {
110        let data = serde_json::to_vec(&packet_data)
111            .expect("PacketData's infallible Serialize impl failed");
112
113        Packet {
114            seq_on_a: sequence,
115            port_id_on_a: msg.port_id_on_a,
116            chan_id_on_a: msg.chan_id_on_a,
117            port_id_on_b,
118            chan_id_on_b,
119            data,
120            timeout_height_on_b: msg.timeout_height_on_b,
121            timeout_timestamp_on_b: msg.timeout_timestamp_on_b,
122        }
123    };
124
125    send_packet_validate(send_packet_ctx_a, &packet)?;
126
127    Ok(())
128}
129
130/// Executes the token transfer. A prior call to [`send_nft_transfer_validate`] MUST have succeeded.
131pub fn send_nft_transfer_execute<SendPacketCtx, TransferCtx>(
132    send_packet_ctx_a: &mut SendPacketCtx,
133    transfer_ctx: &mut TransferCtx,
134    msg: MsgTransfer,
135) -> Result<(), NftTransferError>
136where
137    SendPacketCtx: SendPacketExecutionContext,
138    TransferCtx: NftTransferExecutionContext,
139{
140    let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
141    let chan_end_on_a = send_packet_ctx_a.channel_end(&chan_end_path_on_a)?;
142
143    let port_on_b = chan_end_on_a.counterparty().port_id().clone();
144    let chan_on_b = chan_end_on_a
145        .counterparty()
146        .channel_id()
147        .ok_or_else(|| NftTransferError::MissingDestinationChannel {
148            port_id: msg.port_id_on_a.clone(),
149            channel_id: msg.chan_id_on_a.clone(),
150        })?
151        .clone();
152
153    // get the next sequence
154    let seq_send_path_on_a = SeqSendPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
155    let sequence = send_packet_ctx_a.get_next_sequence_send(&seq_send_path_on_a)?;
156
157    let sender = transfer_ctx.sender_account(&msg.packet_data.sender)?;
158
159    let mut packet_data = msg.packet_data;
160    let class_id = &packet_data.class_id;
161    let token_ids = &packet_data.token_ids;
162    // overwrite even if they are set in MsgTransfer
163    if let Some(uris) = &mut packet_data.token_uris {
164        uris.clear();
165        uris.reserve_exact(token_ids.0.len());
166    }
167    if let Some(data) = &mut packet_data.token_data {
168        data.clear();
169        data.reserve_exact(token_ids.0.len());
170    }
171    for token_id in token_ids.as_ref() {
172        if is_sender_chain_source(msg.port_id_on_a.clone(), msg.chan_id_on_a.clone(), class_id) {
173            transfer_ctx.escrow_nft_execute(
174                &sender,
175                &msg.port_id_on_a,
176                &msg.chan_id_on_a,
177                class_id,
178                token_id,
179                &packet_data.memo.clone().unwrap_or("".into()),
180            )?;
181        } else {
182            transfer_ctx.burn_nft_execute(
183                &sender,
184                class_id,
185                token_id,
186                &packet_data.memo.clone().unwrap_or("".into()),
187            )?;
188        }
189        let nft = transfer_ctx.get_nft(class_id, token_id)?;
190        // Set the URI and the data if both exists
191        if let (Some(uri), Some(data)) = (nft.get_uri(), nft.get_data()) {
192            packet_data
193                .token_uris
194                .get_or_insert_with(|| Vec::with_capacity(token_ids.0.len()))
195                .push(uri.clone());
196            packet_data
197                .token_data
198                .get_or_insert_with(|| Vec::with_capacity(token_ids.0.len()))
199                .push(data.clone());
200        }
201    }
202
203    let nft_class = transfer_ctx.get_nft_class(class_id)?;
204    packet_data.class_uri = nft_class.get_uri().cloned();
205    packet_data.class_data = nft_class.get_data().cloned();
206
207    let packet = {
208        let data = {
209            serde_json::to_vec(&packet_data).expect("PacketData's infallible Serialize impl failed")
210        };
211
212        Packet {
213            seq_on_a: sequence,
214            port_id_on_a: msg.port_id_on_a,
215            chan_id_on_a: msg.chan_id_on_a,
216            port_id_on_b: port_on_b,
217            chan_id_on_b: chan_on_b,
218            data,
219            timeout_height_on_b: msg.timeout_height_on_b,
220            timeout_timestamp_on_b: msg.timeout_timestamp_on_b,
221        }
222    };
223
224    send_packet_execute(send_packet_ctx_a, packet)?;
225
226    {
227        send_packet_ctx_a.log_message(format!(
228            "IBC NFT transfer: {} --({}, [{}])--> {}",
229            packet_data.sender, class_id, token_ids, packet_data.receiver
230        ))?;
231
232        let transfer_event = TransferEvent {
233            sender: packet_data.sender,
234            receiver: packet_data.receiver,
235            class: packet_data.class_id,
236            tokens: packet_data.token_ids,
237            memo: packet_data.memo.unwrap_or("".into()),
238        };
239        send_packet_ctx_a.emit_ibc_event(ModuleEvent::from(transfer_event).into())?;
240
241        send_packet_ctx_a.emit_ibc_event(MessageEvent::Module(MODULE_ID_STR.to_string()).into())?;
242    }
243
244    Ok(())
245}