#![allow(clippy::items_after_test_module)]
use primitive_types::H160;
use rand::Rng;
use serde::{Deserialize, Serialize};
use crate::{
config::DEFAULT_ADDRESS_VERSION,
crypto::HashableForVec,
neo_crypto::utils::FromHexString,
neo_error::NeoError,
neo_types::{ScriptHash, ScriptHashExtension, StringExt, TypeError},
};
pub type Address = String;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum NameOrAddress {
Name(String),
Address(Address),
}
pub trait AddressExtension {
fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError>;
fn random() -> Self;
}
impl AddressExtension for String {
fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
<H160 as ScriptHashExtension>::from_address(self.as_str())
}
fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
self.from_hex_string()
.map(|data| ScriptHash::from_script(data.as_slice()))
.map_err(|_| TypeError::InvalidScript("Invalid hex string".to_string()))
}
fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
if self.is_valid_hex() {
ScriptHash::from_hex(self.as_str())
.map_err(|_| TypeError::InvalidFormat("Invalid hex format".to_string()))
} else {
Err(TypeError::InvalidFormat("Invalid hex format".to_string()))
}
}
fn random() -> Self {
let mut rng = rand::rng();
let mut bytes = [0u8; 20];
rng.fill(&mut bytes);
let script_hash = bytes.sha256_ripemd160();
let mut data = vec![DEFAULT_ADDRESS_VERSION];
data.extend_from_slice(&script_hash);
let sha = &data.hash256().hash256();
data.extend_from_slice(&sha[..4]);
bs58::encode(data).into_string()
}
}
impl AddressExtension for &str {
fn address_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
self.to_string().address_to_script_hash()
}
fn script_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
self.to_string().script_to_script_hash()
}
fn hex_to_script_hash(&self) -> Result<ScriptHash, TypeError> {
self.to_string().hex_to_script_hash()
}
fn random() -> Self {
""
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_address_to_script_hash() {
let n3_address = "NTGYC16CN5QheM4ZwfhUp9JKq8bMjWtcAp";
let expected_script_hash_hex = "87c06be672d5600dce4a260e7b2d497112c0ac50";
let result = n3_address
.address_to_script_hash()
.expect("Should be able to convert valid N3 address to script hash");
assert_eq!(hex::encode(result), expected_script_hash_hex);
let n3_address = "Invalid_Address";
let result = n3_address.to_string().address_to_script_hash();
assert!(result.is_err());
}
#[test]
fn test_from_script_hash() {
let script_hash = <ScriptHash as ScriptHashExtension>::from_slice(
&hex::decode("87c06be672d5600dce4a260e7b2d497112c0ac50")
.expect("script hash hex should decode"),
)
.expect("script hash bytes should be valid");
let address =
from_script_hash(&script_hash).expect("script hash should convert to address");
assert_eq!(address, "NTGYC16CN5QheM4ZwfhUp9JKq8bMjWtcAp");
}
}
pub fn from_script_hash(script_hash: &H160) -> Result<String, NeoError> {
Ok(script_hash.to_address())
}