1234567891011121314151617#![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 59 NotRefungibleDataUsedToMintFungibleCollectionToken,60 61 WrongRefungiblePieces,62 63 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 97 #[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 113 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 125 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 137 Key<Blake2_128, T::CrossAccountId>,138 139 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}171172173impl<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 212 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 226 if total_supply == 0 {227 228 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 235 .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 258 .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 274 <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 325 .ok_or(ArithmeticError::Underflow)?,326 )327 } else {328 None329 };330 331 332 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 358 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 375 <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 411 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 477 <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 501 <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 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 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 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}