From fdf7a81288557f605f416e6ae8952a73f665356f Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 8 Aug 2024 23:01:17 +0200 Subject: [PATCH] implement a strict subset of PlutusV3 transaction info More specifically, that is simply mimicking the script context from v2, minus the new governance features. --- crates/uplc/src/tx.rs | 3 +- crates/uplc/src/tx/eval.rs | 19 ++++++++- crates/uplc/src/tx/phase_one.rs | 17 +++----- crates/uplc/src/tx/script_context.rs | 56 +++++++++++++++++++++--- crates/uplc/src/tx/to_plutus_data.rs | 64 +++++++++++++++++++++++++--- 5 files changed, 135 insertions(+), 24 deletions(-) diff --git a/crates/uplc/src/tx.rs b/crates/uplc/src/tx.rs index cb8a4b7a..b6086d9a 100644 --- a/crates/uplc/src/tx.rs +++ b/crates/uplc/src/tx.rs @@ -96,7 +96,8 @@ pub fn eval_phase_two_raw( run_phase_one: bool, with_redeemer: fn(&Redeemer) -> (), ) -> Result>, Error> { - let multi_era_tx = MultiEraTx::decode_for_era(Era::Babbage, tx_bytes) + let multi_era_tx = MultiEraTx::decode_for_era(Era::Conway, tx_bytes) + .or_else(|_| MultiEraTx::decode_for_era(Era::Babbage, tx_bytes)) .or_else(|_| MultiEraTx::decode_for_era(Era::Alonzo, tx_bytes))?; let cost_mdls = CostMdls::decode_fragment(cost_mdls_bytes)?; diff --git a/crates/uplc/src/tx/eval.rs b/crates/uplc/src/tx/eval.rs index 14cf666d..1fdc4a34 100644 --- a/crates/uplc/src/tx/eval.rs +++ b/crates/uplc/src/tx/eval.rs @@ -6,7 +6,7 @@ use super::{ use crate::{ ast::{FakeNamedDeBruijn, NamedDeBruijn, Program}, machine::cost_model::ExBudget, - tx::script_context::{DataLookupTable, ScriptVersion, TxInfoV1, TxInfoV2}, + tx::script_context::{DataLookupTable, ScriptVersion, TxInfoV1, TxInfoV2, TxInfoV3}, PlutusData, }; use pallas_codec::utils::Bytes; @@ -116,7 +116,22 @@ pub fn eval_redeemer( program(script.0)?, ), - (ScriptVersion::V3(_script), _datum) => todo!(), + (ScriptVersion::V3(script), datum) => do_eval_redeemer( + cost_mdls_opt + .map(|cost_mdls| { + cost_mdls + .plutus_v3 + .as_ref() + .ok_or(Error::CostModelNotFound(Language::PlutusV3)) + }) + .transpose()?, + initial_budget, + &Language::PlutusV3, + datum, + redeemer, + TxInfoV3::from_transaction(tx, utxos, slot_config)?, + program(script.0)?, + ), } .map_err(|err| Error::RedeemerError { tag: redeemer_tag_to_string(&redeemer.tag), diff --git a/crates/uplc/src/tx/phase_one.rs b/crates/uplc/src/tx/phase_one.rs index 41619fca..0d8939bc 100644 --- a/crates/uplc/src/tx/phase_one.rs +++ b/crates/uplc/src/tx/phase_one.rs @@ -10,7 +10,7 @@ use pallas_primitives::conway::{ }; use std::collections::HashMap; -type AlonzoScriptsNeeded = Vec<(ScriptPurpose, ScriptHash)>; +type ScriptsNeeded = Vec<(ScriptPurpose, ScriptHash)>; // subset of phase-1 ledger checks related to scripts pub fn eval_phase_one( @@ -28,7 +28,7 @@ pub fn eval_phase_one( } pub fn validate_missing_scripts( - needed: &AlonzoScriptsNeeded, + needed: &ScriptsNeeded, txscripts: HashMap, ) -> Result<(), Error> { let received_hashes = txscripts.keys().copied().collect::>(); @@ -61,10 +61,7 @@ pub fn validate_missing_scripts( Ok(()) } -pub fn scripts_needed( - tx: &MintedTx, - utxos: &[ResolvedInput], -) -> Result { +pub fn scripts_needed(tx: &MintedTx, utxos: &[ResolvedInput]) -> Result { let mut needed = Vec::new(); let txb = tx.transaction_body.clone(); @@ -106,7 +103,7 @@ pub fn scripts_needed( None }) - .collect::() + .collect::() }) .unwrap_or_default(); @@ -127,7 +124,7 @@ pub fn scripts_needed( _ => None, } }) - .collect::() + .collect::() }) .unwrap_or_default(); @@ -137,7 +134,7 @@ pub fn scripts_needed( .map(|m| { m.iter() .map(|(policy_id, _)| (ScriptPurpose::Minting(*policy_id), *policy_id)) - .collect::() + .collect::() }) .unwrap_or_default(); @@ -156,7 +153,7 @@ pub fn scripts_needed( /// hasExactSetOfRedeemers in Ledger Spec, but we pass `txscripts` directly pub fn has_exact_set_of_redeemers( tx: &MintedTx, - needed: &AlonzoScriptsNeeded, + needed: &ScriptsNeeded, tx_scripts: HashMap, ) -> Result<(), Error> { let mut redeemers_needed = Vec::new(); diff --git a/crates/uplc/src/tx/script_context.rs b/crates/uplc/src/tx/script_context.rs index 225b9162..19b71798 100644 --- a/crates/uplc/src/tx/script_context.rs +++ b/crates/uplc/src/tx/script_context.rs @@ -27,6 +27,7 @@ pub struct TxInInfo { pub out_ref: TransactionInput, pub resolved: TxOut, } + #[derive(Debug, PartialEq, Clone)] pub enum TxOut { V1(TransactionOutput), @@ -290,15 +291,51 @@ impl TxInfoV2 { } } -pub struct TxInfoV3 {} +#[derive(Debug, PartialEq, Clone)] +pub struct TxInfoV3 { + pub inputs: Vec, + pub reference_inputs: Vec, + pub outputs: Vec, + pub fee: Value, + pub mint: MintValue, + pub certificates: Vec, + pub withdrawals: KeyValuePairs, + pub valid_range: TimeRange, + pub signatories: Vec, + pub redeemers: KeyValuePairs, + pub data: KeyValuePairs, + // TODO: + // votes : KeyValuePairs> + // proposalProcedures : Vec + // currentTreasuryAmount : Option + // treasuryDonation : Option + pub id: Hash<32>, +} impl TxInfoV3 { pub fn from_transaction( - _tx: &MintedTx, - _utxos: &[ResolvedInput], - _slot_config: &SlotConfig, + tx: &MintedTx, + utxos: &[ResolvedInput], + slot_config: &SlotConfig, ) -> Result { - todo!("TxInfoV3") + if let TxInfo::V2(tx_info_v2) = TxInfoV2::from_transaction(tx, utxos, slot_config)? { + Ok(TxInfo::V3(TxInfoV3 { + inputs: tx_info_v2.inputs, + reference_inputs: tx_info_v2.reference_inputs, + outputs: tx_info_v2.outputs, + fee: tx_info_v2.fee, + mint: tx_info_v2.mint, + certificates: tx_info_v2.certificates, + withdrawals: tx_info_v2.withdrawals, + valid_range: tx_info_v2.valid_range, + signatories: tx_info_v2.signatories, + redeemers: tx_info_v2.redeemers, + data: tx_info_v2.data, + id: tx_info_v2.id, + })) + } else { + unreachable!() + } } } @@ -306,12 +343,15 @@ impl TxInfoV3 { pub enum TxInfo { V1(TxInfoV1), V2(TxInfoV2), + V3(TxInfoV3), } impl TxInfo { pub fn purpose(&self, needle: &Redeemer) -> Option { match self { - TxInfo::V1(TxInfoV1 { redeemers, .. }) | TxInfo::V2(TxInfoV2 { redeemers, .. }) => { + TxInfo::V1(TxInfoV1 { redeemers, .. }) + | TxInfo::V2(TxInfoV2 { redeemers, .. }) + | TxInfo::V3(TxInfoV3 { redeemers, .. }) => { redeemers.iter().find_map(|(purpose, redeemer)| { if redeemer == needle { Some(purpose.clone()) @@ -327,6 +367,7 @@ impl TxInfo { match self { TxInfo::V1(info) => &info.inputs, TxInfo::V2(info) => &info.inputs, + TxInfo::V3(info) => &info.inputs, } } @@ -334,6 +375,7 @@ impl TxInfo { match self { TxInfo::V1(info) => &info.mint, TxInfo::V2(info) => &info.mint, + TxInfo::V3(info) => &info.mint, } } @@ -341,6 +383,7 @@ impl TxInfo { match self { TxInfo::V1(info) => &info.withdrawals[..], TxInfo::V2(info) => &info.withdrawals[..], + TxInfo::V3(info) => &info.withdrawals[..], } } @@ -348,6 +391,7 @@ impl TxInfo { match self { TxInfo::V1(info) => &info.certificates[..], TxInfo::V2(info) => &info.certificates[..], + TxInfo::V3(info) => &info.certificates[..], } } } diff --git a/crates/uplc/src/tx/to_plutus_data.rs b/crates/uplc/src/tx/to_plutus_data.rs index 2ed11d45..77022865 100644 --- a/crates/uplc/src/tx/to_plutus_data.rs +++ b/crates/uplc/src/tx/to_plutus_data.rs @@ -36,6 +36,8 @@ fn empty_constr(index: u64) -> PlutusData { }) } +struct WithWrappedTransactionId<'a, T>(&'a T); + pub trait ToPlutusData { fn to_plutus_data(&self) -> PlutusData; } @@ -98,12 +100,24 @@ impl ToPlutusData for Address { } } +impl<'a> ToPlutusData for WithWrappedTransactionId<'a, TransactionInput> { + fn to_plutus_data(&self) -> PlutusData { + wrap_multiple_with_constr( + 0, + vec![ + wrap_with_constr(0, self.0.transaction_id.to_plutus_data()), + PlutusData::BigInt(BigInt::Int((self.0.index as i128).try_into().unwrap())), + ], + ) + } +} + impl ToPlutusData for TransactionInput { fn to_plutus_data(&self) -> PlutusData { wrap_multiple_with_constr( 0, vec![ - wrap_with_constr(0, self.transaction_id.to_plutus_data()), + self.transaction_id.to_plutus_data(), PlutusData::BigInt(BigInt::Int((self.index as i128).try_into().unwrap())), ], ) @@ -299,7 +313,7 @@ impl ToPlutusData for TxOut { // legacy_output.datum_hash.to_plutus_data(), // ], // ), - TransactionOutput::Legacy(..) => todo!("TransactionOutput::Legacy"), + TransactionOutput::Legacy(..) => unimplemented!("TransactionOutput::Legacy"), TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr( 0, vec![ @@ -329,7 +343,7 @@ impl ToPlutusData for TxOut { // None::.to_plutus_data(), // ], // ), - TransactionOutput::Legacy(..) => todo!("TransactionOutput::Legacy"), + TransactionOutput::Legacy(..) => unimplemented!("TransactionOutput::Legacy"), TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr( 0, vec![ @@ -556,6 +570,29 @@ impl ToPlutusData for TimeRange { } } +impl<'a> ToPlutusData for WithWrappedTransactionId<'a, Vec> { + fn to_plutus_data(&self) -> PlutusData { + PlutusData::Array( + self.0 + .iter() + .map(|p| WithWrappedTransactionId(p).to_plutus_data()) + .collect(), + ) + } +} + +impl<'a> ToPlutusData for WithWrappedTransactionId<'a, TxInInfo> { + fn to_plutus_data(&self) -> PlutusData { + wrap_multiple_with_constr( + 0, + vec![ + WithWrappedTransactionId(&self.0.out_ref).to_plutus_data(), + self.0.resolved.to_plutus_data(), + ], + ) + } +} + impl ToPlutusData for TxInInfo { fn to_plutus_data(&self) -> PlutusData { wrap_multiple_with_constr( @@ -587,7 +624,7 @@ impl ToPlutusData for TxInfo { TxInfo::V1(tx_info) => wrap_multiple_with_constr( 0, vec![ - tx_info.inputs.to_plutus_data(), + WithWrappedTransactionId(&tx_info.inputs).to_plutus_data(), tx_info.outputs.to_plutus_data(), tx_info.fee.to_plutus_data(), tx_info.mint.to_plutus_data(), @@ -600,6 +637,23 @@ impl ToPlutusData for TxInfo { ], ), TxInfo::V2(tx_info) => wrap_multiple_with_constr( + 0, + vec![ + WithWrappedTransactionId(&tx_info.inputs).to_plutus_data(), + WithWrappedTransactionId(&tx_info.reference_inputs).to_plutus_data(), + tx_info.outputs.to_plutus_data(), + tx_info.fee.to_plutus_data(), + tx_info.mint.to_plutus_data(), + tx_info.certificates.to_plutus_data(), + tx_info.withdrawals.to_plutus_data(), + tx_info.valid_range.to_plutus_data(), + tx_info.signatories.to_plutus_data(), + tx_info.redeemers.to_plutus_data(), + tx_info.data.to_plutus_data(), + wrap_with_constr(0, tx_info.id.to_plutus_data()), + ], + ), + TxInfo::V3(tx_info) => wrap_multiple_with_constr( 0, vec![ tx_info.inputs.to_plutus_data(), @@ -613,7 +667,7 @@ impl ToPlutusData for TxInfo { tx_info.signatories.to_plutus_data(), tx_info.redeemers.to_plutus_data(), tx_info.data.to_plutus_data(), - wrap_with_constr(0, tx_info.id.to_plutus_data()), + tx_info.id.to_plutus_data(), ], ), }