1use 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#[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
57impl 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
100pub(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#[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
175pub(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#[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#[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
262pub type POSIXTimeRange = PlutusInterval<POSIXTime>;
267
268#[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#[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        StakingCredential,
303        PaymentPubKeyHash,
305    ),
306    PoolRegister(
308        PaymentPubKeyHash,
310        PaymentPubKeyHash,
312    ),
313    PoolRetire(
314        PaymentPubKeyHash,
315        BigInt,
317    ),
318    Genesis,
319    Mir,
320}
321
322#[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#[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#[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}