Skip to main content

ics721_types/
ibc_types.rs

1use cosmwasm_schema::cw_serde;
2use cosmwasm_std::{Binary, IbcTimeout};
3
4use crate::{
5    error::Ics721Error,
6    token_types::{ClassId, TokenId},
7};
8
9#[cw_serde]
10#[serde(rename_all = "camelCase")]
11pub struct NonFungibleTokenPacketData {
12    /// Uniquely identifies the collection which the tokens being
13    /// transfered belong to on the sending chain. Must be non-empty.
14    pub class_id: ClassId,
15    /// Optional URL that points to metadata about the
16    /// collection. Must be non-empty if provided.
17    pub class_uri: Option<String>,
18    /// Optional base64 encoded field which contains on-chain metadata
19    /// about the NFT class. Must be non-empty if provided.
20    pub class_data: Option<Binary>,
21    /// Uniquely identifies the tokens in the NFT collection being
22    /// transfered. This MUST be non-empty.
23    pub token_ids: Vec<TokenId>,
24    /// Optional URL that points to metadata for each token being
25    /// transfered. `tokenUris[N]` should hold the metadata for
26    /// `tokenIds[N]` and both lists should have the same if
27    /// provided. Must be non-empty if provided.
28    pub token_uris: Option<Vec<String>>,
29    /// Optional base64 encoded metadata for the tokens being
30    /// transfered. `tokenData[N]` should hold metadata for
31    /// `tokenIds[N]` and both lists should have the same length if
32    /// provided. Must be non-empty if provided.
33    pub token_data: Option<Vec<Binary>>,
34
35    /// The address sending the tokens on the sending chain.
36    pub sender: String,
37    /// The address that should receive the tokens on the receiving
38    /// chain.
39    pub receiver: String,
40    /// Memo to add custom string to the msg
41    pub memo: Option<String>,
42}
43
44macro_rules! non_empty_optional {
45    ($e:expr) => {
46        if $e.map_or(false, |data| data.is_empty()) {
47            return Err(Ics721Error::EmptyOptional {});
48        }
49    };
50}
51
52impl NonFungibleTokenPacketData {
53    pub fn validate(&self) -> Result<(), Ics721Error> {
54        if self.class_id.is_empty() {
55            return Err(Ics721Error::EmptyClassId {});
56        }
57
58        non_empty_optional!(self.class_uri.as_ref());
59        non_empty_optional!(self.class_data.as_ref());
60
61        let token_count = self.token_ids.len();
62        if token_count == 0 {
63            return Err(Ics721Error::NoTokens {});
64        }
65
66        // Non-empty optionality of tokenData an tokenUris implicitly
67        // checked here.
68        if self
69            .token_data
70            .as_ref()
71            .map_or(false, |data| data.len() != token_count)
72            || self
73                .token_uris
74                .as_ref()
75                .map_or(false, |data| data.len() != token_count)
76        {
77            return Err(Ics721Error::TokenInfoLenMissmatch {});
78        }
79
80        // This contract assumes that the backing cw721 is functional,
81        // so no need to check tokenIds for duplicates as the cw721
82        // will prevent minting of duplicates.
83
84        Ok(())
85    }
86}
87
88#[cw_serde]
89pub struct IbcOutgoingMsg {
90    /// The address that should receive the NFT being sent on the
91    /// *receiving chain*.
92    pub receiver: String,
93    /// The *local* channel ID this ought to be sent away on. This
94    /// contract must have a connection on this channel.
95    pub channel_id: String,
96    /// Timeout for the IBC message.
97    pub timeout: IbcTimeout,
98    /// Memo to add custom string to the msg
99    pub memo: Option<String>,
100}
101
102#[cw_serde]
103pub struct IbcOutgoingProxyMsg {
104    pub collection: String,
105    pub msg: Binary,
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::token_types::{ClassId, TokenId};
112
113    #[test]
114    fn test_packet_validation() {
115        let default_token = NonFungibleTokenPacketData {
116            class_id: ClassId::new("id"),
117            class_uri: None,
118            class_data: None,
119            token_ids: vec![TokenId::new("1")],
120            token_uris: None,
121            token_data: None,
122            sender: "violet".to_string(),
123            receiver: "blue".to_string(),
124            memo: None,
125        };
126
127        let empty_class_id = NonFungibleTokenPacketData {
128            class_id: ClassId::new(""),
129            ..default_token.clone()
130        };
131        let err = empty_class_id.validate().unwrap_err();
132        assert_eq!(err, Ics721Error::EmptyClassId {});
133
134        let empty_class_uri = NonFungibleTokenPacketData {
135            class_uri: Some("".to_string()),
136            ..default_token.clone()
137        };
138        let err = empty_class_uri.validate().unwrap_err();
139        assert_eq!(err, Ics721Error::EmptyOptional {});
140
141        let empty_class_data = NonFungibleTokenPacketData {
142            class_data: Some(Binary::default()),
143            ..default_token.clone()
144        };
145        let err = empty_class_data.validate().unwrap_err();
146        assert_eq!(err, Ics721Error::EmptyOptional {});
147
148        let no_tokens = NonFungibleTokenPacketData {
149            token_ids: vec![],
150            ..default_token.clone()
151        };
152        let err = no_tokens.validate().unwrap_err();
153        assert_eq!(err, Ics721Error::NoTokens {});
154
155        let uri_imbalance_empty = NonFungibleTokenPacketData {
156            token_uris: Some(vec![]),
157            ..default_token.clone()
158        };
159        let err = uri_imbalance_empty.validate().unwrap_err();
160        assert_eq!(err, Ics721Error::TokenInfoLenMissmatch {});
161
162        let uri_imbalance = NonFungibleTokenPacketData {
163            token_uris: Some(vec!["a".to_string(), "b".to_string()]),
164            ..default_token.clone()
165        };
166        let err = uri_imbalance.validate().unwrap_err();
167        assert_eq!(err, Ics721Error::TokenInfoLenMissmatch {});
168
169        let data_imbalance_empty = NonFungibleTokenPacketData {
170            token_data: Some(vec![]),
171            ..default_token.clone()
172        };
173        let err = data_imbalance_empty.validate().unwrap_err();
174        assert_eq!(err, Ics721Error::TokenInfoLenMissmatch {});
175
176        let data_imbalance = NonFungibleTokenPacketData {
177            token_data: Some(vec![Binary::default(), Binary::default()]),
178            ..default_token
179        };
180        let err = data_imbalance.validate().unwrap_err();
181        assert_eq!(err, Ics721Error::TokenInfoLenMissmatch {});
182    }
183}