plutus_ledger_api/v1/
transaction.rs

1//! Types related to Cardano transactions.
2use std::{fmt, str::FromStr};
3
4use anyhow::anyhow;
5use cardano_serialization_lib as csl;
6#[cfg(feature = "lbf")]
7use lbr_prelude::json::Json;
8use nom::{
9    character::complete::char,
10    combinator::{all_consuming, map, map_res},
11    error::{context, VerboseError},
12    sequence::{preceded, tuple},
13    Finish, IResult,
14};
15use num_bigint::BigInt;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19use super::{
20    address::{Address, StakingCredential},
21    crypto::{ledger_bytes, LedgerBytes, PaymentPubKeyHash},
22    datum::{Datum, DatumHash},
23    interval::PlutusInterval,
24    value::{CurrencySymbol, Value},
25};
26
27use crate::{
28    self as plutus_ledger_api,
29    aux::{big_int, guard_bytes},
30};
31use crate::{
32    csl::pla_to_csl::{TryFromPLAError, TryToCSL},
33    plutus_data::IsPlutusData,
34};
35use crate::{
36    csl::{csl_to_pla::FromCSL, pla_to_csl::TryFromPLA},
37    error::ConversionError,
38};
39
40//////////////////////
41// TransactionInput //
42//////////////////////
43
44/// An input of a transaction
45///
46/// Also know as `TxOutRef` from Plutus, this identifies a UTxO by its transacton hash and index
47/// inside the transaction
48#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
49#[is_plutus_data_derive_strategy = "Constr"]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[cfg_attr(feature = "lbf", derive(Json))]
52pub struct TransactionInput {
53    pub transaction_id: TransactionHash,
54    pub index: BigInt,
55}
56
57/// Serializing into a hexadecimal tx hash, followed by an tx id after a # (e.g. aabbcc#1)
58impl fmt::Display for TransactionInput {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "{}#{}", self.transaction_id.0, self.index)
61    }
62}
63
64impl FromCSL<csl::TransactionInput> for TransactionInput {
65    fn from_csl(value: &csl::TransactionInput) -> Self {
66        TransactionInput {
67            transaction_id: TransactionHash::from_csl(&value.transaction_id()),
68            index: BigInt::from_csl(&value.index()),
69        }
70    }
71}
72
73impl TryFromPLA<TransactionInput> for csl::TransactionInput {
74    fn try_from_pla(val: &TransactionInput) -> Result<Self, TryFromPLAError> {
75        Ok(csl::TransactionInput::new(
76            &val.transaction_id.try_to_csl()?,
77            val.index.try_to_csl()?,
78        ))
79    }
80}
81
82impl FromCSL<csl::TransactionInputs> for Vec<TransactionInput> {
83    fn from_csl(value: &csl::TransactionInputs) -> Self {
84        (0..value.len())
85            .map(|idx| TransactionInput::from_csl(&value.get(idx)))
86            .collect()
87    }
88}
89
90impl TryFromPLA<Vec<TransactionInput>> for csl::TransactionInputs {
91    fn try_from_pla(val: &Vec<TransactionInput>) -> Result<Self, TryFromPLAError> {
92        val.iter()
93            .try_fold(csl::TransactionInputs::new(), |mut acc, input| {
94                acc.add(&input.try_to_csl()?);
95                Ok(acc)
96            })
97    }
98}
99
100/// Nom parser for TransactionInput
101/// Expects a transaction hash of 32 bytes in hexadecimal followed by a # and an integer index
102/// E.g.: 1122334455667788990011223344556677889900112233445566778899001122#1
103pub(crate) fn transaction_input(
104    input: &str,
105) -> IResult<&str, TransactionInput, VerboseError<&str>> {
106    map(
107        tuple((transaction_hash, preceded(char('#'), big_int))),
108        |(transaction_id, index)| TransactionInput {
109            transaction_id,
110            index,
111        },
112    )(input)
113}
114
115impl FromStr for TransactionInput {
116    type Err = ConversionError;
117
118    fn from_str(s: &str) -> Result<Self, Self::Err> {
119        all_consuming(transaction_input)(s)
120            .finish()
121            .map_err(|err| {
122                ConversionError::ParseError(anyhow!(
123                    "Error while parsing TransactionInput '{}': {}",
124                    s,
125                    err
126                ))
127            })
128            .map(|(_, cs)| cs)
129    }
130}
131
132/////////////////////
133// TransactionHash //
134/////////////////////
135
136/// 32-bytes blake2b256 hash of a transaction body.
137///
138/// Also known as Transaction ID or `TxID`.
139/// Note: Plutus docs might incorrectly state that it uses SHA256.
140#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
141#[is_plutus_data_derive_strategy = "Constr"]
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143#[cfg_attr(feature = "lbf", derive(Json))]
144pub struct TransactionHash(pub LedgerBytes);
145
146impl fmt::Display for TransactionHash {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(f, "{}", self.0)
149    }
150}
151
152impl TransactionHash {
153    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ConversionError> {
154        Ok(TransactionHash(LedgerBytes(guard_bytes(
155            "ScriptHash",
156            bytes,
157            32,
158        )?)))
159    }
160}
161
162impl FromCSL<csl::TransactionHash> for TransactionHash {
163    fn from_csl(value: &csl::TransactionHash) -> Self {
164        TransactionHash(LedgerBytes(value.to_bytes()))
165    }
166}
167
168impl TryFromPLA<TransactionHash> for csl::TransactionHash {
169    fn try_from_pla(val: &TransactionHash) -> Result<Self, TryFromPLAError> {
170        csl::TransactionHash::from_bytes(val.0 .0.to_owned())
171            .map_err(TryFromPLAError::CSLDeserializeError)
172    }
173}
174
175/// Nom parser for TransactionHash
176/// Expects a hexadecimal string representation of 32 bytes
177/// E.g.: 1122334455667788990011223344556677889900112233445566778899001122
178pub(crate) fn transaction_hash(input: &str) -> IResult<&str, TransactionHash, VerboseError<&str>> {
179    context(
180        "transaction_hash",
181        map_res(ledger_bytes, |LedgerBytes(bytes)| {
182            TransactionHash::from_bytes(bytes)
183        }),
184    )(input)
185}
186
187impl FromStr for TransactionHash {
188    type Err = ConversionError;
189
190    fn from_str(s: &str) -> Result<Self, Self::Err> {
191        all_consuming(transaction_hash)(s)
192            .finish()
193            .map_err(|err| {
194                ConversionError::ParseError(anyhow!(
195                    "Error while parsing TransactionHash '{}': {}",
196                    s,
197                    err
198                ))
199            })
200            .map(|(_, cs)| cs)
201    }
202}
203
204///////////////////////
205// TransactionOutput //
206///////////////////////
207
208/// An output of a transaction
209///
210/// This must include the target address, the hash of the datum attached, and the amount of output
211/// tokens
212#[derive(Clone, Debug, PartialEq, Eq, IsPlutusData)]
213#[is_plutus_data_derive_strategy = "Constr"]
214#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
215#[cfg_attr(feature = "lbf", derive(Json))]
216pub struct TransactionOutput {
217    pub address: Address,
218    pub value: Value,
219    pub datum_hash: Option<DatumHash>,
220}
221
222///////////////
223// POSIXTime //
224///////////////
225
226/// POSIX time is measured as the number of milliseconds since 1970-01-01T00:00:00Z
227#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
228#[is_plutus_data_derive_strategy = "Newtype"]
229#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
230#[cfg_attr(feature = "lbf", derive(Json))]
231pub struct POSIXTime(pub BigInt);
232
233#[cfg(feature = "chrono")]
234#[derive(thiserror::Error, Debug)]
235pub enum POSIXTimeConversionError {
236    #[error(transparent)]
237    TryFromBigIntError(#[from] num_bigint::TryFromBigIntError<BigInt>),
238    #[error("POSIXTime is out of bounds.")]
239    OutOfBoundsError,
240}
241
242#[cfg(feature = "chrono")]
243impl<Tz: chrono::TimeZone> From<chrono::DateTime<Tz>> for POSIXTime {
244    fn from(datetime: chrono::DateTime<Tz>) -> POSIXTime {
245        POSIXTime(BigInt::from(datetime.timestamp_millis()))
246    }
247}
248
249#[cfg(feature = "chrono")]
250impl TryFrom<POSIXTime> for chrono::DateTime<chrono::Utc> {
251    type Error = POSIXTimeConversionError;
252
253    fn try_from(posix_time: POSIXTime) -> Result<chrono::DateTime<chrono::Utc>, Self::Error> {
254        let POSIXTime(millis) = posix_time;
255        chrono::DateTime::from_timestamp_millis(
256            <i64>::try_from(millis).map_err(POSIXTimeConversionError::TryFromBigIntError)?,
257        )
258        .ok_or(POSIXTimeConversionError::OutOfBoundsError)
259    }
260}
261
262////////////////////
263// POSIXTimeRange //
264////////////////////
265
266pub type POSIXTimeRange = PlutusInterval<POSIXTime>;
267
268//////////////
269// TxInInfo //
270//////////////
271
272/// An input of a pending transaction.
273#[derive(Clone, Debug, PartialEq, Eq, IsPlutusData)]
274#[is_plutus_data_derive_strategy = "Constr"]
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276#[cfg_attr(feature = "lbf", derive(Json))]
277pub struct TxInInfo {
278    pub reference: TransactionInput,
279    pub output: TransactionOutput,
280}
281
282impl From<(TransactionInput, TransactionOutput)> for TxInInfo {
283    fn from((reference, output): (TransactionInput, TransactionOutput)) -> TxInInfo {
284        TxInInfo { reference, output }
285    }
286}
287
288///////////
289// DCert //
290///////////
291
292/// Partial representation of digests of certificates on the ledger.
293#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, IsPlutusData)]
294#[is_plutus_data_derive_strategy = "Constr"]
295#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
296#[cfg_attr(feature = "lbf", derive(Json))]
297pub enum DCert {
298    DelegRegKey(StakingCredential),
299    DelegDeRegKey(StakingCredential),
300    DelegDelegate(
301        /// Delegator
302        StakingCredential,
303        /// Delegatee
304        PaymentPubKeyHash,
305    ),
306    /// A digest of the PoolParam
307    PoolRegister(
308        /// Pool id
309        PaymentPubKeyHash,
310        /// Pool VFR
311        PaymentPubKeyHash,
312    ),
313    PoolRetire(
314        PaymentPubKeyHash,
315        /// Epoch
316        BigInt,
317    ),
318    Genesis,
319    Mir,
320}
321
322///////////////////
323// ScriptPurpose //
324///////////////////
325
326/// The purpose of the script that's currently running.
327#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, IsPlutusData)]
328#[is_plutus_data_derive_strategy = "Constr"]
329#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
330#[cfg_attr(feature = "lbf", derive(Json))]
331pub enum ScriptPurpose {
332    Minting(CurrencySymbol),
333    Spending(TransactionInput),
334    Rewarding(StakingCredential),
335    Certifying(DCert),
336}
337
338/////////////////////
339// TransactionInfo //
340/////////////////////
341
342/// A pending transaction as seen by validator scripts, also known as TxInfo in Plutus
343#[derive(Debug, PartialEq, Eq, Clone, IsPlutusData)]
344#[is_plutus_data_derive_strategy = "Constr"]
345#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
346#[cfg_attr(feature = "lbf", derive(Json))]
347pub struct TransactionInfo {
348    pub inputs: Vec<TxInInfo>,
349    pub outputs: Vec<TransactionOutput>,
350    pub fee: Value,
351    pub mint: Value,
352    pub d_cert: Vec<DCert>,
353    pub wdrl: Vec<(StakingCredential, BigInt)>,
354    pub valid_range: POSIXTimeRange,
355    pub signatories: Vec<PaymentPubKeyHash>,
356    pub datums: Vec<(DatumHash, Datum)>,
357    pub id: TransactionHash,
358}
359
360///////////////////
361// ScriptContext //
362///////////////////
363
364/// The context that is presented to the currently-executing script.
365#[derive(Debug, PartialEq, Eq, Clone, IsPlutusData)]
366#[is_plutus_data_derive_strategy = "Constr"]
367#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
368#[cfg_attr(feature = "lbf", derive(Json))]
369pub struct ScriptContext {
370    pub tx_info: TransactionInfo,
371    pub purpose: ScriptPurpose,
372}