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]]
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",

View File

@ -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

View File

@ -32,19 +32,20 @@ pub fn eval_redeemer(
tx_info: TxInfo,
program: Program<NamedDeBruijn>,
) -> Result<Redeemer, Error> {
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 {
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());
.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))

View File

@ -81,7 +81,7 @@ pub fn scripts_needed(tx: &MintedTx, utxos: &[ResolvedInput]) -> Result<ScriptsN
if let Address::Shelley(a) = address {
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)
}
ScriptPurpose::Spending(txin) => {
ScriptPurpose::Spending(txin, ()) => {
let redeemer_key = tx_body
.inputs
.iter()

View File

@ -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 {
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(),
};
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 {
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(),
};
match self {
TxOut::V1(output) => datum_from_output(output),
TxOut::V2(output) => datum_from_output(output),
}
}
}
/// 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<T> {
Minting(PolicyId),
Spending(TransactionInput),
Spending(TransactionInput, T),
Rewarding(StakeCredential),
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)]
pub enum ScriptVersion {
Native(NativeScript),
@ -184,7 +185,7 @@ impl DataLookupTable {
#[derive(Debug, PartialEq, Clone)]
pub struct TxInfoV1 {
pub inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>,
pub outputs: Vec<TransactionOutput>,
pub fee: Value,
pub mint: MintValue,
pub certificates: Vec<Certificate>,
@ -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<TxInInfo>,
pub reference_inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>,
pub outputs: Vec<TransactionOutput>,
pub fee: Value,
pub mint: MintValue,
pub certificates: Vec<Certificate>,
@ -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<TxInInfo>,
pub reference_inputs: Vec<TxInInfo>,
pub outputs: Vec<TxOut>,
pub fee: Value,
pub outputs: Vec<TransactionOutput>,
pub fee: Coin,
pub mint: MintValue,
pub certificates: Vec<Certificate>,
pub withdrawals: KeyValuePairs<Address, Coin>,
@ -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<ScriptPurpose> {
pub fn into_script_context(
self,
redeemer: &Redeemer,
datum: Option<&PlutusData>,
) -> Option<ScriptContext> {
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<ScriptPurpose>,
},
V3 {
tx_info: TxInfo,
redeemer: PlutusData,
purpose: ScriptInfo<Option<PlutusData>>,
},
}
//---- 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<Mint>) -> MintValue {
}
}
pub fn get_outputs_info(
to_tx_out: fn(TransactionOutput) -> TxOut,
outputs: &[MintedTransactionOutput],
) -> Vec<TxOut> {
pub fn get_outputs_info(outputs: &[MintedTransactionOutput]) -> Vec<TransactionOutput> {
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<NonEmptySet<Certificate>>) -> Vec<Certificate> {
@ -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::<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 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>) -> 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>) -> 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<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> {
fn to_plutus_data(&self) -> PlutusData {
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> {
// NoOutputDatum = 0 | OutputDatumHash = 1 | OutputDatum = 2
fn to_plutus_data(&self) -> PlutusData {
@ -224,57 +236,81 @@ 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![(
Bytes::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![(
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))
}
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 ToPlutusData for MintValue {
impl<'a> ToPlutusData for WithZeroAdaAsset<'a, MintValue> {
fn to_plutus_data(&self) -> PlutusData {
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![(
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(),
0_i64.to_plutus_data(),
)])),
)];
)],
)
}
}
for (policy_id, assets) in self.mint_value.iter() {
impl ToPlutusData for MintValue {
fn to_plutus_data(&self) -> PlutusData {
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<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(), i64::from(amount).to_plutus_data()));
assets_vec.push((asset.to_plutus_data(), from_quantity(amount)));
}
data_vec.push((
policy_id.to_plutus_data(),
@ -284,6 +320,15 @@ impl ToPlutusData for MintValue {
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 {
@ -299,51 +344,85 @@ impl ToPlutusData for ScriptRef {
}
}
impl ToPlutusData for TxOut {
impl<'a> ToPlutusData for WithOptionDatum<'a, WithZeroAdaAsset<'a, Vec<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"),
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(),
post_alonzo_output.value.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(),
},
],
),
},
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"),
}
}
}
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 {
TransactionOutput::Legacy(legacy_output) => {
from_alonzo_output(legacy_output).to_plutus_data()
}
TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr(
0,
vec![
@ -359,7 +438,6 @@ impl ToPlutusData for TxOut {
.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 {
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<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 {
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<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 {
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(
match self {
ScriptContext::V1V2 { tx_info, purpose } => wrap_multiple_with_constr(
0,
vec![self.tx_info.to_plutus_data(), self.purpose.to_plutus_data()],
)
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
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

View File

@ -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"]

View File

@ -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