Skip to main content

tx3_tir/model/
core.rs

1use std::cmp::Ordering;
2use std::collections::HashSet;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
7pub struct UtxoRef {
8    pub txid: Vec<u8>,
9    pub index: u32,
10}
11
12impl UtxoRef {
13    pub fn new(txid: &[u8], index: u32) -> Self {
14        Self {
15            txid: txid.to_vec(),
16            index,
17        }
18    }
19}
20
21pub trait CanonicalOrd {
22    fn cmp_canonical(&self, other: &Self) -> Ordering;
23}
24
25impl CanonicalOrd for UtxoRef {
26    fn cmp_canonical(&self, other: &Self) -> Ordering {
27        self.txid
28            .cmp(&other.txid)
29            .then_with(|| self.index.cmp(&other.index))
30    }
31}
32
33impl std::fmt::Display for UtxoRef {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(f, "{}#{}", hex::encode(&self.txid), self.index)
36    }
37}
38
39#[derive(Serialize, Deserialize, Debug, Clone)]
40pub struct Utxo {
41    pub r#ref: UtxoRef,
42    pub address: Vec<u8>,
43    pub assets: super::assets::CanonicalAssets,
44
45    // TODO: we should remove the dependency on v1beta0 here and treat the UTxO fields as data instead of expressions
46    pub datum: Option<super::v1beta0::Expression>,
47    pub script: Option<super::v1beta0::Expression>,
48}
49
50impl std::hash::Hash for Utxo {
51    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
52        self.r#ref.hash(state);
53    }
54}
55
56impl PartialEq for Utxo {
57    fn eq(&self, other: &Self) -> bool {
58        self.r#ref == other.r#ref
59    }
60}
61
62impl Eq for Utxo {}
63
64impl CanonicalOrd for Utxo {
65    fn cmp_canonical(&self, other: &Self) -> Ordering {
66        self.r#ref.cmp_canonical(&other.r#ref)
67    }
68}
69
70#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
71#[serde(transparent)]
72pub struct UtxoSet(HashSet<Utxo>);
73
74impl UtxoSet {
75    pub fn new() -> Self {
76        Self(HashSet::new())
77    }
78
79    pub fn iter(&self) -> std::collections::hash_set::Iter<'_, Utxo> {
80        self.0.iter()
81    }
82
83    pub fn iter_sorted_by_ref(&self) -> Vec<&Utxo> {
84        let mut out: Vec<_> = self.0.iter().collect();
85        out.sort_by(|a, b| a.cmp_canonical(b));
86        out
87    }
88
89    pub fn into_sorted_by_ref(self) -> Vec<Utxo> {
90        let mut out: Vec<_> = self.0.into_iter().collect();
91        out.sort_by(|a, b| a.cmp_canonical(b));
92        out
93    }
94
95    pub fn refs(&self) -> HashSet<UtxoRef> {
96        self.iter().map(|utxo| utxo.r#ref.clone()).collect()
97    }
98
99    pub fn into_refs(self) -> HashSet<UtxoRef> {
100        self.into_iter().map(|utxo| utxo.r#ref).collect()
101    }
102
103    pub fn refs_sorted(&self) -> Vec<UtxoRef> {
104        self.iter_sorted_by_ref()
105            .into_iter()
106            .map(|utxo| utxo.r#ref.clone())
107            .collect()
108    }
109
110    pub fn into_refs_sorted(self) -> Vec<UtxoRef> {
111        self.into_sorted_by_ref()
112            .into_iter()
113            .map(|utxo| utxo.r#ref)
114            .collect()
115    }
116
117    pub fn total_assets(&self) -> super::assets::CanonicalAssets {
118        self.iter()
119            .fold(super::assets::CanonicalAssets::empty(), |acc, x| {
120                acc + x.assets.clone()
121            })
122    }
123
124    pub fn first_by_ref(&self) -> Option<&Utxo> {
125        self.iter_sorted_by_ref().into_iter().next()
126    }
127}
128
129impl std::ops::Deref for UtxoSet {
130    type Target = HashSet<Utxo>;
131
132    fn deref(&self) -> &Self::Target {
133        &self.0
134    }
135}
136
137impl std::ops::DerefMut for UtxoSet {
138    fn deref_mut(&mut self) -> &mut Self::Target {
139        &mut self.0
140    }
141}
142
143impl From<HashSet<Utxo>> for UtxoSet {
144    fn from(value: HashSet<Utxo>) -> Self {
145        Self(value)
146    }
147}
148
149impl<const N: usize> From<[Utxo; N]> for UtxoSet {
150    fn from(value: [Utxo; N]) -> Self {
151        value.into_iter().collect()
152    }
153}
154
155impl From<UtxoSet> for HashSet<Utxo> {
156    fn from(value: UtxoSet) -> Self {
157        value.0
158    }
159}
160
161impl FromIterator<Utxo> for UtxoSet {
162    fn from_iter<T: IntoIterator<Item = Utxo>>(iter: T) -> Self {
163        Self(HashSet::from_iter(iter))
164    }
165}
166
167impl IntoIterator for UtxoSet {
168    type Item = Utxo;
169    type IntoIter = std::collections::hash_set::IntoIter<Utxo>;
170
171    fn into_iter(self) -> Self::IntoIter {
172        self.0.into_iter()
173    }
174}
175
176impl<'a> IntoIterator for &'a UtxoSet {
177    type Item = &'a Utxo;
178    type IntoIter = std::collections::hash_set::Iter<'a, Utxo>;
179
180    fn into_iter(self) -> Self::IntoIter {
181        self.iter()
182    }
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
186pub enum Type {
187    Undefined,
188    Unit,
189    Int,
190    Bool,
191    Bytes,
192    Address,
193    Utxo,
194    UtxoRef,
195    AnyAsset,
196    List,
197    Map,
198    Custom(String),
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::model::assets::CanonicalAssets;
205    use crate::model::v1beta0::Expression;
206
207    fn utxo(txid: u8, index: u32, amount: i128) -> Utxo {
208        Utxo {
209            r#ref: UtxoRef::new(&[txid], index),
210            address: vec![],
211            assets: CanonicalAssets::from_naked_amount(amount),
212            datum: Some(Expression::None),
213            script: None,
214        }
215    }
216
217    #[test]
218    fn utxo_set_sorted_helpers_are_canonical() {
219        let set: UtxoSet = [utxo(3, 0, 3), utxo(1, 1, 1), utxo(1, 0, 2)].into();
220
221        let refs = set.refs_sorted();
222        assert_eq!(refs[0], UtxoRef::new(&[1], 0));
223        assert_eq!(refs[1], UtxoRef::new(&[1], 1));
224        assert_eq!(refs[2], UtxoRef::new(&[3], 0));
225
226        assert_eq!(set.first_by_ref().unwrap().r#ref, UtxoRef::new(&[1], 0));
227    }
228
229    #[test]
230    fn utxo_set_total_assets_sums_values() {
231        let set: UtxoSet = [utxo(1, 0, 2), utxo(2, 0, 3)].into();
232        assert_eq!(set.total_assets().naked_amount(), Some(5));
233    }
234}