use sp_std::{fmt::Debug, marker::PhantomData, ops::Deref, vec::Vec};
use frame_support::{ensure, traits::Get, BoundedVec};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{RuntimeDebug, SaturatedConversion};
use crate::{Config, Error};
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T, MinLength, MaxLength))]
#[codec(mel_bound())]
pub struct AsciiWeb3Name<T: Config>(pub BoundedVec<u8, T::MaxNameLength>, PhantomData<(T, T::MinNameLength)>);
impl<T: Config> Deref for AsciiWeb3Name<T> {
type Target = BoundedVec<u8, T::MaxNameLength>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Config> From<AsciiWeb3Name<T>> for Vec<u8> {
fn from(name: AsciiWeb3Name<T>) -> Self {
name.0.into_inner()
}
}
impl<T: Config> AsRef<[u8]> for AsciiWeb3Name<T> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<T: Config> TryFrom<Vec<u8>> for AsciiWeb3Name<T> {
type Error = Error<T>;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
ensure!(
value.len() >= T::MinNameLength::get().saturated_into(),
Self::Error::TooShort
);
let bounded_vec: BoundedVec<u8, T::MaxNameLength> =
BoundedVec::try_from(value).map_err(|_| Self::Error::TooLong)?;
ensure!(is_valid_web3_name(&bounded_vec), Self::Error::InvalidCharacter);
Ok(Self(bounded_vec, PhantomData))
}
}
fn is_valid_web3_name(input: &[u8]) -> bool {
input
.iter()
.all(|c| matches!(c, b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_'))
}
impl<T: Config> PartialEq for AsciiWeb3Name<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: Config> Eq for AsciiWeb3Name<T> {
fn assert_receiver_is_total_eq(&self) {
self.0.assert_receiver_is_total_eq()
}
}
impl<T: Config> PartialOrd for AsciiWeb3Name<T> {
fn partial_cmp(&self, other: &Self) -> Option<sp_std::cmp::Ordering> {
Some(self.0.as_slice().cmp(other.0.as_slice()))
}
}
impl<T: Config> Ord for AsciiWeb3Name<T> {
fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T: Config> Clone for AsciiWeb3Name<T> {
fn clone(&self) -> Self {
Self(self.0.clone(), self.1)
}
}
impl<T: Config> Default for AsciiWeb3Name<T> {
fn default() -> Self {
Self(BoundedVec::default(), PhantomData)
}
}
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
pub struct Web3NameOwnership<Owner, Deposit: MaxEncodedLen, BlockNumber> {
pub owner: Owner,
pub claimed_at: BlockNumber,
pub deposit: Deposit,
}
#[cfg(test)]
mod tests {
use sp_runtime::SaturatedConversion;
use crate::{mock::Test, web3_name::AsciiWeb3Name, Config};
const MIN_LENGTH: u32 = <Test as Config>::MinNameLength::get();
const MAX_LENGTH: u32 = <Test as Config>::MaxNameLength::get();
#[test]
fn valid_web3_name_inputs() {
let valid_inputs = vec![
vec![b'a'; MIN_LENGTH.saturated_into()],
vec![b'a'; MAX_LENGTH.saturated_into()],
b"qwertyuiopasdfghjklzxcvbnm".to_vec(),
b"0123456789".to_vec(),
b"---".to_vec(),
b"___".to_vec(),
];
let invalid_inputs = vec![
b"".to_vec(),
vec![b'a'; MIN_LENGTH.saturated_into::<usize>() - 1usize],
vec![b'a'; MAX_LENGTH.saturated_into::<usize>() + 1usize],
b"almostavalidweb3_name!".to_vec(),
String::from("almostavalidweb3_name😂").as_bytes().to_owned(),
];
for valid in valid_inputs {
assert!(AsciiWeb3Name::<Test>::try_from(valid).is_ok());
}
for invalid in invalid_inputs {
assert!(AsciiWeb3Name::<Test>::try_from(invalid).is_err());
}
}
}