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#[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#[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
59impl 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
102pub(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#[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
177pub(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#[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#[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
264pub type POSIXTimeRange = PlutusInterval<POSIXTime>;
269
270#[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#[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 StakingCredential,
305 PaymentPubKeyHash,
307 ),
308 PoolRegister(
310 PaymentPubKeyHash,
312 PaymentPubKeyHash,
314 ),
315 PoolRetire(
316 PaymentPubKeyHash,
317 BigInt,
319 ),
320 Genesis,
321 Mir,
322}
323
324#[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#[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#[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}