bee_block/payload/milestone/option/
mod.rs

1// Copyright 2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4mod parameters;
5mod receipt;
6
7use alloc::{boxed::Box, vec::Vec};
8
9use derive_more::{Deref, From};
10use iterator_sorted::is_unique_sorted;
11use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable};
12
13pub(crate) use self::{parameters::BinaryParametersLength, receipt::ReceiptFundsCount};
14pub use self::{
15    parameters::ParametersMilestoneOption,
16    receipt::{MigratedFundsEntry, ReceiptMilestoneOption, TailTransactionHash},
17};
18use crate::{protocol::ProtocolParameters, Error};
19
20///
21#[derive(Clone, Debug, Eq, PartialEq, From, Packable)]
22#[cfg_attr(
23    feature = "serde",
24    derive(serde::Serialize, serde::Deserialize),
25    serde(tag = "type", content = "data")
26)]
27#[packable(unpack_error = Error)]
28#[packable(tag_type = u8, with_error = Error::InvalidMilestoneOptionKind)]
29#[packable(unpack_visitor = ProtocolParameters)]
30pub enum MilestoneOption {
31    /// A receipt milestone option.
32    #[packable(tag = ReceiptMilestoneOption::KIND)]
33    Receipt(ReceiptMilestoneOption),
34    /// A parameters milestone option.
35    #[packable(tag = ParametersMilestoneOption::KIND)]
36    Parameters(ParametersMilestoneOption),
37}
38
39impl MilestoneOption {
40    /// Return the milestone option kind of a [`MilestoneOption`].
41    pub fn kind(&self) -> u8 {
42        match self {
43            Self::Receipt(_) => ReceiptMilestoneOption::KIND,
44            Self::Parameters(_) => ParametersMilestoneOption::KIND,
45        }
46    }
47}
48
49pub(crate) type MilestoneOptionCount = BoundedU8<0, { MilestoneOptions::COUNT_MAX }>;
50
51///
52#[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMilestoneOptionCount(p.into())))]
55#[packable(unpack_visitor = ProtocolParameters)]
56pub struct MilestoneOptions(
57    #[packable(verify_with = verify_unique_sorted_packable)] BoxedSlicePrefix<MilestoneOption, MilestoneOptionCount>,
58);
59
60impl TryFrom<Vec<MilestoneOption>> for MilestoneOptions {
61    type Error = Error;
62
63    #[inline(always)]
64    fn try_from(milestone_options: Vec<MilestoneOption>) -> Result<Self, Self::Error> {
65        Self::new(milestone_options)
66    }
67}
68
69impl IntoIterator for MilestoneOptions {
70    type Item = MilestoneOption;
71    type IntoIter = alloc::vec::IntoIter<Self::Item>;
72
73    fn into_iter(self) -> Self::IntoIter {
74        Vec::from(Into::<Box<[MilestoneOption]>>::into(self.0)).into_iter()
75    }
76}
77
78impl MilestoneOptions {
79    ///
80    pub const COUNT_MAX: u8 = 2;
81
82    /// Creates a new [`MilestoneOptions`].
83    pub fn new(milestone_options: Vec<MilestoneOption>) -> Result<Self, Error> {
84        let mut milestone_options =
85            BoxedSlicePrefix::<MilestoneOption, MilestoneOptionCount>::try_from(milestone_options.into_boxed_slice())
86                .map_err(Error::InvalidMilestoneOptionCount)?;
87
88        milestone_options.sort_by_key(MilestoneOption::kind);
89        // Sort is obviously fine now but uniqueness still needs to be checked.
90        verify_unique_sorted::<true>(&milestone_options)?;
91
92        Ok(Self(milestone_options))
93    }
94
95    /// Gets a reference to a [`MilestoneOption`] from a milestone option kind, if any.
96    #[inline(always)]
97    pub fn get(&self, key: u8) -> Option<&MilestoneOption> {
98        self.0
99            .binary_search_by_key(&key, MilestoneOption::kind)
100            // PANIC: indexation is fine since the index has been found.
101            .map(|index| &self.0[index])
102            .ok()
103    }
104
105    /// Gets a reference to a [`ReceiptMilestoneOption`], if any.
106    pub fn receipt(&self) -> Option<&ReceiptMilestoneOption> {
107        if let Some(MilestoneOption::Receipt(receipt)) = self.get(ReceiptMilestoneOption::KIND) {
108            Some(receipt)
109        } else {
110            None
111        }
112    }
113
114    /// Gets a reference to a [`ParametersMilestoneOption`], if any.
115    pub fn parameters(&self) -> Option<&ParametersMilestoneOption> {
116        if let Some(MilestoneOption::Parameters(parameters)) = self.get(ParametersMilestoneOption::KIND) {
117            Some(parameters)
118        } else {
119            None
120        }
121    }
122}
123
124#[inline]
125fn verify_unique_sorted<const VERIFY: bool>(milestone_options: &[MilestoneOption]) -> Result<(), Error> {
126    if VERIFY && !is_unique_sorted(milestone_options.iter().map(MilestoneOption::kind)) {
127        Err(Error::MilestoneOptionsNotUniqueSorted)
128    } else {
129        Ok(())
130    }
131}
132
133#[inline]
134fn verify_unique_sorted_packable<const VERIFY: bool>(
135    milestone_options: &[MilestoneOption],
136    _visitor: &ProtocolParameters,
137) -> Result<(), Error> {
138    verify_unique_sorted::<VERIFY>(milestone_options)
139}
140
141#[cfg(feature = "dto")]
142#[allow(missing_docs)]
143pub mod dto {
144    use serde::{Deserialize, Serialize, Serializer};
145    use serde_json::Value;
146
147    pub use self::{
148        parameters::dto::ParametersMilestoneOptionDto,
149        receipt::dto::{MigratedFundsEntryDto, ReceiptMilestoneOptionDto},
150    };
151    use super::*;
152    use crate::error::dto::DtoError;
153
154    #[derive(Clone, Debug, Eq, PartialEq, From)]
155    pub enum MilestoneOptionDto {
156        /// A receipt milestone option.
157        Receipt(ReceiptMilestoneOptionDto),
158        /// A parameters milestone option.
159        Parameters(ParametersMilestoneOptionDto),
160    }
161
162    impl<'de> Deserialize<'de> for MilestoneOptionDto {
163        fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
164            let value = Value::deserialize(d)?;
165            Ok(
166                match value
167                    .get("type")
168                    .and_then(Value::as_u64)
169                    .ok_or_else(|| serde::de::Error::custom("invalid milestone option type"))?
170                    as u8
171                {
172                    ReceiptMilestoneOption::KIND => {
173                        MilestoneOptionDto::Receipt(ReceiptMilestoneOptionDto::deserialize(value).map_err(|e| {
174                            serde::de::Error::custom(format!("cannot deserialize receipt milestone option: {}", e))
175                        })?)
176                    }
177                    ParametersMilestoneOption::KIND => MilestoneOptionDto::Parameters(
178                        ParametersMilestoneOptionDto::deserialize(value).map_err(|e| {
179                            serde::de::Error::custom(format!("cannot deserialize parameters milestone option: {}", e))
180                        })?,
181                    ),
182                    _ => return Err(serde::de::Error::custom("invalid milestone option type")),
183                },
184            )
185        }
186    }
187
188    impl Serialize for MilestoneOptionDto {
189        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
190        where
191            S: Serializer,
192        {
193            #[derive(Serialize)]
194            #[serde(untagged)]
195            enum MilestoneOptionDto_<'a> {
196                T1(&'a ReceiptMilestoneOptionDto),
197                T2(&'a ParametersMilestoneOptionDto),
198            }
199            #[derive(Serialize)]
200            struct TypedMilestoneOption<'a> {
201                #[serde(flatten)]
202                milestone_option: MilestoneOptionDto_<'a>,
203            }
204            let milestone_option = match self {
205                MilestoneOptionDto::Receipt(o) => TypedMilestoneOption {
206                    milestone_option: MilestoneOptionDto_::T1(o),
207                },
208                MilestoneOptionDto::Parameters(o) => TypedMilestoneOption {
209                    milestone_option: MilestoneOptionDto_::T2(o),
210                },
211            };
212            milestone_option.serialize(serializer)
213        }
214    }
215
216    impl From<&MilestoneOption> for MilestoneOptionDto {
217        fn from(value: &MilestoneOption) -> Self {
218            match value {
219                MilestoneOption::Receipt(v) => Self::Receipt(v.into()),
220                MilestoneOption::Parameters(v) => Self::Parameters(v.into()),
221            }
222        }
223    }
224
225    impl MilestoneOption {
226        pub fn try_from_dto(value: &MilestoneOptionDto, token_supply: u64) -> Result<MilestoneOption, DtoError> {
227            Ok(match value {
228                MilestoneOptionDto::Receipt(v) => {
229                    MilestoneOption::Receipt(ReceiptMilestoneOption::try_from_dto(v, token_supply)?)
230                }
231                MilestoneOptionDto::Parameters(v) => MilestoneOption::Parameters(v.try_into()?),
232            })
233        }
234    }
235
236    impl MilestoneOptionDto {
237        /// Returns the milestone option kind of a [`MilestoneOptionDto`].
238        pub fn kind(&self) -> u8 {
239            match self {
240                Self::Receipt(_) => ReceiptMilestoneOption::KIND,
241                Self::Parameters(_) => ParametersMilestoneOption::KIND,
242            }
243        }
244    }
245}