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 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}