odra_core/
address.rs

1//! Better address representation for Casper.
2use crate::prelude::*;
3use crate::{AddressError, VmError};
4use casper_types::{
5    account::AccountHash,
6    bytesrepr::{self, FromBytes, ToBytes},
7    CLType, CLTyped, ContractPackageHash, Key, PublicKey
8};
9use serde::{Deserialize, Serialize};
10
11/// The length of the hash part of an address.
12const ADDRESS_HASH_LENGTH: usize = 64;
13/// An address has format `hash-<64-byte-hash>`.
14const CONTRACT_STR_LENGTH: usize = 69;
15/// An address has format `contract-package-wasm<64-byte-hash>`.
16const LEGACY_CONTRACT_STR_LENGTH: usize = 85;
17/// An address has format `account-hash-<64-byte-hash>`.
18const ACCOUNT_STR_LENGTH: usize = 77;
19
20/// An enum representing an [`AccountHash`] or a [`ContractPackageHash`].
21#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Debug)]
22pub enum Address {
23    /// Represents an account hash.
24    Account(AccountHash),
25    /// Represents a contract package hash.
26    Contract(ContractPackageHash)
27}
28
29/// A trait for types that can be converted into an [`Address`].
30pub trait Addressable {
31    /// Returns a reference to the [`Address`] of the type.
32    fn address(&self) -> &Address;
33}
34
35impl Addressable for Address {
36    fn address(&self) -> &Address {
37        self
38    }
39}
40
41impl Address {
42    /// Creates a new `Address` from a hex-encoded string.
43    pub const fn new(input: &'static str) -> OdraResult<Self> {
44        let src: &[u8] = input.as_bytes();
45        let src_len: usize = src.len();
46
47        if let Ok(dst) = decode_base16(src) {
48            // depending on the length of the input, we can determine the type of address
49            match src_len {
50                LEGACY_CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))),
51                ACCOUNT_STR_LENGTH => Ok(Self::Account(AccountHash::new(dst))),
52                CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))),
53                _ => Err(OdraError::ExecutionError(
54                    ExecutionError::AddressCreationFailed
55                ))
56            }
57        } else {
58            Err(OdraError::ExecutionError(
59                ExecutionError::AddressCreationFailed
60            ))
61        }
62    }
63
64    /// Returns the inner account hash if `self` is the `Account` variant.
65    pub fn as_account_hash(&self) -> Option<&AccountHash> {
66        if let Self::Account(v) = self {
67            Some(v)
68        } else {
69            None
70        }
71    }
72
73    /// Returns the inner contract hash if `self` is the `Contract` variant.
74    pub fn as_contract_package_hash(&self) -> Option<&ContractPackageHash> {
75        if let Self::Contract(v) = self {
76            Some(v)
77        } else {
78            None
79        }
80    }
81
82    /// Returns true if the address is a contract address.
83    pub fn is_contract(&self) -> bool {
84        self.as_contract_package_hash().is_some()
85    }
86}
87
88impl TryFrom<ContractPackageHash> for Address {
89    type Error = AddressError;
90    fn try_from(contract_package_hash: ContractPackageHash) -> Result<Self, Self::Error> {
91        if contract_package_hash.value().iter().all(|&b| b == 0) {
92            return Err(AddressError::ZeroAddress);
93        }
94        Ok(Self::Contract(contract_package_hash))
95    }
96}
97
98impl TryFrom<AccountHash> for Address {
99    type Error = AddressError;
100    fn try_from(account_hash: AccountHash) -> Result<Self, Self::Error> {
101        if account_hash.value().iter().all(|&b| b == 0) {
102            return Err(AddressError::ZeroAddress);
103        }
104        Ok(Self::Account(account_hash))
105    }
106}
107
108impl From<Address> for Key {
109    fn from(address: Address) -> Self {
110        match address {
111            Address::Account(account_hash) => Key::Account(account_hash),
112            Address::Contract(contract_package_hash) => Key::Hash(contract_package_hash.value())
113        }
114    }
115}
116
117impl TryFrom<Key> for Address {
118    type Error = AddressError;
119
120    fn try_from(key: Key) -> Result<Self, Self::Error> {
121        match key {
122            Key::Account(account_hash) => Self::try_from(account_hash),
123            Key::Hash(contract_package_hash) => {
124                Self::try_from(ContractPackageHash::new(contract_package_hash))
125            }
126            _ => Err(AddressError::AddressCreationError)
127        }
128    }
129}
130
131impl From<PublicKey> for Address {
132    fn from(public_key: PublicKey) -> Self {
133        Self::Account(public_key.to_account_hash())
134    }
135}
136
137impl CLTyped for Address {
138    fn cl_type() -> CLType {
139        CLType::Key
140    }
141}
142
143impl ToBytes for Address {
144    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
145        Key::from(*self).to_bytes()
146    }
147
148    fn serialized_length(&self) -> usize {
149        Key::from(*self).serialized_length()
150    }
151}
152
153impl FromBytes for Address {
154    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
155        let (key, remainder) = Key::from_bytes(bytes)?;
156
157        let address = match key {
158            Key::Account(account_hash) => Address::Account(account_hash),
159            Key::Hash(raw_contract_package_hash) => {
160                Address::Contract(ContractPackageHash::new(raw_contract_package_hash))
161            }
162            _ => return Err(bytesrepr::Error::Formatting)
163        };
164
165        Ok((address, remainder))
166    }
167}
168
169impl TryFrom<&[u8; 33]> for Address {
170    type Error = AddressError;
171    fn try_from(value: &[u8; 33]) -> Result<Self, Self::Error> {
172        let address = Address::from_bytes(value)
173            .map(|(address, _)| address)
174            .map_err(|_| AddressError::AddressCreationError)?;
175        if address
176            .to_bytes()
177            .map_err(|_| AddressError::AddressCreationError)?
178            .iter()
179            .all(|&x| x == 0)
180        {
181            Err(AddressError::ZeroAddress)
182        } else {
183            Ok(address)
184        }
185    }
186}
187
188impl FromStr for Address {
189    type Err = OdraError;
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        match Key::from_formatted_str(s) {
193            Err(_) => Err(OdraError::VmError(VmError::Deserialization)),
194            Ok(key) => match key {
195                Key::Account(_) | Key::Hash(_) => match key.try_into() {
196                    Ok(address) => Ok(address),
197                    Err(_) => Err(OdraError::VmError(VmError::Deserialization))
198                },
199                _ => Err(OdraError::VmError(VmError::Deserialization))
200            }
201        }
202    }
203}
204
205#[allow(clippy::to_string_trait_impl)]
206impl ToString for Address {
207    fn to_string(&self) -> String {
208        Key::from(*self).to_formatted_string()
209    }
210}
211
212impl Serialize for Address {
213    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
214        let s = self.to_string();
215        serializer.serialize_str(&s)
216    }
217}
218
219impl<'de> Deserialize<'de> for Address {
220    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
221        let s = String::deserialize(deserializer)?;
222        Address::from_str(&s).map_err(|_| serde::de::Error::custom("Address deserialization error"))
223    }
224}
225
226const fn decode_base16(input: &[u8]) -> Result<[u8; 32], &'static str> {
227    // fail fast if the input is too short
228    let input_len = input.len();
229    if input_len < ADDRESS_HASH_LENGTH {
230        return Err("Input too short");
231    }
232    // An address is always 32 bytes long.
233    let mut output = [0u8; 32];
234    let mut i = 0;
235    let mut j = 0;
236    // In a const fn, we can't use a for loop.
237    // We consider only the last 64 characters of the input.
238    while i < 64 {
239        let high_value = match hex_char_to_value(input[input_len - ADDRESS_HASH_LENGTH + i]) {
240            Ok(v) => v,
241            Err(e) => return Err(e)
242        };
243
244        let low_value = match hex_char_to_value(input[input_len - ADDRESS_HASH_LENGTH + i + 1]) {
245            Ok(v) => v,
246            Err(e) => return Err(e)
247        };
248
249        output[j] = (high_value << 4) | low_value;
250        i += 2;
251        j += 1;
252    }
253
254    Ok(output)
255}
256
257const fn hex_char_to_value(c: u8) -> Result<u8, &'static str> {
258    match c {
259        b'0'..=b'9' => Ok(c - b'0'),
260        b'a'..=b'f' => Ok(c - b'a' + 10),
261        b'A'..=b'F' => Ok(c - b'A' + 10),
262        _ => Err("Invalid character in input")
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use casper_types::EraId;
269
270    use super::*;
271
272    // TODO: casper-types > 1.5.0 will have prefix fixed.
273    const CONTRACT_PACKAGE_HASH: &str =
274        "contract-package-wasm7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
275    const ACCOUNT_HASH: &str =
276        "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95";
277    const CONTRACT_HASH: &str =
278        "hash-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
279
280    fn mock_account_hash() -> AccountHash {
281        AccountHash::from_formatted_str(ACCOUNT_HASH).unwrap()
282    }
283
284    fn mock_contract_package_hash() -> ContractPackageHash {
285        ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap()
286    }
287
288    #[test]
289    fn test_casper_address_new() {
290        let address = Address::new(CONTRACT_PACKAGE_HASH).unwrap();
291        assert!(address.is_contract());
292        assert_eq!(
293            address.as_contract_package_hash().unwrap(),
294            &mock_contract_package_hash()
295        );
296
297        let address = Address::new(ACCOUNT_HASH).unwrap();
298        assert!(!address.is_contract());
299        assert_eq!(address.as_account_hash().unwrap(), &mock_account_hash());
300
301        let address = Address::new(CONTRACT_HASH).unwrap();
302        assert!(address.is_contract());
303    }
304
305    #[test]
306    fn contract_package_hash_from_str() {
307        let valid_prefix =
308            "account-hash-0000000000000000000000000000000000000000000000000000000000000000";
309        assert!(Address::new(valid_prefix).is_ok());
310
311        let invalid_prefix =
312            "account-hash0000000000000000000000000000000000000000000000000000000000000000";
313        assert!(Address::new(invalid_prefix).is_err());
314
315        let short_addr =
316            "account-hash-00000000000000000000000000000000000000000000000000000000000000";
317        assert!(Address::new(short_addr).is_err());
318
319        let long_addr =
320            "account-hash-000000000000000000000000000000000000000000000000000000000000000000";
321        assert!(Address::new(long_addr).is_err());
322
323        let invalid_hex =
324            "account-hash-000000000000000000000000000000000000000000000000000000000000000g";
325        assert!(Address::new(invalid_hex).is_err());
326    }
327
328    #[test]
329    fn test_casper_address_account_hash_conversion() {
330        let account_hash = mock_account_hash();
331
332        // It is possible to convert Address back to AccountHash.
333        let casper_address = Address::try_from(account_hash).unwrap();
334        assert_eq!(casper_address.as_account_hash().unwrap(), &account_hash);
335
336        // It is not possible to convert Address to ContractPackageHash.
337        assert!(casper_address.as_contract_package_hash().is_none());
338
339        // And it is not a contract.
340        assert!(!casper_address.is_contract());
341
342        test_casper_address_conversions(casper_address);
343    }
344
345    #[test]
346    fn test_casper_address_contract_package_hash_conversion() {
347        let contract_package_hash = mock_contract_package_hash();
348        let casper_address = Address::try_from(contract_package_hash).unwrap();
349
350        // It is possible to convert Address back to ContractPackageHash.
351        assert_eq!(
352            casper_address.as_contract_package_hash().unwrap(),
353            &contract_package_hash
354        );
355
356        // It is not possible to convert Address to AccountHash.
357        assert!(casper_address.as_account_hash().is_none());
358
359        // And it is a contract.
360        assert!(casper_address.is_contract());
361
362        test_casper_address_conversions(casper_address);
363    }
364
365    fn test_casper_address_conversions(casper_address: Address) {
366        // It can be converted into a Key and back to Address.
367        let key = Key::from(casper_address);
368        let restored = Address::try_from(key);
369        assert_eq!(restored.unwrap(), casper_address);
370
371        // It can be converted into bytes and back.
372        let bytes = casper_address.to_bytes().unwrap();
373        let (restored, rest) = Address::from_bytes(&bytes).unwrap();
374        assert!(rest.is_empty());
375        assert_eq!(restored, casper_address);
376    }
377
378    #[test]
379    fn test_casper_address_from_to_string() {
380        let address = Address::from_str(CONTRACT_HASH).unwrap();
381        assert!(address.is_contract());
382        assert_eq!(&address.to_string(), CONTRACT_HASH);
383
384        let address = Address::from_str(ACCOUNT_HASH).unwrap();
385        assert!(!address.is_contract());
386        assert_eq!(&address.to_string(), ACCOUNT_HASH);
387
388        assert_eq!(
389            Address::from_str(CONTRACT_PACKAGE_HASH).unwrap_err(),
390            OdraError::VmError(VmError::Deserialization)
391        )
392    }
393
394    #[test]
395    fn test_from_key_fails() {
396        let key = Key::EraInfo(EraId::from(42));
397        assert_eq!(
398            Address::try_from(key).unwrap_err(),
399            AddressError::AddressCreationError
400        );
401    }
402
403    #[test]
404    fn test_address_serde_roundtrip() {
405        // Test Account serialization.
406        let address_json = format!("\"{}\"", ACCOUNT_HASH);
407        let address = Address::from_str(ACCOUNT_HASH).unwrap();
408        let serialized = serde_json::to_string(&address).unwrap();
409        assert_eq!(serialized, address_json);
410
411        // Test Account deserialization.
412        let deserialized: Address = serde_json::from_str(&address_json).unwrap();
413        assert_eq!(deserialized, address);
414
415        // Test Account serialization roundtrip.
416        let serialized = serde_json::to_string(&address).unwrap();
417        let deserialized: Address = serde_json::from_str(&serialized).unwrap();
418        assert_eq!(deserialized, address);
419
420        // Test Contract serialization.
421        let address_json = format!("\"{}\"", CONTRACT_HASH);
422        let address = Address::from_str(CONTRACT_HASH).unwrap();
423        let serialized = serde_json::to_string(&address).unwrap();
424        assert_eq!(serialized, address_json);
425
426        // Test Contract deserialization.
427        let deserialized: Address = serde_json::from_str(&address_json).unwrap();
428        assert_eq!(deserialized, address);
429
430        // Test Contract serialization roundtrip.
431        let serialized = serde_json::to_string(&address).unwrap();
432        let deserialized: Address = serde_json::from_str(&serialized).unwrap();
433        assert_eq!(deserialized, address);
434    }
435}