plutus_ledger_api/v1/
interval.rs

1//! Types related to PlutusInterval
2
3use crate::feature_traits::FeatureTraits;
4use crate::plutus_data::{
5    parse_constr, parse_constr_with_tag, parse_fixed_len_constr_fields, IsPlutusData, PlutusData,
6    PlutusDataError,
7};
8#[cfg(feature = "lbf")]
9use lbr_prelude::json::Json;
10use num_bigint::BigInt;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use std::cmp;
14
15//////////////
16// Interval //
17//////////////
18
19/// An abstraction over `PlutusInterval`, allowing valid values only
20#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub enum Interval<T> {
23    Finite(T, T),
24    StartAt(T),
25    StartAfter(T),
26    EndAt(T),
27    EndBefore(T),
28    Always,
29    Never,
30}
31
32impl<T> From<Interval<T>> for PlutusInterval<T>
33where
34    T: FeatureTraits,
35{
36    fn from(interval: Interval<T>) -> Self {
37        match interval {
38            Interval::Finite(start, end) => PlutusInterval {
39                from: LowerBound {
40                    bound: Extended::Finite(start),
41                    closed: true,
42                },
43                to: UpperBound {
44                    bound: Extended::Finite(end),
45                    closed: true,
46                },
47            },
48            Interval::StartAt(start) => PlutusInterval {
49                from: LowerBound {
50                    bound: Extended::Finite(start),
51                    closed: true,
52                },
53                to: UpperBound {
54                    bound: Extended::PosInf,
55                    closed: true,
56                },
57            },
58            Interval::StartAfter(start) => PlutusInterval {
59                from: LowerBound {
60                    bound: Extended::Finite(start),
61                    closed: false,
62                },
63                to: UpperBound {
64                    bound: Extended::PosInf,
65                    closed: true,
66                },
67            },
68            Interval::EndAt(end) => PlutusInterval {
69                from: LowerBound {
70                    bound: Extended::NegInf,
71                    closed: true,
72                },
73                to: UpperBound {
74                    bound: Extended::Finite(end),
75                    closed: true,
76                },
77            },
78            Interval::EndBefore(end) => PlutusInterval {
79                from: LowerBound {
80                    bound: Extended::NegInf,
81                    closed: true,
82                },
83                to: UpperBound {
84                    bound: Extended::Finite(end),
85                    closed: false,
86                },
87            },
88            Interval::Always => PlutusInterval {
89                from: LowerBound {
90                    bound: Extended::NegInf,
91                    closed: true,
92                },
93                to: UpperBound {
94                    bound: Extended::PosInf,
95                    closed: true,
96                },
97            },
98            Interval::Never => PlutusInterval {
99                from: LowerBound {
100                    bound: Extended::PosInf,
101                    closed: true,
102                },
103                to: UpperBound {
104                    bound: Extended::NegInf,
105                    closed: true,
106                },
107            },
108        }
109    }
110}
111
112#[derive(thiserror::Error, Debug)]
113pub enum TryFromPlutusIntervalError {
114    #[error("Interval is invalid.")]
115    InvalidInterval,
116    #[error("Interval with open bound could not be converted.")]
117    UnexpectedOpenBound,
118}
119
120impl<T> TryFrom<PlutusInterval<T>> for Interval<T>
121where
122    T: FeatureTraits + PartialOrd,
123{
124    type Error = TryFromPlutusIntervalError;
125
126    fn try_from(interval: PlutusInterval<T>) -> Result<Self, Self::Error> {
127        Ok(match interval {
128            PlutusInterval {
129                from:
130                    LowerBound {
131                        bound: Extended::Finite(start),
132                        closed: lc,
133                    },
134                to:
135                    UpperBound {
136                        bound: Extended::Finite(end),
137                        closed: uc,
138                    },
139            } => {
140                if lc && uc {
141                    if start > end {
142                        Err(TryFromPlutusIntervalError::InvalidInterval)?
143                    } else {
144                        Interval::Finite(start, end)
145                    }
146                } else {
147                    Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
148                }
149            }
150            PlutusInterval {
151                from:
152                    LowerBound {
153                        bound: Extended::Finite(start),
154                        closed: lc,
155                    },
156                to:
157                    UpperBound {
158                        bound: Extended::PosInf,
159                        closed: uc,
160                    },
161            } => {
162                if lc && uc {
163                    Interval::StartAt(start)
164                } else if !lc && uc {
165                    Interval::StartAfter(start)
166                } else {
167                    Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
168                }
169            }
170            PlutusInterval {
171                from:
172                    LowerBound {
173                        bound: Extended::NegInf,
174                        closed: lc,
175                    },
176                to:
177                    UpperBound {
178                        bound: Extended::Finite(end),
179                        closed: uc,
180                    },
181            } => {
182                if uc && lc {
183                    Interval::EndAt(end)
184                } else if !uc && lc {
185                    Interval::EndBefore(end)
186                } else {
187                    Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
188                }
189            }
190            PlutusInterval {
191                from:
192                    LowerBound {
193                        bound: Extended::NegInf,
194                        closed: lc,
195                    },
196                to:
197                    UpperBound {
198                        bound: Extended::PosInf,
199                        closed: uc,
200                    },
201            } => {
202                if lc && uc {
203                    Interval::Always
204                } else {
205                    Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
206                }
207            }
208            PlutusInterval {
209                from:
210                    LowerBound {
211                        bound: Extended::PosInf,
212                        closed: lc,
213                    },
214                to:
215                    UpperBound {
216                        bound: Extended::NegInf,
217                        closed: uc,
218                    },
219            } => {
220                if lc && uc {
221                    Interval::Never
222                } else {
223                    Err(TryFromPlutusIntervalError::UnexpectedOpenBound)?
224                }
225            }
226
227            _ => Err(TryFromPlutusIntervalError::InvalidInterval)?,
228        })
229    }
230}
231
232////////////////////
233// PlutusInterval //
234////////////////////
235
236/// An interval of `T`s.
237///
238/// The interval may be either closed or open at either end, meaning
239/// that the endpoints may or may not be included in the interval.
240///
241/// The interval can also be unbounded on either side.
242///
243/// The 'Eq' instance gives equality of the intervals, not structural equality.
244/// There is no 'Ord' instance, but 'contains' gives a partial order.
245///
246/// Note that some of the functions on `Interval` rely on `Enum` in order to
247/// handle non-inclusive endpoints. For this reason, it may not be safe to
248/// use `Interval`s with non-inclusive endpoints on types whose `Enum`
249/// instances have partial methods.
250#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[cfg_attr(feature = "lbf", derive(Json))]
252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
253pub struct PlutusInterval<T>
254where
255    T: FeatureTraits,
256{
257    pub from: LowerBound<T>,
258    pub to: UpperBound<T>,
259}
260
261impl<T> IsPlutusData for PlutusInterval<T>
262where
263    T: FeatureTraits + IsPlutusData,
264{
265    fn to_plutus_data(&self) -> PlutusData {
266        PlutusData::Constr(
267            BigInt::from(0),
268            vec![self.from.to_plutus_data(), self.to.to_plutus_data()],
269        )
270    }
271
272    fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
273        let fields = parse_constr_with_tag(data, 0)?;
274        let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
275        Ok(Self {
276            from: IsPlutusData::from_plutus_data(field_0)?,
277            to: IsPlutusData::from_plutus_data(field_1)?,
278        })
279    }
280}
281
282////////////////
283// UpperBound //
284////////////////
285
286#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
287#[cfg_attr(feature = "lbf", derive(Json))]
288#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
289pub struct UpperBound<T>
290where
291    T: FeatureTraits,
292{
293    pub bound: Extended<T>,
294    pub closed: bool,
295}
296
297impl<T> IsPlutusData for UpperBound<T>
298where
299    T: FeatureTraits + IsPlutusData,
300{
301    fn to_plutus_data(&self) -> PlutusData {
302        PlutusData::Constr(
303            BigInt::from(0),
304            vec![self.bound.to_plutus_data(), self.closed.to_plutus_data()],
305        )
306    }
307
308    fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
309        let fields = parse_constr_with_tag(data, 0)?;
310        let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
311        Ok(Self {
312            bound: IsPlutusData::from_plutus_data(field_0)?,
313            closed: IsPlutusData::from_plutus_data(field_1)?,
314        })
315    }
316}
317
318////////////////
319// LowerBound //
320////////////////
321
322#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
323#[cfg_attr(feature = "lbf", derive(Json))]
324#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
325pub struct LowerBound<T>
326where
327    T: FeatureTraits,
328{
329    pub bound: Extended<T>,
330    pub closed: bool,
331}
332
333impl<T> IsPlutusData for LowerBound<T>
334where
335    T: FeatureTraits + IsPlutusData,
336{
337    fn to_plutus_data(&self) -> PlutusData {
338        PlutusData::Constr(
339            BigInt::from(0),
340            vec![self.bound.to_plutus_data(), self.closed.to_plutus_data()],
341        )
342    }
343
344    fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
345        let fields = parse_constr_with_tag(data, 0)?;
346        let [field_0, field_1] = parse_fixed_len_constr_fields::<2>(fields)?;
347        Ok(Self {
348            bound: IsPlutusData::from_plutus_data(field_0)?,
349            closed: IsPlutusData::from_plutus_data(field_1)?,
350        })
351    }
352}
353
354//////////////
355// Extended //
356//////////////
357
358/// A set extended with a positive and negative infinity.
359#[derive(Clone, Debug, PartialEq, Eq, Hash)]
360#[cfg_attr(feature = "lbf", derive(Json))]
361#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
362pub enum Extended<T>
363where
364    T: FeatureTraits,
365{
366    NegInf,
367    Finite(T),
368    PosInf,
369}
370
371impl<T> Ord for Extended<T>
372where
373    T: FeatureTraits + Ord,
374{
375    fn cmp(&self, other: &Self) -> cmp::Ordering {
376        match (self, other) {
377            (Extended::PosInf, Extended::PosInf) => cmp::Ordering::Equal,
378            (Extended::PosInf, _) => cmp::Ordering::Greater,
379
380            (Extended::NegInf, Extended::NegInf) => cmp::Ordering::Equal,
381            (Extended::NegInf, _) => cmp::Ordering::Less,
382
383            (Extended::Finite(_), Extended::NegInf) => cmp::Ordering::Greater,
384            (Extended::Finite(self_val), Extended::Finite(other_val)) => self_val.cmp(other_val),
385            (Extended::Finite(_), Extended::PosInf) => cmp::Ordering::Less,
386        }
387    }
388}
389
390impl<T> PartialOrd for Extended<T>
391where
392    T: FeatureTraits + PartialOrd,
393{
394    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
395        match self {
396            Extended::PosInf => match other {
397                Extended::PosInf => Some(cmp::Ordering::Equal),
398                _ => Some(cmp::Ordering::Greater),
399            },
400            Extended::NegInf => match other {
401                Extended::NegInf => Some(cmp::Ordering::Equal),
402                _ => Some(cmp::Ordering::Less),
403            },
404            Extended::Finite(self_val) => match other {
405                Extended::NegInf => Some(cmp::Ordering::Greater),
406                Extended::Finite(other_val) => self_val.partial_cmp(other_val),
407                Extended::PosInf => Some(cmp::Ordering::Less),
408            },
409        }
410    }
411}
412
413impl<T> IsPlutusData for Extended<T>
414where
415    T: FeatureTraits + IsPlutusData,
416{
417    fn to_plutus_data(&self) -> PlutusData {
418        match self {
419            Extended::NegInf => PlutusData::Constr(BigInt::from(0), Vec::with_capacity(0)),
420            Extended::Finite(value) => {
421                PlutusData::Constr(BigInt::from(1), vec![value.to_plutus_data()])
422            }
423            Extended::PosInf => PlutusData::Constr(BigInt::from(2), Vec::with_capacity(0)),
424        }
425    }
426
427    fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
428        let (tag, fields) = parse_constr(data)?;
429        match tag {
430            0 => {
431                let [] = parse_fixed_len_constr_fields::<0>(fields)?;
432                Ok(Extended::NegInf)
433            }
434            1 => {
435                let [field] = parse_fixed_len_constr_fields::<1>(fields)?;
436                Ok(Extended::Finite(IsPlutusData::from_plutus_data(field)?))
437            }
438            2 => {
439                let [] = parse_fixed_len_constr_fields::<0>(fields)?;
440                Ok(Extended::PosInf)
441            }
442            _ => Err(PlutusDataError::UnexpectedPlutusInvariant {
443                wanted: "Constr with tag 0, 1 or 2".to_owned(),
444                got: tag.to_string(),
445            }),
446        }
447    }
448}
449
450#[cfg(test)]
451mod test {
452    use super::*;
453    use crate::{generators::correct::v1::arb_interval_posix_time, v1::transaction::POSIXTime};
454    use proptest::prelude::*;
455
456    proptest! {
457        #[test]
458        fn interval_to_from_plutus_interval(interval in arb_interval_posix_time()) {
459            let plutus_interval: PlutusInterval<POSIXTime> = interval.clone().into();
460            let interval2: Interval<POSIXTime> = plutus_interval.clone().try_into().unwrap();
461
462            assert_eq!(interval, interval2);
463        }
464    }
465}