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#[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 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 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#[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
503pub 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
1004fn 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 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 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 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 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 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 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 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 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 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 insta::assert_debug_snapshot!(script_context.to_plutus_data());
1584 }
1585}