From bdb3281532f289176257c9ff70b9862d15bc3505 Mon Sep 17 00:00:00 2001 From: ruko Date: Thu, 28 Aug 2025 16:59:17 -0700 Subject: [PATCH 1/9] add some tests for cbor decoding --- pallas-primitives/src/conway/model.rs | 119 ++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index 54c1a742a..fba8a5245 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -733,6 +733,125 @@ mod tests { type BlockWrapper<'b> = (u16, Block<'b>); + #[cfg(test)] + mod tests_value { + use super::super::Mint; + use super::super::Multiasset; + use super::super::NonZeroInt; + use super::super::Value; + use pallas_codec::minicbor; + use std::collections::BTreeMap; + + // a value can have zero coins and omit the multiasset + #[test] + fn decode_zero_value() { + let ma: Value = minicbor::decode(&hex::decode("00").unwrap()).unwrap(); + assert_eq!(ma, Value::Coin(0)); + } + + // a value can have zero coins and an empty multiasset map + // Note: this will roundtrip back to "00" + #[test] + fn permit_definite_asset() { + let ma: Value = minicbor::decode(&hex::decode("8200a0").unwrap()).unwrap(); + assert_eq!(ma, Value::Multiasset(0, BTreeMap::new())); + } + + // indefinite-encoded value is invalid + #[test] + fn reject_indefinite_value() { + let ma: Result = minicbor::decode(&hex::decode("9f00a0ff").unwrap()); + assert_eq!( + ma.map_err(|e| e.to_string()), + Err("decode error: Unknown cbor data type for this macro-defined enum.".to_owned()) + ); + } + + // the asset sub-map of a policy map in a multiasset must not be null in Conway + #[test] + fn reject_null_tokens() { + let ma: Result = minicbor::decode(&hex::decode("8200a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); + assert_eq!( + ma.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + // the asset sub-map of a policy map in a multiasset must not have any zero values in + // Conway + #[test] + fn reject_zero_tokens() { + let ma: Result = minicbor::decode(&hex::decode("8200a1581c00000000000000000000000000000000000000000000000000000000a14000").unwrap()); + assert_eq!( + ma.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + #[test] + fn multiasset_reject_null_tokens() { + let ma: Result, _> = minicbor::decode(&hex::decode("a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); + assert_eq!( + ma.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + // the decoder for MaryValue in the haskell node rejects inputs that are "too big" as + // defined by `isMultiAssetSmallEnough` + #[test] + fn multiasset_not_too_big() { + todo!() + } + + #[test] + fn mint_reject_null_tokens() { + let ma: Result = minicbor::decode(&hex::decode("a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); + assert_eq!( + ma.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + } + + mod tests_transaction { + use super::super::TransactionBody; + use pallas_codec::minicbor; + + // a simple tx with just inputs, outputs, and fee. + // address is all 00 bytes, which seems like it should fail? + #[test] + fn decode_simple_tx() { + let tx_bytes = hex::decode("a300828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a04000000").unwrap(); + let tx: TransactionBody = minicbor::decode(&tx_bytes).unwrap(); + assert_eq!(tx.fee, 0); + } + + // the decoder for ConwayTxBodyRaw rejects transaction bodies missing inputs, outputs, or + // fee + #[test] + fn reject_empty_tx() { + let tx_bytes = hex::decode("a0").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + // the mint may not be present if it is empty + #[test] + fn reject_empty_present_mint() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a0400000009a0").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + } + #[cfg(test)] mod tests_voter { use super::super::Voter; From 4198d792239ab3e71512fd5d5fa4bd826a3604b9 Mon Sep 17 00:00:00 2001 From: ruko Date: Tue, 9 Sep 2025 06:24:41 -0700 Subject: [PATCH 2/9] add witness set decoder tests --- pallas-primitives/src/conway/model.rs | 98 +++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index fba8a5245..ad185d18e 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -814,6 +814,102 @@ mod tests { } } + mod tests_witness_set { + use super::super::{Bytes, VKeyWitness, WitnessSet}; + use pallas_codec::minicbor; + + #[test] + fn decode_empty_witness_set() { + let witness_set_bytes = hex::decode("a0").unwrap(); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + assert_eq!(ws.vkeywitness, None); + } + + #[test] + fn decode_witness_set_having_vkeywitness_legacy_may_be_empty() { + let witness_set_bytes = hex::decode("a10080").unwrap(); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + // TODO: It is really surprising that the guard when decoding non + // empty sets from cbor has been commented out + assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); + } + + #[test] + fn decode_witness_set_having_vkeywitness_legacy_may_be_indefinite() { + let witness_set_bytes = hex::decode("a1009fff").unwrap(); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); + } + + #[test] + fn decode_witness_set_having_vkeywitness_legacy_singleton() { + let witness_set_bytes = hex::decode("a10081824040").unwrap(); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + let expected = VKeyWitness { + vkey: Bytes::from(vec![]), + signature: Bytes::from(vec![]), + }; + assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected])); + } + + #[test] + fn decode_witness_set_having_vkeywitness_conwaystyle_singleton() { + let witness_set_bytes = hex::decode("a100d9010281824040").unwrap(); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + let expected = VKeyWitness { + vkey: Bytes::from(vec![]), + signature: Bytes::from(vec![]), + }; + assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected])); + } + + #[test] + fn decode_witness_set_having_vkeywitness_conwaystyle_must_be_nonempty() { + let witness_set_bytes = hex::decode("a100d9010280").unwrap(); + let ws: Result = minicbor::decode(&witness_set_bytes); + assert_eq!( + ws.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + #[test] + fn decode_witness_set_having_vkeywitness_reject_nonsense_tag() { + // VKey witness set with nonsense tag 259 + let witness_set_bytes = hex::decode("a100d9010381824040").unwrap(); + let ws: Result = minicbor::decode(&witness_set_bytes); + assert_eq!( + ws.map_err(|e| e.to_string()), + Err("decode error: Unrecognised tag: Tag(259)".to_owned()) + ); + } + + // Unclear what the behavior should be when there are duplicates. The haskell code + // allows duplicate entries in the CBOR but represents the vkey witnesses using a + // set data type, so that the resulting data structure will only have one element. + #[test] + fn decode_witness_set_having_vkeywitness_duplicate_entries() { + // VKey witness set with nonsense tag 259 + let witness_set_bytes = hex::decode("a100d9010382824040824040").unwrap(); + let ws: Result = minicbor::decode(&witness_set_bytes); + + let expected = VKeyWitness { + vkey: Bytes::from(vec![]), + signature: Bytes::from(vec![]), + }; + //assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected, expected])); + assert_eq!( + ws.map_err(|e| e.to_string()), + Err("bad".to_owned()) + ); + } + + } + mod tests_transaction { use super::super::TransactionBody; use pallas_codec::minicbor; @@ -840,6 +936,8 @@ mod tests { } // the mint may not be present if it is empty + // TODO: equivalent tests for certs, withdrawals, collateral inputs, required signer + // hashes, reference inputs, voting procedures, and proposal procedures #[test] fn reject_empty_present_mint() { let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a0400000009a0").unwrap(); From a7e55476d91f92412397b70ad5f0bf8aeecf6585 Mon Sep 17 00:00:00 2001 From: ruko Date: Tue, 9 Sep 2025 07:06:42 -0700 Subject: [PATCH 3/9] add aux data tests --- pallas-primitives/src/conway/model.rs | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index ad185d18e..daa33efbf 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -910,6 +910,35 @@ mod tests { } + mod tests_auxdata { + use super::super::PostAlonzoAuxiliaryData; + use pallas_codec::minicbor; + + #[test] + fn decode_auxdata_shelley_format_empty() { + let auxdata_bytes = hex::decode("a0").unwrap(); + let auxdata: PostAlonzoAuxiliaryData = + minicbor::decode(&auxdata_bytes).unwrap(); + assert_eq!(auxdata.metadata, None); + } + + #[test] + fn decode_auxdata_shelley_ma_format_empty() { + let auxdata_bytes = hex::decode("82a080").unwrap(); + let auxdata: PostAlonzoAuxiliaryData = + minicbor::decode(&auxdata_bytes).unwrap(); + assert_eq!(auxdata.metadata, None); + } + + #[test] + fn decode_auxdata_alonzo_format_empty() { + let auxdata_bytes = hex::decode("d90103a0").unwrap(); + let auxdata: PostAlonzoAuxiliaryData = + minicbor::decode(&auxdata_bytes).unwrap(); + assert_eq!(auxdata.metadata, None); + } + } + mod tests_transaction { use super::super::TransactionBody; use pallas_codec::minicbor; From 78adb478e2f7f0fec7d1f88c44bef88379d8c41f Mon Sep 17 00:00:00 2001 From: ruko Date: Tue, 9 Sep 2025 07:27:30 -0700 Subject: [PATCH 4/9] use correct AuxiliaryData type --- pallas-primitives/src/conway/model.rs | 46 +++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index daa33efbf..269fdb62c 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -680,6 +680,13 @@ impl<'b> From> for ScriptRef<'b> { #[deprecated(since = "1.0.0-alpha", note = "use `ScriptRef` instead")] pub type MintedScriptRef<'b> = ScriptRef<'b>; +// FIXME: re-exporting here means it does not use the above PostAlonzoAuxiliaryData; instead, it +// uses the one defined in the alonzo module, which only supports plutus V1 scripts +// +// Same problem exists in the babbage module +// +// should probably take a type parameter for the post-alonzo variant or just define a whole +// separate type here and in babbage pub use crate::alonzo::AuxiliaryData; /// A memory representation of an already minted block @@ -893,8 +900,7 @@ mod tests { // set data type, so that the resulting data structure will only have one element. #[test] fn decode_witness_set_having_vkeywitness_duplicate_entries() { - // VKey witness set with nonsense tag 259 - let witness_set_bytes = hex::decode("a100d9010382824040824040").unwrap(); + let witness_set_bytes = hex::decode("a100d9010282824040824040").unwrap(); let ws: Result = minicbor::decode(&witness_set_bytes); let expected = VKeyWitness { @@ -911,31 +917,53 @@ mod tests { } mod tests_auxdata { - use super::super::PostAlonzoAuxiliaryData; + use super::super::AuxiliaryData; use pallas_codec::minicbor; + use std::collections::BTreeMap; #[test] fn decode_auxdata_shelley_format_empty() { let auxdata_bytes = hex::decode("a0").unwrap(); - let auxdata: PostAlonzoAuxiliaryData = + let auxdata: AuxiliaryData = minicbor::decode(&auxdata_bytes).unwrap(); - assert_eq!(auxdata.metadata, None); + match auxdata { + AuxiliaryData::Shelley(s) => { + assert_eq!(s, BTreeMap::new()); + } + _ => { + panic!("Unexpected variant"); + } + } } #[test] fn decode_auxdata_shelley_ma_format_empty() { let auxdata_bytes = hex::decode("82a080").unwrap(); - let auxdata: PostAlonzoAuxiliaryData = + let auxdata: AuxiliaryData = minicbor::decode(&auxdata_bytes).unwrap(); - assert_eq!(auxdata.metadata, None); + match auxdata { + AuxiliaryData::ShelleyMa(s) => { + assert_eq!(s.transaction_metadata, BTreeMap::new()); + } + _ => { + panic!("Unexpected variant"); + } + } } #[test] fn decode_auxdata_alonzo_format_empty() { let auxdata_bytes = hex::decode("d90103a0").unwrap(); - let auxdata: PostAlonzoAuxiliaryData = + let auxdata: AuxiliaryData = minicbor::decode(&auxdata_bytes).unwrap(); - assert_eq!(auxdata.metadata, None); + match auxdata { + AuxiliaryData::PostAlonzo(a) => { + assert_eq!(a.metadata, None); + } + _ => { + panic!("Unexpected variant"); + } + } } } From c5ba3cd3586564ca0a4f307aec32abc42fa72d9d Mon Sep 17 00:00:00 2001 From: ruko Date: Wed, 10 Sep 2025 08:24:06 -0700 Subject: [PATCH 5/9] fix some tests --- pallas-primitives/src/conway/model.rs | 64 ++++++++++++++++++++------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index 269fdb62c..28644991e 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -733,6 +733,20 @@ pub struct Tx<'b> { #[deprecated(since = "1.0.0-alpha", note = "use `Tx` instead")] pub type MintedTx<'b> = Tx<'b>; +fn is_multiasset_small_enough(ma: Multiasset) -> bool { + let per_asset_size = 44; + let per_policy_size = 28; + + let policy_count = ma.len(); + let mut asset_count = 0; + for (policy, assets) in ma { + asset_count += assets.len(); + } + + let size = per_asset_size * asset_count + per_policy_size * policy_count; + size <= 65535 +} + #[cfg(test)] mod tests { use super::Block; @@ -759,19 +773,16 @@ mod tests { // a value can have zero coins and an empty multiasset map // Note: this will roundtrip back to "00" #[test] - fn permit_definite_asset() { + fn permit_definite_value() { let ma: Value = minicbor::decode(&hex::decode("8200a0").unwrap()).unwrap(); assert_eq!(ma, Value::Multiasset(0, BTreeMap::new())); } - // indefinite-encoded value is invalid + // Indefinite-encoded value is valid #[test] - fn reject_indefinite_value() { - let ma: Result = minicbor::decode(&hex::decode("9f00a0ff").unwrap()); - assert_eq!( - ma.map_err(|e| e.to_string()), - Err("decode error: Unknown cbor data type for this macro-defined enum.".to_owned()) - ); + fn permit_indefinite_value() { + let ma: Value = minicbor::decode(&hex::decode("9f00a0ff").unwrap()).unwrap(); + assert_eq!(ma, Value::Multiasset(0, BTreeMap::new())); } // the asset sub-map of a policy map in a multiasset must not be null in Conway @@ -808,7 +819,21 @@ mod tests { // defined by `isMultiAssetSmallEnough` #[test] fn multiasset_not_too_big() { - todo!() + // Creating CBOR representation of a value with 1500 policies + // 1500 * 44 is greater than 65535 so this should fail to decode + let mut s: String = "b905dc".to_owned(); + for i in 0..1500u16 { + // policy + s += "581c0000000000000000000000000000000000000000000000000000"; + s += &hex::encode(i.to_be_bytes()); + // minimal token map (conway requires nonempty asset maps) + s += "a14001"; + } + let ma: Result, _> = minicbor::decode(&hex::decode(s).unwrap()); + match ma { + Ok(_) => panic!("decode succeded but should fail"), + Err(e) => assert_eq!(e.to_string(), "bad") + } } #[test] @@ -837,8 +862,15 @@ mod tests { let witness_set_bytes = hex::decode("a10080").unwrap(); let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - // TODO: It is really surprising that the guard when decoding non - // empty sets from cbor has been commented out + // FIXME: The decoder behavior here is strictly correct w.r.t. the haskell code; we + // must accept a vkeywitness set that is present but empty (in the legacy witness set + // format). + // + // However, the types we are using in pallas here are confusing; vkeywitness is of type + // Option, and in fact, our "NonEmptySet" type allows constructing an + // empty value via CBOR decoding (there used to be a guard, but it was commented out). + // So we end up with a Some(vec![]). It would make more sense to just have a 'Set' + // type. assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); } @@ -898,6 +930,8 @@ mod tests { // Unclear what the behavior should be when there are duplicates. The haskell code // allows duplicate entries in the CBOR but represents the vkey witnesses using a // set data type, so that the resulting data structure will only have one element. + // However, our NonEmptySet type is secretly a vector and does not prevent duplicates. + // Do we ever hash witness sets? i.e. do we need to remember the original bytes? #[test] fn decode_witness_set_having_vkeywitness_duplicate_entries() { let witness_set_bytes = hex::decode("a100d9010282824040824040").unwrap(); @@ -971,8 +1005,8 @@ mod tests { use super::super::TransactionBody; use pallas_codec::minicbor; - // a simple tx with just inputs, outputs, and fee. - // address is all 00 bytes, which seems like it should fail? + // A simple tx with just inputs, outputs, and fee. Address is not well-formed, since the + // 00 header implies both a payment part and a staking part are present. #[test] fn decode_simple_tx() { let tx_bytes = hex::decode("a300828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a04000000").unwrap(); @@ -980,7 +1014,7 @@ mod tests { assert_eq!(tx.fee, 0); } - // the decoder for ConwayTxBodyRaw rejects transaction bodies missing inputs, outputs, or + // The decoder for ConwayTxBodyRaw rejects transaction bodies missing inputs, outputs, or // fee #[test] fn reject_empty_tx() { @@ -992,7 +1026,7 @@ mod tests { ); } - // the mint may not be present if it is empty + // The mint may not be present if it is empty // TODO: equivalent tests for certs, withdrawals, collateral inputs, required signer // hashes, reference inputs, voting procedures, and proposal procedures #[test] From deecbc13bcecab0365ee1c2457555d23cc192b2c Mon Sep 17 00:00:00 2001 From: ruko Date: Thu, 11 Sep 2025 06:01:01 -0700 Subject: [PATCH 6/9] transaction body decoder tests --- pallas-codec/src/utils.rs | 19 +- pallas-primitives/src/conway/model.rs | 472 +++++++++++++++++++++++--- 2 files changed, 440 insertions(+), 51 deletions(-) diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index cb2eec8c1..988e1966f 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -858,9 +858,9 @@ where let inner: Vec = d.decode_with(ctx)?; - // if inner.is_empty() { - // return Err(Error::message("decoding empty set as NonEmptySet")); - // } + if inner.is_empty() { + return Err(Error::message("decoding empty set as NonEmptySet")); + } Ok(Self(inner)) } @@ -998,7 +998,7 @@ impl From<&AnyUInt> for u64 { /// Introduced in Conway /// positive_coin = 1 .. 18446744073709551615 #[derive( - Encode, Decode, Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize, + Encode, Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize, )] #[serde(transparent)] #[cbor(transparent)] @@ -1016,6 +1016,17 @@ impl TryFrom for PositiveCoin { } } +impl<'b, C> minicbor::Decode<'b, C> for PositiveCoin { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let n = d.decode_with(ctx)?; + if n == 0 { + return Err(minicbor::decode::Error::message("PositiveCoin must not be 0")); + } + Ok(PositiveCoin(n)) + } +} + + impl From for u64 { fn from(value: PositiveCoin) -> Self { value.0 diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index 28644991e..aec2f1ef6 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -27,7 +27,33 @@ pub use crate::babbage::OperationalCert; pub use crate::babbage::Header; -pub type Multiasset = BTreeMap>; +use pallas_codec::minicbor::data::Type; + +use std::collections::HashSet; + +#[derive(Serialize, Deserialize, Encode, Debug, PartialEq, Eq, Clone)] +pub struct Multiasset(#[n(0)] BTreeMap>); + +impl<'b, C, A: minicbor::Decode<'b, C>> minicbor::Decode<'b, C> for Multiasset { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let policies: BTreeMap> = d.decode_with(ctx)?; + + // In Conway, all policies must be nonempty, and all amounts must be nonzero. + // We always parameterize Multiasset with NonZeroInt in practice, but maybe it should be + // monomorphic? + for (_policy, assets) in &policies { + if assets.len() == 0 { + return Err(minicbor::decode::Error::message("Policy must not be empty")); + } + } + + let result = Multiasset(policies); + if !is_multiasset_small_enough(&result) { + return Err(minicbor::decode::Error::message("Multiasset must not exceed size limit")); + } + Ok(result) + } +} pub type Mint = Multiasset; @@ -37,10 +63,50 @@ pub enum Value { Multiasset(Coin, Multiasset), } -codec_by_datatype! { - Value, - U8 | U16 | U32 | U64 => Coin, - (coin, multi => Multiasset) +//codec_by_datatype! { +// Value, +// U8 | U16 | U32 | U64 => Coin, +// (coin, multi => Multiasset) +//} + +impl minicbor::Encode for Value { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + match self { + Value::Coin(coin) => { + e.encode(coin)?; + }, + Value::Multiasset(coin, ma) => { + e.array(2)?; + e.encode(coin)?; + e.encode(ma)?; + } + } + Ok(()) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for Value { + fn decode(d: &mut minicbor::Decoder<'b>, _ctx: &mut C) -> Result { + match d.datatype()? { + Type::U8 | Type::U16 | Type::U32 | Type::U64 => { + let coin = d.decode()?; + Ok(Value::Coin(coin)) + } + Type::Array | Type::ArrayIndef => { + let _ = d.array()?; + let coin = d.decode()?; + let multiasset = d.decode()?; + Ok(Value::Multiasset(coin, multiasset)) + } + t => { + Err(minicbor::decode::Error::message(format!("Unexpected datatype {}", t))) + } + } + } } pub use crate::alonzo::TransactionOutput as LegacyTransactionOutput; @@ -311,7 +377,7 @@ pub struct DRepVotingThresholds { pub treasury_withdrawal: UnitInterval, } -#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Encode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct TransactionBody<'a> { #[n(0)] @@ -376,6 +442,183 @@ pub struct TransactionBody<'a> { pub donation: Option, } +// This is ugly but I'm not sure how to do it with minicbor-derive +// the cbor map implementation is here: +// https://github.com/twittner/minicbor/blob/83a4a0f868ac9ffc924a282f8f917aa2ad7c698a/minicbor-derive/src/decode.rs#L405-L424 +// We need to do validation inside the decoder or change the types of the validated fields to +// new types that do their own validation +impl<'b, C> minicbor::Decode<'b, C> for TransactionBody<'b> { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let mut must_inputs = None; + let mut must_outputs = None; + let mut must_fee = None; + let mut ttl = None; + let mut certificates = None; + let mut withdrawals = None; + let mut auxiliary_data_hash = None; + let mut validity_interval_start = None; + let mut mint: Option> = None; + let mut script_data_hash = None; + let mut collateral = None; + let mut required_signers = None; + let mut network_id = None; + let mut collateral_return = None; + let mut total_collateral = None; + let mut reference_inputs = None; + let mut voting_procedures = None; + let mut proposal_procedures = None; + let mut treasury_value = None; + let mut donation = None; + + let map_init = d.map()?; + let mut items_seen = 0; + + let mut seen_key = HashSet::new(); + + loop { + let n = d.i64(); + let Ok(index) = n else { break }; + if seen_key.contains(&index) { + return Err(minicbor::decode::Error::message("transaction body must not contain duplicate keys")); + } + match index { + 0 => { + must_inputs = d.decode_with(ctx)?; + }, + 1 => { + must_outputs = d.decode_with(ctx)?; + }, + 2 => { + must_fee = d.decode_with(ctx)?; + }, + 3 => { + ttl = d.decode_with(ctx)?; + }, + 4 => { + certificates = d.decode_with(ctx)?; + }, + 5 => { + let real_withdrawals: BTreeMap = d.decode_with(ctx)?; + if real_withdrawals.len() == 0 { + return Err(minicbor::decode::Error::message("withdrawals must be non-empty if present")); + } + withdrawals = Some(real_withdrawals); + }, + 7 => { + auxiliary_data_hash = d.decode_with(ctx)?; + }, + 8 => { + validity_interval_start = d.decode_with(ctx)?; + }, + 9 => { + let real_mint: Multiasset = d.decode_with(ctx)?; + if real_mint.0.len() == 0 { + return Err(minicbor::decode::Error::message("mint must be non-empty if present")); + } + mint = Some(real_mint); + }, + 11 => { + script_data_hash = d.decode_with(ctx)?; + }, + 13 => { + collateral = d.decode_with(ctx)?; + }, + 14 => { + required_signers = d.decode_with(ctx)?; + }, + 15 => { + network_id = d.decode_with(ctx)?; + }, + 16 => { + collateral_return = d.decode_with(ctx)?; + }, + 17 => { + total_collateral = d.decode_with(ctx)?; + }, + 18 => { + reference_inputs = d.decode_with(ctx)?; + }, + 19 => { + let real_voting_procedures: VotingProcedures = d.decode_with(ctx)?; + if real_voting_procedures.len() == 0 { + return Err(minicbor::decode::Error::message("voting procedures must be non-empty if present")); + } + voting_procedures = Some(real_voting_procedures); + }, + 20 => { + let real_proposal_procedures: NonEmptySet = d.decode_with(ctx)?; + if real_proposal_procedures.len() == 0 { + return Err(minicbor::decode::Error::message("proposal procedures must be non-empty if present")); + } + proposal_procedures = Some(real_proposal_procedures); + }, + 21 => { + treasury_value = d.decode_with(ctx)?; + }, + 22 => { + donation = d.decode_with(ctx)?; + }, + _ => { + return Err(minicbor::decode::Error::message("unexpected index")); + } + } + seen_key.insert(index); + items_seen += 1; + if let Some(map_count) = map_init { + if items_seen == map_count { + break; + } + } + } + + if let Some(map_count) = map_init { + if map_count != items_seen { + return Err(minicbor::decode::Error::message("map is not valid cbor: declared count did not match actual count")); + } + } else { + let ty = d.datatype()?; + if ty == minicbor::data::Type::Break { + d.skip()?; + } else { + return Err(minicbor::decode::Error::message("unexpected garbage at end of map")); + } + } + + let Some(inputs) = must_inputs else { + return Err(minicbor::decode::Error::message("field inputs is required")); + }; + let Some(outputs) = must_outputs else { + return Err(minicbor::decode::Error::message("field outputs is required")); + }; + let Some(fee) = must_fee else { + return Err(minicbor::decode::Error::message("field fee is required")); + }; + + Ok(Self { + inputs, + outputs, + fee, + ttl, + certificates, + withdrawals, + auxiliary_data_hash, + validity_interval_start, + mint, + script_data_hash, + collateral, + required_signers, + network_id, + collateral_return, + total_collateral, + reference_inputs, + voting_procedures, + proposal_procedures, + treasury_value, + donation, + }) + } +} + #[deprecated(since = "1.0.0-alpha", note = "use `TransactionBody` instead")] pub type MintedTransactionBody<'a> = TransactionBody<'a>; @@ -616,6 +859,19 @@ pub struct WitnessSet<'b> { pub plutus_v3_script: Option>>, } +//impl<'b, C> minicbor::Decode<'b, C> for WitnessSet<'b> { +// fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { +// let vkeywitness = d.decode_with(ctx)?; +// let native_script = d.decode_with(ctx)?; +// let bootstrap_witness = d.decode_with(ctx)?; +// let plutus_v1_script = d.decode_with(ctx)?; +// let plutus_data = d.decode_with(ctx)?; +// let redeemer = d.decode_with(ctx)?; +// let plutus_v2_script = d.decode_with(ctx)?; +// let plutus_v3_script = d.decode_with(ctx)?; +// } + + #[deprecated(since = "1.0.0-alpha", note = "use `WitnessSet` instead")] pub type MintedWitnessSet<'b> = WitnessSet<'b>; @@ -733,13 +989,13 @@ pub struct Tx<'b> { #[deprecated(since = "1.0.0-alpha", note = "use `Tx` instead")] pub type MintedTx<'b> = Tx<'b>; -fn is_multiasset_small_enough(ma: Multiasset) -> bool { +fn is_multiasset_small_enough(ma: &Multiasset) -> bool { let per_asset_size = 44; let per_policy_size = 28; - let policy_count = ma.len(); + let policy_count = ma.0.len(); let mut asset_count = 0; - for (policy, assets) in ma { + for (_policy, assets) in &ma.0 { asset_count += assets.len(); } @@ -775,14 +1031,14 @@ mod tests { #[test] fn permit_definite_value() { let ma: Value = minicbor::decode(&hex::decode("8200a0").unwrap()).unwrap(); - assert_eq!(ma, Value::Multiasset(0, BTreeMap::new())); + assert_eq!(ma, Value::Multiasset(0, Multiasset(BTreeMap::new()))); } // Indefinite-encoded value is valid #[test] fn permit_indefinite_value() { let ma: Value = minicbor::decode(&hex::decode("9f00a0ff").unwrap()).unwrap(); - assert_eq!(ma, Value::Multiasset(0, BTreeMap::new())); + assert_eq!(ma, Value::Multiasset(0, Multiasset(BTreeMap::new()))); } // the asset sub-map of a policy map in a multiasset must not be null in Conway @@ -791,7 +1047,7 @@ mod tests { let ma: Result = minicbor::decode(&hex::decode("8200a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); assert_eq!( ma.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: Policy must not be empty".to_owned()) ); } @@ -802,7 +1058,7 @@ mod tests { let ma: Result = minicbor::decode(&hex::decode("8200a1581c00000000000000000000000000000000000000000000000000000000a14000").unwrap()); assert_eq!( ma.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: PositiveCoin must not be 0".to_owned()) ); } @@ -811,7 +1067,7 @@ mod tests { let ma: Result, _> = minicbor::decode(&hex::decode("a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); assert_eq!( ma.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: Policy must not be empty".to_owned()) ); } @@ -832,7 +1088,7 @@ mod tests { let ma: Result, _> = minicbor::decode(&hex::decode(s).unwrap()); match ma { Ok(_) => panic!("decode succeded but should fail"), - Err(e) => assert_eq!(e.to_string(), "bad") + Err(e) => assert_eq!(e.to_string(), "decode error: Multiasset must not exceed size limit") } } @@ -841,7 +1097,7 @@ mod tests { let ma: Result = minicbor::decode(&hex::decode("a1581c00000000000000000000000000000000000000000000000000000000a0").unwrap()); assert_eq!( ma.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: Policy must not be empty".to_owned()) ); } } @@ -857,33 +1113,57 @@ mod tests { assert_eq!(ws.vkeywitness, None); } - #[test] - fn decode_witness_set_having_vkeywitness_legacy_may_be_empty() { - let witness_set_bytes = hex::decode("a10080").unwrap(); - let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - - // FIXME: The decoder behavior here is strictly correct w.r.t. the haskell code; we - // must accept a vkeywitness set that is present but empty (in the legacy witness set - // format). - // - // However, the types we are using in pallas here are confusing; vkeywitness is of type - // Option, and in fact, our "NonEmptySet" type allows constructing an - // empty value via CBOR decoding (there used to be a guard, but it was commented out). - // So we end up with a Some(vec![]). It would make more sense to just have a 'Set' - // type. - assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); - } + // Legacy format is not supported when decoder version is 9 + // These tests should go in a pre-conway module? + //#[test] + //fn decode_witness_set_having_vkeywitness_legacy_may_be_empty() { + // let witness_set_bytes = hex::decode("a10080").unwrap(); + // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + // // FIXME: The decoder behavior here is strictly correct w.r.t. the haskell code; we + // // must accept a vkeywitness set that is present but empty (in the legacy witness set + // // format). + // // + // // However, the types we are using in pallas here are confusing; vkeywitness is of type + // // Option, and in fact, our "NonEmptySet" type allows constructing an + // // empty value via CBOR decoding (there used to be a guard, but it was commented out). + // // So we end up with a Some(vec![]). It would make more sense to just have a 'Set' + // // type. + // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); + //} + + //#[test] + //fn decode_witness_set_having_vkeywitness_legacy_may_be_indefinite() { + // let witness_set_bytes = hex::decode("a1009fff").unwrap(); + // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); + //} + + //#[test] + //fn decode_witness_set_having_vkeywitness_legacy_singleton() { + // let witness_set_bytes = hex::decode("a10081824040").unwrap(); + // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); + + // let expected = VKeyWitness { + // vkey: Bytes::from(vec![]), + // signature: Bytes::from(vec![]), + // }; + // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected])); + //} #[test] - fn decode_witness_set_having_vkeywitness_legacy_may_be_indefinite() { - let witness_set_bytes = hex::decode("a1009fff").unwrap(); - let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - - assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); + fn decode_witness_set_having_vkeywitness_untagged_must_be_nonempty() { + let witness_set_bytes = hex::decode("a10080").unwrap(); + let ws: Result = minicbor::decode(&witness_set_bytes); + assert_eq!( + ws.map_err(|e| e.to_string()), + Err("decode error: decoding empty set as NonEmptySet".to_owned()) + ); } #[test] - fn decode_witness_set_having_vkeywitness_legacy_singleton() { + fn decode_witness_set_having_vkeywitness_untagged_singleton() { let witness_set_bytes = hex::decode("a10081824040").unwrap(); let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); @@ -912,7 +1192,7 @@ mod tests { let ws: Result = minicbor::decode(&witness_set_bytes); assert_eq!( ws.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: decoding empty set as NonEmptySet".to_owned()) ); } @@ -935,17 +1215,13 @@ mod tests { #[test] fn decode_witness_set_having_vkeywitness_duplicate_entries() { let witness_set_bytes = hex::decode("a100d9010282824040824040").unwrap(); - let ws: Result = minicbor::decode(&witness_set_bytes); + let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); let expected = VKeyWitness { vkey: Bytes::from(vec![]), signature: Bytes::from(vec![]), }; - //assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected, expected])); - assert_eq!( - ws.map_err(|e| e.to_string()), - Err("bad".to_owned()) - ); + assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected.clone(), expected])); } } @@ -1022,7 +1298,29 @@ mod tests { let tx: Result, _> = minicbor::decode(&tx_bytes); assert_eq!( tx.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: field inputs is required".to_owned()) + ); + } + + // Single input, no outputs, fee present but zero + #[test] + fn reject_tx_missing_outputs() { + let tx_bytes = hex::decode("a200818258200000000000000000000000000000000000000000000000000000000000000008090200").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: field outputs is required".to_owned()) + ); + } + + // Single input, single output, no fee + #[test] + fn reject_tx_missing_fee() { + let tx_bytes = hex::decode("a20081825820000000000000000000000000000000000000000000000000000000000000000809018182581c000000000000000000000000000000000000000000000000000000001affffffff").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: field fee is required".to_owned()) ); } @@ -1035,10 +1333,90 @@ mod tests { let tx: Result, _> = minicbor::decode(&tx_bytes); assert_eq!( tx.map_err(|e| e.to_string()), - Err("bad".to_owned()) + Err("decode error: mint must be non-empty if present".to_owned()) ); } + #[test] + fn reject_empty_present_certs() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a040000000480").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: decoding empty set as NonEmptySet".to_owned()) + ); + } + + #[test] + fn reject_empty_present_withdrawals() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a0400000005a0").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: withdrawals must be non-empty if present".to_owned()) + ); + } + + #[test] + fn reject_empty_present_collateral_inputs() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a040000000d80").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: decoding empty set as NonEmptySet".to_owned()) + ); + } + + #[test] + fn reject_empty_present_required_signers() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a040000000e80").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: decoding empty set as NonEmptySet".to_owned()) + ); + } + + #[test] + fn reject_empty_present_voting_procedures() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a0400000013a0").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: voting procedures must be non-empty if present".to_owned()) + ); + } + + #[test] + fn reject_empty_present_proposal_procedures() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a040000001480").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: decoding empty set as NonEmptySet".to_owned()) + ); + } + + #[test] + fn reject_empty_present_donation() { + let tx_bytes = hex::decode("a400828258206767676767676767676767676767676767676767676767676767676767676767008258206767676767676767676767676767676767676767676767676767676767676767000200018182581c000000000000000000000000000000000000000000000000000000001a040000001600").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: PositiveCoin must not be 0".to_owned()) + ); + } + + + #[test] + fn reject_duplicate_keys() { + let tx_bytes = hex::decode("a40081825820000000000000000000000000000000000000000000000000000000000000000809018182581c000000000000000000000000000000000000000000000000000000001affffffff02010201").unwrap(); + let tx: Result, _> = minicbor::decode(&tx_bytes); + assert_eq!( + tx.map_err(|e| e.to_string()), + Err("decode error: transaction body must not contain duplicate keys".to_owned()) + ); + } } #[cfg(test)] From 7a1a9a49c348da79b003848ec5de0aa0364408c9 Mon Sep 17 00:00:00 2001 From: ruko Date: Thu, 11 Sep 2025 06:46:01 -0700 Subject: [PATCH 7/9] delete commented code --- pallas-primitives/src/conway/model.rs | 58 --------------------------- 1 file changed, 58 deletions(-) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index aec2f1ef6..c6b5faffe 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -63,12 +63,6 @@ pub enum Value { Multiasset(Coin, Multiasset), } -//codec_by_datatype! { -// Value, -// U8 | U16 | U32 | U64 => Coin, -// (coin, multi => Multiasset) -//} - impl minicbor::Encode for Value { fn encode( &self, @@ -859,19 +853,6 @@ pub struct WitnessSet<'b> { pub plutus_v3_script: Option>>, } -//impl<'b, C> minicbor::Decode<'b, C> for WitnessSet<'b> { -// fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { -// let vkeywitness = d.decode_with(ctx)?; -// let native_script = d.decode_with(ctx)?; -// let bootstrap_witness = d.decode_with(ctx)?; -// let plutus_v1_script = d.decode_with(ctx)?; -// let plutus_data = d.decode_with(ctx)?; -// let redeemer = d.decode_with(ctx)?; -// let plutus_v2_script = d.decode_with(ctx)?; -// let plutus_v3_script = d.decode_with(ctx)?; -// } - - #[deprecated(since = "1.0.0-alpha", note = "use `WitnessSet` instead")] pub type MintedWitnessSet<'b> = WitnessSet<'b>; @@ -1113,45 +1094,6 @@ mod tests { assert_eq!(ws.vkeywitness, None); } - // Legacy format is not supported when decoder version is 9 - // These tests should go in a pre-conway module? - //#[test] - //fn decode_witness_set_having_vkeywitness_legacy_may_be_empty() { - // let witness_set_bytes = hex::decode("a10080").unwrap(); - // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - - // // FIXME: The decoder behavior here is strictly correct w.r.t. the haskell code; we - // // must accept a vkeywitness set that is present but empty (in the legacy witness set - // // format). - // // - // // However, the types we are using in pallas here are confusing; vkeywitness is of type - // // Option, and in fact, our "NonEmptySet" type allows constructing an - // // empty value via CBOR decoding (there used to be a guard, but it was commented out). - // // So we end up with a Some(vec![]). It would make more sense to just have a 'Set' - // // type. - // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); - //} - - //#[test] - //fn decode_witness_set_having_vkeywitness_legacy_may_be_indefinite() { - // let witness_set_bytes = hex::decode("a1009fff").unwrap(); - // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - - // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![])); - //} - - //#[test] - //fn decode_witness_set_having_vkeywitness_legacy_singleton() { - // let witness_set_bytes = hex::decode("a10081824040").unwrap(); - // let ws: WitnessSet = minicbor::decode(&witness_set_bytes).unwrap(); - - // let expected = VKeyWitness { - // vkey: Bytes::from(vec![]), - // signature: Bytes::from(vec![]), - // }; - // assert_eq!(ws.vkeywitness.map(|s| s.to_vec()), Some(vec![expected])); - //} - #[test] fn decode_witness_set_having_vkeywitness_untagged_must_be_nonempty() { let witness_set_bytes = hex::decode("a10080").unwrap(); From 4f85d34a7d2dc6ccd462e3e007c8297b42308d73 Mon Sep 17 00:00:00 2001 From: ruko Date: Mon, 15 Sep 2025 12:26:16 -0700 Subject: [PATCH 8/9] use is_some_and --- pallas-primitives/src/conway/model.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index c6b5faffe..b3182d611 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -565,11 +565,11 @@ impl<'b, C> minicbor::Decode<'b, C> for TransactionBody<'b> { } } - if let Some(map_count) = map_init { - if map_count != items_seen { - return Err(minicbor::decode::Error::message("map is not valid cbor: declared count did not match actual count")); - } - } else { + if map_init.is_some_and(|map_count| map_count != items_seen) { + return Err(minicbor::decode::Error::message("map is not valid cbor: declared count did not match actual count")); + } + + if map_init.is_none() { let ty = d.datatype()?; if ty == minicbor::data::Type::Break { d.skip()?; From 8d2fbe2bfc3dbb133c57db9910f13d5ba1122172 Mon Sep 17 00:00:00 2001 From: ruko Date: Mon, 15 Sep 2025 12:57:15 -0700 Subject: [PATCH 9/9] make multiasset wrapper transparent to fix pallas-traverse build --- pallas-primitives/src/conway/model.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index b3182d611..d47fd115e 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -34,6 +34,27 @@ use std::collections::HashSet; #[derive(Serialize, Deserialize, Encode, Debug, PartialEq, Eq, Clone)] pub struct Multiasset(#[n(0)] BTreeMap>); +impl From<[(PolicyId, BTreeMap); N]> for Multiasset + where BTreeMap>: From<[(PolicyId, BTreeMap); N]> { + fn from(x: [(PolicyId, BTreeMap); N]) -> Self { + Multiasset(BTreeMap::>::from(x)) + } +} + +impl FromIterator<(PolicyId, BTreeMap)> for Multiasset { + fn from_iter)>>(iter: I) -> Self { + Multiasset(BTreeMap::>::from_iter(iter)) + } +} + +impl std::ops::Deref for Multiasset { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl<'b, C, A: minicbor::Decode<'b, C>> minicbor::Decode<'b, C> for Multiasset { fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { let policies: BTreeMap> = d.decode_with(ctx)?;