From 537cd45c23020ae9ff1fe1821fccce55e194a774 Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Tue, 22 Oct 2024 10:09:37 -0300 Subject: [PATCH] refactor: support roundtrip encoding for script data hash components (#526) --- pallas-applying/src/alonzo.rs | 33 +++++------ pallas-applying/tests/alonzo.rs | 6 +- pallas-applying/tests/babbage.rs | 6 +- pallas-primitives/src/conway/model.rs | 47 ++++----------- pallas-primitives/src/plutus_data.rs | 58 +++++-------------- pallas-traverse/src/hashes.rs | 14 ++--- pallas-traverse/src/redeemers.rs | 13 +++++ pallas-traverse/src/witnesses.rs | 20 ++++--- pallas-txbuilder/src/transaction/serialise.rs | 6 +- 9 files changed, 84 insertions(+), 119 deletions(-) diff --git a/pallas-applying/src/alonzo.rs b/pallas-applying/src/alonzo.rs index 6faf9155..b8b359c0 100644 --- a/pallas-applying/src/alonzo.rs +++ b/pallas-applying/src/alonzo.rs @@ -566,24 +566,22 @@ fn mk_plutus_script_redeemer_pointers( } } } - match mint { - Some(minted_value) => { - let sorted_policies: Vec = sort_policies(minted_value); - for (index, policy) in sorted_policies.iter().enumerate() { - for plutus_script in plutus_scripts.iter() { - let hashed_script: PolicyId = - compute_plutus_v1_script_hash(plutus_script); - if *policy == hashed_script { - res.push(RedeemerPointer { - tag: RedeemerTag::Mint, - index: index as u32, - }) - } + + if let Some(minted_value) = mint { + let sorted_policies: Vec = sort_policies(minted_value); + for (index, policy) in sorted_policies.iter().enumerate() { + for plutus_script in plutus_scripts.iter() { + let hashed_script: PolicyId = compute_plutus_v1_script_hash(plutus_script); + if *policy == hashed_script { + res.push(RedeemerPointer { + tag: RedeemerTag::Mint, + index: index as u32, + }) } } } - None => (), } + res } None => Vec::new(), @@ -727,10 +725,11 @@ fn check_vkey_input_wits( let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref()); let mut inputs_and_collaterals: Vec = Vec::new(); inputs_and_collaterals.extend(tx_body.inputs.clone()); - match &tx_body.collateral { - Some(collaterals) => inputs_and_collaterals.extend(collaterals.clone()), - None => (), + + if let Some(collaterals) = &tx_body.collateral { + inputs_and_collaterals.extend(collaterals.clone()); } + for input in inputs_and_collaterals.iter() { match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) { Some(multi_era_output) => { diff --git a/pallas-applying/tests/alonzo.rs b/pallas-applying/tests/alonzo.rs index d6a18ce2..59ad109e 100644 --- a/pallas-applying/tests/alonzo.rs +++ b/pallas-applying/tests/alonzo.rs @@ -27,6 +27,8 @@ use std::borrow::Cow; #[cfg(test)] mod alonzo_tests { + use pallas_primitives::MaybeIndefArray; + use super::*; #[test] @@ -2046,7 +2048,7 @@ mod alonzo_tests { ); let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone(); let old_datum: KeepRaw = tx_wits.plutus_data.unwrap().pop().unwrap(); - let new_datum: PlutusData = PlutusData::Array(Vec::new()); + let new_datum: PlutusData = PlutusData::Array(MaybeIndefArray::Def(Vec::new())); let mut new_datum_buf: Vec = Vec::new(); let _ = encode(new_datum, &mut new_datum_buf); let keep_raw_new_datum: KeepRaw = @@ -2171,7 +2173,7 @@ mod alonzo_tests { let new_redeemer: Redeemer = Redeemer { tag: RedeemerTag::Spend, index: 15, - data: PlutusData::Array(Vec::new()), + data: PlutusData::Array(MaybeIndefArray::Def(Vec::new())), ex_units: ExUnits { mem: 0, steps: 0 }, }; tx_wits.redeemer = Some(vec![old_redeemer, new_redeemer]); diff --git a/pallas-applying/tests/babbage.rs b/pallas-applying/tests/babbage.rs index c17f8068..3aa6dd82 100644 --- a/pallas-applying/tests/babbage.rs +++ b/pallas-applying/tests/babbage.rs @@ -29,6 +29,8 @@ use std::borrow::Cow; #[cfg(test)] mod babbage_tests { + use pallas_primitives::MaybeIndefArray; + use super::*; #[test] @@ -2150,7 +2152,7 @@ mod babbage_tests { add_collateral_babbage(&mtx.transaction_body, &mut utxos, collateral_info); let mut tx_wits: MintedWitnessSet = mtx.transaction_witness_set.unwrap().clone(); let old_datum: KeepRaw = tx_wits.plutus_data.unwrap().pop().unwrap(); - let new_datum: PlutusData = PlutusData::Array(Vec::new()); + let new_datum: PlutusData = PlutusData::Array(MaybeIndefArray::Def(Vec::new())); let mut new_datum_buf: Vec = Vec::new(); let _ = encode(new_datum, &mut new_datum_buf); let keep_raw_new_datum: KeepRaw = @@ -2245,7 +2247,7 @@ mod babbage_tests { let new_redeemer: Redeemer = Redeemer { tag: RedeemerTag::Spend, index: 15, - data: PlutusData::Array(Vec::new()), + data: PlutusData::Array(MaybeIndefArray::Def(Vec::new())), ex_units: ExUnits { mem: 0, steps: 0 }, }; tx_wits.redeemer = Some(vec![old_redeemer, new_redeemer]); diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index 2ea3854b..c758bade 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -3,9 +3,8 @@ //! Handcrafted, idiomatic rust artifacts based on based on the [Conway CDDL](https://github.com/IntersectMBO/cardano-ledger/blob/master/eras/conway/impl/cddl-files/conway.cddl) file in IntersectMBO repo. use serde::{Deserialize, Serialize}; -use std::ops::Deref; -use pallas_codec::minicbor::{self, decode::Error, Decode, Encode}; +use pallas_codec::minicbor::{self, Decode, Encode}; use pallas_codec::utils::CborWrap; pub use crate::{ @@ -1278,22 +1277,15 @@ pub struct RedeemersValue { pub ex_units: ExUnits, } -// TODO: Redeemers needs to be KeepRaw because of script data hash #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Redeemers(NonEmptyKeyValuePairs); - -impl Deref for Redeemers { - type Target = NonEmptyKeyValuePairs; - - fn deref(&self) -> &Self::Target { - &self.0 - } +pub enum Redeemers { + List(MaybeIndefArray), + Map(NonEmptyKeyValuePairs), } impl From> for Redeemers { fn from(value: NonEmptyKeyValuePairs) -> Self { - Redeemers(value) + Redeemers::Map(value) } } @@ -1301,30 +1293,10 @@ impl<'b, C> minicbor::Decode<'b, C> for Redeemers { fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { match d.datatype()? { minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => { - let redeemers: Vec = d.decode_with(ctx)?; - - let kvs = redeemers - .into_iter() - .map(|x| { - ( - RedeemersKey { - tag: x.tag, - index: x.index, - }, - RedeemersValue { - data: x.data, - ex_units: x.ex_units, - }, - ) - }) - .collect::>() - .try_into() - .map_err(|_| Error::message("decoding empty redeemers"))?; - - Ok(Self(kvs)) + Ok(Self::List(d.decode_with(ctx)?)) } minicbor::data::Type::Map | minicbor::data::Type::MapIndef => { - Ok(Self(d.decode_with(ctx)?)) + Ok(Self::Map(d.decode_with(ctx)?)) } _ => Err(minicbor::decode::Error::message( "invalid type for redeemers struct", @@ -1339,7 +1311,10 @@ impl minicbor::Encode for Redeemers { e: &mut minicbor::Encoder, ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - e.encode_with(&self.0, ctx)?; + match self { + Self::List(x) => e.encode_with(x, ctx)?, + Self::Map(x) => e.encode_with(x, ctx)?, + }; Ok(()) } diff --git a/pallas-primitives/src/plutus_data.rs b/pallas-primitives/src/plutus_data.rs index f8c0f1ac..8b1f124b 100644 --- a/pallas-primitives/src/plutus_data.rs +++ b/pallas-primitives/src/plutus_data.rs @@ -1,10 +1,13 @@ use crate::KeyValuePairs; -use pallas_codec::minicbor::{ - self, - data::{IanaTag, Tag}, - Encode, -}; use pallas_codec::utils::Int; +use pallas_codec::{ + minicbor::{ + self, + data::{IanaTag, Tag}, + Encode, + }, + utils::MaybeIndefArray, +}; use serde::{Deserialize, Serialize}; use std::{fmt, ops::Deref}; @@ -14,7 +17,7 @@ pub enum PlutusData { Map(KeyValuePairs), BigInt(BigInt), BoundedBytes(BoundedBytes), - Array(Vec), + Array(MaybeIndefArray), } impl<'b, C> minicbor::decode::Decode<'b, C> for PlutusData { @@ -96,11 +99,7 @@ impl minicbor::encode::Encode for PlutusData { e.encode_with(a, ctx)?; } Self::Array(a) => { - // we use definite array for empty array or indef array otherwise to match - // haskell implementation https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L153 - // default encoder for a list: - // https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181 - encode_list(a, e, ctx)?; + e.encode_with(a, ctx)?; } }; @@ -182,7 +181,7 @@ impl minicbor::encode::Encode for BigInt { pub struct Constr { pub tag: u64, pub any_constructor: Option, - pub fields: Vec, + pub fields: MaybeIndefArray, } impl<'b, C, A> minicbor::decode::Decode<'b, C> for Constr @@ -227,22 +226,12 @@ where match self.tag { 102 => { - // definite length array here - // https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L152 - e.array(2)?; - e.encode_with(self.any_constructor.unwrap_or_default(), ctx)?; - - // we use definite array for empty array or indef array otherwise to match - // haskell implementation https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L144 - // default encoder for a list: - // https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181 - encode_list(&self.fields, e, ctx)?; + let x = (self.any_constructor.unwrap_or_default(), &self.fields); + e.encode_with(x, ctx)?; Ok(()) } _ => { - // we use definite array for empty array or indef array otherwise to match - // haskell implementation. See above reference. - encode_list(&self.fields, e, ctx)?; + e.encode_with(&self.fields, ctx)?; Ok(()) } } @@ -332,22 +321,3 @@ impl<'b, C> minicbor::decode::Decode<'b, C> for BoundedBytes { Ok(BoundedBytes::from(res)) } } - -fn encode_list>( - a: &Vec, - e: &mut minicbor::Encoder, - ctx: &mut C, -) -> Result<(), minicbor::encode::Error> { - // Mimics default haskell list encoding from cborg: - // We use indef array for non-empty arrays but definite 0-length array for empty - if a.is_empty() { - e.array(0)?; - } else { - e.begin_array()?; - for v in a { - e.encode_with(v, ctx)?; - } - e.end()?; - } - Ok(()) -} diff --git a/pallas-traverse/src/hashes.rs b/pallas-traverse/src/hashes.rs index 14ff5fef..94793676 100644 --- a/pallas-traverse/src/hashes.rs +++ b/pallas-traverse/src/hashes.rs @@ -182,7 +182,7 @@ mod tests { use crate::{Era, MultiEraTx}; use super::{ComputeHash, OriginalHash}; - use pallas_codec::utils::Int; + use pallas_codec::utils::{Int, MaybeIndefArray}; use pallas_codec::{minicbor, utils::Bytes}; use pallas_crypto::hash::Hash; use pallas_crypto::key::ed25519::PublicKey; @@ -283,26 +283,26 @@ mod tests { let pd = alonzo::PlutusData::Constr(alonzo::Constr:: { tag: 1280, any_constructor: None, - fields: vec![ + fields: MaybeIndefArray::Indef(vec![ alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(4))), alonzo::PlutusData::Constr(alonzo::Constr:: { tag: 124, any_constructor: None, - fields: vec![ + fields: MaybeIndefArray::Indef(vec![ alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-4))), alonzo::PlutusData::Constr(alonzo::Constr:: { tag: 102, any_constructor: Some(453), - fields: vec![ + fields: MaybeIndefArray::Indef(vec![ alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(2))), alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(3434))), - ], + ]), }), alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-11828293))), - ], + ]), }), alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(11828293))), - ], + ]), }); // if you need to try this out in the cardano-cli, uncomment this line to see diff --git a/pallas-traverse/src/redeemers.rs b/pallas-traverse/src/redeemers.rs index 83aa2e4c..9f96c9a9 100644 --- a/pallas-traverse/src/redeemers.rs +++ b/pallas-traverse/src/redeemers.rs @@ -65,4 +65,17 @@ impl<'b> MultiEraRedeemer<'b> { Box::new(Cow::Borrowed(redeemers_val)), ) } + + pub fn from_conway_deprecated(redeemer: &'b conway::Redeemer) -> Self { + Self::Conway( + Box::new(Cow::Owned(conway::RedeemersKey { + tag: redeemer.tag, + index: redeemer.index, + })), + Box::new(Cow::Owned(conway::RedeemersValue { + data: redeemer.data.clone(), + ex_units: redeemer.ex_units, + })), + ) + } } diff --git a/pallas-traverse/src/witnesses.rs b/pallas-traverse/src/witnesses.rs index 6ad238ce..9c8cf3e7 100644 --- a/pallas-traverse/src/witnesses.rs +++ b/pallas-traverse/src/witnesses.rs @@ -1,7 +1,7 @@ use pallas_codec::utils::KeepRaw; use pallas_primitives::{ alonzo::{self, BootstrapWitness, NativeScript, VKeyWitness}, - PlutusData, PlutusScript, + conway, PlutusData, PlutusScript, }; use crate::{MultiEraRedeemer, MultiEraTx}; @@ -144,13 +144,17 @@ impl<'b> MultiEraTx<'b> { .flat_map(|x| x.iter()) .map(MultiEraRedeemer::from_alonzo_compatible) .collect(), - Self::Conway(x) => x - .transaction_witness_set - .redeemer - .iter() - .flat_map(|x| x.iter()) - .map(|(k, v)| MultiEraRedeemer::from_conway(k, v)) - .collect(), + Self::Conway(x) => match x.transaction_witness_set.redeemer.as_deref() { + Some(conway::Redeemers::Map(x)) => x + .iter() + .map(|(k, v)| MultiEraRedeemer::from_conway(k, v)) + .collect(), + Some(conway::Redeemers::List(x)) => x + .iter() + .map(MultiEraRedeemer::from_conway_deprecated) + .collect(), + _ => vec![], + }, } } diff --git a/pallas-txbuilder/src/transaction/serialise.rs b/pallas-txbuilder/src/transaction/serialise.rs index 5dd9bccf..92e7c469 100644 --- a/pallas-txbuilder/src/transaction/serialise.rs +++ b/pallas-txbuilder/src/transaction/serialise.rs @@ -395,7 +395,7 @@ mod tests { use std::str::FromStr; use pallas_addresses::Address as PallasAddress; - use pallas_primitives::{babbage::PlutusData, Fragment}; + use pallas_primitives::{babbage::PlutusData, Fragment, MaybeIndefArray}; use crate::transaction::{model::*, Bytes64, DatumBytes, DatumHash, Hash28, TransactionStatus}; @@ -473,8 +473,8 @@ mod tests { ), datums: Some(datums), redeemers: Some(Redeemers::from_map(vec![ - (RedeemerPurpose::Spend(Input { tx_hash: Bytes32([4; 32]), txo_index: 1 }), (Bytes(PlutusData::Array(vec![]).encode_fragment().unwrap()), Some(ExUnits { mem: 1337, steps: 7331 }))), - (RedeemerPurpose::Mint(Hash28([5; 28])), (Bytes(PlutusData::Array(vec![]).encode_fragment().unwrap()), None)), + (RedeemerPurpose::Spend(Input { tx_hash: Bytes32([4; 32]), txo_index: 1 }), (Bytes(PlutusData::Array(MaybeIndefArray::Def(vec![])).encode_fragment().unwrap()), Some(ExUnits { mem: 1337, steps: 7331 }))), + (RedeemerPurpose::Mint(Hash28([5; 28])), (Bytes(PlutusData::Array(MaybeIndefArray::Def(vec![])).encode_fragment().unwrap()), None)), ].into_iter().collect::>())), signature_amount_override: Some(5), change_address: Some(Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap())),