odra_core/
address.rs

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