use std::{borrow::Cow, fmt::Display, ops::Deref, str::FromStr}; use pallas_codec::utils::CborWrap; use pallas_crypto::hash::Hash; use pallas_primitives::{alonzo, byron}; use crate::{MultiEraInput, OutputRef}; impl OutputRef { pub fn new(hash: Hash<32>, index: u64) -> Self { Self(hash, index) } pub fn hash(&self) -> &Hash<32> { &self.0 } pub fn index(&self) -> u64 { self.1 } } impl Display for OutputRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}#{}", self.hash(), self.index()) } } impl FromStr for OutputRef { type Err = crate::Error; fn from_str(s: &str) -> Result { let parts: Vec<_> = s.trim().split('#').collect(); let (hash, idx) = match &parts[..] { &[a, b] => ( Hash::<32>::from_str(a).map_err(|_| crate::Error::invalid_utxo_ref(s))?, u64::from_str(b).map_err(|_| crate::Error::invalid_utxo_ref(s))?, ), _ => return Err(crate::Error::invalid_utxo_ref(s)), }; Ok(Self::new(hash, idx)) } } impl<'b> MultiEraInput<'b> { pub fn from_byron(input: &'b byron::TxIn) -> Self { Self::Byron(Box::new(Cow::Borrowed(input))) } pub fn from_alonzo_compatible(input: &'b alonzo::TransactionInput) -> Self { Self::AlonzoCompatible(Box::new(Cow::Borrowed(input))) } pub fn output_ref(&self) -> OutputRef { match self { MultiEraInput::Byron(x) => match x.deref().deref() { byron::TxIn::Variant0(CborWrap((tx, idx))) => OutputRef(*tx, *idx as u64), byron::TxIn::Other(_, _) => unreachable!(), }, MultiEraInput::AlonzoCompatible(x) => OutputRef(x.transaction_id, x.index), } } pub fn hash(&self) -> &Hash<32> { match self { MultiEraInput::Byron(x) => match x.deref().deref() { byron::TxIn::Variant0(CborWrap((x, _))) => x, byron::TxIn::Other(_, _) => unreachable!(), }, MultiEraInput::AlonzoCompatible(x) => &x.transaction_id, } } pub fn index(&self) -> u64 { match self { MultiEraInput::Byron(x) => match x.deref().deref() { byron::TxIn::Variant0(CborWrap((_, x))) => *x as u64, byron::TxIn::Other(_, _) => unreachable!(), }, MultiEraInput::AlonzoCompatible(x) => x.index, } } pub fn as_alonzo(&self) -> Option<&alonzo::TransactionInput> { match self { MultiEraInput::Byron(_) => None, MultiEraInput::AlonzoCompatible(x) => Some(x), } } pub fn as_byron(&self) -> Option<&byron::TxIn> { match self { MultiEraInput::Byron(x) => Some(x), MultiEraInput::AlonzoCompatible(_) => None, } } } #[cfg(test)] mod tests { use std::str::FromStr; use crate::*; #[test] fn test_expected_values() { let blocks = vec![ include_str!("../../test_data/byron2.block"), include_str!("../../test_data/alonzo1.block"), ]; let mut expected = vec![ "da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d5#14", "e059de2179400cd7e81ddb6683c0136c9d68119ff3a27a472ad2d98e2f1fbc9c#3", "adeb5745e6dba2c05a98f0ad9162b947f1484e998b8b3335f98213e0c67f426e#0", "f0fb258a6e741a02ae91b8dc7fe340b9e5b601a6048bf2a0c205f9cc6f51768d#1", "c2e4e1f1d8217724b76d979166b16cb0cf5cd6506f70f48c618a085b10460c44#2", "aaca2f41f4a17fe464481c69f1220a7bfd93b1a6854f52006094271204e7df7c#0", "89185f2daf9ea3bdfdb5d1fef7eced7e890cb89b8821275c0bf0973be08c4ee9#1", "bf1f12a83095ac6738ecce5e3e540ad2cff160c46af9137eb6dc0b971f0ac5de#0", "df4ebe9ac3ad31a55a06f3e51ca0dbaa947aaf25857ab3a12fe9315cabec11d3#0", "087138a5596168650835c8c00f488e167e869bd991ef0683d2dbf3696b0e6650#1", "cc9f28625de0b5b9bbe8f61c9332bfda2c987162f85d2e42e437666c27826573#0", "d0965859ce9b3025ccbe64f24e3cb30f7400252eb3e235c3604986c2fdd755db#1", ]; for block_str in blocks { let cbor = hex::decode(block_str).expect("invalid hex"); let block = MultiEraBlock::decode(&cbor).expect("invalid cbor"); for tx in block.txs() { for input in tx.inputs() { let ref_ = input.output_ref(); let right = expected.remove(0); assert_eq!(ref_.to_string(), right); } } } } #[test] fn test_duplicate_consumed_inputs() { let tx_bytecode_hex = include_str!("../../test_data/duplicateinput.tx"); let bytecode = hex::decode(tx_bytecode_hex).unwrap(); let tx = MultiEraTx::decode_for_era(Era::Alonzo, &bytecode).unwrap(); let expected_inputs = vec![ "4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1", "4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1", ]; let inputs: Vec = tx .inputs() .into_iter() .map(|x| x.output_ref().to_string()) .collect(); let expected_consumed = vec!["4d9cb6bf1c2e349f1bcd454a632d2b721d5badcf687220430c316588f39506ab#1"]; let consumed: Vec = tx .consumes() .into_iter() .map(|x| x.output_ref().to_string()) .collect(); assert_eq!(inputs, expected_inputs); assert_eq!(consumed, expected_consumed); } #[test] fn test_utxo_ref_parsing() { let valid_vectors = [ "da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d5#14", " da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d5#14 ", ]; for vector in valid_vectors.iter() { let sample = OutputRef::from_str(vector).unwrap(); assert_eq!( sample.hash().to_string(), "da832fb5ef57df5b91817e9a7448d26e92552afb34f8ee5adb491b24bbe990d5" ); assert_eq!(sample.index(), 14); } } }