Skip to content

Commit eaf071e

Browse files
Move Cip0134Uri to cadrano-blockchain-types (#174)
- Move the Cip0134Uri type to the cadrano-blockchain-types crate (from rbac-registration) - Add the txs function to MultiEraBlock. - Implement From<TxnIndex> for usize.
1 parent d84d29c commit eaf071e

File tree

5 files changed

+216
-19
lines changed

5 files changed

+216
-19
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//! An URI in the CIP-0134 format.
2+
3+
// Ignore URIs that are used in tests and doc-examples.
4+
// cSpell:ignoreRegExp web\+cardano:.+
5+
6+
use std::fmt::{Display, Formatter};
7+
8+
use anyhow::{anyhow, Context, Error, Result};
9+
use pallas::ledger::addresses::Address;
10+
11+
/// A URI in the CIP-0134 format.
12+
///
13+
/// See the [proposal] for more details.
14+
///
15+
/// [proposal]: https://github.com/cardano-foundation/CIPs/pull/888
16+
#[derive(Debug, Clone, Eq, PartialEq)]
17+
#[allow(clippy::module_name_repetitions)]
18+
pub struct Cip0134Uri {
19+
/// A URI string.
20+
uri: String,
21+
/// An address parsed from the URI.
22+
address: Address,
23+
}
24+
25+
impl Cip0134Uri {
26+
/// Creates a new `Cip0134Uri` instance by parsing the given URI.
27+
///
28+
/// # Errors
29+
/// - Invalid URI.
30+
///
31+
/// # Examples
32+
///
33+
/// ```
34+
/// use cardano_blockchain_types::Cip0134Uri;
35+
///
36+
/// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
37+
/// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
38+
/// ```
39+
pub fn parse(uri: &str) -> Result<Self> {
40+
let bech32 = uri
41+
.strip_prefix("web+cardano://addr/")
42+
.ok_or_else(|| anyhow!("Missing schema part of URI"))?;
43+
let address = Address::from_bech32(bech32).context("Unable to parse bech32 part of URI")?;
44+
45+
Ok(Self {
46+
uri: uri.to_owned(),
47+
address,
48+
})
49+
}
50+
51+
/// Returns a URI string.
52+
///
53+
/// # Examples
54+
///
55+
/// ```
56+
/// use cardano_blockchain_types::Cip0134Uri;
57+
///
58+
/// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
59+
/// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
60+
/// assert_eq!(cip0134_uri.uri(), uri);
61+
/// ```
62+
#[must_use]
63+
pub fn uri(&self) -> &str {
64+
&self.uri
65+
}
66+
67+
/// Returns a URI string.
68+
///
69+
/// # Examples
70+
///
71+
/// ```
72+
/// use cardano_blockchain_types::Cip0134Uri;
73+
/// use pallas::ledger::addresses::{Address, Network};
74+
///
75+
/// let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
76+
/// let cip0134_uri = Cip0134Uri::parse(uri).unwrap();
77+
/// let Address::Stake(address) = cip0134_uri.address() else {
78+
/// panic!("Unexpected address type");
79+
/// };
80+
/// assert_eq!(address.network(), Network::Mainnet);
81+
/// ```
82+
#[must_use]
83+
pub fn address(&self) -> &Address {
84+
&self.address
85+
}
86+
}
87+
88+
impl Display for Cip0134Uri {
89+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
90+
write!(f, "{}", self.uri())
91+
}
92+
}
93+
94+
impl TryFrom<&[u8]> for Cip0134Uri {
95+
type Error = Error;
96+
97+
fn try_from(value: &[u8]) -> Result<Self> {
98+
let address = std::str::from_utf8(value)
99+
.with_context(|| format!("Invalid utf8 string: '{value:?}'"))?;
100+
Self::parse(address)
101+
}
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
use pallas::ledger::addresses::{Address, Network};
107+
108+
use super::*;
109+
110+
#[test]
111+
fn invalid_prefix() {
112+
// cSpell:disable
113+
let test_uris = [
114+
"addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
115+
"//addr/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
116+
"web+cardano:/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
117+
"somthing+unexpected://addr/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
118+
];
119+
// cSpell:enable
120+
121+
for uri in test_uris {
122+
let err = format!("{:?}", Cip0134Uri::parse(uri).expect_err(uri));
123+
assert!(err.starts_with("Missing schema part of URI"));
124+
}
125+
}
126+
127+
#[test]
128+
fn invalid_bech32() {
129+
let uri = "web+cardano://addr/adr1qx2fxv2umyh";
130+
let err = format!("{:?}", Cip0134Uri::parse(uri).unwrap_err());
131+
assert!(err.starts_with("Unable to parse bech32 part of URI"));
132+
}
133+
134+
#[test]
135+
fn stake_address() {
136+
let test_data = [
137+
(
138+
"web+cardano://addr/stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn",
139+
Network::Testnet,
140+
"337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251",
141+
),
142+
(
143+
"web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw",
144+
Network::Mainnet,
145+
"337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251",
146+
),
147+
(
148+
"web+cardano://addr/drep_vk17axh4sc9zwkpsft3tlgpjemfwc0u5mnld80r85zw7zdqcst6w54sdv4a4e",
149+
Network::Other(7),
150+
"4d7ac30513ac1825715fd0196769761fca6e7f69de33d04ef09a0c41",
151+
)
152+
];
153+
154+
for (uri, network, payload) in test_data {
155+
let cip0134_uri = Cip0134Uri::parse(uri).expect(uri);
156+
let Address::Stake(address) = cip0134_uri.address() else {
157+
panic!("Unexpected address type ({uri})");
158+
};
159+
assert_eq!(network, address.network());
160+
assert_eq!(payload, address.payload().as_hash().to_string());
161+
}
162+
}
163+
164+
#[test]
165+
fn shelley_address() {
166+
let test_data = [
167+
(
168+
"web+cardano://addr/addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x",
169+
Network::Mainnet,
170+
),
171+
(
172+
"web+cardano://addr/addr_test1gz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrdw5vky",
173+
Network::Testnet,
174+
),
175+
(
176+
"web+cardano://addr/cc_hot_vk10y48lq72hypxraew74lwjjn9e2dscuwphckglh2nrrpkgweqk5hschnzv5",
177+
Network::Other(9),
178+
)
179+
];
180+
181+
for (uri, network) in test_data {
182+
let cip0134_uri = Cip0134Uri::parse(uri).expect(uri);
183+
let Address::Shelley(address) = cip0134_uri.address() else {
184+
panic!("Unexpected address type ({uri})");
185+
};
186+
assert_eq!(network, address.network());
187+
}
188+
}
189+
190+
// The Display should return the original URI.
191+
#[test]
192+
fn display() {
193+
let uri = "web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw";
194+
let cip0134_uri = Cip0134Uri::parse(uri).expect(uri);
195+
assert_eq!(uri, cip0134_uri.to_string());
196+
}
197+
}

