diff --git a/Cargo.lock b/Cargo.lock index e85321c7..08ae4d3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1942,7 +1942,6 @@ checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "pallas-addresses" version = "0.29.0" -source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c" dependencies = [ "base58", "bech32", @@ -1957,7 +1956,6 @@ dependencies = [ [[package]] name = "pallas-codec" version = "0.29.0" -source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c" dependencies = [ "hex", "minicbor", @@ -1969,7 +1967,6 @@ dependencies = [ [[package]] name = "pallas-crypto" version = "0.29.0" -source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c" dependencies = [ "cryptoxide", "hex", @@ -1982,7 +1979,6 @@ dependencies = [ [[package]] name = "pallas-primitives" version = "0.29.0" -source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c" dependencies = [ "base58", "bech32", @@ -1997,7 +1993,6 @@ dependencies = [ [[package]] name = "pallas-traverse" version = "0.29.0" -source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c" dependencies = [ "hex", "itertools 0.13.0", @@ -3209,6 +3204,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indoc", + "insta", "itertools 0.10.5", "k256", "miette 5.10.0", diff --git a/crates/uplc/Cargo.toml b/crates/uplc/Cargo.toml index fac61ae4..71960d38 100644 --- a/crates/uplc/Cargo.toml +++ b/crates/uplc/Cargo.toml @@ -42,5 +42,6 @@ k256 = { version = "0.13.0" } [dev-dependencies] hex = "0.4.3" indoc = "2.0.1" +insta.workspace = true pretty_assertions = "1.3.0" walkdir.workspace = true diff --git a/crates/uplc/src/tx/eval.rs b/crates/uplc/src/tx/eval.rs index 1fdc4a34..dc9c1a61 100644 --- a/crates/uplc/src/tx/eval.rs +++ b/crates/uplc/src/tx/eval.rs @@ -32,19 +32,20 @@ pub fn eval_redeemer( tx_info: TxInfo, program: Program, ) -> Result { - let purpose = tx_info - .purpose(redeemer) - .expect("redeemer's purpose shall be known by this point."); + let script_context = tx_info + .into_script_context(redeemer, datum.as_ref()) + .expect("couldn't create script context from transaction?"); - let script_context = ScriptContext { tx_info, purpose }; - - let program = if let Some(datum) = datum { - program.apply_data(datum) - } else { - program - } - .apply_data(redeemer.data.clone()) - .apply_data(script_context.to_plutus_data()); + let program = match script_context { + ScriptContext::V1V2 { .. } => if let Some(datum) = datum { + program.apply_data(datum) + } else { + program + } + .apply_data(redeemer.data.clone()) + .apply_data(script_context.to_plutus_data()), + ScriptContext::V3 { .. } => program.apply_data(script_context.to_plutus_data()), + }; let mut eval_result = if let Some(costs) = cost_mdl_opt { program.eval_as(lang, costs, Some(initial_budget)) diff --git a/crates/uplc/src/tx/phase_one.rs b/crates/uplc/src/tx/phase_one.rs index 0d8939bc..67864070 100644 --- a/crates/uplc/src/tx/phase_one.rs +++ b/crates/uplc/src/tx/phase_one.rs @@ -81,7 +81,7 @@ pub fn scripts_needed(tx: &MintedTx, utxos: &[ResolvedInput]) -> Result { + ScriptPurpose::Spending(txin, ()) => { let redeemer_key = tx_body .inputs .iter() diff --git a/crates/uplc/src/tx/script_context.rs b/crates/uplc/src/tx/script_context.rs index 19b71798..b8471c4d 100644 --- a/crates/uplc/src/tx/script_context.rs +++ b/crates/uplc/src/tx/script_context.rs @@ -25,47 +25,48 @@ pub struct ResolvedInput { #[derive(Debug, PartialEq, Clone)] pub struct TxInInfo { pub out_ref: TransactionInput, - pub resolved: TxOut, + pub resolved: TransactionOutput, } -#[derive(Debug, PartialEq, Clone)] -pub enum TxOut { - V1(TransactionOutput), - V2(TransactionOutput), -} - -impl TxOut { - pub fn address(&self) -> Address { - let address_from_output = |output: &TransactionOutput| match output { - TransactionOutput::Legacy(x) => Address::from_bytes(&x.address).unwrap(), - TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address).unwrap(), - }; - match self { - TxOut::V1(output) => address_from_output(output), - TxOut::V2(output) => address_from_output(output), - } - } - - pub fn datum(&self) -> Option { - let datum_from_output = |output: &TransactionOutput| match output { - TransactionOutput::Legacy(x) => x.datum_hash.map(DatumOption::Hash), - TransactionOutput::PostAlonzo(x) => x.datum_option.clone(), - }; - match self { - TxOut::V1(output) => datum_from_output(output), - TxOut::V2(output) => datum_from_output(output), - } +pub fn output_address(output: &TransactionOutput) -> Address { + match output { + TransactionOutput::Legacy(x) => Address::from_bytes(&x.address).unwrap(), + TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address).unwrap(), } } +pub fn output_datum(output: &TransactionOutput) -> Option { + match output { + TransactionOutput::Legacy(x) => x.datum_hash.map(DatumOption::Hash), + TransactionOutput::PostAlonzo(x) => x.datum_option.clone(), + } +} + +/// The ScriptPurpose is part of the ScriptContext is the case of Plutus V1 and V2. +/// It is superseded by the ScriptInfo in PlutusV3. #[derive(Debug, PartialEq, Eq, Clone)] -pub enum ScriptPurpose { +pub enum ScriptInfo { Minting(PolicyId), - Spending(TransactionInput), + Spending(TransactionInput, T), Rewarding(StakeCredential), Certifying(Certificate), } +pub type ScriptPurpose = ScriptInfo<()>; + +impl ScriptPurpose { + pub fn into_script_info(self, datum: T) -> ScriptInfo { + match self { + Self::Minting(policy_id) => ScriptInfo::Minting(policy_id), + Self::Spending(transaction_output, ()) => { + ScriptInfo::Spending(transaction_output, datum) + } + Self::Rewarding(stake_credential) => ScriptInfo::Rewarding(stake_credential), + Self::Certifying(certificate) => ScriptInfo::Certifying(certificate), + } + } +} + #[derive(Debug, PartialEq, Clone)] pub enum ScriptVersion { Native(NativeScript), @@ -184,7 +185,7 @@ impl DataLookupTable { #[derive(Debug, PartialEq, Clone)] pub struct TxInfoV1 { pub inputs: Vec, - pub outputs: Vec, + pub outputs: Vec, pub fee: Value, pub mint: MintValue, pub certificates: Vec, @@ -219,8 +220,8 @@ impl TxInfoV1 { Ok(TxInfo::V1(TxInfoV1 { inputs, - outputs: get_outputs_info(TxOut::V1, &tx.transaction_body.outputs[..]), - fee: get_fee_info(&tx.transaction_body.fee), + outputs: get_outputs_info(&tx.transaction_body.outputs[..]), + fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)), mint, certificates, withdrawals: withdrawals.into(), @@ -237,7 +238,7 @@ impl TxInfoV1 { pub struct TxInfoV2 { pub inputs: Vec, pub reference_inputs: Vec, - pub outputs: Vec, + pub outputs: Vec, pub fee: Value, pub mint: MintValue, pub certificates: Vec, @@ -277,8 +278,8 @@ impl TxInfoV2 { Ok(TxInfo::V2(TxInfoV2 { inputs, reference_inputs, - outputs: get_outputs_info(TxOut::V2, &tx.transaction_body.outputs[..]), - fee: get_fee_info(&tx.transaction_body.fee), + outputs: get_outputs_info(&tx.transaction_body.outputs[..]), + fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)), mint, certificates, withdrawals, @@ -295,8 +296,8 @@ impl TxInfoV2 { pub struct TxInfoV3 { pub inputs: Vec, pub reference_inputs: Vec, - pub outputs: Vec, - pub fee: Value, + pub outputs: Vec, + pub fee: Coin, pub mint: MintValue, pub certificates: Vec, pub withdrawals: KeyValuePairs, @@ -323,7 +324,7 @@ impl TxInfoV3 { inputs: tx_info_v2.inputs, reference_inputs: tx_info_v2.reference_inputs, outputs: tx_info_v2.outputs, - fee: tx_info_v2.fee, + fee: get_fee_info(&tx.transaction_body.fee), mint: tx_info_v2.mint, certificates: tx_info_v2.certificates, withdrawals: tx_info_v2.withdrawals, @@ -347,19 +348,41 @@ pub enum TxInfo { } impl TxInfo { - pub fn purpose(&self, needle: &Redeemer) -> Option { + pub fn into_script_context( + self, + redeemer: &Redeemer, + datum: Option<&PlutusData>, + ) -> Option { match self { - TxInfo::V1(TxInfoV1 { redeemers, .. }) - | TxInfo::V2(TxInfoV2 { redeemers, .. }) - | TxInfo::V3(TxInfoV3 { redeemers, .. }) => { - redeemers.iter().find_map(|(purpose, redeemer)| { - if redeemer == needle { + TxInfo::V1(TxInfoV1 { ref redeemers, .. }) + | TxInfo::V2(TxInfoV2 { ref redeemers, .. }) => redeemers + .iter() + .find_map(move |(purpose, some_redeemer)| { + if redeemer == some_redeemer { Some(purpose.clone()) } else { None } }) - } + .map(move |purpose| ScriptContext::V1V2 { + tx_info: self, + purpose: purpose.clone().into(), + }), + + TxInfo::V3(TxInfoV3 { ref redeemers, .. }) => redeemers + .iter() + .find_map(move |(purpose, some_redeemer)| { + if redeemer == some_redeemer { + Some(purpose.clone()) + } else { + None + } + }) + .map(move |purpose| ScriptContext::V3 { + tx_info: self, + redeemer: redeemer.data.clone(), + purpose: purpose.clone().into_script_info(datum.cloned()), + }), } } @@ -397,9 +420,16 @@ impl TxInfo { } #[derive(Debug, PartialEq, Clone)] -pub struct ScriptContext { - pub tx_info: TxInfo, - pub purpose: ScriptPurpose, +pub enum ScriptContext { + V1V2 { + tx_info: TxInfo, + purpose: Box, + }, + V3 { + tx_info: TxInfo, + redeemer: PlutusData, + purpose: ScriptInfo>, + }, } //---- Time conversion: slot range => posix time range @@ -470,7 +500,7 @@ pub fn get_tx_in_info_v1( Ok(TxInInfo { out_ref: utxo.input.clone(), - resolved: TxOut::V1(sort_tx_out_value(&utxo.output)), + resolved: sort_tx_out_value(&utxo.output), }) }) .collect() @@ -506,7 +536,7 @@ pub fn get_tx_in_info_v2( Ok(TxInInfo { out_ref: utxo.input.clone(), - resolved: TxOut::V2(sort_tx_out_value(&utxo.output)), + resolved: sort_tx_out_value(&utxo.output), }) }) .collect() @@ -521,19 +551,16 @@ pub fn get_mint_info(mint: &Option) -> MintValue { } } -pub fn get_outputs_info( - to_tx_out: fn(TransactionOutput) -> TxOut, - outputs: &[MintedTransactionOutput], -) -> Vec { +pub fn get_outputs_info(outputs: &[MintedTransactionOutput]) -> Vec { outputs .iter() .cloned() - .map(|output| to_tx_out(sort_tx_out_value(&output.into()))) + .map(|output| sort_tx_out_value(&output.into())) .collect() } -pub fn get_fee_info(fee: &Coin) -> Value { - Value::Coin(*fee) +pub fn get_fee_info(fee: &Coin) -> Coin { + *fee } pub fn get_certificates_info(certificates: &Option>) -> Vec { @@ -649,7 +676,7 @@ fn script_purpose_builder<'a>( RedeemerTag::Spend => inputs .get(index) .cloned() - .map(|i| ScriptPurpose::Spending(i.out_ref)), + .map(|i| ScriptPurpose::Spending(i.out_ref, ())), RedeemerTag::Cert => certificates .get(index) .cloned() @@ -756,13 +783,13 @@ pub fn find_script( })? .get(redeemer.index as usize) .ok_or(Error::MissingScriptForRedeemer) - .and_then(|input| match input.resolved.address() { + .and_then(|input| match output_address(&input.resolved) { Address::Shelley(shelley_address) => { let hash = shelley_address.payment().as_hash(); let script = lookup_script(hash); - let datum = lookup_datum(input.resolved.datum()); + let datum = lookup_datum(output_datum(&input.resolved)); script.and_then(|(script, _)| Ok((script, Some(datum?)))) } @@ -775,7 +802,7 @@ pub fn find_script( } } -fn from_alonzo_value(value: &alonzo::Value) -> Value { +pub fn from_alonzo_value(value: &alonzo::Value) -> Value { match value { alonzo::Value::Coin(coin) => Value::Coin(*coin), alonzo::Value::Multiasset(coin, assets) if assets.is_empty() => Value::Coin(*coin), @@ -810,6 +837,15 @@ fn from_alonzo_value(value: &alonzo::Value) -> Value { } } +pub fn from_alonzo_output(output: &alonzo::TransactionOutput) -> TransactionOutput { + TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput { + address: output.address.clone(), + value: from_alonzo_value(&output.amount), + datum_option: output.datum_hash.map(DatumOption::Hash), + script_ref: None, + }) +} + // --------------------- Sorting fn sort_tx_out_value(tx_output: &TransactionOutput) -> TransactionOutput { @@ -882,3 +918,91 @@ fn sort_redeemers(a: &RedeemersKey, b: &RedeemersKey) -> Ordering { redeemer_tag_as_usize(&a.tag).cmp(&redeemer_tag_as_usize(&b.tag)) } } + +#[cfg(test)] +mod tests { + use crate::{ + ast::Data, + tx::{ + script_context::{TxInfo, TxInfoV3}, + to_plutus_data::ToPlutusData, + ResolvedInput, SlotConfig, + }, + }; + use pallas_primitives::{ + conway::{ExUnits, PlutusData, Redeemer, RedeemerTag, TransactionInput, TransactionOutput}, + Fragment, + }; + use pallas_traverse::{Era, MultiEraTx}; + + fn fixture_tx_info(transaction: &str, inputs: &str, outputs: &str) -> TxInfo { + let transaction_bytes = hex::decode(transaction).unwrap(); + let inputs_bytes = hex::decode(inputs).unwrap(); + let outputs_bytes = hex::decode(outputs).unwrap(); + + let inputs = Vec::::decode_fragment(inputs_bytes.as_slice()).unwrap(); + let outputs = Vec::::decode_fragment(outputs_bytes.as_slice()).unwrap(); + let resolved_inputs: Vec = inputs + .iter() + .zip(outputs.iter()) + .map(|(input, output)| ResolvedInput { + input: input.clone(), + output: output.clone(), + }) + .collect(); + + TxInfoV3::from_transaction( + MultiEraTx::decode_for_era(Era::Conway, transaction_bytes.as_slice()) + .unwrap() + .as_conway() + .unwrap(), + &resolved_inputs, + &SlotConfig::default(), + ) + .unwrap() + } + + #[allow(dead_code)] + fn from_haskell(data: &str) -> PlutusData { + PlutusData::decode_fragment(hex::decode(data).unwrap().as_slice()).unwrap() + } + + #[test] + fn tx_to_plutus_data() { + let datum = Some(Data::constr(0, Vec::new())); + + let redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: 0, + data: Data::constr(0, Vec::new()), + ex_units: ExUnits { + mem: 1000000, + steps: 100000000, + }, + }; + + let script_context = fixture_tx_info( + "84a7008182582000000000000000000000000000000000000000000000000000\ + 0000000000000000018182581d60111111111111111111111111111111111111\ + 111111111111111111111a3b9aca0002182a0b5820ffffffffffffffffffffff\ + ffffffffffffffffffffffffffffffffffffffffff0d81825820000000000000\ + 0000000000000000000000000000000000000000000000000000001082581d60\ + 000000000000000000000000000000000000000000000000000000001a3b9aca\ + 001101a20581840000d87980821a000f42401a05f5e100078152510101003222\ + 253330044a229309b2b2b9a1f5f6", + "8182582000000000000000000000000000000000000000000000000000000000\ + 0000000000", + "81a300581d7039f47fd3b388ef53c48f08de24766d3e55dade6cae908cc24e0f\ + 4f3e011a3b9aca00028201d81843d87980", + ) + .into_script_context(&redeemer, datum.as_ref()) + .unwrap(); + + // NOTE: The initial snapshot has been generated using the Haskell + // implementation of the ledger library for that same serialized + // transactions. It is meant to control that our construction of the + // script context and its serialization matches exactly those + // from the Haskell ledger / cardano node. + insta::assert_debug_snapshot!(script_context.to_plutus_data()) + } +} diff --git a/crates/uplc/src/tx/to_plutus_data.rs b/crates/uplc/src/tx/to_plutus_data.rs index 77022865..b04f620c 100644 --- a/crates/uplc/src/tx/to_plutus_data.rs +++ b/crates/uplc/src/tx/to_plutus_data.rs @@ -1,23 +1,20 @@ -use super::script_context::{ScriptContext, ScriptPurpose, TimeRange, TxInInfo, TxInfo, TxOut}; -use crate::machine::runtime::{convert_constr_to_tag, ANY_TAG}; +use super::script_context::{ + ScriptContext, ScriptInfo, ScriptPurpose, TimeRange, TxInInfo, TxInfo, +}; +use crate::{ + ast::Data, + machine::runtime::{convert_constr_to_tag, ANY_TAG}, + tx::script_context::from_alonzo_output, +}; use pallas_addresses::{Address, ShelleyDelegationPart, ShelleyPaymentPart, StakePayload}; -use pallas_codec::utils::{AnyUInt, Bytes, Int, KeyValuePairs}; +use pallas_codec::utils::{AnyUInt, Bytes, Int, KeyValuePairs, NonEmptyKeyValuePairs}; use pallas_crypto::hash::Hash; use pallas_primitives::conway::{ - AssetName, BigInt, Certificate, Constr, DatumOption, Mint, PlutusData, PseudoScript, Redeemer, - ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value, + AssetName, BigInt, Certificate, Coin, Constr, DatumOption, Mint, PlutusData, PolicyId, + PseudoScript, Redeemer, ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value, }; use pallas_traverse::ComputeHash; -fn wrap_with_constr(index: u64, data: PlutusData) -> PlutusData { - let converted = convert_constr_to_tag(index); - PlutusData::Constr(Constr { - tag: converted.unwrap_or(ANY_TAG), - any_constructor: converted.map_or(Some(index), |_| None), - fields: vec![data], - }) -} - fn wrap_multiple_with_constr(index: u64, data: Vec) -> PlutusData { let converted = convert_constr_to_tag(index); PlutusData::Constr(Constr { @@ -27,17 +24,20 @@ fn wrap_multiple_with_constr(index: u64, data: Vec) -> PlutusData { }) } +fn wrap_with_constr(index: u64, data: PlutusData) -> PlutusData { + wrap_multiple_with_constr(index, vec![data]) +} + fn empty_constr(index: u64) -> PlutusData { - let converted = convert_constr_to_tag(index); - PlutusData::Constr(Constr { - tag: converted.unwrap_or(ANY_TAG), - any_constructor: converted.map_or(Some(index), |_| None), - fields: vec![], - }) + wrap_multiple_with_constr(index, vec![]) } struct WithWrappedTransactionId<'a, T>(&'a T); +struct WithZeroAdaAsset<'a, T>(&'a T); + +struct WithOptionDatum<'a, T>(&'a T); + pub trait ToPlutusData { fn to_plutus_data(&self) -> PlutusData; } @@ -165,6 +165,19 @@ where } } +impl<'a> ToPlutusData for WithWrappedTransactionId<'a, KeyValuePairs> { + fn to_plutus_data(&self) -> PlutusData { + let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![]; + for (key, value) in self.0.iter() { + data_vec.push(( + WithWrappedTransactionId(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 { @@ -174,7 +187,6 @@ impl ToPlutusData for Option { } } -// 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 { @@ -224,68 +236,101 @@ impl ToPlutusData for u64 { } } +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, Value> { + fn to_plutus_data(&self) -> PlutusData { + match self.0 { + Value::Coin(coin) => { + PlutusData::Map(KeyValuePairs::Def(vec![coin_to_plutus_data(coin)])) + } + Value::Multiasset(coin, multiassets) => value_to_plutus_data( + multiassets.iter(), + |amount| u64::from(amount).to_plutus_data(), + vec![coin_to_plutus_data(coin)], + ), + } + } +} + impl ToPlutusData for Value { fn to_plutus_data(&self) -> PlutusData { match self { - Value::Coin(coin) => PlutusData::Map(KeyValuePairs::Def(vec![( + Value::Coin(coin) => PlutusData::Map(KeyValuePairs::Def(if *coin > 0 { + vec![coin_to_plutus_data(coin)] + } else { + vec![] + })), + Value::Multiasset(coin, multiassets) => value_to_plutus_data( + multiassets.iter(), + |amount| u64::from(amount).to_plutus_data(), + if *coin > 0 { + vec![coin_to_plutus_data(coin)] + } else { + vec![] + }, + ), + } + } +} + +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, MintValue> { + fn to_plutus_data(&self) -> PlutusData { + value_to_plutus_data( + self.0.mint_value.iter(), + |amount| i64::from(amount).to_plutus_data(), + vec![( Bytes::from(vec![]).to_plutus_data(), PlutusData::Map(KeyValuePairs::Def(vec![( AssetName::from(vec![]).to_plutus_data(), - coin.to_plutus_data(), + 0_i64.to_plutus_data(), )])), - )])), - Value::Multiasset(coin, multiassets) => { - let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![( - Bytes::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(), u64::from(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 MintValue { fn to_plutus_data(&self) -> PlutusData { - let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![( - Bytes::from(vec![]).to_plutus_data(), - PlutusData::Map(KeyValuePairs::Def(vec![( - AssetName::from(vec![]).to_plutus_data(), - 0_i64.to_plutus_data(), - )])), - )]; - - for (policy_id, assets) in self.mint_value.iter() { - let mut assets_vec = vec![]; - for (asset, amount) in assets.iter() { - assets_vec.push((asset.to_plutus_data(), i64::from(amount).to_plutus_data())); - } - data_vec.push(( - policy_id.to_plutus_data(), - PlutusData::Map(KeyValuePairs::Def(assets_vec)), - )); - } - - PlutusData::Map(KeyValuePairs::Def(data_vec)) + value_to_plutus_data( + self.mint_value.iter(), + |amount| i64::from(amount).to_plutus_data(), + vec![], + ) } } +fn value_to_plutus_data<'a, I, Q>( + mint: I, + from_quantity: fn(&'a Q) -> PlutusData, + mut data_vec: Vec<(PlutusData, PlutusData)>, +) -> PlutusData +where + I: Iterator)>, + Q: Clone, +{ + for (policy_id, assets) in mint { + let mut assets_vec = vec![]; + for (asset, amount) in assets.iter() { + assets_vec.push((asset.to_plutus_data(), from_quantity(amount))); + } + data_vec.push(( + policy_id.to_plutus_data(), + PlutusData::Map(KeyValuePairs::Def(assets_vec)), + )); + } + + PlutusData::Map(KeyValuePairs::Def(data_vec)) +} + +fn coin_to_plutus_data(coin: &Coin) -> (PlutusData, PlutusData) { + ( + Bytes::from(vec![]).to_plutus_data(), + PlutusData::Map(KeyValuePairs::Def(vec![( + AssetName::from(vec![]).to_plutus_data(), + coin.to_plutus_data(), + )])), + ) +} + impl ToPlutusData for ScriptRef { fn to_plutus_data(&self) -> PlutusData { match &self { @@ -299,67 +344,100 @@ impl ToPlutusData for ScriptRef { } } -impl ToPlutusData for TxOut { +impl<'a> ToPlutusData for WithOptionDatum<'a, WithZeroAdaAsset<'a, Vec>> { + fn to_plutus_data(&self) -> PlutusData { + PlutusData::Array( + self.0 + .0 + .iter() + .map(|p| WithOptionDatum(&WithZeroAdaAsset(p)).to_plutus_data()) + .collect(), + ) + } +} + +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, Vec> { + fn to_plutus_data(&self) -> PlutusData { + PlutusData::Array( + self.0 + .iter() + .map(|p| WithZeroAdaAsset(p).to_plutus_data()) + .collect(), + ) + } +} + +impl<'a> ToPlutusData for WithOptionDatum<'a, WithZeroAdaAsset<'a, TransactionOutput>> { + fn to_plutus_data(&self) -> PlutusData { + match self.0 .0 { + TransactionOutput::Legacy(legacy_output) => { + WithOptionDatum(&WithZeroAdaAsset(&from_alonzo_output(legacy_output))) + .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(), + WithZeroAdaAsset(&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(), + }, + ], + ), + } + } +} + +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, TransactionOutput> { + fn to_plutus_data(&self) -> PlutusData { + match self.0 { + TransactionOutput::Legacy(legacy_output) => { + WithZeroAdaAsset(&from_alonzo_output(legacy_output)).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(), + WithZeroAdaAsset(&post_alonzo_output.value).to_plutus_data(), + post_alonzo_output.datum_option.to_plutus_data(), + post_alonzo_output + .script_ref + .as_ref() + .map(|s| s.clone().unwrap()) + .to_plutus_data(), + ], + ), + } + } +} + +impl ToPlutusData for TransactionOutput { 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::Legacy(..) => unimplemented!("TransactionOutput::Legacy"), - 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::Legacy(..) => unimplemented!("TransactionOutput::Legacy"), - 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 - .as_ref() - .map(|s| s.clone().unwrap()) - .to_plutus_data(), - ], - ), - }, + TransactionOutput::Legacy(legacy_output) => { + from_alonzo_output(legacy_output).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 + .as_ref() + .map(|s| s.clone().unwrap()) + .to_plutus_data(), + ], + ), } } } @@ -570,24 +648,57 @@ impl ToPlutusData for TimeRange { } } -impl<'a> ToPlutusData for WithWrappedTransactionId<'a, Vec> { +impl<'a> ToPlutusData + for WithOptionDatum<'a, WithZeroAdaAsset<'a, WithWrappedTransactionId<'a, Vec>>> +{ fn to_plutus_data(&self) -> PlutusData { PlutusData::Array( self.0 + .0 + .0 .iter() - .map(|p| WithWrappedTransactionId(p).to_plutus_data()) + .map(|p| { + WithOptionDatum(&WithZeroAdaAsset(&WithWrappedTransactionId(p))) + .to_plutus_data() + }) .collect(), ) } } -impl<'a> ToPlutusData for WithWrappedTransactionId<'a, TxInInfo> { +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, WithWrappedTransactionId<'a, Vec>> { + fn to_plutus_data(&self) -> PlutusData { + PlutusData::Array( + self.0 + .0 + .iter() + .map(|p| WithZeroAdaAsset(&WithWrappedTransactionId(p)).to_plutus_data()) + .collect(), + ) + } +} + +impl<'a> ToPlutusData for WithZeroAdaAsset<'a, 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(), + WithWrappedTransactionId(&self.0 .0.out_ref).to_plutus_data(), + WithZeroAdaAsset(&self.0 .0.resolved).to_plutus_data(), + ], + ) + } +} + +impl<'a> ToPlutusData + for WithOptionDatum<'a, WithZeroAdaAsset<'a, WithWrappedTransactionId<'a, TxInInfo>>> +{ + fn to_plutus_data(&self) -> PlutusData { + wrap_multiple_with_constr( + 0, + vec![ + WithWrappedTransactionId(&self.0 .0 .0.out_ref).to_plutus_data(), + WithOptionDatum(&WithZeroAdaAsset(&self.0 .0 .0.resolved)).to_plutus_data(), ], ) } @@ -605,11 +716,22 @@ impl ToPlutusData for TxInInfo { } } +impl<'a> ToPlutusData for WithWrappedTransactionId<'a, ScriptPurpose> { + fn to_plutus_data(&self) -> PlutusData { + match self.0 { + ScriptPurpose::Spending(out_ref, ()) => { + wrap_with_constr(1, WithWrappedTransactionId(out_ref).to_plutus_data()) + } + otherwise => otherwise.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::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()) } @@ -618,16 +740,37 @@ impl ToPlutusData for ScriptPurpose { } } +impl ToPlutusData for ScriptInfo +where + T: ToPlutusData, +{ + fn to_plutus_data(&self) -> PlutusData { + match self { + ScriptInfo::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()), + ScriptInfo::Spending(out_ref, datum) => { + wrap_multiple_with_constr(1, vec![out_ref.to_plutus_data(), datum.to_plutus_data()]) + } + ScriptInfo::Rewarding(stake_credential) => { + wrap_with_constr(2, stake_credential.to_plutus_data()) + } + ScriptInfo::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![ - 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(), + WithOptionDatum(&WithZeroAdaAsset(&WithWrappedTransactionId( + &tx_info.inputs, + ))) + .to_plutus_data(), + WithOptionDatum(&WithZeroAdaAsset(&tx_info.outputs)).to_plutus_data(), + WithZeroAdaAsset(&tx_info.fee).to_plutus_data(), + WithZeroAdaAsset(&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(), @@ -639,16 +782,17 @@ 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(), + WithZeroAdaAsset(&WithWrappedTransactionId(&tx_info.inputs)).to_plutus_data(), + WithZeroAdaAsset(&WithWrappedTransactionId(&tx_info.reference_inputs)) + .to_plutus_data(), + WithZeroAdaAsset(&tx_info.outputs).to_plutus_data(), + WithZeroAdaAsset(&tx_info.fee).to_plutus_data(), + WithZeroAdaAsset(&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(), + WithWrappedTransactionId(&tx_info.redeemers).to_plutus_data(), tx_info.data.to_plutus_data(), wrap_with_constr(0, tx_info.id.to_plutus_data()), ], @@ -668,6 +812,10 @@ impl ToPlutusData for TxInfo { tx_info.redeemers.to_plutus_data(), tx_info.data.to_plutus_data(), tx_info.id.to_plutus_data(), + Data::map(vec![]), // TODO tx_info.votes :: Map Voter (Map GovernanceActionId Vote) + Data::list(vec![]), // TODO tx_info.proposal_procedures :: [ProposalProcedure] + empty_constr(1), // TODO tx_info.current_treasury_amount :: Haskell.Maybe V2.Lovelace + empty_constr(1), // TODO tx_info.treasury_donation :: Haskell.Maybe V2.Lovelace ], ), } @@ -676,10 +824,27 @@ impl ToPlutusData for TxInfo { 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()], - ) + match self { + ScriptContext::V1V2 { tx_info, purpose } => wrap_multiple_with_constr( + 0, + vec![ + tx_info.to_plutus_data(), + WithWrappedTransactionId(purpose.as_ref()).to_plutus_data(), + ], + ), + ScriptContext::V3 { + tx_info, + redeemer, + purpose, + } => wrap_multiple_with_constr( + 0, + vec![ + tx_info.to_plutus_data(), + redeemer.to_plutus_data(), + purpose.to_plutus_data(), + ], + ), + } } } diff --git a/examples/acceptance_tests/run-all-script-contexts b/examples/acceptance_tests/run-all-script-contexts index 87af6f43..757180cc 100755 --- a/examples/acceptance_tests/run-all-script-contexts +++ b/examples/acceptance_tests/run-all-script-contexts @@ -1,12 +1,14 @@ #!/usr/bin/env bash +AIKEN=${1:-"cargo run -r --quiet --"} + TESTS=() for lang in $(ls script_context); do for interaction in $(find script_context/$lang/validators -type f); do title=$(basename $interaction) title="${title%.*}" cd script_context/$lang - ./test.sh $title & + ./test.sh $title "$AIKEN" & TESTS+=("$title,$lang,$!") cd - 1>/dev/null done diff --git a/examples/acceptance_tests/script_context/v2/aiken.lock b/examples/acceptance_tests/script_context/v2/aiken.lock index 58718127..64d68c42 100644 --- a/examples/acceptance_tests/script_context/v2/aiken.lock +++ b/examples/acceptance_tests/script_context/v2/aiken.lock @@ -13,4 +13,4 @@ requirements = [] source = "github" [etags] -"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1723298787, nanos_since_epoch = 494542000 }, "5e58899446492a704d0927a43299139856bef746e697b55895ba34206fa28452"] +"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1723298816, nanos_since_epoch = 935691000 }, "5e58899446492a704d0927a43299139856bef746e697b55895ba34206fa28452"] diff --git a/examples/acceptance_tests/script_context/v2/test.sh b/examples/acceptance_tests/script_context/v2/test.sh index 7344a814..95b7053e 100755 --- a/examples/acceptance_tests/script_context/v2/test.sh +++ b/examples/acceptance_tests/script_context/v2/test.sh @@ -12,6 +12,8 @@ if [ -z $TITLE ]; then exit 1 fi +AIKEN=${2:-"cargo run -r --quiet --"} + if ! command -v jq &> /dev/null then echo "\033[1mjq\033[0m missing from system but required." @@ -24,7 +26,7 @@ then exit 1 fi -cargo run -r --quiet -- build 2>/dev/null +$AIKEN build 2>/dev/null if [ $? -ne 0 ]; then exit $? fi @@ -39,4 +41,4 @@ cp ctx/$TITLE/inputs.cbor.template ctx/$TITLE/inputs.cbor sed "s/{{ VALIDATOR_HASH }}/$VALIDATOR_HASH/" ctx/$TITLE/outputs.cbor.template > ctx/$TITLE/outputs.cbor sed "s/{{ VALIDATOR }}/$VALIDATOR/" ctx/$TITLE/tx.cbor.template | sed "s/{{ VALIDATOR_HASH }}/$VALIDATOR_HASH/" > ctx/$TITLE/tx.cbor -cargo run -r --quiet -- tx simulate 1>$TITLE.log 2>&1 ctx/$TITLE/tx.cbor ctx/$TITLE/inputs.cbor ctx/$TITLE/outputs.cbor +$AIKEN tx simulate 1>$TITLE.log 2>&1 ctx/$TITLE/tx.cbor ctx/$TITLE/inputs.cbor ctx/$TITLE/outputs.cbor