odra_types/
address.rs

1//! Better address representation for Casper.
2
3use core::str::FromStr;
4
5use crate::AddressError;
6use crate::AddressError::ZeroAddress;
7use crate::{OdraError, VmError};
8use alloc::{
9    string::{String, ToString},
10    vec::Vec
11};
12use casper_types::{
13    account::AccountHash,
14    bytesrepr::{self, FromBytes, ToBytes},
15    CLType, CLTyped, ContractPackageHash, Key, PublicKey
16};
17
18/// An enum representing an [`AccountHash`] or a [`ContractPackageHash`].
19#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Debug)]
20pub enum Address {
21    /// Represents an account hash.
22    Account(AccountHash),
23    /// Represents a contract package hash.
24    Contract(ContractPackageHash)
25}
26
27impl Address {
28    /// Returns the inner account hash if `self` is the `Account` variant.
29    pub fn as_account_hash(&self) -> Option<&AccountHash> {
30        if let Self::Account(v) = self {
31            Some(v)
32        } else {
33            None
34        }
35    }
36
37    /// Returns the inner contract hash if `self` is the `Contract` variant.
38    pub fn as_contract_package_hash(&self) -> Option<&ContractPackageHash> {
39        if let Self::Contract(v) = self {
40            Some(v)
41        } else {
42            None
43        }
44    }
45
46    #[cfg(feature = "test-support")]
47    pub fn account_from_str(str: &str) -> Self {
48        use alloc::format;
49        use casper_types::account::{ACCOUNT_HASH_FORMATTED_STRING_PREFIX, ACCOUNT_HASH_LENGTH};
50        let desired_length = ACCOUNT_HASH_LENGTH * 2;
51        let padding_length = desired_length - str.len();
52        let padding = "0".repeat(padding_length);
53
54        let account_str = format!("{}{}{}", ACCOUNT_HASH_FORMATTED_STRING_PREFIX, str, padding);
55        Self::Account(AccountHash::from_formatted_str(account_str.as_str()).unwrap())
56    }
57
58    #[cfg(feature = "test-support")]
59    pub fn contract_from_u32(i: u32) -> Self {
60        use alloc::format;
61        use casper_types::KEY_HASH_LENGTH;
62        let desired_length = KEY_HASH_LENGTH * 2;
63        let padding_length = desired_length - i.to_string().len();
64        let padding = "0".repeat(padding_length);
65
66        let a = i.to_string();
67        let account_str = format!("{}{}{}", "contract-package-", a, padding);
68        Self::Contract(ContractPackageHash::from_formatted_str(account_str.as_str()).unwrap())
69    }
70}
71
72impl OdraAddress for Address {
73    fn is_contract(&self) -> bool {
74        self.as_contract_package_hash().is_some()
75    }
76}
77
78impl TryFrom<ContractPackageHash> for Address {
79    type Error = AddressError;
80    fn try_from(contract_package_hash: ContractPackageHash) -> Result<Self, Self::Error> {
81        if contract_package_hash.value().iter().all(|&b| b == 0) {
82            return Err(ZeroAddress);
83        }
84        Ok(Self::Contract(contract_package_hash))
85    }
86}
87
88impl TryFrom<AccountHash> for Address {
89    type Error = AddressError;
90    fn try_from(account_hash: AccountHash) -> Result<Self, Self::Error> {
91        if account_hash.value().iter().all(|&b| b == 0) {
92            return Err(ZeroAddress);
93        }
94        Ok(Self::Account(account_hash))
95    }
96}
97
98impl From<Address> for Key {
99    fn from(address: Address) -> Self {
100        match address {
101            Address::Account(account_hash) => Key::Account(account_hash),
102            Address::Contract(contract_package_hash) => Key::Hash(contract_package_hash.value())
103        }
104    }
105}
106
107impl TryFrom<Key> for Address {
108    type Error = AddressError;
109
110    fn try_from(key: Key) -> Result<Self, Self::Error> {
111        match key {
112            Key::Account(account_hash) => Self::try_from(account_hash),
113            Key::Hash(contract_package_hash) => {
114                Self::try_from(ContractPackageHash::new(contract_package_hash))
115            }
116            _ => Err(AddressError::AddressCreationError)
117        }
118    }
119}
120
121impl From<PublicKey> for Address {
122    fn from(public_key: PublicKey) -> Self {
123        Self::Account(public_key.to_account_hash())
124    }
125}
126
127impl CLTyped for Address {
128    fn cl_type() -> CLType {
129        CLType::Key
130    }
131}
132
133impl ToBytes for Address {
134    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
135        Key::from(*self).to_bytes()
136    }
137
138    fn serialized_length(&self) -> usize {
139        Key::from(*self).serialized_length()
140    }
141}
142
143impl FromBytes for Address {
144    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
145        let (key, remainder) = Key::from_bytes(bytes)?;
146
147        let address = match key {
148            Key::Account(account_hash) => Address::Account(account_hash),
149            Key::Hash(raw_contract_package_hash) => {
150                Address::Contract(ContractPackageHash::new(raw_contract_package_hash))
151            }
152            _ => return Err(bytesrepr::Error::Formatting)
153        };
154
155        Ok((address, remainder))
156    }
157}
158
159impl TryFrom<&[u8; 33]> for Address {
160    type Error = AddressError;
161    fn try_from(value: &[u8; 33]) -> Result<Self, Self::Error> {
162        let address = Address::from_bytes(value)
163            .map(|(address, _)| address)
164            .map_err(|_| AddressError::AddressCreationError)?;
165        if address
166            .to_bytes()
167            .map_err(|_| AddressError::AddressCreationError)?
168            .iter()
169            .all(|&x| x == 0)
170        {
171            Err(ZeroAddress)
172        } else {
173            Ok(address)
174        }
175    }
176}
177
178impl FromStr for Address {
179    type Err = OdraError;
180
181    fn from_str(s: &str) -> Result<Self, Self::Err> {
182        match Key::from_formatted_str(s) {
183            Err(_) => Err(OdraError::VmError(VmError::Deserialization)),
184            Ok(key) => match key {
185                Key::Account(_) | Key::Hash(_) => match key.try_into() {
186                    Ok(address) => Ok(address),
187                    Err(_) => Err(OdraError::VmError(VmError::Deserialization))
188                },
189                _ => Err(OdraError::VmError(VmError::Deserialization))
190            }
191        }
192    }
193}
194
195impl ToString for Address {
196    fn to_string(&self) -> String {
197        Key::from(*self).to_formatted_string()
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    // TODO: casper-types > 1.5.0 will have prefix fixed.
206    const CONTRACT_PACKAGE_HASH: &str =
207        "contract-package-wasm7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
208    const ACCOUNT_HASH: &str =
209        "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95";
210    const CONTRACT_HASH: &str =
211        "hash-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
212
213    fn mock_account_hash() -> AccountHash {
214        AccountHash::from_formatted_str(ACCOUNT_HASH).unwrap()
215    }
216
217    fn mock_contract_package_hash() -> ContractPackageHash {
218        ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap()
219    }
220
221    #[test]
222    fn test_casper_address_account_hash_conversion() {
223        let account_hash = mock_account_hash();
224
225        // It is possible to convert Address back to AccountHash.
226        let casper_address = Address::try_from(account_hash).unwrap();
227        assert_eq!(casper_address.as_account_hash().unwrap(), &account_hash);
228
229        // It is not possible to convert Address to ContractPackageHash.
230        assert!(casper_address.as_contract_package_hash().is_none());
231
232        // And it is not a contract.
233        assert!(!casper_address.is_contract());
234
235        test_casper_address_conversions(casper_address);
236    }
237
238    #[test]
239    fn test_casper_address_contract_package_hash_conversion() {
240        let contract_package_hash = mock_contract_package_hash();
241        let casper_address = Address::try_from(contract_package_hash).unwrap();
242
243        // It is possible to convert Address back to ContractPackageHash.
244        assert_eq!(
245            casper_address.as_contract_package_hash().unwrap(),
246            &contract_package_hash
247        );
248
249        // It is not possible to convert Address to AccountHash.
250        assert!(casper_address.as_account_hash().is_none());
251
252        // And it is a contract.
253        assert!(casper_address.is_contract());
254
255        test_casper_address_conversions(casper_address);
256    }
257
258    fn test_casper_address_conversions(casper_address: Address) {
259        // It can be converted into a Key and back to Address.
260        let key = Key::from(casper_address);
261        let restored = Address::try_from(key);
262        assert_eq!(restored.unwrap(), casper_address);
263
264        // It can be converted into bytes and back.
265        let bytes = casper_address.to_bytes().unwrap();
266        let (restored, rest) = Address::from_bytes(&bytes).unwrap();
267        assert!(rest.is_empty());
268        assert_eq!(restored, casper_address);
269    }
270
271    #[test]
272    fn test_casper_address_from_to_string() {
273        let address = Address::from_str(CONTRACT_HASH).unwrap();
274        assert!(address.is_contract());
275        assert_eq!(&address.to_string(), CONTRACT_HASH);
276
277        let address = Address::from_str(ACCOUNT_HASH).unwrap();
278        assert!(!address.is_contract());
279        assert_eq!(&address.to_string(), ACCOUNT_HASH);
280
281        assert_eq!(
282            Address::from_str(CONTRACT_PACKAGE_HASH).unwrap_err(),
283            OdraError::VmError(VmError::Deserialization)
284        )
285    }
286}
287
288pub trait OdraAddress {
289    /// Returns true if the address is a contract address.
290    fn is_contract(&self) -> bool;
291}