plutus_ledger_api/v1/
address.rs

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