oasis_contract_sdk_types/
address.rs

1//! A minimal representation of an Oasis Runtime SDK address.
2use std::convert::TryFrom;
3
4use bech32::{Bech32, Hrp};
5use thiserror::Error;
6
7const ADDRESS_VERSION_SIZE: usize = 1;
8const ADDRESS_DATA_SIZE: usize = 20;
9const ADDRESS_SIZE: usize = ADDRESS_VERSION_SIZE + ADDRESS_DATA_SIZE;
10
11const ADDRESS_BECH32_HRP: Hrp = Hrp::parse_unchecked("oasis");
12
13/// Error.
14#[derive(Error, Debug)]
15pub enum Error {
16    #[error("malformed address")]
17    MalformedAddress,
18}
19
20/// An account address.
21#[derive(
22    Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, cbor::Encode, cbor::Decode,
23)]
24#[cbor(transparent)]
25pub struct Address([u8; ADDRESS_SIZE]);
26
27impl Address {
28    /// Size of an address in bytes.
29    pub const SIZE: usize = ADDRESS_SIZE;
30
31    /// Tries to create a new address from raw bytes.
32    pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
33        if data.len() != ADDRESS_SIZE {
34            return Err(Error::MalformedAddress);
35        }
36
37        let mut a = [0; ADDRESS_SIZE];
38        a.copy_from_slice(data);
39
40        Ok(Self(a))
41    }
42
43    /// Tries to create a new address from Bech32-encoded string.
44    pub fn from_bech32(data: &str) -> Result<Self, Error> {
45        let (hrp, data) = bech32::decode(data).map_err(|_| Error::MalformedAddress)?;
46        if hrp != ADDRESS_BECH32_HRP {
47            return Err(Error::MalformedAddress);
48        }
49
50        Address::from_bytes(&data)
51    }
52
53    /// Converts an address to Bech32 representation.
54    pub fn to_bech32(self) -> String {
55        bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &self.0).unwrap()
56    }
57}
58
59impl AsRef<[u8]> for Address {
60    fn as_ref(&self) -> &[u8] {
61        &self.0
62    }
63}
64
65impl TryFrom<&[u8]> for Address {
66    type Error = Error;
67
68    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
69        Self::from_bytes(bytes)
70    }
71}
72
73#[cfg(feature = "oasis-runtime-sdk")]
74impl From<oasis_runtime_sdk::types::address::Address> for Address {
75    fn from(a: oasis_runtime_sdk::types::address::Address) -> Self {
76        Self(a.into_bytes())
77    }
78}
79
80#[cfg(feature = "oasis-runtime-sdk")]
81impl From<Address> for oasis_runtime_sdk::types::address::Address {
82    fn from(a: Address) -> Self {
83        oasis_runtime_sdk::types::address::Address::from_bytes(&a.0).unwrap()
84    }
85}
86
87#[cfg(test)]
88mod test {
89    use bech32::Bech32m;
90
91    use super::*;
92
93    #[test]
94    fn test_address_try_from_bytes() {
95        let bytes_fixture = vec![42u8; ADDRESS_SIZE + 1];
96        assert_eq!(
97            Address::try_from(&bytes_fixture[0..ADDRESS_SIZE]).unwrap(),
98            Address::from_bytes(&bytes_fixture[0..ADDRESS_SIZE]).unwrap()
99        );
100        assert!(matches!(
101            Address::try_from(bytes_fixture.as_slice()).unwrap_err(),
102            Error::MalformedAddress
103        ));
104    }
105
106    #[test]
107    fn test_address_from_bech32_invalid_hrp() {
108        assert!(matches!(
109            Address::from_bech32("sisoa1qpcprk8jxpsjxw9fadxvzrv9ln7td69yus8rmtux").unwrap_err(),
110            Error::MalformedAddress,
111        ));
112    }
113
114    #[test]
115    fn test_address_from_bech32_invalid_variant() {
116        let b = vec![42u8; ADDRESS_SIZE];
117        let bech32_addr = bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &b).unwrap();
118        let bech32m_addr = bech32::encode::<Bech32m>(ADDRESS_BECH32_HRP, &b).unwrap();
119
120        assert!(
121            Address::from_bech32(&bech32_addr).is_ok(),
122            "bech32 address should be ok"
123        );
124        assert!(
125            Address::from_bech32(&bech32m_addr).is_ok(),
126            "bech32m address should be ok",
127        );
128    }
129}