Skip to main content

odra_casper_types/
casper_address.rs

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