ics721_types/
ibc_types.rs1use 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 pub class_id: ClassId,
15 pub class_uri: Option<String>,
18 pub class_data: Option<Binary>,
21 pub token_ids: Vec<TokenId>,
24 pub token_uris: Option<Vec<String>>,
29 pub token_data: Option<Vec<Binary>>,
34
35 pub sender: String,
37 pub receiver: String,
40 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 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 Ok(())
85 }
86}
87
88#[cw_serde]
89pub struct IbcOutgoingMsg {
90 pub receiver: String,
93 pub channel_id: String,
96 pub timeout: IbcTimeout,
98 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}