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