use pallas_addresses::{ Address, ScriptHash, ShelleyDelegationPart, ShelleyPaymentPart, StakePayload, }; use pallas_codec::{ minicbor::{bytes::ByteVec, data::Int}, utils::{AnyUInt, KeyValuePairs, MaybeIndefArray}, }; use pallas_crypto::hash::{Hash, Hasher}; use pallas_primitives::{ babbage::{ AddrKeyhash, AssetName, BigInt, Certificate, Coin, Constr, CostModel, DatumHash, DatumOption, ExUnits, Language, Mint, MintedTx, PlutusV1Script, PlutusV2Script, PolicyId, Redeemer, RedeemerTag, RewardAccount, Script, ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Tx, Value, Withdrawals, }, Fragment, ToHash, }; use pallas_traverse::{Era, MultiEraTx}; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, ops::Deref, str::FromStr, vec, }; use uplc::{ ast::{FakeNamedDeBruijn, NamedDeBruijn, Program}, machine::cost_model::ExBudget, PlutusData, }; use crate::args::ResolvedInputOld; pub fn get_tx_in_info_old(resolved_inputs: &[ResolvedInputOld]) -> anyhow::Result> { let mut tx_in_info = Vec::new(); for resolved_input in resolved_inputs { let tx_out_ref = TransactionInput { transaction_id: Hash::from_str(resolved_input.input.tx_hash.as_str())?, index: resolved_input.input.index, } .to_plutus_data(); let address = Address::from_bech32(&resolved_input.output.address)?; let lovelace = resolved_input.output.value.0; let mut assets = resolved_input.output.value.1.clone(); assets.insert( "".to_string(), vec![("".to_string(), lovelace)].into_iter().collect(), ); let tx_out = PlutusData::Constr(Constr { tag: 0, any_constructor: None, fields: MaybeIndefArray::Indef(vec![ // txOutAddress address.to_plutus_data(), // txOutValue PlutusData::Map(KeyValuePairs::Def( assets .iter() .map(|val| { let currency_symbol = PlutusData::BoundedBytes(hex::decode(val.0).unwrap().into()); let token_map = PlutusData::Map(KeyValuePairs::Def( val.1 .iter() .map(|token| { ( PlutusData::BoundedBytes( token.0.as_bytes().to_vec().into(), ), PlutusData::BigInt(BigInt::Int((*token.1).into())), ) }) .collect(), )); (currency_symbol, token_map) }) .collect(), )), ]), }); tx_in_info.push(PlutusData::Constr(Constr { tag: 0, any_constructor: None, fields: MaybeIndefArray::Indef(vec![tx_out_ref, tx_out]), })); } Ok(tx_in_info) } // --------------- fn wrap_with_constr(index: u64, data: PlutusData) -> PlutusData { PlutusData::Constr(Constr { tag: constr_index(index), any_constructor: None, fields: MaybeIndefArray::Indef(vec![data]), }) } fn wrap_multiple_with_constr(index: u64, data: Vec) -> PlutusData { PlutusData::Constr(Constr { tag: constr_index(index), any_constructor: None, fields: MaybeIndefArray::Indef(data), }) } fn empty_constr(index: u64) -> PlutusData { PlutusData::Constr(Constr { tag: constr_index(index), any_constructor: None, fields: MaybeIndefArray::Indef(vec![]), }) } /// Translate constructor index to cbor tag. fn constr_index(index: u64) -> u64 { 121 + index } pub trait ToPlutusData { fn to_plutus_data(&self) -> PlutusData; } impl ToPlutusData for Address { fn to_plutus_data(&self) -> PlutusData { match self { Address::Shelley(shelley_address) => { let payment_part = shelley_address.payment(); let stake_part = shelley_address.delegation(); let payment_part_plutus_data = match payment_part { ShelleyPaymentPart::Key(payment_keyhash) => { wrap_with_constr(0, payment_keyhash.to_plutus_data()) } ShelleyPaymentPart::Script(script_hash) => { wrap_with_constr(1, script_hash.to_plutus_data()) } }; let stake_part_plutus_data = match stake_part { ShelleyDelegationPart::Key(stake_keyhash) => { Some(StakeCredential::AddrKeyhash(stake_keyhash.clone())).to_plutus_data() } ShelleyDelegationPart::Script(script_hash) => { Some(StakeCredential::Scripthash(script_hash.clone())).to_plutus_data() } ShelleyDelegationPart::Pointer(pointer) => Some(wrap_multiple_with_constr( 1, vec![ pointer.slot().to_plutus_data(), pointer.tx_idx().to_plutus_data(), pointer.cert_idx().to_plutus_data(), ], )) .to_plutus_data(), ShelleyDelegationPart::Null => None::.to_plutus_data(), }; wrap_multiple_with_constr(0, vec![payment_part_plutus_data, stake_part_plutus_data]) } _ => unreachable!(), } } } 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()), PlutusData::BigInt(BigInt::Int(self.index.into())), ], ) } } impl ToPlutusData for Hash { fn to_plutus_data(&self) -> PlutusData { PlutusData::BoundedBytes(self.to_vec().into()) } } impl ToPlutusData for ByteVec { fn to_plutus_data(&self) -> PlutusData { PlutusData::BoundedBytes(self.clone()) } } impl ToPlutusData for (K, V) { fn to_plutus_data(&self) -> PlutusData { wrap_multiple_with_constr(0, vec![self.0.to_plutus_data(), self.1.to_plutus_data()]) } } impl ToPlutusData for MaybeIndefArray { fn to_plutus_data(&self) -> PlutusData { PlutusData::Array(MaybeIndefArray::Indef( self.iter().map(|p| p.to_plutus_data()).collect(), )) } } impl ToPlutusData for KeyValuePairs { fn to_plutus_data(&self) -> PlutusData { let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![]; for (key, value) in self.iter() { data_vec.push((key.to_plutus_data(), value.to_plutus_data())) } PlutusData::Map(KeyValuePairs::Def(data_vec)) } } impl ToPlutusData for Option { fn to_plutus_data(&self) -> PlutusData { match self { None => empty_constr(1), Some(data) => wrap_with_constr(0, data.to_plutus_data()), } } } // Does this here surely overwrite Option from above for DatumOption? impl ToPlutusData for Option { // NoOutputDatum = 0 | OutputDatumHash = 1 | OutputDatum = 2 fn to_plutus_data(&self) -> PlutusData { match self { None => empty_constr(0), Some(option) => match option { DatumOption::Hash(hash) => wrap_with_constr(1, hash.to_plutus_data()), DatumOption::Data(data) => wrap_with_constr(2, data.0.clone()), }, } } } impl ToPlutusData for AnyUInt { fn to_plutus_data(&self) -> PlutusData { match self { AnyUInt::U8(u8) => PlutusData::BigInt(BigInt::Int(Int::from(*u8))), AnyUInt::U16(u16) => PlutusData::BigInt(BigInt::Int(Int::from(*u16))), AnyUInt::U32(u32) => PlutusData::BigInt(BigInt::Int(Int::from(*u32))), AnyUInt::U64(u64) => PlutusData::BigInt(BigInt::Int(Int::from(*u64))), AnyUInt::MajorByte(u8) => PlutusData::BigInt(BigInt::Int(Int::from(*u8))), // is this correct? I don't know exactly what is does } } } impl ToPlutusData for Int { fn to_plutus_data(&self) -> PlutusData { PlutusData::BigInt(BigInt::Int(self.clone())) } } impl ToPlutusData for BigInt { fn to_plutus_data(&self) -> PlutusData { PlutusData::BigInt(self.clone()) } } impl ToPlutusData for i64 { fn to_plutus_data(&self) -> PlutusData { PlutusData::BigInt(BigInt::Int(Int::from(*self))) } } impl ToPlutusData for u64 { fn to_plutus_data(&self) -> PlutusData { PlutusData::BigInt(BigInt::Int(Int::from(*self))) } } impl ToPlutusData for Value { fn to_plutus_data(&self) -> PlutusData { match self { Value::Coin(coin) => PlutusData::Map(KeyValuePairs::Def(vec![( PolicyId::from(vec![]).to_plutus_data(), PlutusData::Map(KeyValuePairs::Def(vec![( AssetName::from(vec![]).to_plutus_data(), coin.to_plutus_data(), )])), )])), Value::Multiasset(coin, multiassets) => { let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![( PolicyId::from(vec![]).to_plutus_data(), PlutusData::Map(KeyValuePairs::Def(vec![( AssetName::from(vec![]).to_plutus_data(), coin.to_plutus_data(), )])), )]; for (policy_id, assets) in multiassets.iter() { let mut assets_vec = vec![]; for (asset, amount) in assets.iter() { assets_vec.push((asset.to_plutus_data(), amount.to_plutus_data())); } data_vec.push(( policy_id.to_plutus_data(), PlutusData::Map(KeyValuePairs::Def(assets_vec)), )); } PlutusData::Map(KeyValuePairs::Def(data_vec)) } } } } impl ToPlutusData for ScriptRef { fn to_plutus_data(&self) -> PlutusData { match &self.0 { Script::NativeScript(native_script) => native_script.to_hash().to_plutus_data(), Script::PlutusV1Script(plutus_v1) => plutus_v1.to_hash().to_plutus_data(), Script::PlutusV2Script(plutus_v2) => plutus_v2.to_hash().to_plutus_data(), } } } impl ToPlutusData for TxOut { fn to_plutus_data(&self) -> PlutusData { match self { TxOut::V1(output) => match output { TransactionOutput::Legacy(legacy_output) => wrap_multiple_with_constr( 0, vec![ Address::from_bytes(&legacy_output.address) .unwrap() .to_plutus_data(), legacy_output.amount.to_plutus_data(), legacy_output.datum_hash.to_plutus_data(), ], ), TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr( 0, vec![ Address::from_bytes(&post_alonzo_output.address) .unwrap() .to_plutus_data(), post_alonzo_output.value.to_plutus_data(), match post_alonzo_output.datum_option { Some(DatumOption::Hash(hash)) => Some(hash).to_plutus_data(), _ => None::.to_plutus_data(), }, ], ), }, TxOut::V2(output) => match output { TransactionOutput::Legacy(legacy_output) => wrap_multiple_with_constr( 0, vec![ Address::from_bytes(&legacy_output.address) .unwrap() .to_plutus_data(), legacy_output.amount.to_plutus_data(), match legacy_output.datum_hash { Some(hash) => wrap_with_constr(1, hash.to_plutus_data()), _ => empty_constr(0), }, None::.to_plutus_data(), ], ), TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr( 0, vec![ Address::from_bytes(&post_alonzo_output.address) .unwrap() .to_plutus_data(), post_alonzo_output.value.to_plutus_data(), post_alonzo_output.datum_option.to_plutus_data(), post_alonzo_output.script_ref.to_plutus_data(), ], ), }, } } } impl ToPlutusData for StakeCredential { // Stake Credential needs to be wrapped inside another Constr, because we could have either a StakingHash or a StakingPtr // The current implementation of StakeCredential doesn't capture the credential of a Pointer address. // So a StakeCredential for a Pointer address needs to be converted separately fn to_plutus_data(&self) -> PlutusData { match self { StakeCredential::AddrKeyhash(addr_keyhas) => { wrap_with_constr(0, wrap_with_constr(0, addr_keyhas.to_plutus_data())) } StakeCredential::Scripthash(script_hash) => { wrap_with_constr(0, wrap_with_constr(1, script_hash.to_plutus_data())) } } } } impl ToPlutusData for Certificate { fn to_plutus_data(&self) -> PlutusData { match self { Certificate::StakeRegistration(stake_credential) => { wrap_with_constr(0, stake_credential.to_plutus_data()) } Certificate::StakeDeregistration(stake_credential) => { wrap_with_constr(1, stake_credential.to_plutus_data()) } Certificate::StakeDelegation(stake_credential, pool_keyhash) => { wrap_multiple_with_constr( 2, vec![ stake_credential.to_plutus_data(), pool_keyhash.to_plutus_data(), ], ) } Certificate::PoolRegistration { operator, vrf_keyhash, pledge: _, cost: _, margin: _, reward_account: _, pool_owners: _, relays: _, pool_metadata: _, } => wrap_multiple_with_constr( 3, vec![operator.to_plutus_data(), vrf_keyhash.to_plutus_data()], ), Certificate::PoolRetirement(pool_keyhash, epoch) => wrap_multiple_with_constr( 4, vec![pool_keyhash.to_plutus_data(), epoch.to_plutus_data()], ), Certificate::GenesisKeyDelegation(_, _, _) => empty_constr(5), Certificate::MoveInstantaneousRewardsCert(_) => empty_constr(6), } } } impl ToPlutusData for Redeemer { fn to_plutus_data(&self) -> PlutusData { self.data.clone() } } impl ToPlutusData for PlutusData { fn to_plutus_data(&self) -> PlutusData { self.clone() } } impl ToPlutusData for TimeRange { fn to_plutus_data(&self) -> PlutusData { match &self { TimeRange { lower_bound: Some(lower_bound), upper_bound: None, } => { wrap_multiple_with_constr( 0, vec![ // LowerBound wrap_multiple_with_constr( 0, vec![ // Finite wrap_with_constr(1, lower_bound.to_plutus_data()), // Closure true.to_plutus_data(), ], ), //UpperBound wrap_multiple_with_constr( 0, vec![ // PosInf empty_constr(2), // Closure true.to_plutus_data(), ], ), ], ) } TimeRange { lower_bound: None, upper_bound: Some(upper_bound), } => { wrap_multiple_with_constr( 0, vec![ // LowerBound wrap_multiple_with_constr( 0, vec![ // NegInf empty_constr(0), // Closure true.to_plutus_data(), ], ), //UpperBound wrap_multiple_with_constr( 0, vec![ // Finite wrap_with_constr(1, upper_bound.to_plutus_data()), // Closure true.to_plutus_data(), ], ), ], ) } TimeRange { lower_bound: Some(lower_bound), upper_bound: Some(upper_bound), } => { wrap_multiple_with_constr( 0, vec![ // LowerBound wrap_multiple_with_constr( 0, vec![ // Finite wrap_with_constr(1, lower_bound.to_plutus_data()), // Closure true.to_plutus_data(), ], ), //UpperBound wrap_multiple_with_constr( 0, vec![ // Finite wrap_with_constr(1, upper_bound.to_plutus_data()), // Closure false.to_plutus_data(), ], ), ], ) } TimeRange { lower_bound: None, upper_bound: None, } => { wrap_multiple_with_constr( 0, vec![ // LowerBound wrap_multiple_with_constr( 0, vec![ // NegInf empty_constr(0), // Closure true.to_plutus_data(), ], ), //UpperBound wrap_multiple_with_constr( 0, vec![ // PosInf empty_constr(2), // Closure true.to_plutus_data(), ], ), ], ) } } } } impl ToPlutusData for TxInInfo { fn to_plutus_data(&self) -> PlutusData { wrap_multiple_with_constr( 0, vec![ self.out_ref.to_plutus_data(), self.resolved.to_plutus_data(), ], ) } } impl ToPlutusData for ScriptPurpose { fn to_plutus_data(&self) -> PlutusData { match self { ScriptPurpose::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()), ScriptPurpose::Spending(out_ref) => wrap_with_constr(1, out_ref.to_plutus_data()), ScriptPurpose::Rewarding(stake_credential) => { wrap_with_constr(2, stake_credential.to_plutus_data()) } ScriptPurpose::Certifying(dcert) => wrap_with_constr(3, dcert.to_plutus_data()), } } } impl ToPlutusData for TxInfo { fn to_plutus_data(&self) -> PlutusData { match self { TxInfo::V1(tx_info) => wrap_multiple_with_constr( 0, vec![ tx_info.inputs.to_plutus_data(), tx_info.outputs.to_plutus_data(), tx_info.fee.to_plutus_data(), tx_info.mint.to_plutus_data(), tx_info.dcert.to_plutus_data(), tx_info.wdrl.to_plutus_data(), tx_info.valid_range.to_plutus_data(), tx_info.signatories.to_plutus_data(), tx_info.data.to_plutus_data(), wrap_with_constr(0, tx_info.id.to_plutus_data()), ], ), TxInfo::V2(tx_info) => wrap_multiple_with_constr( 0, vec![ tx_info.inputs.to_plutus_data(), 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.dcert.to_plutus_data(), tx_info.wdrl.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()), ], ), } } } impl ToPlutusData for ScriptContext { fn to_plutus_data(&self) -> PlutusData { wrap_multiple_with_constr( 0, vec![self.tx_info.to_plutus_data(), self.purpose.to_plutus_data()], ) } } impl ToPlutusData for bool { fn to_plutus_data(&self) -> PlutusData { match self { false => empty_constr(0), true => empty_constr(1), } } } #[derive(Debug, PartialEq, Clone)] pub struct ResolvedInput { input: TransactionInput, output: TransactionOutput, } #[derive(Debug, PartialEq, Clone)] pub struct TxInInfo { out_ref: TransactionInput, resolved: TxOut, } #[derive(Debug, PartialEq, Clone)] pub enum TxOut { V1(TransactionOutput), V2(TransactionOutput), } #[derive(Debug, PartialEq, Clone)] pub enum ScriptPurpose { Minting(PolicyId), Spending(TransactionInput), Rewarding(StakeCredential), Certifying(Certificate), } #[derive(Debug, PartialEq, Clone)] pub struct TxInfoV1 { inputs: MaybeIndefArray, outputs: MaybeIndefArray, fee: Value, mint: Mint, dcert: MaybeIndefArray, wdrl: MaybeIndefArray<(RewardAccount, Coin)>, valid_range: TimeRange, signatories: MaybeIndefArray, data: MaybeIndefArray<(DatumHash, PlutusData)>, id: Hash<32>, } #[derive(Debug, PartialEq, Clone)] pub struct TxInfoV2 { inputs: MaybeIndefArray, reference_inputs: MaybeIndefArray, outputs: MaybeIndefArray, fee: Value, mint: Mint, dcert: MaybeIndefArray, wdrl: Withdrawals, valid_range: TimeRange, signatories: MaybeIndefArray, redeemers: KeyValuePairs, data: KeyValuePairs, id: Hash<32>, } #[derive(Debug, PartialEq, Clone)] pub enum TxInfo { V1(TxInfoV1), V2(TxInfoV2), } #[derive(Debug, PartialEq, Clone)] pub struct ScriptContext { tx_info: TxInfo, purpose: ScriptPurpose, } //---- Time conversion: slot range => posix time range #[derive(Debug, PartialEq, Clone)] struct TimeRange { lower_bound: Option, upper_bound: Option, } pub struct SlotConfig { slot_length: u64, zero_time: u64, } fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> u64 { let ms_after_begin = slot * sc.slot_length; sc.zero_time + ms_after_begin } fn slot_range_to_posix_time_range(slot_range: TimeRange, sc: &SlotConfig) -> TimeRange { TimeRange { lower_bound: match slot_range.lower_bound { Some(lower_bound) => Some(slot_to_begin_posix_time(lower_bound, sc)), None => None, }, upper_bound: match slot_range.upper_bound { Some(upper_bound) => Some(slot_to_begin_posix_time(upper_bound, sc)), None => None, }, } } #[derive(Debug, PartialEq, Clone)] enum ScriptVersion { V1(PlutusV1Script), V2(PlutusV2Script), } #[derive(Debug, PartialEq, Clone)] enum ExecutionPurpose { WithDatum(ScriptVersion, PlutusData), // Spending NoDatum(ScriptVersion), // Minting, Wdrl, DCert } struct DataLookupTable { datum: HashMap, scripts: HashMap, } fn get_tx_in_info_v1( inputs: &MaybeIndefArray, utxos: &MaybeIndefArray, ) -> anyhow::Result> { let result = inputs .iter() .map(|input| { let utxo = match utxos.iter().find(|utxo| utxo.input == *input) { Some(u) => u, None => unreachable!("Resolved input not found."), }; let address = Address::from_bytes(match &utxo.output { TransactionOutput::Legacy(output) => output.address.as_ref(), TransactionOutput::PostAlonzo(output) => output.address.as_ref(), }) .unwrap(); match address { Address::Byron(_) => unreachable!("Byron addresses not supported in Plutus."), Address::Stake(_) => { unreachable!("This is impossible. A stake address cannot lock a UTxO.") } _ => {} } match &utxo.output { TransactionOutput::Legacy(_) => {} TransactionOutput::PostAlonzo(output) => { match output.datum_option { Some(DatumOption::Data(_)) => { unreachable!("Inline datum not allowed in PlutusV1.") } _ => {} }; if output.script_ref.is_some() { unreachable!("Reference scripts not allowed in PlutusV1.") } } } TxInInfo { out_ref: utxo.input.clone(), resolved: TxOut::V1(utxo.output.clone()), } }) .collect::>(); Ok(MaybeIndefArray::Indef(result)) } fn get_tx_in_info_v2( inputs: &MaybeIndefArray, utxos: &MaybeIndefArray, ) -> anyhow::Result> { let result = inputs .iter() .map(|input| { let utxo = match utxos.iter().find(|utxo| utxo.input == *input) { Some(u) => u, None => unreachable!("Resolved input not found."), }; let address = Address::from_bytes(match &utxo.output { TransactionOutput::Legacy(output) => output.address.as_ref(), TransactionOutput::PostAlonzo(output) => output.address.as_ref(), }) .unwrap(); match address { Address::Byron(_) => unreachable!("Byron addresses not supported in Plutus."), Address::Stake(_) => { unreachable!("This is impossible. A stake address cannot lock a UTxO.") } _ => {} } TxInInfo { out_ref: utxo.input.clone(), resolved: TxOut::V2(utxo.output.clone()), } }) .collect::>(); Ok(MaybeIndefArray::Indef(result)) } fn get_script_purpose( redeemer: &Redeemer, inputs: &MaybeIndefArray, mint: &Option, dcert: &Option>, wdrl: &Option, ) -> anyhow::Result { // sorting according to specs section 4.1: https://hydra.iohk.io/build/18583827/download/1/alonzo-changes.pdf let tag = redeemer.tag.clone(); let index = redeemer.index; match tag { RedeemerTag::Mint => { // sort lexical by policy id let mut policy_ids = mint .as_ref() .unwrap_or(&KeyValuePairs::Indef(vec![])) .iter() .map(|(policy_id, _)| policy_id.clone()) .collect::>(); policy_ids.sort(); match policy_ids.get(index as usize) { Some(policy_id) => Ok(ScriptPurpose::Minting(policy_id.clone())), None => unreachable!("Script purpose not found for redeemer."), } } RedeemerTag::Spend => { // sort lexical by tx_hash and index let mut inputs = inputs .iter() .map(|input| input.clone()) .collect::>(); // is this correct? Does this sort lexical from low to high? maybe get Ordering into pallas for TransactionInput? inputs.sort_by( |i_a, i_b| match i_a.transaction_id.cmp(&i_b.transaction_id) { std::cmp::Ordering::Less => std::cmp::Ordering::Less, std::cmp::Ordering::Equal => i_a.index.cmp(&i_b.index), std::cmp::Ordering::Greater => std::cmp::Ordering::Greater, }, ); match inputs.get(index as usize) { Some(input) => Ok(ScriptPurpose::Spending(input.clone())), None => unreachable!("Script purpose not found for redeemer."), } } RedeemerTag::Reward => { // sort lexical by reward account let mut reward_accounts = wdrl .as_ref() .unwrap_or(&KeyValuePairs::Indef(vec![])) .iter() .map(|(policy_id, _)| policy_id.clone()) .collect::>(); reward_accounts.sort(); let reward_account = match reward_accounts.get(index as usize) { Some(ra) => ra.clone(), None => unreachable!("Script purpose not found for redeemer."), }; let addresss = Address::from_bytes(&reward_account)?; let credential = match addresss { Address::Stake(stake_address) => match stake_address.payload() { StakePayload::Script(script_hash) => { StakeCredential::Scripthash(script_hash.clone()) } StakePayload::Stake(_) => { unreachable!( "This is impossible. A key hash cannot be the hash of a script." ); } }, _ => unreachable!( "This is impossible. Only shelley reward addresses can be a part of withdrawals." ), }; Ok(ScriptPurpose::Rewarding(credential)) } RedeemerTag::Cert => { // sort by order given in the tx (just take it as it is basically) match dcert .as_ref() .unwrap_or(&MaybeIndefArray::Indef(vec![])) .get(index as usize) { Some(cert) => Ok(ScriptPurpose::Certifying(cert.clone())), None => unreachable!("Script purpose not found for redeemer."), } } } } fn get_tx_info_v1( tx: &MintedTx, utxos: &MaybeIndefArray, slot_config: &SlotConfig, ) -> anyhow::Result { let body = tx.transaction_body.clone(); if body.reference_inputs.is_some() { unreachable!("Reference inputs not allowed in PlutusV1.") } let inputs = get_tx_in_info_v1(&body.inputs, &utxos)?; let outputs = MaybeIndefArray::Indef( body.outputs .iter() .map(|output| TxOut::V1(output.clone())) .collect(), ); let fee = Value::Coin(AnyUInt::U64(body.fee)); let mint = body.mint.clone().unwrap_or(KeyValuePairs::Indef(vec![])); let dcert = body .certificates .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let wdrl = MaybeIndefArray::Indef( body.withdrawals .clone() .unwrap_or(KeyValuePairs::Indef(vec![])) .deref() .clone(), ); let valid_range = slot_range_to_posix_time_range( TimeRange { lower_bound: body.validity_interval_start, upper_bound: body.ttl, }, &slot_config, ); let signatories = body .required_signers .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let data = MaybeIndefArray::Indef( tx.transaction_witness_set .plutus_data .as_ref() .unwrap_or(&MaybeIndefArray::Indef(vec![])) .iter() .map(|d| (d.to_hash(), d.clone())) .collect(), ); let id = tx.transaction_body.to_hash(); Ok(TxInfo::V1(TxInfoV1 { inputs, outputs, fee, mint, dcert, wdrl, valid_range, signatories, data, id, })) } fn get_tx_info_v2( tx: &MintedTx, utxos: &MaybeIndefArray, slot_config: &SlotConfig, ) -> anyhow::Result { let body = tx.transaction_body.clone(); let inputs = get_tx_in_info_v2(&body.inputs, &utxos)?; let reference_inputs = get_tx_in_info_v2( &body .reference_inputs .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])), &utxos, )?; let outputs = MaybeIndefArray::Indef( body.outputs .iter() .map(|output| TxOut::V2(output.clone())) .collect(), ); let fee = Value::Coin(AnyUInt::U64(body.fee)); let mint = body.mint.clone().unwrap_or(KeyValuePairs::Indef(vec![])); let dcert = body .certificates .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let wdrl = body .withdrawals .clone() .unwrap_or(KeyValuePairs::Indef(vec![])); let valid_range = slot_range_to_posix_time_range( TimeRange { lower_bound: body.validity_interval_start, upper_bound: body.ttl, }, &slot_config, ); let signatories = body .required_signers .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let redeemers = KeyValuePairs::Indef( tx.transaction_witness_set .redeemer .as_ref() .unwrap_or(&MaybeIndefArray::Indef(vec![])) .iter() .map(|r| { ( get_script_purpose( &r, &tx.transaction_body.inputs, &tx.transaction_body.mint, &tx.transaction_body.certificates, &tx.transaction_body.withdrawals, ) .unwrap(), r.clone(), ) }) .collect(), ); let data = KeyValuePairs::Indef( tx.transaction_witness_set .plutus_data .as_ref() .unwrap_or(&MaybeIndefArray::Indef(vec![])) .iter() .map(|d| (d.to_hash(), d.clone())) .collect(), ); let id = tx.transaction_body.to_hash(); Ok(TxInfo::V2(TxInfoV2 { inputs, reference_inputs, outputs, fee, mint, dcert, wdrl, valid_range, signatories, redeemers, data, id, })) } fn get_execution_purpose( utxos: &MaybeIndefArray, script_purpose: &ScriptPurpose, lookup_table: &DataLookupTable, ) -> ExecutionPurpose { match script_purpose { ScriptPurpose::Minting(policy_id) => { let policy_id_array: [u8; 28] = policy_id.to_vec().try_into().unwrap(); let hash = Hash::from(policy_id_array); let script = match lookup_table.scripts.get(&hash) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; ExecutionPurpose::NoDatum(script.clone()) } ScriptPurpose::Spending(out_ref) => { let utxo = utxos.iter().find(|utxo| utxo.input == *out_ref).unwrap(); match &utxo.output { TransactionOutput::Legacy(output) => { let address = Address::from_bytes(&output.address).unwrap(); match address { Address::Shelley(shelley_address) => { let script = match lookup_table .scripts .get(&shelley_address.payment().as_hash()) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; let datum = match lookup_table.datum.get(&output.datum_hash.unwrap_or_else(|| unreachable!("Missing datum hash in input."))) { Some(d) => d.clone(), None => unreachable!("Missing datum in witness set.") }; ExecutionPurpose::WithDatum(script.clone(), datum.clone()) } _ => unreachable!( "This is impossible. Only shelley addresses can contain a script hash." ), } } TransactionOutput::PostAlonzo(output) => { let address = Address::from_bytes(&output.address).unwrap(); match address { Address::Shelley(shelley_address) => { let script = match lookup_table .scripts .get(&shelley_address.payment().as_hash()) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; let datum = match &output.datum_option { Some(DatumOption::Hash(hash)) => { lookup_table.datum.get(&hash).unwrap().clone() } Some(DatumOption::Data(data)) => data.0.clone(), _ => unreachable!( "Missing datum hash or inline datum in input."), }; ExecutionPurpose::WithDatum(script.clone(), datum) } _ => unreachable!( "This is impossible. Only shelley addresses can contain a script hash." ), } } } } ScriptPurpose::Rewarding(stake_credential) => { let script_hash = match stake_credential { StakeCredential::Scripthash(hash) => hash.clone(), _ => unreachable!("This is impossible. A key hash cannot be the hash of a script."), }; let script = match lookup_table.scripts.get(&script_hash) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; ExecutionPurpose::NoDatum(script) } ScriptPurpose::Certifying(cert) => match cert { // StakeRegistration doesn't require a witness from a stake key/script. So I assume it doesn't need to be handled in Plutus either? Certificate::StakeDeregistration(stake_credential) => { let script_hash = match stake_credential { StakeCredential::Scripthash(hash) => hash.clone(), _ => unreachable!( "This is impossible. A key hash cannot be the hash of a script." ), }; let script = match lookup_table.scripts.get(&script_hash) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; ExecutionPurpose::NoDatum(script) } Certificate::StakeDelegation(stake_credential, _) => { let script_hash = match stake_credential { StakeCredential::Scripthash(hash) => hash.clone(), _ => unreachable!( "This is impossible. A key hash cannot be the hash of a script." ), }; let script = match lookup_table.scripts.get(&script_hash) { Some(s) => s.clone(), None => unreachable!("Missing required scripts.") }; ExecutionPurpose::NoDatum(script) } _ => unreachable!("This is impossible. Only stake deregistration and stake delegation are valid script purposes."), }, } } fn get_script_and_datum_lookup_table( tx: &MintedTx, utxos: &MaybeIndefArray, ) -> DataLookupTable { let mut datum = HashMap::new(); let mut scripts = HashMap::new(); // discovery in witness set let plutus_data_witnesses = tx .transaction_witness_set .plutus_data .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let scripts_v1_witnesses = tx .transaction_witness_set .plutus_v1_script .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); let scripts_v2_witnesses = tx .transaction_witness_set .plutus_v2_script .clone() .unwrap_or(MaybeIndefArray::Indef(vec![])); for plutus_data in plutus_data_witnesses.iter() { datum.insert(plutus_data.to_hash(), plutus_data.clone()); } for script in scripts_v1_witnesses.iter() { // scripts.insert(script.to_hash(), ScriptVersion::PlutusV1(script.clone())); // TODO: fix hashing bug in pallas let mut prefixed_script: Vec = vec![0x01]; prefixed_script.extend(script.0.iter()); let hash = Hasher::<224>::hash(&prefixed_script); scripts.insert(hash, ScriptVersion::V1(script.clone())); } for script in scripts_v2_witnesses.iter() { // scripts.insert(script.to_hash(), ScriptVersion::PlutusV2(script.clone())); // TODO: fix hashing bug in pallas let mut prefixed_script: Vec = vec![0x02]; prefixed_script.extend(script.0.iter()); let hash = Hasher::<224>::hash(&prefixed_script); scripts.insert(hash, ScriptVersion::V2(script.clone())); } // discovery in utxos (script ref) for utxo in utxos.iter() { match &utxo.output { TransactionOutput::Legacy(_) => {} TransactionOutput::PostAlonzo(output) => match &output.script_ref { Some(script) => match &script.0 { Script::PlutusV1Script(v1) => { // scripts.insert(v1.to_hash(), ScriptVersion::PlutusV1(v1.clone())); // TODO: fix hashing bug in pallas let mut prefixed_script: Vec = vec![0x01]; prefixed_script.extend(v1.0.iter()); let hash = Hasher::<224>::hash(&prefixed_script); scripts.insert(hash, ScriptVersion::V1(v1.clone())); } Script::PlutusV2Script(v2) => { // scripts.insert(v2.to_hash(), ScriptVersion::PlutusV2(v2.clone())); // TODO: fix hashing bug in pallas let mut prefixed_script: Vec = vec![0x02]; prefixed_script.extend(v2.0.iter()); let hash = Hasher::<224>::hash(&prefixed_script); scripts.insert(hash, ScriptVersion::V2(v2.clone())); } _ => {} }, _ => {} }, } } DataLookupTable { datum, scripts } } fn eval_redeemer( tx: &MintedTx, utxos: &MaybeIndefArray, slot_config: &SlotConfig, redeemer: &Redeemer, lookup_table: &DataLookupTable, ) -> anyhow::Result { let purpose = get_script_purpose( redeemer, &tx.transaction_body.inputs, &tx.transaction_body.mint, &tx.transaction_body.certificates, &tx.transaction_body.withdrawals, )?; let execution_purpose: ExecutionPurpose = get_execution_purpose(utxos, &purpose, lookup_table); match execution_purpose { ExecutionPurpose::WithDatum(script_version, datum) => match script_version { ScriptVersion::V1(script) => { let tx_info = get_tx_info_v1(tx, utxos, slot_config)?; let script_context = ScriptContext { tx_info, purpose }; let program: Program = { let mut buffer = Vec::new(); let prog = Program::::from_cbor(&script.0, &mut buffer)?; prog.into() }; let result = program .apply_data(datum.clone()) .apply_data(redeemer.data.clone()) .apply_data(script_context.to_plutus_data()) .eval(); match result.0 { Ok(_) => {} Err(err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message } let new_redeemer = Redeemer { tag: redeemer.tag.clone(), index: redeemer.index, data: redeemer.data.clone(), ex_units: ExUnits { mem: (ExBudget::default().mem - result.1.mem) as u32, steps: (ExBudget::default().cpu - result.1.cpu) as u64, }, }; Ok(new_redeemer) } ScriptVersion::V2(script) => { let tx_info = get_tx_info_v2(tx, utxos, slot_config)?; let script_context = ScriptContext { tx_info, purpose }; let program: Program = { let mut buffer = Vec::new(); let prog = Program::::from_cbor(&script.0, &mut buffer)?; prog.into() }; let result = program .apply_data(datum.clone()) .apply_data(redeemer.data.clone()) .apply_data(script_context.to_plutus_data()) .eval(); match result.0 { Ok(_) => {} Err(err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message } let new_redeemer = Redeemer { tag: redeemer.tag.clone(), index: redeemer.index, data: redeemer.data.clone(), ex_units: ExUnits { mem: (ExBudget::default().mem - result.1.mem) as u32, steps: (ExBudget::default().cpu - result.1.cpu) as u64, }, }; Ok(new_redeemer) } }, ExecutionPurpose::NoDatum(script_version) => match script_version { ScriptVersion::V1(script) => { let tx_info = get_tx_info_v1(tx, utxos, slot_config)?; let script_context = ScriptContext { tx_info, purpose }; let program: Program = { let mut buffer = Vec::new(); let prog = Program::::from_cbor(&script.0, &mut buffer)?; prog.into() }; let result = program .apply_data(redeemer.data.clone()) .apply_data(script_context.to_plutus_data()) .eval(); match result.0 { Ok(_) => {} Err(err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message } let new_redeemer = Redeemer { tag: redeemer.tag.clone(), index: redeemer.index, data: redeemer.data.clone(), ex_units: ExUnits { mem: (ExBudget::default().mem - result.1.mem) as u32, steps: (ExBudget::default().cpu - result.1.cpu) as u64, }, }; Ok(new_redeemer) } ScriptVersion::V2(script) => { let tx_info = get_tx_info_v2(tx, utxos, slot_config)?; let script_context = ScriptContext { tx_info, purpose }; let program: Program = { let mut buffer = Vec::new(); let prog = Program::::from_cbor(&script.0, &mut buffer)?; prog.into() }; let result = program .apply_data(redeemer.data.clone()) .apply_data(script_context.to_plutus_data()) .eval(); match result.0 { Ok(_) => {} Err(err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message } let new_redeemer = Redeemer { tag: redeemer.tag.clone(), index: redeemer.index, data: redeemer.data.clone(), ex_units: ExUnits { mem: (ExBudget::default().mem - result.1.mem) as u32, steps: (ExBudget::default().cpu - result.1.cpu) as u64, }, }; Ok(new_redeemer) } }, } } fn eval_tx( tx: &MintedTx, utxos: &MaybeIndefArray, //TODO: costMdls slot_config: &SlotConfig, ) -> anyhow::Result> { let redeemers = tx.transaction_witness_set.redeemer.as_ref(); let lookup_table = get_script_and_datum_lookup_table(tx, utxos); match redeemers { Some(rs) => { let mut collected_redeemers = vec![]; for redeemer in rs.iter() { collected_redeemers.push(eval_redeemer( tx, utxos, slot_config, &redeemer, &lookup_table, )?) } Ok(MaybeIndefArray::Indef(collected_redeemers)) } None => Ok(MaybeIndefArray::Indef(vec![])), } } #[cfg(test)] mod tests { use pallas_codec::utils::MaybeIndefArray; use pallas_primitives::{ babbage::{TransactionInput, TransactionOutput}, Fragment, ToHash, }; use pallas_traverse::{Era, MultiEraTx}; use uplc::PlutusData; use super::{eval_tx, ResolvedInput, SlotConfig, TxInInfo}; #[test] fn test_eval() { /* PlutusV2 {-# INLINEABLE mintTestValidator #-} mintTestValidator :: () -> Api.ScriptContext -> Bool mintTestValidator _ ctx = Api.txInfoFee txInfo == Api.txInfoFee txInfo && (case Api.txInfoSignatories txInfo of [] -> True) where txInfo :: Api.TxInfo txInfo = Api.scriptContextTxInfo ctx */ let tx_bytes = hex::decode("84a80081825820975c17a4fed0051be622328efa548e206657d2b65a19224bf6ff8132571e6a5002018282581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f41f0a1581cc4f241450001af08f3ddbaf9335db79883cbcd81071b8e3508de3055a1400a82581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a0084192f021a00053b6109a1581cc4f241450001af08f3ddbaf9335db79883cbcd81071b8e3508de3055a1400a0b5820b4f96b0acec8beff2adededa8ba317bcac92174f0f65ccefe569b9a6aac7375a0d818258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d011082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af0cdfa2111a0007d912a3008182582031ae74f8058527afb305d7495b10a99422d9337fc199e1f28044f2c477a0f9465840b8b97b7c3b4e19ecfc2fcd9884ee53a35887ee6e4d36901b9ecbac3fe032d7e8a4358305afa573a86396e378255651ed03501906e9def450e588d4bb36f42a050581840100d87980821a000b68081a0cf3a5bf06815909b25909af010000323322323232323232323232323232323232323232332232323232323232323233223232223232533533223233025323233355300f1200135028502623500122333553012120013502b50292350012233350012330314800000488cc0c80080048cc0c400520000013355300e1200123500122335501c0023335001233553012120012350012233550200023550140010012233355500f0150020012335530121200123500122335502000235501300100133355500a01000200130105002300f5001533532350012222222222220045001102a2216135001220023333573466e1cd55ce9baa0044800080808c98c8080cd5ce01081000f1999ab9a3370e6aae7540092000233221233001003002323232323232323232323232323333573466e1cd55cea8062400046666666666664444444444442466666666666600201a01801601401201000e00c00a00800600466a03803a6ae854030cd4070074d5d0a80599a80e00f1aba1500a3335502075ca03e6ae854024ccd54081d7280f9aba1500833501c02835742a00e666aa040052eb4d5d0a8031919191999ab9a3370e6aae75400920002332212330010030023232323333573466e1cd55cea8012400046644246600200600466a066eb4d5d0a801181a1aba135744a004464c6406c66ae700dc0d80d04d55cf280089baa00135742a0046464646666ae68cdc39aab9d5002480008cc8848cc00400c008cd40cdd69aba150023034357426ae8940088c98c80d8cd5ce01b81b01a09aab9e5001137540026ae84d5d1280111931901919ab9c033032030135573ca00226ea8004d5d0a80299a80e3ae35742a008666aa04004a40026ae85400cccd54081d710009aba150023027357426ae8940088c98c80b8cd5ce01781701609aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226aae7940044dd50009aba150023017357426ae8940088c98c8080cd5ce01081000f080f89931900f99ab9c4901035054350001f135573ca00226ea8004444888ccd54c010480054040cd54c01c480048d400488cd54054008d54024004ccd54c0104800488d4008894cd4ccd54c03048004c8cd409c88ccd400c88008008004d40048800448cc004894cd400840b040040a48d400488cc028008014018400c4cd405001000d4044004cd54c01c480048d400488c8cd5405800cc004014c8004d540a4894cd40044d5402800c884d4008894cd4cc03000802044888cc0080280104c01800c008c8004d5408888448894cd40044008884cc014008ccd54c01c480040140100044484888c00c0104484888c004010c8004d5407c8844894cd400454038884cd403cc010008cd54c01848004010004c8004d5407888448894cd40044d400c88004884ccd401488008c010008ccd54c01c4800401401000488ccd5cd19b8f00200101e01d2350012222222222220091232230023758002640026aa038446666aae7c004940288cd4024c010d5d080118019aba2002015232323333573466e1cd55cea80124000466442466002006004601a6ae854008c014d5d09aba2500223263201533573802c02a02626aae7940044dd50009191919191999ab9a3370e6aae75401120002333322221233330010050040030023232323333573466e1cd55cea80124000466442466002006004602c6ae854008cd4040054d5d09aba2500223263201a33573803603403026aae7940044dd50009aba150043335500875ca00e6ae85400cc8c8c8cccd5cd19b875001480108c84888c008010d5d09aab9e500323333573466e1d4009200223212223001004375c6ae84d55cf280211999ab9a3370ea00690001091100191931900e19ab9c01d01c01a019018135573aa00226ea8004d5d0a80119a8063ae357426ae8940088c98c8058cd5ce00b80b00a09aba25001135744a00226aae7940044dd5000899aa800bae75a224464460046eac004c8004d5406488c8cccd55cf80112804119a80399aa80498031aab9d5002300535573ca00460086ae8800c04c4d5d08008891001091091198008020018891091980080180109119191999ab9a3370ea0029000119091180100198029aba135573ca00646666ae68cdc3a801240044244002464c6402066ae700440400380344d55cea80089baa001232323333573466e1d400520062321222230040053007357426aae79400c8cccd5cd19b875002480108c848888c008014c024d5d09aab9e500423333573466e1d400d20022321222230010053007357426aae7940148cccd5cd19b875004480008c848888c00c014dd71aba135573ca00c464c6402066ae7004404003803403002c4d55cea80089baa001232323333573466e1cd55cea80124000466442466002006004600a6ae854008dd69aba135744a004464c6401866ae700340300284d55cf280089baa0012323333573466e1cd55cea800a400046eb8d5d09aab9e500223263200a33573801601401026ea80048c8c8c8c8c8cccd5cd19b8750014803084888888800c8cccd5cd19b875002480288488888880108cccd5cd19b875003480208cc8848888888cc004024020dd71aba15005375a6ae84d5d1280291999ab9a3370ea00890031199109111111198010048041bae35742a00e6eb8d5d09aba2500723333573466e1d40152004233221222222233006009008300c35742a0126eb8d5d09aba2500923333573466e1d40192002232122222223007008300d357426aae79402c8cccd5cd19b875007480008c848888888c014020c038d5d09aab9e500c23263201333573802802602202001e01c01a01801626aae7540104d55cf280189aab9e5002135573ca00226ea80048c8c8c8c8cccd5cd19b875001480088ccc888488ccc00401401000cdd69aba15004375a6ae85400cdd69aba135744a00646666ae68cdc3a80124000464244600400660106ae84d55cf280311931900619ab9c00d00c00a009135573aa00626ae8940044d55cf280089baa001232323333573466e1d400520022321223001003375c6ae84d55cf280191999ab9a3370ea004900011909118010019bae357426aae7940108c98c8024cd5ce00500480380309aab9d50011375400224464646666ae68cdc3a800a40084244400246666ae68cdc3a8012400446424446006008600c6ae84d55cf280211999ab9a3370ea00690001091100111931900519ab9c00b00a008007006135573aa00226ea80048c8cccd5cd19b8750014800880348cccd5cd19b8750024800080348c98c8018cd5ce00380300200189aab9d37540029309000a4810350543100112330010020072253350021001100612335002223335003220020020013500122001122123300100300222333573466e1c00800401000c488008488004448c8c00400488cc00cc008008005f5f6").unwrap(); let raw_inputs = hex::decode("84825820b16778c9cf065d9efeefe37ec269b4fc5107ecdbd0dd6bf3274b224165c2edd9008258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d01825820975c17a4fed0051be622328efa548e206657d2b65a19224bf6ff8132571e6a500282582018f86700660fc88d0370a8f95ea58f75507e6b27a18a17925ad3b1777eb0d77600").unwrap(); let raw_outputs = hex::decode("8482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f8548a1581c15be994a64bdb79dde7fe080d8e7ff81b33a9e4860e9ee0d857a8e85a144576177610182581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af14b8b482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a0098968082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a00acd8c6").unwrap(); let inputs = MaybeIndefArray::::decode_fragment(&raw_inputs).unwrap(); let outputs = MaybeIndefArray::::decode_fragment(&raw_outputs).unwrap(); let utxos: MaybeIndefArray = MaybeIndefArray::Indef( inputs .iter() .zip(outputs.iter()) .map(|(input, output)| ResolvedInput { input: input.clone(), output: output.clone(), }) .collect(), ); let slot_config = SlotConfig { zero_time: 1660003200000, // Preview network slot_length: 1000, }; let multi_era_tx = MultiEraTx::decode(Era::Babbage, &tx_bytes) .or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes)) .unwrap(); match multi_era_tx { MultiEraTx::Babbage(tx) => { let redeemers = eval_tx(&tx, &utxos, &slot_config).unwrap(); println!("{:?}", redeemers.len()); } _ => unreachable!(), }; } #[test] fn test_eval_1() { /* PlutusV2 {-# INLINEABLE mintTestValidator #-} mintTestValidator :: () -> Api.ScriptContext -> Bool mintTestValidator _ ctx = Api.txInfoFee txInfo == Api.txInfoFee txInfo where txInfo :: Api.TxInfo txInfo = Api.scriptContextTxInfo ctx */ let tx_bytes = hex::decode("84a800818258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d01018282581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f41f0a1581c88c9dfd60601e22509d58b904c2730fe2bdef6a52a41a6f376b0ba94a1400a82581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af004152021a0005357209a1581c88c9dfd60601e22509d58b904c2730fe2bdef6a52a41a6f376b0ba94a1400a0b5820ff1a62ad8cb2d73ffb8687471e2c99b48bf3b067966a7ea9285f95adcee708a20d818258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d011082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af0ce889111a0007d02ba3008182582031ae74f8058527afb305d7495b10a99422d9337fc199e1f28044f2c477a0f9465840b3dde25e3a2b825d3f120955211e722cc4a7c65fa67697076f2725a2ed0adec0d4bfc742934fe7c29d475bb0630aed1b1cdcf5fac9e06d84976455a661b0dc080581840100d87980821a000b46701a0cd5772f068159099a59099701000032332232323232323232323232323232323232323322323232323232323232332232322232325335332232323233355300f1200135027502623500122333553012120013502a50292350012233350012330304800000488cc0c40080048cc0c000520000013355300e1200123500122335501c0023335001233553012120012350012233550200023550140010012233355500f0150020012335530121200123500122335502000235501300100133355500a01000200130105002300f5001135001220023333573466e1cd55ce9baa0044800080808c98c8080cd5ce01081000f1999ab9a3370e6aae7540092000233221233001003002323232323232323232323232323333573466e1cd55cea8062400046666666666664444444444442466666666666600201a01801601401201000e00c00a00800600466a03803a6ae854030cd4070074d5d0a80599a80e00f1aba1500a3335502075ca03e6ae854024ccd54081d7280f9aba1500833501c02835742a00e666aa040052eb4d5d0a8031919191999ab9a3370e6aae75400920002332212330010030023232323333573466e1cd55cea8012400046644246600200600466a066eb4d5d0a801181a1aba135744a004464c6406c66ae700dc0d80d04d55cf280089baa00135742a0046464646666ae68cdc39aab9d5002480008cc8848cc00400c008cd40cdd69aba150023034357426ae8940088c98c80d8cd5ce01b81b01a09aab9e5001137540026ae84d5d1280111931901919ab9c033032030135573ca00226ea8004d5d0a80299a80e3ae35742a008666aa04004a40026ae85400cccd54081d710009aba150023027357426ae8940088c98c80b8cd5ce01781701609aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226aae7940044dd50009aba150023017357426ae8940088c98c8080cd5ce01081000f080f89931900f99ab9c491035054350001f135573ca00226ea8004444888ccd54c010480054040cd54c01c480048d400488cd54054008d54024004ccd54c0104800488d4008894cd4ccd54c03048004c8cd409888ccd400c88008008004d40048800448cc004894cd400840ac40040a08d400488cc028008014018400c4cd405001000d4044004cd54c01c480048d400488c8cd5405800cc004014c8004d540a0894cd40044d5402800c884d4008894cd4cc03000802044888cc0080280104c01800c008c8004d5408488448894cd40044008884cc014008ccd54c01c480040140100044484888c00c0104484888c004010c8004d540788844894cd400454038884cd403cc010008cd54c01848004010004c8004d5407488448894cd40044d400c88004884ccd401488008c010008ccd54c01c4800401401000488ccd5cd19b8f00200101d01c2350012222222222220091232230023758002640026aa036446666aae7c004940288cd4024c010d5d080118019aba2002015232323333573466e1cd55cea80124000466442466002006004601a6ae854008c014d5d09aba2500223263201533573802c02a02626aae7940044dd50009191919191999ab9a3370e6aae75401120002333322221233330010050040030023232323333573466e1cd55cea80124000466442466002006004602c6ae854008cd4040054d5d09aba2500223263201a33573803603403026aae7940044dd50009aba150043335500875ca00e6ae85400cc8c8c8cccd5cd19b875001480108c84888c008010d5d09aab9e500323333573466e1d4009200223212223001004375c6ae84d55cf280211999ab9a3370ea00690001091100191931900e19ab9c01d01c01a019018135573aa00226ea8004d5d0a80119a8063ae357426ae8940088c98c8058cd5ce00b80b00a09aba25001135744a00226aae7940044dd5000899aa800bae75a224464460046eac004c8004d5406088c8cccd55cf80112804119a80399aa80498031aab9d5002300535573ca00460086ae8800c04c4d5d08008891001091091198008020018891091980080180109119191999ab9a3370ea0029000119091180100198029aba135573ca00646666ae68cdc3a801240044244002464c6402066ae700440400380344d55cea80089baa001232323333573466e1d400520062321222230040053007357426aae79400c8cccd5cd19b875002480108c848888c008014c024d5d09aab9e500423333573466e1d400d20022321222230010053007357426aae7940148cccd5cd19b875004480008c848888c00c014dd71aba135573ca00c464c6402066ae7004404003803403002c4d55cea80089baa001232323333573466e1cd55cea80124000466442466002006004600a6ae854008dd69aba135744a004464c6401866ae700340300284d55cf280089baa0012323333573466e1cd55cea800a400046eb8d5d09aab9e500223263200a33573801601401026ea80048c8c8c8c8c8cccd5cd19b8750014803084888888800c8cccd5cd19b875002480288488888880108cccd5cd19b875003480208cc8848888888cc004024020dd71aba15005375a6ae84d5d1280291999ab9a3370ea00890031199109111111198010048041bae35742a00e6eb8d5d09aba2500723333573466e1d40152004233221222222233006009008300c35742a0126eb8d5d09aba2500923333573466e1d40192002232122222223007008300d357426aae79402c8cccd5cd19b875007480008c848888888c014020c038d5d09aab9e500c23263201333573802802602202001e01c01a01801626aae7540104d55cf280189aab9e5002135573ca00226ea80048c8c8c8c8cccd5cd19b875001480088ccc888488ccc00401401000cdd69aba15004375a6ae85400cdd69aba135744a00646666ae68cdc3a80124000464244600400660106ae84d55cf280311931900619ab9c00d00c00a009135573aa00626ae8940044d55cf280089baa001232323333573466e1d400520022321223001003375c6ae84d55cf280191999ab9a3370ea004900011909118010019bae357426aae7940108c98c8024cd5ce00500480380309aab9d50011375400224464646666ae68cdc3a800a40084244400246666ae68cdc3a8012400446424446006008600c6ae84d55cf280211999ab9a3370ea00690001091100111931900519ab9c00b00a008007006135573aa00226ea80048c8cccd5cd19b8750014800880308cccd5cd19b8750024800080308c98c8018cd5ce00380300200189aab9d37540029309000a4810350543100112330012253350021001100700612335002223335003220020020013500122001122123300100300222333573466e1c00800401000c488008488004448c8c00400488cc00cc0080080041f5f6").unwrap(); let raw_inputs = hex::decode("84825820b16778c9cf065d9efeefe37ec269b4fc5107ecdbd0dd6bf3274b224165c2edd9008258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d01825820975c17a4fed0051be622328efa548e206657d2b65a19224bf6ff8132571e6a500282582018f86700660fc88d0370a8f95ea58f75507e6b27a18a17925ad3b1777eb0d77600").unwrap(); let raw_outputs = hex::decode("8482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f8548a1581c15be994a64bdb79dde7fe080d8e7ff81b33a9e4860e9ee0d857a8e85a144576177610182581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af14b8b482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a0098968082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a00acd8c6").unwrap(); let inputs = MaybeIndefArray::::decode_fragment(&raw_inputs).unwrap(); let outputs = MaybeIndefArray::::decode_fragment(&raw_outputs).unwrap(); let utxos: MaybeIndefArray = MaybeIndefArray::Indef( inputs .iter() .zip(outputs.iter()) .map(|(input, output)| ResolvedInput { input: input.clone(), output: output.clone(), }) .collect(), ); let slot_config = SlotConfig { zero_time: 1660003200000, // Preview network slot_length: 1000, }; let multi_era_tx = MultiEraTx::decode(Era::Babbage, &tx_bytes) .or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes)) .unwrap(); match multi_era_tx { MultiEraTx::Babbage(tx) => { let redeemers = eval_tx(&tx, &utxos, &slot_config).unwrap(); println!("{:?}", redeemers.len()); } _ => unreachable!(), }; } #[test] fn test_eval_2() { /* Plutus V1 {-# INLINEABLE mintTestValidator #-} mintTestValidator :: () -> Api.ScriptContext -> Bool mintTestValidator _ ctx = Api.txInfoFee txInfo == Api.txInfoFee txInfo where txInfo :: Api.TxInfo txInfo = Api.scriptContextTxInfo ctx */ let tx_bytes = hex::decode("84a800818258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d01018282581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f41f0a1581ccba7bc9e83499376b6ad49304157778dba7c14bd748e4fd31792a930a1400a82581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af006ac1021a00050c0309a1581ccba7bc9e83499376b6ad49304157778dba7c14bd748e4fd31792a930a1400a0b5820eb3b868ec2b33dffaf5d5481703ed00870333812b96e0f75ae89fd150b4744300d818258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d011082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af0d26af111a00079205a3008182582031ae74f8058527afb305d7495b10a99422d9337fc199e1f28044f2c477a0f94658405a2dd70be89483bd6291a018c6ca91328dad37e092fdeab2eea14685004878da5f1e5962f35d771498bf54e79be3dcf922ea93b46a1356960dcf0bfd80a91b0b038159094259093f010000323322323232323232323232323232323232323233223232323232323232332232322232325335332232323233355300f12001350265025235001223335530121200135029502823500122333500123302f4800000488cc0c00080048cc0bc00520000013355300e120012350012233550250023335001233553012120012350012233550290023550140010012233355500f0150020012335530121200123500122335502900235501300100133355500a01000200130105002300f5001135001220023333573466e1cd55ce9baa00448000807c8c98c8078cd5ce01000f80e1999ab9a3370e6aae754009200023322123300100300232323232323232323232323333573466e1cd55cea8052400046666666666444444444424666666666600201601401201000e00c00a00800600466a034464646666ae68cdc39aab9d5002480008cc8848cc00400c008c094d5d0a801180f9aba135744a004464c6405c66ae700c00bc0b04d55cf280089baa00135742a01466a0340366ae854024ccd54075d7280e1aba150083335501d75ca0386ae85401ccd4068094d5d0a80319a80d19aa8140133ad35742a00a6464646666ae68cdc39aab9d5002480008cc8848cc00400c008c8c8c8cccd5cd19b8735573aa004900011991091980080180119a815bad35742a00460586ae84d5d1280111931901919ab9c034033030135573ca00226ea8004d5d0a8011919191999ab9a3370e6aae754009200023322123300100300233502b75a6ae854008c0b0d5d09aba2500223263203233573806806606026aae7940044dd50009aba135744a004464c6405c66ae700c00bc0b04d55cf280089baa00135742a00866a034eb8d5d0a80199a80d19aa8143ae200135742a00460446ae84d5d1280111931901519ab9c02c02b028135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135573ca00226ea8004d5d0a8011919191999ab9a3370ea0029003119091111802002980e9aba135573ca00646666ae68cdc3a8012400846424444600400a603e6ae84d55cf280211999ab9a3370ea0069001119091111800802980d9aba135573ca00a46666ae68cdc3a8022400046424444600600a6eb8d5d09aab9e500623263202533573804e04c04604404204026aae7540044dd50009aba135744a004464c6403c66ae7008007c07040784c98c8074cd5ce249035054350001e135573ca00226ea8004444888ccd54c01048005403ccd54c01c480048d400488cd54078008d54024004ccd54c0104800488d4008894cd4ccd54c03048004c8cd409488ccd400c88008008004d40048800448cc004894cd400840a8400409c8d400488cc028008014018400c4cd404c01000d4040004cd54c01c480048d400488c8cd5407c00cc004014c8004d5409c894cd40044d5402800c884d4008894cd4cc03000802044888cc0080280104c01800c008c8004d5408088448894cd40044008884cc014008ccd54c01c480040140100044484888c00c0104484888c004010c8004d540748844894cd400454034884cd4038c010008cd54c01848004010004c8004d5407088448894cd40044d400c88004884ccd401488008c010008ccd54c01c4800401401000488ccd5cd19b8f00200101c01b23500122222222220081232230023758002640026aa034446666aae7c004940248cd4020c010d5d080118019aba200201423232323333573466e1cd55cea801a40004666444246660020080060046464646666ae68cdc39aab9d5002480008cc8848cc00400c008c054d5d0a80119a80700a1aba135744a004464c6403066ae700680640584d55cf280089baa00135742a006666aa00eeb94018d5d0a80119a8053ae357426ae8940088c98c8050cd5ce00b00a80909aba25001135573ca00226ea80044cd54005d73ad112232230023756002640026aa03044646666aae7c008940208cd401ccd5404cc018d55cea80118029aab9e500230043574400602626ae840044488008488488cc00401000c488c8c8cccd5cd19b875001480008c8488c00800cc014d5d09aab9e500323333573466e1d40092002212200123263201033573802402201c01a26aae7540044dd5000919191999ab9a3370e6aae7540092000233221233001003002300535742a0046eb4d5d09aba2500223263200d33573801e01c01626aae7940044dd50009191999ab9a3370e6aae75400520002375c6ae84d55cf280111931900599ab9c00d00c0091375400224464646666ae68cdc3a800a40084244400246666ae68cdc3a8012400446424446006008600c6ae84d55cf280211999ab9a3370ea00690001091100111931900719ab9c01000f00c00b00a135573aa00226ea80048c8cccd5cd19b8750014800880448cccd5cd19b8750024800080448c98c8028cd5ce00600580400389aab9d3754002464646464646666ae68cdc3a800a401842444444400646666ae68cdc3a8012401442444444400846666ae68cdc3a801a40104664424444444660020120106eb8d5d0a8029bad357426ae8940148cccd5cd19b875004480188cc8848888888cc008024020dd71aba15007375c6ae84d5d1280391999ab9a3370ea00a900211991091111111980300480418061aba15009375c6ae84d5d1280491999ab9a3370ea00c900111909111111180380418069aba135573ca01646666ae68cdc3a803a400046424444444600a010601c6ae84d55cf280611931900919ab9c01401301000f00e00d00c00b00a135573aa00826aae79400c4d55cf280109aab9e5001137540024646464646666ae68cdc3a800a4004466644424466600200a0080066eb4d5d0a8021bad35742a0066eb4d5d09aba2500323333573466e1d4009200023212230020033008357426aae7940188c98c802ccd5ce00680600480409aab9d5003135744a00226aae7940044dd5000919191999ab9a3370ea002900111909118008019bae357426aae79400c8cccd5cd19b875002480008c8488c00800cdd71aba135573ca008464c6401066ae700280240180144d55cea80089baa0011122232323333573466e1cd55cea80124000466aa010600c6ae854008c014d5d09aba2500223263200833573801401200c26aae7940044dd5000a4c22442466002006004240029210350543100112330012253350021001100700612335002223335003220020020013500122001122123300100300222333573466e1c00800401000c488008488004448c8c00400488cc00cc00800800410581840100d87980821a000a01a61a0b3b82b2f5f6").unwrap(); let raw_inputs = hex::decode("84825820b16778c9cf065d9efeefe37ec269b4fc5107ecdbd0dd6bf3274b224165c2edd9008258206c732139de33e916342707de2aebef2252c781640326ff37b86ec99d97f1ba8d01825820975c17a4fed0051be622328efa548e206657d2b65a19224bf6ff8132571e6a500282582018f86700660fc88d0370a8f95ea58f75507e6b27a18a17925ad3b1777eb0d77600").unwrap(); let raw_outputs = hex::decode("8482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f67235821a000f8548a1581c15be994a64bdb79dde7fe080d8e7ff81b33a9e4860e9ee0d857a8e85a144576177610182581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351b00000001af14b8b482581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a0098968082581d60b6c8794e9a7a26599440a4d0fd79cd07644d15917ff13694f1f672351a00acd8c6").unwrap(); let inputs = MaybeIndefArray::::decode_fragment(&raw_inputs).unwrap(); let outputs = MaybeIndefArray::::decode_fragment(&raw_outputs).unwrap(); let utxos: MaybeIndefArray = MaybeIndefArray::Indef( inputs .iter() .zip(outputs.iter()) .map(|(input, output)| ResolvedInput { input: input.clone(), output: output.clone(), }) .collect(), ); let slot_config = SlotConfig { zero_time: 1660003200000, // Preview network slot_length: 1000, }; let multi_era_tx = MultiEraTx::decode(Era::Babbage, &tx_bytes) .or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes)) .unwrap(); match multi_era_tx { MultiEraTx::Babbage(tx) => { let redeemers = eval_tx(&tx, &utxos, &slot_config).unwrap(); println!("{:?}", redeemers.len()); } _ => unreachable!(), }; } }