difftreelog
refactor account for key sizes in Properties
in: master
1 file changed
primitives/data-structs/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Primitives crate.18//!19//! This crate contains types, traits and constants.2021#![cfg_attr(not(feature = "std"), no_std)]2223use core::{24 convert::{TryFrom, TryInto},25 fmt,26};27use frame_support::storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use bondrewd::Bitfields;36use frame_support::{BoundedVec, traits::ConstU32};37use derivative::Derivative;38use scale_info::TypeInfo;3940mod bondrewd_codec;41mod bounded;42pub mod budget;43pub mod mapping;44mod migration;4546/// Maximum of decimal points.47pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;4849/// Maximum pieces for refungible token.50pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;51pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;5253/// Maximum tokens for user.54pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {55 100_00056} else {57 1058};5960/// Maximum for collections can be created.61pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {62 100_00063} else {64 1065};6667/// Maximum for various custom data of token.68pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {69 204870} else {71 1072};7374/// Maximum admins per collection.75pub const COLLECTION_ADMINS_LIMIT: u32 = 5;7677/// Maximum tokens per collection.78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;7980/// Maximum tokens per account.81pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {82 1_000_00083} else {84 1085};8687/// Default timeout for transfer sponsoring NFT item.88pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;89/// Default timeout for transfer sponsoring fungible item.90pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;91/// Default timeout for transfer sponsoring refungible item.92pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;9394/// Default timeout for sponsored approving.95pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9697// Schema limits98pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;99pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;100pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;101102// TODO: not used. Delete?103pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;104105/// Maximal length of a collection name.106pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;107108/// Maximal length of a collection description.109pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;110111/// Maximal length of a token prefix.112pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;113114/// Maximal length of a property key.115pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;116117/// Maximal length of a property value.118pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;119120/// A maximum number of token properties.121pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;122123/// Maximal lenght of extended property value.124pub const MAX_AUX_PROPERTY_VALUE_LENGTH: u32 = 2048;125126/// Maximum size for all collection properties.127pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;128129/// Maximum size of all token properties.130pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;131132/// How much items can be created per single133/// create_many call.134pub const MAX_ITEMS_PER_BATCH: u32 = 200;135136/// Used for limit bounded types of token custom data.137pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;138139/// Collection id.140#[derive(141 Encode,142 Decode,143 PartialEq,144 Eq,145 PartialOrd,146 Ord,147 Clone,148 Copy,149 Debug,150 Default,151 TypeInfo,152 MaxEncodedLen,153)]154#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]155pub struct CollectionId(pub u32);156impl EncodeLike<u32> for CollectionId {}157impl EncodeLike<CollectionId> for u32 {}158159impl From<u32> for CollectionId {160 fn from(value: u32) -> Self {161 Self(value)162 }163}164165/// Token id.166#[derive(167 Encode,168 Decode,169 PartialEq,170 Eq,171 PartialOrd,172 Ord,173 Clone,174 Copy,175 Debug,176 Default,177 TypeInfo,178 MaxEncodedLen,179)]180#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]181pub struct TokenId(pub u32);182impl EncodeLike<u32> for TokenId {}183impl EncodeLike<TokenId> for u32 {}184185impl TokenId {186 /// Try to get next token id.187 ///188 /// If next id cause overflow, then [`ArithmeticError::Overflow`] returned.189 pub fn try_next(self) -> Result<TokenId, ArithmeticError> {190 self.0191 .checked_add(1)192 .ok_or(ArithmeticError::Overflow)193 .map(Self)194 }195}196197impl From<TokenId> for U256 {198 fn from(t: TokenId) -> Self {199 t.0.into()200 }201}202203impl TryFrom<U256> for TokenId {204 type Error = &'static str;205206 fn try_from(value: U256) -> Result<Self, Self::Error> {207 Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))208 }209}210211/// Token data.212#[struct_versioning::versioned(version = 2, upper)]213#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]214#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]215pub struct TokenData<CrossAccountId> {216 /// Properties of token.217 pub properties: Vec<Property>,218219 /// Token owner.220 pub owner: Option<CrossAccountId>,221222 /// Token pieces.223 #[version(2.., upper(0))]224 pub pieces: u128,225}226227// TODO: unused type228pub struct OverflowError;229impl From<OverflowError> for &'static str {230 fn from(_: OverflowError) -> Self {231 "overflow occured"232 }233}234235/// Alias for decimal points type.236pub type DecimalPoints = u8;237238/// Collection mode.239///240/// Collection can represent various types of tokens.241/// Each collection can contain only one type of tokens at a time.242/// This type helps to understand which tokens the collection contains.243#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]244#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]245pub enum CollectionMode {246 /// Non fungible tokens.247 NFT,248 /// Fungible tokens.249 Fungible(DecimalPoints),250 /// Refungible tokens.251 ReFungible,252}253254impl CollectionMode {255 /// Get collection mod as number.256 pub fn id(&self) -> u8 {257 match self {258 CollectionMode::NFT => 1,259 CollectionMode::Fungible(_) => 2,260 CollectionMode::ReFungible => 3,261 }262 }263}264265// TODO: unused trait266pub trait SponsoringResolve<AccountId, Call> {267 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;268}269270/// Access mode for some token operations.271#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]272#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]273pub enum AccessMode {274 /// Access grant for owner and admins. Used as default.275 Normal,276 /// Like a [`Normal`](AccessMode::Normal) but also users in allow list.277 AllowList,278}279impl Default for AccessMode {280 fn default() -> Self {281 Self::Normal282 }283}284285// TODO: remove in future.286#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]287#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]288pub enum SchemaVersion {289 ImageURL,290 Unique,291}292impl Default for SchemaVersion {293 fn default() -> Self {294 Self::ImageURL295 }296}297298// TODO: unused type299#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]300#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]301pub struct Ownership<AccountId> {302 pub owner: AccountId,303 pub fraction: u128,304}305306/// The state of collection sponsorship.307#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]308#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]309pub enum SponsorshipState<AccountId> {310 /// The fees are applied to the transaction sender.311 Disabled,312 /// The sponsor is under consideration. Until the sponsor gives his consent,313 /// the fee will still be charged to sender.314 Unconfirmed(AccountId),315 /// Transactions are sponsored by specified account.316 Confirmed(AccountId),317}318319impl<AccountId> SponsorshipState<AccountId> {320 /// Get a sponsor of the collection who has confirmed his status.321 pub fn sponsor(&self) -> Option<&AccountId> {322 match self {323 Self::Confirmed(sponsor) => Some(sponsor),324 _ => None,325 }326 }327328 /// Get a sponsor of the collection who has pending or confirmed status.329 pub fn pending_sponsor(&self) -> Option<&AccountId> {330 match self {331 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),332 _ => None,333 }334 }335336 /// Whether the sponsorship is confirmed.337 pub fn confirmed(&self) -> bool {338 matches!(self, Self::Confirmed(_))339 }340}341342impl<T> Default for SponsorshipState<T> {343 fn default() -> Self {344 Self::Disabled345 }346}347348pub type CollectionName = BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>;349pub type CollectionDescription = BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>;350pub type CollectionTokenPrefix = BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>;351352#[derive(Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]353#[bondrewd(enforce_bytes = 1)]354pub struct CollectionFlags {355 /// Tokens in foreign collections can be transferred, but not burnt356 #[bondrewd(bits = "0..1")]357 pub foreign: bool,358 /// Supports ERC721Metadata359 #[bondrewd(bits = "1..2")]360 pub erc721metadata: bool,361 /// External collections can't be managed using `unique` api362 #[bondrewd(bits = "7..8")]363 pub external: bool,364365 #[bondrewd(reserve, bits = "2..7")]366 pub reserved: u8,367}368bondrewd_codec!(CollectionFlags);369370/// Base structure for represent collection.371///372/// Used to provide basic functionality for all types of collections.373///374/// #### Note375/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version).376#[struct_versioning::versioned(version = 2, upper)]377#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]378pub struct Collection<AccountId> {379 /// Collection owner account.380 pub owner: AccountId,381382 /// Collection mode.383 pub mode: CollectionMode,384385 /// Access mode.386 #[version(..2)]387 pub access: AccessMode,388389 /// Collection name.390 pub name: CollectionName,391392 /// Collection description.393 pub description: CollectionDescription,394395 /// Token prefix.396 pub token_prefix: CollectionTokenPrefix,397398 #[version(..2)]399 pub mint_mode: bool,400401 #[version(..2)]402 pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,403404 #[version(..2)]405 pub schema_version: SchemaVersion,406407 /// The state of sponsorship of the collection.408 pub sponsorship: SponsorshipState<AccountId>,409410 /// Collection limits.411 pub limits: CollectionLimits,412413 /// Collection permissions.414 #[version(2.., upper(Default::default()))]415 pub permissions: CollectionPermissions,416417 #[version(2.., upper(Default::default()))]418 pub flags: CollectionFlags,419420 #[version(..2)]421 pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,422423 #[version(..2)]424 pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,425426 #[version(..2)]427 pub meta_update_permission: MetaUpdatePermission,428}429430#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]431#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]432pub struct RpcCollectionFlags {433 /// Is collection is foreign.434 pub foreign: bool,435 /// Collection supports ERC721Metadata.436 pub erc721metadata: bool,437}438439/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version).440#[struct_versioning::versioned(version = 2, upper)]441#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]442#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]443pub struct RpcCollection<AccountId> {444 /// Collection owner account.445 pub owner: AccountId,446447 /// Collection mode.448 pub mode: CollectionMode,449450 /// Collection name.451 pub name: Vec<u16>,452453 /// Collection description.454 pub description: Vec<u16>,455456 /// Token prefix.457 pub token_prefix: Vec<u8>,458459 /// The state of sponsorship of the collection.460 pub sponsorship: SponsorshipState<AccountId>,461462 /// Collection limits.463 pub limits: CollectionLimits,464465 /// Collection permissions.466 pub permissions: CollectionPermissions,467468 /// Token property permissions.469 pub token_property_permissions: Vec<PropertyKeyPermission>,470471 /// Collection properties.472 pub properties: Vec<Property>,473474 /// Is collection read only.475 pub read_only: bool,476477 /// Extra collection flags478 #[version(2.., upper(RpcCollectionFlags {foreign: false, erc721metadata: false}))]479 pub flags: RpcCollectionFlags,480}481482/// Data used for create collection.483///484/// All fields are wrapped in [`Option`], where `None` means chain default.485#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]486#[derivative(Debug, Default(bound = ""))]487pub struct CreateCollectionData<AccountId> {488 /// Collection mode.489 #[derivative(Default(value = "CollectionMode::NFT"))]490 pub mode: CollectionMode,491492 /// Access mode.493 pub access: Option<AccessMode>,494495 /// Collection name.496 pub name: CollectionName,497498 /// Collection description.499 pub description: CollectionDescription,500501 /// Token prefix.502 pub token_prefix: CollectionTokenPrefix,503504 /// Pending collection sponsor.505 pub pending_sponsor: Option<AccountId>,506507 /// Collection limits.508 pub limits: Option<CollectionLimits>,509510 /// Collection permissions.511 pub permissions: Option<CollectionPermissions>,512513 /// Token property permissions.514 pub token_property_permissions: CollectionPropertiesPermissionsVec,515516 /// Collection properties.517 pub properties: CollectionPropertiesVec,518}519520/// Bounded vector of properties permissions. Max length is [`MAX_PROPERTIES_PER_ITEM`].521// TODO: maybe rename to PropertiesPermissionsVec522pub type CollectionPropertiesPermissionsVec =523 BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;524525/// Bounded vector of properties. Max length is [`MAX_PROPERTIES_PER_ITEM`].526pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;527528/// Limits and restrictions of a collection.529///530/// All fields are wrapped in [`Option`], where `None` means chain default.531///532/// Update with `pallet_common::Pallet::clamp_limits`.533// IMPORTANT: When adding/removing fields from this struct - don't forget to also534#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]535#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]536// When adding/removing fields from this struct - don't forget to also update with `pallet_common::Pallet::clamp_limits`.537// TODO: move `pallet_common::Pallet::clamp_limits` into `impl CollectionLimits`.538// TODO: may be remove [`Option`] and **pub** from fields and create struct with default values.539pub struct CollectionLimits {540 /// How many tokens can a user have on one account.541 /// * Default - [`ACCOUNT_TOKEN_OWNERSHIP_LIMIT`].542 /// * Limit - [`MAX_TOKEN_OWNERSHIP`].543 pub account_token_ownership_limit: Option<u32>,544545 /// How many bytes of data are available for sponsorship.546 /// * Default - [`CUSTOM_DATA_LIMIT`].547 /// * Limit - [`CUSTOM_DATA_LIMIT`].548 pub sponsored_data_size: Option<u32>,549550 // FIXME should we delete this or repurpose it?551 /// Times in how many blocks we sponsor data.552 ///553 /// If is `Some(v)` then **setVariableMetadata** is sponsored if there is `v` block between transactions.554 ///555 /// * Default - [`SponsoringDisabled`](SponsoringRateLimit::SponsoringDisabled).556 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].557 ///558 /// In any case, chain default: [`SponsoringRateLimit::SponsoringDisabled`]559 pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,560 /// Maximum amount of tokens inside the collection. Chain default: [`COLLECTION_TOKEN_LIMIT`]561562 /// How many tokens can be mined into this collection.563 ///564 /// * Default - [`COLLECTION_TOKEN_LIMIT`].565 /// * Limit - [`COLLECTION_TOKEN_LIMIT`].566 pub token_limit: Option<u32>,567568 /// Timeouts for transfer sponsoring.569 ///570 /// * Default571 /// - **Fungible** - [`FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT`]572 /// - **NFT** - [`NFT_SPONSOR_TRANSFER_TIMEOUT`]573 /// - **Refungible** - [`REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT`]574 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].575 pub sponsor_transfer_timeout: Option<u32>,576577 /// Timeout for sponsoring an approval in passed blocks.578 ///579 /// * Default - [`SPONSOR_APPROVE_TIMEOUT`].580 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].581 pub sponsor_approve_timeout: Option<u32>,582583 /// Whether the collection owner of the collection can send tokens (which belong to other users).584 ///585 /// * Default - **false**.586 pub owner_can_transfer: Option<bool>,587588 /// Can the collection owner burn other people's tokens.589 ///590 /// * Default - **true**.591 pub owner_can_destroy: Option<bool>,592593 /// Is it possible to send tokens from this collection between users.594 ///595 /// * Default - **true**.596 pub transfers_enabled: Option<bool>,597}598599impl CollectionLimits {600 pub fn with_default_limits(collection_type: CollectionMode) -> Self {601 CollectionLimits {602 account_token_ownership_limit: Some(ACCOUNT_TOKEN_OWNERSHIP_LIMIT),603 sponsored_data_size: Some(CUSTOM_DATA_LIMIT),604 sponsored_data_rate_limit: Some(SponsoringRateLimit::SponsoringDisabled),605 token_limit: Some(COLLECTION_TOKEN_LIMIT),606 sponsor_transfer_timeout: match collection_type {607 CollectionMode::NFT => Some(NFT_SPONSOR_TRANSFER_TIMEOUT),608 CollectionMode::ReFungible => Some(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),609 CollectionMode::Fungible(_) => Some(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),610 },611 sponsor_approve_timeout: Some(SPONSOR_APPROVE_TIMEOUT),612 owner_can_transfer: Some(false),613 owner_can_destroy: Some(true),614 transfers_enabled: Some(true),615 }616 }617618 /// Get effective value for [`account_token_ownership_limit`](self.account_token_ownership_limit).619 pub fn account_token_ownership_limit(&self) -> u32 {620 self.account_token_ownership_limit621 .unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)622 .min(MAX_TOKEN_OWNERSHIP)623 }624625 /// Get effective value for [`sponsored_data_size`](self.sponsored_data_size).626 pub fn sponsored_data_size(&self) -> u32 {627 self.sponsored_data_size628 .unwrap_or(CUSTOM_DATA_LIMIT)629 .min(CUSTOM_DATA_LIMIT)630 }631632 /// Get effective value for [`token_limit`](self.token_limit).633 pub fn token_limit(&self) -> u32 {634 self.token_limit635 .unwrap_or(COLLECTION_TOKEN_LIMIT)636 .min(COLLECTION_TOKEN_LIMIT)637 }638639 // TODO: may be replace u32 to mode?640 /// Get effective value for [`sponsor_transfer_timeout`](self.sponsor_transfer_timeout).641 pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {642 self.sponsor_transfer_timeout643 .unwrap_or(default)644 .min(MAX_SPONSOR_TIMEOUT)645 }646647 /// Get effective value for [`sponsor_approve_timeout`](self.sponsor_approve_timeout).648 pub fn sponsor_approve_timeout(&self) -> u32 {649 self.sponsor_approve_timeout650 .unwrap_or(SPONSOR_APPROVE_TIMEOUT)651 .min(MAX_SPONSOR_TIMEOUT)652 }653654 /// Get effective value for [`owner_can_transfer`](self.owner_can_transfer).655 pub fn owner_can_transfer(&self) -> bool {656 self.owner_can_transfer.unwrap_or(false)657 }658659 /// Get effective value for [`owner_can_transfer_instaled`](self.owner_can_transfer_instaled).660 pub fn owner_can_transfer_instaled(&self) -> bool {661 self.owner_can_transfer.is_some()662 }663664 /// Get effective value for [`owner_can_destroy`](self.owner_can_destroy).665 pub fn owner_can_destroy(&self) -> bool {666 self.owner_can_destroy.unwrap_or(true)667 }668669 /// Get effective value for [`transfers_enabled`](self.transfers_enabled).670 pub fn transfers_enabled(&self) -> bool {671 self.transfers_enabled.unwrap_or(true)672 }673674 /// Get effective value for [`sponsored_data_rate_limit`](self.sponsored_data_rate_limit).675 pub fn sponsored_data_rate_limit(&self) -> Option<u32> {676 match self677 .sponsored_data_rate_limit678 .unwrap_or(SponsoringRateLimit::SponsoringDisabled)679 {680 SponsoringRateLimit::SponsoringDisabled => None,681 SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),682 }683 }684}685686/// Permissions on certain operations within a collection.687///688/// Some fields are wrapped in [`Option`], where `None` means chain default.689///690/// Update with `pallet_common::Pallet::clamp_permissions`.691#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]692#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]693// When adding/removing fields from this struct - don't forget to also update `pallet_common::Pallet::clamp_permissions`.694// TODO: move `pallet_common::Pallet::clamp_permissions` into `impl CollectionPermissions`.695pub struct CollectionPermissions {696 /// Access mode.697 ///698 /// * Default - [`AccessMode::Normal`].699 pub access: Option<AccessMode>,700701 /// Minting allowance.702 ///703 /// * Default - **false**.704 pub mint_mode: Option<bool>,705706 /// Permissions for nesting.707 ///708 /// * Default709 /// - `token_owner` - **false**710 /// - `collection_admin` - **false**711 /// - `restricted` - **None**712 pub nesting: Option<NestingPermissions>,713}714715impl CollectionPermissions {716 /// Get effective value for [`access`](self.access).717 pub fn access(&self) -> AccessMode {718 self.access.unwrap_or(AccessMode::Normal)719 }720721 /// Get effective value for [`mint_mode`](self.mint_mode).722 pub fn mint_mode(&self) -> bool {723 self.mint_mode.unwrap_or(false)724 }725726 /// Get effective value for [`nesting`](self.nesting).727 pub fn nesting(&self) -> &NestingPermissions {728 static DEFAULT: NestingPermissions = NestingPermissions {729 token_owner: false,730 collection_admin: false,731 restricted: None,732 #[cfg(feature = "runtime-benchmarks")]733 permissive: false,734 };735 self.nesting.as_ref().unwrap_or(&DEFAULT)736 }737}738739/// Inner set for collections allowed to nest.740type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;741742/// Wraper for collections set allowing nest.743#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]744#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]745#[derivative(Debug)]746pub struct OwnerRestrictedSet(747 #[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]748 #[derivative(Debug(format_with = "bounded::set_debug"))]749 pub OwnerRestrictedSetInner,750);751752impl OwnerRestrictedSet {753 /// Create new set.754 pub fn new() -> Self {755 Self(Default::default())756 }757}758impl core::ops::Deref for OwnerRestrictedSet {759 type Target = OwnerRestrictedSetInner;760 fn deref(&self) -> &Self::Target {761 &self.0762 }763}764impl core::ops::DerefMut for OwnerRestrictedSet {765 fn deref_mut(&mut self) -> &mut Self::Target {766 &mut self.0767 }768}769770/// Part of collection permissions, if set, defines who is able to nest tokens into other tokens.771#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]772#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]773#[derivative(Debug)]774pub struct NestingPermissions {775 /// Owner of token can nest tokens under it.776 pub token_owner: bool,777 /// Admin of token collection can nest tokens under token.778 pub collection_admin: bool,779 /// If set - only tokens from specified collections can be nested.780 pub restricted: Option<OwnerRestrictedSet>,781782 #[cfg(feature = "runtime-benchmarks")]783 /// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`.784 pub permissive: bool,785}786787/// Enum denominating how often can sponsoring occur if it is enabled.788///789/// Used for [`collection limits`](CollectionLimits).790#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]791#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]792pub enum SponsoringRateLimit {793 /// Sponsoring is disabled, and the collection sponsor will not pay for transactions794 SponsoringDisabled,795 /// Once per how many blocks can sponsorship of a transaction type occur796 Blocks(u32),797}798799/// Data used to describe an NFT at creation.800#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]801#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]802#[derivative(Debug)]803pub struct CreateNftData {804 /// Key-value pairs used to describe the token as metadata805 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]806 #[derivative(Debug(format_with = "bounded::vec_debug"))]807 /// Properties that wil be assignet to created item.808 pub properties: CollectionPropertiesVec,809}810811/// Data used to describe a Fungible token at creation.812#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]813#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]814pub struct CreateFungibleData {815 /// Number of fungible coins minted816 pub value: u128,817}818819/// Data used to describe a Refungible token at creation.820#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]821#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]822#[derivative(Debug)]823pub struct CreateReFungibleData {824 /// Number of pieces the RFT is split into825 pub pieces: u128,826827 /// Key-value pairs used to describe the token as metadata828 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]829 #[derivative(Debug(format_with = "bounded::vec_debug"))]830 pub properties: CollectionPropertiesVec,831}832833// TODO: remove this.834#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]835#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]836pub enum MetaUpdatePermission {837 ItemOwner,838 Admin,839 None,840}841842/// Enum holding data used for creation of all three item types.843/// Unified data for create item.844#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]845#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]846pub enum CreateItemData {847 /// Data for create NFT.848 NFT(CreateNftData),849 /// Data for create Fungible item.850 Fungible(CreateFungibleData),851 /// Data for create ReFungible item.852 ReFungible(CreateReFungibleData),853}854855/// Extended data for create NFT.856#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]857#[derivative(Debug)]858pub struct CreateNftExData<CrossAccountId> {859 /// Properties that wil be assignet to created item.860 #[derivative(Debug(format_with = "bounded::vec_debug"))]861 pub properties: CollectionPropertiesVec,862863 /// Owner of creating item.864 pub owner: CrossAccountId,865}866867/// Extended data for create ReFungible item.868#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]869#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]870pub struct CreateRefungibleExMultipleOwners<CrossAccountId> {871 #[derivative(Debug(format_with = "bounded::map_debug"))]872 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,873 #[derivative(Debug(format_with = "bounded::vec_debug"))]874 pub properties: CollectionPropertiesVec,875}876877/// Extended data for create ReFungible item.878#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]879#[derivative(Debug(bound = "CrossAccountId: fmt::Debug"))]880pub struct CreateRefungibleExSingleOwner<CrossAccountId> {881 pub user: CrossAccountId,882 pub pieces: u128,883 #[derivative(Debug(format_with = "bounded::vec_debug"))]884 pub properties: CollectionPropertiesVec,885}886887/// Unified extended data for creating item.888#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]889#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]890pub enum CreateItemExData<CrossAccountId> {891 /// Extended data for create NFT.892 NFT(893 #[derivative(Debug(format_with = "bounded::vec_debug"))]894 BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,895 ),896897 /// Extended data for create Fungible item.898 Fungible(899 #[derivative(Debug(format_with = "bounded::map_debug"))]900 BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,901 ),902903 /// Extended data for create ReFungible item in case of904 /// many tokens, each may have only one owner905 RefungibleMultipleItems(906 #[derivative(Debug(format_with = "bounded::vec_debug"))]907 BoundedVec<CreateRefungibleExSingleOwner<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,908 ),909910 /// Extended data for create ReFungible item in case of911 /// single token, which may have many owners912 RefungibleMultipleOwners(CreateRefungibleExMultipleOwners<CrossAccountId>),913}914915impl From<CreateNftData> for CreateItemData {916 fn from(item: CreateNftData) -> Self {917 CreateItemData::NFT(item)918 }919}920921impl From<CreateReFungibleData> for CreateItemData {922 fn from(item: CreateReFungibleData) -> Self {923 CreateItemData::ReFungible(item)924 }925}926927impl From<CreateFungibleData> for CreateItemData {928 fn from(item: CreateFungibleData) -> Self {929 CreateItemData::Fungible(item)930 }931}932933/// Token's address, dictated by its collection and token IDs.934#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]935#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]936// todo possibly rename to be used generally as an address pair937pub struct TokenChild {938 /// Token id.939 pub token: TokenId,940941 /// Collection id.942 pub collection: CollectionId,943}944945/// Collection statistics.946#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]947#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]948pub struct CollectionStats {949 /// Number of created items.950 pub created: u32,951952 /// Number of burned items.953 pub destroyed: u32,954955 /// Number of current items.956 pub alive: u32,957}958959/// This type works like [`PhantomData`] but supports generating _scale-info_ descriptions to generate node metadata.960#[derive(Encode, Decode, Clone, Debug)]961#[cfg_attr(feature = "std", derive(PartialEq))]962pub struct PhantomType<T>(core::marker::PhantomData<T>);963964impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {965 type Identity = PhantomType<T>;966967 fn type_info() -> scale_info::Type {968 use scale_info::{969 Type, Path,970 build::{FieldsBuilder, UnnamedFields},971 form::MetaForm,972 type_params,973 };974 Type::builder()975 .path(Path::new("up_data_structs", "PhantomType"))976 .type_params(type_params!(T))977 .composite(978 <FieldsBuilder<MetaForm, UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()),979 )980 }981}982impl<T> MaxEncodedLen for PhantomType<T> {983 fn max_encoded_len() -> usize {984 0985 }986}987988/// Bounded vector of bytes.989pub type BoundedBytes<S> = BoundedVec<u8, S>;990991/// Extra properties for external collections.992pub type AuxPropertyValue = BoundedBytes<ConstU32<MAX_AUX_PROPERTY_VALUE_LENGTH>>;993994/// Property key.995pub type PropertyKey = BoundedBytes<ConstU32<MAX_PROPERTY_KEY_LENGTH>>;996997/// Property value.998pub type PropertyValue = BoundedBytes<ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;9991000/// Property permission.1001#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone, Default)]1002#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1003pub struct PropertyPermission {1004 /// Permission to change the property and property permission.1005 ///1006 /// If it **false** then you can not change corresponding property even if [`collection_admin`] and [`token_owner`] are **true**.1007 pub mutable: bool,10081009 /// Change permission for the collection administrator.1010 pub collection_admin: bool,10111012 /// Permission to change the property for the owner of the token.1013 pub token_owner: bool,1014}10151016impl PropertyPermission {1017 /// Creates mutable property permission but changes restricted for collection admin and token owner.1018 pub fn none() -> Self {1019 Self {1020 mutable: true,1021 collection_admin: false,1022 token_owner: false,1023 }1024 }1025}10261027/// Property is simpl key-value record.1028#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]1029#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1030pub struct Property {1031 /// Property key.1032 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1033 pub key: PropertyKey,10341035 /// Property value.1036 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1037 pub value: PropertyValue,1038}10391040impl Into<(PropertyKey, PropertyValue)> for Property {1041 fn into(self) -> (PropertyKey, PropertyValue) {1042 (self.key, self.value)1043 }1044}10451046/// Record for proprty key permission.1047#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]1048#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1049pub struct PropertyKeyPermission {1050 /// Key.1051 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1052 pub key: PropertyKey,10531054 /// Permission.1055 pub permission: PropertyPermission,1056}10571058impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {1059 fn into(self) -> (PropertyKey, PropertyPermission) {1060 (self.key, self.permission)1061 }1062}10631064/// Errors for properties actions.1065#[derive(Debug)]1066pub enum PropertiesError {1067 /// The space allocated for properties has run out.1068 ///1069 /// * Limit for colection - [`MAX_COLLECTION_PROPERTIES_SIZE`].1070 /// * Limit for token - [`MAX_TOKEN_PROPERTIES_SIZE`].1071 NoSpaceForProperty,10721073 /// The property limit has been reached.1074 ///1075 /// * Limit - [`MAX_PROPERTIES_PER_ITEM`].1076 PropertyLimitReached,10771078 /// Property key contains not allowed character.1079 InvalidCharacterInPropertyKey,10801081 /// Property key length is too long.1082 ///1083 /// * Limit - [`MAX_PROPERTY_KEY_LENGTH`].1084 PropertyKeyIsTooLong,10851086 /// Property key is empty.1087 EmptyPropertyKey,1088}10891090/// Token owner error: it could be either `NotFound` ot `MultipleOwners`.1091#[derive(Debug)]1092pub enum TokenOwnerError {1093 NotFound,1094 MultipleOwners,1095}10961097/// Marker for scope of property.1098///1099/// Scoped property can't be changed by user. Used for external collections.1100#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Clone, Copy)]1101pub enum PropertyScope {1102 None,1103 Rmrk,1104}11051106impl PropertyScope {1107 /// Apply scope to property key.1108 pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {1109 let scope_str: &[u8] = match self {1110 Self::None => return Ok(key),1111 Self::Rmrk => b"rmrk",1112 };11131114 [scope_str, b":", key.as_slice()]1115 .concat()1116 .try_into()1117 .map_err(|_| PropertiesError::PropertyKeyIsTooLong)1118 }1119}11201121/// Trait for operate with properties.1122pub trait TrySetProperty: Sized {1123 type Value;11241125 /// Try to set property with scope.1126 fn try_scoped_set(1127 &mut self,1128 scope: PropertyScope,1129 key: PropertyKey,1130 value: Self::Value,1131 ) -> Result<Option<Self::Value>, PropertiesError>;11321133 /// Try to set property with scope from iterator.1134 fn try_scoped_set_from_iter<I, KV>(1135 &mut self,1136 scope: PropertyScope,1137 iter: I,1138 ) -> Result<(), PropertiesError>1139 where1140 I: Iterator<Item = KV>,1141 KV: Into<(PropertyKey, Self::Value)>,1142 {1143 for kv in iter {1144 let (key, value) = kv.into();1145 self.try_scoped_set(scope, key, value)?;1146 }11471148 Ok(())1149 }11501151 /// Try to set property.1152 fn try_set(1153 &mut self,1154 key: PropertyKey,1155 value: Self::Value,1156 ) -> Result<Option<Self::Value>, PropertiesError> {1157 self.try_scoped_set(PropertyScope::None, key, value)1158 }11591160 /// Try to set property from iterator.1161 fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>1162 where1163 I: Iterator<Item = KV>,1164 KV: Into<(PropertyKey, Self::Value)>,1165 {1166 self.try_scoped_set_from_iter(PropertyScope::None, iter)1167 }1168}11691170/// Wrapped map for storing properties.1171#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]1172#[derivative(Default(bound = ""))]1173pub struct PropertiesMap<Value>(1174 BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,1175);11761177impl<Value> PropertiesMap<Value> {1178 /// Create new property map.1179 pub fn new() -> Self {1180 Self(BoundedBTreeMap::new())1181 }11821183 /// Remove property from map.1184 pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {1185 Self::check_property_key(key)?;11861187 Ok(self.0.remove(key))1188 }11891190 /// Get property with appropriate key from map.1191 pub fn get(&self, key: &PropertyKey) -> Option<&Value> {1192 self.0.get(key)1193 }11941195 /// Check if map contains key.1196 pub fn contains_key(&self, key: &PropertyKey) -> bool {1197 self.0.contains_key(key)1198 }11991200 fn metadata_encoded_len() -> usize {1201 // Max length of key length + max length of value length for max properties1202 // + max length of table size length1203 (4 * 4) * (MAX_PROPERTIES_PER_ITEM as usize) + 41204 }12051206 /// Check if map contains key with key validation.1207 fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {1208 if key.is_empty() {1209 return Err(PropertiesError::EmptyPropertyKey);1210 }12111212 for byte in key.as_slice().iter() {1213 let byte = *byte;12141215 if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' && byte != b'.' {1216 return Err(PropertiesError::InvalidCharacterInPropertyKey);1217 }1218 }12191220 Ok(())1221 }12221223 pub fn values(&self) -> impl Iterator<Item = &Value> {1224 self.0.values()1225 }1226}12271228impl<Value> IntoIterator for PropertiesMap<Value> {1229 type Item = (PropertyKey, Value);1230 type IntoIter = <1231 BoundedBTreeMap<1232 PropertyKey,1233 Value,1234 ConstU32<MAX_PROPERTIES_PER_ITEM>1235 > as IntoIterator1236 >::IntoIter;12371238 fn into_iter(self) -> Self::IntoIter {1239 self.0.into_iter()1240 }1241}12421243impl<Value> TrySetProperty for PropertiesMap<Value> {1244 type Value = Value;12451246 fn try_scoped_set(1247 &mut self,1248 scope: PropertyScope,1249 key: PropertyKey,1250 value: Self::Value,1251 ) -> Result<Option<Self::Value>, PropertiesError> {1252 Self::check_property_key(&key)?;12531254 let key = scope.apply(key)?;1255 self.01256 .try_insert(key, value)1257 .map_err(|_| PropertiesError::PropertyLimitReached)1258 }1259}12601261/// Alias for property permissions map.1262pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;12631264/// Wrapper for properties map with consumed space control.1265#[derive(Encode, Decode, TypeInfo, Clone, PartialEq)]1266pub struct Properties<const S: u32> {1267 map: PropertiesMap<PropertyValue>,1268 consumed_space: u32,1269 space_limit: u32,1270}12711272impl<const S: u32> MaxEncodedLen for Properties<S> {1273 fn max_encoded_len() -> usize {1274 <PropertiesMap<PropertyValue>>::metadata_encoded_len()1275 + (u32::max_encoded_len() * 2)1276 + S as usize1277 }1278}12791280impl<const S: u32> Default for Properties<S> {1281 fn default() -> Self {1282 Self::new()1283 }1284}12851286impl<const S: u32> Properties<S> {1287 /// Create new properies container.1288 pub fn new() -> Self {1289 Self {1290 map: PropertiesMap::new(),1291 consumed_space: 0,1292 space_limit: u32::MAX,1293 }1294 }12951296 /// Remove propery with appropiate key.1297 pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {1298 let value = self.map.remove(key)?;12991300 if let Some(ref value) = value {1301 let value_len = value.len() as u32;1302 self.consumed_space -= value_len;1303 }13041305 Ok(value)1306 }13071308 /// Get property with appropriate key.1309 pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {1310 self.map.get(key)1311 }13121313 /// Recomputes the consumed space for the current properties state.1314 /// Needed to repair a token due to a bug fixed in the [PR #733](https://github.com/UniqueNetwork/unique-chain/pull/773).1315 pub fn recompute_consumed_space(&mut self) {1316 self.consumed_space = self.map.values().map(|value| value.len() as u32).sum();1317 }1318}13191320impl<const S: u32> IntoIterator for Properties<S> {1321 type Item = (PropertyKey, PropertyValue);1322 type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;13231324 fn into_iter(self) -> Self::IntoIter {1325 self.map.into_iter()1326 }1327}13281329impl<const S: u32> TrySetProperty for Properties<S> {1330 type Value = PropertyValue;13311332 fn try_scoped_set(1333 &mut self,1334 scope: PropertyScope,1335 key: PropertyKey,1336 value: Self::Value,1337 ) -> Result<Option<Self::Value>, PropertiesError> {1338 let value_len = value.len();13391340 if self.consumed_space as usize + value_len > self.space_limit.min(S) as usize1341 && !cfg!(feature = "runtime-benchmarks")1342 {1343 return Err(PropertiesError::NoSpaceForProperty);1344 }13451346 let value_len = value_len as u32;1347 let old_value = self.map.try_scoped_set(scope, key, value)?;13481349 let old_value_len = old_value.as_ref().map(|v| v.len() as u32).unwrap_or(0);13501351 if value_len > old_value_len {1352 self.consumed_space += value_len - old_value_len;1353 } else {1354 self.consumed_space -= old_value_len - value_len;1355 }13561357 Ok(old_value)1358 }1359}13601361pub type CollectionProperties = Properties<MAX_COLLECTION_PROPERTIES_SIZE>;1362pub type TokenProperties = Properties<MAX_TOKEN_PROPERTIES_SIZE>;1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Primitives crate.18//!19//! This crate contains types, traits and constants.2021#![cfg_attr(not(feature = "std"), no_std)]2223use core::{24 convert::{TryFrom, TryInto},25 fmt,26};27use frame_support::storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet};2829#[cfg(feature = "serde")]30use serde::{Serialize, Deserialize};3132use sp_core::U256;33use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};34use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};35use bondrewd::Bitfields;36use frame_support::{BoundedVec, traits::ConstU32};37use derivative::Derivative;38use scale_info::TypeInfo;3940mod bondrewd_codec;41mod bounded;42pub mod budget;43pub mod mapping;44mod migration;4546/// Maximum of decimal points.47pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;4849/// Maximum pieces for refungible token.50pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;51pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;5253/// Maximum tokens for user.54pub const MAX_TOKEN_OWNERSHIP: u32 = if cfg!(not(feature = "limit-testing")) {55 100_00056} else {57 1058};5960/// Maximum for collections can be created.61pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {62 100_00063} else {64 1065};6667/// Maximum for various custom data of token.68pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {69 204870} else {71 1072};7374/// Maximum admins per collection.75pub const COLLECTION_ADMINS_LIMIT: u32 = 5;7677/// Maximum tokens per collection.78pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;7980/// Maximum tokens per account.81pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {82 1_000_00083} else {84 1085};8687/// Default timeout for transfer sponsoring NFT item.88pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;89/// Default timeout for transfer sponsoring fungible item.90pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;91/// Default timeout for transfer sponsoring refungible item.92pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;9394/// Default timeout for sponsored approving.95pub const SPONSOR_APPROVE_TIMEOUT: u32 = 5;9697// Schema limits98pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 8192;99pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 8192;100pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 32768;101102// TODO: not used. Delete?103pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;104105/// Maximal length of a collection name.106pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;107108/// Maximal length of a collection description.109pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;110111/// Maximal length of a token prefix.112pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;113114/// Maximal length of a property key.115pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;116117/// Maximal length of a property value.118pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;119120/// A maximum number of token properties.121pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;122123/// Maximal lenght of extended property value.124pub const MAX_AUX_PROPERTY_VALUE_LENGTH: u32 = 2048;125126/// Maximum size for all collection properties.127pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;128129/// Maximum size of all token properties.130pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;131132/// How much items can be created per single133/// create_many call.134pub const MAX_ITEMS_PER_BATCH: u32 = 200;135136/// Used for limit bounded types of token custom data.137pub type CustomDataLimit = ConstU32<CUSTOM_DATA_LIMIT>;138139/// Collection id.140#[derive(141 Encode,142 Decode,143 PartialEq,144 Eq,145 PartialOrd,146 Ord,147 Clone,148 Copy,149 Debug,150 Default,151 TypeInfo,152 MaxEncodedLen,153)]154#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]155pub struct CollectionId(pub u32);156impl EncodeLike<u32> for CollectionId {}157impl EncodeLike<CollectionId> for u32 {}158159impl From<u32> for CollectionId {160 fn from(value: u32) -> Self {161 Self(value)162 }163}164165/// Token id.166#[derive(167 Encode,168 Decode,169 PartialEq,170 Eq,171 PartialOrd,172 Ord,173 Clone,174 Copy,175 Debug,176 Default,177 TypeInfo,178 MaxEncodedLen,179)]180#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]181pub struct TokenId(pub u32);182impl EncodeLike<u32> for TokenId {}183impl EncodeLike<TokenId> for u32 {}184185impl TokenId {186 /// Try to get next token id.187 ///188 /// If next id cause overflow, then [`ArithmeticError::Overflow`] returned.189 pub fn try_next(self) -> Result<TokenId, ArithmeticError> {190 self.0191 .checked_add(1)192 .ok_or(ArithmeticError::Overflow)193 .map(Self)194 }195}196197impl From<TokenId> for U256 {198 fn from(t: TokenId) -> Self {199 t.0.into()200 }201}202203impl TryFrom<U256> for TokenId {204 type Error = &'static str;205206 fn try_from(value: U256) -> Result<Self, Self::Error> {207 Ok(TokenId(value.try_into().map_err(|_| "too large token id")?))208 }209}210211/// Token data.212#[struct_versioning::versioned(version = 2, upper)]213#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]214#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]215pub struct TokenData<CrossAccountId> {216 /// Properties of token.217 pub properties: Vec<Property>,218219 /// Token owner.220 pub owner: Option<CrossAccountId>,221222 /// Token pieces.223 #[version(2.., upper(0))]224 pub pieces: u128,225}226227// TODO: unused type228pub struct OverflowError;229impl From<OverflowError> for &'static str {230 fn from(_: OverflowError) -> Self {231 "overflow occured"232 }233}234235/// Alias for decimal points type.236pub type DecimalPoints = u8;237238/// Collection mode.239///240/// Collection can represent various types of tokens.241/// Each collection can contain only one type of tokens at a time.242/// This type helps to understand which tokens the collection contains.243#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]244#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]245pub enum CollectionMode {246 /// Non fungible tokens.247 NFT,248 /// Fungible tokens.249 Fungible(DecimalPoints),250 /// Refungible tokens.251 ReFungible,252}253254impl CollectionMode {255 /// Get collection mod as number.256 pub fn id(&self) -> u8 {257 match self {258 CollectionMode::NFT => 1,259 CollectionMode::Fungible(_) => 2,260 CollectionMode::ReFungible => 3,261 }262 }263}264265// TODO: unused trait266pub trait SponsoringResolve<AccountId, Call> {267 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;268}269270/// Access mode for some token operations.271#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]272#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]273pub enum AccessMode {274 /// Access grant for owner and admins. Used as default.275 Normal,276 /// Like a [`Normal`](AccessMode::Normal) but also users in allow list.277 AllowList,278}279impl Default for AccessMode {280 fn default() -> Self {281 Self::Normal282 }283}284285// TODO: remove in future.286#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]287#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]288pub enum SchemaVersion {289 ImageURL,290 Unique,291}292impl Default for SchemaVersion {293 fn default() -> Self {294 Self::ImageURL295 }296}297298// TODO: unused type299#[derive(Encode, Decode, Default, Debug, Clone, PartialEq, TypeInfo)]300#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]301pub struct Ownership<AccountId> {302 pub owner: AccountId,303 pub fraction: u128,304}305306/// The state of collection sponsorship.307#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]308#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]309pub enum SponsorshipState<AccountId> {310 /// The fees are applied to the transaction sender.311 Disabled,312 /// The sponsor is under consideration. Until the sponsor gives his consent,313 /// the fee will still be charged to sender.314 Unconfirmed(AccountId),315 /// Transactions are sponsored by specified account.316 Confirmed(AccountId),317}318319impl<AccountId> SponsorshipState<AccountId> {320 /// Get a sponsor of the collection who has confirmed his status.321 pub fn sponsor(&self) -> Option<&AccountId> {322 match self {323 Self::Confirmed(sponsor) => Some(sponsor),324 _ => None,325 }326 }327328 /// Get a sponsor of the collection who has pending or confirmed status.329 pub fn pending_sponsor(&self) -> Option<&AccountId> {330 match self {331 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),332 _ => None,333 }334 }335336 /// Whether the sponsorship is confirmed.337 pub fn confirmed(&self) -> bool {338 matches!(self, Self::Confirmed(_))339 }340}341342impl<T> Default for SponsorshipState<T> {343 fn default() -> Self {344 Self::Disabled345 }346}347348pub type CollectionName = BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>;349pub type CollectionDescription = BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>;350pub type CollectionTokenPrefix = BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>;351352#[derive(Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]353#[bondrewd(enforce_bytes = 1)]354pub struct CollectionFlags {355 /// Tokens in foreign collections can be transferred, but not burnt356 #[bondrewd(bits = "0..1")]357 pub foreign: bool,358 /// Supports ERC721Metadata359 #[bondrewd(bits = "1..2")]360 pub erc721metadata: bool,361 /// External collections can't be managed using `unique` api362 #[bondrewd(bits = "7..8")]363 pub external: bool,364365 #[bondrewd(reserve, bits = "2..7")]366 pub reserved: u8,367}368bondrewd_codec!(CollectionFlags);369370/// Base structure for represent collection.371///372/// Used to provide basic functionality for all types of collections.373///374/// #### Note375/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version).376#[struct_versioning::versioned(version = 2, upper)]377#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]378pub struct Collection<AccountId> {379 /// Collection owner account.380 pub owner: AccountId,381382 /// Collection mode.383 pub mode: CollectionMode,384385 /// Access mode.386 #[version(..2)]387 pub access: AccessMode,388389 /// Collection name.390 pub name: CollectionName,391392 /// Collection description.393 pub description: CollectionDescription,394395 /// Token prefix.396 pub token_prefix: CollectionTokenPrefix,397398 #[version(..2)]399 pub mint_mode: bool,400401 #[version(..2)]402 pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,403404 #[version(..2)]405 pub schema_version: SchemaVersion,406407 /// The state of sponsorship of the collection.408 pub sponsorship: SponsorshipState<AccountId>,409410 /// Collection limits.411 pub limits: CollectionLimits,412413 /// Collection permissions.414 #[version(2.., upper(Default::default()))]415 pub permissions: CollectionPermissions,416417 #[version(2.., upper(Default::default()))]418 pub flags: CollectionFlags,419420 #[version(..2)]421 pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,422423 #[version(..2)]424 pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,425426 #[version(..2)]427 pub meta_update_permission: MetaUpdatePermission,428}429430#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]431#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]432pub struct RpcCollectionFlags {433 /// Is collection is foreign.434 pub foreign: bool,435 /// Collection supports ERC721Metadata.436 pub erc721metadata: bool,437}438439/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version).440#[struct_versioning::versioned(version = 2, upper)]441#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]442#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]443pub struct RpcCollection<AccountId> {444 /// Collection owner account.445 pub owner: AccountId,446447 /// Collection mode.448 pub mode: CollectionMode,449450 /// Collection name.451 pub name: Vec<u16>,452453 /// Collection description.454 pub description: Vec<u16>,455456 /// Token prefix.457 pub token_prefix: Vec<u8>,458459 /// The state of sponsorship of the collection.460 pub sponsorship: SponsorshipState<AccountId>,461462 /// Collection limits.463 pub limits: CollectionLimits,464465 /// Collection permissions.466 pub permissions: CollectionPermissions,467468 /// Token property permissions.469 pub token_property_permissions: Vec<PropertyKeyPermission>,470471 /// Collection properties.472 pub properties: Vec<Property>,473474 /// Is collection read only.475 pub read_only: bool,476477 /// Extra collection flags478 #[version(2.., upper(RpcCollectionFlags {foreign: false, erc721metadata: false}))]479 pub flags: RpcCollectionFlags,480}481482/// Data used for create collection.483///484/// All fields are wrapped in [`Option`], where `None` means chain default.485#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]486#[derivative(Debug, Default(bound = ""))]487pub struct CreateCollectionData<AccountId> {488 /// Collection mode.489 #[derivative(Default(value = "CollectionMode::NFT"))]490 pub mode: CollectionMode,491492 /// Access mode.493 pub access: Option<AccessMode>,494495 /// Collection name.496 pub name: CollectionName,497498 /// Collection description.499 pub description: CollectionDescription,500501 /// Token prefix.502 pub token_prefix: CollectionTokenPrefix,503504 /// Pending collection sponsor.505 pub pending_sponsor: Option<AccountId>,506507 /// Collection limits.508 pub limits: Option<CollectionLimits>,509510 /// Collection permissions.511 pub permissions: Option<CollectionPermissions>,512513 /// Token property permissions.514 pub token_property_permissions: CollectionPropertiesPermissionsVec,515516 /// Collection properties.517 pub properties: CollectionPropertiesVec,518}519520/// Bounded vector of properties permissions. Max length is [`MAX_PROPERTIES_PER_ITEM`].521// TODO: maybe rename to PropertiesPermissionsVec522pub type CollectionPropertiesPermissionsVec =523 BoundedVec<PropertyKeyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;524525/// Bounded vector of properties. Max length is [`MAX_PROPERTIES_PER_ITEM`].526pub type CollectionPropertiesVec = BoundedVec<Property, ConstU32<MAX_PROPERTIES_PER_ITEM>>;527528/// Limits and restrictions of a collection.529///530/// All fields are wrapped in [`Option`], where `None` means chain default.531///532/// Update with `pallet_common::Pallet::clamp_limits`.533// IMPORTANT: When adding/removing fields from this struct - don't forget to also534#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]535#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]536// When adding/removing fields from this struct - don't forget to also update with `pallet_common::Pallet::clamp_limits`.537// TODO: move `pallet_common::Pallet::clamp_limits` into `impl CollectionLimits`.538// TODO: may be remove [`Option`] and **pub** from fields and create struct with default values.539pub struct CollectionLimits {540 /// How many tokens can a user have on one account.541 /// * Default - [`ACCOUNT_TOKEN_OWNERSHIP_LIMIT`].542 /// * Limit - [`MAX_TOKEN_OWNERSHIP`].543 pub account_token_ownership_limit: Option<u32>,544545 /// How many bytes of data are available for sponsorship.546 /// * Default - [`CUSTOM_DATA_LIMIT`].547 /// * Limit - [`CUSTOM_DATA_LIMIT`].548 pub sponsored_data_size: Option<u32>,549550 // FIXME should we delete this or repurpose it?551 /// Times in how many blocks we sponsor data.552 ///553 /// If is `Some(v)` then **setVariableMetadata** is sponsored if there is `v` block between transactions.554 ///555 /// * Default - [`SponsoringDisabled`](SponsoringRateLimit::SponsoringDisabled).556 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].557 ///558 /// In any case, chain default: [`SponsoringRateLimit::SponsoringDisabled`]559 pub sponsored_data_rate_limit: Option<SponsoringRateLimit>,560 /// Maximum amount of tokens inside the collection. Chain default: [`COLLECTION_TOKEN_LIMIT`]561562 /// How many tokens can be mined into this collection.563 ///564 /// * Default - [`COLLECTION_TOKEN_LIMIT`].565 /// * Limit - [`COLLECTION_TOKEN_LIMIT`].566 pub token_limit: Option<u32>,567568 /// Timeouts for transfer sponsoring.569 ///570 /// * Default571 /// - **Fungible** - [`FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT`]572 /// - **NFT** - [`NFT_SPONSOR_TRANSFER_TIMEOUT`]573 /// - **Refungible** - [`REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT`]574 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].575 pub sponsor_transfer_timeout: Option<u32>,576577 /// Timeout for sponsoring an approval in passed blocks.578 ///579 /// * Default - [`SPONSOR_APPROVE_TIMEOUT`].580 /// * Limit - [`MAX_SPONSOR_TIMEOUT`].581 pub sponsor_approve_timeout: Option<u32>,582583 /// Whether the collection owner of the collection can send tokens (which belong to other users).584 ///585 /// * Default - **false**.586 pub owner_can_transfer: Option<bool>,587588 /// Can the collection owner burn other people's tokens.589 ///590 /// * Default - **true**.591 pub owner_can_destroy: Option<bool>,592593 /// Is it possible to send tokens from this collection between users.594 ///595 /// * Default - **true**.596 pub transfers_enabled: Option<bool>,597}598599impl CollectionLimits {600 pub fn with_default_limits(collection_type: CollectionMode) -> Self {601 CollectionLimits {602 account_token_ownership_limit: Some(ACCOUNT_TOKEN_OWNERSHIP_LIMIT),603 sponsored_data_size: Some(CUSTOM_DATA_LIMIT),604 sponsored_data_rate_limit: Some(SponsoringRateLimit::SponsoringDisabled),605 token_limit: Some(COLLECTION_TOKEN_LIMIT),606 sponsor_transfer_timeout: match collection_type {607 CollectionMode::NFT => Some(NFT_SPONSOR_TRANSFER_TIMEOUT),608 CollectionMode::ReFungible => Some(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),609 CollectionMode::Fungible(_) => Some(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),610 },611 sponsor_approve_timeout: Some(SPONSOR_APPROVE_TIMEOUT),612 owner_can_transfer: Some(false),613 owner_can_destroy: Some(true),614 transfers_enabled: Some(true),615 }616 }617618 /// Get effective value for [`account_token_ownership_limit`](self.account_token_ownership_limit).619 pub fn account_token_ownership_limit(&self) -> u32 {620 self.account_token_ownership_limit621 .unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)622 .min(MAX_TOKEN_OWNERSHIP)623 }624625 /// Get effective value for [`sponsored_data_size`](self.sponsored_data_size).626 pub fn sponsored_data_size(&self) -> u32 {627 self.sponsored_data_size628 .unwrap_or(CUSTOM_DATA_LIMIT)629 .min(CUSTOM_DATA_LIMIT)630 }631632 /// Get effective value for [`token_limit`](self.token_limit).633 pub fn token_limit(&self) -> u32 {634 self.token_limit635 .unwrap_or(COLLECTION_TOKEN_LIMIT)636 .min(COLLECTION_TOKEN_LIMIT)637 }638639 // TODO: may be replace u32 to mode?640 /// Get effective value for [`sponsor_transfer_timeout`](self.sponsor_transfer_timeout).641 pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {642 self.sponsor_transfer_timeout643 .unwrap_or(default)644 .min(MAX_SPONSOR_TIMEOUT)645 }646647 /// Get effective value for [`sponsor_approve_timeout`](self.sponsor_approve_timeout).648 pub fn sponsor_approve_timeout(&self) -> u32 {649 self.sponsor_approve_timeout650 .unwrap_or(SPONSOR_APPROVE_TIMEOUT)651 .min(MAX_SPONSOR_TIMEOUT)652 }653654 /// Get effective value for [`owner_can_transfer`](self.owner_can_transfer).655 pub fn owner_can_transfer(&self) -> bool {656 self.owner_can_transfer.unwrap_or(false)657 }658659 /// Get effective value for [`owner_can_transfer_instaled`](self.owner_can_transfer_instaled).660 pub fn owner_can_transfer_instaled(&self) -> bool {661 self.owner_can_transfer.is_some()662 }663664 /// Get effective value for [`owner_can_destroy`](self.owner_can_destroy).665 pub fn owner_can_destroy(&self) -> bool {666 self.owner_can_destroy.unwrap_or(true)667 }668669 /// Get effective value for [`transfers_enabled`](self.transfers_enabled).670 pub fn transfers_enabled(&self) -> bool {671 self.transfers_enabled.unwrap_or(true)672 }673674 /// Get effective value for [`sponsored_data_rate_limit`](self.sponsored_data_rate_limit).675 pub fn sponsored_data_rate_limit(&self) -> Option<u32> {676 match self677 .sponsored_data_rate_limit678 .unwrap_or(SponsoringRateLimit::SponsoringDisabled)679 {680 SponsoringRateLimit::SponsoringDisabled => None,681 SponsoringRateLimit::Blocks(v) => Some(v.min(MAX_SPONSOR_TIMEOUT)),682 }683 }684}685686/// Permissions on certain operations within a collection.687///688/// Some fields are wrapped in [`Option`], where `None` means chain default.689///690/// Update with `pallet_common::Pallet::clamp_permissions`.691#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo, MaxEncodedLen)]692#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]693// When adding/removing fields from this struct - don't forget to also update `pallet_common::Pallet::clamp_permissions`.694// TODO: move `pallet_common::Pallet::clamp_permissions` into `impl CollectionPermissions`.695pub struct CollectionPermissions {696 /// Access mode.697 ///698 /// * Default - [`AccessMode::Normal`].699 pub access: Option<AccessMode>,700701 /// Minting allowance.702 ///703 /// * Default - **false**.704 pub mint_mode: Option<bool>,705706 /// Permissions for nesting.707 ///708 /// * Default709 /// - `token_owner` - **false**710 /// - `collection_admin` - **false**711 /// - `restricted` - **None**712 pub nesting: Option<NestingPermissions>,713}714715impl CollectionPermissions {716 /// Get effective value for [`access`](self.access).717 pub fn access(&self) -> AccessMode {718 self.access.unwrap_or(AccessMode::Normal)719 }720721 /// Get effective value for [`mint_mode`](self.mint_mode).722 pub fn mint_mode(&self) -> bool {723 self.mint_mode.unwrap_or(false)724 }725726 /// Get effective value for [`nesting`](self.nesting).727 pub fn nesting(&self) -> &NestingPermissions {728 static DEFAULT: NestingPermissions = NestingPermissions {729 token_owner: false,730 collection_admin: false,731 restricted: None,732 #[cfg(feature = "runtime-benchmarks")]733 permissive: false,734 };735 self.nesting.as_ref().unwrap_or(&DEFAULT)736 }737}738739/// Inner set for collections allowed to nest.740type OwnerRestrictedSetInner = BoundedBTreeSet<CollectionId, ConstU32<16>>;741742/// Wraper for collections set allowing nest.743#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]744#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]745#[derivative(Debug)]746pub struct OwnerRestrictedSet(747 #[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]748 #[derivative(Debug(format_with = "bounded::set_debug"))]749 pub OwnerRestrictedSetInner,750);751752impl OwnerRestrictedSet {753 /// Create new set.754 pub fn new() -> Self {755 Self(Default::default())756 }757}758impl core::ops::Deref for OwnerRestrictedSet {759 type Target = OwnerRestrictedSetInner;760 fn deref(&self) -> &Self::Target {761 &self.0762 }763}764impl core::ops::DerefMut for OwnerRestrictedSet {765 fn deref_mut(&mut self) -> &mut Self::Target {766 &mut self.0767 }768}769770/// Part of collection permissions, if set, defines who is able to nest tokens into other tokens.771#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)]772#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]773#[derivative(Debug)]774pub struct NestingPermissions {775 /// Owner of token can nest tokens under it.776 pub token_owner: bool,777 /// Admin of token collection can nest tokens under token.778 pub collection_admin: bool,779 /// If set - only tokens from specified collections can be nested.780 pub restricted: Option<OwnerRestrictedSet>,781782 #[cfg(feature = "runtime-benchmarks")]783 /// Anyone can nest tokens, mutually exclusive with `token_owner`, `admin`.784 pub permissive: bool,785}786787/// Enum denominating how often can sponsoring occur if it is enabled.788///789/// Used for [`collection limits`](CollectionLimits).790#[derive(Encode, Decode, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]791#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]792pub enum SponsoringRateLimit {793 /// Sponsoring is disabled, and the collection sponsor will not pay for transactions794 SponsoringDisabled,795 /// Once per how many blocks can sponsorship of a transaction type occur796 Blocks(u32),797}798799/// Data used to describe an NFT at creation.800#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]801#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]802#[derivative(Debug)]803pub struct CreateNftData {804 /// Key-value pairs used to describe the token as metadata805 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]806 #[derivative(Debug(format_with = "bounded::vec_debug"))]807 /// Properties that wil be assignet to created item.808 pub properties: CollectionPropertiesVec,809}810811/// Data used to describe a Fungible token at creation.812#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq, TypeInfo)]813#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]814pub struct CreateFungibleData {815 /// Number of fungible coins minted816 pub value: u128,817}818819/// Data used to describe a Refungible token at creation.820#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]821#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]822#[derivative(Debug)]823pub struct CreateReFungibleData {824 /// Number of pieces the RFT is split into825 pub pieces: u128,826827 /// Key-value pairs used to describe the token as metadata828 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]829 #[derivative(Debug(format_with = "bounded::vec_debug"))]830 pub properties: CollectionPropertiesVec,831}832833// TODO: remove this.834#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]835#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]836pub enum MetaUpdatePermission {837 ItemOwner,838 Admin,839 None,840}841842/// Enum holding data used for creation of all three item types.843/// Unified data for create item.844#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]845#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]846pub enum CreateItemData {847 /// Data for create NFT.848 NFT(CreateNftData),849 /// Data for create Fungible item.850 Fungible(CreateFungibleData),851 /// Data for create ReFungible item.852 ReFungible(CreateReFungibleData),853}854855/// Extended data for create NFT.856#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]857#[derivative(Debug)]858pub struct CreateNftExData<CrossAccountId> {859 /// Properties that wil be assignet to created item.860 #[derivative(Debug(format_with = "bounded::vec_debug"))]861 pub properties: CollectionPropertiesVec,862863 /// Owner of creating item.864 pub owner: CrossAccountId,865}866867/// Extended data for create ReFungible item.868#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]869#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]870pub struct CreateRefungibleExMultipleOwners<CrossAccountId> {871 #[derivative(Debug(format_with = "bounded::map_debug"))]872 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,873 #[derivative(Debug(format_with = "bounded::vec_debug"))]874 pub properties: CollectionPropertiesVec,875}876877/// Extended data for create ReFungible item.878#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]879#[derivative(Debug(bound = "CrossAccountId: fmt::Debug"))]880pub struct CreateRefungibleExSingleOwner<CrossAccountId> {881 pub user: CrossAccountId,882 pub pieces: u128,883 #[derivative(Debug(format_with = "bounded::vec_debug"))]884 pub properties: CollectionPropertiesVec,885}886887/// Unified extended data for creating item.888#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]889#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]890pub enum CreateItemExData<CrossAccountId> {891 /// Extended data for create NFT.892 NFT(893 #[derivative(Debug(format_with = "bounded::vec_debug"))]894 BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,895 ),896897 /// Extended data for create Fungible item.898 Fungible(899 #[derivative(Debug(format_with = "bounded::map_debug"))]900 BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,901 ),902903 /// Extended data for create ReFungible item in case of904 /// many tokens, each may have only one owner905 RefungibleMultipleItems(906 #[derivative(Debug(format_with = "bounded::vec_debug"))]907 BoundedVec<CreateRefungibleExSingleOwner<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,908 ),909910 /// Extended data for create ReFungible item in case of911 /// single token, which may have many owners912 RefungibleMultipleOwners(CreateRefungibleExMultipleOwners<CrossAccountId>),913}914915impl From<CreateNftData> for CreateItemData {916 fn from(item: CreateNftData) -> Self {917 CreateItemData::NFT(item)918 }919}920921impl From<CreateReFungibleData> for CreateItemData {922 fn from(item: CreateReFungibleData) -> Self {923 CreateItemData::ReFungible(item)924 }925}926927impl From<CreateFungibleData> for CreateItemData {928 fn from(item: CreateFungibleData) -> Self {929 CreateItemData::Fungible(item)930 }931}932933/// Token's address, dictated by its collection and token IDs.934#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]935#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]936// todo possibly rename to be used generally as an address pair937pub struct TokenChild {938 /// Token id.939 pub token: TokenId,940941 /// Collection id.942 pub collection: CollectionId,943}944945/// Collection statistics.946#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]947#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]948pub struct CollectionStats {949 /// Number of created items.950 pub created: u32,951952 /// Number of burned items.953 pub destroyed: u32,954955 /// Number of current items.956 pub alive: u32,957}958959/// This type works like [`PhantomData`] but supports generating _scale-info_ descriptions to generate node metadata.960#[derive(Encode, Decode, Clone, Debug)]961#[cfg_attr(feature = "std", derive(PartialEq))]962pub struct PhantomType<T>(core::marker::PhantomData<T>);963964impl<T: TypeInfo + 'static> TypeInfo for PhantomType<T> {965 type Identity = PhantomType<T>;966967 fn type_info() -> scale_info::Type {968 use scale_info::{969 Type, Path,970 build::{FieldsBuilder, UnnamedFields},971 form::MetaForm,972 type_params,973 };974 Type::builder()975 .path(Path::new("up_data_structs", "PhantomType"))976 .type_params(type_params!(T))977 .composite(978 <FieldsBuilder<MetaForm, UnnamedFields>>::default().field(|b| b.ty::<[T; 0]>()),979 )980 }981}982impl<T> MaxEncodedLen for PhantomType<T> {983 fn max_encoded_len() -> usize {984 0985 }986}987988/// Bounded vector of bytes.989pub type BoundedBytes<S> = BoundedVec<u8, S>;990991/// Extra properties for external collections.992pub type AuxPropertyValue = BoundedBytes<ConstU32<MAX_AUX_PROPERTY_VALUE_LENGTH>>;993994/// Property key.995pub type PropertyKey = BoundedBytes<ConstU32<MAX_PROPERTY_KEY_LENGTH>>;996997/// Property value.998pub type PropertyValue = BoundedBytes<ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;9991000/// Property permission.1001#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone, Default)]1002#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1003pub struct PropertyPermission {1004 /// Permission to change the property and property permission.1005 ///1006 /// If it **false** then you can not change corresponding property even if [`collection_admin`] and [`token_owner`] are **true**.1007 pub mutable: bool,10081009 /// Change permission for the collection administrator.1010 pub collection_admin: bool,10111012 /// Permission to change the property for the owner of the token.1013 pub token_owner: bool,1014}10151016impl PropertyPermission {1017 /// Creates mutable property permission but changes restricted for collection admin and token owner.1018 pub fn none() -> Self {1019 Self {1020 mutable: true,1021 collection_admin: false,1022 token_owner: false,1023 }1024 }1025}10261027/// Property is simpl key-value record.1028#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]1029#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1030pub struct Property {1031 /// Property key.1032 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1033 pub key: PropertyKey,10341035 /// Property value.1036 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1037 pub value: PropertyValue,1038}10391040impl Into<(PropertyKey, PropertyValue)> for Property {1041 fn into(self) -> (PropertyKey, PropertyValue) {1042 (self.key, self.value)1043 }1044}10451046/// Record for proprty key permission.1047#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]1048#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]1049pub struct PropertyKeyPermission {1050 /// Key.1051 #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]1052 pub key: PropertyKey,10531054 /// Permission.1055 pub permission: PropertyPermission,1056}10571058impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {1059 fn into(self) -> (PropertyKey, PropertyPermission) {1060 (self.key, self.permission)1061 }1062}10631064/// Errors for properties actions.1065#[derive(Debug)]1066pub enum PropertiesError {1067 /// The space allocated for properties has run out.1068 ///1069 /// * Limit for colection - [`MAX_COLLECTION_PROPERTIES_SIZE`].1070 /// * Limit for token - [`MAX_TOKEN_PROPERTIES_SIZE`].1071 NoSpaceForProperty,10721073 /// The property limit has been reached.1074 ///1075 /// * Limit - [`MAX_PROPERTIES_PER_ITEM`].1076 PropertyLimitReached,10771078 /// Property key contains not allowed character.1079 InvalidCharacterInPropertyKey,10801081 /// Property key length is too long.1082 ///1083 /// * Limit - [`MAX_PROPERTY_KEY_LENGTH`].1084 PropertyKeyIsTooLong,10851086 /// Property key is empty.1087 EmptyPropertyKey,1088}10891090/// Token owner error: it could be either `NotFound` ot `MultipleOwners`.1091#[derive(Debug)]1092pub enum TokenOwnerError {1093 NotFound,1094 MultipleOwners,1095}10961097/// Marker for scope of property.1098///1099/// Scoped property can't be changed by user. Used for external collections.1100#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Clone, Copy)]1101pub enum PropertyScope {1102 None,1103 Rmrk,1104}11051106impl PropertyScope {1107 pub fn prefix(&self) -> &'static [u8] {1108 match self {1109 Self::None => b"",1110 Self::Rmrk => b"rmrk:",1111 }1112 }1113 /// Apply scope to property key.1114 pub fn apply(self, key: PropertyKey) -> Result<PropertyKey, PropertiesError> {1115 let prefix = self.prefix();1116 if prefix == b"" {1117 return Ok(key);1118 }1119 [prefix, key.as_slice()]1120 .concat()1121 .try_into()1122 .map_err(|_| PropertiesError::PropertyKeyIsTooLong)1123 }1124}11251126/// Trait for operate with properties.1127pub trait TrySetProperty: Sized {1128 type Value;11291130 /// Try to set property with scope.1131 fn try_scoped_set(1132 &mut self,1133 scope: PropertyScope,1134 key: PropertyKey,1135 value: Self::Value,1136 ) -> Result<Option<Self::Value>, PropertiesError>;11371138 /// Try to set property with scope from iterator.1139 fn try_scoped_set_from_iter<I, KV>(1140 &mut self,1141 scope: PropertyScope,1142 iter: I,1143 ) -> Result<(), PropertiesError>1144 where1145 I: Iterator<Item = KV>,1146 KV: Into<(PropertyKey, Self::Value)>,1147 {1148 for kv in iter {1149 let (key, value) = kv.into();1150 self.try_scoped_set(scope, key, value)?;1151 }11521153 Ok(())1154 }11551156 /// Try to set property.1157 fn try_set(1158 &mut self,1159 key: PropertyKey,1160 value: Self::Value,1161 ) -> Result<Option<Self::Value>, PropertiesError> {1162 self.try_scoped_set(PropertyScope::None, key, value)1163 }11641165 /// Try to set property from iterator.1166 fn try_set_from_iter<I, KV>(&mut self, iter: I) -> Result<(), PropertiesError>1167 where1168 I: Iterator<Item = KV>,1169 KV: Into<(PropertyKey, Self::Value)>,1170 {1171 self.try_scoped_set_from_iter(PropertyScope::None, iter)1172 }1173}11741175/// Wrapped map for storing properties.1176#[derive(Encode, Decode, TypeInfo, Derivative, Clone, PartialEq, MaxEncodedLen)]1177#[derivative(Default(bound = ""))]1178pub struct PropertiesMap<Value>(1179 BoundedBTreeMap<PropertyKey, Value, ConstU32<MAX_PROPERTIES_PER_ITEM>>,1180);11811182impl<Value> PropertiesMap<Value> {1183 /// Create new property map.1184 pub fn new() -> Self {1185 Self(BoundedBTreeMap::new())1186 }11871188 /// Remove property from map.1189 pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<Value>, PropertiesError> {1190 Self::check_property_key(key)?;11911192 Ok(self.0.remove(key))1193 }11941195 /// Get property with appropriate key from map.1196 pub fn get(&self, key: &PropertyKey) -> Option<&Value> {1197 self.0.get(key)1198 }11991200 /// Check if map contains key.1201 pub fn contains_key(&self, key: &PropertyKey) -> bool {1202 self.0.contains_key(key)1203 }12041205 /// Check if map contains key with key validation.1206 fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {1207 if key.is_empty() {1208 return Err(PropertiesError::EmptyPropertyKey);1209 }12101211 for byte in key.as_slice().iter() {1212 let byte = *byte;12131214 if !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' && byte != b'.' {1215 return Err(PropertiesError::InvalidCharacterInPropertyKey);1216 }1217 }12181219 Ok(())1220 }12211222 pub fn values(&self) -> impl Iterator<Item = &Value> {1223 self.0.values()1224 }12251226 pub fn iter(&self) -> impl Iterator<Item = (&PropertyKey, &Value)> {1227 self.0.iter()1228 }1229}12301231impl<Value> IntoIterator for PropertiesMap<Value> {1232 type Item = (PropertyKey, Value);1233 type IntoIter = <1234 BoundedBTreeMap<1235 PropertyKey,1236 Value,1237 ConstU32<MAX_PROPERTIES_PER_ITEM>1238 > as IntoIterator1239 >::IntoIter;12401241 fn into_iter(self) -> Self::IntoIter {1242 self.0.into_iter()1243 }1244}12451246impl<Value> TrySetProperty for PropertiesMap<Value> {1247 type Value = Value;12481249 fn try_scoped_set(1250 &mut self,1251 scope: PropertyScope,1252 key: PropertyKey,1253 value: Self::Value,1254 ) -> Result<Option<Self::Value>, PropertiesError> {1255 Self::check_property_key(&key)?;12561257 let key = scope.apply(key)?;1258 self.01259 .try_insert(key, value)1260 .map_err(|_| PropertiesError::PropertyLimitReached)1261 }1262}12631264/// Alias for property permissions map.1265pub type PropertiesPermissionMap = PropertiesMap<PropertyPermission>;12661267fn slice_size(data: &[u8]) -> u32 {1268 scoped_slice_size(PropertyScope::None, data)1269}1270fn scoped_slice_size(scope: PropertyScope, data: &[u8]) -> u32 {1271 use codec::Compact;1272 let prefix = scope.prefix();1273 <Compact<u32>>::encoded_size(&Compact(data.len() as u32 + prefix.len() as u32)) as u321274 + data.len() as u321275 + prefix.len() as u321276}12771278/// Wrapper for properties map with consumed space control.1279#[derive(Encode, Decode, TypeInfo, Clone, PartialEq)]1280pub struct Properties<const S: u32> {1281 map: PropertiesMap<PropertyValue>,1282 consumed_space: u32,1283 // May be not zero, previously served as a current S generic1284 _reserved: u32,1285}12861287impl<const S: u32> MaxEncodedLen for Properties<S> {1288 fn max_encoded_len() -> usize {1289 // len of map + len of consumed_space + len of space_limit1290 u32::max_encoded_len() * 3 + S as usize1291 }1292}12931294impl<const S: u32> Default for Properties<S> {1295 fn default() -> Self {1296 Self::new()1297 }1298}12991300impl<const S: u32> Properties<S> {1301 /// Create new properies container.1302 pub fn new() -> Self {1303 Self {1304 map: PropertiesMap::new(),1305 consumed_space: 0,1306 _reserved: 0,1307 }1308 }13091310 /// Remove propery with appropiate key.1311 pub fn remove(&mut self, key: &PropertyKey) -> Result<Option<PropertyValue>, PropertiesError> {1312 let value = self.map.remove(key)?;13131314 if let Some(ref value) = value {1315 let kv_len = slice_size(key) + slice_size(value);1316 self.consumed_space = self.consumed_space.saturating_sub(kv_len);1317 }13181319 Ok(value)1320 }13211322 /// Get property with appropriate key.1323 pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {1324 self.map.get(key)1325 }13261327 /// Recomputes the consumed space for the current properties state.1328 /// Needed to repair a token due to a bug fixed in the [PR #733](https://github.com/UniqueNetwork/unique-chain/pull/773).1329 pub fn recompute_consumed_space(&mut self) {1330 self.consumed_space = self1331 .map1332 .iter()1333 .map(|(key, value)| slice_size(key) + slice_size(value))1334 .sum();1335 }1336}13371338impl<const S: u32> IntoIterator for Properties<S> {1339 type Item = (PropertyKey, PropertyValue);1340 type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;13411342 fn into_iter(self) -> Self::IntoIter {1343 self.map.into_iter()1344 }1345}13461347impl<const S: u32> TrySetProperty for Properties<S> {1348 type Value = PropertyValue;13491350 fn try_scoped_set(1351 &mut self,1352 scope: PropertyScope,1353 key: PropertyKey,1354 value: Self::Value,1355 ) -> Result<Option<Self::Value>, PropertiesError> {1356 let key_size = scoped_slice_size(scope, &key);1357 let value_size = slice_size(&value) as u32;13581359 if self.consumed_space + value_size + key_size > S && !cfg!(feature = "runtime-benchmarks")1360 {1361 return Err(PropertiesError::NoSpaceForProperty);1362 }13631364 let old_value = self.map.try_scoped_set(scope, key, value)?;13651366 if let Some(old_value) = old_value.as_ref() {1367 let old_value_size = slice_size(&old_value);1368 self.consumed_space = self.consumed_space.saturating_sub(old_value_size) + value_size;1369 } else {1370 self.consumed_space += key_size + value_size;1371 }13721373 Ok(old_value)1374 }1375}13761377pub type CollectionProperties = Properties<MAX_COLLECTION_PROPERTIES_SIZE>;1378pub type TokenProperties = Properties<MAX_TOKEN_PROPERTIES_SIZE>;