1use tezos_data_encoding::enc::BinWriter;
17use tezos_data_encoding::encoding::HasEncoding;
18use tezos_data_encoding::nom::NomReader;
19
20use crate::contract::Contract;
21use crate::entrypoint::Entrypoint;
22use crate::michelson::Michelson;
23#[cfg(feature = "proto-alpha")]
24use crate::public_key_hash::PublicKeyHash;
25
26#[derive(Debug, PartialEq, Eq, HasEncoding, NomReader, BinWriter)]
30pub enum OutboxMessage<Expr: Michelson> {
31 #[encoding(tag = 0)]
33 AtomicTransactionBatch(OutboxMessageTransactionBatch<Expr>),
34 #[cfg(feature = "proto-alpha")]
36 #[encoding(tag = 2)]
37 WhitelistUpdate(OutboxMessageWhitelistUpdate),
38}
39
40#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
42pub struct OutboxMessageTransactionBatch<Expr: Michelson> {
43 #[encoding(dynamic, list)]
44 batch: Vec<OutboxMessageTransaction<Expr>>,
45}
46
47impl<Expr: Michelson> OutboxMessageTransactionBatch<Expr> {
48 pub fn len(&self) -> usize {
50 self.batch.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
55 self.batch.is_empty()
56 }
57}
58
59impl<Expr: Michelson> core::ops::Index<usize> for OutboxMessageTransactionBatch<Expr> {
60 type Output = OutboxMessageTransaction<Expr>;
61
62 fn index(&self, index: usize) -> &Self::Output {
63 self.batch.index(index)
64 }
65}
66
67impl<Expr: Michelson> From<Vec<OutboxMessageTransaction<Expr>>>
68 for OutboxMessageTransactionBatch<Expr>
69{
70 fn from(batch: Vec<OutboxMessageTransaction<Expr>>) -> Self {
71 Self { batch }
72 }
73}
74
75#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
85pub struct OutboxMessageTransaction<Expr: Michelson> {
86 pub parameters: Expr,
88 pub destination: Contract,
93 pub entrypoint: Entrypoint,
95}
96
97#[cfg(feature = "proto-alpha")]
107#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
108pub struct OutboxMessageWhitelistUpdate {
109 #[encoding(dynamic, list)]
111 pub whitelist: Option<Vec<PublicKeyHash>>,
112}
113
114#[cfg(feature = "proto-alpha")]
116#[derive(Debug, PartialEq, Eq)]
117pub enum InvalidWhitelist {
118 EmptyWhitelist,
120 DuplicatedKeys,
122}
123
124#[cfg(feature = "proto-alpha")]
125fn has_unique_elements<T>(iter: T) -> bool
126where
127 T: IntoIterator,
128 T::Item: Eq + Ord,
129{
130 let mut uniq = std::collections::BTreeSet::new();
131 iter.into_iter().all(move |x| uniq.insert(x))
132}
133
134#[cfg(feature = "proto-alpha")]
137impl TryFrom<Option<Vec<PublicKeyHash>>> for OutboxMessageWhitelistUpdate {
138 type Error = InvalidWhitelist;
139
140 fn try_from(whitelist: Option<Vec<PublicKeyHash>>) -> Result<Self, Self::Error> {
141 match whitelist {
142 Some(mut list) => {
143 if list.is_empty() {
144 return Err(InvalidWhitelist::EmptyWhitelist);
145 };
146 if !has_unique_elements(&mut list) {
147 Err(InvalidWhitelist::DuplicatedKeys)
148 } else {
149 Ok(Self {
150 whitelist: Some(list),
151 })
152 }
153 }
154 None => Ok(Self { whitelist: None }),
155 }
156 }
157}
158
159#[cfg(test)]
160mod test {
161 use crate::michelson::ticket::StringTicket;
162
163 use super::*;
164
165 const ENCODED_OUTBOX_MESSAGE_PREFIX: [u8; 5] = [0, 0, 0, 0, 152];
167
168 #[cfg(feature = "proto-alpha")]
170 const ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX: [u8; 1] = [2];
171
172 const ENCODED_TRANSACTION_ONE: [u8; 74] = [
173 7, 7, b'\n', 0, 0, 0, 22, 1, 209, 163, b'|', 8, 138, 18, b'!', 182, b'6', 187, b'_',
176 204, 179, b'^', 5, 24, 16, b'8', 186, b'|', 0, 7, 7, 1, 0, 0, 0, 3, b'r', b'e', b'd', 0, 1, 1, 36, 102, 103, 169, 49, 254, 11, 210, 251, 28, 182, 4, 247, 20, 96, 30, 136, 40,
186 69, 80, 0, 0, 0, 0, 7, b'd', b'e', b'f', b'a', b'u', b'l', b't',
190 ];
191
192 const ENCODED_TRANSACTION_TWO: [u8; 78] = [
193 7, 7, b'\n', 0, 0, 0, 22, 1, b'$', b'f', b'g', 169, b'1', 254, 11, 210, 251, 28, 182, 4,
197 247, 20, b'`', 30, 136, b'(', b'E', b'P', 0, 7, 7, 1, 0, 0, 0, 6, b'y', b'e', b'l', b'l', b'o', b'w', 0, 137, 5, 1, 21, 237, 173, b'\'', 159, b'U', 226, 254, b'@', 17, 222, b'm', b',', b'$', 253,
205 245, 27, 242, b'%', 197, 0, 0, 0, 0, 7, b'a', b'n', b'o', b't', b'h', b'e', b'r', ];
209
210 #[cfg(feature = "proto-alpha")]
215 const ENCODED_WHITELIST_UPDATE: [u8; 47] = [
216 0xff, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x2, 0x29, 0x8c, 0x3, 0xed, 0x7d, 0x45, 0x4a, 0x10, 0x1e, 0xb7, 0x2, 0x2b, 0xc9,
222 0x5f, 0x7e, 0x5f, 0x41, 0xac, 0x78,
223 0x0, 0xe7, 0x67, 0xf, 0x32, 0x3, 0x81, 0x7, 0xa5, 0x9a, 0x2b, 0x9c, 0xfe, 0xfa, 0xe3,
226 0x6e, 0xa2, 0x1f, 0x5a, 0xa6, 0x3c,
227 ];
228
229 #[test]
230 fn encode_transaction() {
231 let mut bin = vec![];
232 transaction_one().bin_write(&mut bin).unwrap();
233 assert_eq!(&ENCODED_TRANSACTION_ONE, bin.as_slice());
234 }
235
236 #[test]
237 #[cfg(feature = "proto-alpha")]
238 fn encode_whitelist_update() {
239 let mut bin = vec![];
240 whitelist().bin_write(&mut bin).unwrap();
241 assert_eq!(&ENCODED_WHITELIST_UPDATE, bin.as_slice());
242 }
243
244 #[test]
245 fn decode_transaction() {
246 let (remaining, decoded) =
247 OutboxMessageTransaction::nom_read(ENCODED_TRANSACTION_TWO.as_slice())
248 .unwrap();
249
250 assert!(remaining.is_empty());
251 assert_eq!(transaction_two(), decoded);
252 }
253
254 #[test]
255 #[cfg(feature = "proto-alpha")]
256 fn decode_whitelist_update() {
257 let (remaining, decoded) =
258 OutboxMessageWhitelistUpdate::nom_read(ENCODED_WHITELIST_UPDATE.as_slice())
259 .unwrap();
260 assert!(remaining.is_empty());
261 assert_eq!(whitelist(), decoded);
262 }
263
264 #[test]
265 fn encode_outbox_message() {
266 let mut expected = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
267 expected.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
268 expected.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
269
270 let message = OutboxMessage::AtomicTransactionBatch(
271 vec![transaction_one(), transaction_two()].into(),
272 );
273
274 let mut bin = vec![];
275 message.bin_write(&mut bin).unwrap();
276
277 assert_eq!(expected, bin);
278 }
279
280 #[test]
281 #[cfg(feature = "proto-alpha")]
282 fn encode_outbox_message_whitelist() {
283 let mut expected = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
284 expected.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
285
286 let message: OutboxMessage<StringTicket> =
287 OutboxMessage::WhitelistUpdate(whitelist());
288
289 let mut bin = vec![];
290 message.bin_write(&mut bin).unwrap();
291
292 assert_eq!(expected, bin);
293 }
294
295 #[test]
296 fn decode_outbox_message() {
297 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
298 bytes.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
299 bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
300
301 let expected = OutboxMessage::AtomicTransactionBatch(
302 vec![transaction_two(), transaction_one()].into(),
303 );
304
305 let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
306
307 assert!(remaining.is_empty());
308 assert_eq!(expected, message);
309 }
310
311 #[test]
312 #[cfg(feature = "proto-alpha")]
313 fn decode_outbox_message_whitelist() {
314 let mut bytes = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
315 bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
316
317 let expected: OutboxMessage<StringTicket> =
318 OutboxMessage::WhitelistUpdate(whitelist());
319
320 let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
321
322 assert!(remaining.is_empty());
323 assert_eq!(expected, message);
324 }
325
326 #[test]
327 fn decode_outbox_message_err_on_invalid_prefix() {
328 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
329 bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
331 bytes.extend_from_slice([10; 1000].as_slice());
333
334 assert!(matches!(
335 OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()),
336 Err(_)
337 ));
338 }
339
340 #[test]
341 #[cfg(feature = "proto-alpha")]
342 fn decode_outbox_message_whitelist_err_on_invalid_prefix() {
343 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
344 bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
346 bytes.extend_from_slice([10; 1000].as_slice());
348
349 assert!(matches!(
350 OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()),
351 Err(_)
352 ));
353 }
354
355 fn transaction_one() -> OutboxMessageTransaction<StringTicket> {
356 let ticket = StringTicket::new(
357 Contract::from_b58check("KT1ThEdxfUcWUwqsdergy3QnbCWGHSUHeHJq").unwrap(),
358 "red".to_string(),
359 1_u64,
360 )
361 .unwrap();
362 make_transaction(ticket, "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc", "default")
363 }
364
365 fn transaction_two() -> OutboxMessageTransaction<StringTicket> {
366 let ticket = StringTicket::new(
367 Contract::from_b58check("KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc").unwrap(),
368 "yellow".to_string(),
369 329_u64,
370 )
371 .unwrap();
372 make_transaction(ticket, "KT1AaiUqbT3NmQts2w7ofY4vJviVchztiW4y", "another")
373 }
374
375 fn make_transaction(
376 ticket: StringTicket,
377 destination: &str,
378 entrypoint: &str,
379 ) -> OutboxMessageTransaction<StringTicket> {
380 let parameters = ticket;
381 let destination = Contract::from_b58check(destination).unwrap();
382 let entrypoint = Entrypoint::try_from(entrypoint.to_string()).unwrap();
383
384 OutboxMessageTransaction {
385 parameters,
386 destination,
387 entrypoint,
388 }
389 }
390
391 #[cfg(feature = "proto-alpha")]
392 fn whitelist() -> OutboxMessageWhitelistUpdate {
393 let whitelist = Some(vec![
394 PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx").unwrap(),
395 PublicKeyHash::from_b58check("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN").unwrap(),
396 ]);
397 OutboxMessageWhitelistUpdate { whitelist }
398 }
399
400 #[test]
401 #[cfg(feature = "proto-alpha")]
402 fn tryfrom_whitelist() {
403 let addr =
404 PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx").unwrap();
405 let l1 = Some(vec![addr.clone(), addr.clone()]);
406 let w1: Result<OutboxMessageWhitelistUpdate, _> = l1.try_into();
407 assert_eq!(
408 w1.expect_err("Expected Err(InvalidWhitelist::DuplicatedKeys)"),
409 InvalidWhitelist::DuplicatedKeys
410 );
411 let l2 = Some(vec![]);
412 let w2: Result<OutboxMessageWhitelistUpdate, _> = l2.try_into();
413 assert_eq!(
414 w2.expect_err("Expected Err(InvalidWhitelist::EmptyWhitelist)"),
415 InvalidWhitelist::EmptyWhitelist
416 );
417 let l3 = None;
418 let w3: Result<OutboxMessageWhitelistUpdate, _> = l3.try_into();
419 assert_eq!(
420 w3.expect("Expected Ok(message)"),
421 (OutboxMessageWhitelistUpdate { whitelist: None })
422 );
423 let l4 = Some(vec![addr]);
424 let w4: Result<OutboxMessageWhitelistUpdate, _> = l4.clone().try_into();
425 assert_eq!(
426 w4.expect("Expected Ok(message)"),
427 (OutboxMessageWhitelistUpdate { whitelist: l4 })
428 );
429 }
430}