plutus_ledger_api/v1/
address.rs

1//! Types related to Cardano addresses
2use std::borrow::Cow;
3use std::str::FromStr;
4
5use anyhow::anyhow;
6use cardano_serialization_lib as csl;
7#[cfg(feature = "lbf")]
8use lbr_prelude::json::{self, Error, Json};
9use num_bigint::BigInt;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12#[cfg(feature = "serde")]
13use serde_with::{DeserializeFromStr, SerializeDisplay};
14
15use crate as plutus_ledger_api;
16use crate::csl::csl_to_pla::{FromCSL, TryFromCSL, TryFromCSLError, TryToPLA};
17use crate::csl::pla_to_csl::{TryFromPLA, TryFromPLAError, TryToCSL};
18use crate::plutus_data::{
19    parse_constr, parse_fixed_len_constr_fields, IsPlutusData, PlutusData, PlutusDataError,
20};
21use crate::v1::crypto::Ed25519PubKeyHash;
22use crate::v1::script::ValidatorHash;
23
24/////////////
25// Address //
26/////////////
27
28/// Shelley Address for wallets or validators
29///
30/// An address consists of a payment part (credential) and a delegation part (staking_credential).
31/// In order to serialize an address to `bech32`, the network kind must be known.
32/// For a better understanding of all the Cardano address types, read [CIP 19](https://cips.cardano.org/cips/cip19/)
33#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
34#[is_plutus_data_derive_strategy = "Constr"]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "lbf", derive(Json))]
37pub struct Address {
38    pub credential: Credential,
39    pub staking_credential: Option<StakingCredential>,
40}
41
42impl Address {
43    pub fn with_extra_info<'a>(&'a self, network_tag: u8) -> AddressWithExtraInfo<'a> {
44        AddressWithExtraInfo {
45            address: Cow::Borrowed(self),
46            network_tag,
47        }
48    }
49
50    pub fn into_address_with_extra_info<'a>(self, network_tag: u8) -> AddressWithExtraInfo<'a> {
51        AddressWithExtraInfo {
52            address: Cow::Owned(self),
53            network_tag,
54        }
55    }
56}
57
58impl FromStr for Address {
59    type Err = anyhow::Error;
60
61    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
62        let csl_addr = csl::Address::from_bech32(s)
63            .map_err(|err| anyhow!("Couldn't parse bech32 address: {}", err))?;
64        csl_addr
65            .try_to_pla()
66            .map_err(|err| anyhow!("Couldn't convert address: {}", err))
67    }
68}
69
70impl TryFromCSL<csl::Address> for Address {
71    fn try_from_csl(value: &csl::Address) -> Result<Self, TryFromCSLError> {
72        if let Some(addr) = csl::BaseAddress::from_address(value) {
73            Ok(Address {
74                credential: Credential::from_csl(&addr.payment_cred()),
75                staking_credential: Some(StakingCredential::from_csl(&addr.stake_cred())),
76            })
77        } else if let Some(addr) = csl::PointerAddress::from_address(value) {
78            Ok(Address {
79                credential: Credential::from_csl(&addr.payment_cred()),
80                staking_credential: Some(StakingCredential::from_csl(&addr.stake_pointer())),
81            })
82        } else if let Some(addr) = csl::EnterpriseAddress::from_address(value) {
83            Ok(Address {
84                credential: Credential::from_csl(&addr.payment_cred()),
85                staking_credential: None,
86            })
87        } else {
88            Err(TryFromCSLError::ImpossibleConversion(format!(
89                "Unable to represent address {:?}",
90                value
91            )))
92        }
93    }
94}
95
96#[derive(Clone, Debug)]
97#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
98/// Address with network information. The `WithExtraInfo` variant has Display instance, serializing into
99/// a bech32 address format.
100pub struct AddressWithExtraInfo<'a> {
101    pub address: Cow<'a, Address>,
102    pub network_tag: u8,
103}
104
105impl TryFromPLA<AddressWithExtraInfo<'_>> for csl::Address {
106    fn try_from_pla(val: &AddressWithExtraInfo<'_>) -> Result<Self, TryFromPLAError> {
107        let payment = val.address.credential.try_to_csl()?;
108
109        Ok(match val.address.staking_credential {
110            None => csl::EnterpriseAddress::new(val.network_tag, &payment).to_address(),
111            Some(ref sc) => match sc {
112                StakingCredential::Hash(c) => {
113                    csl::BaseAddress::new(val.network_tag, &payment, &c.try_to_csl()?).to_address()
114                }
115                StakingCredential::Pointer(ptr) => {
116                    csl::PointerAddress::new(val.network_tag, &payment, &ptr.try_to_csl()?)
117                        .to_address()
118                }
119            },
120        })
121    }
122}
123
124impl<'a> TryFromCSL<csl::Address> for AddressWithExtraInfo<'a> {
125    fn try_from_csl(value: &csl::Address) -> Result<Self, TryFromCSLError>
126    where
127        Self: Sized,
128    {
129        Ok(AddressWithExtraInfo {
130            address: Cow::Owned(value.try_to_pla()?),
131            network_tag: value.network_id().map_err(|err| {
132                TryFromCSLError::ImpossibleConversion(format!(
133                    "Couldn't extract network tag from address: {err}"
134                ))
135            })?,
136        })
137    }
138}
139
140/// Serializing into a bech32 address format.
141impl std::fmt::Display for AddressWithExtraInfo<'_> {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        let bech32_addr: Option<String> = self
144            .try_to_csl()
145            .ok()
146            .and_then(|csl_addr: csl::Address| csl_addr.to_bech32(None).ok());
147        match bech32_addr {
148            Some(addr) => write!(f, "{}", addr),
149            None => write!(f, "INVALID ADDRESS {:?}", self),
150        }
151    }
152}
153
154impl<'a> FromStr for AddressWithExtraInfo<'a> {
155    type Err = anyhow::Error;
156
157    fn from_str(s: &str) -> Result<Self, Self::Err> {
158        let csl_addr = csl::Address::from_bech32(s)
159            .map_err(|err| anyhow!("Couldn't parse bech32 address: {}", err))?;
160        csl_addr
161            .try_to_pla()
162            .map_err(|err| anyhow!("Couldn't convert address: {}", err))
163    }
164}
165
166////////////////
167// Credential //
168////////////////
169
170/// A public key hash or validator hash credential (used as a payment or a staking credential)
171#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
172#[is_plutus_data_derive_strategy = "Constr"]
173#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
174pub enum Credential {
175    PubKey(Ed25519PubKeyHash),
176    Script(ValidatorHash),
177}
178
179#[cfg(feature = "lbf")]
180impl Json for Credential {
181    fn to_json(&self) -> serde_json::Value {
182        match self {
183            Credential::PubKey(pkh) => {
184                json::json_constructor("PubKeyCredential", vec![pkh.to_json()])
185            }
186            Credential::Script(val_hash) => {
187                json::json_constructor("ScriptCredential", vec![val_hash.to_json()])
188            }
189        }
190    }
191
192    fn from_json(value: &serde_json::Value) -> Result<Self, Error> {
193        json::case_json_constructor(
194            "Plutus.V1.Credential",
195            vec![
196                (
197                    "PubKeyCredential",
198                    Box::new(|ctor_fields| match &ctor_fields[..] {
199                        [pkh] => Ok(Credential::PubKey(Json::from_json(pkh)?)),
200                        _ => Err(Error::UnexpectedArrayLength {
201                            wanted: 1,
202                            got: ctor_fields.len(),
203                            parser: "Plutus.V1.Credential".to_owned(),
204                        }),
205                    }),
206                ),
207                (
208                    "ScriptCredential",
209                    Box::new(|ctor_fields| match &ctor_fields[..] {
210                        [val_hash] => Ok(Credential::Script(Json::from_json(val_hash)?)),
211                        _ => Err(Error::UnexpectedArrayLength {
212                            wanted: 1,
213                            got: ctor_fields.len(),
214                            parser: "Plutus.V1.Credential".to_owned(),
215                        }),
216                    }),
217                ),
218            ],
219            value,
220        )
221    }
222}
223
224impl FromCSL<csl::Credential> for Credential {
225    fn from_csl(value: &csl::Credential) -> Self {
226        match value.kind() {
227            csl::CredKind::Key => {
228                Credential::PubKey(Ed25519PubKeyHash::from_csl(&value.to_keyhash().unwrap()))
229            }
230            csl::CredKind::Script => {
231                Credential::Script(ValidatorHash::from_csl(&value.to_scripthash().unwrap()))
232            }
233        }
234    }
235}
236
237impl TryFromPLA<Credential> for csl::Credential {
238    fn try_from_pla(val: &Credential) -> Result<Self, TryFromPLAError> {
239        match val {
240            Credential::PubKey(pkh) => Ok(csl::Credential::from_keyhash(&pkh.try_to_csl()?)),
241            Credential::Script(sh) => Ok(csl::Credential::from_scripthash(&sh.0.try_to_csl()?)),
242        }
243    }
244}
245
246///////////////////////
247// StakingCredential //
248///////////////////////
249
250/// Credential (public key hash or pointer) used for staking
251#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
253pub enum StakingCredential {
254    Hash(Credential),
255    Pointer(ChainPointer),
256}
257
258// NOTE(chfanghr): ChainPointer doesn't have a IsPlutusData instance so derive doesn't work here.
259impl IsPlutusData for StakingCredential {
260    fn to_plutus_data(&self) -> PlutusData {
261        match self {
262            StakingCredential::Hash(credential) => {
263                PlutusData::Constr(BigInt::from(0), vec![credential.to_plutus_data()])
264            }
265            StakingCredential::Pointer(ChainPointer {
266                slot_number,
267                transaction_index,
268                certificate_index,
269            }) => PlutusData::Constr(
270                BigInt::from(1),
271                vec![
272                    slot_number.to_plutus_data(),
273                    transaction_index.to_plutus_data(),
274                    certificate_index.to_plutus_data(),
275                ],
276            ),
277        }
278    }
279
280    fn from_plutus_data(data: &PlutusData) -> Result<Self, PlutusDataError> {
281        let (tag, fields) = parse_constr(data)?;
282        match tag {
283            0 => {
284                let [field] = parse_fixed_len_constr_fields::<1>(fields)?;
285                Ok(Self::Hash(Credential::from_plutus_data(field)?))
286            }
287            1 => {
288                let [field_0, field_1, field_2] = parse_fixed_len_constr_fields::<3>(fields)?;
289                Ok(Self::Pointer(ChainPointer {
290                    slot_number: Slot::from_plutus_data(field_0)?,
291                    transaction_index: TransactionIndex::from_plutus_data(field_1)?,
292                    certificate_index: CertificateIndex::from_plutus_data(field_2)?,
293                }))
294            }
295            _ => Err(PlutusDataError::UnexpectedPlutusInvariant {
296                wanted: "Constr with tag 0 or 1".to_owned(),
297                got: tag.to_string(),
298            }),
299        }
300    }
301}
302
303#[cfg(feature = "lbf")]
304impl Json for StakingCredential {
305    fn to_json(&self) -> serde_json::Value {
306        match self {
307            StakingCredential::Hash(pkh) => {
308                json::json_constructor("StakingHash", vec![pkh.to_json()])
309            }
310            StakingCredential::Pointer(val_hash) => {
311                json::json_constructor("StakingPtr", vec![val_hash.to_json()])
312            }
313        }
314    }
315
316    fn from_json(value: &serde_json::Value) -> Result<Self, Error> {
317        json::case_json_constructor(
318            "Plutus.V1.StakingCredential",
319            vec![
320                (
321                    "StakingHash",
322                    Box::new(|ctor_fields| match &ctor_fields[..] {
323                        [pkh] => Ok(StakingCredential::Hash(Json::from_json(pkh)?)),
324                        _ => Err(Error::UnexpectedArrayLength {
325                            wanted: 1,
326                            got: ctor_fields.len(),
327                            parser: "Plutus.V1.StakingCredential".to_owned(),
328                        }),
329                    }),
330                ),
331                (
332                    "StakingPtr",
333                    Box::new(|ctor_fields| match &ctor_fields[..] {
334                        [val_hash] => Ok(StakingCredential::Pointer(Json::from_json(val_hash)?)),
335                        _ => Err(Error::UnexpectedArrayLength {
336                            wanted: 1,
337                            got: ctor_fields.len(),
338                            parser: "Plutus.V1.StakingCredential".to_owned(),
339                        }),
340                    }),
341                ),
342            ],
343            value,
344        )
345    }
346}
347
348impl FromCSL<csl::Credential> for StakingCredential {
349    fn from_csl(value: &csl::Credential) -> Self {
350        StakingCredential::Hash(Credential::from_csl(value))
351    }
352}
353
354impl TryFromPLA<StakingCredential> for csl::Credential {
355    fn try_from_pla(val: &StakingCredential) -> Result<Self, TryFromPLAError> {
356        match val {
357            StakingCredential::Hash(c) => c.try_to_csl(),
358            StakingCredential::Pointer(_) => Err(TryFromPLAError::ImpossibleConversion(
359                "cannot represent chain pointer".into(),
360            )),
361        }
362    }
363}
364
365impl FromCSL<csl::Pointer> for StakingCredential {
366    fn from_csl(value: &csl::Pointer) -> Self {
367        StakingCredential::Pointer(ChainPointer::from_csl(value))
368    }
369}
370
371#[derive(Clone, Debug)]
372pub struct RewardAddressWithExtraInfo<'a> {
373    pub staking_credential: Cow<'a, StakingCredential>,
374    pub network_tag: u8,
375}
376
377impl TryFromPLA<RewardAddressWithExtraInfo<'_>> for csl::RewardAddress {
378    fn try_from_pla(val: &RewardAddressWithExtraInfo<'_>) -> Result<Self, TryFromPLAError> {
379        Ok(csl::RewardAddress::new(
380            val.network_tag,
381            &val.staking_credential.as_ref().try_to_csl()?,
382        ))
383    }
384}
385
386//////////////////
387// ChainPointer //
388//////////////////
389
390/// In an address, a chain pointer refers to a point of the chain containing a stake key
391/// registration certificate. A point is identified by 3 coordinates:
392/// - An absolute slot number
393/// - A transaction inder (within that slot)
394/// - A (delegation) certificate index (within that transacton)
395#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
396#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
397#[cfg_attr(feature = "lbf", derive(Json))]
398pub struct ChainPointer {
399    pub slot_number: Slot,
400    pub transaction_index: TransactionIndex,
401    pub certificate_index: CertificateIndex,
402}
403
404impl FromCSL<csl::Pointer> for ChainPointer {
405    fn from_csl(value: &csl::Pointer) -> Self {
406        ChainPointer {
407            slot_number: Slot::from_csl(&value.slot_bignum()),
408            transaction_index: TransactionIndex::from_csl(&value.tx_index_bignum()),
409            certificate_index: CertificateIndex::from_csl(&value.cert_index_bignum()),
410        }
411    }
412}
413
414impl TryFromPLA<ChainPointer> for csl::Pointer {
415    fn try_from_pla(val: &ChainPointer) -> Result<Self, TryFromPLAError> {
416        Ok(csl::Pointer::new_pointer(
417            &val.slot_number.try_to_csl()?,
418            &val.transaction_index.try_to_csl()?,
419            &val.certificate_index.try_to_csl()?,
420        ))
421    }
422}
423
424//////////
425// Slot //
426//////////
427
428/// Number of slots elapsed since genesis
429#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
430#[is_plutus_data_derive_strategy = "Newtype"]
431#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
432#[cfg_attr(feature = "lbf", derive(Json))]
433pub struct Slot(pub BigInt);
434
435impl FromCSL<csl::BigNum> for Slot {
436    fn from_csl(value: &csl::BigNum) -> Self {
437        Slot(BigInt::from_csl(value))
438    }
439}
440
441impl TryFromPLA<Slot> for csl::BigNum {
442    fn try_from_pla(val: &Slot) -> Result<Self, TryFromPLAError> {
443        val.0.try_to_csl()
444    }
445}
446
447//////////////////////
448// CertificateIndex //
449//////////////////////
450
451/// Position of the certificate in a certain transaction
452#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
453#[is_plutus_data_derive_strategy = "Newtype"]
454#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
455#[cfg_attr(feature = "lbf", derive(Json))]
456pub struct CertificateIndex(pub BigInt);
457
458impl FromCSL<csl::BigNum> for CertificateIndex {
459    fn from_csl(value: &csl::BigNum) -> Self {
460        CertificateIndex(BigInt::from_csl(value))
461    }
462}
463
464impl TryFromPLA<CertificateIndex> for csl::BigNum {
465    fn try_from_pla(val: &CertificateIndex) -> Result<Self, TryFromPLAError> {
466        val.0.try_to_csl()
467    }
468}
469
470//////////////////////
471// TransactionIndex //
472//////////////////////
473
474/// Position of a transaction in a given slot
475/// This is not identical to the index of a `TransactionInput`
476#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)]
477#[is_plutus_data_derive_strategy = "Newtype"]
478#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
479#[cfg_attr(feature = "lbf", derive(Json))]
480pub struct TransactionIndex(pub BigInt);
481
482impl FromCSL<csl::BigNum> for TransactionIndex {
483    fn from_csl(value: &csl::BigNum) -> Self {
484        TransactionIndex(BigInt::from_csl(value))
485    }
486}
487
488impl TryFromPLA<TransactionIndex> for csl::BigNum {
489    fn try_from_pla(val: &TransactionIndex) -> Result<Self, TryFromPLAError> {
490        val.0.try_to_csl()
491    }
492}