1use std::cmp::Ordering;
2
3use crate::{ids, key, platformvm, txs};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Serialize, Deserialize, Eq, Clone)]
10pub struct Output {
11 #[serde(rename = "assetID")]
12 pub asset_id: ids::Id,
13
14 #[serde(rename = "fxID", skip_serializing_if = "Option::is_none")]
16 pub fx_id: Option<ids::Id>,
17
18 #[serde(rename = "output")]
28 pub transfer_output: Option<key::secp256k1::txs::transfer::Output>,
29 pub stakeable_lock_out: Option<platformvm::txs::StakeableLockOut>,
30}
31
32impl Default for Output {
33 fn default() -> Self {
34 Self {
35 asset_id: ids::Id::empty(),
36 fx_id: None,
37 transfer_output: None,
38 stakeable_lock_out: None,
39 }
40 }
41}
42
43impl Ord for Output {
45 fn cmp(&self, other: &Output) -> Ordering {
46 let asset_id_ord = self.asset_id.cmp(&(other.asset_id));
47 if asset_id_ord != Ordering::Equal {
48 return asset_id_ord;
50 }
51 if self.transfer_output.is_none()
52 && self.stakeable_lock_out.is_none()
53 && other.transfer_output.is_none()
54 && other.stakeable_lock_out.is_none()
55 {
56 return Ordering::Equal;
59 }
60
61 let type_id_self = {
64 if self.transfer_output.is_some() {
65 key::secp256k1::txs::transfer::Output::type_id()
66 } else {
67 platformvm::txs::StakeableLockOut::type_id()
68 }
69 };
70 let type_id_other = {
71 if other.transfer_output.is_some() {
72 key::secp256k1::txs::transfer::Output::type_id()
73 } else {
74 platformvm::txs::StakeableLockOut::type_id()
75 }
76 };
77 let type_id_ord = type_id_self.cmp(&type_id_other);
78 if type_id_ord != Ordering::Equal {
79 return type_id_ord;
81 }
82
83 match type_id_self {
86 7 => {
87 let transfer_output_self = self.transfer_output.clone().unwrap();
90 let transfer_output_other = other.transfer_output.clone().unwrap();
91 transfer_output_self.cmp(&transfer_output_other)
92 }
93 22 => {
94 let stakeable_lock_out_self = self.stakeable_lock_out.clone().unwrap();
97 let stakeable_lock_out_other = other.stakeable_lock_out.clone().unwrap();
98 stakeable_lock_out_self.cmp(&stakeable_lock_out_other)
99 }
100 _ => {
101 panic!("unexpected type ID {} for TransferableOutput", type_id_self);
103 }
104 }
105 }
106}
107
108impl PartialOrd for Output {
109 fn partial_cmp(&self, other: &Output) -> Option<Ordering> {
110 Some(self.cmp(other))
111 }
112}
113
114impl PartialEq for Output {
115 fn eq(&self, other: &Output) -> bool {
116 self.cmp(other) == Ordering::Equal
117 }
118}
119
120#[test]
124fn test_sort_transferable_outputs() {
125 use crate::ids::short;
126
127 let mut outputs: Vec<Output> = Vec::new();
128 for i in (0..10).rev() {
129 outputs.push(Output {
130 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
131 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
132 locktime: i as u64,
133 transfer_output: key::secp256k1::txs::transfer::Output {
134 amount: (i + 1) as u64,
135 output_owners: key::secp256k1::txs::OutputOwners {
136 locktime: i as u64,
137 threshold: i as u32,
138 addresses: vec![
139 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
140 short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
141 ],
142 },
143 },
144 }),
145 ..Output::default()
146 });
147 outputs.push(Output {
148 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
149 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
150 locktime: i as u64,
151 transfer_output: key::secp256k1::txs::transfer::Output {
152 amount: (i + 1) as u64,
153 output_owners: key::secp256k1::txs::OutputOwners {
154 locktime: i as u64,
155 threshold: i as u32,
156 addresses: vec![
157 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
158 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
159 ],
160 },
161 },
162 }),
163 ..Output::default()
164 });
165 outputs.push(Output {
166 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
167 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
168 locktime: i as u64,
169 transfer_output: key::secp256k1::txs::transfer::Output {
170 amount: (i + 1) as u64,
171 output_owners: key::secp256k1::txs::OutputOwners {
172 locktime: i as u64,
173 threshold: i as u32,
174 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
175 },
176 },
177 }),
178 ..Output::default()
179 });
180 outputs.push(Output {
181 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
182 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
183 locktime: i as u64,
184 transfer_output: key::secp256k1::txs::transfer::Output {
185 amount: i as u64,
186 output_owners: key::secp256k1::txs::OutputOwners {
187 locktime: i as u64,
188 threshold: i as u32,
189 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
190 },
191 },
192 }),
193 ..Output::default()
194 });
195 outputs.push(Output {
196 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
197 transfer_output: Some(key::secp256k1::txs::transfer::Output {
198 amount: i as u64,
199 output_owners: key::secp256k1::txs::OutputOwners {
200 locktime: i as u64,
201 threshold: i as u32,
202 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
203 },
204 }),
205 ..Output::default()
206 });
207 outputs.push(Output {
208 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
209 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
210 locktime: i as u64,
211 transfer_output: key::secp256k1::txs::transfer::Output {
212 amount: (i + 1) as u64,
213 output_owners: key::secp256k1::txs::OutputOwners {
214 locktime: i as u64,
215 threshold: i as u32,
216 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
217 },
218 },
219 }),
220 ..Output::default()
221 });
222 outputs.push(Output {
223 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
224 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
225 locktime: i as u64,
226 transfer_output: key::secp256k1::txs::transfer::Output {
227 amount: i as u64,
228 output_owners: key::secp256k1::txs::OutputOwners {
229 locktime: i as u64,
230 threshold: i as u32,
231 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
232 },
233 },
234 }),
235 ..Output::default()
236 });
237 outputs.push(Output {
238 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
239 transfer_output: Some(key::secp256k1::txs::transfer::Output {
240 amount: i as u64,
241 output_owners: key::secp256k1::txs::OutputOwners {
242 locktime: i as u64,
243 threshold: i as u32,
244 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
245 },
246 }),
247 ..Output::default()
248 });
249 }
250 assert!(!cmp_manager::is_sorted_and_unique(&outputs));
251 outputs.sort();
252
253 let mut sorted_outputs: Vec<Output> = Vec::new();
254 for i in 0..10 {
255 sorted_outputs.push(Output {
256 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
257 transfer_output: Some(key::secp256k1::txs::transfer::Output {
258 amount: i as u64,
259 output_owners: key::secp256k1::txs::OutputOwners {
260 locktime: i as u64,
261 threshold: i as u32,
262 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
263 },
264 }),
265 ..Output::default()
266 });
267 sorted_outputs.push(Output {
268 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
269 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
270 locktime: i as u64,
271 transfer_output: key::secp256k1::txs::transfer::Output {
272 amount: i as u64,
273 output_owners: key::secp256k1::txs::OutputOwners {
274 locktime: i as u64,
275 threshold: i as u32,
276 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
277 },
278 },
279 }),
280 ..Output::default()
281 });
282 sorted_outputs.push(Output {
283 asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
284 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
285 locktime: i as u64,
286 transfer_output: key::secp256k1::txs::transfer::Output {
287 amount: (i + 1) as u64,
288 output_owners: key::secp256k1::txs::OutputOwners {
289 locktime: i as u64,
290 threshold: i as u32,
291 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
292 },
293 },
294 }),
295 ..Output::default()
296 });
297 sorted_outputs.push(Output {
298 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
299 transfer_output: Some(key::secp256k1::txs::transfer::Output {
300 amount: i as u64,
301 output_owners: key::secp256k1::txs::OutputOwners {
302 locktime: i as u64,
303 threshold: i as u32,
304 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
305 },
306 }),
307 ..Output::default()
308 });
309 sorted_outputs.push(Output {
310 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
311 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
312 locktime: i as u64,
313 transfer_output: key::secp256k1::txs::transfer::Output {
314 amount: i as u64,
315 output_owners: key::secp256k1::txs::OutputOwners {
316 locktime: i as u64,
317 threshold: i as u32,
318 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
319 },
320 },
321 }),
322 ..Output::default()
323 });
324 sorted_outputs.push(Output {
325 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
326 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
327 locktime: i as u64,
328 transfer_output: key::secp256k1::txs::transfer::Output {
329 amount: (i + 1) as u64,
330 output_owners: key::secp256k1::txs::OutputOwners {
331 locktime: i as u64,
332 threshold: i as u32,
333 addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
334 },
335 },
336 }),
337 ..Output::default()
338 });
339 sorted_outputs.push(Output {
340 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
341 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
342 locktime: i as u64,
343 transfer_output: key::secp256k1::txs::transfer::Output {
344 amount: (i + 1) as u64,
345 output_owners: key::secp256k1::txs::OutputOwners {
346 locktime: i as u64,
347 threshold: i as u32,
348 addresses: vec![
349 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
350 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
351 ],
352 },
353 },
354 }),
355 ..Output::default()
356 });
357 sorted_outputs.push(Output {
358 asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
359 stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
360 locktime: i as u64,
361 transfer_output: key::secp256k1::txs::transfer::Output {
362 amount: (i + 1) as u64,
363 output_owners: key::secp256k1::txs::OutputOwners {
364 locktime: i as u64,
365 threshold: i as u32,
366 addresses: vec![
367 short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
368 short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
369 ],
370 },
371 },
372 }),
373 ..Output::default()
374 });
375 }
376 assert!(cmp_manager::is_sorted_and_unique(&outputs));
377 assert_eq!(outputs, sorted_outputs);
378}
379
380#[derive(Debug, Serialize, Deserialize, Eq, Clone)]
384pub struct Input {
385 #[serde(flatten)]
386 pub utxo_id: txs::utxo::Id,
387
388 #[serde(rename = "assetID")]
389 pub asset_id: ids::Id,
390
391 #[serde(rename = "fxID")]
392 pub fx_id: ids::Id, #[serde(rename = "input")]
404 pub transfer_input: Option<key::secp256k1::txs::transfer::Input>,
405 pub stakeable_lock_in: Option<platformvm::txs::StakeableLockIn>,
406}
407
408impl Default for Input {
409 fn default() -> Self {
410 Self {
411 utxo_id: txs::utxo::Id::default(),
412 asset_id: ids::Id::empty(),
413 fx_id: ids::Id::empty(),
414 transfer_input: None,
415 stakeable_lock_in: None,
416 }
417 }
418}
419
420impl Ord for Input {
424 fn cmp(&self, other: &Input) -> Ordering {
425 self.utxo_id
426 .tx_id
427 .cmp(&(other.utxo_id.tx_id)) .then_with(
429 || self.utxo_id.output_index.cmp(&other.utxo_id.output_index), )
431 }
432}
433
434impl PartialOrd for Input {
435 fn partial_cmp(&self, other: &Input) -> Option<Ordering> {
436 Some(self.cmp(other))
437 }
438}
439
440impl PartialEq for Input {
441 fn eq(&self, other: &Input) -> bool {
442 self.cmp(other) == Ordering::Equal
443 }
444}
445
446#[test]
451fn test_sort_transferable_inputs() {
452 let mut inputs: Vec<Input> = Vec::new();
453 for i in (0..10).rev() {
454 inputs.push(Input {
455 utxo_id: txs::utxo::Id {
456 tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
457 output_index: (i + 1) as u32,
458 ..txs::utxo::Id::default()
459 },
460 ..Input::default()
461 });
462 inputs.push(Input {
463 utxo_id: txs::utxo::Id {
464 tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
465 output_index: i as u32,
466 ..txs::utxo::Id::default()
467 },
468 ..Input::default()
469 });
470 }
471 assert!(!cmp_manager::is_sorted_and_unique(&inputs));
472 inputs.sort();
473
474 let mut sorted_inputs: Vec<Input> = Vec::new();
475 for i in 0..10 {
476 sorted_inputs.push(Input {
477 utxo_id: txs::utxo::Id {
478 tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
479 output_index: i as u32,
480 ..txs::utxo::Id::default()
481 },
482 ..Input::default()
483 });
484 sorted_inputs.push(Input {
485 utxo_id: txs::utxo::Id {
486 tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
487 output_index: (i + 1) as u32,
488 ..txs::utxo::Id::default()
489 },
490 ..Input::default()
491 });
492 }
493 assert!(cmp_manager::is_sorted_and_unique(&sorted_inputs));
494 assert_eq!(inputs, sorted_inputs);
495}