Fix ToPlutusData serializer for V3

This is intense, as we still want to preserve the serializer for V1 &
  V2, and I've tried as much as possible to avoid polluting the
  application layer with many enum types such as:

  ```
  pub enum TxOut {
    V1(TransactionOutput),
    V2(TransactionOutput),
    V3(TransactionOutput),
  }
  ```

  Those types make working with the script context cumbersome, and are
  only truly required to provide different serialisation strategies. So
  instead, we keep one top-level `TxInfo V1/V2/V3` type, and we ensure
  to pass serialization strategies as type wrappers.

  This way, the strategy propagates through the structure up until it's
  eliminated when it reaches the relevant types.

  All-in-all, this strikes a correct balance between maintainability and
  repetition; and it makes it possible to define _different but mostly
  identical_ encoders for the various versions.

  With it, I've been able to successfully encode a V3 script context and
  match it against one produced using the Haskell libraries. More to
  come.
This commit is contained in:
KtorZ 2024-08-09 16:50:12 +02:00
parent f848bad3f2
commit 821f7bd8c7
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
9 changed files with 523 additions and 232 deletions

6
Cargo.lock generated vendored
View File

@ -1942,7 +1942,6 @@ checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
[[package]] [[package]]
name = "pallas-addresses" name = "pallas-addresses"
version = "0.29.0" version = "0.29.0"
source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c"
dependencies = [ dependencies = [
"base58", "base58",
"bech32", "bech32",
@ -1957,7 +1956,6 @@ dependencies = [
[[package]] [[package]]
name = "pallas-codec" name = "pallas-codec"
version = "0.29.0" version = "0.29.0"
source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c"
dependencies = [ dependencies = [
"hex", "hex",
"minicbor", "minicbor",
@ -1969,7 +1967,6 @@ dependencies = [
[[package]] [[package]]
name = "pallas-crypto" name = "pallas-crypto"
version = "0.29.0" version = "0.29.0"
source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c"
dependencies = [ dependencies = [
"cryptoxide", "cryptoxide",
"hex", "hex",
@ -1982,7 +1979,6 @@ dependencies = [
[[package]] [[package]]
name = "pallas-primitives" name = "pallas-primitives"
version = "0.29.0" version = "0.29.0"
source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c"
dependencies = [ dependencies = [
"base58", "base58",
"bech32", "bech32",
@ -1997,7 +1993,6 @@ dependencies = [
[[package]] [[package]]
name = "pallas-traverse" name = "pallas-traverse"
version = "0.29.0" version = "0.29.0"
source = "git+https://github.com/KtorZ/pallas.git?rev=8ea5a1adc9919b70b213dfe597e920d6e113120c#8ea5a1adc9919b70b213dfe597e920d6e113120c"
dependencies = [ dependencies = [
"hex", "hex",
"itertools 0.13.0", "itertools 0.13.0",
@ -3209,6 +3204,7 @@ dependencies = [
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indoc", "indoc",
"insta",
"itertools 0.10.5", "itertools 0.10.5",
"k256", "k256",
"miette 5.10.0", "miette 5.10.0",

View File

@ -42,5 +42,6 @@ k256 = { version = "0.13.0" }
[dev-dependencies] [dev-dependencies]
hex = "0.4.3" hex = "0.4.3"
indoc = "2.0.1" indoc = "2.0.1"
insta.workspace = true
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
walkdir.workspace = true walkdir.workspace = true

View File

@ -32,19 +32,20 @@ pub fn eval_redeemer(
tx_info: TxInfo, tx_info: TxInfo,
program: Program<NamedDeBruijn>, program: Program<NamedDeBruijn>,
) -> Result<Redeemer, Error> { ) -> Result<Redeemer, Error> {
let purpose = tx_info let script_context = tx_info
.purpose(redeemer) .into_script_context(redeemer, datum.as_ref())
.expect("redeemer's purpose shall be known by this point."); .expect("couldn't create script context from transaction?");
let script_context = ScriptContext { tx_info, purpose }; let program = match script_context {
ScriptContext::V1V2 { .. } => if let Some(datum) = datum {
let program = if let Some(datum) = datum { program.apply_data(datum)
program.apply_data(datum) } else {
} else { program
program }
} .apply_data(redeemer.data.clone())
.apply_data(redeemer.data.clone()) .apply_data(script_context.to_plutus_data()),
.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 { let mut eval_result = if let Some(costs) = cost_mdl_opt {
program.eval_as(lang, costs, Some(initial_budget)) program.eval_as(lang, costs, Some(initial_budget))

View File

@ -81,7 +81,7 @@ pub fn scripts_needed(tx: &MintedTx, utxos: &[ResolvedInput]) -> Result<ScriptsN
if let Address::Shelley(a) = address { if let Address::Shelley(a) = address {
if let ShelleyPaymentPart::Script(h) = a.payment() { if let ShelleyPaymentPart::Script(h) = a.payment() {
spend.push((ScriptPurpose::Spending(input.clone()), *h)); spend.push((ScriptPurpose::Spending(input.clone(), ()), *h));
} }
} }
} }
@ -241,7 +241,7 @@ fn build_redeemer_key(
Ok(redeemer_key) Ok(redeemer_key)
} }
ScriptPurpose::Spending(txin) => { ScriptPurpose::Spending(txin, ()) => {
let redeemer_key = tx_body let redeemer_key = tx_body
.inputs .inputs
.iter() .iter()

View File

@ -25,47 +25,48 @@ pub struct ResolvedInput {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct TxInInfo { pub struct TxInInfo {
pub out_ref: TransactionInput, pub out_ref: TransactionInput,
pub resolved: TxOut, pub resolved: TransactionOutput,
} }
#[derive(Debug, PartialEq, Clone)] pub fn output_address(output: &TransactionOutput) -> Address {
pub enum TxOut { match output {
V1(TransactionOutput), TransactionOutput::Legacy(x) => Address::from_bytes(&x.address).unwrap(),
V2(TransactionOutput), TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address).unwrap(),
}
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<DatumOption> {
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_datum(output: &TransactionOutput) -> Option<DatumOption> {
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)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum ScriptPurpose { pub enum ScriptInfo<T> {
Minting(PolicyId), Minting(PolicyId),
Spending(TransactionInput), Spending(TransactionInput, T),
Rewarding(StakeCredential), Rewarding(StakeCredential),
Certifying(Certificate), Certifying(Certificate),
} }
pub type ScriptPurpose = ScriptInfo<()>;
impl ScriptPurpose {
pub fn into_script_info<T>(self, datum: T) -> ScriptInfo<T> {
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)] #[derive(Debug, PartialEq, Clone)]
pub enum ScriptVersion { pub enum ScriptVersion {
Native(NativeScript), Native(NativeScript),
@ -184,7 +185,7 @@ impl DataLookupTable {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct TxInfoV1 { pub struct TxInfoV1 {
pub inputs: Vec<TxInInfo>, pub inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>, pub outputs: Vec<TransactionOutput>,
pub fee: Value, pub fee: Value,
pub mint: MintValue, pub mint: MintValue,
pub certificates: Vec<Certificate>, pub certificates: Vec<Certificate>,
@ -219,8 +220,8 @@ impl TxInfoV1 {
Ok(TxInfo::V1(TxInfoV1 { Ok(TxInfo::V1(TxInfoV1 {
inputs, inputs,
outputs: get_outputs_info(TxOut::V1, &tx.transaction_body.outputs[..]), outputs: get_outputs_info(&tx.transaction_body.outputs[..]),
fee: get_fee_info(&tx.transaction_body.fee), fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)),
mint, mint,
certificates, certificates,
withdrawals: withdrawals.into(), withdrawals: withdrawals.into(),
@ -237,7 +238,7 @@ impl TxInfoV1 {
pub struct TxInfoV2 { pub struct TxInfoV2 {
pub inputs: Vec<TxInInfo>, pub inputs: Vec<TxInInfo>,
pub reference_inputs: Vec<TxInInfo>, pub reference_inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>, pub outputs: Vec<TransactionOutput>,
pub fee: Value, pub fee: Value,
pub mint: MintValue, pub mint: MintValue,
pub certificates: Vec<Certificate>, pub certificates: Vec<Certificate>,
@ -277,8 +278,8 @@ impl TxInfoV2 {
Ok(TxInfo::V2(TxInfoV2 { Ok(TxInfo::V2(TxInfoV2 {
inputs, inputs,
reference_inputs, reference_inputs,
outputs: get_outputs_info(TxOut::V2, &tx.transaction_body.outputs[..]), outputs: get_outputs_info(&tx.transaction_body.outputs[..]),
fee: get_fee_info(&tx.transaction_body.fee), fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)),
mint, mint,
certificates, certificates,
withdrawals, withdrawals,
@ -295,8 +296,8 @@ impl TxInfoV2 {
pub struct TxInfoV3 { pub struct TxInfoV3 {
pub inputs: Vec<TxInInfo>, pub inputs: Vec<TxInInfo>,
pub reference_inputs: Vec<TxInInfo>, pub reference_inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>, pub outputs: Vec<TransactionOutput>,
pub fee: Value, pub fee: Coin,
pub mint: MintValue, pub mint: MintValue,
pub certificates: Vec<Certificate>, pub certificates: Vec<Certificate>,
pub withdrawals: KeyValuePairs<Address, Coin>, pub withdrawals: KeyValuePairs<Address, Coin>,
@ -323,7 +324,7 @@ impl TxInfoV3 {
inputs: tx_info_v2.inputs, inputs: tx_info_v2.inputs,
reference_inputs: tx_info_v2.reference_inputs, reference_inputs: tx_info_v2.reference_inputs,
outputs: tx_info_v2.outputs, outputs: tx_info_v2.outputs,
fee: tx_info_v2.fee, fee: get_fee_info(&tx.transaction_body.fee),
mint: tx_info_v2.mint, mint: tx_info_v2.mint,
certificates: tx_info_v2.certificates, certificates: tx_info_v2.certificates,
withdrawals: tx_info_v2.withdrawals, withdrawals: tx_info_v2.withdrawals,
@ -347,19 +348,41 @@ pub enum TxInfo {
} }
impl TxInfo { impl TxInfo {
pub fn purpose(&self, needle: &Redeemer) -> Option<ScriptPurpose> { pub fn into_script_context(
self,
redeemer: &Redeemer,
datum: Option<&PlutusData>,
) -> Option<ScriptContext> {
match self { match self {
TxInfo::V1(TxInfoV1 { redeemers, .. }) TxInfo::V1(TxInfoV1 { ref redeemers, .. })
| TxInfo::V2(TxInfoV2 { redeemers, .. }) | TxInfo::V2(TxInfoV2 { ref redeemers, .. }) => redeemers
| TxInfo::V3(TxInfoV3 { redeemers, .. }) => { .iter()
redeemers.iter().find_map(|(purpose, redeemer)| { .find_map(move |(purpose, some_redeemer)| {
if redeemer == needle { if redeemer == some_redeemer {
Some(purpose.clone()) Some(purpose.clone())
} else { } else {
None 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)] #[derive(Debug, PartialEq, Clone)]
pub struct ScriptContext { pub enum ScriptContext {
pub tx_info: TxInfo, V1V2 {
pub purpose: ScriptPurpose, tx_info: TxInfo,
purpose: Box<ScriptPurpose>,
},
V3 {
tx_info: TxInfo,
redeemer: PlutusData,
purpose: ScriptInfo<Option<PlutusData>>,
},
} }
//---- Time conversion: slot range => posix time range //---- Time conversion: slot range => posix time range
@ -470,7 +500,7 @@ pub fn get_tx_in_info_v1(
Ok(TxInInfo { Ok(TxInInfo {
out_ref: utxo.input.clone(), out_ref: utxo.input.clone(),
resolved: TxOut::V1(sort_tx_out_value(&utxo.output)), resolved: sort_tx_out_value(&utxo.output),
}) })
}) })
.collect() .collect()
@ -506,7 +536,7 @@ pub fn get_tx_in_info_v2(
Ok(TxInInfo { Ok(TxInInfo {
out_ref: utxo.input.clone(), out_ref: utxo.input.clone(),
resolved: TxOut::V2(sort_tx_out_value(&utxo.output)), resolved: sort_tx_out_value(&utxo.output),
}) })
}) })
.collect() .collect()
@ -521,19 +551,16 @@ pub fn get_mint_info(mint: &Option<Mint>) -> MintValue {
} }
} }
pub fn get_outputs_info( pub fn get_outputs_info(outputs: &[MintedTransactionOutput]) -> Vec<TransactionOutput> {
to_tx_out: fn(TransactionOutput) -> TxOut,
outputs: &[MintedTransactionOutput],
) -> Vec<TxOut> {
outputs outputs
.iter() .iter()
.cloned() .cloned()
.map(|output| to_tx_out(sort_tx_out_value(&output.into()))) .map(|output| sort_tx_out_value(&output.into()))
.collect() .collect()
} }
pub fn get_fee_info(fee: &Coin) -> Value { pub fn get_fee_info(fee: &Coin) -> Coin {
Value::Coin(*fee) *fee
} }
pub fn get_certificates_info(certificates: &Option<NonEmptySet<Certificate>>) -> Vec<Certificate> { pub fn get_certificates_info(certificates: &Option<NonEmptySet<Certificate>>) -> Vec<Certificate> {
@ -649,7 +676,7 @@ fn script_purpose_builder<'a>(
RedeemerTag::Spend => inputs RedeemerTag::Spend => inputs
.get(index) .get(index)
.cloned() .cloned()
.map(|i| ScriptPurpose::Spending(i.out_ref)), .map(|i| ScriptPurpose::Spending(i.out_ref, ())),
RedeemerTag::Cert => certificates RedeemerTag::Cert => certificates
.get(index) .get(index)
.cloned() .cloned()
@ -756,13 +783,13 @@ pub fn find_script(
})? })?
.get(redeemer.index as usize) .get(redeemer.index as usize)
.ok_or(Error::MissingScriptForRedeemer) .ok_or(Error::MissingScriptForRedeemer)
.and_then(|input| match input.resolved.address() { .and_then(|input| match output_address(&input.resolved) {
Address::Shelley(shelley_address) => { Address::Shelley(shelley_address) => {
let hash = shelley_address.payment().as_hash(); let hash = shelley_address.payment().as_hash();
let script = lookup_script(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?)))) 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 { match value {
alonzo::Value::Coin(coin) => Value::Coin(*coin), alonzo::Value::Coin(coin) => Value::Coin(*coin),
alonzo::Value::Multiasset(coin, assets) if assets.is_empty() => 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 // --------------------- Sorting
fn sort_tx_out_value(tx_output: &TransactionOutput) -> TransactionOutput { 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)) 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::<TransactionInput>::decode_fragment(inputs_bytes.as_slice()).unwrap();
let outputs = Vec::<TransactionOutput>::decode_fragment(outputs_bytes.as_slice()).unwrap();
let resolved_inputs: Vec<ResolvedInput> = 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())
}
}

View File

@ -1,23 +1,20 @@
use super::script_context::{ScriptContext, ScriptPurpose, TimeRange, TxInInfo, TxInfo, TxOut}; use super::script_context::{
use crate::machine::runtime::{convert_constr_to_tag, ANY_TAG}; 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_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_crypto::hash::Hash;
use pallas_primitives::conway::{ use pallas_primitives::conway::{
AssetName, BigInt, Certificate, Constr, DatumOption, Mint, PlutusData, PseudoScript, Redeemer, AssetName, BigInt, Certificate, Coin, Constr, DatumOption, Mint, PlutusData, PolicyId,
ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value, PseudoScript, Redeemer, ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value,
}; };
use pallas_traverse::ComputeHash; 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>) -> PlutusData { fn wrap_multiple_with_constr(index: u64, data: Vec<PlutusData>) -> PlutusData {
let converted = convert_constr_to_tag(index); let converted = convert_constr_to_tag(index);
PlutusData::Constr(Constr { PlutusData::Constr(Constr {
@ -27,17 +24,20 @@ fn wrap_multiple_with_constr(index: u64, data: Vec<PlutusData>) -> PlutusData {
}) })
} }
fn wrap_with_constr(index: u64, data: PlutusData) -> PlutusData {
wrap_multiple_with_constr(index, vec![data])
}
fn empty_constr(index: u64) -> PlutusData { fn empty_constr(index: u64) -> PlutusData {
let converted = convert_constr_to_tag(index); wrap_multiple_with_constr(index, vec![])
PlutusData::Constr(Constr {
tag: converted.unwrap_or(ANY_TAG),
any_constructor: converted.map_or(Some(index), |_| None),
fields: vec![],
})
} }
struct WithWrappedTransactionId<'a, T>(&'a T); struct WithWrappedTransactionId<'a, T>(&'a T);
struct WithZeroAdaAsset<'a, T>(&'a T);
struct WithOptionDatum<'a, T>(&'a T);
pub trait ToPlutusData { pub trait ToPlutusData {
fn to_plutus_data(&self) -> PlutusData; fn to_plutus_data(&self) -> PlutusData;
} }
@ -165,6 +165,19 @@ where
} }
} }
impl<'a> ToPlutusData for WithWrappedTransactionId<'a, KeyValuePairs<ScriptPurpose, Redeemer>> {
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<A: ToPlutusData> ToPlutusData for Option<A> { impl<A: ToPlutusData> ToPlutusData for Option<A> {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
match self { match self {
@ -174,7 +187,6 @@ impl<A: ToPlutusData> ToPlutusData for Option<A> {
} }
} }
// Does this here surely overwrite Option from above for DatumOption?
impl ToPlutusData for Option<DatumOption> { impl ToPlutusData for Option<DatumOption> {
// NoOutputDatum = 0 | OutputDatumHash = 1 | OutputDatum = 2 // NoOutputDatum = 0 | OutputDatumHash = 1 | OutputDatum = 2
fn to_plutus_data(&self) -> PlutusData { 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 { impl ToPlutusData for Value {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
match self { 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(), Bytes::from(vec![]).to_plutus_data(),
PlutusData::Map(KeyValuePairs::Def(vec![( PlutusData::Map(KeyValuePairs::Def(vec![(
AssetName::from(vec![]).to_plutus_data(), 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 { impl ToPlutusData for MintValue {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![( value_to_plutus_data(
Bytes::from(vec![]).to_plutus_data(), self.mint_value.iter(),
PlutusData::Map(KeyValuePairs::Def(vec![( |amount| i64::from(amount).to_plutus_data(),
AssetName::from(vec![]).to_plutus_data(), vec![],
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))
} }
} }
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<Item = &'a (PolicyId, NonEmptyKeyValuePairs<AssetName, Q>)>,
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 { impl ToPlutusData for ScriptRef {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
match &self { match &self {
@ -299,67 +344,100 @@ impl ToPlutusData for ScriptRef {
} }
} }
impl ToPlutusData for TxOut { impl<'a> ToPlutusData for WithOptionDatum<'a, WithZeroAdaAsset<'a, Vec<TransactionOutput>>> {
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<TransactionOutput>> {
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::<Hash<32>>.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 { fn to_plutus_data(&self) -> PlutusData {
match self { match self {
TxOut::V1(output) => match output { TransactionOutput::Legacy(legacy_output) => {
// TransactionOutput::Legacy(legacy_output) => wrap_multiple_with_constr( from_alonzo_output(legacy_output).to_plutus_data()
// 0, }
// vec![ TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr(
// Address::from_bytes(&legacy_output.address) 0,
// .unwrap() vec![
// .to_plutus_data(), Address::from_bytes(&post_alonzo_output.address)
// legacy_output.amount.to_plutus_data(), .unwrap()
// legacy_output.datum_hash.to_plutus_data(), .to_plutus_data(),
// ], post_alonzo_output.value.to_plutus_data(),
// ), post_alonzo_output.datum_option.to_plutus_data(),
TransactionOutput::Legacy(..) => unimplemented!("TransactionOutput::Legacy"), post_alonzo_output
TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr( .script_ref
0, .as_ref()
vec![ .map(|s| s.clone().unwrap())
Address::from_bytes(&post_alonzo_output.address) .to_plutus_data(),
.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::<Hash<32>>.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::<ScriptRef>.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(),
],
),
},
} }
} }
} }
@ -570,24 +648,57 @@ impl ToPlutusData for TimeRange {
} }
} }
impl<'a> ToPlutusData for WithWrappedTransactionId<'a, Vec<TxInInfo>> { impl<'a> ToPlutusData
for WithOptionDatum<'a, WithZeroAdaAsset<'a, WithWrappedTransactionId<'a, Vec<TxInInfo>>>>
{
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
PlutusData::Array( PlutusData::Array(
self.0 self.0
.0
.0
.iter() .iter()
.map(|p| WithWrappedTransactionId(p).to_plutus_data()) .map(|p| {
WithOptionDatum(&WithZeroAdaAsset(&WithWrappedTransactionId(p)))
.to_plutus_data()
})
.collect(), .collect(),
) )
} }
} }
impl<'a> ToPlutusData for WithWrappedTransactionId<'a, TxInInfo> { impl<'a> ToPlutusData for WithZeroAdaAsset<'a, WithWrappedTransactionId<'a, Vec<TxInInfo>>> {
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 { fn to_plutus_data(&self) -> PlutusData {
wrap_multiple_with_constr( wrap_multiple_with_constr(
0, 0,
vec![ vec![
WithWrappedTransactionId(&self.0.out_ref).to_plutus_data(), WithWrappedTransactionId(&self.0 .0.out_ref).to_plutus_data(),
self.0.resolved.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 { impl ToPlutusData for ScriptPurpose {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
match self { match self {
ScriptPurpose::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()), 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) => { ScriptPurpose::Rewarding(stake_credential) => {
wrap_with_constr(2, stake_credential.to_plutus_data()) wrap_with_constr(2, stake_credential.to_plutus_data())
} }
@ -618,16 +740,37 @@ impl ToPlutusData for ScriptPurpose {
} }
} }
impl<T> ToPlutusData for ScriptInfo<T>
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 { impl ToPlutusData for TxInfo {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
match self { match self {
TxInfo::V1(tx_info) => wrap_multiple_with_constr( TxInfo::V1(tx_info) => wrap_multiple_with_constr(
0, 0,
vec![ vec![
WithWrappedTransactionId(&tx_info.inputs).to_plutus_data(), WithOptionDatum(&WithZeroAdaAsset(&WithWrappedTransactionId(
tx_info.outputs.to_plutus_data(), &tx_info.inputs,
tx_info.fee.to_plutus_data(), )))
tx_info.mint.to_plutus_data(), .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.certificates.to_plutus_data(),
tx_info.withdrawals.to_plutus_data(), tx_info.withdrawals.to_plutus_data(),
tx_info.valid_range.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( TxInfo::V2(tx_info) => wrap_multiple_with_constr(
0, 0,
vec![ vec![
WithWrappedTransactionId(&tx_info.inputs).to_plutus_data(), WithZeroAdaAsset(&WithWrappedTransactionId(&tx_info.inputs)).to_plutus_data(),
WithWrappedTransactionId(&tx_info.reference_inputs).to_plutus_data(), WithZeroAdaAsset(&WithWrappedTransactionId(&tx_info.reference_inputs))
tx_info.outputs.to_plutus_data(), .to_plutus_data(),
tx_info.fee.to_plutus_data(), WithZeroAdaAsset(&tx_info.outputs).to_plutus_data(),
tx_info.mint.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.certificates.to_plutus_data(),
tx_info.withdrawals.to_plutus_data(), tx_info.withdrawals.to_plutus_data(),
tx_info.valid_range.to_plutus_data(), tx_info.valid_range.to_plutus_data(),
tx_info.signatories.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(), tx_info.data.to_plutus_data(),
wrap_with_constr(0, tx_info.id.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.redeemers.to_plutus_data(),
tx_info.data.to_plutus_data(), tx_info.data.to_plutus_data(),
tx_info.id.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 { impl ToPlutusData for ScriptContext {
fn to_plutus_data(&self) -> PlutusData { fn to_plutus_data(&self) -> PlutusData {
wrap_multiple_with_constr( match self {
0, ScriptContext::V1V2 { tx_info, purpose } => wrap_multiple_with_constr(
vec![self.tx_info.to_plutus_data(), self.purpose.to_plutus_data()], 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(),
],
),
}
} }
} }

View File

@ -1,12 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
AIKEN=${1:-"cargo run -r --quiet --"}
TESTS=() TESTS=()
for lang in $(ls script_context); do for lang in $(ls script_context); do
for interaction in $(find script_context/$lang/validators -type f); do for interaction in $(find script_context/$lang/validators -type f); do
title=$(basename $interaction) title=$(basename $interaction)
title="${title%.*}" title="${title%.*}"
cd script_context/$lang cd script_context/$lang
./test.sh $title & ./test.sh $title "$AIKEN" &
TESTS+=("$title,$lang,$!") TESTS+=("$title,$lang,$!")
cd - 1>/dev/null cd - 1>/dev/null
done done

View File

@ -13,4 +13,4 @@ requirements = []
source = "github" source = "github"
[etags] [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"]

View File

@ -12,6 +12,8 @@ if [ -z $TITLE ]; then
exit 1 exit 1
fi fi
AIKEN=${2:-"cargo run -r --quiet --"}
if ! command -v jq &> /dev/null if ! command -v jq &> /dev/null
then then
echo "\033[1mjq\033[0m missing from system but required." echo "\033[1mjq\033[0m missing from system but required."
@ -24,7 +26,7 @@ then
exit 1 exit 1
fi fi
cargo run -r --quiet -- build 2>/dev/null $AIKEN build 2>/dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
exit $? exit $?
fi 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_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 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