diff --git a/core/src/address.rs b/core/src/address.rs index da867d03..86d91ab7 100644 --- a/core/src/address.rs +++ b/core/src/address.rs @@ -1,6 +1,6 @@ //! Better address representation for Casper. -use crate::prelude::*; use crate::AddressError::ZeroAddress; +use crate::{prelude::*, utils, ExecutionError, OdraResult}; use crate::{AddressError, OdraError, VmError}; use casper_types::{ account::AccountHash, @@ -9,6 +9,14 @@ use casper_types::{ }; use serde::{Deserialize, Serialize}; +const ADDRESS_HASH_LENGTH: usize = 64; +/// An address has format `hash-<64-byte-hash>`. +const CONTRACT_STR_LENGTH: usize = 69; +/// An address has format `contract-package-wasm<64-byte-hash>`. +const LEGACY_CONTRACT_STR_LENGTH: usize = 85; +/// An address has format `account-hash-<64-byte-hash>`. +const ACCOUNT_STR_LENGTH: usize = 77; + /// An enum representing an [`AccountHash`] or a [`ContractPackageHash`]. #[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum Address { @@ -19,6 +27,35 @@ pub enum Address { } impl Address { + /// Creates a new `Address` from a hex-encoded string. + pub const fn new(input: &'static str) -> OdraResult { + let src: &[u8] = input.as_bytes(); + let src_len: usize = src.len(); + + // fail fast if the input is too short + if src_len < ADDRESS_HASH_LENGTH { + return Err(OdraError::ExecutionError( + ExecutionError::AddressCreationFailed + )); + } + // skip the prefix, process the last 64 bytes + if let Ok(dst) = utils::decode_hex_32(src, src_len - ADDRESS_HASH_LENGTH) { + // depending on the length of the input, we can determine the type of address + match src_len { + LEGACY_CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))), + ACCOUNT_STR_LENGTH => Ok(Self::Account(AccountHash::new(dst))), + CONTRACT_STR_LENGTH => Ok(Self::Contract(ContractPackageHash::new(dst))), + _ => Err(OdraError::ExecutionError( + ExecutionError::AddressCreationFailed + )) + } + } else { + Err(OdraError::ExecutionError( + ExecutionError::AddressCreationFailed + )) + } + } + /// Returns the inner account hash if `self` is the `Account` variant. pub fn as_account_hash(&self) -> Option<&AccountHash> { if let Self::Account(v) = self { @@ -202,6 +239,20 @@ mod tests { ContractPackageHash::from_formatted_str(CONTRACT_PACKAGE_HASH).unwrap() } + #[test] + fn test_casper_address_new() { + let address = Address::new(CONTRACT_PACKAGE_HASH).unwrap(); + assert!(address.is_contract()); + assert_eq!( + address.as_contract_package_hash().unwrap(), + &mock_contract_package_hash() + ); + + let address = Address::new(ACCOUNT_HASH).unwrap(); + assert!(!address.is_contract()); + assert_eq!(address.as_account_hash().unwrap(), &mock_account_hash()); + } + #[test] fn test_casper_address_account_hash_conversion() { let account_hash = mock_account_hash(); diff --git a/core/src/utils.rs b/core/src/utils.rs index 5e741262..11440401 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -106,6 +106,39 @@ pub fn hex_to_slice(src: &[u8], dst: &mut [u8]) { } } +pub(crate) const fn decode_hex_32(input: &[u8], start: usize) -> Result<[u8; 32], &'static str> { + let mut output = [0u8; 32]; + let mut i = 0; + let mut j = 0; + + while i < 64 { + let high_value = match hex_char_to_value(input[start + i]) { + Ok(v) => v, + Err(e) => return Err(e) + }; + + let low_value = match hex_char_to_value(input[start + i + 1]) { + Ok(v) => v, + Err(e) => return Err(e) + }; + + output[j] = (high_value << 4) | low_value; + i += 2; + j += 1; + } + + Ok(output) +} + +const fn hex_char_to_value(c: u8) -> Result { + match c { + b'0'..=b'9' => Ok(c - b'0'), + b'a'..=b'f' => Ok(c - b'a' + 10), + b'A'..=b'F' => Ok(c - b'A' + 10), + _ => Err("Invalid character in input") + } +} + #[cfg(test)] mod tests { use super::event_absolute_position;