use crate::error::AddressError;
use crate::utils::contains_non_hex_chars;
use crate::utils::hex_str_to_bytes;
use crate::utils::ArrayString;
use bech32::{self, FromBase32};
use bech32::{ToBase32, Variant};
use core::fmt::Display;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use sha2::{Digest, Sha256};
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::str::FromStr;
#[cfg(feature = "ethermint")]
use clarity::address::Address as EthAddress;
pub const DEFAULT_PREFIX: &str = "cosmos";
#[derive(PartialEq, Eq, Copy, Clone, Hash)]
pub enum Address {
Base(BaseAddress),
Derived(DerivedAddress),
}
impl Serialize for Address {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Address {
fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let decoded = Address::from_bech32(s);
match decoded {
Ok(d) => Ok(d),
Err(e) => Err(serde::de::Error::custom(e.to_string())),
}
}
}
#[derive(PartialEq, Eq, Copy, Clone, Hash, Deserialize, Serialize)]
pub struct BaseAddress {
bytes: [u8; 20],
prefix: ArrayString,
}
#[derive(PartialEq, Eq, Copy, Clone, Hash, Deserialize, Serialize)]
pub struct DerivedAddress {
bytes: [u8; 32],
prefix: ArrayString,
}
impl Address {
pub fn from_slice<T: Into<String>>(bytes: &[u8], prefix: T) -> Result<Self, AddressError> {
match bytes.len() {
20 => {
let mut result = [0u8; 20];
result.copy_from_slice(bytes);
Ok(Address::Base(BaseAddress::from_bytes(result, prefix)?))
}
32 => {
let mut result = [0u8; 32];
result.copy_from_slice(bytes);
Ok(Address::Derived(DerivedAddress::from_bytes(
result, prefix,
)?))
}
_ => Err(AddressError::BytesDecodeErrorWrongLength),
}
}
pub fn from_bech32(s: String) -> Result<Self, AddressError> {
let (hrp, data, _) = match bech32::decode(&s) {
Ok(val) => val,
Err(_) => {
return Err(AddressError::Bech32InvalidEncoding);
}
};
let vec: Vec<u8> = match FromBase32::from_base32(&data) {
Ok(val) => val,
Err(_e) => return Err(AddressError::Bech32InvalidBase32),
};
match vec.len() {
20 => {
let mut addr = [0u8; 20];
addr.copy_from_slice(&vec);
Ok(Address::Base(BaseAddress::from_bytes(addr, &hrp)?))
}
32 => {
let mut addr = [0u8; 32];
addr.copy_from_slice(&vec);
Ok(Address::Derived(DerivedAddress::from_bytes(addr, &hrp)?))
}
_ => Err(AddressError::Bech32WrongLength),
}
}
pub fn to_bech32<T: Into<String>>(&self, hrp: T) -> Result<String, AddressError> {
let bech32 = bech32::encode(&hrp.into(), self.get_bytes().to_base32(), Variant::Bech32)?;
Ok(bech32)
}
pub fn change_prefix<T: Into<String>>(&mut self, prefix: T) -> Result<(), AddressError> {
match self {
Address::Base(base_address) => {
base_address.prefix = ArrayString::new(&prefix.into())?;
}
Address::Derived(derived_address) => {
derived_address.prefix = ArrayString::new(&prefix.into())?;
}
}
Ok(())
}
pub fn re_prefix<T: Into<String>>(&self, prefix: T) -> Result<Address, AddressError> {
match self {
Address::Base(base_address) => Ok(Address::Base(BaseAddress {
bytes: base_address.bytes,
prefix: ArrayString::new(&prefix.into())?,
})),
Address::Derived(derived_address) => Ok(Address::Derived(DerivedAddress {
bytes: derived_address.bytes,
prefix: ArrayString::new(&prefix.into())?,
})),
}
}
pub fn get_bytes(&self) -> &[u8] {
match self {
Address::Base(base_address) => &base_address.bytes,
Address::Derived(derived_address) => &derived_address.bytes,
}
}
pub fn to_vec(&self) -> Vec<u8> {
self.get_bytes().to_vec()
}
pub fn get_prefix(&self) -> String {
match self {
Address::Base(base_address) => base_address.prefix,
Address::Derived(derived_address) => derived_address.prefix,
}
.to_string()
}
}
#[cfg(feature = "ethermint")]
impl TryInto<EthAddress> for &Address {
type Error = clarity::error::Error;
fn try_into(self) -> Result<EthAddress, Self::Error> {
EthAddress::from_slice(self.get_bytes())
}
}
#[cfg(feature = "ethermint")]
impl TryInto<EthAddress> for Address {
type Error = clarity::error::Error;
fn try_into(self) -> Result<EthAddress, Self::Error> {
EthAddress::from_slice(self.get_bytes())
}
}
#[cfg(feature = "ethermint")]
impl From<EthAddress> for Address {
fn from(value: EthAddress) -> Self {
Address::from_slice(value.as_bytes(), DEFAULT_PREFIX).unwrap()
}
}
#[cfg(feature = "ethermint")]
impl Address {
pub fn from_eth_address_with_prefix<T: Into<String>>(
address: EthAddress,
prefix: T,
) -> Result<Self, AddressError> {
Address::from_slice(address.as_bytes(), prefix)
}
}
impl FromStr for Address {
type Err = AddressError;
fn from_str(s: &str) -> Result<Self, AddressError> {
if contains_non_hex_chars(s) {
Address::from_bech32(s.to_string())
} else {
match hex_str_to_bytes(s) {
Ok(bytes) => Address::from_slice(&bytes, DEFAULT_PREFIX),
Err(e) => Err(AddressError::HexDecodeError(e)),
}
}
}
}
impl Display for Address {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let display = self.to_bech32(self.get_prefix()).unwrap();
write!(f, "{display}").expect("Unable to write");
Ok(())
}
}
impl Debug for Address {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_bech32(self.get_prefix()).unwrap())
}
}
impl BaseAddress {
pub fn from_bytes<T: Into<String>>(bytes: [u8; 20], prefix: T) -> Result<Self, AddressError> {
Ok(Self {
bytes,
prefix: ArrayString::new(&prefix.into())?,
})
}
}
impl DerivedAddress {
pub fn from_bytes<T: Into<String>>(bytes: [u8; 32], prefix: T) -> Result<Self, AddressError> {
Ok(Self {
bytes,
prefix: ArrayString::new(&prefix.into())?,
})
}
}
pub fn get_module_account_address(
module_name: &str,
prefix: Option<&str>,
) -> Result<Address, AddressError> {
let prefix = prefix.unwrap_or(DEFAULT_PREFIX);
let mut hasher = Sha256::new();
hasher.update(module_name.as_bytes());
let result = hasher.finalize();
Address::from_slice(&result[0..20], prefix)
}
#[cfg(feature = "ethermint")]
pub fn cosmos_address_to_eth_address(
address: Address,
) -> Result<EthAddress, clarity::error::Error> {
EthAddress::from_slice(address.get_bytes())
}
#[cfg(feature = "ethermint")]
pub fn eth_address_to_cosmos_address(
address: EthAddress,
prefix: Option<&str>,
) -> Result<Address, AddressError> {
let prefix = prefix.unwrap_or(DEFAULT_PREFIX);
Address::from_slice(address.as_bytes(), prefix)
}
#[test]
fn test_bech32() {
let address = Address::from_slice(&[0; 20], "cosmos").unwrap();
assert_eq!(
address.to_bech32("cosmos").unwrap(),
"cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnrql8a"
);
let decoded = Address::from_bech32("cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnrql8a".to_string())
.expect("Unable to decode");
assert_eq!(address, decoded);
Address::from_bech32("cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp".to_string())
.expect("Failed to decode");
}
#[test]
fn test_default_prefix() {
Address::from_slice(&[0; 20], DEFAULT_PREFIX).unwrap();
}
#[test]
fn test_parse() {
let address = Address::from_slice(&[0; 20], "cosmos").unwrap();
let decoded = "cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqnrql8a"
.parse()
.expect("Unable to decode");
assert_eq!(address, decoded);
let _test: Address = "cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp"
.parse()
.unwrap();
}
#[cfg(feature = "ethermint")]
#[test]
fn test_address_conversion() {
let test: Address = "cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp"
.parse()
.unwrap();
let eth_address = cosmos_address_to_eth_address(test).unwrap();
let _cosmos_address = eth_address_to_cosmos_address(eth_address, None).unwrap();
}
#[cfg(feature = "ethermint")]
#[test]
fn test_trait_conversions() {
use std::convert::TryInto;
let test: Address = "cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp"
.parse()
.unwrap();
let _eth_address: EthAddress = test.try_into().unwrap();
let test: Address = "cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp"
.parse()
.unwrap();
let eth_address: EthAddress = (&test).try_into().unwrap();
let cosmos_address: Address = eth_address.into();
assert_eq!(test.get_bytes(), cosmos_address.get_bytes());
}
#[test]
fn test_re_prefix() {
let test: Address = "cosmos1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96ckjvrp"
.parse()
.unwrap();
let osmosis_address = test.re_prefix("osmo").unwrap();
assert_eq!(
osmosis_address.to_string(),
"osmo1vlms2r8f6x7yxjh3ynyzc7ckarqd8a96sdpu4n"
);
assert_eq!(test.get_bytes(), osmosis_address.get_bytes());
let cosmos_address = test.re_prefix("cosmos").unwrap();
assert_eq!(test.to_string(), cosmos_address.to_string());
}