use std::cmp::Ordering;
use crate::{ids, key, platformvm, txs};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Eq, Clone)]
pub struct Output {
#[serde(rename = "assetID")]
pub asset_id: ids::Id,
#[serde(rename = "fxID", skip_serializing_if = "Option::is_none")]
pub fx_id: Option<ids::Id>,
#[serde(rename = "output")]
pub transfer_output: Option<key::secp256k1::txs::transfer::Output>,
pub stakeable_lock_out: Option<platformvm::txs::StakeableLockOut>,
}
impl Default for Output {
fn default() -> Self {
Self {
asset_id: ids::Id::empty(),
fx_id: None,
transfer_output: None,
stakeable_lock_out: None,
}
}
}
impl Ord for Output {
fn cmp(&self, other: &Output) -> Ordering {
let asset_id_ord = self.asset_id.cmp(&(other.asset_id));
if asset_id_ord != Ordering::Equal {
return asset_id_ord;
}
if self.transfer_output.is_none()
&& self.stakeable_lock_out.is_none()
&& other.transfer_output.is_none()
&& other.stakeable_lock_out.is_none()
{
return Ordering::Equal;
}
let type_id_self = {
if self.transfer_output.is_some() {
key::secp256k1::txs::transfer::Output::type_id()
} else {
platformvm::txs::StakeableLockOut::type_id()
}
};
let type_id_other = {
if other.transfer_output.is_some() {
key::secp256k1::txs::transfer::Output::type_id()
} else {
platformvm::txs::StakeableLockOut::type_id()
}
};
let type_id_ord = type_id_self.cmp(&type_id_other);
if type_id_ord != Ordering::Equal {
return type_id_ord;
}
match type_id_self {
7 => {
let transfer_output_self = self.transfer_output.clone().unwrap();
let transfer_output_other = other.transfer_output.clone().unwrap();
transfer_output_self.cmp(&transfer_output_other)
}
22 => {
let stakeable_lock_out_self = self.stakeable_lock_out.clone().unwrap();
let stakeable_lock_out_other = other.stakeable_lock_out.clone().unwrap();
stakeable_lock_out_self.cmp(&stakeable_lock_out_other)
}
_ => {
panic!("unexpected type ID {} for TransferableOutput", type_id_self);
}
}
}
}
impl PartialOrd for Output {
fn partial_cmp(&self, other: &Output) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Output {
fn eq(&self, other: &Output) -> bool {
self.cmp(other) == Ordering::Equal
}
}
#[test]
fn test_sort_transferable_outputs() {
use crate::ids::short;
let mut outputs: Vec<Output> = Vec::new();
for i in (0..10).rev() {
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
transfer_output: Some(key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
transfer_output: Some(key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
}),
..Output::default()
});
}
assert!(!cmp_manager::is_sorted_and_unique(&outputs));
outputs.sort();
let mut sorted_outputs: Vec<Output> = Vec::new();
for i in 0..10 {
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
transfer_output: Some(key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
transfer_output: Some(key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: i as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
},
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
],
},
},
}),
..Output::default()
});
sorted_outputs.push(Output {
asset_id: ids::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5, 6, 7, 8, 9]),
stakeable_lock_out: Some(platformvm::txs::StakeableLockOut {
locktime: i as u64,
transfer_output: key::secp256k1::txs::transfer::Output {
amount: (i + 1) as u64,
output_owners: key::secp256k1::txs::OutputOwners {
locktime: i as u64,
threshold: i as u32,
addresses: vec![
short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
],
},
},
}),
..Output::default()
});
}
assert!(cmp_manager::is_sorted_and_unique(&outputs));
assert_eq!(outputs, sorted_outputs);
}
#[derive(Debug, Serialize, Deserialize, Eq, Clone)]
pub struct Input {
#[serde(flatten)]
pub utxo_id: txs::utxo::Id,
#[serde(rename = "assetID")]
pub asset_id: ids::Id,
#[serde(rename = "fxID")]
pub fx_id: ids::Id,
#[serde(rename = "input")]
pub transfer_input: Option<key::secp256k1::txs::transfer::Input>,
pub stakeable_lock_in: Option<platformvm::txs::StakeableLockIn>,
}
impl Default for Input {
fn default() -> Self {
Self {
utxo_id: txs::utxo::Id::default(),
asset_id: ids::Id::empty(),
fx_id: ids::Id::empty(),
transfer_input: None,
stakeable_lock_in: None,
}
}
}
impl Ord for Input {
fn cmp(&self, other: &Input) -> Ordering {
self.utxo_id
.tx_id
.cmp(&(other.utxo_id.tx_id)) .then_with(
|| self.utxo_id.output_index.cmp(&other.utxo_id.output_index), )
}
}
impl PartialOrd for Input {
fn partial_cmp(&self, other: &Input) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Input {
fn eq(&self, other: &Input) -> bool {
self.cmp(other) == Ordering::Equal
}
}
#[test]
fn test_sort_transferable_inputs() {
let mut inputs: Vec<Input> = Vec::new();
for i in (0..10).rev() {
inputs.push(Input {
utxo_id: txs::utxo::Id {
tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
output_index: (i + 1) as u32,
..txs::utxo::Id::default()
},
..Input::default()
});
inputs.push(Input {
utxo_id: txs::utxo::Id {
tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
output_index: i as u32,
..txs::utxo::Id::default()
},
..Input::default()
});
}
assert!(!cmp_manager::is_sorted_and_unique(&inputs));
inputs.sort();
let mut sorted_inputs: Vec<Input> = Vec::new();
for i in 0..10 {
sorted_inputs.push(Input {
utxo_id: txs::utxo::Id {
tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
output_index: i as u32,
..txs::utxo::Id::default()
},
..Input::default()
});
sorted_inputs.push(Input {
utxo_id: txs::utxo::Id {
tx_id: ids::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
output_index: (i + 1) as u32,
..txs::utxo::Id::default()
},
..Input::default()
});
}
assert!(cmp_manager::is_sorted_and_unique(&sorted_inputs));
assert_eq!(inputs, sorted_inputs);
}