difftreelog
feat nest on create/transfer
in: master
12 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -723,17 +723,20 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: CreateItemData,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
fn create_multiple_items(
&self,
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: Vec<CreateItemData>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
fn create_multiple_items_ex(
&self,
sender: T::CrossAccountId,
data: CreateItemExData<T::CrossAccountId>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
fn burn_item(
&self,
@@ -748,6 +751,7 @@
to: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
fn approve(
&self,
@@ -781,11 +785,12 @@
data: BoundedVec<u8, CustomDataLimit>,
) -> DispatchResultWithPostInfo;
- fn nest_token(
+ fn check_nesting(
&self,
sender: T::CrossAccountId,
- from: (CollectionId, TokenId),
+ from: CollectionId,
under: TokenId,
+ budget: &dyn Budget,
) -> DispatchResult;
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
pallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -55,7 +55,6 @@
use frame_system::ensure_signed;
pub use frame_support::dispatch::DispatchResult;
- use frame_support::{pallet_prelude::*, traits::PalletInfo};
use frame_system::pallet_prelude::*;
/// DispatchError is opaque, but we need to somehow extract correct error in case of OutOfGas failure
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -17,7 +17,7 @@
use core::marker::PhantomData;
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
-use up_data_structs::{TokenId, CreateItemExData, budget::Budget};
+use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::ArithmeticError;
use sp_std::{vec::Vec, vec};
@@ -78,10 +78,11 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: up_data_structs::CreateItemData,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
match data {
up_data_structs::CreateItemData::Fungible(data) => with_weight(
- <Pallet<T>>::create_item(self, &sender, (to, data.value)),
+ <Pallet<T>>::create_item(self, &sender, (to, data.value), nesting_budget),
<CommonWeights<T>>::create_item(),
),
_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
@@ -93,6 +94,7 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: Vec<up_data_structs::CreateItemData>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let mut sum: u128 = 0;
for data in data {
@@ -107,7 +109,7 @@
}
with_weight(
- <Pallet<T>>::create_item(self, &sender, (to, sum)),
+ <Pallet<T>>::create_item(self, &sender, (to, sum), nesting_budget),
<CommonWeights<T>>::create_item(),
)
}
@@ -116,6 +118,7 @@
&self,
sender: <T>::CrossAccountId,
data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
let data = match data {
@@ -124,7 +127,7 @@
};
with_weight(
- <Pallet<T>>::create_multiple_items(self, &sender, data.into_inner()),
+ <Pallet<T>>::create_multiple_items(self, &sender, data.into_inner(), nesting_budget),
weight,
)
}
@@ -152,6 +155,7 @@
to: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
@@ -159,7 +163,7 @@
);
with_weight(
- <Pallet<T>>::transfer(self, &from, &to, amount),
+ <Pallet<T>>::transfer(self, &from, &to, amount, nesting_budget),
<CommonWeights<T>>::transfer(),
)
}
@@ -230,11 +234,12 @@
fail!(<Error<T>>::FungibleItemsDontHaveData)
}
- fn nest_token(
+ fn check_nesting(
&self,
_sender: <T>::CrossAccountId,
- _from: (up_data_structs::CollectionId, TokenId),
+ _from: CollectionId,
_under: TokenId,
+ _budget: &dyn Budget,
) -> sp_runtime::DispatchResult {
fail!(<Error<T>>::FungibleDisallowsNesting)
}
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -81,8 +81,11 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, amount).map_err(|_| "transfer error")?;
+ <Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
Ok(true)
}
#[weight(<SelfWeightOf<T>>::transfer_from())]
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -193,6 +193,7 @@
from: &T::CrossAccountId,
to: &T::CrossAccountId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
ensure!(
collection.limits.transfers_enabled(),
@@ -222,12 +223,12 @@
let handle = <CollectionHandle<T>>::try_get(target.0)?;
let dispatch = T::CollectionDispatch::dispatch(handle);
let dispatch = dispatch.as_dyn();
-
- // =========
- dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;
+ dispatch.check_nesting(from.clone(), collection.id, target.1, nesting_budget)?;
}
+ // =========
+
if let Some(balance_to) = balance_to {
// from != to
if balance_from == 0 {
@@ -257,6 +258,7 @@
collection: &FungibleHandle<T>,
sender: &T::CrossAccountId,
data: BTreeMap<T::CrossAccountId, u128>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
@@ -285,6 +287,16 @@
.ok_or(ArithmeticError::Overflow)?;
}
+ for (to, _) in balances.iter() {
+ if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
+ let handle = <CollectionHandle<T>>::try_get(target.0)?;
+ let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = dispatch.as_dyn();
+
+ dispatch.check_nesting(sender.clone(), collection.id, target.1, nesting_budget)?;
+ }
+ }
+
// =========
<TotalSupply<T>>::insert(collection.id, total_supply);
@@ -407,7 +419,7 @@
// =========
- Self::transfer(collection, from, to, amount)?;
+ Self::transfer(collection, from, to, amount, nesting_budget)?;
if let Some(allowance) = allowance {
Self::set_allowance_unchecked(collection, from, spender, allowance);
}
@@ -437,7 +449,13 @@
collection: &FungibleHandle<T>,
sender: &T::CrossAccountId,
data: CreateItemData<T>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
- Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())
+ Self::create_multiple_items(
+ collection,
+ sender,
+ [(data.0, data.1)].into_iter().collect(),
+ nesting_budget,
+ )
}
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -89,9 +89,15 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: up_data_structs::CreateItemData,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
with_weight(
- <Pallet<T>>::create_item(self, &sender, map_create_data::<T>(data, &to)?),
+ <Pallet<T>>::create_item(
+ self,
+ &sender,
+ map_create_data::<T>(data, &to)?,
+ nesting_budget,
+ ),
<CommonWeights<T>>::create_item(),
)
}
@@ -101,6 +107,7 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: Vec<up_data_structs::CreateItemData>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let data = data
.into_iter()
@@ -109,7 +116,7 @@
let amount = data.len();
with_weight(
- <Pallet<T>>::create_multiple_items(self, &sender, data),
+ <Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),
<CommonWeights<T>>::create_multiple_items(amount as u32),
)
}
@@ -118,6 +125,7 @@
&self,
sender: <T>::CrossAccountId,
data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
let data = match data {
@@ -126,7 +134,7 @@
};
with_weight(
- <Pallet<T>>::create_multiple_items(self, &sender, data.into_inner()),
+ <Pallet<T>>::create_multiple_items(self, &sender, data.into_inner(), nesting_budget),
weight,
)
}
@@ -154,11 +162,12 @@
to: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
if amount == 1 {
with_weight(
- <Pallet<T>>::transfer(self, &from, &to, token),
+ <Pallet<T>>::transfer(self, &from, &to, token, nesting_budget),
<CommonWeights<T>>::transfer(),
)
} else {
@@ -239,13 +248,14 @@
)
}
- fn nest_token(
+ fn check_nesting(
&self,
sender: T::CrossAccountId,
- (from, _): (CollectionId, TokenId),
+ from: CollectionId,
under: TokenId,
+ budget: &dyn Budget,
) -> sp_runtime::DispatchResult {
- <Pallet<T>>::nest_token(self, sender, from, under)
+ <Pallet<T>>::check_nesting(self, sender, from, under, budget)
}
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -256,6 +256,10 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let token_id: u32 = token_id.try_into()?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
if <TokensMinted<T>>::get(self.id)
.checked_add(1)
.ok_or("item id overflow")?
@@ -272,6 +276,7 @@
variable_data: BoundedVec::default(),
owner: to,
},
+ &budget,
)
.map_err(dispatch_to_evm::<T>)?;
@@ -296,6 +301,10 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
if <TokensMinted<T>>::get(self.id)
.checked_add(1)
.ok_or("item id overflow")?
@@ -314,6 +323,7 @@
variable_data: BoundedVec::default(),
owner: to,
},
+ &budget,
)
.map_err(dispatch_to_evm::<T>)?;
Ok(true)
@@ -338,8 +348,11 @@
let caller = T::CrossAccountId::from_eth(caller);
let to = T::CrossAccountId::from_eth(to);
let token = token_id.try_into()?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;
+ <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
@@ -409,6 +422,9 @@
let mut expected_index = <TokensMinted<T>>::get(self.id)
.checked_add(1)
.ok_or("item id overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
let total_tokens = token_ids.len();
for id in token_ids.into_iter() {
@@ -426,7 +442,8 @@
})
.collect();
- <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;
+ <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
@@ -447,6 +464,9 @@
let mut expected_index = <TokensMinted<T>>::get(self.id)
.checked_add(1)
.ok_or("item id overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
let mut data = Vec::with_capacity(tokens.len());
for (id, token_uri) in tokens {
@@ -465,7 +485,8 @@
});
}
- <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;
+ <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -251,6 +251,7 @@
from: &T::CrossAccountId,
to: &T::CrossAccountId,
token: TokenId,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
ensure!(
collection.limits.transfers_enabled(),
@@ -294,12 +295,12 @@
let handle = <CollectionHandle<T>>::try_get(target.0)?;
let dispatch = T::CollectionDispatch::dispatch(handle);
let dispatch = dispatch.as_dyn();
-
- // =========
- dispatch.nest_token(from.clone(), (collection.id, token), target.1)?;
+ dispatch.check_nesting(from.clone(), collection.id, target.1, nesting_budget)?;
}
+ // =========
+
<TokenData<T>>::insert(
(collection.id, token),
ItemData {
@@ -340,6 +341,7 @@
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
data: Vec<CreateItemData<T>>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
@@ -379,6 +381,16 @@
);
}
+ for (to, _) in balances.iter() {
+ if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
+ let handle = <CollectionHandle<T>>::try_get(target.0)?;
+ let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = dispatch.as_dyn();
+
+ dispatch.check_nesting(sender.clone(), collection.id, target.1, nesting_budget)?;
+ }
+ }
+
// =========
<TokensMinted<T>>::insert(collection.id, tokens_minted);
@@ -556,7 +568,7 @@
// =========
// Allowance is reset in [`transfer`]
- Self::transfer(collection, from, to, token)
+ Self::transfer(collection, from, to, token, nesting_budget)
}
pub fn burn_from(
@@ -595,35 +607,34 @@
Ok(())
}
- pub fn nest_token(
+ pub fn check_nesting(
handle: &NonfungibleHandle<T>,
sender: T::CrossAccountId,
from: CollectionId,
under: TokenId,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
fn ensure_sender_allowed<T: Config>(
collection: CollectionId,
token: TokenId,
sender: T::CrossAccountId,
+ budget: &dyn Budget,
) -> DispatchResult {
ensure!(
- <TokenData<T>>::get((collection, token))
- .ok_or(<CommonError<T>>::TokenNotFound)?
- .owner
- .conv_eq(&sender),
+ <PalletStructure<T>>::indirectly_owned(sender, collection, token, budget)?,
<CommonError<T>>::OnlyOwnerAllowedToNest,
);
Ok(())
}
match handle.limits.nesting_rule() {
NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),
- NestingRule::Owner => ensure_sender_allowed::<T>(from, under, sender)?,
+ NestingRule::Owner => ensure_sender_allowed::<T>(from, under, sender, nesting_budget)?,
NestingRule::OwnerRestricted(whitelist) => {
ensure!(
whitelist.contains(&from),
<CommonError<T>>::SourceCollectionIsNotAllowedToNest
);
- ensure_sender_allowed::<T>(from, under, sender)?
+ ensure_sender_allowed::<T>(from, under, sender, nesting_budget)?
}
}
Ok(())
@@ -634,7 +645,8 @@
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
data: CreateItemData<T>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
- Self::create_multiple_items(collection, sender, vec![data])
+ Self::create_multiple_items(collection, sender, vec![data], nesting_budget)
}
}
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -19,7 +19,8 @@
use sp_std::collections::btree_map::BTreeMap;
use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight, BoundedVec};
use up_data_structs::{
- TokenId, CustomDataLimit, CreateItemExData, CreateRefungibleExData, budget::Budget,
+ CollectionId, TokenId, CustomDataLimit, CreateItemExData, CreateRefungibleExData,
+ budget::Budget,
};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
@@ -120,9 +121,15 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: up_data_structs::CreateItemData,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
with_weight(
- <Pallet<T>>::create_item(self, &sender, map_create_data::<T>(data, &to)?),
+ <Pallet<T>>::create_item(
+ self,
+ &sender,
+ map_create_data::<T>(data, &to)?,
+ nesting_budget,
+ ),
<CommonWeights<T>>::create_item(),
)
}
@@ -132,6 +139,7 @@
sender: T::CrossAccountId,
to: T::CrossAccountId,
data: Vec<up_data_structs::CreateItemData>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let data = data
.into_iter()
@@ -140,7 +148,7 @@
let amount = data.len();
with_weight(
- <Pallet<T>>::create_multiple_items(self, &sender, data),
+ <Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),
<CommonWeights<T>>::create_multiple_items(amount as u32),
)
}
@@ -149,6 +157,7 @@
&self,
sender: <T>::CrossAccountId,
data: CreateItemExData<T::CrossAccountId>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
let data = match data {
@@ -162,7 +171,7 @@
};
with_weight(
- <Pallet<T>>::create_multiple_items(self, &sender, data),
+ <Pallet<T>>::create_multiple_items(self, &sender, data, nesting_budget),
weight,
)
}
@@ -185,9 +194,10 @@
to: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
with_weight(
- <Pallet<T>>::transfer(self, &from, &to, token, amount),
+ <Pallet<T>>::transfer(self, &from, &to, token, amount, nesting_budget),
<CommonWeights<T>>::transfer(),
)
}
@@ -247,11 +257,12 @@
)
}
- fn nest_token(
+ fn check_nesting(
&self,
_sender: <T>::CrossAccountId,
- _from: (up_data_structs::CollectionId, TokenId),
+ _from: CollectionId,
_under: TokenId,
+ _budget: &dyn Budget,
) -> sp_runtime::DispatchResult {
fail!(<Error<T>>::RefungibleDisallowsNesting)
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -289,6 +289,7 @@
to: &T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
ensure!(
collection.limits.transfers_enabled(),
@@ -351,10 +352,10 @@
let dispatch = T::CollectionDispatch::dispatch(handle);
let dispatch = dispatch.as_dyn();
- // =========
+ dispatch.check_nesting(from.clone(), collection.id, target.1, nesting_budget)?;
+ }
- dispatch.nest_token(from.clone(), (collection.id, token), target.1)?;
- }
+ // =========
if let Some(balance_to) = balance_to {
// from != to
@@ -389,6 +390,7 @@
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
data: Vec<CreateRefungibleExData<T::CrossAccountId>>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
if !collection.is_owner_or_admin(sender) {
ensure!(
@@ -453,6 +455,23 @@
}
}
+ for token in data.iter() {
+ for (to, _) in token.users.iter() {
+ if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
+ let handle = <CollectionHandle<T>>::try_get(target.0)?;
+ let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = dispatch.as_dyn();
+
+ dispatch.check_nesting(
+ sender.clone(),
+ collection.id,
+ target.1,
+ nesting_budget,
+ )?;
+ }
+ }
+ }
+
// =========
<TokensMinted<T>>::insert(collection.id, tokens_minted);
@@ -591,7 +610,7 @@
// =========
- Self::transfer(collection, from, to, token, amount)?;
+ Self::transfer(collection, from, to, token, amount, nesting_budget)?;
if let Some(allowance) = allowance {
Self::set_allowance_unchecked(collection, from, spender, token, allowance);
}
@@ -648,7 +667,8 @@
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
data: CreateRefungibleExData<T::CrossAccountId>,
+ nesting_budget: &dyn Budget,
) -> DispatchResult {
- Self::create_multiple_items(collection, sender, vec![data])
+ Self::create_multiple_items(collection, sender, vec![data], nesting_budget)
}
}
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -19,7 +19,6 @@
use frame_support::Parameter;
use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};
use frame_support::pallet_prelude::*;
- use frame_system::pallet_prelude::*;
use super::*;
pallets/unique/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829use frame_support::{30 decl_module, decl_storage, decl_error, decl_event,31 dispatch::DispatchResult,32 ensure,33 weights::{Weight},34 transactional,35 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},36 BoundedVec,37};38use scale_info::TypeInfo;39use frame_system::{self as system, ensure_signed};40use sp_runtime::{sp_std::prelude::Vec};41use up_data_structs::{42 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,43 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,44 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,45 SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,46 CreateItemExData, budget,47};48use pallet_evm::account::CrossAccountId;49use pallet_common::{50 CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,51 dispatch::dispatch_call, dispatch::CollectionDispatch,52};5354#[cfg(test)]55mod mock;5657#[cfg(test)]58mod tests;5960mod eth;61mod sponsorship;62pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};63pub use eth::sponsoring::UniqueEthSponsorshipHandler;6465pub mod common;66use common::CommonWeights;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273pub trait SponsorshipPredict<T: Config> {74 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>75 where76 u64: From<<T as frame_system::Config>::BlockNumber>;77}7879decl_error! {80 /// Error for non-fungible-token module.81 pub enum Error for Module<T: Config> {82 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.83 CollectionDecimalPointLimitExceeded,84 /// This address is not set as sponsor, use setCollectionSponsor first.85 ConfirmUnsetSponsorFail,86 /// Length of items properties must be greater than 0.87 EmptyArgument,88 }89}9091pub trait Config:92 system::Config93 + pallet_evm_coder_substrate::Config94 + pallet_common::Config95 + pallet_nonfungible::Config96 + pallet_refungible::Config97 + pallet_fungible::Config98 + Sized99 + TypeInfo100{101 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;102103 /// Weight information for extrinsics in this pallet.104 type WeightInfo: WeightInfo;105}106107decl_event! {108 pub enum Event<T>109 where110 <T as frame_system::Config>::AccountId,111 <T as pallet_evm::account::Config>::CrossAccountId,112 {113 /// Collection sponsor was removed114 ///115 /// # Arguments116 ///117 /// * collection_id: Globally unique collection identifier.118 CollectionSponsorRemoved(CollectionId),119120 /// Collection admin was added121 ///122 /// # Arguments123 ///124 /// * collection_id: Globally unique collection identifier.125 ///126 /// * admin: Admin address.127 CollectionAdminAdded(CollectionId, CrossAccountId),128129 /// Collection owned was change130 ///131 /// # Arguments132 ///133 /// * collection_id: Globally unique collection identifier.134 ///135 /// * owner: New owner address.136 CollectionOwnedChanged(CollectionId, AccountId),137138 /// Collection sponsor was set139 ///140 /// # Arguments141 ///142 /// * collection_id: Globally unique collection identifier.143 ///144 /// * owner: New sponsor address.145 CollectionSponsorSet(CollectionId, AccountId),146147 /// const on chain schema was set148 ///149 /// # Arguments150 ///151 /// * collection_id: Globally unique collection identifier.152 ConstOnChainSchemaSet(CollectionId),153154 /// New sponsor was confirm155 ///156 /// # Arguments157 ///158 /// * collection_id: Globally unique collection identifier.159 ///160 /// * sponsor: New sponsor address.161 SponsorshipConfirmed(CollectionId, AccountId),162163 /// Collection admin was removed164 ///165 /// # Arguments166 ///167 /// * collection_id: Globally unique collection identifier.168 ///169 /// * admin: Admin address.170 CollectionAdminRemoved(CollectionId, CrossAccountId),171172 /// Address was remove from allow list173 ///174 /// # Arguments175 ///176 /// * collection_id: Globally unique collection identifier.177 ///178 /// * user: Address.179 AllowListAddressRemoved(CollectionId, CrossAccountId),180181 /// Address was add to allow list182 ///183 /// # Arguments184 ///185 /// * collection_id: Globally unique collection identifier.186 ///187 /// * user: Address.188 AllowListAddressAdded(CollectionId, CrossAccountId),189190 /// Collection limits was set191 ///192 /// # Arguments193 ///194 /// * collection_id: Globally unique collection identifier.195 CollectionLimitSet(CollectionId),196197 /// Mint permission was set198 ///199 /// # Arguments200 ///201 /// * collection_id: Globally unique collection identifier.202 MintPermissionSet(CollectionId),203204 /// Offchain schema was set205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique collection identifier.209 OffchainSchemaSet(CollectionId),210211 /// Public access mode was set212 ///213 /// # Arguments214 ///215 /// * collection_id: Globally unique collection identifier.216 ///217 /// * mode: New access state.218 PublicAccessModeSet(CollectionId, AccessMode),219220 /// Schema version was set221 ///222 /// # Arguments223 ///224 /// * collection_id: Globally unique collection identifier.225 SchemaVersionSet(CollectionId),226227 /// Variable on chain schema was set228 ///229 /// # Arguments230 ///231 /// * collection_id: Globally unique collection identifier.232 VariableOnChainSchemaSet(CollectionId),233 }234}235236type SelfWeightOf<T> = <T as Config>::WeightInfo;237238// # Used definitions239//240// ## User control levels241//242// chain-controlled - key is uncontrolled by user243// i.e autoincrementing index244// can use non-cryptographic hash245// real - key is controlled by user246// but it is hard to generate enough colliding values, i.e owner of signed txs247// can use non-cryptographic hash248// controlled - key is completly controlled by users249// i.e maps with mutable keys250// should use cryptographic hash251//252// ## User control level downgrade reasons253//254// ?1 - chain-controlled -> controlled255// collections/tokens can be destroyed, resulting in massive holes256// ?2 - chain-controlled -> controlled257// same as ?1, but can be only added, resulting in easier exploitation258// ?3 - real -> controlled259// no confirmation required, so addresses can be easily generated260decl_storage! {261 trait Store for Module<T: Config> as Unique {262263 //#region Private members264 /// Used for migrations265 ChainVersion: u64;266 //#endregion267268 //#region Tokens transfer rate limit baskets269 /// (Collection id (controlled?2), who created (real))270 /// TODO: Off chain worker should remove from this map when collection gets removed271 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;272 /// Collection id (controlled?2), token id (controlled?2)273 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;274 /// Collection id (controlled?2), owning user (real)275 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;276 /// Collection id (controlled?2), token id (controlled?2)277 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;278 //#endregion279280 /// Variable metadata sponsoring281 /// Collection id (controlled?2), token id (controlled?2)282 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;283 /// Approval sponsoring284 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;285 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;287 }288}289290decl_module! {291 pub struct Module<T: Config> for enum Call292 where293 origin: T::Origin294 {295 type Error = Error<T>;296297 fn deposit_event() = default;298299 fn on_initialize(_now: T::BlockNumber) -> Weight {300 0301 }302303 /// 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 of the collection is set to the address that signed the transaction and can be changed later.304 ///305 /// # Permissions306 ///307 /// * Anyone.308 ///309 /// # Arguments310 ///311 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.312 ///313 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.314 ///315 /// * token_prefix: UTF-8 string with token prefix.316 ///317 /// * mode: [CollectionMode] collection type and type dependent data.318 // returns collection ID319 #[weight = <SelfWeightOf<T>>::create_collection()]320 #[transactional]321 #[deprecated]322 pub fn create_collection(origin,323 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,324 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,325 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,326 mode: CollectionMode) -> DispatchResult {327 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328 name: collection_name,329 description: collection_description,330 token_prefix,331 mode,332 ..Default::default()333 };334 Self::create_collection_ex(origin, data)335 }336337 /// This method creates a collection338 ///339 /// Prefer it to deprecated [`created_collection`] method340 #[weight = <SelfWeightOf<T>>::create_collection()]341 #[transactional]342 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {343 let sender = ensure_signed(origin)?;344345 // =========346347 T::CollectionDispatch::create(sender, data)?;348349 Ok(())350 }351352 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.353 ///354 /// # Permissions355 ///356 /// * Collection Owner.357 ///358 /// # Arguments359 ///360 /// * collection_id: collection to destroy.361 #[weight = <SelfWeightOf<T>>::destroy_collection()]362 #[transactional]363 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365 let collection = <CollectionHandle<T>>::try_get(collection_id)?;366367 // =========368369 T::CollectionDispatch::destroy(sender, collection)?;370371 <NftTransferBasket<T>>::remove_prefix(collection_id, None);372 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);373 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);374375 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);376 <NftApproveBasket<T>>::remove_prefix(collection_id, None);377 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);378 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);379380 Ok(())381 }382383 /// Add an address to allow list.384 ///385 /// # Permissions386 ///387 /// * Collection Owner388 /// * Collection Admin389 ///390 /// # Arguments391 ///392 /// * collection_id.393 ///394 /// * address.395 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]396 #[transactional]397 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{398399 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);400 let collection = <CollectionHandle<T>>::try_get(collection_id)?;401402 <PalletCommon<T>>::toggle_allowlist(403 &collection,404 &sender,405 &address,406 true,407 )?;408409 Self::deposit_event(Event::<T>::AllowListAddressAdded(410 collection_id,411 address412 ));413414 Ok(())415 }416417 /// Remove an address from allow list.418 ///419 /// # Permissions420 ///421 /// * Collection Owner422 /// * Collection Admin423 ///424 /// # Arguments425 ///426 /// * collection_id.427 ///428 /// * address.429 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]430 #[transactional]431 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435436 <PalletCommon<T>>::toggle_allowlist(437 &collection,438 &sender,439 &address,440 false,441 )?;442443 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(444 collection_id,445 address446 ));447448 Ok(())449 }450451 /// Toggle between normal and allow list access for the methods with access for `Anyone`.452 ///453 /// # Permissions454 ///455 /// * Collection Owner.456 ///457 /// # Arguments458 ///459 /// * collection_id.460 ///461 /// * mode: [AccessMode]462 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]463 #[transactional]464 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult465 {466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.access = mode.clone();472473 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(474 collection_id,475 mode476 ));477478 target_collection.save()479 }480481 /// Allows Anyone to create tokens if:482 /// * Allow List is enabled, and483 /// * Address is added to allow list, and484 /// * This method was called with True parameter485 ///486 /// # Permissions487 /// * Collection Owner488 ///489 /// # Arguments490 ///491 /// * collection_id.492 ///493 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.494 #[weight = <SelfWeightOf<T>>::set_mint_permission()]495 #[transactional]496 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult497 {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499500 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;501 target_collection.check_is_owner(&sender)?;502503 target_collection.mint_mode = mint_permission;504505 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(506 collection_id507 ));508509 target_collection.save()510 }511512 /// Change the owner of the collection.513 ///514 /// # Permissions515 ///516 /// * Collection Owner.517 ///518 /// # Arguments519 ///520 /// * collection_id.521 ///522 /// * new_owner.523 #[weight = <SelfWeightOf<T>>::change_collection_owner()]524 #[transactional]525 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {526527 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528529 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;530 target_collection.check_is_owner(&sender)?;531532 target_collection.owner = new_owner.clone();533 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(534 collection_id,535 new_owner536 ));537538 target_collection.save()539 }540541 /// Adds an admin of the Collection.542 /// 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.543 ///544 /// # Permissions545 ///546 /// * Collection Owner.547 /// * Collection Admin.548 ///549 /// # Arguments550 ///551 /// * collection_id: ID of the Collection to add admin for.552 ///553 /// * new_admin_id: Address of new admin to add.554 #[weight = <SelfWeightOf<T>>::add_collection_admin()]555 #[transactional]556 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {557 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);558 let collection = <CollectionHandle<T>>::try_get(collection_id)?;559560 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(561 collection_id,562 new_admin_id.clone()563 ));564565 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)566 }567568 /// 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.569 ///570 /// # Permissions571 ///572 /// * Collection Owner.573 /// * Collection Admin.574 ///575 /// # Arguments576 ///577 /// * collection_id: ID of the Collection to remove admin for.578 ///579 /// * account_id: Address of admin to remove.580 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]581 #[transactional]582 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {583 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);584 let collection = <CollectionHandle<T>>::try_get(collection_id)?;585586 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(587 collection_id,588 account_id.clone()589 ));590591 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)592 }593594 /// # Permissions595 ///596 /// * Collection Owner597 ///598 /// # Arguments599 ///600 /// * collection_id.601 ///602 /// * new_sponsor.603 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]604 #[transactional]605 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {606 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607608 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;609 target_collection.check_is_owner(&sender)?;610611 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());612613 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(614 collection_id,615 new_sponsor616 ));617618 target_collection.save()619 }620621 /// # Permissions622 ///623 /// * Sponsor.624 ///625 /// # Arguments626 ///627 /// * collection_id.628 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]629 #[transactional]630 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {631 let sender = ensure_signed(origin)?;632633 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634 ensure!(635 target_collection.sponsorship.pending_sponsor() == Some(&sender),636 Error::<T>::ConfirmUnsetSponsorFail637 );638639 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());640641 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(642 collection_id,643 sender644 ));645646 target_collection.save()647 }648649 /// Switch back to pay-per-own-transaction model.650 ///651 /// # Permissions652 ///653 /// * Collection owner.654 ///655 /// # Arguments656 ///657 /// * collection_id.658 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]659 #[transactional]660 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {661 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;664 target_collection.check_is_owner(&sender)?;665666 target_collection.sponsorship = SponsorshipState::Disabled;667668 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(669 collection_id670 ));671 target_collection.save()672 }673674 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.675 ///676 /// # Permissions677 ///678 /// * Collection Owner.679 /// * Collection Admin.680 /// * Anyone if681 /// * Allow List is enabled, and682 /// * Address is added to allow list, and683 /// * MintPermission is enabled (see SetMintPermission method)684 ///685 /// # Arguments686 ///687 /// * collection_id: ID of the collection.688 ///689 /// * owner: Address, initial owner of the NFT.690 ///691 /// * data: Token data to store on chain.692 #[weight = <CommonWeights<T>>::create_item()]693 #[transactional]694 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {695 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696697 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))698 }699700 /// This method creates multiple items in a collection created with CreateCollection method.701 ///702 /// # Permissions703 ///704 /// * Collection Owner.705 /// * Collection Admin.706 /// * Anyone if707 /// * Allow List is enabled, and708 /// * Address is added to allow list, and709 /// * MintPermission is enabled (see SetMintPermission method)710 ///711 /// # Arguments712 ///713 /// * collection_id: ID of the collection.714 ///715 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].716 ///717 /// * owner: Address, initial owner of the NFT.718 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]719 #[transactional]720 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {721 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);722 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);723724 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))725 }726727 #[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]728 #[transactional]729 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {730 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))733 }734735 // TODO! transaction weight736737 /// Set transfers_enabled value for particular collection738 ///739 /// # Permissions740 ///741 /// * Collection Owner.742 ///743 /// # Arguments744 ///745 /// * collection_id: ID of the collection.746 ///747 /// * value: New flag value.748 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]749 #[transactional]750 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {751 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;753 target_collection.check_is_owner(&sender)?;754755 // =========756757 target_collection.limits.transfers_enabled = Some(value);758 target_collection.save()759 }760761 /// Destroys a concrete instance of NFT.762 ///763 /// # Permissions764 ///765 /// * Collection Owner.766 /// * Collection Admin.767 /// * Current NFT Owner.768 ///769 /// # Arguments770 ///771 /// * collection_id: ID of the collection.772 ///773 /// * item_id: ID of NFT to burn.774 #[weight = <CommonWeights<T>>::burn_item()]775 #[transactional]776 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {777 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778779 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;780 if value == 1 {781 <NftTransferBasket<T>>::remove(collection_id, item_id);782 <NftApproveBasket<T>>::remove(collection_id, item_id);783 }784 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?785 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());786 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));787 Ok(post_info)788 }789790 /// Destroys a concrete instance of NFT on behalf of the owner791 /// See also: [`approve`]792 ///793 /// # Permissions794 ///795 /// * Collection Owner.796 /// * Collection Admin.797 /// * Current NFT Owner.798 ///799 /// # Arguments800 ///801 /// * collection_id: ID of the collection.802 ///803 /// * item_id: ID of NFT to burn.804 ///805 /// * from: owner of item806 #[weight = <CommonWeights<T>>::burn_from()]807 #[transactional]808 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {809 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810 let budget = budget::Value::new(2);811812 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))813 }814815 /// Change ownership of the token.816 ///817 /// # Permissions818 ///819 /// * Collection Owner820 /// * Collection Admin821 /// * Current NFT owner822 ///823 /// # Arguments824 ///825 /// * recipient: Address of token recipient.826 ///827 /// * collection_id.828 ///829 /// * item_id: ID of the item830 /// * Non-Fungible Mode: Required.831 /// * Fungible Mode: Ignored.832 /// * Re-Fungible Mode: Required.833 ///834 /// * value: Amount to transfer.835 /// * Non-Fungible Mode: Ignored836 /// * Fungible Mode: Must specify transferred amount837 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)838 #[weight = <CommonWeights<T>>::transfer()]839 #[transactional]840 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {841 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);842843 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))844 }845846 /// Set, change, or remove approved address to transfer the ownership of the NFT.847 ///848 /// # Permissions849 ///850 /// * Collection Owner851 /// * Collection Admin852 /// * Current NFT owner853 ///854 /// # Arguments855 ///856 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).857 ///858 /// * collection_id.859 ///860 /// * item_id: ID of the item.861 #[weight = <CommonWeights<T>>::approve()]862 #[transactional]863 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {864 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);865866 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))867 }868869 /// 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.870 ///871 /// # Permissions872 /// * Collection Owner873 /// * Collection Admin874 /// * Current NFT owner875 /// * Address approved by current NFT owner876 ///877 /// # Arguments878 ///879 /// * from: Address that owns token.880 ///881 /// * recipient: Address of token recipient.882 ///883 /// * collection_id.884 ///885 /// * item_id: ID of the item.886 ///887 /// * value: Amount to transfer.888 #[weight = <CommonWeights<T>>::transfer_from()]889 #[transactional]890 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {891 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);892 let budget = budget::Value::new(2);893894 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))895 }896897 /// Set off-chain data schema.898 ///899 /// # Permissions900 ///901 /// * Collection Owner902 /// * Collection Admin903 ///904 /// # Arguments905 ///906 /// * collection_id.907 ///908 /// * schema: String representing the offchain data schema.909 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]910 #[transactional]911 pub fn set_variable_meta_data (912 origin,913 collection_id: CollectionId,914 item_id: TokenId,915 data: BoundedVec<u8, CustomDataLimit>,916 ) -> DispatchResultWithPostInfo {917 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);918919 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))920 }921922 /// Set meta_update_permission value for particular collection923 ///924 /// # Permissions925 ///926 /// * Collection Owner.927 ///928 /// # Arguments929 ///930 /// * collection_id: ID of the collection.931 ///932 /// * value: New flag value.933 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]934 #[transactional]935 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {936 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);937 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;938939 ensure!(940 target_collection.meta_update_permission != MetaUpdatePermission::None,941 <CommonError<T>>::MetadataFlagFrozen,942 );943 target_collection.check_is_owner(&sender)?;944945 target_collection.meta_update_permission = value;946947 target_collection.save()948 }949950 /// Set schema standard951 /// ImageURL952 /// Unique953 ///954 /// # Permissions955 ///956 /// * Collection Owner957 /// * Collection Admin958 ///959 /// # Arguments960 ///961 /// * collection_id.962 ///963 /// * schema: SchemaVersion: enum964 #[weight = <SelfWeightOf<T>>::set_schema_version()]965 #[transactional]966 pub fn set_schema_version(967 origin,968 collection_id: CollectionId,969 version: SchemaVersion970 ) -> DispatchResult {971 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);972 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;973 target_collection.check_is_owner_or_admin(&sender)?;974 target_collection.schema_version = version;975976 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(977 collection_id978 ));979980 target_collection.save()981 }982983 /// Set off-chain data schema.984 ///985 /// # Permissions986 ///987 /// * Collection Owner988 /// * Collection Admin989 ///990 /// # Arguments991 ///992 /// * collection_id.993 ///994 /// * schema: String representing the offchain data schema.995 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]996 #[transactional]997 pub fn set_offchain_schema(998 origin,999 collection_id: CollectionId,1000 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1001 ) -> DispatchResult {1002 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1003 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1004 target_collection.check_is_owner_or_admin(&sender)?;10051006 target_collection.offchain_schema = schema;10071008 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1009 collection_id1010 ));10111012 target_collection.save()1013 }10141015 /// Set const on-chain data schema.1016 ///1017 /// # Permissions1018 ///1019 /// * Collection Owner1020 /// * Collection Admin1021 ///1022 /// # Arguments1023 ///1024 /// * collection_id.1025 ///1026 /// * schema: String representing the const on-chain data schema.1027 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1028 #[transactional]1029 pub fn set_const_on_chain_schema (1030 origin,1031 collection_id: CollectionId,1032 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1033 ) -> DispatchResult {1034 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1035 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1036 target_collection.check_is_owner_or_admin(&sender)?;10371038 target_collection.const_on_chain_schema = schema;10391040 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1041 collection_id1042 ));10431044 target_collection.save()1045 }10461047 /// Set variable on-chain data schema.1048 ///1049 /// # Permissions1050 ///1051 /// * Collection Owner1052 /// * Collection Admin1053 ///1054 /// # Arguments1055 ///1056 /// * collection_id.1057 ///1058 /// * schema: String representing the variable on-chain data schema.1059 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1060 #[transactional]1061 pub fn set_variable_on_chain_schema (1062 origin,1063 collection_id: CollectionId,1064 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1065 ) -> DispatchResult {1066 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1067 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1068 target_collection.check_is_owner_or_admin(&sender)?;10691070 target_collection.variable_on_chain_schema = schema;10711072 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1073 collection_id1074 ));10751076 target_collection.save()1077 }10781079 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1080 #[transactional]1081 pub fn set_collection_limits(1082 origin,1083 collection_id: CollectionId,1084 new_limit: CollectionLimits,1085 ) -> DispatchResult {1086 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1088 target_collection.check_is_owner(&sender)?;1089 let old_limit = &target_collection.limits;10901091 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10921093 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1094 collection_id1095 ));10961097 target_collection.save()1098 }1099 }1100}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829use frame_support::{30 decl_module, decl_storage, decl_error, decl_event,31 dispatch::DispatchResult,32 ensure,33 weights::{Weight},34 transactional,35 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},36 BoundedVec,37};38use scale_info::TypeInfo;39use frame_system::{self as system, ensure_signed};40use sp_runtime::{sp_std::prelude::Vec};41use up_data_structs::{42 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,43 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,44 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,45 SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,46 CreateItemExData, budget,47};48use pallet_evm::account::CrossAccountId;49use pallet_common::{50 CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,51 dispatch::dispatch_call, dispatch::CollectionDispatch,52};5354#[cfg(test)]55mod mock;5657#[cfg(test)]58mod tests;5960mod eth;61mod sponsorship;62pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};63pub use eth::sponsoring::UniqueEthSponsorshipHandler;6465pub mod common;66use common::CommonWeights;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273pub trait SponsorshipPredict<T: Config> {74 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>75 where76 u64: From<<T as frame_system::Config>::BlockNumber>;77}7879decl_error! {80 /// Error for non-fungible-token module.81 pub enum Error for Module<T: Config> {82 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.83 CollectionDecimalPointLimitExceeded,84 /// This address is not set as sponsor, use setCollectionSponsor first.85 ConfirmUnsetSponsorFail,86 /// Length of items properties must be greater than 0.87 EmptyArgument,88 }89}9091pub trait Config:92 system::Config93 + pallet_evm_coder_substrate::Config94 + pallet_common::Config95 + pallet_nonfungible::Config96 + pallet_refungible::Config97 + pallet_fungible::Config98 + Sized99 + TypeInfo100{101 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;102103 /// Weight information for extrinsics in this pallet.104 type WeightInfo: WeightInfo;105}106107decl_event! {108 pub enum Event<T>109 where110 <T as frame_system::Config>::AccountId,111 <T as pallet_evm::account::Config>::CrossAccountId,112 {113 /// Collection sponsor was removed114 ///115 /// # Arguments116 ///117 /// * collection_id: Globally unique collection identifier.118 CollectionSponsorRemoved(CollectionId),119120 /// Collection admin was added121 ///122 /// # Arguments123 ///124 /// * collection_id: Globally unique collection identifier.125 ///126 /// * admin: Admin address.127 CollectionAdminAdded(CollectionId, CrossAccountId),128129 /// Collection owned was change130 ///131 /// # Arguments132 ///133 /// * collection_id: Globally unique collection identifier.134 ///135 /// * owner: New owner address.136 CollectionOwnedChanged(CollectionId, AccountId),137138 /// Collection sponsor was set139 ///140 /// # Arguments141 ///142 /// * collection_id: Globally unique collection identifier.143 ///144 /// * owner: New sponsor address.145 CollectionSponsorSet(CollectionId, AccountId),146147 /// const on chain schema was set148 ///149 /// # Arguments150 ///151 /// * collection_id: Globally unique collection identifier.152 ConstOnChainSchemaSet(CollectionId),153154 /// New sponsor was confirm155 ///156 /// # Arguments157 ///158 /// * collection_id: Globally unique collection identifier.159 ///160 /// * sponsor: New sponsor address.161 SponsorshipConfirmed(CollectionId, AccountId),162163 /// Collection admin was removed164 ///165 /// # Arguments166 ///167 /// * collection_id: Globally unique collection identifier.168 ///169 /// * admin: Admin address.170 CollectionAdminRemoved(CollectionId, CrossAccountId),171172 /// Address was remove from allow list173 ///174 /// # Arguments175 ///176 /// * collection_id: Globally unique collection identifier.177 ///178 /// * user: Address.179 AllowListAddressRemoved(CollectionId, CrossAccountId),180181 /// Address was add to allow list182 ///183 /// # Arguments184 ///185 /// * collection_id: Globally unique collection identifier.186 ///187 /// * user: Address.188 AllowListAddressAdded(CollectionId, CrossAccountId),189190 /// Collection limits was set191 ///192 /// # Arguments193 ///194 /// * collection_id: Globally unique collection identifier.195 CollectionLimitSet(CollectionId),196197 /// Mint permission was set198 ///199 /// # Arguments200 ///201 /// * collection_id: Globally unique collection identifier.202 MintPermissionSet(CollectionId),203204 /// Offchain schema was set205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique collection identifier.209 OffchainSchemaSet(CollectionId),210211 /// Public access mode was set212 ///213 /// # Arguments214 ///215 /// * collection_id: Globally unique collection identifier.216 ///217 /// * mode: New access state.218 PublicAccessModeSet(CollectionId, AccessMode),219220 /// Schema version was set221 ///222 /// # Arguments223 ///224 /// * collection_id: Globally unique collection identifier.225 SchemaVersionSet(CollectionId),226227 /// Variable on chain schema was set228 ///229 /// # Arguments230 ///231 /// * collection_id: Globally unique collection identifier.232 VariableOnChainSchemaSet(CollectionId),233 }234}235236type SelfWeightOf<T> = <T as Config>::WeightInfo;237238// # Used definitions239//240// ## User control levels241//242// chain-controlled - key is uncontrolled by user243// i.e autoincrementing index244// can use non-cryptographic hash245// real - key is controlled by user246// but it is hard to generate enough colliding values, i.e owner of signed txs247// can use non-cryptographic hash248// controlled - key is completly controlled by users249// i.e maps with mutable keys250// should use cryptographic hash251//252// ## User control level downgrade reasons253//254// ?1 - chain-controlled -> controlled255// collections/tokens can be destroyed, resulting in massive holes256// ?2 - chain-controlled -> controlled257// same as ?1, but can be only added, resulting in easier exploitation258// ?3 - real -> controlled259// no confirmation required, so addresses can be easily generated260decl_storage! {261 trait Store for Module<T: Config> as Unique {262263 //#region Private members264 /// Used for migrations265 ChainVersion: u64;266 //#endregion267268 //#region Tokens transfer rate limit baskets269 /// (Collection id (controlled?2), who created (real))270 /// TODO: Off chain worker should remove from this map when collection gets removed271 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;272 /// Collection id (controlled?2), token id (controlled?2)273 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;274 /// Collection id (controlled?2), owning user (real)275 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;276 /// Collection id (controlled?2), token id (controlled?2)277 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;278 //#endregion279280 /// Variable metadata sponsoring281 /// Collection id (controlled?2), token id (controlled?2)282 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;283 /// Approval sponsoring284 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;285 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;287 }288}289290decl_module! {291 pub struct Module<T: Config> for enum Call292 where293 origin: T::Origin294 {295 type Error = Error<T>;296297 fn deposit_event() = default;298299 fn on_initialize(_now: T::BlockNumber) -> Weight {300 0301 }302303 /// 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 of the collection is set to the address that signed the transaction and can be changed later.304 ///305 /// # Permissions306 ///307 /// * Anyone.308 ///309 /// # Arguments310 ///311 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.312 ///313 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.314 ///315 /// * token_prefix: UTF-8 string with token prefix.316 ///317 /// * mode: [CollectionMode] collection type and type dependent data.318 // returns collection ID319 #[weight = <SelfWeightOf<T>>::create_collection()]320 #[transactional]321 #[deprecated]322 pub fn create_collection(origin,323 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,324 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,325 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,326 mode: CollectionMode) -> DispatchResult {327 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328 name: collection_name,329 description: collection_description,330 token_prefix,331 mode,332 ..Default::default()333 };334 Self::create_collection_ex(origin, data)335 }336337 /// This method creates a collection338 ///339 /// Prefer it to deprecated [`created_collection`] method340 #[weight = <SelfWeightOf<T>>::create_collection()]341 #[transactional]342 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {343 let sender = ensure_signed(origin)?;344345 // =========346347 T::CollectionDispatch::create(sender, data)?;348349 Ok(())350 }351352 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.353 ///354 /// # Permissions355 ///356 /// * Collection Owner.357 ///358 /// # Arguments359 ///360 /// * collection_id: collection to destroy.361 #[weight = <SelfWeightOf<T>>::destroy_collection()]362 #[transactional]363 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365 let collection = <CollectionHandle<T>>::try_get(collection_id)?;366367 // =========368369 T::CollectionDispatch::destroy(sender, collection)?;370371 <NftTransferBasket<T>>::remove_prefix(collection_id, None);372 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);373 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);374375 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);376 <NftApproveBasket<T>>::remove_prefix(collection_id, None);377 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);378 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);379380 Ok(())381 }382383 /// Add an address to allow list.384 ///385 /// # Permissions386 ///387 /// * Collection Owner388 /// * Collection Admin389 ///390 /// # Arguments391 ///392 /// * collection_id.393 ///394 /// * address.395 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]396 #[transactional]397 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{398399 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);400 let collection = <CollectionHandle<T>>::try_get(collection_id)?;401402 <PalletCommon<T>>::toggle_allowlist(403 &collection,404 &sender,405 &address,406 true,407 )?;408409 Self::deposit_event(Event::<T>::AllowListAddressAdded(410 collection_id,411 address412 ));413414 Ok(())415 }416417 /// Remove an address from allow list.418 ///419 /// # Permissions420 ///421 /// * Collection Owner422 /// * Collection Admin423 ///424 /// # Arguments425 ///426 /// * collection_id.427 ///428 /// * address.429 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]430 #[transactional]431 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435436 <PalletCommon<T>>::toggle_allowlist(437 &collection,438 &sender,439 &address,440 false,441 )?;442443 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(444 collection_id,445 address446 ));447448 Ok(())449 }450451 /// Toggle between normal and allow list access for the methods with access for `Anyone`.452 ///453 /// # Permissions454 ///455 /// * Collection Owner.456 ///457 /// # Arguments458 ///459 /// * collection_id.460 ///461 /// * mode: [AccessMode]462 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]463 #[transactional]464 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult465 {466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.access = mode.clone();472473 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(474 collection_id,475 mode476 ));477478 target_collection.save()479 }480481 /// Allows Anyone to create tokens if:482 /// * Allow List is enabled, and483 /// * Address is added to allow list, and484 /// * This method was called with True parameter485 ///486 /// # Permissions487 /// * Collection Owner488 ///489 /// # Arguments490 ///491 /// * collection_id.492 ///493 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.494 #[weight = <SelfWeightOf<T>>::set_mint_permission()]495 #[transactional]496 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult497 {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499500 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;501 target_collection.check_is_owner(&sender)?;502503 target_collection.mint_mode = mint_permission;504505 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(506 collection_id507 ));508509 target_collection.save()510 }511512 /// Change the owner of the collection.513 ///514 /// # Permissions515 ///516 /// * Collection Owner.517 ///518 /// # Arguments519 ///520 /// * collection_id.521 ///522 /// * new_owner.523 #[weight = <SelfWeightOf<T>>::change_collection_owner()]524 #[transactional]525 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {526527 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528529 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;530 target_collection.check_is_owner(&sender)?;531532 target_collection.owner = new_owner.clone();533 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(534 collection_id,535 new_owner536 ));537538 target_collection.save()539 }540541 /// Adds an admin of the Collection.542 /// 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.543 ///544 /// # Permissions545 ///546 /// * Collection Owner.547 /// * Collection Admin.548 ///549 /// # Arguments550 ///551 /// * collection_id: ID of the Collection to add admin for.552 ///553 /// * new_admin_id: Address of new admin to add.554 #[weight = <SelfWeightOf<T>>::add_collection_admin()]555 #[transactional]556 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {557 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);558 let collection = <CollectionHandle<T>>::try_get(collection_id)?;559560 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(561 collection_id,562 new_admin_id.clone()563 ));564565 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)566 }567568 /// 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.569 ///570 /// # Permissions571 ///572 /// * Collection Owner.573 /// * Collection Admin.574 ///575 /// # Arguments576 ///577 /// * collection_id: ID of the Collection to remove admin for.578 ///579 /// * account_id: Address of admin to remove.580 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]581 #[transactional]582 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {583 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);584 let collection = <CollectionHandle<T>>::try_get(collection_id)?;585586 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(587 collection_id,588 account_id.clone()589 ));590591 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)592 }593594 /// # Permissions595 ///596 /// * Collection Owner597 ///598 /// # Arguments599 ///600 /// * collection_id.601 ///602 /// * new_sponsor.603 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]604 #[transactional]605 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {606 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607608 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;609 target_collection.check_is_owner(&sender)?;610611 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());612613 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(614 collection_id,615 new_sponsor616 ));617618 target_collection.save()619 }620621 /// # Permissions622 ///623 /// * Sponsor.624 ///625 /// # Arguments626 ///627 /// * collection_id.628 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]629 #[transactional]630 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {631 let sender = ensure_signed(origin)?;632633 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634 ensure!(635 target_collection.sponsorship.pending_sponsor() == Some(&sender),636 Error::<T>::ConfirmUnsetSponsorFail637 );638639 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());640641 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(642 collection_id,643 sender644 ));645646 target_collection.save()647 }648649 /// Switch back to pay-per-own-transaction model.650 ///651 /// # Permissions652 ///653 /// * Collection owner.654 ///655 /// # Arguments656 ///657 /// * collection_id.658 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]659 #[transactional]660 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {661 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;664 target_collection.check_is_owner(&sender)?;665666 target_collection.sponsorship = SponsorshipState::Disabled;667668 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(669 collection_id670 ));671 target_collection.save()672 }673674 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.675 ///676 /// # Permissions677 ///678 /// * Collection Owner.679 /// * Collection Admin.680 /// * Anyone if681 /// * Allow List is enabled, and682 /// * Address is added to allow list, and683 /// * MintPermission is enabled (see SetMintPermission method)684 ///685 /// # Arguments686 ///687 /// * collection_id: ID of the collection.688 ///689 /// * owner: Address, initial owner of the NFT.690 ///691 /// * data: Token data to store on chain.692 #[weight = <CommonWeights<T>>::create_item()]693 #[transactional]694 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {695 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696 let budget = budget::Value::new(2);697698 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))699 }700701 /// This method creates multiple items in a collection created with CreateCollection method.702 ///703 /// # Permissions704 ///705 /// * Collection Owner.706 /// * Collection Admin.707 /// * Anyone if708 /// * Allow List is enabled, and709 /// * Address is added to allow list, and710 /// * MintPermission is enabled (see SetMintPermission method)711 ///712 /// # Arguments713 ///714 /// * collection_id: ID of the collection.715 ///716 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].717 ///718 /// * owner: Address, initial owner of the NFT.719 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]720 #[transactional]721 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {722 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);723 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724 let budget = budget::Value::new(2);725726 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))727 }728729 #[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]730 #[transactional]731 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {732 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733 let budget = budget::Value::new(2);734735 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))736 }737738 // TODO! transaction weight739740 /// Set transfers_enabled value for particular collection741 ///742 /// # Permissions743 ///744 /// * Collection Owner.745 ///746 /// # Arguments747 ///748 /// * collection_id: ID of the collection.749 ///750 /// * value: New flag value.751 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]752 #[transactional]753 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {754 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756 target_collection.check_is_owner(&sender)?;757758 // =========759760 target_collection.limits.transfers_enabled = Some(value);761 target_collection.save()762 }763764 /// Destroys a concrete instance of NFT.765 ///766 /// # Permissions767 ///768 /// * Collection Owner.769 /// * Collection Admin.770 /// * Current NFT Owner.771 ///772 /// # Arguments773 ///774 /// * collection_id: ID of the collection.775 ///776 /// * item_id: ID of NFT to burn.777 #[weight = <CommonWeights<T>>::burn_item()]778 #[transactional]779 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781782 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;783 if value == 1 {784 <NftTransferBasket<T>>::remove(collection_id, item_id);785 <NftApproveBasket<T>>::remove(collection_id, item_id);786 }787 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?788 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());789 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));790 Ok(post_info)791 }792793 /// Destroys a concrete instance of NFT on behalf of the owner794 /// See also: [`approve`]795 ///796 /// # Permissions797 ///798 /// * Collection Owner.799 /// * Collection Admin.800 /// * Current NFT Owner.801 ///802 /// # Arguments803 ///804 /// * collection_id: ID of the collection.805 ///806 /// * item_id: ID of NFT to burn.807 ///808 /// * from: owner of item809 #[weight = <CommonWeights<T>>::burn_from()]810 #[transactional]811 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813 let budget = budget::Value::new(2);814815 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))816 }817818 /// Change ownership of the token.819 ///820 /// # Permissions821 ///822 /// * Collection Owner823 /// * Collection Admin824 /// * Current NFT owner825 ///826 /// # Arguments827 ///828 /// * recipient: Address of token recipient.829 ///830 /// * collection_id.831 ///832 /// * item_id: ID of the item833 /// * Non-Fungible Mode: Required.834 /// * Fungible Mode: Ignored.835 /// * Re-Fungible Mode: Required.836 ///837 /// * value: Amount to transfer.838 /// * Non-Fungible Mode: Ignored839 /// * Fungible Mode: Must specify transferred amount840 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)841 #[weight = <CommonWeights<T>>::transfer()]842 #[transactional]843 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {844 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);845 let budget = budget::Value::new(2);846847 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))848 }849850 /// Set, change, or remove approved address to transfer the ownership of the NFT.851 ///852 /// # Permissions853 ///854 /// * Collection Owner855 /// * Collection Admin856 /// * Current NFT owner857 ///858 /// # Arguments859 ///860 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).861 ///862 /// * collection_id.863 ///864 /// * item_id: ID of the item.865 #[weight = <CommonWeights<T>>::approve()]866 #[transactional]867 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {868 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869870 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))871 }872873 /// 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.874 ///875 /// # Permissions876 /// * Collection Owner877 /// * Collection Admin878 /// * Current NFT owner879 /// * Address approved by current NFT owner880 ///881 /// # Arguments882 ///883 /// * from: Address that owns token.884 ///885 /// * recipient: Address of token recipient.886 ///887 /// * collection_id.888 ///889 /// * item_id: ID of the item.890 ///891 /// * value: Amount to transfer.892 #[weight = <CommonWeights<T>>::transfer_from()]893 #[transactional]894 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {895 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896 let budget = budget::Value::new(2);897898 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))899 }900901 /// Set off-chain data schema.902 ///903 /// # Permissions904 ///905 /// * Collection Owner906 /// * Collection Admin907 ///908 /// # Arguments909 ///910 /// * collection_id.911 ///912 /// * schema: String representing the offchain data schema.913 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]914 #[transactional]915 pub fn set_variable_meta_data (916 origin,917 collection_id: CollectionId,918 item_id: TokenId,919 data: BoundedVec<u8, CustomDataLimit>,920 ) -> DispatchResultWithPostInfo {921 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);922923 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))924 }925926 /// Set meta_update_permission value for particular collection927 ///928 /// # Permissions929 ///930 /// * Collection Owner.931 ///932 /// # Arguments933 ///934 /// * collection_id: ID of the collection.935 ///936 /// * value: New flag value.937 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]938 #[transactional]939 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {940 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;942943 ensure!(944 target_collection.meta_update_permission != MetaUpdatePermission::None,945 <CommonError<T>>::MetadataFlagFrozen,946 );947 target_collection.check_is_owner(&sender)?;948949 target_collection.meta_update_permission = value;950951 target_collection.save()952 }953954 /// Set schema standard955 /// ImageURL956 /// Unique957 ///958 /// # Permissions959 ///960 /// * Collection Owner961 /// * Collection Admin962 ///963 /// # Arguments964 ///965 /// * collection_id.966 ///967 /// * schema: SchemaVersion: enum968 #[weight = <SelfWeightOf<T>>::set_schema_version()]969 #[transactional]970 pub fn set_schema_version(971 origin,972 collection_id: CollectionId,973 version: SchemaVersion974 ) -> DispatchResult {975 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);976 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;977 target_collection.check_is_owner_or_admin(&sender)?;978 target_collection.schema_version = version;979980 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(981 collection_id982 ));983984 target_collection.save()985 }986987 /// Set off-chain data schema.988 ///989 /// # Permissions990 ///991 /// * Collection Owner992 /// * Collection Admin993 ///994 /// # Arguments995 ///996 /// * collection_id.997 ///998 /// * schema: String representing the offchain data schema.999 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1000 #[transactional]1001 pub fn set_offchain_schema(1002 origin,1003 collection_id: CollectionId,1004 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1005 ) -> DispatchResult {1006 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1008 target_collection.check_is_owner_or_admin(&sender)?;10091010 target_collection.offchain_schema = schema;10111012 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1013 collection_id1014 ));10151016 target_collection.save()1017 }10181019 /// Set const on-chain data schema.1020 ///1021 /// # Permissions1022 ///1023 /// * Collection Owner1024 /// * Collection Admin1025 ///1026 /// # Arguments1027 ///1028 /// * collection_id.1029 ///1030 /// * schema: String representing the const on-chain data schema.1031 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1032 #[transactional]1033 pub fn set_const_on_chain_schema (1034 origin,1035 collection_id: CollectionId,1036 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1037 ) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1040 target_collection.check_is_owner_or_admin(&sender)?;10411042 target_collection.const_on_chain_schema = schema;10431044 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1045 collection_id1046 ));10471048 target_collection.save()1049 }10501051 /// Set variable on-chain data schema.1052 ///1053 /// # Permissions1054 ///1055 /// * Collection Owner1056 /// * Collection Admin1057 ///1058 /// # Arguments1059 ///1060 /// * collection_id.1061 ///1062 /// * schema: String representing the variable on-chain data schema.1063 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1064 #[transactional]1065 pub fn set_variable_on_chain_schema (1066 origin,1067 collection_id: CollectionId,1068 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1069 ) -> DispatchResult {1070 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1071 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1072 target_collection.check_is_owner_or_admin(&sender)?;10731074 target_collection.variable_on_chain_schema = schema;10751076 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1077 collection_id1078 ));10791080 target_collection.save()1081 }10821083 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1084 #[transactional]1085 pub fn set_collection_limits(1086 origin,1087 collection_id: CollectionId,1088 new_limit: CollectionLimits,1089 ) -> DispatchResult {1090 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1091 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1092 target_collection.check_is_owner(&sender)?;1093 let old_limit = &target_collection.limits;10941095 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10961097 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1098 collection_id1099 ));11001101 target_collection.save()1102 }1103 }1104}