griffin_core/uplc/tx/
script_context.rs

1use super::{to_plutus_data::MintValue, Error};
2use crate::pallas_addresses::{Address, Network, StakePayload};
3use crate::pallas_codec::utils::{
4    Bytes, KeyValuePairs, NonEmptyKeyValuePairs, NonEmptySet, Nullable, PositiveCoin,
5};
6use crate::pallas_crypto::hash::Hash;
7use crate::pallas_primitives::{
8    alonzo,
9    conway::{
10        AddrKeyhash, Certificate, Coin, DatumHash, DatumOption, GovAction, GovActionId, Mint,
11        MintedTransactionBody, MintedTransactionOutput, MintedTx, MintedWitnessSet, NativeScript,
12        PlutusData, PlutusScript, PolicyId, PostAlonzoTransactionOutput, ProposalProcedure,
13        PseudoDatumOption, PseudoScript, Redeemer, RedeemerTag, RedeemersKey, RequiredSigners,
14        RewardAccount, ScriptHash, StakeCredential, TransactionInput, TransactionOutput, Value,
15        Voter, VotingProcedure,
16    },
17};
18use crate::pallas_traverse::{ComputeHash, OriginalHash};
19use crate::uplc::tx::iter_redeemers;
20use alloc::{boxed::Box, string::ToString, vec::Vec};
21use core::{cmp::Ordering, ops::Deref};
22use hashbrown::HashMap;
23use itertools::Itertools;
24
25#[derive(Debug, PartialEq, Clone)]
26pub struct ResolvedInput {
27    pub input: TransactionInput,
28    pub output: TransactionOutput,
29}
30
31#[derive(Debug, PartialEq, Clone)]
32pub struct TxInInfo {
33    pub out_ref: TransactionInput,
34    pub resolved: TransactionOutput,
35}
36
37pub fn output_address(output: &TransactionOutput) -> Address {
38    match output {
39        TransactionOutput::Legacy(x) => Address::from_bytes(&x.address).unwrap(),
40        TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address).unwrap(),
41    }
42}
43
44pub fn output_datum(output: &TransactionOutput) -> Option<DatumOption> {
45    match output {
46        TransactionOutput::Legacy(x) => x.datum_hash.map(DatumOption::Hash),
47        TransactionOutput::PostAlonzo(x) => x.datum_option.clone(),
48    }
49}
50
51/// The ScriptPurpose is part of the ScriptContext is the case of Plutus V1 and V2.
52/// It is superseded by the ScriptInfo in PlutusV3.
53#[derive(Debug, PartialEq, Eq, Clone)]
54pub enum ScriptInfo<T> {
55    Minting(PolicyId),
56    Spending(TransactionInput, T),
57    Rewarding(StakeCredential),
58    Certifying(usize, Certificate),
59    Voting(Voter),
60    Proposing(usize, ProposalProcedure),
61}
62
63pub type ScriptPurpose = ScriptInfo<()>;
64
65impl ScriptPurpose {
66    pub fn into_script_info<T>(self, datum: T) -> ScriptInfo<T> {
67        match self {
68            Self::Minting(policy_id) => ScriptInfo::Minting(policy_id),
69            Self::Spending(transaction_output, ()) => {
70                ScriptInfo::Spending(transaction_output, datum)
71            }
72            Self::Rewarding(stake_credential) => ScriptInfo::Rewarding(stake_credential),
73            Self::Certifying(ix, certificate) => ScriptInfo::Certifying(ix, certificate),
74            Self::Voting(voter) => ScriptInfo::Voting(voter),
75            Self::Proposing(ix, procedure) => ScriptInfo::Proposing(ix, procedure),
76        }
77    }
78}
79
80#[derive(Debug, PartialEq, Clone)]
81pub enum ScriptVersion {
82    Native(NativeScript),
83    V1(PlutusScript<1>),
84    V2(PlutusScript<2>),
85    V3(PlutusScript<3>),
86}
87
88pub struct DataLookupTable {
89    datum: HashMap<DatumHash, PlutusData>,
90    scripts: HashMap<ScriptHash, ScriptVersion>,
91}
92
93impl DataLookupTable {
94    pub fn from_transaction(tx: &MintedTx, utxos: &[ResolvedInput]) -> DataLookupTable {
95        let mut datum = HashMap::new();
96        let mut scripts = HashMap::new();
97
98        // discovery in witness set
99
100        let plutus_data_witnesses = tx
101            .transaction_witness_set
102            .plutus_data
103            .clone()
104            .map(|s| s.to_vec())
105            .unwrap_or_default();
106
107        let scripts_native_witnesses = tx
108            .transaction_witness_set
109            .native_script
110            .clone()
111            .map(|s| s.to_vec())
112            .unwrap_or_default();
113
114        let scripts_v1_witnesses = tx
115            .transaction_witness_set
116            .plutus_v1_script
117            .clone()
118            .map(|s| s.to_vec())
119            .unwrap_or_default();
120
121        let scripts_v2_witnesses = tx
122            .transaction_witness_set
123            .plutus_v2_script
124            .clone()
125            .map(|s| s.to_vec())
126            .unwrap_or_default();
127
128        let scripts_v3_witnesses = tx
129            .transaction_witness_set
130            .plutus_v3_script
131            .clone()
132            .map(|s| s.to_vec())
133            .unwrap_or_default();
134
135        for plutus_data in plutus_data_witnesses.iter() {
136            datum.insert(plutus_data.original_hash(), plutus_data.clone().unwrap());
137        }
138
139        for script in scripts_native_witnesses.iter() {
140            scripts.insert(
141                script.compute_hash(),
142                ScriptVersion::Native(script.clone().unwrap()),
143            );
144        }
145
146        for script in scripts_v1_witnesses.iter() {
147            scripts.insert(script.compute_hash(), ScriptVersion::V1(script.clone()));
148        }
149
150        for script in scripts_v2_witnesses.iter() {
151            scripts.insert(script.compute_hash(), ScriptVersion::V2(script.clone()));
152        }
153
154        for script in scripts_v3_witnesses.iter() {
155            scripts.insert(script.compute_hash(), ScriptVersion::V3(script.clone()));
156        }
157
158        // discovery in utxos (script ref)
159
160        for utxo in utxos.iter() {
161            match &utxo.output {
162                TransactionOutput::Legacy(_) => {}
163                TransactionOutput::PostAlonzo(output) => {
164                    if let Some(script) = &output.script_ref {
165                        match &script.0 {
166                            PseudoScript::NativeScript(ns) => {
167                                scripts
168                                    .insert(ns.compute_hash(), ScriptVersion::Native(ns.clone()));
169                            }
170                            PseudoScript::PlutusV1Script(v1) => {
171                                scripts.insert(v1.compute_hash(), ScriptVersion::V1(v1.clone()));
172                            }
173                            PseudoScript::PlutusV2Script(v2) => {
174                                scripts.insert(v2.compute_hash(), ScriptVersion::V2(v2.clone()));
175                            }
176                            PseudoScript::PlutusV3Script(v3) => {
177                                scripts.insert(v3.compute_hash(), ScriptVersion::V3(v3.clone()));
178                            }
179                        }
180                    }
181                }
182            }
183        }
184
185        DataLookupTable { datum, scripts }
186    }
187}
188
189impl DataLookupTable {
190    pub fn scripts(&self) -> HashMap<ScriptHash, ScriptVersion> {
191        self.scripts.clone()
192    }
193}
194
195#[derive(Debug, PartialEq, Clone)]
196pub struct TxInfoV1 {
197    pub inputs: Vec<TxInInfo>,
198    pub outputs: Vec<TransactionOutput>,
199    pub fee: Value,
200    pub mint: MintValue,
201    pub certificates: Vec<Certificate>,
202    pub withdrawals: Vec<(Address, Coin)>,
203    pub valid_range: TimeRange,
204    pub signatories: Vec<AddrKeyhash>,
205    pub data: Vec<(DatumHash, PlutusData)>,
206    pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
207    pub id: Hash<32>,
208}
209
210impl TxInfoV1 {
211    pub fn from_transaction(
212        tx: &MintedTx,
213        utxos: &[ResolvedInput],
214        slot_config: &SlotConfig,
215    ) -> Result<TxInfo, Error> {
216        if tx.transaction_body.reference_inputs.is_some() {
217            return Err(Error::ScriptAndInputRefNotAllowed);
218        }
219
220        let inputs = get_tx_in_info_v1(&tx.transaction_body.inputs, utxos)?;
221        let certificates = get_certificates_info(&tx.transaction_body.certificates);
222        let withdrawals =
223            KeyValuePairs::from(get_withdrawals_info(&tx.transaction_body.withdrawals));
224        let mint = get_mint_info(&tx.transaction_body.mint);
225
226        let redeemers = get_redeemers_info(
227            &tx.transaction_witness_set,
228            script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[], &[]),
229        )?;
230
231        Ok(TxInfo::V1(TxInfoV1 {
232            inputs,
233            outputs: get_outputs_info(&tx.transaction_body.outputs[..]),
234            fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)),
235            mint,
236            certificates,
237            withdrawals: withdrawals.into(),
238            valid_range: get_validity_range_info(&tx.transaction_body, slot_config)?,
239            signatories: get_signatories_info(&tx.transaction_body.required_signers),
240            data: get_data_info(&tx.transaction_witness_set),
241            redeemers,
242            id: tx.transaction_body.original_hash(),
243        }))
244    }
245}
246
247#[derive(Debug, PartialEq, Clone)]
248pub struct TxInfoV2 {
249    pub inputs: Vec<TxInInfo>,
250    pub reference_inputs: Vec<TxInInfo>,
251    pub outputs: Vec<TransactionOutput>,
252    pub fee: Value,
253    pub mint: MintValue,
254    pub certificates: Vec<Certificate>,
255    pub withdrawals: KeyValuePairs<Address, Coin>,
256    pub valid_range: TimeRange,
257    pub signatories: Vec<AddrKeyhash>,
258    pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
259    pub data: KeyValuePairs<DatumHash, PlutusData>,
260    pub id: Hash<32>,
261}
262
263impl TxInfoV2 {
264    pub fn from_transaction(
265        tx: &MintedTx,
266        utxos: &[ResolvedInput],
267        slot_config: &SlotConfig,
268    ) -> Result<TxInfo, Error> {
269        let inputs = get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)?;
270        let certificates = get_certificates_info(&tx.transaction_body.certificates);
271        let withdrawals =
272            KeyValuePairs::from(get_withdrawals_info(&tx.transaction_body.withdrawals));
273        let mint = get_mint_info(&tx.transaction_body.mint);
274
275        let redeemers = get_redeemers_info(
276            &tx.transaction_witness_set,
277            script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[], &[]),
278        )?;
279
280        let reference_inputs = tx
281            .transaction_body
282            .reference_inputs
283            .clone()
284            .map(|refs| get_tx_in_info_v2(&refs[..], utxos))
285            .transpose()?
286            .unwrap_or_default();
287
288        Ok(TxInfo::V2(TxInfoV2 {
289            inputs,
290            reference_inputs,
291            outputs: get_outputs_info(&tx.transaction_body.outputs[..]),
292            fee: Value::Coin(get_fee_info(&tx.transaction_body.fee)),
293            mint,
294            certificates,
295            withdrawals,
296            valid_range: get_validity_range_info(&tx.transaction_body, slot_config)?,
297            signatories: get_signatories_info(&tx.transaction_body.required_signers),
298            data: KeyValuePairs::from(get_data_info(&tx.transaction_witness_set)),
299            redeemers,
300            id: tx.transaction_body.original_hash(),
301        }))
302    }
303}
304
305#[derive(Debug, PartialEq, Clone)]
306pub struct TxInfoV3 {
307    pub inputs: Vec<TxInInfo>,
308    pub reference_inputs: Vec<TxInInfo>,
309    pub outputs: Vec<TransactionOutput>,
310    pub fee: Coin,
311    pub mint: MintValue,
312    pub certificates: Vec<Certificate>,
313    pub withdrawals: KeyValuePairs<Address, Coin>,
314    pub valid_range: TimeRange,
315    pub signatories: Vec<AddrKeyhash>,
316    pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
317    pub data: KeyValuePairs<DatumHash, PlutusData>,
318    pub id: Hash<32>,
319    pub votes: KeyValuePairs<Voter, KeyValuePairs<GovActionId, VotingProcedure>>,
320    pub proposal_procedures: Vec<ProposalProcedure>,
321    pub current_treasury_amount: Option<Coin>,
322    pub treasury_donation: Option<PositiveCoin>,
323}
324
325impl TxInfoV3 {
326    pub fn from_transaction(
327        tx: &MintedTx,
328        utxos: &[ResolvedInput],
329        slot_config: &SlotConfig,
330    ) -> Result<TxInfo, Error> {
331        let inputs = get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)?;
332
333        let certificates = get_certificates_info(&tx.transaction_body.certificates);
334
335        let withdrawals =
336            KeyValuePairs::from(get_withdrawals_info(&tx.transaction_body.withdrawals));
337
338        let mint = get_mint_info(&tx.transaction_body.mint);
339
340        let proposal_procedures =
341            get_proposal_procedures_info(&tx.transaction_body.proposal_procedures);
342
343        let votes = get_votes_info(&tx.transaction_body.voting_procedures);
344
345        let redeemers = get_redeemers_info(
346            &tx.transaction_witness_set,
347            script_purpose_builder(
348                &inputs[..],
349                &mint,
350                &certificates,
351                &withdrawals,
352                &proposal_procedures,
353                &votes.iter().map(|(k, _v)| k).collect_vec()[..],
354            ),
355        )?;
356
357        let reference_inputs = tx
358            .transaction_body
359            .reference_inputs
360            .clone()
361            .map(|refs| get_tx_in_info_v2(&refs[..], utxos))
362            .transpose()?
363            .unwrap_or_default();
364
365        Ok(TxInfo::V3(TxInfoV3 {
366            inputs,
367            reference_inputs,
368            outputs: get_outputs_info(&tx.transaction_body.outputs[..]),
369            fee: get_fee_info(&tx.transaction_body.fee),
370            mint,
371            certificates,
372            withdrawals,
373            valid_range: get_validity_range_info(&tx.transaction_body, slot_config)?,
374            signatories: get_signatories_info(&tx.transaction_body.required_signers),
375            data: KeyValuePairs::from(get_data_info(&tx.transaction_witness_set)),
376            redeemers,
377            proposal_procedures,
378            votes,
379            current_treasury_amount: get_current_treasury_amount_info(
380                &tx.transaction_body.treasury_value,
381            ),
382            treasury_donation: get_treasury_donation_info(&tx.transaction_body.donation),
383            id: tx.transaction_body.original_hash(),
384        }))
385    }
386}
387
388#[derive(Debug, PartialEq, Clone)]
389pub enum TxInfo {
390    V1(TxInfoV1),
391    V2(TxInfoV2),
392    V3(TxInfoV3),
393}
394
395impl TxInfo {
396    pub fn into_script_context(
397        self,
398        redeemer: &Redeemer,
399        datum: Option<&PlutusData>,
400    ) -> Option<ScriptContext> {
401        match self {
402            TxInfo::V1(TxInfoV1 { ref redeemers, .. })
403            | TxInfo::V2(TxInfoV2 { ref redeemers, .. }) => redeemers
404                .iter()
405                .find_map(move |(purpose, some_redeemer)| {
406                    if redeemer.tag == some_redeemer.tag && redeemer.index == some_redeemer.index {
407                        Some(purpose.clone())
408                    } else {
409                        None
410                    }
411                })
412                .map(move |purpose| ScriptContext::V1V2 {
413                    tx_info: self.into(),
414                    purpose: purpose.clone().into(),
415                }),
416
417            TxInfo::V3(TxInfoV3 { ref redeemers, .. }) => redeemers
418                .iter()
419                .find_map(move |(purpose, some_redeemer)| {
420                    if redeemer.tag == some_redeemer.tag && redeemer.index == some_redeemer.index {
421                        Some(purpose.clone())
422                    } else {
423                        None
424                    }
425                })
426                .map(move |purpose| ScriptContext::V3 {
427                    tx_info: self.into(),
428                    redeemer: redeemer.data.clone(),
429                    purpose: purpose.clone().into_script_info(datum.cloned()).into(),
430                }),
431        }
432    }
433
434    pub fn inputs(&self) -> &[TxInInfo] {
435        match self {
436            TxInfo::V1(info) => &info.inputs,
437            TxInfo::V2(info) => &info.inputs,
438            TxInfo::V3(info) => &info.inputs,
439        }
440    }
441
442    pub fn mint(&self) -> &MintValue {
443        match self {
444            TxInfo::V1(info) => &info.mint,
445            TxInfo::V2(info) => &info.mint,
446            TxInfo::V3(info) => &info.mint,
447        }
448    }
449
450    pub fn withdrawals(&self) -> &[(Address, Coin)] {
451        match self {
452            TxInfo::V1(info) => &info.withdrawals[..],
453            TxInfo::V2(info) => &info.withdrawals[..],
454            TxInfo::V3(info) => &info.withdrawals[..],
455        }
456    }
457
458    pub fn certificates(&self) -> &[Certificate] {
459        match self {
460            TxInfo::V1(info) => &info.certificates[..],
461            TxInfo::V2(info) => &info.certificates[..],
462            TxInfo::V3(info) => &info.certificates[..],
463        }
464    }
465}
466
467#[derive(Debug, PartialEq, Clone)]
468pub enum ScriptContext {
469    V1V2 {
470        tx_info: Box<TxInfo>,
471        purpose: Box<ScriptPurpose>,
472    },
473    V3 {
474        tx_info: Box<TxInfo>,
475        redeemer: PlutusData,
476        purpose: Box<ScriptInfo<Option<PlutusData>>>,
477    },
478}
479
480//---- Time conversion: slot range => posix time range
481#[derive(Debug, PartialEq, Eq, Clone)]
482pub struct TimeRange {
483    pub lower_bound: Option<u64>,
484    pub upper_bound: Option<u64>,
485}
486
487pub struct SlotConfig {
488    pub slot_length: u32,
489    pub zero_slot: u64,
490    pub zero_time: u64,
491}
492
493impl Default for SlotConfig {
494    fn default() -> Self {
495        Self {
496            slot_length: 1000,
497            zero_slot: 4492800,
498            zero_time: 1596059091000,
499        }
500    }
501}
502
503// --------------------- Translations
504
505pub fn get_tx_in_info_v1(
506    inputs: &[TransactionInput],
507    utxos: &[ResolvedInput],
508) -> Result<Vec<TxInInfo>, Error> {
509    inputs
510        .iter()
511        .sorted()
512        .map(|input| {
513            let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
514                Some(resolved) => resolved,
515                None => return Err(Error::ResolvedInputNotFound(input.clone())),
516            };
517            let address = Address::from_bytes(match &utxo.output {
518                TransactionOutput::Legacy(output) => output.address.as_ref(),
519                TransactionOutput::PostAlonzo(output) => output.address.as_ref(),
520            })
521            .unwrap();
522
523            match address {
524                Address::Byron(_) => {
525                    return Err(Error::ByronAddressNotAllowed);
526                }
527                Address::Stake(_) => {
528                    return Err(Error::NoPaymentCredential);
529                }
530                _ => {}
531            };
532
533            match &utxo.output {
534                TransactionOutput::Legacy(_) => {}
535                TransactionOutput::PostAlonzo(output) => {
536                    if let Some(DatumOption::Data(_)) = output.datum_option {
537                        return Err(Error::InlineDatumNotAllowed);
538                    }
539
540                    if output.script_ref.is_some() {
541                        return Err(Error::ScriptAndInputRefNotAllowed);
542                    }
543                }
544            }
545
546            Ok(TxInInfo {
547                out_ref: utxo.input.clone(),
548                resolved: sort_tx_out_value(&utxo.output),
549            })
550        })
551        .collect()
552}
553
554pub fn get_tx_in_info_v2(
555    inputs: &[TransactionInput],
556    utxos: &[ResolvedInput],
557) -> Result<Vec<TxInInfo>, Error> {
558    inputs
559        .iter()
560        .sorted()
561        .map(|input| {
562            let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
563                Some(resolved) => resolved,
564                None => return Err(Error::ResolvedInputNotFound(input.clone())),
565            };
566            let address = Address::from_bytes(match &utxo.output {
567                TransactionOutput::Legacy(output) => output.address.as_ref(),
568                TransactionOutput::PostAlonzo(output) => output.address.as_ref(),
569            })
570            .unwrap();
571
572            match address {
573                Address::Byron(_) => {
574                    return Err(Error::ByronAddressNotAllowed);
575                }
576                Address::Stake(_) => {
577                    return Err(Error::NoPaymentCredential);
578                }
579                _ => {}
580            };
581
582            Ok(TxInInfo {
583                out_ref: utxo.input.clone(),
584                resolved: sort_tx_out_value(&utxo.output),
585            })
586        })
587        .collect()
588}
589
590pub fn get_mint_info(mint: &Option<Mint>) -> MintValue {
591    MintValue {
592        mint_value: mint
593            .as_ref()
594            .map(sort_mint)
595            .unwrap_or(NonEmptyKeyValuePairs::Indef(vec![])),
596    }
597}
598
599pub fn get_outputs_info(outputs: &[MintedTransactionOutput]) -> Vec<TransactionOutput> {
600    outputs
601        .iter()
602        .cloned()
603        .map(|output| sort_tx_out_value(&output.into()))
604        .collect()
605}
606
607pub fn get_fee_info(fee: &Coin) -> Coin {
608    *fee
609}
610
611pub fn get_current_treasury_amount_info(amount: &Option<Coin>) -> Option<Coin> {
612    *amount
613}
614
615pub fn get_treasury_donation_info(amount: &Option<PositiveCoin>) -> Option<PositiveCoin> {
616    *amount
617}
618
619pub fn get_certificates_info(certificates: &Option<NonEmptySet<Certificate>>) -> Vec<Certificate> {
620    certificates.clone().map(|s| s.to_vec()).unwrap_or_default()
621}
622
623pub fn get_proposal_procedures_info(
624    proposal_procedures: &Option<NonEmptySet<ProposalProcedure>>,
625) -> Vec<ProposalProcedure> {
626    proposal_procedures
627        .clone()
628        .map(|s| s.to_vec())
629        .unwrap_or_default()
630}
631
632pub fn get_withdrawals_info(
633    withdrawals: &Option<NonEmptyKeyValuePairs<RewardAccount, Coin>>,
634) -> Vec<(Address, Coin)> {
635    withdrawals
636        .clone()
637        .map(|w| {
638            w.into_iter()
639                .sorted_by(|(accnt_a, _), (accnt_b, _)| sort_reward_accounts(accnt_a, accnt_b))
640                .map(|(reward_account, coin)| (Address::from_bytes(&reward_account).unwrap(), coin))
641                .collect()
642        })
643        .unwrap_or_default()
644}
645
646pub fn get_validity_range_info(
647    body: &MintedTransactionBody,
648    slot_config: &SlotConfig,
649) -> Result<TimeRange, Error> {
650    fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> Result<u64, Error> {
651        if slot < sc.zero_slot {
652            return Err(Error::SlotTooFarInThePast {
653                oldest_allowed: sc.zero_slot,
654            });
655        }
656        let ms_after_begin = (slot - sc.zero_slot) * sc.slot_length as u64;
657        Ok(sc.zero_time + ms_after_begin)
658    }
659
660    fn slot_range_to_posix_time_range(
661        slot_range: TimeRange,
662        sc: &SlotConfig,
663    ) -> Result<TimeRange, Error> {
664        Ok(TimeRange {
665            lower_bound: slot_range
666                .lower_bound
667                .map(|lower_bound| slot_to_begin_posix_time(lower_bound, sc))
668                .transpose()?,
669            upper_bound: slot_range
670                .upper_bound
671                .map(|upper_bound| slot_to_begin_posix_time(upper_bound, sc))
672                .transpose()?,
673        })
674    }
675
676    slot_range_to_posix_time_range(
677        TimeRange {
678            lower_bound: body.validity_interval_start,
679            upper_bound: body.ttl,
680        },
681        slot_config,
682    )
683}
684
685pub fn get_signatories_info(signers: &Option<RequiredSigners>) -> Vec<AddrKeyhash> {
686    signers
687        .as_deref()
688        .map(|s| s.iter().cloned().sorted().collect())
689        .unwrap_or_default()
690}
691
692pub fn get_data_info(witness_set: &MintedWitnessSet) -> Vec<(DatumHash, PlutusData)> {
693    witness_set
694        .plutus_data
695        .as_deref()
696        .map(|s| {
697            s.iter()
698                .cloned()
699                .map(|d| (d.original_hash(), d.clone().unwrap()))
700                .sorted()
701                .collect()
702        })
703        .unwrap_or_default()
704}
705
706pub fn get_redeemers_info<'a>(
707    witness_set: &'a MintedWitnessSet,
708    to_script_purpose: impl Fn(RedeemersKey) -> Result<ScriptPurpose, Error> + 'a,
709) -> Result<KeyValuePairs<ScriptPurpose, Redeemer>, Error> {
710    Ok(KeyValuePairs::from(
711        witness_set
712            .redeemer
713            .as_deref()
714            .map(|m| {
715                iter_redeemers(m)
716                    .sorted_by(|(a, _, _), (b, _, _)| sort_redeemers(a, b))
717                    .map(|(key, data, ex_units)| {
718                        let redeemer = Redeemer {
719                            tag: key.tag,
720                            index: key.index,
721                            data: data.clone(),
722                            ex_units,
723                        };
724
725                        to_script_purpose(key).map(|purpose| (purpose, redeemer))
726                    })
727                    .collect::<Result<Vec<_>, _>>()
728            })
729            .transpose()?
730            .unwrap_or_default(),
731    ))
732}
733
734pub fn get_votes_info(
735    votes: &Option<
736        NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId, VotingProcedure>>,
737    >,
738) -> KeyValuePairs<Voter, KeyValuePairs<GovActionId, VotingProcedure>> {
739    KeyValuePairs::from(
740        votes
741            .as_deref()
742            .map(|votes| {
743                votes
744                    .iter()
745                    .sorted_by(|(a, _), (b, _)| sort_voters(a, b))
746                    .cloned()
747                    .map(|(voter, actions)| {
748                        (
749                            voter,
750                            KeyValuePairs::from(
751                                actions
752                                    .iter()
753                                    .sorted_by(|(a, _), (b, _)| sort_gov_action_id(a, b))
754                                    .cloned()
755                                    .collect::<Vec<_>>(),
756                            ),
757                        )
758                    })
759                    .collect_vec()
760            })
761            .unwrap_or_default(),
762    )
763}
764
765fn script_purpose_builder<'a>(
766    inputs: &'a [TxInInfo],
767    mint: &'a MintValue,
768    certificates: &'a [Certificate],
769    withdrawals: &'a KeyValuePairs<Address, Coin>,
770    proposal_procedures: &'a [ProposalProcedure],
771    votes: &'a [&'a Voter],
772) -> impl Fn(RedeemersKey) -> Result<ScriptPurpose, Error> + 'a {
773    move |redeemer: RedeemersKey| {
774        let tag = redeemer.tag;
775        let index = redeemer.index as usize;
776
777        match tag {
778            RedeemerTag::Mint => mint
779                .mint_value
780                .get(index)
781                .map(|(policy_id, _)| ScriptPurpose::Minting(*policy_id)),
782
783            RedeemerTag::Spend => inputs
784                .get(index)
785                .cloned()
786                .map(|i| ScriptPurpose::Spending(i.out_ref, ())),
787
788            RedeemerTag::Cert => certificates
789                .get(index)
790                .cloned()
791                .map(|c| ScriptPurpose::Certifying(index, c)),
792
793            RedeemerTag::Reward => withdrawals
794                .get(index)
795                .cloned()
796                .map(|(address, _)| match address {
797                    Address::Stake(stake_address) => match stake_address.payload() {
798                        StakePayload::Script(script_hash) => Ok(ScriptPurpose::Rewarding(
799                            StakeCredential::ScriptHash(*script_hash),
800                        )),
801                        StakePayload::Stake(_) => Err(Error::NonScriptWithdrawal),
802                    },
803                    _ => Err(Error::BadWithdrawalAddress),
804                })
805                .transpose()?,
806
807            RedeemerTag::Vote => votes
808                .get(index)
809                .cloned()
810                .cloned()
811                .map(ScriptPurpose::Voting),
812
813            RedeemerTag::Propose => proposal_procedures
814                .get(index)
815                .cloned()
816                .map(|p| ScriptPurpose::Proposing(index, p)),
817        }
818        .ok_or(Error::ExtraneousRedeemer)
819    }
820}
821
822pub fn find_script(
823    redeemer: &Redeemer,
824    tx: &MintedTx,
825    utxos: &[ResolvedInput],
826    lookup_table: &DataLookupTable,
827) -> Result<(ScriptVersion, Option<PlutusData>), Error> {
828    let lookup_script = |script_hash: &ScriptHash| match lookup_table.scripts.get(script_hash) {
829        Some(s) => Ok((s.clone(), None)),
830        None => Err(Error::MissingRequiredScript {
831            hash: script_hash.to_string(),
832        }),
833    };
834
835    let lookup_datum = |datum: Option<DatumOption>| match datum {
836        Some(DatumOption::Hash(hash)) => match lookup_table.datum.get(&hash) {
837            Some(d) => Ok(Some(d.clone())),
838            None => Err(Error::MissingRequiredDatum {
839                hash: hash.to_string(),
840            }),
841        },
842        Some(DatumOption::Data(data)) => Ok(Some(data.0.clone())),
843        None => Ok(None),
844    };
845
846    match redeemer.tag {
847        RedeemerTag::Mint => get_mint_info(&tx.transaction_body.mint)
848            .mint_value
849            .get(redeemer.index as usize)
850            .ok_or(Error::MissingScriptForRedeemer)
851            .and_then(|(policy_id, _)| {
852                let policy_id_array: [u8; 28] = policy_id.to_vec().try_into().unwrap();
853                let hash = Hash::from(policy_id_array);
854                lookup_script(&hash)
855            }),
856
857        RedeemerTag::Reward => get_withdrawals_info(&tx.transaction_body.withdrawals)
858            .get(redeemer.index as usize)
859            .ok_or(Error::MissingScriptForRedeemer)
860            .and_then(|(addr, _)| {
861                let stake_addr = if let Address::Stake(stake_addr) = addr {
862                    stake_addr
863                } else {
864                    unreachable!("withdrawal always contains stake addresses")
865                };
866
867                if let StakePayload::Script(hash) = stake_addr.payload() {
868                    lookup_script(hash)
869                } else {
870                    Err(Error::NonScriptWithdrawal)
871                }
872            }),
873
874        RedeemerTag::Cert => get_certificates_info(&tx.transaction_body.certificates)
875            .get(redeemer.index as usize)
876            .ok_or(Error::MissingScriptForRedeemer)
877            .and_then(|cert| match cert {
878                Certificate::StakeDeregistration(stake_credential)
879                | Certificate::UnReg(stake_credential, _)
880                | Certificate::VoteDeleg(stake_credential, _)
881                | Certificate::VoteRegDeleg(stake_credential, _, _)
882                | Certificate::StakeVoteDeleg(stake_credential, _, _)
883                | Certificate::StakeRegDeleg(stake_credential, _, _)
884                | Certificate::StakeVoteRegDeleg(stake_credential, _, _, _)
885                | Certificate::RegDRepCert(stake_credential, _, _)
886                | Certificate::UnRegDRepCert(stake_credential, _)
887                | Certificate::UpdateDRepCert(stake_credential, _)
888                | Certificate::AuthCommitteeHot(stake_credential, _)
889                | Certificate::ResignCommitteeCold(stake_credential, _)
890                | Certificate::StakeDelegation(stake_credential, _) => match stake_credential {
891                    StakeCredential::ScriptHash(hash) => Ok(hash),
892                    _ => Err(Error::NonScriptStakeCredential),
893                },
894                Certificate::StakeRegistration { .. }
895                | Certificate::PoolRetirement { .. }
896                | Certificate::Reg { .. }
897                | Certificate::PoolRegistration { .. } => Err(Error::UnsupportedCertificateType),
898            })
899            .and_then(lookup_script),
900
901        RedeemerTag::Spend => get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)
902            .or_else(|err| {
903                if matches!(err, Error::ByronAddressNotAllowed) {
904                    get_tx_in_info_v1(&tx.transaction_body.inputs, utxos)
905                } else {
906                    Err(err)
907                }
908            })?
909            .get(redeemer.index as usize)
910            .ok_or(Error::MissingScriptForRedeemer)
911            .and_then(|input| match output_address(&input.resolved) {
912                Address::Shelley(shelley_address) => {
913                    let hash = shelley_address.payment().as_hash();
914                    let (script, _) = lookup_script(hash)?;
915                    let datum = lookup_datum(output_datum(&input.resolved))?;
916
917                    if datum.is_none()
918                        && matches!(script, ScriptVersion::V1(..) | ScriptVersion::V2(..))
919                    {
920                        return Err(Error::MissingRequiredInlineDatumOrHash);
921                    }
922
923                    Ok((script, datum))
924                }
925                _ => Err(Error::NonScriptStakeCredential),
926            }),
927
928        RedeemerTag::Vote => get_votes_info(&tx.transaction_body.voting_procedures)
929            .get(redeemer.index as usize)
930            .ok_or(Error::MissingScriptForRedeemer)
931            .and_then(|(voter, _)| match voter {
932                Voter::ConstitutionalCommitteeScript(hash) => Ok(hash),
933                Voter::ConstitutionalCommitteeKey(..) => Err(Error::NonScriptStakeCredential),
934                Voter::DRepScript(hash) => Ok(hash),
935                Voter::DRepKey(..) => Err(Error::NonScriptStakeCredential),
936                Voter::StakePoolKey(..) => Err(Error::NonScriptStakeCredential),
937            })
938            .and_then(lookup_script),
939
940        RedeemerTag::Propose => {
941            get_proposal_procedures_info(&tx.transaction_body.proposal_procedures)
942                .get(redeemer.index as usize)
943                .ok_or(Error::MissingScriptForRedeemer)
944                .and_then(|procedure| match procedure.gov_action {
945                    GovAction::ParameterChange(_, _, Nullable::Some(ref hash)) => Ok(hash),
946                    GovAction::TreasuryWithdrawals(_, Nullable::Some(ref hash)) => Ok(hash),
947                    GovAction::HardForkInitiation(..)
948                    | GovAction::Information
949                    | GovAction::NewConstitution(..)
950                    | GovAction::TreasuryWithdrawals(..)
951                    | GovAction::ParameterChange(..)
952                    | GovAction::NoConfidence(..)
953                    | GovAction::UpdateCommittee(..) => Err(Error::NoGuardrailScriptForProcedure),
954                })
955                .and_then(lookup_script)
956        }
957    }
958}
959
960pub fn from_alonzo_value(value: &alonzo::Value) -> Value {
961    match value {
962        alonzo::Value::Coin(coin) => Value::Coin(*coin),
963        alonzo::Value::Multiasset(coin, assets) if assets.is_empty() => Value::Coin(*coin),
964        alonzo::Value::Multiasset(coin, assets) => Value::Multiasset(
965            *coin,
966            NonEmptyKeyValuePairs::try_from(
967                assets
968                    .iter()
969                    .cloned()
970                    .map(|(policy_id, tokens)| {
971                        (
972                            policy_id,
973                            NonEmptyKeyValuePairs::try_from(
974                                tokens
975                                    .iter()
976                                    .cloned()
977                                    .map(|(asset_name, quantity)| {
978                                        (
979                                            asset_name,
980                                            quantity.try_into().expect("0 Ada in output value?"),
981                                        )
982                                    })
983                                    .collect_vec(),
984                            )
985                            .expect("empty tokens under a policy?"),
986                        )
987                    })
988                    .collect_vec(),
989            )
990            .expect("assets cannot be empty due to pattern-guard"),
991        ),
992    }
993}
994
995pub fn from_alonzo_output(output: &alonzo::TransactionOutput) -> TransactionOutput {
996    TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput {
997        address: output.address.clone(),
998        value: from_alonzo_value(&output.amount),
999        datum_option: output.datum_hash.map(DatumOption::Hash),
1000        script_ref: None,
1001    })
1002}
1003
1004// --------------------- Sorting
1005
1006fn sort_tx_out_value(tx_output: &TransactionOutput) -> TransactionOutput {
1007    match tx_output {
1008        TransactionOutput::Legacy(output) => {
1009            let new_output = PostAlonzoTransactionOutput {
1010                address: output.address.clone(),
1011                value: sort_value(&from_alonzo_value(&output.amount)),
1012                datum_option: output.datum_hash.map(PseudoDatumOption::Hash),
1013                script_ref: None,
1014            };
1015            TransactionOutput::PostAlonzo(new_output)
1016        }
1017        TransactionOutput::PostAlonzo(output) => {
1018            let mut new_output = output.clone();
1019            new_output.value = sort_value(&output.value);
1020            TransactionOutput::PostAlonzo(new_output)
1021        }
1022    }
1023}
1024
1025fn sort_mint(mint: &Mint) -> Mint {
1026    let mut mint_vec = vec![];
1027
1028    for m in mint.deref().iter().sorted() {
1029        mint_vec.push((
1030            m.0,
1031            NonEmptyKeyValuePairs::Indef(
1032                m.1.deref().clone().into_iter().sorted().clone().collect(),
1033            ),
1034        ));
1035    }
1036
1037    NonEmptyKeyValuePairs::Indef(mint_vec)
1038}
1039
1040fn sort_value(value: &Value) -> Value {
1041    match value {
1042        Value::Coin(_) => value.clone(),
1043        Value::Multiasset(coin, ma) => {
1044            let mut ma_vec = vec![];
1045            for m in ma.deref().iter().sorted() {
1046                ma_vec.push((
1047                    m.0,
1048                    NonEmptyKeyValuePairs::Indef(
1049                        m.1.deref().clone().into_iter().sorted().clone().collect(),
1050                    ),
1051                ));
1052            }
1053            Value::Multiasset(*coin, NonEmptyKeyValuePairs::Indef(ma_vec))
1054        }
1055    }
1056}
1057
1058fn sort_redeemers(a: &RedeemersKey, b: &RedeemersKey) -> Ordering {
1059    fn redeemer_tag_as_usize(tag: &RedeemerTag) -> usize {
1060        match tag {
1061            RedeemerTag::Spend => 0,
1062            RedeemerTag::Mint => 1,
1063            RedeemerTag::Cert => 2,
1064            RedeemerTag::Reward => 3,
1065            RedeemerTag::Vote => 4,
1066            RedeemerTag::Propose => 5,
1067        }
1068    }
1069
1070    if a.tag == b.tag {
1071        a.index.cmp(&b.index)
1072    } else {
1073        redeemer_tag_as_usize(&a.tag).cmp(&redeemer_tag_as_usize(&b.tag))
1074    }
1075}
1076
1077pub fn sort_voters(a: &Voter, b: &Voter) -> Ordering {
1078    fn explode(voter: &Voter) -> (usize, &Hash<28>) {
1079        match voter {
1080            Voter::ConstitutionalCommitteeScript(hash) => (0, hash),
1081            Voter::ConstitutionalCommitteeKey(hash) => (1, hash),
1082            Voter::DRepScript(hash) => (2, hash),
1083            Voter::DRepKey(hash) => (3, hash),
1084            Voter::StakePoolKey(hash) => (4, hash),
1085        }
1086    }
1087
1088    let (tag_a, hash_a) = explode(a);
1089    let (tag_b, hash_b) = explode(b);
1090
1091    if tag_a == tag_b {
1092        hash_a.cmp(hash_b)
1093    } else {
1094        tag_a.cmp(&tag_b)
1095    }
1096}
1097
1098fn sort_gov_action_id(a: &GovActionId, b: &GovActionId) -> Ordering {
1099    if a.transaction_id == b.transaction_id {
1100        a.action_index.cmp(&b.action_index)
1101    } else {
1102        a.transaction_id.cmp(&b.transaction_id)
1103    }
1104}
1105
1106pub fn sort_reward_accounts(a: &Bytes, b: &Bytes) -> Ordering {
1107    let addr_a = Address::from_bytes(a).expect("invalid reward address in withdrawals.");
1108    let addr_b = Address::from_bytes(b).expect("invalid reward address in withdrawals.");
1109
1110    fn network_tag(network: Network) -> u8 {
1111        match network {
1112            Network::Testnet => 0,
1113            Network::Mainnet => 1,
1114            Network::Other(tag) => tag,
1115        }
1116    }
1117
1118    if let (Address::Stake(accnt_a), Address::Stake(accnt_b)) = (addr_a, addr_b) {
1119        if accnt_a.network() != accnt_b.network() {
1120            return network_tag(accnt_a.network()).cmp(&network_tag(accnt_b.network()));
1121        }
1122
1123        match (accnt_a.payload(), accnt_b.payload()) {
1124            (StakePayload::Script(..), StakePayload::Stake(..)) => Ordering::Less,
1125            (StakePayload::Stake(..), StakePayload::Script(..)) => Ordering::Greater,
1126            (StakePayload::Script(hash_a), StakePayload::Script(hash_b)) => hash_a.cmp(hash_b),
1127            (StakePayload::Stake(hash_a), StakePayload::Stake(hash_b)) => hash_a.cmp(hash_b),
1128        }
1129    } else {
1130        unreachable!("invalid reward address in withdrawals.");
1131    }
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use crate::pallas_primitives::{
1137        conway::{ExUnits, PlutusData, Redeemer, RedeemerTag, TransactionInput, TransactionOutput},
1138        Fragment,
1139    };
1140    use crate::pallas_traverse::{Era, MultiEraTx};
1141    use crate::uplc::{
1142        ast::Data,
1143        tx::{
1144            script_context::{TxInfo, TxInfoV3},
1145            to_plutus_data::ToPlutusData,
1146            ResolvedInput, SlotConfig,
1147        },
1148    };
1149
1150    fn fixture_tx_info(transaction: &str, inputs: &str, outputs: &str) -> TxInfo {
1151        let transaction_bytes = hex::decode(transaction).unwrap();
1152        let inputs_bytes = hex::decode(inputs).unwrap();
1153        let outputs_bytes = hex::decode(outputs).unwrap();
1154
1155        let inputs = Vec::<TransactionInput>::decode_fragment(inputs_bytes.as_slice()).unwrap();
1156        let outputs = Vec::<TransactionOutput>::decode_fragment(outputs_bytes.as_slice()).unwrap();
1157        let resolved_inputs: Vec<ResolvedInput> = inputs
1158            .iter()
1159            .zip(outputs.iter())
1160            .map(|(input, output)| ResolvedInput {
1161                input: input.clone(),
1162                output: output.clone(),
1163            })
1164            .collect();
1165
1166        TxInfoV3::from_transaction(
1167            MultiEraTx::decode_for_era(Era::Conway, transaction_bytes.as_slice())
1168                .unwrap()
1169                .as_conway()
1170                .unwrap(),
1171            &resolved_inputs,
1172            &SlotConfig::default(),
1173        )
1174        .unwrap()
1175    }
1176
1177    #[allow(dead_code)]
1178    fn from_haskell(data: &str) -> PlutusData {
1179        PlutusData::decode_fragment(hex::decode(data).unwrap().as_slice()).unwrap()
1180    }
1181
1182    #[test]
1183    fn script_context_simple_send() {
1184        let datum = Some(Data::constr(0, Vec::new()));
1185
1186        let redeemer = Redeemer {
1187            tag: RedeemerTag::Spend,
1188            index: 0,
1189            data: Data::constr(0, Vec::new()),
1190            ex_units: ExUnits {
1191                mem: 1000000,
1192                steps: 100000000,
1193            },
1194        };
1195
1196        let script_context = fixture_tx_info(
1197            "84a7008182582000000000000000000000000000000000000000000000000000\
1198             0000000000000000018182581d60111111111111111111111111111111111111\
1199             111111111111111111111a3b9aca0002182a0b5820ffffffffffffffffffffff\
1200             ffffffffffffffffffffffffffffffffffffffffff0d81825820000000000000\
1201             0000000000000000000000000000000000000000000000000000001082581d60\
1202             000000000000000000000000000000000000000000000000000000001a3b9aca\
1203             001101a20581840000d87980821a000f42401a05f5e100078152510101003222\
1204             253330044a229309b2b2b9a1f5f6",
1205            "8182582000000000000000000000000000000000000000000000000000000000\
1206             0000000000",
1207            "81a300581d7039f47fd3b388ef53c48f08de24766d3e55dade6cae908cc24e0f\
1208             4f3e011a3b9aca00028201d81843d87980",
1209        )
1210        .into_script_context(&redeemer, datum.as_ref())
1211        .unwrap();
1212
1213        // NOTE: The initial snapshot has been generated using the Haskell
1214        // implementation of the ledger library for that same serialized
1215        // transactions. It is meant to control that our construction of the
1216        // script context and its serialization matches exactly those
1217        // from the Haskell ledger / cardano node.
1218        insta::assert_debug_snapshot!(script_context.to_plutus_data())
1219    }
1220
1221    #[test]
1222    fn script_context_mint() {
1223        let redeemer = Redeemer {
1224            tag: RedeemerTag::Mint,
1225            index: 1,
1226            data: Data::integer(42.into()),
1227            ex_units: ExUnits {
1228                mem: 1000000,
1229                steps: 100000000,
1230            },
1231        };
1232
1233        let script_context = fixture_tx_info(
1234            "84a9008182582000000000000000000000000000000000000000000000000000\
1235             00000000000000000183a300581d600000000000000000000000000000000000\
1236             0000000000000000000000011a000f42400282005820923918e403bf43c34b4e\
1237             f6b48eb2ee04babed17320d8d1b9ff9ad086e86f44eca2005839000000000000\
1238             0000000000000000000000000000000000000000000000000000000000000000\
1239             0000000000000000000000000000000000000001821a000f4240a2581c12593b\
1240             4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874a14474756e611b\
1241             00005af3107a4000581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa7844\
1242             3fcec2dc262ea14561696b656e182aa300583910000000000000000000000000\
1243             0000000000000000000000000000000000000000000000000000000000000000\
1244             00000000000000000000000001821a000f4240a1581c0c8eaf490c53afbf27e3\
1245             d84a3b57da51fbafe5aa78443fcec2dc262ea14763617264616e6f0103d81847\
1246             82034463666f6f02182a09a2581c12593b4cbf7fdfd8636db99fe356437cd6af\
1247             8539aadaa0a401964874a14474756e611b00005af3107a4000581c0c8eaf490c\
1248             53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262ea24763617264616e6f\
1249             014561696b656e2d0b5820ffffffffffffffffffffffffffffffffffffffffff\
1250             ffffffffffffffffffffff0d8182582000000000000000000000000000000000\
1251             00000000000000000000000000000000001082581d6000000000000000000000\
1252             0000000000000000000000000000000000001a3b9aca00110112818258200000\
1253             00000000000000000000000000000000000000000000000000000000000000a3\
1254             0582840100d87980821a000f42401a05f5e100840101182a821a000f42401a05\
1255             f5e1000481d879800782587d587b010100323232323232322533333300800115\
1256             3330033370e900018029baa001153330073006375400224a6660089445261533\
1257             0054911856616c696461746f722072657475726e65642066616c736500136560\
1258             02002002002002002153300249010b5f746d70323a20566f696400165734ae71\
1259             55ceaab9e5573eae915895589301010032323232323232253333330080011533\
1260             30033370e900018029baa001153330073006375400224a666008a6600a920110\
1261             5f5f5f5f5f6d696e745f325f5f5f5f5f0014a22930a99802a4811856616c6964\
1262             61746f722072657475726e65642066616c736500136560020020020020020021\
1263             53300249010b5f746d70323a20566f696400165734ae7155ceaab9e5573eae91\
1264             f5f6",
1265            "8182582000000000000000000000000000000000000000000000000000000000\
1266             0000000000",
1267            "81a200581d600000000000000000000000000000000000000000000000000000\
1268             0000011a000f4240",
1269        )
1270        .into_script_context(&redeemer, None)
1271        .unwrap();
1272
1273        // NOTE: The initial snapshot has been generated using the Haskell
1274        // implementation of the ledger library for that same serialized
1275        // transactions. It is meant to control that our construction of the
1276        // script context and its serialization matches exactly those
1277        // from the Haskell ledger / cardano node.
1278        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1279    }
1280
1281    #[test]
1282    fn script_context_propose_all_but_pparams() {
1283        let redeemer = Redeemer {
1284            tag: RedeemerTag::Propose,
1285            index: 3,
1286            data: Data::constr(0, vec![]),
1287            ex_units: ExUnits {
1288                mem: 1000000,
1289                steps: 100000000,
1290            },
1291        };
1292
1293        let script_context = fixture_tx_info(
1294            "84a4008182582000000000000000000000000000000000000000000000000000\
1295             0000000000000000018002182a14d9010289841a001e8480581df00000000000\
1296             00000000000000000000000000000000000000000000008301f6820a00827668\
1297             747470733a2f2f61696b656e2d6c616e672e6f72675820000000000000000000\
1298             0000000000000000000000000000000000000000000000841a001e8480581df0\
1299             0000000000000000000000000000000000000000000000000000000083018258\
1300             2000000000000000000000000000000000000000000000000000000000000000\
1301             0000820b00827668747470733a2f2f61696b656e2d6c616e672e6f7267582000\
1302             0000000000000000000000000000000000000000000000000000000000000084\
1303             1a001e8480581df0000000000000000000000000000000000000000000000000\
1304             000000008302a1581de011111111111111111111111111111111111111111111\
1305             1111111111111a000f4240f6827668747470733a2f2f61696b656e2d6c616e67\
1306             2e6f726758200000000000000000000000000000000000000000000000000000\
1307             000000000000841a001e8480581df00000000000000000000000000000000000\
1308             00000000000000000000008302a1581de0222222222222222222222222222222\
1309             222222222222222222222222221a000f4240581c9b24324046544393443e1fb3\
1310             5c8b72c3c39e18a516a95df5f6654101827668747470733a2f2f61696b656e2d\
1311             6c616e672e6f7267582000000000000000000000000000000000000000000000\
1312             00000000000000000000841a001e8480581df000000000000000000000000000\
1313             0000000000000000000000000000008203f6827668747470733a2f2f61696b65\
1314             6e2d6c616e672e6f726758200000000000000000000000000000000000000000\
1315             000000000000000000000000841a001e8480581df00000000000000000000000\
1316             00000000000000000000000000000000008504f6818200581c00000000000000\
1317             000000000000000000000000000000000000000000a18200581c000000000000\
1318             000000000000000000000000000000000000000000001901f4d81e8201028276\
1319             68747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000\
1320             000000000000000000000000000000000000000000000000841a001e8480581d\
1321             f0000000000000000000000000000000000000000000000000000000008305f6\
1322             8282782068747470733a2f2f636f6e737469747574696f6e2e63617264616e6f\
1323             2e6f726758200000000000000000000000000000000000000000000000000000\
1324             000000000000f6827668747470733a2f2f61696b656e2d6c616e672e6f726758\
1325             2000000000000000000000000000000000000000000000000000000000000000\
1326             00841a001e8480581df000000000000000000000000000000000000000000000\
1327             0000000000008305f68282782068747470733a2f2f636f6e737469747574696f\
1328             6e2e63617264616e6f2e6f726758200000000000000000000000000000000000\
1329             000000000000000000000000000000581c000000000000000000000000000000\
1330             00000000000000000000000000827668747470733a2f2f61696b656e2d6c616e\
1331             672e6f7267582000000000000000000000000000000000000000000000000000\
1332             00000000000000841a001e8480581de000000000000000000000000000000000\
1333             0000000000000000000000008106827668747470733a2f2f61696b656e2d6c61\
1334             6e672e6f72675820000000000000000000000000000000000000000000000000\
1335             0000000000000000a20581840503d87980821a000f42401a05f5e1000781587d\
1336             587b0101003232323232323225333333008001153330033370e900018029baa0\
1337             01153330073006375400224a66600894452615330054911856616c696461746f\
1338             722072657475726e65642066616c736500136560020020020020020021533002\
1339             49010b5f746d70313a20566f696400165734ae7155ceaab9e5573eae91f5f6",
1340            "8182582000000000000000000000000000000000000000000000000000000000\
1341             0000000000",
1342            "81a200581d600000000000000000000000000000000000000000000000000000\
1343             0000011a000f4240",
1344        )
1345        .into_script_context(&redeemer, None)
1346        .unwrap();
1347
1348        // NOTE: The initial snapshot has been generated using the Haskell
1349        // implementation of the ledger library for that same serialized
1350        // transactions. It is meant to control that our construction of the
1351        // script context and its serialization matches exactly those
1352        // from the Haskell ledger / cardano node.
1353        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1354    }
1355
1356    #[test]
1357    fn script_context_propose_pparams_no_cost_models() {
1358        let redeemer = Redeemer {
1359            tag: RedeemerTag::Propose,
1360            index: 0,
1361            data: Data::constr(0, vec![]),
1362            ex_units: ExUnits {
1363                mem: 1000000,
1364                steps: 100000000,
1365            },
1366        };
1367
1368        let script_context = fixture_tx_info(
1369            "84a4008182582000000000000000000000000000000000000000000000000000\
1370             0000000000000000018002182a14d9010281841a001e8480581df00000000000\
1371             00000000000000000000000000000000000000000000008400f6b81d00182c01\
1372             1a00025ef50712081901f409d81e82030a0ad81e82031903e80bd81e82020a02\
1373             1a00016000031940000419044c051a001e8480061a1dcd650010190154111910\
1374             d61382d81e821902411903e8d81e821902d11a000f424014821a00d59f801b00\
1375             000002540be40015821a03b20b801b00000004a817c800161913881718961818\
1376             03181985d81e8218331864d81e8218341864d81e8218351864d81e8218361864\
1377             d81e8218371864181a8ad81e8218431864d81e8218431864d81e82183c1864d8\
1378             1e82184b1864d81e82183c1864d81e8218431864d81e8218431864d81e821843\
1379             1864d81e82184b1864d81e8218431864181b07181c1892181d06181e1b000000\
1380             174876e800181f1a1dcd65001820141821d81e820f01581c9b24324046544393\
1381             443e1fb35c8b72c3c39e18a516a95df5f6654101827668747470733a2f2f6169\
1382             6b656e2d6c616e672e6f72675820000000000000000000000000000000000000\
1383             0000000000000000000000000000a20581840500d87980821a000f42401a05f5\
1384             e1000781587d587b0101003232323232323225333333008001153330033370e9\
1385             00018029baa001153330073006375400224a6660089445261533005491185661\
1386             6c696461746f722072657475726e65642066616c736500136560020020020020\
1387             02002153300249010b5f746d70313a20566f696400165734ae7155ceaab9e557\
1388             3eae91f5f6",
1389            "8182582000000000000000000000000000000000000000000000000000000000\
1390             0000000000",
1391            "81a200581d600000000000000000000000000000000000000000000000000000\
1392             0000011a000f4240",
1393        )
1394        .into_script_context(&redeemer, None)
1395        .unwrap();
1396
1397        // NOTE: The initial snapshot has been generated using the Haskell
1398        // implementation of the ledger library for that same serialized
1399        // transactions. It is meant to control that our construction of the
1400        // script context and its serialization matches exactly those
1401        // from the Haskell ledger / cardano node.
1402        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1403    }
1404
1405    #[test]
1406    fn script_context_certificates() {
1407        let redeemer = Redeemer {
1408            tag: RedeemerTag::Cert,
1409            index: 20,
1410            data: Data::constr(0, vec![]),
1411            ex_units: ExUnits {
1412                mem: 1000000,
1413                steps: 100000000,
1414            },
1415        };
1416
1417        // NOTE: The transaction also contains treasury donation and current treasury amount
1418        let script_context = fixture_tx_info(
1419            "84a6008182582000000000000000000000000000000000000000000000000000\
1420             00000000000000000180049582008201581c2222222222222222222222222222\
1421             222222222222222222222222222282008200581c000000000000000000000000\
1422             0000000000000000000000000000000082018200581c00000000000000000000\
1423             0000000000000000000000000000000000008a03581c11111111111111111111\
1424             1111111111111111111111111111111111115820999999999999999999999999\
1425             99999999999999999999999999999999999999991a000f4240190154d81e8201\
1426             1864581de0000000000000000000000000000000000000000000000000000000\
1427             00d901028080f68304581c111111111111111111111111111111111111111111\
1428             1111111111111119053983078200581c00000000000000000000000000000000\
1429             0000000000000000000000001a002dc6c083088200581c000000000000000000\
1430             000000000000000000000000000000000000001a002dc6c083098200581c0000\
1431             00000000000000000000000000000000000000000000000000008200581c0000\
1432             000000000000000000000000000000000000000000000000000083098200581c\
1433             000000000000000000000000000000000000000000000000000000008201581c\
1434             0000000000000000000000000000000000000000000000000000000083098200\
1435             581c000000000000000000000000000000000000000000000000000000008102\
1436             83098200581c0000000000000000000000000000000000000000000000000000\
1437             00008103840a8200581c00000000000000000000000000000000000000000000\
1438             000000000000581c111111111111111111111111111111111111111111111111\
1439             111111118103840b8200581c0000000000000000000000000000000000000000\
1440             0000000000000000581c11111111111111111111111111111111111111111111\
1441             1111111111111a002dc6c0840c8200581c000000000000000000000000000000\
1442             0000000000000000000000000081031a002dc6c0850d8200581c000000000000\
1443             00000000000000000000000000000000000000000000581c1111111111111111\
1444             111111111111111111111111111111111111111181031a002dc6c0830e820058\
1445             1c00000000000000000000000000000000000000000000000000000000820058\
1446             1c22222222222222222222222222222222222222222222222222222222830f82\
1447             00581c00000000000000000000000000000000000000000000000000000000f6\
1448             84108200581c0000000000000000000000000000000000000000000000000000\
1449             00001a002dc6c0f683118200581c000000000000000000000000000000000000\
1450             000000000000000000001a002dc6c083128200581c0000000000000000000000\
1451             0000000000000000000000000000000000f683028201581c9b24324046544393\
1452             443e1fb35c8b72c3c39e18a516a95df5f6654101581c11111111111111111111\
1453             11111111111111111111111111111111111102182a151a00989680160ea20581\
1454             840214d87980821a000f42401a05f5e1000781587d587b010100323232323232\
1455             3225333333008001153330033370e900018029baa00115333007300637540022\
1456             4a66600894452615330054911856616c696461746f722072657475726e656420\
1457             66616c73650013656002002002002002002153300249010b5f746d70313a2056\
1458             6f696400165734ae7155ceaab9e5573eae91f5f6",
1459            "8182582000000000000000000000000000000000000000000000000000000000\
1460             0000000000",
1461            "81a200581d600000000000000000000000000000000000000000000000000000\
1462             0000011a000f4240",
1463        )
1464        .into_script_context(&redeemer, None)
1465        .unwrap();
1466
1467        // NOTE: The initial snapshot has been generated using the Haskell
1468        // implementation of the ledger library for that same serialized
1469        // transactions. It is meant to control that our construction of the
1470        // script context and its serialization matches exactly those
1471        // from the Haskell ledger / cardano node.
1472        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1473    }
1474
1475    #[test]
1476    fn script_context_voting() {
1477        let redeemer = Redeemer {
1478            tag: RedeemerTag::Vote,
1479            index: 0,
1480            data: Data::constr(0, vec![Data::integer(42.into())]),
1481            ex_units: ExUnits {
1482                mem: 1000000,
1483                steps: 100000000,
1484            },
1485        };
1486
1487        // NOTE: The transaction also contains treasury donation and current treasury amount
1488        let script_context = fixture_tx_info(
1489            "84a4008182582000000000000000000000000000000000000000000000000000\
1490             0000000000000000018002182a13a58200581c00000000000000000000000000\
1491             000000000000000000000000000000a182582099999999999999999999999999\
1492             9999999999999999999999999999999999999918988200827668747470733a2f\
1493             2f61696b656e2d6c616e672e6f72675820000000000000000000000000000000\
1494             00000000000000000000000000000000008202581c0000000000000000000000\
1495             0000000000000000000000000000000000a38258209999999999999999999999\
1496             999999999999999999999999999999999999999999008202f682582088888888\
1497             88888888888888888888888888888888888888888888888888888888018202f6\
1498             8258207777777777777777777777777777777777777777777777777777777777\
1499             777777028202f68203581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c\
1500             85966de0f2ba1ea1825820999999999999999999999999999999999999999999\
1501             9999999999999999999999038200f68204581c00000000000000000000000000\
1502             000000000000000000000000000000a182582099999999999999999999999999\
1503             99999999999999999999999999999999999999048201f68201581c43fa47afc6\
1504             8a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1ea18258209999999999\
1505             999999999999999999999999999999999999999999999999999999018201f6a2\
1506             0582840402d87980821a000f42401a05f5e100840400d87981182a821a000f42\
1507             401a05f5e1000781587d587b0101003232323232323225333333008001153330\
1508             033370e900018029baa001153330073006375400224a66600894452615330054\
1509             911856616c696461746f722072657475726e65642066616c7365001365600200\
1510             2002002002002153300249010b5f746d70303a20566f696400165734ae7155ce\
1511             aab9e5573eae91f5f6",
1512            "8182582000000000000000000000000000000000000000000000000000000000\
1513             0000000000",
1514            "81a200581d600000000000000000000000000000000000000000000000000000\
1515             0000011a000f4240",
1516        )
1517        .into_script_context(&redeemer, None)
1518        .unwrap();
1519
1520        // NOTE: The initial snapshot has been generated using the Haskell
1521        // implementation of the ledger library for that same serialized
1522        // transactions. It is meant to control that our construction of the
1523        // script context and its serialization matches exactly those
1524        // from the Haskell ledger / cardano node.
1525        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1526    }
1527
1528    #[test]
1529    fn script_context_withdraw() {
1530        let redeemer = Redeemer {
1531            tag: RedeemerTag::Reward,
1532            index: 0,
1533            data: Data::constr(0, vec![]),
1534            ex_units: ExUnits {
1535                mem: 1000000,
1536                steps: 100000000,
1537            },
1538        };
1539
1540        // NOTE: The transaction also contains treasury donation and current treasury amount
1541        let script_context = fixture_tx_info(
1542            "84a7008182582000000000000000000000000000000000000000000000000000\
1543             00000000000000000183a2005839200000000000000000000000000000000000\
1544             0000000000000000000000111111111111111111111111111111111111111111\
1545             11111111111111011a000f4240a2005823400000000000000000000000000000\
1546             00000000000000000000000000008198bd431b03011a000f4240a20058235011\
1547             1111111111111111111111111111111111111111111111111111118198bd431b\
1548             03011a000f424002182a031a00448e0105a1581df004036eecadc2f19e95f831\
1549             b4bc08919cde1d1088d74602bd3dcd78a2000e81581c00000000000000000000\
1550             0000000000000000000000000000000000001601a10582840000d87a81d87980\
1551             821a000f42401a05f5e100840300d87980821a000f42401a05f5e100f5f6",
1552            "8182582000000000000000000000000000000000000000000000000000000000\
1553             0000000000",
1554            "81a40058393004036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd\
1555             78a204036eecadc2f19e95f831b4bc08919cde1d1088d74602bd3dcd78a2011a\
1556             000f4240028201d81843d8798003d818590221820359021c5902190101003232\
1557             323232323232322232533333300c00215323330073001300937540062a660109\
1558             211c52756e6e696e672032206172672076616c696461746f72206d696e740013\
1559             533333300d004153330073001300937540082a66601660146ea8010494ccc021\
1560             288a4c2a660129211856616c696461746f722072657475726e65642066616c73\
1561             65001365600600600600600600600315330084911d52756e6e696e6720332061\
1562             72672076616c696461746f72207370656e640013533333300d00415333007300\
1563             1300937540082a66601660146ea8010494cccccc03800454ccc020c008c028dd\
1564             50008a99980618059baa0011253330094a22930a998052491856616c69646174\
1565             6f722072657475726e65642066616c7365001365600600600600600600600600\
1566             6006006006006300c300a37540066e1d20001533007001161533007001161533\
1567             00700116153300700116490191496e636f72726563742072656465656d657220\
1568             7479706520666f722076616c696461746f72207370656e642e0a202020202020\
1569             2020202020202020202020202020446f75626c6520636865636b20796f752068\
1570             6176652077726170706564207468652072656465656d65722074797065206173\
1571             2073706563696669656420696e20796f757220706c757475732e6a736f6e0015\
1572             330034910b5f746d70313a20566f6964001615330024910b5f746d70303a2056\
1573             6f696400165734ae7155ceaab9e5573eae855d21",
1574        )
1575        .into_script_context(&redeemer, None)
1576        .unwrap();
1577
1578        // NOTE: The initial snapshot has been generated using the Haskell
1579        // implementation of the ledger library for that same serialized
1580        // transactions. It is meant to control that our construction of the
1581        // script context and its serialization matches exactly those
1582        // from the Haskell ledger / cardano node.
1583        insta::assert_debug_snapshot!(script_context.to_plutus_data());
1584    }
1585}