rust/cardano-blockchain-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Catalyst Enhanced `MultiEraBlock` Structures
22
33
mod auxdata;
4+
mod cip134_uri;
45
mod fork;
56
mod metadata;
67
mod multi_era_block_data;
@@ -18,6 +19,7 @@ pub use auxdata::{
1819
metadatum_value::MetadatumValue,
1920
scripts::{Script, ScriptArray, ScriptType, TransactionScripts},
2021
};
22+
pub use cip134_uri::Cip0134Uri;
2123
pub use fork::Fork;
2224
pub use metadata::cip36::{voting_pk::VotingPubKey, Cip36};
2325
pub use multi_era_block_data::MultiEraBlock;

rust/cardano-blockchain-types/src/multi_era_block_data.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{cmp::Ordering, fmt::Display, sync::Arc};
1212
use anyhow::bail;
1313
use ed25519_dalek::VerifyingKey;
1414
use ouroboros::self_referencing;
15+
use pallas::ledger::traverse::MultiEraTx;
1516
use tracing::debug;
1617

1718
use crate::{
@@ -91,7 +92,7 @@ impl MultiEraBlock {
9192
/// # Errors
9293
///
9394
/// If the given bytes cannot be decoded as a multi-era block, an error is returned.
94-
fn new_block(
95+
pub fn new(
9596
network: Network, raw_data: Vec<u8>, previous: &Point, fork: Fork,
9697
) -> anyhow::Result<Self> {
9798
let builder = SelfReferencedMultiEraBlockTryBuilder {
@@ -149,17 +150,6 @@ impl MultiEraBlock {
149150
})
150151
}
151152

152-
/// Creates a new `MultiEraBlockData` from the given bytes.
153-
///
154-
/// # Errors
155-
///
156-
/// If the given bytes cannot be decoded as a multi-era block, an error is returned.
157-
pub fn new(
158-
network: Network, raw_data: Vec<u8>, previous: &Point, fork: Fork,
159-
) -> anyhow::Result<Self> {
160-
MultiEraBlock::new_block(network, raw_data, previous, fork)
161-
}
162-
163153
/// Remake the block on a new fork.
164154
pub fn set_fork(&mut self, fork: Fork) {
165155
self.fork = fork;
@@ -282,6 +272,12 @@ impl MultiEraBlock {
282272
None
283273
}
284274

275+
/// Returns a list of transactions withing this block.
276+
#[must_use]
277+
pub fn txs(&self) -> Vec<MultiEraTx> {
278+
self.decode().txs()
279+
}
280+
285281
/// Get the auxiliary data of the block.
286282
#[must_use]
287283
pub fn aux_data(&self) -> &BlockAuxData {

rust/cardano-blockchain-types/src/slot.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,14 @@ use serde::Serialize;
1111

1212
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Serialize)]
1313

14-
/// Slot on the blockchain, typically one slot equals one second. However chain
14+
/// Slot on the blockchain, typically one slot equals one second. However chain
1515
/// parameters can alter how long a slot is.
1616
pub struct Slot(u64);
1717

1818
impl Slot {
1919
/// Convert an `<T>` to Slot. (saturate if out of range.)
2020
pub fn from_saturating<
21-
T: Copy
22-
+ TryInto<u64>
23-
+ std::ops::Sub<Output = T>
24-
+ std::cmp::PartialOrd<T>
25-
+ num_traits::identities::Zero,
21+
T: Copy + TryInto<u64> + Sub<Output = T> + PartialOrd<T> + num_traits::identities::Zero,
2622
>(
2723
value: T,
2824
) -> Self {

rust/cardano-blockchain-types/src/txn_index.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ impl<
1010
T: Copy
1111
+ TryInto<u16>
1212
+ std::ops::Sub<Output = T>
13-
+ std::cmp::PartialOrd<T>
13+
+ PartialOrd<T>
1414
+ num_traits::identities::Zero,
1515
> From<T> for TxnIndex
1616
{
@@ -25,6 +25,12 @@ impl From<TxnIndex> for i16 {
2525
}
2626
}
2727

28+
impl From<TxnIndex> for usize {
29+
fn from(value: TxnIndex) -> Self {
30+
value.0.into()
31+
}
32+
}
33+
2834
#[cfg(test)]
2935
mod tests {
3036
use super::*;

0 commit comments

Comments
 (0)