pub use v1::*;
pub mod v1 {
use crate::errors::asset::{Error, IdentifierError, NamespaceError, ReferenceError};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use core::str;
use frame_support::{sp_runtime::RuntimeDebug, traits::ConstU32, BoundedVec};
use sp_core::U256;
use sp_std::{fmt::Display, vec::Vec};
pub const MINIMUM_ASSET_ID_LENGTH: usize = MINIMUM_ASSET_NAMESPACE_LENGTH + 1 + MINIMUM_ASSET_REFERENCE_LENGTH;
pub const MAXIMUM_ASSET_ID_LENGTH: usize =
MAXIMUM_NAMESPACE_LENGTH + 1 + MAXIMUM_ASSET_REFERENCE_LENGTH + 1 + MAXIMUM_ASSET_IDENTIFIER_LENGTH;
pub const MINIMUM_ASSET_NAMESPACE_LENGTH: usize = 3;
pub const MAXIMUM_NAMESPACE_LENGTH: usize = 8;
const MAXIMUM_ASSET_NAMESPACE_LENGTH_U32: u32 = MAXIMUM_NAMESPACE_LENGTH as u32;
pub const MINIMUM_ASSET_REFERENCE_LENGTH: usize = 1;
pub const MAXIMUM_ASSET_REFERENCE_LENGTH: usize = 128;
const MAXIMUM_ASSET_REFERENCE_LENGTH_U32: u32 = MAXIMUM_ASSET_REFERENCE_LENGTH as u32;
pub const MINIMUM_ASSET_IDENTIFIER_LENGTH: usize = 1;
pub const MAXIMUM_ASSET_IDENTIFIER_LENGTH: usize = 78;
const MAXIMUM_ASSET_IDENTIFIER_LENGTH_U32: u32 = MAXIMUM_ASSET_IDENTIFIER_LENGTH as u32;
const ASSET_NAMESPACE_REFERENCE_SEPARATOR: u8 = b':';
const ASSET_REFERENCE_IDENTIFIER_SEPARATOR: u8 = b':';
pub const SLIP44_NAMESPACE: &[u8] = b"slip44";
pub const ERC20_NAMESPACE: &[u8] = b"erc20";
pub const ERC721_NAMESPACE: &[u8] = b"erc721";
pub const ERC1155_NAMESPACE: &[u8] = b"erc1155";
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum AssetId {
Slip44(Slip44Reference),
Erc20(EvmSmartContractFungibleReference),
Erc721(EvmSmartContractNonFungibleReference),
Erc1155(EvmSmartContractNonFungibleReference),
Generic(GenericAssetId),
}
impl From<Slip44Reference> for AssetId {
fn from(reference: Slip44Reference) -> Self {
Self::Slip44(reference)
}
}
impl From<EvmSmartContractFungibleReference> for AssetId {
fn from(reference: EvmSmartContractFungibleReference) -> Self {
Self::Erc20(reference)
}
}
impl AssetId {
pub fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
let input_length = input.len();
if !(MINIMUM_ASSET_ID_LENGTH..=MAXIMUM_ASSET_ID_LENGTH).contains(&input_length) {
log::trace!(
"Length of provided input {} is not included in the inclusive range [{},{}]",
input_length,
MINIMUM_ASSET_ID_LENGTH,
MAXIMUM_ASSET_ID_LENGTH
);
return Err(Error::InvalidFormat);
}
let AssetComponents {
namespace,
reference,
identifier,
} = split_components(input);
match (namespace, reference, identifier) {
(Some(SLIP44_NAMESPACE), _, Some(_)) => {
log::trace!("Slip44 namespace does not accept an asset identifier.");
Err(Error::InvalidFormat)
}
(Some(SLIP44_NAMESPACE), Some(slip44_reference), None) => {
Slip44Reference::from_utf8_encoded(slip44_reference).map(Self::Slip44)
}
(Some(ERC20_NAMESPACE), _, Some(_)) => {
log::trace!("Erc20 namespace does not accept an asset identifier.");
Err(Error::InvalidFormat)
}
(Some(ERC20_NAMESPACE), Some(erc20_reference), None) => {
EvmSmartContractFungibleReference::from_utf8_encoded(erc20_reference).map(Self::Erc20)
}
(Some(ERC721_NAMESPACE), Some(erc721_reference), identifier) => {
let reference = EvmSmartContractFungibleReference::from_utf8_encoded(erc721_reference)?;
let identifier = identifier.map_or(Ok(None), |id| {
EvmSmartContractNonFungibleIdentifier::from_utf8_encoded(id).map(Some)
})?;
Ok(Self::Erc721(EvmSmartContractNonFungibleReference(
reference, identifier,
)))
}
(Some(ERC1155_NAMESPACE), Some(erc1155_reference), identifier) => {
let reference = EvmSmartContractFungibleReference::from_utf8_encoded(erc1155_reference)?;
let identifier = identifier.map_or(Ok(None), |id| {
EvmSmartContractNonFungibleIdentifier::from_utf8_encoded(id).map(Some)
})?;
Ok(Self::Erc1155(EvmSmartContractNonFungibleReference(
reference, identifier,
)))
}
_ => GenericAssetId::from_utf8_encoded(input).map(Self::Generic),
}
}
}
impl Display for AssetId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Slip44(reference) => {
write!(
f,
"{}",
str::from_utf8(SLIP44_NAMESPACE)
.expect("Conversion of Slip44 namespace to string should never fail.")
)?;
write!(f, "{}", char::from(ASSET_NAMESPACE_REFERENCE_SEPARATOR))?;
reference.fmt(f)?;
}
Self::Erc20(reference) => {
write!(
f,
"{}",
str::from_utf8(ERC20_NAMESPACE)
.expect("Conversion of Erc20 namespace to string should never fail.")
)?;
write!(f, "{}", char::from(ASSET_NAMESPACE_REFERENCE_SEPARATOR))?;
reference.fmt(f)?;
}
Self::Erc721(EvmSmartContractNonFungibleReference(reference, identifier)) => {
write!(
f,
"{}",
str::from_utf8(ERC721_NAMESPACE)
.expect("Conversion of Erc721 namespace to string should never fail.")
)?;
write!(f, "{}", char::from(ASSET_NAMESPACE_REFERENCE_SEPARATOR))?;
reference.fmt(f)?;
if let Some(id) = identifier {
write!(f, "{}", char::from(ASSET_REFERENCE_IDENTIFIER_SEPARATOR))?;
id.fmt(f)?;
}
}
Self::Erc1155(EvmSmartContractNonFungibleReference(reference, identifier)) => {
write!(
f,
"{}",
str::from_utf8(ERC1155_NAMESPACE)
.expect("Conversion of Erc1155 namespace to string should never fail.")
)?;
write!(f, "{}", char::from(ASSET_NAMESPACE_REFERENCE_SEPARATOR))?;
reference.fmt(f)?;
if let Some(id) = identifier {
write!(f, "{}", char::from(ASSET_REFERENCE_IDENTIFIER_SEPARATOR))?;
id.fmt(f)?;
}
}
Self::Generic(GenericAssetId {
namespace,
reference,
id,
}) => {
namespace.fmt(f)?;
write!(f, "{}", char::from(ASSET_NAMESPACE_REFERENCE_SEPARATOR))?;
reference.fmt(f)?;
if let Some(identifier) = id {
write!(f, "{}", char::from(ASSET_REFERENCE_IDENTIFIER_SEPARATOR))?;
identifier.fmt(f)?;
}
}
}
Ok(())
}
}
const fn check_namespace_length_bounds(namespace: &[u8]) -> Result<(), NamespaceError> {
let namespace_length = namespace.len();
if namespace_length < MINIMUM_ASSET_NAMESPACE_LENGTH {
Err(NamespaceError::TooShort)
} else if namespace_length > MAXIMUM_NAMESPACE_LENGTH {
Err(NamespaceError::TooLong)
} else {
Ok(())
}
}
const fn check_reference_length_bounds(reference: &[u8]) -> Result<(), ReferenceError> {
let reference_length = reference.len();
if reference_length < MINIMUM_ASSET_REFERENCE_LENGTH {
Err(ReferenceError::TooShort)
} else if reference_length > MAXIMUM_ASSET_REFERENCE_LENGTH {
Err(ReferenceError::TooLong)
} else {
Ok(())
}
}
const fn check_identifier_length_bounds(identifier: &[u8]) -> Result<(), IdentifierError> {
let identifier_length = identifier.len();
if identifier_length < MINIMUM_ASSET_IDENTIFIER_LENGTH {
Err(IdentifierError::TooShort)
} else if identifier_length > MAXIMUM_ASSET_IDENTIFIER_LENGTH {
Err(IdentifierError::TooLong)
} else {
Ok(())
}
}
fn split_components(input: &[u8]) -> AssetComponents {
let mut split = input.splitn(2, |c| *c == ASSET_NAMESPACE_REFERENCE_SEPARATOR);
let (namespace, reference) = (split.next(), split.next());
let (reference, identifier) = if let Some(r) = reference {
let mut split = r.splitn(2, |c| *c == ASSET_REFERENCE_IDENTIFIER_SEPARATOR);
(split.next(), split.next())
} else {
(reference, None)
};
AssetComponents {
namespace,
reference,
identifier,
}
}
struct AssetComponents<'a> {
namespace: Option<&'a [u8]>,
reference: Option<&'a [u8]>,
identifier: Option<&'a [u8]>,
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct Slip44Reference(pub(crate) U256);
impl Slip44Reference {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
check_reference_length_bounds(input)?;
let decoded = str::from_utf8(input).map_err(|_| {
log::trace!("Provided input is not a valid UTF8 string as expected by a Slip44 reference.");
ReferenceError::InvalidFormat
})?;
let parsed = U256::from_dec_str(decoded).map_err(|_| {
log::trace!("Provided input is not a valid u256 value as expected by a Slip44 reference.");
ReferenceError::InvalidFormat
})?;
Ok(Self(parsed))
}
}
impl TryFrom<U256> for Slip44Reference {
type Error = Error;
fn try_from(value: U256) -> Result<Self, Self::Error> {
if value
<= U256::from_str_radix("9999999999999999999999999999999999999999999999999999999999999999", 10)
.expect("Casting the maximum value for a Slip44 reference into a U256 should never fail.")
{
Ok(Self(value))
} else {
Err(ReferenceError::TooLong.into())
}
}
}
impl From<u128> for Slip44Reference {
fn from(value: u128) -> Self {
Self(value.into())
}
}
impl Slip44Reference {
pub fn inner(&self) -> &U256 {
&self.0
}
}
impl Display for Slip44Reference {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct EvmSmartContractFungibleReference(pub(crate) [u8; 20]);
impl EvmSmartContractFungibleReference {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
if let [b'0', b'x', contract_address @ ..] = input {
check_reference_length_bounds(contract_address)?;
let decoded = hex::decode(contract_address).map_err(|_| {
log::trace!("Provided input is not a valid hex value as expected by a smart contract reference.");
ReferenceError::InvalidFormat
})?;
let inner: [u8; 20] = decoded.try_into().map_err(|_| {
log::trace!("Provided input is not 20 bytes long as expected by a smart contract reference.");
ReferenceError::InvalidFormat
})?;
Ok(Self(inner))
} else {
log::trace!("Provided input does not have the `0x` prefix as expected by a smart contract reference.");
Err(ReferenceError::InvalidFormat.into())
}
}
}
impl EvmSmartContractFungibleReference {
pub fn inner(&self) -> &[u8] {
&self.0
}
}
impl Display for EvmSmartContractFungibleReference {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "0x{}", hex::encode(self.0))
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct EvmSmartContractNonFungibleReference(
pub(crate) EvmSmartContractFungibleReference,
pub(crate) Option<EvmSmartContractNonFungibleIdentifier>,
);
impl EvmSmartContractNonFungibleReference {
pub fn smart_contract(&self) -> &EvmSmartContractFungibleReference {
&self.0
}
pub fn identifier(&self) -> &Option<EvmSmartContractNonFungibleIdentifier> {
&self.1
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct EvmSmartContractNonFungibleIdentifier(
pub(crate) BoundedVec<u8, ConstU32<MAXIMUM_ASSET_IDENTIFIER_LENGTH_U32>>,
);
impl EvmSmartContractNonFungibleIdentifier {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
check_identifier_length_bounds(input)?;
input.iter().try_for_each(|c| {
if !c.is_ascii_digit() {
log::trace!("Provided input has some invalid values as expected by a smart contract-based asset identifier.");
Err(IdentifierError::InvalidFormat)
} else {
Ok(())
}
})?;
Ok(Self(
Vec::<u8>::from(input)
.try_into()
.map_err(|_| IdentifierError::InvalidFormat)?,
))
}
}
impl EvmSmartContractNonFungibleIdentifier {
pub fn inner(&self) -> &[u8] {
&self.0
}
}
impl Display for EvmSmartContractNonFungibleIdentifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
str::from_utf8(&self.0)
.expect("Conversion of EvmSmartContractNonFungibleIdentifier to string should never fail.")
)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct GenericAssetId {
pub(crate) namespace: GenericAssetNamespace,
pub(crate) reference: GenericAssetReference,
pub(crate) id: Option<GenericAssetIdentifier>,
}
impl GenericAssetId {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let AssetComponents {
namespace,
reference,
identifier,
} = split_components(input.as_ref());
match (namespace, reference, identifier) {
(Some(namespace), Some(reference), identifier) => Ok(Self {
namespace: GenericAssetNamespace::from_utf8_encoded(namespace)?,
reference: GenericAssetReference::from_utf8_encoded(reference)?,
id: identifier.map_or(Ok(None), |id| GenericAssetIdentifier::from_utf8_encoded(id).map(Some))?,
}),
_ => Err(Error::InvalidFormat),
}
}
}
impl GenericAssetId {
pub fn namespace(&self) -> &GenericAssetNamespace {
&self.namespace
}
pub fn reference(&self) -> &GenericAssetReference {
&self.reference
}
pub fn id(&self) -> &Option<GenericAssetIdentifier> {
&self.id
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct GenericAssetNamespace(pub(crate) BoundedVec<u8, ConstU32<MAXIMUM_ASSET_NAMESPACE_LENGTH_U32>>);
impl GenericAssetNamespace {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
check_namespace_length_bounds(input)?;
input.iter().try_for_each(|c| {
if !matches!(c, b'-' | b'a'..=b'z' | b'0'..=b'9') {
log::trace!("Provided input has some invalid values as expected by a generic asset namespace.");
Err(NamespaceError::InvalidFormat)
} else {
Ok(())
}
})?;
Ok(Self(
Vec::<u8>::from(input)
.try_into()
.map_err(|_| NamespaceError::InvalidFormat)?,
))
}
}
impl GenericAssetNamespace {
pub fn inner(&self) -> &[u8] {
&self.0
}
}
impl Display for GenericAssetNamespace {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
str::from_utf8(&self.0).expect("Conversion of GenericAssetNamespace to string should never fail.")
)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct GenericAssetReference(pub(crate) BoundedVec<u8, ConstU32<MAXIMUM_ASSET_REFERENCE_LENGTH_U32>>);
impl GenericAssetReference {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
check_reference_length_bounds(input)?;
input.iter().try_for_each(|c| {
if !matches!(c, b'-' | b'.' | b'%' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') {
log::trace!("Provided input has some invalid values as expected by a generic asset reference.");
Err(ReferenceError::InvalidFormat)
} else {
Ok(())
}
})?;
Ok(Self(
Vec::<u8>::from(input)
.try_into()
.map_err(|_| ReferenceError::InvalidFormat)?,
))
}
}
impl GenericAssetReference {
pub fn inner(&self) -> &[u8] {
&self.0
}
}
impl Display for GenericAssetReference {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
str::from_utf8(&self.0).expect("Conversion of GenericAssetReference to string should never fail.")
)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct GenericAssetIdentifier(pub(crate) BoundedVec<u8, ConstU32<MAXIMUM_ASSET_IDENTIFIER_LENGTH_U32>>);
impl GenericAssetIdentifier {
pub(crate) fn from_utf8_encoded<I>(input: I) -> Result<Self, Error>
where
I: AsRef<[u8]> + Into<Vec<u8>>,
{
let input = input.as_ref();
check_identifier_length_bounds(input)?;
input.iter().try_for_each(|c| {
if !matches!(c, b'-' | b'.' | b'%' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') {
log::trace!("Provided input has some invalid values as expected by a generic asset identifier.");
Err(IdentifierError::InvalidFormat)
} else {
Ok(())
}
})?;
Ok(Self(
Vec::<u8>::from(input)
.try_into()
.map_err(|_| IdentifierError::InvalidFormat)?,
))
}
}
impl GenericAssetIdentifier {
pub fn inner(&self) -> &[u8] {
&self.0
}
}
impl Display for GenericAssetIdentifier {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
str::from_utf8(&self.0).expect("Conversion of GenericAssetIdentifier to string should never fail.")
)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_slip44_assets() {
let valid_assets = [
"slip44:60",
"slip44:0",
"slip44:2",
"slip44:714",
"slip44:234",
"slip44:134",
"slip44:0",
"slip44:9999999999999999999999999999999999999999999999999999999999999999",
];
for asset in valid_assets {
let asset_id = AssetId::from_utf8_encoded(asset.as_bytes())
.unwrap_or_else(|_| panic!("Asset ID {:?} should not fail to parse for slip44 assets", asset));
assert_eq!(asset_id.to_string(), asset);
}
let invalid_assets = [
"",
"s",
"sl",
"sli",
"slip",
"slip4",
"slip44",
"slip44:",
"slip44:a",
"slip44::",
"slip44:›",
"slip44:😁",
"slip44:999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
];
for asset in invalid_assets {
assert!(
AssetId::from_utf8_encoded(asset.as_bytes()).is_err(),
"Asset ID {:?} should fail to parse for slip44 assets",
asset
);
}
}
#[test]
fn test_erc20_assets() {
let valid_assets = [
"erc20:0x6b175474e89094c44da98b954eedeac495271d0f",
"erc20:0x8f8221AFBB33998D8584A2B05749BA73C37A938A",
];
for asset in valid_assets {
let asset_id = AssetId::from_utf8_encoded(asset.as_bytes())
.unwrap_or_else(|_| panic!("Asset ID {:?} should not fail to parse for erc20 assets", asset));
assert_eq!(asset_id.to_string(), asset.to_lowercase());
}
let invalid_assets = [
"",
"e",
"er",
"erc",
"erc2",
"erc20",
"erc20:",
"erc20::",
"erc20:›",
"erc20:😁",
"erc20:0x8f8221AFBB33998D8584A2B05749BA73C37A93",
"erc20:0x8f8221AFBB33998D8584A2B05749BA73C37A938",
"erc20:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1",
"erc20:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:1",
"erc20:8f8221AFBB33998D8584A2B05749BA73C37A938A1",
];
for asset in invalid_assets {
assert!(
AssetId::from_utf8_encoded(asset.as_bytes()).is_err(),
"Asset ID {:?} should fail to parse for erc20 assets",
asset
);
}
}
#[test]
fn test_erc721_assets() {
let valid_assets = [
"erc721:0x6b175474e89094c44da98b954eedeac495271d0f",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A:0",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A:999999999999999999999999999999999999999999999999999999999999999999999999",
];
for asset in valid_assets {
let asset_id = AssetId::from_utf8_encoded(asset.as_bytes())
.unwrap_or_else(|_| panic!("Asset ID {:?} should not fail to parse for erc721 assets", asset));
assert_eq!(asset_id.to_string(), asset.to_lowercase());
}
let invalid_assets = [
"",
"e",
"er",
"erc",
"erc7",
"erc72",
"erc721",
"erc721:",
"erc721::",
"erc721:›",
"erc721:😁",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A93",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:a",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:9999999999999999999999999999999999999999999999999999999999999999999999999",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:‹",
"erc721:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:😁",
"erc721:8f8221AFBB33998D8584A2B05749BA73C37A938A1",
];
for asset in invalid_assets {
assert!(
AssetId::from_utf8_encoded(asset.as_bytes()).is_err(),
"Asset ID {:?} should fail to parse for erc721 assets",
asset
);
}
}
#[test]
fn test_erc1155_assets() {
let valid_assets = [
"erc1155:0x6b175474e89094c44da98b954eedeac495271d0f",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A:0",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A:999999999999999999999999999999999999999999999999999999999999999999999999",
];
for asset in valid_assets {
let asset_id = AssetId::from_utf8_encoded(asset.as_bytes())
.unwrap_or_else(|_| panic!("Asset ID {:?} should not fail to parse for erc1155 assets", asset));
assert_eq!(asset_id.to_string(), asset.to_lowercase());
}
let invalid_assets = [
"",
"e",
"er",
"erc",
"erc1",
"erc11",
"erc115",
"erc1155",
"erc1155:",
"erc1155::",
"erc1155:›",
"erc1155:😁",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A93",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:a",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:9999999999999999999999999999999999999999999999999999999999999999999999999",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:‹",
"erc1155:0x8f8221AFBB33998D8584A2B05749BA73C37A938A1:😁",
"erc721:8f8221AFBB33998D8584A2B05749BA73C37A938A1",
];
for asset in invalid_assets {
assert!(
AssetId::from_utf8_encoded(asset.as_bytes()).is_err(),
"Asset ID {:?} should fail to parse for erc1155 assets",
asset
);
}
}
#[test]
fn test_generic_assets() {
let valid_assets = [
"123:a",
"12345678:-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-",
"12345678:-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%-:-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890123456789012%",
"para:411f057b9107718c9624d6aa4a3f23c1",
"para:kilt-spiritnet",
"w3n:john-doe",
];
for asset in valid_assets {
let asset_id = AssetId::from_utf8_encoded(asset.as_bytes())
.unwrap_or_else(|_| panic!("Asset ID {:?} should not fail to parse for generic assets", asset));
assert_eq!(asset_id.to_string(), asset);
}
let invalid_assets = [
"",
"a",
"as",
"as:",
"‹",
"‹:",
"asd:",
":",
"::",
":::",
"::::",
"valid:valid:",
"too-loong:valid",
"valid:too-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
"valid:valid:too-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
"no-val!d:valid",
"valid:no-val!d",
"valid:valid:no-val!d",
];
for asset in invalid_assets {
assert!(
AssetId::from_utf8_encoded(asset.as_bytes()).is_err(),
"Asset ID {:?} should fail to parse for generic assets",
asset
);
}
}
}
}