difftreelog
refactor combine sponsorship fields
in: master
5 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,8 +193,7 @@
mint_mode: false,
offchain_schema: vec![],
schema_version: SchemaVersion::default(),
- sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
- sponsor_confirmed: true,
+ sponsorship: SponsorshipState::Confirmed(get_account_id_from_seed::<sr25519::Public>("Alice")),
const_on_chain_schema: vec![],
variable_on_chain_schema: vec![],
limits: CollectionLimits::default()
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, IsSubType,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial, DispatchClass,29 },30 StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71 Invalid,72 NFT,73 // decimal points74 Fungible(DecimalPoints),75 ReFungible,76}7778impl Default for CollectionMode {79 fn default() -> Self {80 Self::Invalid81 }82}8384impl Into<u8> for CollectionMode {85 fn into(self) -> u8 {86 match self {87 CollectionMode::Invalid => 0,88 CollectionMode::NFT => 1,89 CollectionMode::Fungible(_) => 2,90 CollectionMode::ReFungible => 3,91 }92 }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98 Normal,99 WhiteList,100}101impl Default for AccessMode {102 fn default() -> Self {103 Self::Normal104 }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110 ImageURL,111 Unique,112}113impl Default for SchemaVersion {114 fn default() -> Self {115 Self::ImageURL116 }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122 pub owner: AccountId,123 pub fraction: u128,124}125126#[derive(Encode, Decode, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub enum SponsorshipState<AccountId> {129 /// The fees are applied to the transaction sender130 Disabled,131 Unconfirmed(AccountId),132 /// Transactions are sponsored by specified account133 Confirmed(AccountId),134}135136impl<AccountId> SponsorshipState<AccountId> {137 fn sponsor(&self) -> Option<&AccountId> {138 match self {139 Self::Confirmed(sponsor) => Some(sponsor),140 _ => None,141 }142 }143144 fn pending_sponsor(&self) -> Option<&AccountId> {145 match self {146 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),147 _ => None,148 }149 }150151 fn confirmed(&self) -> bool {152 matches!(self, Self::Confirmed(_))153 }154}155156impl<T> Default for SponsorshipState<T> {157 fn default() -> Self {158 Self::Disabled159 }160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct CollectionType<AccountId> {165 pub owner: AccountId,166 pub mode: CollectionMode,167 pub access: AccessMode,168 pub decimal_points: DecimalPoints,169 pub name: Vec<u16>, // 64 include null escape char170 pub description: Vec<u16>, // 256 include null escape char171 pub token_prefix: Vec<u8>, // 16 include null escape char172 pub mint_mode: bool,173 pub offchain_schema: Vec<u8>,174 pub schema_version: SchemaVersion,175 pub sponsorship: SponsorshipState<AccountId>,176 pub limits: CollectionLimits, // Collection private restrictions 177 pub variable_on_chain_schema: Vec<u8>, //178 pub const_on_chain_schema: Vec<u8>, //179}180181#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]182#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]183pub struct NftItemType<AccountId> {184 pub owner: AccountId,185 pub const_data: Vec<u8>,186 pub variable_data: Vec<u8>,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct FungibleItemType {192 pub value: u128,193}194195#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]196#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]197pub struct ReFungibleItemType<AccountId> {198 pub owner: Vec<Ownership<AccountId>>,199 pub const_data: Vec<u8>,200 pub variable_data: Vec<u8>,201}202203// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]204// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]205// pub struct VestingItem<AccountId, Moment> {206// pub sender: AccountId,207// pub recipient: AccountId,208// pub collection_id: CollectionId,209// pub item_id: TokenId,210// pub amount: u64,211// pub vesting_date: Moment,212// }213214#[derive(Encode, Decode, Debug, Clone, PartialEq)]215#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]216pub struct CollectionLimits {217 pub account_token_ownership_limit: u32,218 pub sponsored_data_size: u32,219 pub token_limit: u32,220221 // Timeouts for item types in passed blocks222 pub sponsor_transfer_timeout: u32,223 pub owner_can_transfer: bool,224 pub owner_can_destroy: bool,225}226227impl Default for CollectionLimits {228 fn default() -> CollectionLimits {229 CollectionLimits { 230 account_token_ownership_limit: 10_000_000, 231 token_limit: u32::max_value(),232 sponsored_data_size: u32::MAX,233 sponsor_transfer_timeout: 14400,234 owner_can_transfer: true,235 owner_can_destroy: true236 }237 }238}239240#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]241#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]242pub struct ChainLimits {243 pub collection_numbers_limit: u32,244 pub account_token_ownership_limit: u32,245 pub collections_admins_limit: u64,246 pub custom_data_limit: u32,247248 // Timeouts for item types in passed blocks249 pub nft_sponsor_transfer_timeout: u32,250 pub fungible_sponsor_transfer_timeout: u32,251 pub refungible_sponsor_transfer_timeout: u32,252253 // Schema limits254 pub offchain_schema_limit: u32,255 pub variable_on_chain_schema_limit: u32,256 pub const_on_chain_schema_limit: u32,257}258259pub trait WeightInfo {260 fn create_collection() -> Weight;261 fn destroy_collection() -> Weight;262 fn add_to_white_list() -> Weight;263 fn remove_from_white_list() -> Weight;264 fn set_public_access_mode() -> Weight;265 fn set_mint_permission() -> Weight;266 fn change_collection_owner() -> Weight;267 fn add_collection_admin() -> Weight;268 fn remove_collection_admin() -> Weight;269 fn set_collection_sponsor() -> Weight;270 fn confirm_sponsorship() -> Weight;271 fn remove_collection_sponsor() -> Weight;272 fn create_item(s: usize) -> Weight;273 fn burn_item() -> Weight;274 fn transfer() -> Weight;275 fn approve() -> Weight;276 fn transfer_from() -> Weight;277 fn set_offchain_schema() -> Weight;278 fn set_const_on_chain_schema() -> Weight;279 fn set_variable_on_chain_schema() -> Weight;280 fn set_variable_meta_data() -> Weight;281 fn enable_contract_sponsoring() -> Weight;282 fn set_schema_version() -> Weight;283 fn set_chain_limits() -> Weight;284 fn set_contract_sponsoring_rate_limit() -> Weight;285 fn toggle_contract_white_list() -> Weight;286 fn add_to_contract_white_list() -> Weight;287 fn remove_from_contract_white_list() -> Weight;288 fn set_collection_limits() -> Weight;289}290291#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]292#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]293pub struct CreateNftData {294 pub const_data: Vec<u8>,295 pub variable_data: Vec<u8>,296}297298#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]299#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]300pub struct CreateFungibleData {301 pub value: u128,302}303304#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]305#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]306pub struct CreateReFungibleData {307 pub const_data: Vec<u8>,308 pub variable_data: Vec<u8>,309 pub pieces: u128,310}311312#[derive(Encode, Decode, Debug, Clone, PartialEq)]313#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]314pub enum CreateItemData {315 NFT(CreateNftData),316 Fungible(CreateFungibleData),317 ReFungible(CreateReFungibleData),318}319320impl CreateItemData {321 pub fn len(&self) -> usize {322 let len = match self {323 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),324 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),325 _ => 0326 };327 328 return len;329 }330}331332impl From<CreateNftData> for CreateItemData {333 fn from(item: CreateNftData) -> Self {334 CreateItemData::NFT(item)335 }336}337338impl From<CreateReFungibleData> for CreateItemData {339 fn from(item: CreateReFungibleData) -> Self {340 CreateItemData::ReFungible(item)341 }342}343344impl From<CreateFungibleData> for CreateItemData {345 fn from(item: CreateFungibleData) -> Self {346 CreateItemData::Fungible(item)347 }348}349350351decl_error! {352 /// Error for non-fungible-token module.353 pub enum Error for Module<T: Config> {354 /// Total collections bound exceeded.355 TotalCollectionsLimitExceeded,356 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.357 CollectionDecimalPointLimitExceeded, 358 /// Collection name can not be longer than 63 char.359 CollectionNameLimitExceeded, 360 /// Collection description can not be longer than 255 char.361 CollectionDescriptionLimitExceeded, 362 /// Token prefix can not be longer than 15 char.363 CollectionTokenPrefixLimitExceeded,364 /// This collection does not exist.365 CollectionNotFound,366 /// Item not exists.367 TokenNotFound,368 /// Admin not found369 AdminNotFound,370 /// Arithmetic calculation overflow.371 NumOverflow, 372 /// Account already has admin role.373 AlreadyAdmin, 374 /// You do not own this collection.375 NoPermission,376 /// This address is not set as sponsor, use setCollectionSponsor first.377 ConfirmUnsetSponsorFail,378 /// Collection is not in mint mode.379 PublicMintingNotAllowed,380 /// Sender parameter and item owner must be equal.381 MustBeTokenOwner,382 /// Item balance not enough.383 TokenValueTooLow,384 /// Size of item is too large.385 NftSizeLimitExceeded,386 /// No approve found387 ApproveNotFound,388 /// Requested value more than approved.389 TokenValueNotEnough,390 /// Only approved addresses can call this method.391 ApproveRequired,392 /// Address is not in white list.393 AddresNotInWhiteList,394 /// Number of collection admins bound exceeded.395 CollectionAdminsLimitExceeded,396 /// Owned tokens by a single address bound exceeded.397 AddressOwnershipLimitExceeded,398 /// Length of items properties must be greater than 0.399 EmptyArgument,400 /// const_data exceeded data limit.401 TokenConstDataLimitExceeded,402 /// variable_data exceeded data limit.403 TokenVariableDataLimitExceeded,404 /// Not NFT item data used to mint in NFT collection.405 NotNftDataUsedToMintNftCollectionToken,406 /// Not Fungible item data used to mint in Fungible collection.407 NotFungibleDataUsedToMintFungibleCollectionToken,408 /// Not Re Fungible item data used to mint in Re Fungible collection.409 NotReFungibleDataUsedToMintReFungibleCollectionToken,410 /// Unexpected collection type.411 UnexpectedCollectionType,412 /// Can't store metadata in fungible tokens.413 CantStoreMetadataInFungibleTokens,414 /// Collection token limit exceeded415 CollectionTokenLimitExceeded,416 /// Account token limit exceeded per collection417 AccountTokenLimitExceeded,418 /// Collection limit bounds per collection exceeded419 CollectionLimitBoundsExceeded,420 /// Tried to enable permissions which are only permitted to be disabled421 OwnerPermissionsCantBeReverted,422 /// Schema data size limit bound exceeded423 SchemaDataLimitExceeded,424 /// Maximum refungibility exceeded425 WrongRefungiblePieces426 }427}428429pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {430 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;431432 /// Weight information for extrinsics in this pallet.433 type WeightInfo: WeightInfo;434}435436#[cfg(feature = "runtime-benchmarks")]437mod benchmarking;438439// #endregion440441// # Used definitions442//443// ## User control levels444//445// chain-controlled - key is uncontrolled by user446// i.e autoincrementing index447// can use non-cryptographic hash448// real - key is controlled by user449// but it is hard to generate enough colliding values, i.e owner of signed txs450// can use non-cryptographic hash451// controlled - key is completly controlled by users452// i.e maps with mutable keys453// should use cryptographic hash454//455// ## User control level downgrade reasons456//457// ?1 - chain-controlled -> controlled458// collections/tokens can be destroyed, resulting in massive holes459// ?2 - chain-controlled -> controlled460// same as ?1, but can be only added, resulting in easier exploitation461// ?3 - real -> controlled462// no confirmation required, so addresses can be easily generated463decl_storage! {464 trait Store for Module<T: Config> as Nft {465466 //#region Private members467 /// Id of next collection468 CreatedCollectionCount: u32;469 /// Used for migrations470 ChainVersion: u64;471 /// Id of last collection token472 /// Collection id (controlled?1)473 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;474 //#endregion475476 //#region Chain limits struct477 pub ChainLimit get(fn chain_limit) config(): ChainLimits;478 //#endregion479480 //#region Bound counters481 /// Amount of collections destroyed, used for total amount tracking with482 /// CreatedCollectionCount483 DestroyedCollectionCount: u32;484 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)485 /// Account id (real)486 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;487 //#endregion488489 //#region Basic collections490 /// Collection info491 /// Collection id (controlled?1)492 pub Collection get(fn collection) config(): map hasher(blake2_128_concat) CollectionId => CollectionType<T::AccountId>;493 /// List of collection admins494 /// Collection id (controlled?2)495 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;496 /// Whitelisted collection users497 /// Collection id (controlled?2), user id (controlled?3)498 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;499 //#endregion500501 /// How many of collection items user have502 /// Collection id (controlled?2), account id (real)503 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;504505 /// Amount of items which spender can transfer out of owners account (via transferFrom)506 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))507 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;508509 //#region Item collections510 /// Collection id (controlled?2), token id (controlled?1)511 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => NftItemType<T::AccountId>;512 /// Collection id (controlled?2), owner (controlled?2)513 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;514 /// Collection id (controlled?2), token id (controlled?1)515 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => ReFungibleItemType<T::AccountId>;516 //#endregion517518 //#region Index list519 /// Collection id (controlled?2), tokens owner (controlled?2)520 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;521 //#endregion522523 //#region Tokens transfer rate limit baskets524 /// (Collection id (controlled?2), who created (real))525 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;526 /// Collection id (controlled?2), token id (controlled?2)527 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;528 /// Collection id (controlled?2), owning user (real)529 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;530 /// Collection id (controlled?2), token id (controlled?2)531 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;532 //#endregion533534 //#region Contract Sponsorship and Ownership535 /// Contract address (real)536 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;537 /// Contract address (real)538 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;539 /// (Contract address(real), caller (real))540 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;541 /// Contract address (real)542 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;543 /// Contract address (real)544 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 545 /// Contract address (real) => Whitelisted user (controlled?3)546 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 547 //#endregion548 }549 add_extra_genesis {550 build(|config: &GenesisConfig<T>| {551 // Modification of storage552 for (_num, _c) in &config.collection {553 <Module<T>>::init_collection(_c);554 }555556 for (_num, _c, _i) in &config.nft_item_id {557 <Module<T>>::init_nft_token(*_c, _i);558 }559560 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {561 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);562 }563564 for (_num, _c, _i) in &config.refungible_item_id {565 <Module<T>>::init_refungible_token(*_c, _i);566 }567 })568 }569}570571decl_event!(572 pub enum Event<T>573 where574 AccountId = <T as system::Config>::AccountId,575 {576 /// New collection was created577 /// 578 /// # Arguments579 /// 580 /// * collection_id: Globally unique identifier of newly created collection.581 /// 582 /// * mode: [CollectionMode] converted into u8.583 /// 584 /// * account_id: Collection owner.585 Created(CollectionId, u8, AccountId),586587 /// New item was created.588 /// 589 /// # Arguments590 /// 591 /// * collection_id: Id of the collection where item was created.592 /// 593 /// * item_id: Id of an item. Unique within the collection.594 ///595 /// * recipient: Owner of newly created item 596 ItemCreated(CollectionId, TokenId, AccountId),597598 /// Collection item was burned.599 /// 600 /// # Arguments601 /// 602 /// collection_id.603 /// 604 /// item_id: Identifier of burned NFT.605 ItemDestroyed(CollectionId, TokenId),606607 /// Item was transferred608 ///609 /// * collection_id: Id of collection to which item is belong610 ///611 /// * item_id: Id of an item612 ///613 /// * sender: Original owner of item614 ///615 /// * recipient: New owner of item616 ///617 /// * amount: Always 1 for NFT618 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),619 }620);621622decl_module! {623 pub struct Module<T: Config> for enum Call 624 where 625 origin: T::Origin626 {627 fn deposit_event() = default;628 type Error = Error<T>;629630 fn on_initialize(now: T::BlockNumber) -> Weight {631 0632 }633634 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.635 /// 636 /// # Permissions637 /// 638 /// * Anyone.639 /// 640 /// # Arguments641 /// 642 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.643 /// 644 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.645 /// 646 /// * token_prefix: UTF-8 string with token prefix.647 /// 648 /// * mode: [CollectionMode] collection type and type dependent data.649 // returns collection ID650 #[weight = <T as Config>::WeightInfo::create_collection()]651 pub fn create_collection(origin,652 collection_name: Vec<u16>,653 collection_description: Vec<u16>,654 token_prefix: Vec<u8>,655 mode: CollectionMode) -> DispatchResult {656657 // Anyone can create a collection658 let who = ensure_signed(origin)?;659660 let decimal_points = match mode {661 CollectionMode::Fungible(points) => points,662 _ => 0663 };664665 let chain_limit = ChainLimit::get();666667 let created_count = CreatedCollectionCount::get();668 let destroyed_count = DestroyedCollectionCount::get();669670 // bound Total number of collections671 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);672673 // check params674 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);675 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);676 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);677 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);678679 // Generate next collection ID680 let next_id = created_count681 .checked_add(1)682 .ok_or(Error::<T>::NumOverflow)?;683684 CreatedCollectionCount::put(next_id);685686 let limits = CollectionLimits {687 sponsored_data_size: chain_limit.custom_data_limit,688 ..Default::default()689 };690691 // Create new collection692 let new_collection = CollectionType {693 owner: who.clone(),694 name: collection_name,695 mode: mode.clone(),696 mint_mode: false,697 access: AccessMode::Normal,698 description: collection_description,699 decimal_points: decimal_points,700 token_prefix: token_prefix,701 offchain_schema: Vec::new(),702 schema_version: SchemaVersion::ImageURL,703 sponsorship: SponsorshipState::Disabled,704 variable_on_chain_schema: Vec::new(),705 const_on_chain_schema: Vec::new(),706 limits,707 };708709 // Add new collection to map710 <Collection<T>>::insert(next_id, new_collection);711712 // call event713 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));714715 Ok(())716 }717718 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.719 /// 720 /// # Permissions721 /// 722 /// * Collection Owner.723 /// 724 /// # Arguments725 /// 726 /// * collection_id: collection to destroy.727 #[weight = <T as Config>::WeightInfo::destroy_collection()]728 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {729730 let sender = ensure_signed(origin)?;731 Self::check_owner_permissions(collection_id, sender)?;732733 let target_collection = <Collection<T>>::get(collection_id);734 if !target_collection.limits.owner_can_destroy {735 fail!(Error::<T>::NoPermission);736 }737738 <AddressTokens<T>>::remove_prefix(collection_id);739 <Allowances<T>>::remove_prefix(collection_id);740 <Balance<T>>::remove_prefix(collection_id);741 <ItemListIndex>::remove(collection_id);742 <AdminList<T>>::remove(collection_id);743 <Collection<T>>::remove(collection_id);744 <WhiteList<T>>::remove_prefix(collection_id);745746 <NftItemList<T>>::remove_prefix(collection_id);747 <FungibleItemList<T>>::remove_prefix(collection_id);748 <ReFungibleItemList<T>>::remove_prefix(collection_id);749750 <NftTransferBasket<T>>::remove_prefix(collection_id);751 <FungibleTransferBasket<T>>::remove_prefix(collection_id);752 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);753754 DestroyedCollectionCount::put(DestroyedCollectionCount::get()755 .checked_add(1)756 .ok_or(Error::<T>::NumOverflow)?);757758 Ok(())759 }760761 /// Add an address to white list.762 /// 763 /// # Permissions764 /// 765 /// * Collection Owner766 /// * Collection Admin767 /// 768 /// # Arguments769 /// 770 /// * collection_id.771 /// 772 /// * address.773 #[weight = <T as Config>::WeightInfo::add_to_white_list()]774 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{775776 let sender = ensure_signed(origin)?;777 Self::check_owner_or_admin_permissions(collection_id, sender)?;778779 <WhiteList<T>>::insert(collection_id, address, true);780 781 Ok(())782 }783784 /// Remove an address from white list.785 /// 786 /// # Permissions787 /// 788 /// * Collection Owner789 /// * Collection Admin790 /// 791 /// # Arguments792 /// 793 /// * collection_id.794 /// 795 /// * address.796 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]797 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{798799 let sender = ensure_signed(origin)?;800 Self::check_owner_or_admin_permissions(collection_id, sender)?;801802 <WhiteList<T>>::remove(collection_id, address);803804 Ok(())805 }806807 /// Toggle between normal and white list access for the methods with access for `Anyone`.808 /// 809 /// # Permissions810 /// 811 /// * Collection Owner.812 /// 813 /// # Arguments814 /// 815 /// * collection_id.816 /// 817 /// * mode: [AccessMode]818 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]819 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult820 {821 let sender = ensure_signed(origin)?;822823 Self::check_owner_permissions(collection_id, sender)?;824 let mut target_collection = <Collection<T>>::get(collection_id);825 target_collection.access = mode;826 <Collection<T>>::insert(collection_id, target_collection);827828 Ok(())829 }830831 /// Allows Anyone to create tokens if:832 /// * White List is enabled, and833 /// * Address is added to white list, and834 /// * This method was called with True parameter835 /// 836 /// # Permissions837 /// * Collection Owner838 ///839 /// # Arguments840 /// 841 /// * collection_id.842 /// 843 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.844 #[weight = <T as Config>::WeightInfo::set_mint_permission()]845 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult846 {847 let sender = ensure_signed(origin)?;848849 Self::check_owner_permissions(collection_id, sender)?;850 let mut target_collection = <Collection<T>>::get(collection_id);851 target_collection.mint_mode = mint_permission;852 <Collection<T>>::insert(collection_id, target_collection);853854 Ok(())855 }856857 /// Change the owner of the collection.858 /// 859 /// # Permissions860 /// 861 /// * Collection Owner.862 /// 863 /// # Arguments864 /// 865 /// * collection_id.866 /// 867 /// * new_owner.868 #[weight = <T as Config>::WeightInfo::change_collection_owner()]869 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {870871 let sender = ensure_signed(origin)?;872 Self::check_owner_permissions(collection_id, sender)?;873 let mut target_collection = <Collection<T>>::get(collection_id);874 target_collection.owner = new_owner;875 <Collection<T>>::insert(collection_id, target_collection);876877 Ok(())878 }879880 /// Adds an admin of the Collection.881 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 882 /// 883 /// # Permissions884 /// 885 /// * Collection Owner.886 /// * Collection Admin.887 /// 888 /// # Arguments889 /// 890 /// * collection_id: ID of the Collection to add admin for.891 /// 892 /// * new_admin_id: Address of new admin to add.893 #[weight = <T as Config>::WeightInfo::add_collection_admin()]894 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {895896 let sender = ensure_signed(origin)?;897 Self::check_owner_or_admin_permissions(collection_id, sender)?;898 let mut admin_arr: Vec<T::AccountId> = Vec::new();899900 if <AdminList<T>>::contains_key(collection_id)901 {902 admin_arr = <AdminList<T>>::get(collection_id);903 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);904 }905906 // Number of collection admins907 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);908909 admin_arr.push(new_admin_id);910 <AdminList<T>>::insert(collection_id, admin_arr);911912 Ok(())913 }914915 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.916 ///917 /// # Permissions918 /// 919 /// * Collection Owner.920 /// * Collection Admin.921 /// 922 /// # Arguments923 /// 924 /// * collection_id: ID of the Collection to remove admin for.925 /// 926 /// * account_id: Address of admin to remove.927 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]928 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {929930 let sender = ensure_signed(origin)?;931 Self::check_owner_or_admin_permissions(collection_id, sender)?;932 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);933934 let mut admin_arr = <AdminList<T>>::get(collection_id);935 admin_arr.retain(|i| *i != account_id);936 <AdminList<T>>::insert(collection_id, admin_arr);937938 Ok(())939 }940941 /// # Permissions942 /// 943 /// * Collection Owner944 /// 945 /// # Arguments946 /// 947 /// * collection_id.948 /// 949 /// * new_sponsor.950 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]951 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {952953 let sender = ensure_signed(origin)?;954 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);955956 let mut target_collection = <Collection<T>>::get(collection_id);957 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);958959 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);960 <Collection<T>>::insert(collection_id, target_collection);961962 Ok(())963 }964965 /// # Permissions966 /// 967 /// * Sponsor.968 /// 969 /// # Arguments970 /// 971 /// * collection_id.972 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]973 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {974975 let sender = ensure_signed(origin)?;976 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);977978 let mut target_collection = <Collection<T>>::get(collection_id);979 ensure!(980 target_collection.sponsorship.pending_sponsor() == Some(&sender),981 Error::<T>::ConfirmUnsetSponsorFail982 );983984 target_collection.sponsorship = SponsorshipState::Confirmed(sender);985 <Collection<T>>::insert(collection_id, target_collection);986987 Ok(())988 }989990 /// Switch back to pay-per-own-transaction model.991 ///992 /// # Permissions993 ///994 /// * Collection owner.995 /// 996 /// # Arguments997 /// 998 /// * collection_id.999 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1000 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {10011002 let sender = ensure_signed(origin)?;1003 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);10041005 let mut target_collection = <Collection<T>>::get(collection_id);1006 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);10071008 target_collection.sponsorship = SponsorshipState::Disabled;1009 <Collection<T>>::insert(collection_id, target_collection);10101011 Ok(())1012 }10131014 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1015 /// 1016 /// # Permissions1017 /// 1018 /// * Collection Owner.1019 /// * Collection Admin.1020 /// * Anyone if1021 /// * White List is enabled, and1022 /// * Address is added to white list, and1023 /// * MintPermission is enabled (see SetMintPermission method)1024 /// 1025 /// # Arguments1026 /// 1027 /// * collection_id: ID of the collection.1028 /// 1029 /// * owner: Address, initial owner of the NFT.1030 ///1031 /// * data: Token data to store on chain.1032 // #[weight =1033 // (130_000_000 as Weight)1034 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1035 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1036 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]10371038 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1039 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {10401041 let sender = ensure_signed(origin)?;10421043 Self::collection_exists(collection_id)?;10441045 let target_collection = <Collection<T>>::get(collection_id);10461047 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;1048 Self::validate_create_item_args(&target_collection, &data)?;1049 Self::create_item_no_validation(collection_id, owner, data)?;10501051 Ok(())1052 }10531054 /// This method creates multiple instances of NFT Collection created with CreateCollection method.1055 /// 1056 /// # Permissions1057 /// 1058 /// * Collection Owner.1059 /// * Collection Admin.1060 /// * Anyone if1061 /// * White List is enabled, and1062 /// * Address is added to white list, and1063 /// * MintPermission is enabled (see SetMintPermission method)1064 /// 1065 /// # Arguments1066 /// 1067 /// * collection_id: ID of the collection.1068 /// 1069 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1070 /// 1071 /// * owner: Address, initial owner of the NFT.1072 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1073 .map(|data| { data.len() })1074 .sum())]1075 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {10761077 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1078 let sender = ensure_signed(origin)?;10791080 Self::collection_exists(collection_id)?;1081 let target_collection = <Collection<T>>::get(collection_id);10821083 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;10841085 for data in &items_data {1086 Self::validate_create_item_args(&target_collection, data)?;1087 }1088 for data in &items_data {1089 Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;1090 }10911092 Ok(())1093 }10941095 /// Destroys a concrete instance of NFT.1096 /// 1097 /// # Permissions1098 /// 1099 /// * Collection Owner.1100 /// * Collection Admin.1101 /// * Current NFT Owner.1102 /// 1103 /// # Arguments1104 /// 1105 /// * collection_id: ID of the collection.1106 /// 1107 /// * item_id: ID of NFT to burn.1108 #[weight = <T as Config>::WeightInfo::burn_item()]1109 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {11101111 let sender = ensure_signed(origin)?;1112 Self::collection_exists(collection_id)?;11131114 // Transfer permissions check1115 let target_collection = <Collection<T>>::get(collection_id);1116 ensure!(1117 Self::is_item_owner(sender.clone(), collection_id, item_id) ||1118 (1119 target_collection.limits.owner_can_transfer &&1120 Self::is_owner_or_admin_permissions(collection_id, sender.clone())1121 ),1122 Error::<T>::NoPermission1123 );11241125 if target_collection.access == AccessMode::WhiteList {1126 Self::check_white_list(collection_id, &sender)?;1127 }11281129 match target_collection.mode1130 {1131 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1132 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1133 CollectionMode::ReFungible => Self::burn_refungible_item(collection_id, item_id, &sender)?,1134 _ => ()1135 };11361137 // call event1138 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));11391140 Ok(())1141 }11421143 /// Change ownership of the token.1144 /// 1145 /// # Permissions1146 /// 1147 /// * Collection Owner1148 /// * Collection Admin1149 /// * Current NFT owner1150 ///1151 /// # Arguments1152 /// 1153 /// * recipient: Address of token recipient.1154 /// 1155 /// * collection_id.1156 /// 1157 /// * item_id: ID of the item1158 /// * Non-Fungible Mode: Required.1159 /// * Fungible Mode: Ignored.1160 /// * Re-Fungible Mode: Required.1161 /// 1162 /// * value: Amount to transfer.1163 /// * Non-Fungible Mode: Ignored1164 /// * Fungible Mode: Must specify transferred amount1165 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1166 #[weight = <T as Config>::WeightInfo::transfer()]1167 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1168 let sender = ensure_signed(origin)?;1169 Self::transfer_internal(sender, recipient, collection_id, item_id, value)1170 }11711172 /// Set, change, or remove approved address to transfer the ownership of the NFT.1173 /// 1174 /// # Permissions1175 /// 1176 /// * Collection Owner1177 /// * Collection Admin1178 /// * Current NFT owner1179 /// 1180 /// # Arguments1181 /// 1182 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1183 /// 1184 /// * collection_id.1185 /// 1186 /// * item_id: ID of the item.1187 #[weight = <T as Config>::WeightInfo::approve()]1188 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {11891190 let sender = ensure_signed(origin)?;11911192 Self::collection_exists(collection_id)?;1193 Self::token_exists(collection_id, item_id, &sender)?;11941195 // Transfer permissions check1196 let target_collection = <Collection<T>>::get(collection_id);1197 let allowance_limit = if target_collection.limits.owner_can_transfer &&1198 Self::is_owner_or_admin_permissions(1199 collection_id,1200 sender.clone(),1201 ) {1202 None1203 } else if let Some(amount) = Self::owned_amount(1204 sender.clone(),1205 collection_id,1206 item_id,1207 ) {1208 Some(amount)1209 } else {1210 fail!(Error::<T>::NoPermission);1211 };12121213 if target_collection.access == AccessMode::WhiteList {1214 Self::check_white_list(collection_id, &sender)?;1215 Self::check_white_list(collection_id, &spender)?;1216 }12171218 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1219 let mut allowance: u128 = amount;1220 if allowance_exists {1221 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1222 }1223 if let Some(limit) = allowance_limit {1224 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1225 }1226 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);12271228 Ok(())1229 }1230 1231 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1232 /// 1233 /// # Permissions1234 /// * Collection Owner1235 /// * Collection Admin1236 /// * Current NFT owner1237 /// * Address approved by current NFT owner1238 /// 1239 /// # Arguments1240 /// 1241 /// * from: Address that owns token.1242 /// 1243 /// * recipient: Address of token recipient.1244 /// 1245 /// * collection_id.1246 /// 1247 /// * item_id: ID of the item.1248 /// 1249 /// * value: Amount to transfer.1250 #[weight = <T as Config>::WeightInfo::transfer_from()]1251 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12521253 let sender = ensure_signed(origin)?;1254 let mut appoved_transfer = false;12551256 // Check approval1257 let mut approval: u128 = 0;1258 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1259 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1260 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1261 appoved_transfer = true;1262 }12631264 let target_collection = <Collection<T>>::get(collection_id);12651266 // Limits check1267 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;12681269 // Transfer permissions check 1270 ensure!(1271 appoved_transfer || 1272 (1273 target_collection.limits.owner_can_transfer &&1274 Self::is_owner_or_admin_permissions(collection_id, sender.clone())1275 ),1276 Error::<T>::NoPermission1277 );12781279 if target_collection.access == AccessMode::WhiteList {1280 Self::check_white_list(collection_id, &sender)?;1281 Self::check_white_list(collection_id, &recipient)?;1282 }12831284 // Reduce approval by transferred amount or remove if remaining approval drops to 01285 if approval.checked_sub(value).unwrap_or(0) > 0 {1286 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1287 }1288 else {1289 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1290 }12911292 match target_collection.mode1293 {1294 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1295 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1296 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1297 _ => ()1298 };12991300 Ok(())1301 }13021303 // #[weight = 0]1304 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {13051306 // // let no_perm_mes = "You do not have permissions to modify this collection";1307 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1308 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1309 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13101311 // // // on_nft_received call13121313 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13141315 // Ok(())1316 // }13171318 /// Set off-chain data schema.1319 /// 1320 /// # Permissions1321 /// 1322 /// * Collection Owner1323 /// * Collection Admin1324 /// 1325 /// # Arguments1326 /// 1327 /// * collection_id.1328 /// 1329 /// * schema: String representing the offchain data schema.1330 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1331 pub fn set_variable_meta_data (1332 origin,1333 collection_id: CollectionId,1334 item_id: TokenId,1335 data: Vec<u8>1336 ) -> DispatchResult {1337 let sender = ensure_signed(origin)?;1338 1339 Self::collection_exists(collection_id)?;1340 Self::token_exists(collection_id, item_id, &sender)?;13411342 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);13431344 // Modify permissions check1345 let target_collection = <Collection<T>>::get(collection_id);1346 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1347 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1348 Error::<T>::NoPermission);13491350 match target_collection.mode1351 {1352 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1353 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1354 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1355 _ => fail!(Error::<T>::UnexpectedCollectionType)1356 };13571358 Ok(())1359 }1360 1361 /// Set schema standard1362 /// ImageURL1363 /// Unique1364 /// 1365 /// # Permissions1366 /// 1367 /// * Collection Owner1368 /// * Collection Admin1369 /// 1370 /// # Arguments1371 /// 1372 /// * collection_id.1373 /// 1374 /// * schema: SchemaVersion: enum1375 #[weight = <T as Config>::WeightInfo::set_schema_version()]1376 pub fn set_schema_version(1377 origin,1378 collection_id: CollectionId,1379 version: SchemaVersion1380 ) -> DispatchResult {1381 let sender = ensure_signed(origin)?;1382 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1383 let mut target_collection = <Collection<T>>::get(collection_id);1384 target_collection.schema_version = version;1385 <Collection<T>>::insert(collection_id, target_collection);13861387 Ok(())1388 }13891390 /// Set off-chain data schema.1391 /// 1392 /// # Permissions1393 /// 1394 /// * Collection Owner1395 /// * Collection Admin1396 /// 1397 /// # Arguments1398 /// 1399 /// * collection_id.1400 /// 1401 /// * schema: String representing the offchain data schema.1402 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1403 pub fn set_offchain_schema(1404 origin,1405 collection_id: CollectionId,1406 schema: Vec<u8>1407 ) -> DispatchResult {1408 let sender = ensure_signed(origin)?;1409 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14101411 // check schema limit1412 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14131414 let mut target_collection = <Collection<T>>::get(collection_id);1415 target_collection.offchain_schema = schema;1416 <Collection<T>>::insert(collection_id, target_collection);14171418 Ok(())1419 }14201421 /// Set const on-chain data schema.1422 /// 1423 /// # Permissions1424 /// 1425 /// * Collection Owner1426 /// * Collection Admin1427 /// 1428 /// # Arguments1429 /// 1430 /// * collection_id.1431 /// 1432 /// * schema: String representing the const on-chain data schema.1433 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1434 pub fn set_const_on_chain_schema (1435 origin,1436 collection_id: CollectionId,1437 schema: Vec<u8>1438 ) -> DispatchResult {1439 let sender = ensure_signed(origin)?;1440 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14411442 // check schema limit1443 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14441445 let mut target_collection = <Collection<T>>::get(collection_id);1446 target_collection.const_on_chain_schema = schema;1447 <Collection<T>>::insert(collection_id, target_collection);14481449 Ok(())1450 }14511452 /// Set variable on-chain data schema.1453 /// 1454 /// # Permissions1455 /// 1456 /// * Collection Owner1457 /// * Collection Admin1458 /// 1459 /// # Arguments1460 /// 1461 /// * collection_id.1462 /// 1463 /// * schema: String representing the variable on-chain data schema.1464 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1465 pub fn set_variable_on_chain_schema (1466 origin,1467 collection_id: CollectionId,1468 schema: Vec<u8>1469 ) -> DispatchResult {1470 let sender = ensure_signed(origin)?;1471 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14721473 // check schema limit1474 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14751476 let mut target_collection = <Collection<T>>::get(collection_id);1477 target_collection.variable_on_chain_schema = schema;1478 <Collection<T>>::insert(collection_id, target_collection);14791480 Ok(())1481 }14821483 // Sudo permissions function1484 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1485 pub fn set_chain_limits(1486 origin,1487 limits: ChainLimits1488 ) -> DispatchResult {14891490 #[cfg(not(feature = "runtime-benchmarks"))]1491 ensure_root(origin)?;14921493 <ChainLimit>::put(limits);1494 Ok(())1495 }14961497 /// Enable smart contract self-sponsoring.1498 /// 1499 /// # Permissions1500 /// 1501 /// * Contract Owner1502 /// 1503 /// # Arguments1504 /// 1505 /// * contract address1506 /// * enable flag1507 /// 1508 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1509 pub fn enable_contract_sponsoring(1510 origin,1511 contract_address: T::AccountId,1512 enable: bool1513 ) -> DispatchResult {15141515 let sender = ensure_signed(origin)?;15161517 #[cfg(feature = "runtime-benchmarks")]1518 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15191520 Self::ensure_contract_owned(sender, &contract_address)?;15211522 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1523 Ok(())1524 }15251526 /// Set the rate limit for contract sponsoring to specified number of blocks.1527 /// 1528 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1529 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1530 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1531 /// from contract endowment if there are at least B blocks between such transactions. 1532 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1533 /// 1534 /// # Permissions1535 /// 1536 /// * Contract Owner1537 /// 1538 /// # Arguments1539 /// 1540 /// -`contract_address`: Address of the contract to sponsor1541 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1542 /// 1543 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1544 pub fn set_contract_sponsoring_rate_limit(1545 origin,1546 contract_address: T::AccountId,1547 rate_limit: T::BlockNumber1548 ) -> DispatchResult {1549 let sender = ensure_signed(origin)?;15501551 #[cfg(feature = "runtime-benchmarks")]1552 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15531554 Self::ensure_contract_owned(sender, &contract_address)?;1555 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1556 Ok(())1557 }15581559 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1560 /// 1561 /// # Permissions1562 /// 1563 /// * Address that deployed smart contract.1564 /// 1565 /// # Arguments1566 /// 1567 /// -`contract_address`: Address of the contract.1568 /// 1569 /// - `enable`: . 1570 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1571 pub fn toggle_contract_white_list(1572 origin,1573 contract_address: T::AccountId,1574 enable: bool1575 ) -> DispatchResult {1576 let sender = ensure_signed(origin)?;15771578 #[cfg(feature = "runtime-benchmarks")]1579 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15801581 Self::ensure_contract_owned(sender, &contract_address)?;1582 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1583 Ok(())1584 }1585 1586 /// Add an address to smart contract white list.1587 /// 1588 /// # Permissions1589 /// 1590 /// * Address that deployed smart contract.1591 /// 1592 /// # Arguments1593 /// 1594 /// -`contract_address`: Address of the contract.1595 ///1596 /// -`account_address`: Address to add.1597 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1598 pub fn add_to_contract_white_list(1599 origin,1600 contract_address: T::AccountId,1601 account_address: T::AccountId1602 ) -> DispatchResult {1603 let sender = ensure_signed(origin)?;16041605 #[cfg(feature = "runtime-benchmarks")]1606 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1607 1608 Self::ensure_contract_owned(sender, &contract_address)?; 1609 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1610 Ok(())1611 }16121613 /// Remove an address from smart contract white list.1614 /// 1615 /// # Permissions1616 /// 1617 /// * Address that deployed smart contract.1618 /// 1619 /// # Arguments1620 /// 1621 /// -`contract_address`: Address of the contract.1622 ///1623 /// -`account_address`: Address to remove.1624 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1625 pub fn remove_from_contract_white_list(1626 origin,1627 contract_address: T::AccountId,1628 account_address: T::AccountId1629 ) -> DispatchResult {1630 let sender = ensure_signed(origin)?;16311632 #[cfg(feature = "runtime-benchmarks")]1633 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16341635 Self::ensure_contract_owned(sender, &contract_address)?;1636 <ContractWhiteList<T>>::remove(contract_address, account_address);1637 Ok(())1638 }16391640 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1641 pub fn set_collection_limits(1642 origin,1643 collection_id: u32,1644 new_limits: CollectionLimits,1645 ) -> DispatchResult {1646 let sender = ensure_signed(origin)?;1647 Self::check_owner_permissions(collection_id, sender.clone())?;1648 let mut target_collection = <Collection<T>>::get(collection_id);1649 let old_limits = target_collection.limits;1650 let chain_limits = ChainLimit::get();16511652 // collection bounds1653 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1654 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1655 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1656 Error::<T>::CollectionLimitBoundsExceeded);16571658 // token_limit check prev1659 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1660 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16611662 ensure!(1663 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1664 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1665 Error::<T>::OwnerPermissionsCantBeReverted,1666 );16671668 target_collection.limits = new_limits;1669 <Collection<T>>::insert(collection_id, target_collection);16701671 Ok(())1672 } 1673 }1674}16751676impl<T: Config> Module<T> {16771678 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {16791680 let target_collection = <Collection<T>>::get(collection_id);16811682 // Limits check1683 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;16841685 // Transfer permissions check1686 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1687 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1688 Error::<T>::NoPermission);16891690 if target_collection.access == AccessMode::WhiteList {1691 Self::check_white_list(collection_id, &sender)?;1692 Self::check_white_list(collection_id, &recipient)?;1693 }16941695 match target_collection.mode1696 {1697 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient.clone())?,1698 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1699 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient.clone())?,1700 _ => ()1701 };17021703 Self::deposit_event(RawEvent::Transfer(collection_id, item_id, sender, recipient, value));17041705 Ok(())1706 }170717081709 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {17101711 // check token limit and account token limit1712 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1713 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1714 1715 Ok(())1716 }17171718 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {17191720 // check token limit and account token limit1721 let total_items: u32 = ItemListIndex::get(collection_id);1722 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1723 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1724 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);17251726 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1727 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1728 Self::check_white_list(collection_id, owner)?;1729 Self::check_white_list(collection_id, sender)?;1730 }17311732 Ok(())1733 }17341735 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1736 match target_collection.mode1737 {1738 CollectionMode::NFT => {1739 if let CreateItemData::NFT(data) = data {1740 // check sizes1741 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1742 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1743 } else {1744 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1745 }1746 },1747 CollectionMode::Fungible(_) => {1748 if let CreateItemData::Fungible(_) = data {1749 } else {1750 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1751 }1752 },1753 CollectionMode::ReFungible => {1754 if let CreateItemData::ReFungible(data) = data {17551756 // check sizes1757 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1758 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);17591760 // Check refungibility limits1761 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1762 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1763 } else {1764 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1765 }1766 },1767 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1768 };17691770 Ok(())1771 }17721773 fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1774 match data1775 {1776 CreateItemData::NFT(data) => {1777 let item = NftItemType {1778 owner: owner.clone(),1779 const_data: data.const_data,1780 variable_data: data.variable_data1781 };17821783 Self::add_nft_item(collection_id, item)?;1784 },1785 CreateItemData::Fungible(data) => {1786 Self::add_fungible_item(collection_id, &owner, data.value)?;1787 },1788 CreateItemData::ReFungible(data) => {1789 let mut owner_list = Vec::new();1790 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});17911792 let item = ReFungibleItemType {1793 owner: owner_list,1794 const_data: data.const_data,1795 variable_data: data.variable_data1796 };17971798 Self::add_refungible_item(collection_id, item)?;1799 }1800 };18011802 // call event1803 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id), owner));18041805 Ok(())1806 }18071808 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {18091810 // Does new owner already have an account?1811 let mut balance: u128 = 0;1812 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1813 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1814 } 18151816 // Mint 1817 let item = FungibleItemType {1818 value: balance + value1819 };1820 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);18211822 // Update balance1823 let new_balance = <Balance<T>>::get(collection_id, owner)1824 .checked_add(value)1825 .ok_or(Error::<T>::NumOverflow)?;1826 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18271828 Ok(())1829 }18301831 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1832 let current_index = <ItemListIndex>::get(collection_id)1833 .checked_add(1)1834 .ok_or(Error::<T>::NumOverflow)?;1835 let itemcopy = item.clone();18361837 let value = item.owner.first().unwrap().fraction;1838 let owner = item.owner.first().unwrap().owner.clone();18391840 Self::add_token_index(collection_id, current_index, &owner)?;18411842 <ItemListIndex>::insert(collection_id, current_index);1843 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);18441845 // Update balance1846 let new_balance = <Balance<T>>::get(collection_id, &owner)1847 .checked_add(value)1848 .ok_or(Error::<T>::NumOverflow)?;1849 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);18501851 Ok(())1852 }18531854 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1855 let current_index = <ItemListIndex>::get(collection_id)1856 .checked_add(1)1857 .ok_or(Error::<T>::NumOverflow)?;18581859 let item_owner = item.owner.clone();1860 Self::add_token_index(collection_id, current_index, &item.owner)?;18611862 <ItemListIndex>::insert(collection_id, current_index);1863 <NftItemList<T>>::insert(collection_id, current_index, item);18641865 // Update balance1866 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1867 .checked_add(1)1868 .ok_or(Error::<T>::NumOverflow)?;1869 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);18701871 Ok(())1872 }18731874 fn burn_refungible_item(1875 collection_id: CollectionId,1876 item_id: TokenId,1877 owner: &T::AccountId,1878 ) -> DispatchResult {1879 ensure!(1880 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1881 Error::<T>::TokenNotFound1882 );1883 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1884 let rft_balance = token1885 .owner1886 .iter()1887 .filter(|&i| i.owner == *owner)1888 .next()1889 .unwrap();1890 Self::remove_token_index(collection_id, item_id, owner)?;18911892 // update balance1893 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1894 .checked_sub(rft_balance.fraction)1895 .ok_or(Error::<T>::NumOverflow)?;1896 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);18971898 // Re-create owners list with sender removed1899 let index = token1900 .owner1901 .iter()1902 .position(|i| i.owner == *owner)1903 .unwrap();1904 token.owner.remove(index);1905 let owner_count = token.owner.len();19061907 // Burn the token completely if this was the last (only) owner1908 if owner_count == 0 {1909 <ReFungibleItemList<T>>::remove(collection_id, item_id);1910 }1911 else {1912 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1913 }19141915 Ok(())1916 }19171918 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1919 ensure!(1920 <NftItemList<T>>::contains_key(collection_id, item_id),1921 Error::<T>::TokenNotFound1922 );1923 let item = <NftItemList<T>>::get(collection_id, item_id);1924 Self::remove_token_index(collection_id, item_id, &item.owner)?;19251926 // update balance1927 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1928 .checked_sub(1)1929 .ok_or(Error::<T>::NumOverflow)?;1930 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1931 <NftItemList<T>>::remove(collection_id, item_id);19321933 Ok(())1934 }19351936 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1937 ensure!(1938 <FungibleItemList<T>>::contains_key(collection_id, owner),1939 Error::<T>::TokenNotFound1940 );1941 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1942 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);19431944 // update balance1945 let new_balance = <Balance<T>>::get(collection_id, owner)1946 .checked_sub(value)1947 .ok_or(Error::<T>::NumOverflow)?;1948 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);19491950 if balance.value - value > 0 {1951 balance.value -= value;1952 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1953 }1954 else {1955 <FungibleItemList<T>>::remove(collection_id, owner);1956 }19571958 Ok(())1959 }19601961 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1962 ensure!(1963 <Collection<T>>::contains_key(collection_id),1964 Error::<T>::CollectionNotFound1965 );1966 Ok(())1967 }19681969 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1970 Self::collection_exists(collection_id)?;19711972 let target_collection = <Collection<T>>::get(collection_id);1973 ensure!(1974 subject == target_collection.owner,1975 Error::<T>::NoPermission1976 );19771978 Ok(())1979 }19801981 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1982 let target_collection = <Collection<T>>::get(collection_id);1983 let mut result: bool = subject == target_collection.owner;1984 let exists = <AdminList<T>>::contains_key(collection_id);19851986 if !result & exists {1987 if <AdminList<T>>::get(collection_id).contains(&subject) {1988 result = true1989 }1990 }19911992 result1993 }19941995 fn check_owner_or_admin_permissions(1996 collection_id: CollectionId,1997 subject: T::AccountId,1998 ) -> DispatchResult {1999 Self::collection_exists(collection_id)?;2000 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());20012002 ensure!(2003 result,2004 Error::<T>::NoPermission2005 );2006 Ok(())2007 }20082009 fn owned_amount(2010 subject: T::AccountId,2011 collection_id: CollectionId,2012 item_id: TokenId,2013 ) -> Option<u128> {2014 let target_collection = <Collection<T>>::get(collection_id);20152016 match target_collection.mode {2017 CollectionMode::NFT => {2018 if <NftItemList<T>>::get(collection_id, item_id).owner == subject {2019 return Some(1)2020 }2021 None2022 },2023 CollectionMode::Fungible(_) => {2024 if <FungibleItemList<T>>::contains_key(collection_id, &subject) {2025 return Some(<FungibleItemList<T>>::get(collection_id, &subject)2026 .value);2027 }2028 None2029 },2030 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)2031 .owner2032 .iter()2033 .find(|i| i.owner == subject)2034 .map(|i| i.fraction),2035 CollectionMode::Invalid => None,2036 }2037 }20382039 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {2040 let target_collection = <Collection<T>>::get(collection_id);20412042 match target_collection.mode {2043 CollectionMode::NFT => {2044 <NftItemList<T>>::get(collection_id, item_id).owner == subject2045 }2046 CollectionMode::Fungible(_) => {2047 <FungibleItemList<T>>::contains_key(collection_id, &subject)2048 }2049 CollectionMode::ReFungible => {2050 <ReFungibleItemList<T>>::get(collection_id, item_id)2051 .owner2052 .iter()2053 .any(|i| i.owner == subject)2054 }2055 CollectionMode::Invalid => false,2056 }2057 }20582059 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {2060 let mes = Error::<T>::AddresNotInWhiteList;2061 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);20622063 Ok(())2064 }20652066 /// Check if token exists. In case of Fungible, check if there is an entry for 2067 /// the owner in fungible balances double map2068 fn token_exists(2069 collection_id: CollectionId,2070 item_id: TokenId,2071 owner: &T::AccountId2072 ) -> DispatchResult {2073 let target_collection = <Collection<T>>::get(collection_id);2074 let exists = match target_collection.mode2075 {2076 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2077 CollectionMode::Fungible(_) => <FungibleItemList<T>>::contains_key(collection_id, owner),2078 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2079 _ => false2080 };20812082 ensure!(exists == true, Error::<T>::TokenNotFound);2083 Ok(())2084 }20852086 fn transfer_fungible(2087 collection_id: CollectionId,2088 value: u128,2089 owner: &T::AccountId,2090 recipient: &T::AccountId,2091 ) -> DispatchResult {2092 Self::token_exists(collection_id, 0, owner)?;20932094 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2095 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20962097 // Send balance to recipient (updates balanceOf of recipient)2098 Self::add_fungible_item(collection_id, recipient, value)?;20992100 // update balanceOf of sender2101 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);21022103 // Reduce or remove sender2104 if balance.value == value {2105 <FungibleItemList<T>>::remove(collection_id, owner);2106 }2107 else {2108 balance.value -= value;2109 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2110 }21112112 Ok(())2113 }21142115 fn transfer_refungible(2116 collection_id: CollectionId,2117 item_id: TokenId,2118 value: u128,2119 owner: T::AccountId,2120 new_owner: T::AccountId,2121 ) -> DispatchResult {2122 Self::token_exists(collection_id, item_id, &owner)?;21232124 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);2125 let item = full_item2126 .owner2127 .iter()2128 .filter(|i| i.owner == owner)2129 .next()2130 .ok_or(Error::<T>::NumOverflow)?;2131 let amount = item.fraction;21322133 ensure!(amount >= value, Error::<T>::TokenValueTooLow);21342135 // update balance2136 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2137 .checked_sub(value)2138 .ok_or(Error::<T>::NumOverflow)?;2139 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);21402141 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2142 .checked_add(value)2143 .ok_or(Error::<T>::NumOverflow)?;2144 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);21452146 let old_owner = item.owner.clone();2147 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21482149 // transfer2150 if amount == value && !new_owner_has_account {2151 // change owner2152 // new owner do not have account2153 let mut new_full_item = full_item.clone();2154 new_full_item2155 .owner2156 .iter_mut()2157 .find(|i| i.owner == owner)2158 .unwrap()2159 .owner = new_owner.clone();2160 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21612162 // update index collection2163 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2164 } else {2165 let mut new_full_item = full_item.clone();2166 new_full_item2167 .owner2168 .iter_mut()2169 .find(|i| i.owner == owner)2170 .unwrap()2171 .fraction -= value;21722173 // separate amount2174 if new_owner_has_account {2175 // new owner has account2176 new_full_item2177 .owner2178 .iter_mut()2179 .find(|i| i.owner == new_owner)2180 .unwrap()2181 .fraction += value;2182 } else {2183 // new owner do not have account2184 new_full_item.owner.push(Ownership {2185 owner: new_owner.clone(),2186 fraction: value,2187 });2188 Self::add_token_index(collection_id, item_id, &new_owner)?;2189 }21902191 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2192 }21932194 Ok(())2195 }21962197 fn transfer_nft(2198 collection_id: CollectionId,2199 item_id: TokenId,2200 sender: T::AccountId,2201 new_owner: T::AccountId,2202 ) -> DispatchResult {2203 Self::token_exists(collection_id, item_id, &sender)?;22042205 let mut item = <NftItemList<T>>::get(collection_id, item_id);22062207 ensure!(2208 sender == item.owner,2209 Error::<T>::MustBeTokenOwner2210 );22112212 // update balance2213 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2214 .checked_sub(1)2215 .ok_or(Error::<T>::NumOverflow)?;2216 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);22172218 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2219 .checked_add(1)2220 .ok_or(Error::<T>::NumOverflow)?;2221 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);22222223 // change owner2224 let old_owner = item.owner.clone();2225 item.owner = new_owner.clone();2226 <NftItemList<T>>::insert(collection_id, item_id, item);22272228 // update index collection2229 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;22302231 Ok(())2232 }2233 2234 fn set_re_fungible_variable_data(2235 collection_id: CollectionId,2236 item_id: TokenId,2237 data: Vec<u8>2238 ) -> DispatchResult {2239 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);22402241 item.variable_data = data;22422243 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);22442245 Ok(())2246 }22472248 fn set_nft_variable_data(2249 collection_id: CollectionId,2250 item_id: TokenId,2251 data: Vec<u8>2252 ) -> DispatchResult {2253 let mut item = <NftItemList<T>>::get(collection_id, item_id);2254 2255 item.variable_data = data;22562257 <NftItemList<T>>::insert(collection_id, item_id, item);2258 2259 Ok(())2260 }22612262 fn init_collection(item: &CollectionType<T::AccountId>) {2263 // check params2264 assert!(2265 item.decimal_points <= MAX_DECIMAL_POINTS,2266 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2267 );2268 assert!(2269 item.name.len() <= 64,2270 "Collection name can not be longer than 63 char"2271 );2272 assert!(2273 item.name.len() <= 256,2274 "Collection description can not be longer than 255 char"2275 );2276 assert!(2277 item.token_prefix.len() <= 16,2278 "Token prefix can not be longer than 15 char"2279 );22802281 // Generate next collection ID2282 let next_id = CreatedCollectionCount::get()2283 .checked_add(1)2284 .unwrap();22852286 CreatedCollectionCount::put(next_id);2287 }22882289 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2290 let current_index = <ItemListIndex>::get(collection_id)2291 .checked_add(1)2292 .unwrap();22932294 let item_owner = item.owner.clone();2295 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22962297 <ItemListIndex>::insert(collection_id, current_index);22982299 // Update balance2300 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2301 .checked_add(1)2302 .unwrap();2303 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2304 }23052306 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2307 let current_index = <ItemListIndex>::get(collection_id)2308 .checked_add(1)2309 .unwrap();23102311 Self::add_token_index(collection_id, current_index, owner).unwrap();23122313 <ItemListIndex>::insert(collection_id, current_index);23142315 // Update balance2316 let new_balance = <Balance<T>>::get(collection_id, owner)2317 .checked_add(item.value)2318 .unwrap();2319 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2320 }23212322 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2323 let current_index = <ItemListIndex>::get(collection_id)2324 .checked_add(1)2325 .unwrap();23262327 let value = item.owner.first().unwrap().fraction;2328 let owner = item.owner.first().unwrap().owner.clone();23292330 Self::add_token_index(collection_id, current_index, &owner).unwrap();23312332 <ItemListIndex>::insert(collection_id, current_index);23332334 // Update balance2335 let new_balance = <Balance<T>>::get(collection_id, &owner)2336 .checked_add(value)2337 .unwrap();2338 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2339 }23402341 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {23422343 // add to account limit2344 if <AccountItemCount<T>>::contains_key(owner) {23452346 // bound Owned tokens by a single address2347 let count = <AccountItemCount<T>>::get(owner);2348 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);23492350 <AccountItemCount<T>>::insert(owner.clone(), count2351 .checked_add(1)2352 .ok_or(Error::<T>::NumOverflow)?);2353 }2354 else {2355 <AccountItemCount<T>>::insert(owner.clone(), 1);2356 }23572358 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2359 if list_exists {2360 let mut list = <AddressTokens<T>>::get(collection_id, owner);2361 let item_contains = list.contains(&item_index.clone());23622363 if !item_contains {2364 list.push(item_index.clone());2365 }23662367 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2368 } else {2369 let mut itm = Vec::new();2370 itm.push(item_index.clone());2371 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2372 }23732374 Ok(())2375 }23762377 fn remove_token_index(2378 collection_id: CollectionId,2379 item_index: TokenId,2380 owner: &T::AccountId,2381 ) -> DispatchResult {23822383 // update counter2384 <AccountItemCount<T>>::insert(owner.clone(), 2385 <AccountItemCount<T>>::get(owner)2386 .checked_sub(1)2387 .ok_or(Error::<T>::NumOverflow)?);238823892390 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2391 if list_exists {2392 let mut list = <AddressTokens<T>>::get(collection_id, owner);2393 let item_contains = list.contains(&item_index.clone());23942395 if item_contains {2396 list.retain(|&item| item != item_index);2397 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2398 }2399 }24002401 Ok(())2402 }24032404 fn move_token_index(2405 collection_id: CollectionId,2406 item_index: TokenId,2407 old_owner: &T::AccountId,2408 new_owner: &T::AccountId,2409 ) -> DispatchResult {2410 Self::remove_token_index(collection_id, item_index, old_owner)?;2411 Self::add_token_index(collection_id, item_index, new_owner)?;24122413 Ok(())2414 }2415 2416 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2417 if <ContractOwner<T>>::contains_key(contract.clone()) {2418 let owner = <ContractOwner<T>>::get(contract);2419 ensure!(account == owner, Error::<T>::NoPermission);2420 } else {2421 fail!(Error::<T>::NoPermission);2422 }24232424 Ok(())2425 }2426}24272428////////////////////////////////////////////////////////////////////////////////////////////////////2429// Economic models2430// #region24312432/// Fee multiplier.2433pub type Multiplier = FixedU128;24342435type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;24362437/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2438/// in the queue.2439#[derive(Encode, Decode, Clone, Eq, PartialEq)]2440pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);24412442impl<T: Config + Send + Sync> sp_std::fmt::Debug 2443 for ChargeTransactionPayment<T>2444{2445 #[cfg(feature = "std")]2446 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2447 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2448 }2449 #[cfg(not(feature = "std"))]2450 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2451 Ok(())2452 }2453}24542455impl<T: Config> ChargeTransactionPayment<T>2456where2457 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2458 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2459 T::AccountId: AsRef<[u8]>,2460 T::AccountId: UncheckedFrom<T::Hash>,2461{2462 fn traditional_fee(2463 len: usize,2464 info: &DispatchInfoOf<T::Call>,2465 tip: BalanceOf<T>,2466 ) -> BalanceOf<T>2467 where2468 T::Call: Dispatchable<Info = DispatchInfo>,2469 {2470 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2471 }24722473 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2474 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2475 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2476 let len_saturation = max_block_length as u64 / (len as u64).max(1);2477 let coefficient: BalanceOf<T> = weight_saturation2478 .min(len_saturation)2479 .saturated_into::<BalanceOf<T>>();2480 final_fee2481 .saturating_mul(coefficient)2482 .saturated_into::<TransactionPriority>()2483 }24842485 fn withdraw_fee(2486 &self,2487 who: &T::AccountId,2488 call: &T::Call,2489 info: &DispatchInfoOf<T::Call>,2490 len: usize,2491 ) -> Result<2492 (2493 BalanceOf<T>,2494 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2495 ),2496 TransactionValidityError,2497 > {2498 let tip = self.0;24992500 // Set fee based on call type. Creating collection costs 1 Unique.2501 // All other transactions have traditional fees so far2502 // let fee = match call.is_sub_type() {2503 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2504 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2505 // // _ => <BalanceOf<T>>::from(100)2506 // };2507 let fee = Self::traditional_fee(len, info, tip);25082509 // Only mess with balances if fee is not zero.2510 if fee.is_zero() {2511 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2512 .map(|i| (fee, i));2513 }25142515 // Determine who is paying transaction fee based on ecnomic model2516 // Parse call to extract collection ID and access collection sponsor2517 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2518 Some(Call::create_item(collection_id, _owner, _properties)) => {25192520 // sponsor timeout2521 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;25222523 let collection = <Collection<T>>::get(collection_id);25242525 let limit = collection.limits.sponsor_transfer_timeout;2526 let mut sponsored = true;2527 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2528 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2529 let limit_time = last_tx_block + limit.into();2530 if block_number <= limit_time {2531 sponsored = false;2532 }2533 }2534 if sponsored {2535 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2536 }25372538 // check free create limit2539 if (collection.limits.sponsored_data_size >= (_properties.len() as u32)) &&2540 (sponsored)2541 {2542 collection.sponsorship.sponsor()2543 .cloned()2544 .unwrap_or_default()2545 } else {2546 T::AccountId::default()2547 }2548 }2549 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2550 2551 let mut sponsor_transfer = false;2552 if <Collection<T>>::get(collection_id).sponsorship.confirmed() {25532554 let collection_limits = <Collection<T>>::get(collection_id).limits;2555 let collection_mode = <Collection<T>>::get(collection_id).mode;2556 2557 // sponsor timeout2558 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2559 sponsor_transfer = match collection_mode {2560 CollectionMode::NFT => {2561 2562 // get correct limit2563 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2564 collection_limits.sponsor_transfer_timeout2565 } else {2566 ChainLimit::get().nft_sponsor_transfer_timeout2567 };2568 2569 let mut sponsored = true;2570 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2571 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2572 let limit_time = last_tx_block + limit.into();2573 if block_number <= limit_time {2574 sponsored = false;2575 }2576 }2577 if sponsored {2578 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2579 }25802581 sponsored2582 }2583 CollectionMode::Fungible(_) => {2584 2585 // get correct limit2586 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2587 collection_limits.sponsor_transfer_timeout2588 } else {2589 ChainLimit::get().fungible_sponsor_transfer_timeout2590 };2591 2592 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2593 let mut sponsored = true;2594 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2595 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2596 let limit_time = last_tx_block + limit.into();2597 if block_number <= limit_time {2598 sponsored = false;2599 }2600 }2601 if sponsored {2602 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2603 }26042605 sponsored2606 }2607 CollectionMode::ReFungible => {2608 2609 // get correct limit2610 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2611 collection_limits.sponsor_transfer_timeout2612 } else {2613 ChainLimit::get().refungible_sponsor_transfer_timeout2614 };2615 2616 let mut sponsored = true;2617 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2618 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2619 let limit_time = last_tx_block + limit.into();2620 if block_number <= limit_time {2621 sponsored = false;2622 }2623 }2624 if sponsored {2625 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2626 }26272628 sponsored2629 }2630 _ => {2631 false2632 },2633 };2634 }26352636 if !sponsor_transfer {2637 T::AccountId::default()2638 } else {2639 <Collection<T>>::get(collection_id).sponsorship.sponsor()2640 .cloned()2641 .unwrap_or_default()2642 }2643 }26442645 _ => T::AccountId::default(),2646 };26472648 // Sponsor smart contracts2649 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {26502651 // On instantiation: set the contract owner2652 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {26532654 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2655 &who,2656 code_hash,2657 salt,2658 );2659 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());26602661 T::AccountId::default()2662 },26632664 // On instantiation with code: set the contract owner2665 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {26662667 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2668 &who,2669 &T::Hashing::hash(&_code),2670 _salt,2671 );26722673 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());26742675 T::AccountId::default()2676 }26772678 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2679 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {26802681 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());26822683 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2684 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2685 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2686 2687 if !owned_contract && white_list_enabled {2688 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2689 return Err(InvalidTransaction::Call.into());2690 }2691 }26922693 let mut sponsor_transfer = false;2694 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2695 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2696 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2697 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2698 let limit_time = last_tx_block + rate_limit;26992700 if block_number >= limit_time {2701 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2702 sponsor_transfer = true;2703 }2704 } else {2705 sponsor_transfer = false;2706 }2707 2708 2709 let mut sp = T::AccountId::default();2710 if sponsor_transfer {2711 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2712 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2713 sp = called_contract;2714 }2715 }2716 }27172718 sp2719 },27202721 _ => sponsor,2722 };27232724 let mut who_pays_fee: T::AccountId = sponsor.clone();2725 if sponsor == T::AccountId::default() {2726 who_pays_fee = who.clone();2727 }27282729 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2730 .map(|i| (fee, i))2731 }2732}273327342735impl<T: Config + Send + Sync> SignedExtension2736 for ChargeTransactionPayment<T>2737where2738 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2739 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2740 T::AccountId: AsRef<[u8]>,2741 T::AccountId: UncheckedFrom<T::Hash>,2742{2743 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2744 type AccountId = T::AccountId;2745 type Call = T::Call;2746 type AdditionalSigned = ();2747 type Pre = (2748 // tip2749 BalanceOf<T>,2750 // who pays fee2751 Self::AccountId,2752 // imbalance resulting from withdrawing the fee2753 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2754 );2755 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2756 Ok(())2757 }27582759 fn validate(2760 &self,2761 who: &Self::AccountId,2762 call: &Self::Call,2763 info: &DispatchInfoOf<Self::Call>,2764 len: usize,2765 ) -> TransactionValidity {2766 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2767 Ok(ValidTransaction {2768 priority: Self::get_priority(len, info, fee),2769 ..Default::default()2770 })2771 }27722773 fn pre_dispatch(2774 self,2775 who: &Self::AccountId,2776 call: &Self::Call,2777 info: &DispatchInfoOf<Self::Call>,2778 len: usize,2779 ) -> Result<Self::Pre, TransactionValidityError> {2780 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2781 Ok((self.0, who.clone(), imbalance))2782 }27832784 fn post_dispatch(2785 pre: Self::Pre,2786 info: &DispatchInfoOf<Self::Call>,2787 post_info: &PostDispatchInfoOf<Self::Call>,2788 len: usize,2789 _result: &DispatchResult,2790 ) -> Result<(), TransactionValidityError> {2791 let (tip, who, imbalance) = pre;2792 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(2793 len as u32,2794 info,2795 post_info,2796 tip,2797 );2798 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2799 Ok(())2800 }2801}28022803// #endregionruntime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -31,6 +31,13 @@
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
},
+ "SponsorshipState": {
+ "_enum": {
+ "Disabled": null,
+ "Unconfirmed": "AccountId",
+ "Confirmed": "AccountId"
+ }
+ },
"CollectionType": {
"Owner": "AccountId",
"Mode": "CollectionMode",
@@ -42,8 +49,7 @@
"MintMode": "bool",
"OffchainSchema": "Vec<u8>",
"SchemaVersion": "SchemaVersion",
- "Sponsor": "AccountId",
- "SponsorConfirmed": "bool",
+ "Sponsorship": "SponsorshipState",
"Limits": "CollectionLimits",
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>"
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -26,6 +26,7 @@
"testSetVariableMetaData": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetaData.test.ts",
"testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",
"testRemoveCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionAdmin.test.ts",
+ "testRemoveCollectionSponsor": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionSponsor.test.ts",
"testRemoveFromWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromWhiteList.test.ts",
"testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts",
"testCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -396,8 +396,7 @@
// What to expect
expect(result.success).to.be.true;
- expect(collection.Sponsor).to.be.equal(nullPublicKey);
- expect(collection.SponsorConfirmed).to.be.false;
+ expect(collection.Sponsorship).to.be.deep.equal({ Disabled: null });
});
}