plutus_ledger_api/v2/
transaction.rs

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