plutus_ledger_api/v2/
transaction.rs

1//! Types related to Cardano transactions.
2
3use std::collections::BTreeMap;
4
5use cardano_serialization_lib as csl;
6#[cfg(feature = "lbf")]
7use lbr_prelude::json::Json;
8use num_bigint::BigInt;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use crate as plutus_ledger_api;
13use crate::csl::csl_to_pla::{FromCSL, TryFromCSL, TryFromCSLError, TryToPLA};
14use crate::csl::pla_to_csl::{TryFromPLA, TryFromPLAError, TryToCSL};
15use crate::plutus_data::IsPlutusData;
16#[cfg(feature = "chrono")]
17pub use crate::v1::transaction::POSIXTimeConversionError;
18pub use crate::v1::transaction::{
19    DCert, POSIXTime, POSIXTimeRange, ScriptPurpose, TransactionHash, TransactionInput,
20};
21
22use super::address::AddressWithExtraInfo;
23use super::{
24    address::{Address, RewardAddressWithExtraInfo, StakingCredential},
25    assoc_map::AssocMap,
26    crypto::PaymentPubKeyHash,
27    datum::{Datum, DatumHash, OutputDatum},
28    redeemer::Redeemer,
29    script::ScriptHash,
30    value::Value,
31};
32
33///////////////////////
34// TransactionOutput //
35///////////////////////
36
37/// An output of a transaction
38///
39/// This must include the target address, an optional datum, an optional reference script, and the
40/// amount of output tokens
41#[derive(Clone, Debug, PartialEq, Eq, IsPlutusData)]
42#[is_plutus_data_derive_strategy = "Constr"]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44#[cfg_attr(feature = "lbf", derive(Json))]
45pub struct TransactionOutput {
46    pub address: Address,
47    pub value: Value,
48    pub datum: OutputDatum,
49    pub reference_script: Option<ScriptHash>,
50}
51
52impl TryFromCSL<csl::TransactionOutput> for TransactionOutput {
53    fn try_from_csl(value: &csl::TransactionOutput) -> Result<Self, TryFromCSLError> {
54        Ok(TransactionOutput {
55            address: value.address().try_to_pla()?,
56            datum: if value.has_data_hash() {
57                OutputDatum::DatumHash(DatumHash::from_csl(&value.data_hash().unwrap()))
58            } else if value.has_plutus_data() {
59                OutputDatum::InlineDatum(Datum(value.plutus_data().unwrap().try_to_pla()?))
60            } else {
61                OutputDatum::None
62            },
63            reference_script: if value.has_script_ref() {
64                let script_ref = value.script_ref().unwrap();
65                let script_hash = if script_ref.is_native_script() {
66                    script_ref.native_script().unwrap().hash()
67                } else {
68                    script_ref.plutus_script().unwrap().hash()
69                };
70                Some(ScriptHash::from_csl(&script_hash))
71            } else {
72                None
73            },
74            value: Value::from_csl(&value.amount()),
75        })
76    }
77}
78
79impl TryFromCSL<csl::TransactionOutputs> for Vec<TransactionOutput> {
80    fn try_from_csl(value: &csl::TransactionOutputs) -> Result<Self, TryFromCSLError> {
81        (0..value.len())
82            .map(|idx| TransactionOutput::try_from_csl(&value.get(idx)))
83            .collect()
84    }
85}
86
87#[derive(Clone, Debug)]
88pub struct TransactionOutputWithExtraInfo<'a> {
89    pub transaction_output: &'a TransactionOutput,
90    pub scripts: &'a BTreeMap<ScriptHash, csl::PlutusScript>,
91    pub network_id: u8,
92    pub data_cost: &'a csl::DataCost,
93}
94
95impl TryFromPLA<TransactionOutputWithExtraInfo<'_>> for csl::TransactionOutput {
96    fn try_from_pla(val: &TransactionOutputWithExtraInfo<'_>) -> Result<Self, TryFromPLAError> {
97        let mut output_builder = csl::TransactionOutputBuilder::new().with_address(
98            &AddressWithExtraInfo {
99                address: &val.transaction_output.address,
100                network_tag: val.network_id,
101            }
102            .try_to_csl()?,
103        );
104
105        output_builder = match &val.transaction_output.datum {
106            OutputDatum::None => output_builder,
107            OutputDatum::InlineDatum(Datum(d)) => output_builder.with_plutus_data(&d.try_to_csl()?),
108            OutputDatum::DatumHash(dh) => output_builder.with_data_hash(&dh.try_to_csl()?),
109        };
110
111        let script_ref = val
112            .transaction_output
113            .reference_script
114            .clone()
115            .map(|script_hash| -> Result<_, TryFromPLAError> {
116                let script = val
117                    .scripts
118                    .get(&script_hash)
119                    .ok_or(TryFromPLAError::MissingScript(script_hash))?;
120                Ok(csl::ScriptRef::new_plutus_script(script))
121            })
122            .transpose()?;
123
124        if let Some(script_ref) = &script_ref {
125            output_builder = output_builder.with_script_ref(script_ref);
126        };
127
128        let value_without_min_utxo = val.transaction_output.value.try_to_csl()?;
129
130        let mut calc = csl::MinOutputAdaCalculator::new_empty(val.data_cost)
131            .map_err(TryFromPLAError::CSLJsError)?;
132        calc.set_amount(&value_without_min_utxo);
133        match &val.transaction_output.datum {
134            OutputDatum::None => {}
135            OutputDatum::InlineDatum(Datum(d)) => {
136                calc.set_plutus_data(&d.try_to_csl()?);
137            }
138            OutputDatum::DatumHash(dh) => {
139                calc.set_data_hash(&dh.try_to_csl()?);
140            }
141        };
142        if let Some(script_ref) = script_ref {
143            calc.set_script_ref(&script_ref);
144        }
145
146        let required_coin = calc.calculate_ada().map_err(TryFromPLAError::CSLJsError)?;
147        let coin = std::cmp::max(value_without_min_utxo.coin(), required_coin);
148
149        let value = match value_without_min_utxo.multiasset() {
150            Some(multiasset) => csl::Value::new_with_assets(&coin, &multiasset),
151            None => csl::Value::new(&coin),
152        };
153
154        output_builder
155            .next()
156            .map_err(TryFromPLAError::CSLJsError)?
157            .with_value(&value)
158            .build()
159            .map_err(TryFromPLAError::CSLJsError)
160    }
161}
162
163//////////////
164// TxInInfo //
165//////////////
166
167/// An input of a pending transaction.
168#[derive(Clone, Debug, PartialEq, Eq, IsPlutusData)]
169#[is_plutus_data_derive_strategy = "Constr"]
170#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
171#[cfg_attr(feature = "lbf", derive(Json))]
172pub struct TxInInfo {
173    pub reference: TransactionInput,
174    pub output: TransactionOutput,
175}
176
177impl From<(TransactionInput, TransactionOutput)> for TxInInfo {
178    fn from((reference, output): (TransactionInput, TransactionOutput)) -> TxInInfo {
179        TxInInfo { reference, output }
180    }
181}
182
183// TransactionInfo //
184/////////////////////
185
186/// A pending transaction as seen by validator scripts, also known as TxInfo in Plutus
187#[derive(Debug, PartialEq, Eq, Clone, IsPlutusData)]
188#[is_plutus_data_derive_strategy = "Constr"]
189#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
190#[cfg_attr(feature = "lbf", derive(Json))]
191pub struct TransactionInfo {
192    pub inputs: Vec<TxInInfo>,
193    pub reference_inputs: Vec<TxInInfo>,
194    pub outputs: Vec<TransactionOutput>,
195    pub fee: Value,
196    pub mint: Value,
197    pub d_cert: Vec<DCert>,
198    pub wdrl: AssocMap<StakingCredential, BigInt>,
199    pub valid_range: POSIXTimeRange,
200    pub signatories: Vec<PaymentPubKeyHash>,
201    pub redeemers: AssocMap<ScriptPurpose, Redeemer>,
202    pub datums: AssocMap<DatumHash, Datum>,
203    pub id: TransactionHash,
204}
205
206#[derive(Clone, Debug)]
207pub struct WithdrawalsWithExtraInfo<'a> {
208    pub withdrawals: &'a AssocMap<StakingCredential, BigInt>,
209    pub network_tag: u8,
210}
211
212impl TryFromPLA<WithdrawalsWithExtraInfo<'_>> for csl::Withdrawals {
213    fn try_from_pla(val: &WithdrawalsWithExtraInfo<'_>) -> Result<Self, TryFromPLAError> {
214        val.withdrawals
215            .0
216            .iter()
217            .try_fold(csl::Withdrawals::new(), |mut acc, (s, q)| {
218                acc.insert(
219                    &RewardAddressWithExtraInfo {
220                        staking_credential: s,
221                        network_tag: val.network_tag,
222                    }
223                    .try_to_csl()?,
224                    &q.try_to_csl()?,
225                );
226                Ok(acc)
227            })
228    }
229}
230
231///////////////////
232// ScriptContext //
233///////////////////
234
235/// The context that is presented to the currently-executing script.
236#[derive(Debug, PartialEq, Eq, Clone, IsPlutusData)]
237#[is_plutus_data_derive_strategy = "Constr"]
238#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
239#[cfg_attr(feature = "lbf", derive(Json))]
240pub struct ScriptContext {
241    pub tx_info: TransactionInfo,
242    pub purpose: ScriptPurpose,
243}