1use mavryk_data_encoding::enc::BinWriter;
17use mavryk_data_encoding::encoding::HasEncoding;
18use mavryk_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
75impl<Expr: Michelson> From<OutboxMessageTransaction<Expr>> for OutboxMessage<Expr> {
76 fn from(transaction: OutboxMessageTransaction<Expr>) -> Self {
77 Self::AtomicTransactionBatch(vec![transaction].into())
78 }
79}
80
81impl<Expr: Michelson> From<Vec<OutboxMessageTransaction<Expr>>> for OutboxMessage<Expr> {
82 fn from(batch: Vec<OutboxMessageTransaction<Expr>>) -> Self {
83 Self::AtomicTransactionBatch(batch.into())
84 }
85}
86
87#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
97pub struct OutboxMessageTransaction<Expr: Michelson> {
98 pub parameters: Expr,
100 pub destination: Contract,
105 pub entrypoint: Entrypoint,
107}
108
109#[cfg(feature = "proto-alpha")]
119#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
120pub struct OutboxMessageWhitelistUpdate {
121 #[encoding(dynamic, list)]
123 pub whitelist: Option<Vec<PublicKeyHash>>,
124}
125
126#[cfg(feature = "proto-alpha")]
128#[derive(Debug, PartialEq, Eq)]
129pub enum InvalidWhitelist {
130 EmptyWhitelist,
132 DuplicatedKeys,
134}
135
136#[cfg(feature = "proto-alpha")]
137fn has_unique_elements<T>(iter: T) -> bool
138where
139 T: IntoIterator,
140 T::Item: Eq + Ord,
141{
142 let mut uniq = std::collections::BTreeSet::new();
143 iter.into_iter().all(move |x| uniq.insert(x))
144}
145
146#[cfg(feature = "proto-alpha")]
149impl TryFrom<Option<Vec<PublicKeyHash>>> for OutboxMessageWhitelistUpdate {
150 type Error = InvalidWhitelist;
151
152 fn try_from(whitelist: Option<Vec<PublicKeyHash>>) -> Result<Self, Self::Error> {
153 match whitelist {
154 Some(mut list) => {
155 if list.is_empty() {
156 return Err(InvalidWhitelist::EmptyWhitelist);
157 };
158 if !has_unique_elements(&mut list) {
159 Err(InvalidWhitelist::DuplicatedKeys)
160 } else {
161 Ok(Self {
162 whitelist: Some(list),
163 })
164 }
165 }
166 None => Ok(Self { whitelist: None }),
167 }
168 }
169}
170
171#[cfg(test)]
172mod test {
173 use crate::michelson::ticket::StringTicket;
174
175 use super::*;
176
177 const ENCODED_OUTBOX_MESSAGE_PREFIX: [u8; 5] = [0, 0, 0, 0, 152];
179
180 #[cfg(feature = "proto-alpha")]
182 const ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX: [u8; 1] = [2];
183
184 const ENCODED_TRANSACTION_ONE: [u8; 74] = [
185 7, 7, b'\n', 0, 0, 0, 22, 1, 209, 163, b'|', 8, 138, 18, b'!', 182, b'6', 187, b'_',
188 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,
198 69, 80, 0, 0, 0, 0, 7, b'd', b'e', b'f', b'a', b'u', b'l', b't',
202 ];
203
204 const ENCODED_TRANSACTION_TWO: [u8; 78] = [
205 7, 7, b'\n', 0, 0, 0, 22, 1, b'$', b'f', b'g', 169, b'1', 254, 11, 210, 251, 28, 182, 4,
209 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,
217 245, 27, 242, b'%', 197, 0, 0, 0, 0, 7, b'a', b'n', b'o', b't', b'h', b'e', b'r', ];
221
222 #[cfg(feature = "proto-alpha")]
227 const ENCODED_WHITELIST_UPDATE: [u8; 47] = [
228 0xff, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x2, 0x29, 0x8c, 0x3, 0xed, 0x7d, 0x45, 0x4a, 0x10, 0x1e, 0xb7, 0x2, 0x2b, 0xc9,
234 0x5f, 0x7e, 0x5f, 0x41, 0xac, 0x78,
235 0x0, 0xe7, 0x67, 0xf, 0x32, 0x3, 0x81, 0x7, 0xa5, 0x9a, 0x2b, 0x9c, 0xfe, 0xfa, 0xe3,
238 0x6e, 0xa2, 0x1f, 0x5a, 0xa6, 0x3c,
239 ];
240
241 #[test]
242 fn encode_transaction() {
243 let mut bin = vec![];
244 transaction_one().bin_write(&mut bin).unwrap();
245 assert_eq!(&ENCODED_TRANSACTION_ONE, bin.as_slice());
246 }
247
248 #[test]
249 #[cfg(feature = "proto-alpha")]
250 fn encode_whitelist_update() {
251 let mut bin = vec![];
252 whitelist().bin_write(&mut bin).unwrap();
253 assert_eq!(&ENCODED_WHITELIST_UPDATE, bin.as_slice());
254 }
255
256 #[test]
257 fn decode_transaction() {
258 let (remaining, decoded) =
259 OutboxMessageTransaction::nom_read(ENCODED_TRANSACTION_TWO.as_slice())
260 .unwrap();
261
262 assert!(remaining.is_empty());
263 assert_eq!(transaction_two(), decoded);
264 }
265
266 #[test]
267 #[cfg(feature = "proto-alpha")]
268 fn decode_whitelist_update() {
269 let (remaining, decoded) =
270 OutboxMessageWhitelistUpdate::nom_read(ENCODED_WHITELIST_UPDATE.as_slice())
271 .unwrap();
272 assert!(remaining.is_empty());
273 assert_eq!(whitelist(), decoded);
274 }
275
276 #[test]
277 fn encode_outbox_message() {
278 let mut expected = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
279 expected.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
280 expected.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
281
282 let message = OutboxMessage::AtomicTransactionBatch(
283 vec![transaction_one(), transaction_two()].into(),
284 );
285
286 let mut bin = vec![];
287 message.bin_write(&mut bin).unwrap();
288
289 assert_eq!(expected, bin);
290 }
291
292 #[test]
293 #[cfg(feature = "proto-alpha")]
294 fn encode_outbox_message_whitelist() {
295 let mut expected = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
296 expected.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
297
298 let message: OutboxMessage<StringTicket> =
299 OutboxMessage::WhitelistUpdate(whitelist());
300
301 let mut bin = vec![];
302 message.bin_write(&mut bin).unwrap();
303
304 assert_eq!(expected, bin);
305 }
306
307 #[test]
308 fn decode_outbox_message() {
309 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
310 bytes.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
311 bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
312
313 let expected = OutboxMessage::AtomicTransactionBatch(
314 vec![transaction_two(), transaction_one()].into(),
315 );
316
317 let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
318
319 assert!(remaining.is_empty());
320 assert_eq!(expected, message);
321 }
322
323 #[test]
324 #[cfg(feature = "proto-alpha")]
325 fn decode_outbox_message_whitelist() {
326 let mut bytes = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
327 bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
328
329 let expected: OutboxMessage<StringTicket> =
330 OutboxMessage::WhitelistUpdate(whitelist());
331
332 let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
333
334 assert!(remaining.is_empty());
335 assert_eq!(expected, message);
336 }
337
338 #[test]
339 fn decode_outbox_message_err_on_invalid_prefix() {
340 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
341 bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
343 bytes.extend_from_slice([10; 1000].as_slice());
345
346 assert!(OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()).is_err());
347 }
348
349 #[test]
350 #[cfg(feature = "proto-alpha")]
351 fn decode_outbox_message_whitelist_err_on_invalid_prefix() {
352 let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
353 bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
355 bytes.extend_from_slice([10; 1000].as_slice());
357
358 assert!(OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()).is_err());
359 }
360
361 fn transaction_one() -> OutboxMessageTransaction<StringTicket> {
362 let ticket = StringTicket::new(
363 Contract::from_b58check("KT1ThEdxfUcWUwqsdergy3QnbCWGHSUHeHJq").unwrap(),
364 "red".to_string(),
365 1_u64,
366 )
367 .unwrap();
368 make_transaction(ticket, "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc", "default")
369 }
370
371 fn transaction_two() -> OutboxMessageTransaction<StringTicket> {
372 let ticket = StringTicket::new(
373 Contract::from_b58check("KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc").unwrap(),
374 "yellow".to_string(),
375 329_u64,
376 )
377 .unwrap();
378 make_transaction(ticket, "KT1AaiUqbT3NmQts2w7ofY4vJviVchztiW4y", "another")
379 }
380
381 fn make_transaction(
382 ticket: StringTicket,
383 destination: &str,
384 entrypoint: &str,
385 ) -> OutboxMessageTransaction<StringTicket> {
386 let parameters = ticket;
387 let destination = Contract::from_b58check(destination).unwrap();
388 let entrypoint = Entrypoint::try_from(entrypoint.to_string()).unwrap();
389
390 OutboxMessageTransaction {
391 parameters,
392 destination,
393 entrypoint,
394 }
395 }
396
397 #[cfg(feature = "proto-alpha")]
398 fn whitelist() -> OutboxMessageWhitelistUpdate {
399 let whitelist = Some(vec![
400 PublicKeyHash::from_b58check("mv18Cw7psUrAAPBpXYd9CtCpHg9EgjHP9KTe").unwrap(),
401 PublicKeyHash::from_b58check("mv1V73YiKvinVumxwvYWjCZBoT44wqBNhta7").unwrap(),
402 ]);
403 OutboxMessageWhitelistUpdate { whitelist }
404 }
405
406 #[test]
407 #[cfg(feature = "proto-alpha")]
408 fn tryfrom_whitelist() {
409 let addr =
410 PublicKeyHash::from_b58check("mv18Cw7psUrAAPBpXYd9CtCpHg9EgjHP9KTe").unwrap();
411 let l1 = Some(vec![addr.clone(), addr.clone()]);
412 let w1: Result<OutboxMessageWhitelistUpdate, _> = l1.try_into();
413 assert_eq!(
414 w1.expect_err("Expected Err(InvalidWhitelist::DuplicatedKeys)"),
415 InvalidWhitelist::DuplicatedKeys
416 );
417 let l2 = Some(vec![]);
418 let w2: Result<OutboxMessageWhitelistUpdate, _> = l2.try_into();
419 assert_eq!(
420 w2.expect_err("Expected Err(InvalidWhitelist::EmptyWhitelist)"),
421 InvalidWhitelist::EmptyWhitelist
422 );
423 let l3 = None;
424 let w3: Result<OutboxMessageWhitelistUpdate, _> = l3.try_into();
425 assert_eq!(
426 w3.expect("Expected Ok(message)"),
427 (OutboxMessageWhitelistUpdate { whitelist: None })
428 );
429 let l4 = Some(vec![addr]);
430 let w4: Result<OutboxMessageWhitelistUpdate, _> = l4.clone().try_into();
431 assert_eq!(
432 w4.expect("Expected Ok(message)"),
433 (OutboxMessageWhitelistUpdate { whitelist: l4 })
434 );
435 }
436}