use tezos_data_encoding::enc::BinWriter;
use tezos_data_encoding::encoding::HasEncoding;
use tezos_data_encoding::nom::NomReader;
use crate::contract::Contract;
use crate::entrypoint::Entrypoint;
use crate::michelson::Michelson;
#[cfg(feature = "proto-alpha")]
use crate::public_key_hash::PublicKeyHash;
#[derive(Debug, PartialEq, Eq, HasEncoding, NomReader, BinWriter)]
pub enum OutboxMessage<Expr: Michelson> {
#[encoding(tag = 0)]
AtomicTransactionBatch(OutboxMessageTransactionBatch<Expr>),
#[cfg(feature = "proto-alpha")]
#[encoding(tag = 2)]
WhitelistUpdate(OutboxMessageWhitelistUpdate),
}
#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
pub struct OutboxMessageTransactionBatch<Expr: Michelson> {
#[encoding(dynamic, list)]
batch: Vec<OutboxMessageTransaction<Expr>>,
}
impl<Expr: Michelson> OutboxMessageTransactionBatch<Expr> {
pub fn len(&self) -> usize {
self.batch.len()
}
pub fn is_empty(&self) -> bool {
self.batch.is_empty()
}
}
impl<Expr: Michelson> core::ops::Index<usize> for OutboxMessageTransactionBatch<Expr> {
type Output = OutboxMessageTransaction<Expr>;
fn index(&self, index: usize) -> &Self::Output {
self.batch.index(index)
}
}
impl<Expr: Michelson> From<Vec<OutboxMessageTransaction<Expr>>>
for OutboxMessageTransactionBatch<Expr>
{
fn from(batch: Vec<OutboxMessageTransaction<Expr>>) -> Self {
Self { batch }
}
}
#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
pub struct OutboxMessageTransaction<Expr: Michelson> {
pub parameters: Expr,
pub destination: Contract,
pub entrypoint: Entrypoint,
}
#[cfg(feature = "proto-alpha")]
#[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)]
pub struct OutboxMessageWhitelistUpdate {
#[encoding(dynamic, list)]
pub whitelist: Option<Vec<PublicKeyHash>>,
}
#[cfg(feature = "proto-alpha")]
#[derive(Debug, PartialEq, Eq)]
pub enum InvalidWhitelist {
EmptyWhitelist,
DuplicatedKeys,
}
#[cfg(feature = "proto-alpha")]
fn has_unique_elements<T>(iter: T) -> bool
where
T: IntoIterator,
T::Item: Eq + Ord,
{
let mut uniq = std::collections::BTreeSet::new();
iter.into_iter().all(move |x| uniq.insert(x))
}
#[cfg(feature = "proto-alpha")]
impl TryFrom<Option<Vec<PublicKeyHash>>> for OutboxMessageWhitelistUpdate {
type Error = InvalidWhitelist;
fn try_from(whitelist: Option<Vec<PublicKeyHash>>) -> Result<Self, Self::Error> {
match whitelist {
Some(mut list) => {
if list.is_empty() {
return Err(InvalidWhitelist::EmptyWhitelist);
};
if !has_unique_elements(&mut list) {
Err(InvalidWhitelist::DuplicatedKeys)
} else {
Ok(Self {
whitelist: Some(list),
})
}
}
None => Ok(Self { whitelist: None }),
}
}
}
#[cfg(test)]
mod test {
use crate::michelson::ticket::StringTicket;
use super::*;
const ENCODED_OUTBOX_MESSAGE_PREFIX: [u8; 5] = [0, 0, 0, 0, 152];
#[cfg(feature = "proto-alpha")]
const ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX: [u8; 1] = [2];
const ENCODED_TRANSACTION_ONE: [u8; 74] = [
7, 7, b'\n', 0, 0, 0, 22, 1, 209, 163, b'|', 8, 138, 18, b'!', 182, b'6', 187, b'_',
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,
69, 80, 0, 0, 0, 0, 7, b'd', b'e', b'f', b'a', b'u', b'l', b't',
];
const ENCODED_TRANSACTION_TWO: [u8; 78] = [
7, 7, b'\n', 0, 0, 0, 22, 1, b'$', b'f', b'g', 169, b'1', 254, 11, 210, 251, 28, 182, 4,
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,
245, 27, 242, b'%', 197, 0, 0, 0, 0, 7, b'a', b'n', b'o', b't', b'h', b'e', b'r', ];
#[cfg(feature = "proto-alpha")]
const ENCODED_WHITELIST_UPDATE: [u8; 47] = [
0xff, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x2, 0x29, 0x8c, 0x3, 0xed, 0x7d, 0x45, 0x4a, 0x10, 0x1e, 0xb7, 0x2, 0x2b, 0xc9,
0x5f, 0x7e, 0x5f, 0x41, 0xac, 0x78,
0x0, 0xe7, 0x67, 0xf, 0x32, 0x3, 0x81, 0x7, 0xa5, 0x9a, 0x2b, 0x9c, 0xfe, 0xfa, 0xe3,
0x6e, 0xa2, 0x1f, 0x5a, 0xa6, 0x3c,
];
#[test]
fn encode_transaction() {
let mut bin = vec![];
transaction_one().bin_write(&mut bin).unwrap();
assert_eq!(&ENCODED_TRANSACTION_ONE, bin.as_slice());
}
#[test]
#[cfg(feature = "proto-alpha")]
fn encode_whitelist_update() {
let mut bin = vec![];
whitelist().bin_write(&mut bin).unwrap();
assert_eq!(&ENCODED_WHITELIST_UPDATE, bin.as_slice());
}
#[test]
fn decode_transaction() {
let (remaining, decoded) =
OutboxMessageTransaction::nom_read(ENCODED_TRANSACTION_TWO.as_slice())
.unwrap();
assert!(remaining.is_empty());
assert_eq!(transaction_two(), decoded);
}
#[test]
#[cfg(feature = "proto-alpha")]
fn decode_whitelist_update() {
let (remaining, decoded) =
OutboxMessageWhitelistUpdate::nom_read(ENCODED_WHITELIST_UPDATE.as_slice())
.unwrap();
assert!(remaining.is_empty());
assert_eq!(whitelist(), decoded);
}
#[test]
fn encode_outbox_message() {
let mut expected = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
expected.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
expected.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
let message = OutboxMessage::AtomicTransactionBatch(
vec![transaction_one(), transaction_two()].into(),
);
let mut bin = vec![];
message.bin_write(&mut bin).unwrap();
assert_eq!(expected, bin);
}
#[test]
#[cfg(feature = "proto-alpha")]
fn encode_outbox_message_whitelist() {
let mut expected = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
expected.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
let message: OutboxMessage<StringTicket> =
OutboxMessage::WhitelistUpdate(whitelist());
let mut bin = vec![];
message.bin_write(&mut bin).unwrap();
assert_eq!(expected, bin);
}
#[test]
fn decode_outbox_message() {
let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
bytes.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice());
bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
let expected = OutboxMessage::AtomicTransactionBatch(
vec![transaction_two(), transaction_one()].into(),
);
let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
assert!(remaining.is_empty());
assert_eq!(expected, message);
}
#[test]
#[cfg(feature = "proto-alpha")]
fn decode_outbox_message_whitelist() {
let mut bytes = ENCODED_OUTBOX_MESSAGE_WHITELIST_PREFIX.to_vec();
bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
let expected: OutboxMessage<StringTicket> =
OutboxMessage::WhitelistUpdate(whitelist());
let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap();
assert!(remaining.is_empty());
assert_eq!(expected, message);
}
#[test]
fn decode_outbox_message_err_on_invalid_prefix() {
let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice());
bytes.extend_from_slice([10; 1000].as_slice());
assert!(matches!(
OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()),
Err(_)
));
}
#[test]
#[cfg(feature = "proto-alpha")]
fn decode_outbox_message_whitelist_err_on_invalid_prefix() {
let mut bytes = ENCODED_OUTBOX_MESSAGE_PREFIX.to_vec();
bytes.extend_from_slice(ENCODED_WHITELIST_UPDATE.as_slice());
bytes.extend_from_slice([10; 1000].as_slice());
assert!(matches!(
OutboxMessage::<StringTicket>::nom_read(bytes.as_slice()),
Err(_)
));
}
fn transaction_one() -> OutboxMessageTransaction<StringTicket> {
let ticket = StringTicket::new(
Contract::from_b58check("KT1ThEdxfUcWUwqsdergy3QnbCWGHSUHeHJq").unwrap(),
"red".to_string(),
1_u64,
)
.unwrap();
make_transaction(ticket, "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc", "default")
}
fn transaction_two() -> OutboxMessageTransaction<StringTicket> {
let ticket = StringTicket::new(
Contract::from_b58check("KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc").unwrap(),
"yellow".to_string(),
329_u64,
)
.unwrap();
make_transaction(ticket, "KT1AaiUqbT3NmQts2w7ofY4vJviVchztiW4y", "another")
}
fn make_transaction(
ticket: StringTicket,
destination: &str,
entrypoint: &str,
) -> OutboxMessageTransaction<StringTicket> {
let parameters = ticket;
let destination = Contract::from_b58check(destination).unwrap();
let entrypoint = Entrypoint::try_from(entrypoint.to_string()).unwrap();
OutboxMessageTransaction {
parameters,
destination,
entrypoint,
}
}
#[cfg(feature = "proto-alpha")]
fn whitelist() -> OutboxMessageWhitelistUpdate {
let whitelist = Some(vec![
PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx").unwrap(),
PublicKeyHash::from_b58check("tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN").unwrap(),
]);
OutboxMessageWhitelistUpdate { whitelist }
}
#[test]
#[cfg(feature = "proto-alpha")]
fn tryfrom_whitelist() {
let addr =
PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx").unwrap();
let l1 = Some(vec![addr.clone(), addr.clone()]);
let w1: Result<OutboxMessageWhitelistUpdate, _> = l1.try_into();
assert_eq!(
w1.expect_err("Expected Err(InvalidWhitelist::DuplicatedKeys)"),
InvalidWhitelist::DuplicatedKeys
);
let l2 = Some(vec![]);
let w2: Result<OutboxMessageWhitelistUpdate, _> = l2.try_into();
assert_eq!(
w2.expect_err("Expected Err(InvalidWhitelist::EmptyWhitelist)"),
InvalidWhitelist::EmptyWhitelist
);
let l3 = None;
let w3: Result<OutboxMessageWhitelistUpdate, _> = l3.try_into();
assert_eq!(
w3.expect("Expected Ok(message)"),
(OutboxMessageWhitelistUpdate { whitelist: None })
);
let l4 = Some(vec![addr]);
let w4: Result<OutboxMessageWhitelistUpdate, _> = l4.clone().try_into();
assert_eq!(
w4.expect("Expected Ok(message)"),
(OutboxMessageWhitelistUpdate { whitelist: l4 })
);
}
}