kon-cli/crates/cardano-tx-builder/src/address.rs

153 lines
4.6 KiB
Rust

/// Seems like quite a lot of code just to pullout `from_bech32` on address
/// This is mostly extracted from private function of pallas_addresses
use anyhow::{Result, anyhow};
use pallas_addresses;
pub use pallas_addresses::Network;
use pallas_primitives::NetworkId;
use crate::utils::v2a;
/// Network
pub fn network_from_id(id: NetworkId) -> Network {
match id {
NetworkId::Testnet => Network::Testnet,
NetworkId::Mainnet => Network::Mainnet,
}
}
fn parse_network(header: u8) -> Network {
let masked = header & 0b0000_1111;
match masked {
0b_0000_0000 => Network::Testnet,
0b_0000_0001 => Network::Mainnet,
_ => Network::Other(masked),
}
}
/// Credential
#[derive(Debug, PartialEq, Clone)]
pub enum Credential {
Key([u8; 28]),
Script([u8; 28]),
}
impl Into<pallas_addresses::ShelleyPaymentPart> for Credential {
fn into(self) -> pallas_addresses::ShelleyPaymentPart {
match self {
Credential::Key(hash) => pallas_addresses::ShelleyPaymentPart::Key(hash.into()),
Credential::Script(hash) => pallas_addresses::ShelleyPaymentPart::Script(hash.into()),
}
}
}
impl Into<pallas_addresses::ShelleyDelegationPart> for Credential {
fn into(self) -> pallas_addresses::ShelleyDelegationPart {
match self {
Credential::Key(hash) => pallas_addresses::ShelleyDelegationPart::Key(hash.into()),
Credential::Script(hash) => {
pallas_addresses::ShelleyDelegationPart::Script(hash.into())
}
}
}
}
/// Address
#[derive(Debug)]
pub struct Address {
pub network: Network,
pub payment: Credential,
pub delegation: Option<Credential>,
}
impl Address {
pub fn new(network: Network, payment: Credential, delegation: Option<Credential>) -> Self {
Self {
network,
payment,
delegation,
}
}
pub fn from_bech32(s: &str) -> Result<Self> {
let (_, bytes) = bech32::decode(s)?;
let x = Address::try_from(bytes)?;
Ok(x)
}
pub fn to_bech32(self) -> String {
Into::<pallas_addresses::ShelleyAddress>::into(self)
.to_bech32()
.unwrap()
}
}
impl TryFrom<Vec<u8>> for Address {
fn try_from(bytes: Vec<u8>) -> std::result::Result<Self, Self::Error> {
let header = *bytes.first().expect("Missing bytes");
let network = parse_network(header);
let payment: [u8; 28] = v2a(bytes[1..=28].to_vec())?.into();
let delegation = v2a(bytes[28..].to_vec());
match header & 0b1111_0000 {
0b0000_0000 => Ok(Address::new(
network,
Credential::Key(payment),
Some(Credential::Key(delegation?.into())),
)),
0b0001_0000 => Ok(Address::new(
network,
Credential::Script(payment),
Some(Credential::Key(delegation?.into())),
)),
0b0010_0000 => Ok(Address::new(
network,
Credential::Key(payment),
Some(Credential::Script(delegation?.into())),
)),
0b0011_0000 => Ok(Address::new(
network,
Credential::Script(payment),
Some(Credential::Script(delegation?.into())),
)),
0b0110_0000 => Ok(Address::new(network, Credential::Key(payment), None)),
0b0111_0000 => Ok(Address::new(network, Credential::Script(payment), None)),
_ => Err(anyhow!("Header not supported")),
}
}
type Error = anyhow::Error;
}
impl Into<pallas_addresses::ShelleyAddress> for Address {
fn into(self) -> pallas_addresses::ShelleyAddress {
let network = self.network;
pallas_addresses::ShelleyAddress::new(
network,
self.payment.into(),
self.delegation
.map_or(pallas_addresses::ShelleyDelegationPart::Null, |x| x.into()),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_bech32() {
let testnet = "addr_test1vqpymjsk49r6p9f2f6d3gm04ther0ka7ts8jsv8dukg4nvgcdjxy9";
let addr0 = Address::from_bech32(testnet).unwrap();
let mainnet = "addr1vypymjsk49r6p9f2f6d3gm04ther0ka7ts8jsv8dukg4nvgr9x6tq";
let addr1 = Address::from_bech32(mainnet).unwrap();
assert_eq!(addr0.delegation, addr1.delegation, "oops");
let sa0: pallas_addresses::ShelleyAddress = addr0.into();
assert_eq!(sa0.to_bech32().unwrap(), testnet, "oops");
let sa1: pallas_addresses::ShelleyAddress = addr1.into();
assert_eq!(sa1.to_bech32().unwrap(), mainnet, "oops");
}
}