difftreelog
feat check nesting rule
in: master
10 files changed
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use up_data_structs::{CollectionId, TokenId};
+use up_data_structs::CollectionId;
use sp_core::H160;
// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -345,6 +345,13 @@
/// Not sufficient founds to perform action
NotSufficientFounds,
+
+ /// Collection has nesting disabled
+ NestingIsDisabled,
+ /// Only owner may nest tokens under this collection
+ OnlyOwnerAllowedToNest,
+ /// Only tokens from specific collections may nest tokens under this
+ SourceCollectionIsNotAllowedToNest,
}
#[pallet::storage]
@@ -400,8 +407,6 @@
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
- let mut weight = 0;
-
if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {
use up_data_structs::{CollectionVersion1, CollectionVersion2};
<CollectionById<T>>::translate_values::<CollectionVersion1<T::AccountId>, _>(|v| {
@@ -409,7 +414,7 @@
});
}
- weight
+ 0
}
}
}
@@ -774,6 +779,13 @@
data: BoundedVec<u8, CustomDataLimit>,
) -> DispatchResultWithPostInfo;
+ fn nest_token(
+ &self,
+ sender: T::CrossAccountId,
+ from: (CollectionId, TokenId),
+ under: TokenId,
+ ) -> DispatchResult;
+
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
fn token_exists(&self, token: TokenId) -> bool;
fn last_token_id(&self) -> TokenId;
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -228,6 +228,15 @@
fail!(<Error<T>>::FungibleItemsDontHaveData)
}
+ fn nest_token(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _from: (up_data_structs::CollectionId, TokenId),
+ _under: TokenId,
+ ) -> sp_runtime::DispatchResult {
+ fail!(<Error<T>>::FungibleDisallowsNesting)
+ }
+
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
if <Balance<T>>::get((self.id, account)) != 0 {
vec![TokenId::default()]
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -18,9 +18,14 @@
use core::ops::Deref;
use frame_support::{ensure};
-use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
-use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
use pallet_evm::account::CrossAccountId;
+use up_data_structs::{
+ AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,
+};
+use pallet_common::{
+ Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
+ CollectionHandle, dispatch::CollectionDispatch,
+};
use pallet_evm_coder_substrate::WithRecorder;
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
@@ -52,6 +57,8 @@
FungibleItemsHaveNoId,
/// Tried to set data for fungible item
FungibleItemsDontHaveData,
+ /// Fungible token does not support nested
+ FungibleDisallowsNesting,
}
#[pallet::config]
@@ -207,7 +214,15 @@
None
};
- // =========
+ 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.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;
+ }
if let Some(balance_to) = balance_to {
// from != to
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/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, CustomDataLimit, CreateItemExData};
+use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData, CollectionId};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
use sp_std::vec::Vec;
@@ -237,6 +237,15 @@
)
}
+ fn nest_token(
+ &self,
+ sender: T::CrossAccountId,
+ (from, _): (CollectionId, TokenId),
+ under: TokenId,
+ ) -> sp_runtime::DispatchResult {
+ <Pallet<T>>::nest_token(self, sender, from, under)
+ }
+
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
<Owned<T>>::iter_prefix((self.id, account))
.map(|(id, _)| id)
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -17,12 +17,16 @@
#![cfg_attr(not(feature = "std"), no_std)]
use erc::ERC721Events;
-use frame_support::{BoundedVec, ensure};
+use frame_support::{BoundedVec, ensure, fail};
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
+ mapping::TokenAddressMapping, NestingRule,
};
-use pallet_common::{Error as CommonError, Pallet as PalletCommon, Event as CommonEvent};
use pallet_evm::account::CrossAccountId;
+use pallet_common::{
+ Error as CommonError, Pallet as PalletCommon, Event as CommonEvent,
+ CollectionHandle, dispatch::CollectionDispatch,
+};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
@@ -282,8 +286,16 @@
None
};
- // =========
+ 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.nest_token(from.clone(), (collection.id, token), target.1)?;
+ }
+
<TokenData<T>>::insert(
(collection.id, token),
ItemData {
@@ -567,6 +579,40 @@
Ok(())
}
+ pub fn nest_token(
+ handle: &NonfungibleHandle<T>,
+ sender: T::CrossAccountId,
+ from: CollectionId,
+ under: TokenId,
+ ) -> DispatchResult {
+ fn ensure_sender_allowed<T: Config>(
+ collection: CollectionId,
+ token: TokenId,
+ sender: T::CrossAccountId,
+ ) -> DispatchResult {
+ ensure!(
+ <TokenData<T>>::get((collection, token))
+ .ok_or(<CommonError<T>>::TokenNotFound)?
+ .owner
+ .conv_eq(&sender),
+ <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::OwnerRestricted(whitelist) => {
+ ensure!(
+ whitelist.contains(&from),
+ <CommonError<T>>::SourceCollectionIsNotAllowedToNest
+ );
+ ensure_sender_allowed::<T>(from, under, sender)?
+ }
+ }
+ Ok(())
+ }
+
/// Delegated to `create_multiple_items`
pub fn create_item(
collection: &NonfungibleHandle<T>,
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -243,6 +243,15 @@
)
}
+ fn nest_token(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _from: (up_data_structs::CollectionId, TokenId),
+ _under: TokenId,
+ ) -> sp_runtime::DispatchResult {
+ fail!(<Error<T>>::RefungibleDisallowsNesting)
+ }
+
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
<Owned<T>>::iter_prefix((self.id, account))
.map(|(id, _)| id)
pallets/refungible/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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData,23};24use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};25use pallet_evm::account::CrossAccountId;26use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};27use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};28use core::ops::Deref;29use codec::{Encode, Decode, MaxEncodedLen};30use scale_info::TypeInfo;3132pub use pallet::*;33#[cfg(feature = "runtime-benchmarks")]34pub mod benchmarking;35pub mod common;36pub mod erc;37pub mod weights;38pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3940#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]41pub struct ItemData {42 pub const_data: BoundedVec<u8, CustomDataLimit>,43 pub variable_data: BoundedVec<u8, CustomDataLimit>,44}4546#[frame_support::pallet]47pub mod pallet {48 use super::*;49 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};50 use up_data_structs::{CollectionId, TokenId};51 use super::weights::WeightInfo;5253 #[pallet::error]54 pub enum Error<T> {55 /// Not Refungible item data used to mint in Refungible collection.56 NotRefungibleDataUsedToMintFungibleCollectionToken,57 /// Maximum refungibility exceeded58 WrongRefungiblePieces,59 }6061 #[pallet::config]62 pub trait Config: frame_system::Config + pallet_common::Config {63 type WeightInfo: WeightInfo;64 }6566 #[pallet::pallet]67 #[pallet::generate_store(pub(super) trait Store)]68 pub struct Pallet<T>(_);6970 #[pallet::storage]71 pub type TokensMinted<T: Config> =72 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;73 #[pallet::storage]74 pub type TokensBurnt<T: Config> =75 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7677 #[pallet::storage]78 pub type TokenData<T: Config> = StorageNMap<79 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),80 Value = ItemData,81 QueryKind = ValueQuery,82 >;8384 #[pallet::storage]85 pub type TotalSupply<T: Config> = StorageNMap<86 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),87 Value = u128,88 QueryKind = ValueQuery,89 >;9091 /// Used to enumerate tokens owned by account92 #[pallet::storage]93 pub type Owned<T: Config> = StorageNMap<94 Key = (95 Key<Twox64Concat, CollectionId>,96 Key<Blake2_128Concat, T::CrossAccountId>,97 Key<Twox64Concat, TokenId>,98 ),99 Value = bool,100 QueryKind = ValueQuery,101 >;102103 #[pallet::storage]104 pub type AccountBalance<T: Config> = StorageNMap<105 Key = (106 Key<Twox64Concat, CollectionId>,107 // Owner108 Key<Blake2_128Concat, T::CrossAccountId>,109 ),110 Value = u32,111 QueryKind = ValueQuery,112 >;113114 #[pallet::storage]115 pub type Balance<T: Config> = StorageNMap<116 Key = (117 Key<Twox64Concat, CollectionId>,118 Key<Twox64Concat, TokenId>,119 // Owner120 Key<Blake2_128Concat, T::CrossAccountId>,121 ),122 Value = u128,123 QueryKind = ValueQuery,124 >;125126 #[pallet::storage]127 pub type Allowance<T: Config> = StorageNMap<128 Key = (129 Key<Twox64Concat, CollectionId>,130 Key<Twox64Concat, TokenId>,131 // Owner132 Key<Blake2_128, T::CrossAccountId>,133 // Spender134 Key<Blake2_128Concat, T::CrossAccountId>,135 ),136 Value = u128,137 QueryKind = ValueQuery,138 >;139}140141pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);142impl<T: Config> RefungibleHandle<T> {143 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {144 Self(inner)145 }146 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {147 self.0148 }149}150impl<T: Config> Deref for RefungibleHandle<T> {151 type Target = pallet_common::CollectionHandle<T>;152153 fn deref(&self) -> &Self::Target {154 &self.0155 }156}157158impl<T: Config> Pallet<T> {159 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {160 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)161 }162 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {163 <TotalSupply<T>>::contains_key((collection.id, token))164 }165}166167// unchecked calls skips any permission checks168impl<T: Config> Pallet<T> {169 pub fn init_collection(170 owner: T::AccountId,171 data: CreateCollectionData<T::AccountId>,172 ) -> Result<CollectionId, DispatchError> {173 <PalletCommon<T>>::init_collection(owner, data)174 }175 pub fn destroy_collection(176 collection: RefungibleHandle<T>,177 sender: &T::CrossAccountId,178 ) -> DispatchResult {179 let id = collection.id;180181 // =========182183 PalletCommon::destroy_collection(collection.0, sender)?;184185 <TokensMinted<T>>::remove(id);186 <TokensBurnt<T>>::remove(id);187 <TokenData<T>>::remove_prefix((id,), None);188 <TotalSupply<T>>::remove_prefix((id,), None);189 <Balance<T>>::remove_prefix((id,), None);190 <Allowance<T>>::remove_prefix((id,), None);191 <Owned<T>>::remove_prefix((id,), None);192 <AccountBalance<T>>::remove_prefix((id,), None);193 Ok(())194 }195196 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {197 let burnt = <TokensBurnt<T>>::get(collection.id)198 .checked_add(1)199 .ok_or(ArithmeticError::Overflow)?;200201 <TokensBurnt<T>>::insert(collection.id, burnt);202 <TokenData<T>>::remove((collection.id, token_id));203 <TotalSupply<T>>::remove((collection.id, token_id));204 <Balance<T>>::remove_prefix((collection.id, token_id), None);205 <Allowance<T>>::remove_prefix((collection.id, token_id), None);206 // TODO: ERC721 transfer event207 Ok(())208 }209210 pub fn burn(211 collection: &RefungibleHandle<T>,212 owner: &T::CrossAccountId,213 token: TokenId,214 amount: u128,215 ) -> DispatchResult {216 let total_supply = <TotalSupply<T>>::get((collection.id, token))217 .checked_sub(amount)218 .ok_or(<CommonError<T>>::TokenValueTooLow)?;219220 // This was probally last owner of this token?221 if total_supply == 0 {222 // Ensure user actually owns this amount223 ensure!(224 <Balance<T>>::get((collection.id, token, owner)) == amount,225 <CommonError<T>>::TokenValueTooLow226 );227 let account_balance = <AccountBalance<T>>::get((collection.id, owner))228 .checked_sub(1)229 // Should not occur230 .ok_or(ArithmeticError::Underflow)?;231232 // =========233234 <Owned<T>>::remove((collection.id, owner, token));235 <AccountBalance<T>>::insert((collection.id, owner), account_balance);236 Self::burn_token(collection, token)?;237 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(238 collection.id,239 token,240 owner.clone(),241 amount,242 ));243 return Ok(());244 }245246 let balance = <Balance<T>>::get((collection.id, token, owner))247 .checked_sub(amount)248 .ok_or(<CommonError<T>>::TokenValueTooLow)?;249 let account_balance = if balance == 0 {250 <AccountBalance<T>>::get((collection.id, owner))251 .checked_sub(1)252 // Should not occur253 .ok_or(ArithmeticError::Underflow)?254 } else {255 0256 };257258 // =========259260 if balance == 0 {261 <Owned<T>>::remove((collection.id, owner, token));262 <Balance<T>>::remove((collection.id, token, owner));263 <AccountBalance<T>>::insert((collection.id, owner), account_balance);264 } else {265 <Balance<T>>::insert((collection.id, token, owner), balance);266 }267 <TotalSupply<T>>::insert((collection.id, token), total_supply);268 // TODO: ERC20 transfer event269 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(270 collection.id,271 token,272 owner.clone(),273 amount,274 ));275 Ok(())276 }277278 pub fn transfer(279 collection: &RefungibleHandle<T>,280 from: &T::CrossAccountId,281 to: &T::CrossAccountId,282 token: TokenId,283 amount: u128,284 ) -> DispatchResult {285 ensure!(286 collection.limits.transfers_enabled(),287 <CommonError<T>>::TransferNotAllowed288 );289290 if collection.access == AccessMode::AllowList {291 collection.check_allowlist(from)?;292 collection.check_allowlist(to)?;293 }294 <PalletCommon<T>>::ensure_correct_receiver(to)?;295296 let balance_from = <Balance<T>>::get((collection.id, token, from))297 .checked_sub(amount)298 .ok_or(<CommonError<T>>::TokenValueTooLow)?;299 let mut create_target = false;300 let from_to_differ = from != to;301 let balance_to = if from != to {302 let old_balance = <Balance<T>>::get((collection.id, token, to));303 if old_balance == 0 {304 create_target = true;305 }306 Some(307 old_balance308 .checked_add(amount)309 .ok_or(ArithmeticError::Overflow)?,310 )311 } else {312 None313 };314315 let account_balance_from = if balance_from == 0 {316 Some(317 <AccountBalance<T>>::get((collection.id, from))318 .checked_sub(1)319 // Should not occur320 .ok_or(ArithmeticError::Underflow)?,321 )322 } else {323 None324 };325 // Account data is created in token, AccountBalance should be increased326 // But only if from != to as we shouldn't check overflow in this case327 let account_balance_to = if create_target && from_to_differ {328 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))329 .checked_add(1)330 .ok_or(ArithmeticError::Overflow)?;331 ensure!(332 account_balance_to < collection.limits.account_token_ownership_limit(),333 <CommonError<T>>::AccountTokenLimitExceeded,334 );335336 Some(account_balance_to)337 } else {338 None339 };340341 // =========342343 if let Some(balance_to) = balance_to {344 // from != to345 if balance_from == 0 {346 <Balance<T>>::remove((collection.id, token, from));347 } else {348 <Balance<T>>::insert((collection.id, token, from), balance_from);349 }350 <Balance<T>>::insert((collection.id, token, to), balance_to);351 if let Some(account_balance_from) = account_balance_from {352 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);353 <Owned<T>>::remove((collection.id, from, token));354 }355 if let Some(account_balance_to) = account_balance_to {356 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);357 <Owned<T>>::insert((collection.id, to, token), true);358 }359 }360361 // TODO: ERC20 transfer event362 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(363 collection.id,364 token,365 from.clone(),366 to.clone(),367 amount,368 ));369 Ok(())370 }371372 pub fn create_multiple_items(373 collection: &RefungibleHandle<T>,374 sender: &T::CrossAccountId,375 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,376 ) -> DispatchResult {377 if !collection.is_owner_or_admin(sender) {378 ensure!(379 collection.mint_mode,380 <CommonError<T>>::PublicMintingNotAllowed381 );382 collection.check_allowlist(sender)?;383384 for item in data.iter() {385 for user in item.users.keys() {386 collection.check_allowlist(user)?;387 }388 }389 }390391 for item in data.iter() {392 for (owner, _) in item.users.iter() {393 <PalletCommon<T>>::ensure_correct_receiver(owner)?;394 }395 }396397 // Total pieces per tokens398 let totals = data399 .iter()400 .map(|data| {401 Ok(data402 .users403 .iter()404 .map(|u| u.1)405 .try_fold(0u128, |acc, v| acc.checked_add(*v))406 .ok_or(ArithmeticError::Overflow)?)407 })408 .collect::<Result<Vec<_>, DispatchError>>()?;409 for total in &totals {410 ensure!(411 *total <= MAX_REFUNGIBLE_PIECES,412 <Error<T>>::WrongRefungiblePieces413 );414 }415416 let first_token_id = <TokensMinted<T>>::get(collection.id);417 let tokens_minted = first_token_id418 .checked_add(data.len() as u32)419 .ok_or(ArithmeticError::Overflow)?;420 ensure!(421 tokens_minted < collection.limits.token_limit(),422 <CommonError<T>>::CollectionTokenLimitExceeded423 );424425 let mut balances = BTreeMap::new();426 for data in &data {427 for owner in data.users.keys() {428 let balance = balances429 .entry(owner)430 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));431 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;432433 ensure!(434 *balance <= collection.limits.account_token_ownership_limit(),435 <CommonError<T>>::AccountTokenLimitExceeded,436 );437 }438 }439440 // =========441442 <TokensMinted<T>>::insert(collection.id, tokens_minted);443 for (account, balance) in balances {444 <AccountBalance<T>>::insert((collection.id, account), balance);445 }446 for (i, token) in data.into_iter().enumerate() {447 let token_id = first_token_id + i as u32 + 1;448 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);449450 <TokenData<T>>::insert(451 (collection.id, token_id),452 ItemData {453 const_data: token.const_data,454 variable_data: token.variable_data,455 },456 );457 for (user, amount) in token.users.into_iter() {458 if amount == 0 {459 continue;460 }461 <Balance<T>>::insert((collection.id, token_id, &user), amount);462 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);463 // TODO: ERC20 transfer event464 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(465 collection.id,466 TokenId(token_id),467 user,468 amount,469 ));470 }471 }472 Ok(())473 }474475 pub fn set_allowance_unchecked(476 collection: &RefungibleHandle<T>,477 sender: &T::CrossAccountId,478 spender: &T::CrossAccountId,479 token: TokenId,480 amount: u128,481 ) {482 if amount == 0 {483 <Allowance<T>>::remove((collection.id, token, sender, spender));484 } else {485 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);486 }487 // TODO: ERC20 approval event488 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(489 collection.id,490 token,491 sender.clone(),492 spender.clone(),493 amount,494 ))495 }496497 pub fn set_allowance(498 collection: &RefungibleHandle<T>,499 sender: &T::CrossAccountId,500 spender: &T::CrossAccountId,501 token: TokenId,502 amount: u128,503 ) -> DispatchResult {504 if collection.access == AccessMode::AllowList {505 collection.check_allowlist(sender)?;506 collection.check_allowlist(spender)?;507 }508509 <PalletCommon<T>>::ensure_correct_receiver(spender)?;510511 if <Balance<T>>::get((collection.id, token, sender)) < amount {512 ensure!(513 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),514 <CommonError<T>>::CantApproveMoreThanOwned515 );516 }517518 // =========519520 Self::set_allowance_unchecked(collection, sender, spender, token, amount);521 Ok(())522 }523524 pub fn transfer_from(525 collection: &RefungibleHandle<T>,526 spender: &T::CrossAccountId,527 from: &T::CrossAccountId,528 to: &T::CrossAccountId,529 token: TokenId,530 amount: u128,531 ) -> DispatchResult {532 if spender.conv_eq(from) {533 return Self::transfer(collection, from, to, token, amount);534 }535 if collection.access == AccessMode::AllowList {536 // `from`, `to` checked in [`transfer`]537 collection.check_allowlist(spender)?;538 }539540 let allowance =541 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);542 if allowance.is_none() {543 ensure!(544 collection.ignores_allowance(spender),545 <CommonError<T>>::ApprovedValueTooLow546 );547 }548549 // =========550551 Self::transfer(collection, from, to, token, amount)?;552 if let Some(allowance) = allowance {553 Self::set_allowance_unchecked(collection, from, spender, token, allowance);554 }555 Ok(())556 }557558 pub fn burn_from(559 collection: &RefungibleHandle<T>,560 spender: &T::CrossAccountId,561 from: &T::CrossAccountId,562 token: TokenId,563 amount: u128,564 ) -> DispatchResult {565 if spender.conv_eq(from) {566 return Self::burn(collection, from, token, amount);567 }568 if collection.access == AccessMode::AllowList {569 // `from` checked in [`burn`]570 collection.check_allowlist(spender)?;571 }572573 let allowance =574 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);575 if allowance.is_none() {576 ensure!(577 collection.ignores_allowance(spender),578 <CommonError<T>>::ApprovedValueTooLow579 );580 }581582 // =========583584 Self::burn(collection, from, token, amount)?;585 if let Some(allowance) = allowance {586 Self::set_allowance_unchecked(collection, from, spender, token, allowance);587 }588 Ok(())589 }590591 pub fn set_variable_metadata(592 collection: &RefungibleHandle<T>,593 sender: &T::CrossAccountId,594 token: TokenId,595 data: BoundedVec<u8, CustomDataLimit>,596 ) -> DispatchResult {597 collection.check_can_update_meta(598 sender,599 &T::CrossAccountId::from_sub(collection.owner.clone()),600 )?;601602 let token_data = <TokenData<T>>::get((collection.id, token));603604 // =========605606 <TokenData<T>>::insert(607 (collection.id, token),608 ItemData {609 variable_data: data,610 ..token_data611 },612 );613 Ok(())614 }615616 /// Delegated to `create_multiple_items`617 pub fn create_item(618 collection: &RefungibleHandle<T>,619 sender: &T::CrossAccountId,620 data: CreateRefungibleExData<T::CrossAccountId>,621 ) -> DispatchResult {622 Self::create_multiple_items(collection, sender, vec![data])623 }624}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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 CollectionHandle, dispatch::CollectionDispatch,28};29use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};30use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};31use core::ops::Deref;32use codec::{Encode, Decode, MaxEncodedLen};33use scale_info::TypeInfo;3435pub use pallet::*;36#[cfg(feature = "runtime-benchmarks")]37pub mod benchmarking;38pub mod common;39pub mod erc;40pub mod weights;41pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4243#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]44pub struct ItemData {45 pub const_data: BoundedVec<u8, CustomDataLimit>,46 pub variable_data: BoundedVec<u8, CustomDataLimit>,47}4849#[frame_support::pallet]50pub mod pallet {51 use super::*;52 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};53 use up_data_structs::{CollectionId, TokenId};54 use super::weights::WeightInfo;5556 #[pallet::error]57 pub enum Error<T> {58 /// Not Refungible item data used to mint in Refungible collection.59 NotRefungibleDataUsedToMintFungibleCollectionToken,60 /// Maximum refungibility exceeded61 WrongRefungiblePieces,62 /// Refungible token can't nest other tokens63 RefungibleDisallowsNesting,64 }6566 #[pallet::config]67 pub trait Config: frame_system::Config + pallet_common::Config {68 type WeightInfo: WeightInfo;69 }7071 #[pallet::pallet]72 #[pallet::generate_store(pub(super) trait Store)]73 pub struct Pallet<T>(_);7475 #[pallet::storage]76 pub type TokensMinted<T: Config> =77 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;78 #[pallet::storage]79 pub type TokensBurnt<T: Config> =80 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;8182 #[pallet::storage]83 pub type TokenData<T: Config> = StorageNMap<84 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),85 Value = ItemData,86 QueryKind = ValueQuery,87 >;8889 #[pallet::storage]90 pub type TotalSupply<T: Config> = StorageNMap<91 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),92 Value = u128,93 QueryKind = ValueQuery,94 >;9596 /// Used to enumerate tokens owned by account97 #[pallet::storage]98 pub type Owned<T: Config> = StorageNMap<99 Key = (100 Key<Twox64Concat, CollectionId>,101 Key<Blake2_128Concat, T::CrossAccountId>,102 Key<Twox64Concat, TokenId>,103 ),104 Value = bool,105 QueryKind = ValueQuery,106 >;107108 #[pallet::storage]109 pub type AccountBalance<T: Config> = StorageNMap<110 Key = (111 Key<Twox64Concat, CollectionId>,112 // Owner113 Key<Blake2_128Concat, T::CrossAccountId>,114 ),115 Value = u32,116 QueryKind = ValueQuery,117 >;118119 #[pallet::storage]120 pub type Balance<T: Config> = StorageNMap<121 Key = (122 Key<Twox64Concat, CollectionId>,123 Key<Twox64Concat, TokenId>,124 // Owner125 Key<Blake2_128Concat, T::CrossAccountId>,126 ),127 Value = u128,128 QueryKind = ValueQuery,129 >;130131 #[pallet::storage]132 pub type Allowance<T: Config> = StorageNMap<133 Key = (134 Key<Twox64Concat, CollectionId>,135 Key<Twox64Concat, TokenId>,136 // Owner137 Key<Blake2_128, T::CrossAccountId>,138 // Spender139 Key<Blake2_128Concat, T::CrossAccountId>,140 ),141 Value = u128,142 QueryKind = ValueQuery,143 >;144}145146pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);147impl<T: Config> RefungibleHandle<T> {148 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {149 Self(inner)150 }151 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {152 self.0153 }154}155impl<T: Config> Deref for RefungibleHandle<T> {156 type Target = pallet_common::CollectionHandle<T>;157158 fn deref(&self) -> &Self::Target {159 &self.0160 }161}162163impl<T: Config> Pallet<T> {164 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {165 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)166 }167 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {168 <TotalSupply<T>>::contains_key((collection.id, token))169 }170}171172// unchecked calls skips any permission checks173impl<T: Config> Pallet<T> {174 pub fn init_collection(175 owner: T::AccountId,176 data: CreateCollectionData<T::AccountId>,177 ) -> Result<CollectionId, DispatchError> {178 <PalletCommon<T>>::init_collection(owner, data)179 }180 pub fn destroy_collection(181 collection: RefungibleHandle<T>,182 sender: &T::CrossAccountId,183 ) -> DispatchResult {184 let id = collection.id;185186 // =========187188 PalletCommon::destroy_collection(collection.0, sender)?;189190 <TokensMinted<T>>::remove(id);191 <TokensBurnt<T>>::remove(id);192 <TokenData<T>>::remove_prefix((id,), None);193 <TotalSupply<T>>::remove_prefix((id,), None);194 <Balance<T>>::remove_prefix((id,), None);195 <Allowance<T>>::remove_prefix((id,), None);196 <Owned<T>>::remove_prefix((id,), None);197 <AccountBalance<T>>::remove_prefix((id,), None);198 Ok(())199 }200201 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {202 let burnt = <TokensBurnt<T>>::get(collection.id)203 .checked_add(1)204 .ok_or(ArithmeticError::Overflow)?;205206 <TokensBurnt<T>>::insert(collection.id, burnt);207 <TokenData<T>>::remove((collection.id, token_id));208 <TotalSupply<T>>::remove((collection.id, token_id));209 <Balance<T>>::remove_prefix((collection.id, token_id), None);210 <Allowance<T>>::remove_prefix((collection.id, token_id), None);211 // TODO: ERC721 transfer event212 Ok(())213 }214215 pub fn burn(216 collection: &RefungibleHandle<T>,217 owner: &T::CrossAccountId,218 token: TokenId,219 amount: u128,220 ) -> DispatchResult {221 let total_supply = <TotalSupply<T>>::get((collection.id, token))222 .checked_sub(amount)223 .ok_or(<CommonError<T>>::TokenValueTooLow)?;224225 // This was probally last owner of this token?226 if total_supply == 0 {227 // Ensure user actually owns this amount228 ensure!(229 <Balance<T>>::get((collection.id, token, owner)) == amount,230 <CommonError<T>>::TokenValueTooLow231 );232 let account_balance = <AccountBalance<T>>::get((collection.id, owner))233 .checked_sub(1)234 // Should not occur235 .ok_or(ArithmeticError::Underflow)?;236237 // =========238239 <Owned<T>>::remove((collection.id, owner, token));240 <AccountBalance<T>>::insert((collection.id, owner), account_balance);241 Self::burn_token(collection, token)?;242 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(243 collection.id,244 token,245 owner.clone(),246 amount,247 ));248 return Ok(());249 }250251 let balance = <Balance<T>>::get((collection.id, token, owner))252 .checked_sub(amount)253 .ok_or(<CommonError<T>>::TokenValueTooLow)?;254 let account_balance = if balance == 0 {255 <AccountBalance<T>>::get((collection.id, owner))256 .checked_sub(1)257 // Should not occur258 .ok_or(ArithmeticError::Underflow)?259 } else {260 0261 };262263 // =========264265 if balance == 0 {266 <Owned<T>>::remove((collection.id, owner, token));267 <Balance<T>>::remove((collection.id, token, owner));268 <AccountBalance<T>>::insert((collection.id, owner), account_balance);269 } else {270 <Balance<T>>::insert((collection.id, token, owner), balance);271 }272 <TotalSupply<T>>::insert((collection.id, token), total_supply);273 // TODO: ERC20 transfer event274 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(275 collection.id,276 token,277 owner.clone(),278 amount,279 ));280 Ok(())281 }282283 pub fn transfer(284 collection: &RefungibleHandle<T>,285 from: &T::CrossAccountId,286 to: &T::CrossAccountId,287 token: TokenId,288 amount: u128,289 ) -> DispatchResult {290 ensure!(291 collection.limits.transfers_enabled(),292 <CommonError<T>>::TransferNotAllowed293 );294295 if collection.access == AccessMode::AllowList {296 collection.check_allowlist(from)?;297 collection.check_allowlist(to)?;298 }299 <PalletCommon<T>>::ensure_correct_receiver(to)?;300301 let balance_from = <Balance<T>>::get((collection.id, token, from))302 .checked_sub(amount)303 .ok_or(<CommonError<T>>::TokenValueTooLow)?;304 let mut create_target = false;305 let from_to_differ = from != to;306 let balance_to = if from != to {307 let old_balance = <Balance<T>>::get((collection.id, token, to));308 if old_balance == 0 {309 create_target = true;310 }311 Some(312 old_balance313 .checked_add(amount)314 .ok_or(ArithmeticError::Overflow)?,315 )316 } else {317 None318 };319320 let account_balance_from = if balance_from == 0 {321 Some(322 <AccountBalance<T>>::get((collection.id, from))323 .checked_sub(1)324 // Should not occur325 .ok_or(ArithmeticError::Underflow)?,326 )327 } else {328 None329 };330 // Account data is created in token, AccountBalance should be increased331 // But only if from != to as we shouldn't check overflow in this case332 let account_balance_to = if create_target && from_to_differ {333 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))334 .checked_add(1)335 .ok_or(ArithmeticError::Overflow)?;336 ensure!(337 account_balance_to < collection.limits.account_token_ownership_limit(),338 <CommonError<T>>::AccountTokenLimitExceeded,339 );340341 Some(account_balance_to)342 } else {343 None344 };345346 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {347 let handle = <CollectionHandle<T>>::try_get(target.0)?;348 let dispatch = T::CollectionDispatch::dispatch(handle);349 let dispatch = dispatch.as_dyn();350351 // =========352353 dispatch.nest_token(from.clone(), (collection.id, token), target.1)?;354 }355356 if let Some(balance_to) = balance_to {357 // from != to358 if balance_from == 0 {359 <Balance<T>>::remove((collection.id, token, from));360 } else {361 <Balance<T>>::insert((collection.id, token, from), balance_from);362 }363 <Balance<T>>::insert((collection.id, token, to), balance_to);364 if let Some(account_balance_from) = account_balance_from {365 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);366 <Owned<T>>::remove((collection.id, from, token));367 }368 if let Some(account_balance_to) = account_balance_to {369 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);370 <Owned<T>>::insert((collection.id, to, token), true);371 }372 }373374 // TODO: ERC20 transfer event375 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(376 collection.id,377 token,378 from.clone(),379 to.clone(),380 amount,381 ));382 Ok(())383 }384385 pub fn create_multiple_items(386 collection: &RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,389 ) -> DispatchResult {390 if !collection.is_owner_or_admin(sender) {391 ensure!(392 collection.mint_mode,393 <CommonError<T>>::PublicMintingNotAllowed394 );395 collection.check_allowlist(sender)?;396397 for item in data.iter() {398 for user in item.users.keys() {399 collection.check_allowlist(user)?;400 }401 }402 }403404 for item in data.iter() {405 for (owner, _) in item.users.iter() {406 <PalletCommon<T>>::ensure_correct_receiver(owner)?;407 }408 }409410 // Total pieces per tokens411 let totals = data412 .iter()413 .map(|data| {414 Ok(data415 .users416 .iter()417 .map(|u| u.1)418 .try_fold(0u128, |acc, v| acc.checked_add(*v))419 .ok_or(ArithmeticError::Overflow)?)420 })421 .collect::<Result<Vec<_>, DispatchError>>()?;422 for total in &totals {423 ensure!(424 *total <= MAX_REFUNGIBLE_PIECES,425 <Error<T>>::WrongRefungiblePieces426 );427 }428429 let first_token_id = <TokensMinted<T>>::get(collection.id);430 let tokens_minted = first_token_id431 .checked_add(data.len() as u32)432 .ok_or(ArithmeticError::Overflow)?;433 ensure!(434 tokens_minted < collection.limits.token_limit(),435 <CommonError<T>>::CollectionTokenLimitExceeded436 );437438 let mut balances = BTreeMap::new();439 for data in &data {440 for owner in data.users.keys() {441 let balance = balances442 .entry(owner)443 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));444 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;445446 ensure!(447 *balance <= collection.limits.account_token_ownership_limit(),448 <CommonError<T>>::AccountTokenLimitExceeded,449 );450 }451 }452453 // =========454455 <TokensMinted<T>>::insert(collection.id, tokens_minted);456 for (account, balance) in balances {457 <AccountBalance<T>>::insert((collection.id, account), balance);458 }459 for (i, token) in data.into_iter().enumerate() {460 let token_id = first_token_id + i as u32 + 1;461 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);462463 <TokenData<T>>::insert(464 (collection.id, token_id),465 ItemData {466 const_data: token.const_data,467 variable_data: token.variable_data,468 },469 );470 for (user, amount) in token.users.into_iter() {471 if amount == 0 {472 continue;473 }474 <Balance<T>>::insert((collection.id, token_id, &user), amount);475 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);476 // TODO: ERC20 transfer event477 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(478 collection.id,479 TokenId(token_id),480 user,481 amount,482 ));483 }484 }485 Ok(())486 }487488 pub fn set_allowance_unchecked(489 collection: &RefungibleHandle<T>,490 sender: &T::CrossAccountId,491 spender: &T::CrossAccountId,492 token: TokenId,493 amount: u128,494 ) {495 if amount == 0 {496 <Allowance<T>>::remove((collection.id, token, sender, spender));497 } else {498 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);499 }500 // TODO: ERC20 approval event501 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(502 collection.id,503 token,504 sender.clone(),505 spender.clone(),506 amount,507 ))508 }509510 pub fn set_allowance(511 collection: &RefungibleHandle<T>,512 sender: &T::CrossAccountId,513 spender: &T::CrossAccountId,514 token: TokenId,515 amount: u128,516 ) -> DispatchResult {517 if collection.access == AccessMode::AllowList {518 collection.check_allowlist(sender)?;519 collection.check_allowlist(spender)?;520 }521522 <PalletCommon<T>>::ensure_correct_receiver(spender)?;523524 if <Balance<T>>::get((collection.id, token, sender)) < amount {525 ensure!(526 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),527 <CommonError<T>>::CantApproveMoreThanOwned528 );529 }530531 // =========532533 Self::set_allowance_unchecked(collection, sender, spender, token, amount);534 Ok(())535 }536537 pub fn transfer_from(538 collection: &RefungibleHandle<T>,539 spender: &T::CrossAccountId,540 from: &T::CrossAccountId,541 to: &T::CrossAccountId,542 token: TokenId,543 amount: u128,544 ) -> DispatchResult {545 if spender.conv_eq(from) {546 return Self::transfer(collection, from, to, token, amount);547 }548 if collection.access == AccessMode::AllowList {549 // `from`, `to` checked in [`transfer`]550 collection.check_allowlist(spender)?;551 }552553 let allowance =554 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);555 if allowance.is_none() {556 ensure!(557 collection.ignores_allowance(spender),558 <CommonError<T>>::ApprovedValueTooLow559 );560 }561562 // =========563564 Self::transfer(collection, from, to, token, amount)?;565 if let Some(allowance) = allowance {566 Self::set_allowance_unchecked(collection, from, spender, token, allowance);567 }568 Ok(())569 }570571 pub fn burn_from(572 collection: &RefungibleHandle<T>,573 spender: &T::CrossAccountId,574 from: &T::CrossAccountId,575 token: TokenId,576 amount: u128,577 ) -> DispatchResult {578 if spender.conv_eq(from) {579 return Self::burn(collection, from, token, amount);580 }581 if collection.access == AccessMode::AllowList {582 // `from` checked in [`burn`]583 collection.check_allowlist(spender)?;584 }585586 let allowance =587 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);588 if allowance.is_none() {589 ensure!(590 collection.ignores_allowance(spender),591 <CommonError<T>>::ApprovedValueTooLow592 );593 }594595 // =========596597 Self::burn(collection, from, token, amount)?;598 if let Some(allowance) = allowance {599 Self::set_allowance_unchecked(collection, from, spender, token, allowance);600 }601 Ok(())602 }603604 pub fn set_variable_metadata(605 collection: &RefungibleHandle<T>,606 sender: &T::CrossAccountId,607 token: TokenId,608 data: BoundedVec<u8, CustomDataLimit>,609 ) -> DispatchResult {610 collection.check_can_update_meta(611 sender,612 &T::CrossAccountId::from_sub(collection.owner.clone()),613 )?;614615 let token_data = <TokenData<T>>::get((collection.id, token));616617 // =========618619 <TokenData<T>>::insert(620 (collection.id, token),621 ItemData {622 variable_data: data,623 ..token_data624 },625 );626 Ok(())627 }628629 /// Delegated to `create_multiple_items`630 pub fn create_item(631 collection: &RefungibleHandle<T>,632 sender: &T::CrossAccountId,633 data: CreateRefungibleExData<T::CrossAccountId>,634 ) -> DispatchResult {635 Self::create_multiple_items(collection, sender, vec![data])636 }637}primitives/data-structs/src/bounded.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/data-structs/src/bounded.rs
@@ -0,0 +1,138 @@
+use core::fmt;
+use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
+use sp_std::vec::Vec;
+
+use frame_support::{
+ BoundedVec,
+ storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
+};
+
+/// BoundedVec doesn't supports serde
+#[cfg(feature = "serde1")]
+pub mod vec_serde {
+ use core::convert::TryFrom;
+ use frame_support::{BoundedVec, traits::Get};
+ use serde::{
+ ser::{self, Serialize},
+ de::{self, Deserialize, Error},
+ };
+ use sp_std::vec::Vec;
+
+ pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ V: Serialize,
+ {
+ (value as &Vec<_>).serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ V: de::Deserialize<'de>,
+ S: Get<u32>,
+ {
+ // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?
+ let vec = <Vec<V>>::deserialize(deserializer)?;
+ let len = vec.len();
+ TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+ }
+}
+
+pub fn vec_debug<V, S>(v: &BoundedVec<V, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
+where
+ V: fmt::Debug,
+{
+ use core::fmt::Debug;
+ (&v as &Vec<V>).fmt(f)
+}
+
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+pub mod map_serde {
+ use core::convert::TryFrom;
+ use sp_std::collections::btree_map::BTreeMap;
+ use frame_support::{traits::Get, storage::bounded_btree_map::BoundedBTreeMap};
+ use serde::{
+ ser::{self, Serialize},
+ de::{self, Deserialize, Error},
+ };
+ pub fn serialize<D, K, V, S>(
+ value: &BoundedBTreeMap<K, V, S>,
+ serializer: D,
+ ) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ K: Serialize + Ord,
+ V: Serialize,
+ {
+ (value as &BTreeMap<_, _>).serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D, K, V, S>(
+ deserializer: D,
+ ) -> Result<BoundedBTreeMap<K, V, S>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ K: de::Deserialize<'de> + Ord,
+ V: de::Deserialize<'de>,
+ S: Get<u32>,
+ {
+ let map = <BTreeMap<K, V>>::deserialize(deserializer)?;
+ let len = map.len();
+ TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+ }
+}
+
+pub fn map_debug<K, V, S>(
+ v: &BoundedBTreeMap<K, V, S>,
+ f: &mut fmt::Formatter,
+) -> Result<(), fmt::Error>
+where
+ K: fmt::Debug + Ord,
+ V: fmt::Debug,
+{
+ use core::fmt::Debug;
+ (&v as &BTreeMap<K, V>).fmt(f)
+}
+
+#[cfg(feature = "serde1")]
+#[allow(dead_code)]
+pub mod set_serde {
+ use core::convert::TryFrom;
+ use sp_std::collections::btree_set::BTreeSet;
+ use frame_support::{traits::Get, storage::bounded_btree_set::BoundedBTreeSet};
+ use serde::{
+ ser::{self, Serialize},
+ de::{self, Deserialize, Error},
+ };
+ pub fn serialize<D, K, S>(
+ value: &BoundedBTreeSet<K, S>,
+ serializer: D,
+ ) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ K: Serialize + Ord,
+ {
+ (value as &BTreeSet<_>).serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D, K, S>(deserializer: D) -> Result<BoundedBTreeSet<K, S>, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ K: de::Deserialize<'de> + Ord,
+ S: Get<u32>,
+ {
+ let map = <BTreeSet<K>>::deserialize(deserializer)?;
+ let len = map.len();
+ TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
+ }
+}
+
+pub fn set_debug<K, S>(v: &BoundedBTreeSet<K, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
+where
+ K: fmt::Debug + Ord,
+{
+ use core::fmt::Debug;
+ (&v as &BTreeSet<K>).fmt(f)
+}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -22,9 +22,7 @@
};
use frame_support::{
storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
- traits::ConstU16,
};
-use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
@@ -36,6 +34,7 @@
use derivative::Derivative;
use scale_info::TypeInfo;
+mod bounded;
pub mod mapping;
mod migration;
@@ -255,14 +254,14 @@
pub owner: AccountId,
pub mode: CollectionMode,
pub access: AccessMode,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
pub mint_mode: bool,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
pub schema_version: SchemaVersion,
pub sponsorship: SponsorshipState<AccountId>,
@@ -272,9 +271,9 @@
#[version(2.., upper(limits.into()))]
pub limits: CollectionLimitsVersion2,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
pub meta_update_permission: MetaUpdatePermission,
}
@@ -286,20 +285,20 @@
#[derivative(Default(value = "CollectionMode::NFT"))]
pub mode: CollectionMode,
pub access: Option<AccessMode>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
pub schema_version: Option<SchemaVersion>,
pub pending_sponsor: Option<AccountId>,
pub limits: Option<CollectionLimits>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
pub meta_update_permission: Option<MetaUpdatePermission>,
}
@@ -410,8 +409,8 @@
Owner,
/// Owner can nest tokens from specified collections
OwnerRestricted(
- #[cfg_attr(feature = "serde1", serde(with = "bounded_set_serde"))]
- #[derivative(Debug(format_with = "bounded_set_debug"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::set_serde"))]
+ #[derivative(Debug(format_with = "bounded::set_debug"))]
BoundedBTreeSet<CollectionId, ConstU32<16>>,
),
}
@@ -421,150 +420,17 @@
pub enum SponsoringRateLimit {
SponsoringDisabled,
Blocks(u32),
-}
-
-/// BoundedVec doesn't supports serde
-#[cfg(feature = "serde1")]
-mod bounded_serde {
- use core::convert::TryFrom;
- use frame_support::{BoundedVec, traits::Get};
- use serde::{
- ser::{self, Serialize},
- de::{self, Deserialize, Error},
- };
- use sp_std::vec::Vec;
-
- pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- V: Serialize,
- {
- (value as &Vec<_>).serialize(serializer)
- }
-
- pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>
- where
- D: de::Deserializer<'de>,
- V: de::Deserialize<'de>,
- S: Get<u32>,
- {
- // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?
- let vec = <Vec<V>>::deserialize(deserializer)?;
- let len = vec.len();
- TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
- }
-}
-
-fn bounded_debug<V, S>(v: &BoundedVec<V, S>, f: &mut fmt::Formatter) -> Result<(), fmt::Error>
-where
- V: fmt::Debug,
-{
- use core::fmt::Debug;
- (&v as &Vec<V>).fmt(f)
-}
-
-#[cfg(feature = "serde1")]
-#[allow(dead_code)]
-mod bounded_map_serde {
- use core::convert::TryFrom;
- use sp_std::collections::btree_map::BTreeMap;
- use frame_support::{traits::Get, storage::bounded_btree_map::BoundedBTreeMap};
- use serde::{
- ser::{self, Serialize},
- de::{self, Deserialize, Error},
- };
- pub fn serialize<D, K, V, S>(
- value: &BoundedBTreeMap<K, V, S>,
- serializer: D,
- ) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- K: Serialize + Ord,
- V: Serialize,
- {
- (value as &BTreeMap<_, _>).serialize(serializer)
- }
-
- pub fn deserialize<'de, D, K, V, S>(
- deserializer: D,
- ) -> Result<BoundedBTreeMap<K, V, S>, D::Error>
- where
- D: de::Deserializer<'de>,
- K: de::Deserialize<'de> + Ord,
- V: de::Deserialize<'de>,
- S: Get<u32>,
- {
- let map = <BTreeMap<K, V>>::deserialize(deserializer)?;
- let len = map.len();
- TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
- }
-}
-
-fn bounded_map_debug<K, V, S>(
- v: &BoundedBTreeMap<K, V, S>,
- f: &mut fmt::Formatter,
-) -> Result<(), fmt::Error>
-where
- K: fmt::Debug + Ord,
- V: fmt::Debug,
-{
- use core::fmt::Debug;
- (&v as &BTreeMap<K, V>).fmt(f)
-}
-
-#[cfg(feature = "serde1")]
-#[allow(dead_code)]
-mod bounded_set_serde {
- use core::convert::TryFrom;
- use sp_std::collections::btree_set::BTreeSet;
- use frame_support::{traits::Get, storage::bounded_btree_set::BoundedBTreeSet};
- use serde::{
- ser::{self, Serialize},
- de::{self, Deserialize, Error},
- };
- pub fn serialize<D, K, S>(
- value: &BoundedBTreeSet<K, S>,
- serializer: D,
- ) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- K: Serialize + Ord,
- {
- (value as &BTreeSet<_>).serialize(serializer)
- }
-
- pub fn deserialize<'de, D, K, S>(deserializer: D) -> Result<BoundedBTreeSet<K, S>, D::Error>
- where
- D: de::Deserializer<'de>,
- K: de::Deserialize<'de> + Ord,
- S: Get<u32>,
- {
- let map = <BTreeSet<K>>::deserialize(deserializer)?;
- let len = map.len();
- TryFrom::try_from(map).map_err(|_| D::Error::invalid_length(len, &"lesser size"))
- }
}
-fn bounded_set_debug<K, S>(
- v: &BoundedBTreeSet<K, S>,
- f: &mut fmt::Formatter,
-) -> Result<(), fmt::Error>
-where
- K: fmt::Debug + Ord,
-{
- use core::fmt::Debug;
- (&v as &BTreeSet<K>).fmt(f)
-}
-
#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derivative(Debug)]
pub struct CreateNftData {
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub variable_data: BoundedVec<u8, CustomDataLimit>,
}
@@ -578,11 +444,11 @@
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derivative(Debug)]
pub struct CreateReFungibleData {
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub variable_data: BoundedVec<u8, CustomDataLimit>,
pub pieces: u128,
}
@@ -612,9 +478,9 @@
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug)]
pub struct CreateNftExData<CrossAccountId> {
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub variable_data: BoundedVec<u8, CustomDataLimit>,
pub owner: CrossAccountId,
}
@@ -622,11 +488,11 @@
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub struct CreateRefungibleExData<CrossAccountId> {
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
pub variable_data: BoundedVec<u8, CustomDataLimit>,
- #[derivative(Debug(format_with = "bounded_map_debug"))]
+ #[derivative(Debug(format_with = "bounded::map_debug"))]
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
}
@@ -634,16 +500,16 @@
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub enum CreateItemExData<CrossAccountId> {
NFT(
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
BoundedVec<CreateNftExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
),
Fungible(
- #[derivative(Debug(format_with = "bounded_map_debug"))]
+ #[derivative(Debug(format_with = "bounded::map_debug"))]
BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
),
/// Many tokens, each may have only one owner
RefungibleMultipleItems(
- #[derivative(Debug(format_with = "bounded_debug"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
BoundedVec<CreateRefungibleExData<CrossAccountId>, ConstU32<MAX_ITEMS_PER_BATCH>>,
),
/// Single token, which may have many owners