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, budget::Budget,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};26use pallet_structure::Pallet as PalletStructure;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};29use core::ops::Deref;30use codec::{Encode, Decode, MaxEncodedLen};31use scale_info::TypeInfo;3233pub use pallet::*;34#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod common;37pub mod erc;38pub mod weights;39pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;40414243#[struct_versioning::versioned(version = 2, upper)]44#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]45pub struct ItemData {46 pub const_data: BoundedVec<u8, CustomDataLimit>,4748 #[version(..2)]49 pub variable_data: BoundedVec<u8, CustomDataLimit>,50}5152#[frame_support::pallet]53pub mod pallet {54 use super::*;55 use frame_support::{56 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,57 traits::StorageVersion,58 };59 use frame_system::pallet_prelude::*;60 use up_data_structs::{CollectionId, TokenId};61 use super::weights::WeightInfo;6263 #[pallet::error]64 pub enum Error<T> {65 66 NotRefungibleDataUsedToMintFungibleCollectionToken,67 68 WrongRefungiblePieces,69 70 RepartitionWhileNotOwningAllPieces,71 72 RefungibleDisallowsNesting,73 74 SettingPropertiesNotAllowed,75 }7677 #[pallet::config]78 pub trait Config:79 frame_system::Config + pallet_common::Config + pallet_structure::Config80 {81 type WeightInfo: WeightInfo;82 }8384 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8586 #[pallet::pallet]87 #[pallet::storage_version(STORAGE_VERSION)]88 #[pallet::generate_store(pub(super) trait Store)]89 pub struct Pallet<T>(_);9091 92 #[pallet::storage]93 pub type TokensMinted<T: Config> =94 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9596 97 #[pallet::storage]98 pub type TokensBurnt<T: Config> =99 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;100101 102 #[pallet::storage]103 pub type TokenData<T: Config> = StorageNMap<104 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),105 Value = ItemData,106 QueryKind = ValueQuery,107 >;108109 110 #[pallet::storage]111 pub type TotalSupply<T: Config> = StorageNMap<112 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),113 Value = u128,114 QueryKind = ValueQuery,115 >;116117 118 #[pallet::storage]119 pub type Owned<T: Config> = StorageNMap<120 Key = (121 Key<Twox64Concat, CollectionId>,122 Key<Blake2_128Concat, T::CrossAccountId>,123 Key<Twox64Concat, TokenId>,124 ),125 Value = bool,126 QueryKind = ValueQuery,127 >;128129 130 #[pallet::storage]131 pub type AccountBalance<T: Config> = StorageNMap<132 Key = (133 Key<Twox64Concat, CollectionId>,134 135 Key<Blake2_128Concat, T::CrossAccountId>,136 ),137 Value = u32,138 QueryKind = ValueQuery,139 >;140141 142 #[pallet::storage]143 pub type Balance<T: Config> = StorageNMap<144 Key = (145 Key<Twox64Concat, CollectionId>,146 Key<Twox64Concat, TokenId>,147 148 Key<Blake2_128Concat, T::CrossAccountId>,149 ),150 Value = u128,151 QueryKind = ValueQuery,152 >;153154 155 #[pallet::storage]156 pub type Allowance<T: Config> = StorageNMap<157 Key = (158 Key<Twox64Concat, CollectionId>,159 Key<Twox64Concat, TokenId>,160 161 Key<Blake2_128, T::CrossAccountId>,162 163 Key<Blake2_128Concat, T::CrossAccountId>,164 ),165 Value = u128,166 QueryKind = ValueQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 <TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {174 Some(<ItemDataVersion2>::from(v))175 })176 }177178 0179 }180 }181}182183pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);184impl<T: Config> RefungibleHandle<T> {185 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {186 Self(inner)187 }188 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189 self.0190 }191}192impl<T: Config> Deref for RefungibleHandle<T> {193 type Target = pallet_common::CollectionHandle<T>;194195 fn deref(&self) -> &Self::Target {196 &self.0197 }198}199200impl<T: Config> Pallet<T> {201 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {202 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)203 }204 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {205 <TotalSupply<T>>::contains_key((collection.id, token))206 }207}208209210impl<T: Config> Pallet<T> {211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, false)216 }217 pub fn destroy_collection(218 collection: RefungibleHandle<T>,219 sender: &T::CrossAccountId,220 ) -> DispatchResult {221 let id = collection.id;222223 if Self::collection_has_tokens(id) {224 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());225 }226227 228229 PalletCommon::destroy_collection(collection.0, sender)?;230231 <TokensMinted<T>>::remove(id);232 <TokensBurnt<T>>::remove(id);233 <TokenData<T>>::remove_prefix((id,), None);234 <TotalSupply<T>>::remove_prefix((id,), None);235 <Balance<T>>::remove_prefix((id,), None);236 <Allowance<T>>::remove_prefix((id,), None);237 <Owned<T>>::remove_prefix((id,), None);238 <AccountBalance<T>>::remove_prefix((id,), None);239 Ok(())240 }241242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TokenData<T>>::iter_prefix((collection_id,))244 .next()245 .is_some()246 }247248 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {249 let burnt = <TokensBurnt<T>>::get(collection.id)250 .checked_add(1)251 .ok_or(ArithmeticError::Overflow)?;252253 <TokensBurnt<T>>::insert(collection.id, burnt);254 <TokenData<T>>::remove((collection.id, token_id));255 <TotalSupply<T>>::remove((collection.id, token_id));256 <Balance<T>>::remove_prefix((collection.id, token_id), None);257 <Allowance<T>>::remove_prefix((collection.id, token_id), None);258 259 Ok(())260 }261262 pub fn burn(263 collection: &RefungibleHandle<T>,264 owner: &T::CrossAccountId,265 token: TokenId,266 amount: u128,267 ) -> DispatchResult {268 let total_supply = <TotalSupply<T>>::get((collection.id, token))269 .checked_sub(amount)270 .ok_or(<CommonError<T>>::TokenValueTooLow)?;271272 273 if total_supply == 0 {274 275 ensure!(276 <Balance<T>>::get((collection.id, token, owner)) == amount,277 <CommonError<T>>::TokenValueTooLow278 );279 let account_balance = <AccountBalance<T>>::get((collection.id, owner))280 .checked_sub(1)281 282 .ok_or(ArithmeticError::Underflow)?;283284 285286 <Owned<T>>::remove((collection.id, owner, token));287 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);288 <AccountBalance<T>>::insert((collection.id, owner), account_balance);289 Self::burn_token(collection, token)?;290 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(291 collection.id,292 token,293 owner.clone(),294 amount,295 ));296 return Ok(());297 }298299 let balance = <Balance<T>>::get((collection.id, token, owner))300 .checked_sub(amount)301 .ok_or(<CommonError<T>>::TokenValueTooLow)?;302 let account_balance = if balance == 0 {303 <AccountBalance<T>>::get((collection.id, owner))304 .checked_sub(1)305 306 .ok_or(ArithmeticError::Underflow)?307 } else {308 0309 };310311 312313 if balance == 0 {314 <Owned<T>>::remove((collection.id, owner, token));315 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);316 <Balance<T>>::remove((collection.id, token, owner));317 <AccountBalance<T>>::insert((collection.id, owner), account_balance);318 } else {319 <Balance<T>>::insert((collection.id, token, owner), balance);320 }321 <TotalSupply<T>>::insert((collection.id, token), total_supply);322 323 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(324 collection.id,325 token,326 owner.clone(),327 amount,328 ));329 Ok(())330 }331332 pub fn transfer(333 collection: &RefungibleHandle<T>,334 from: &T::CrossAccountId,335 to: &T::CrossAccountId,336 token: TokenId,337 amount: u128,338 nesting_budget: &dyn Budget,339 ) -> DispatchResult {340 ensure!(341 collection.limits.transfers_enabled(),342 <CommonError<T>>::TransferNotAllowed343 );344345 if collection.permissions.access() == AccessMode::AllowList {346 collection.check_allowlist(from)?;347 collection.check_allowlist(to)?;348 }349 <PalletCommon<T>>::ensure_correct_receiver(to)?;350351 let balance_from = <Balance<T>>::get((collection.id, token, from))352 .checked_sub(amount)353 .ok_or(<CommonError<T>>::TokenValueTooLow)?;354 let mut create_target = false;355 let from_to_differ = from != to;356 let balance_to = if from != to {357 let old_balance = <Balance<T>>::get((collection.id, token, to));358 if old_balance == 0 {359 create_target = true;360 }361 Some(362 old_balance363 .checked_add(amount)364 .ok_or(ArithmeticError::Overflow)?,365 )366 } else {367 None368 };369370 let account_balance_from = if balance_from == 0 {371 Some(372 <AccountBalance<T>>::get((collection.id, from))373 .checked_sub(1)374 375 .ok_or(ArithmeticError::Underflow)?,376 )377 } else {378 None379 };380 381 382 let account_balance_to = if create_target && from_to_differ {383 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))384 .checked_add(1)385 .ok_or(ArithmeticError::Overflow)?;386 ensure!(387 account_balance_to < collection.limits.account_token_ownership_limit(),388 <CommonError<T>>::AccountTokenLimitExceeded,389 );390391 Some(account_balance_to)392 } else {393 None394 };395396 397398 <PalletStructure<T>>::nest_if_sent_to_token(399 from.clone(),400 to,401 collection.id,402 token,403 nesting_budget,404 )?;405406 if let Some(balance_to) = balance_to {407 408 if balance_from == 0 {409 <Balance<T>>::remove((collection.id, token, from));410 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);411 } else {412 <Balance<T>>::insert((collection.id, token, from), balance_from);413 }414 <Balance<T>>::insert((collection.id, token, to), balance_to);415 if let Some(account_balance_from) = account_balance_from {416 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);417 <Owned<T>>::remove((collection.id, from, token));418 }419 if let Some(account_balance_to) = account_balance_to {420 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);421 <Owned<T>>::insert((collection.id, to, token), true);422 }423 }424425 426 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(427 collection.id,428 token,429 from.clone(),430 to.clone(),431 amount,432 ));433 Ok(())434 }435436 pub fn create_multiple_items(437 collection: &RefungibleHandle<T>,438 sender: &T::CrossAccountId,439 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,440 nesting_budget: &dyn Budget,441 ) -> DispatchResult {442 if !collection.is_owner_or_admin(sender) {443 ensure!(444 collection.permissions.mint_mode(),445 <CommonError<T>>::PublicMintingNotAllowed446 );447 collection.check_allowlist(sender)?;448449 for item in data.iter() {450 for user in item.users.keys() {451 collection.check_allowlist(user)?;452 }453 }454 }455456 for item in data.iter() {457 for (owner, _) in item.users.iter() {458 <PalletCommon<T>>::ensure_correct_receiver(owner)?;459 }460 }461462 463 let totals = data464 .iter()465 .map(|data| {466 Ok(data467 .users468 .iter()469 .map(|u| u.1)470 .try_fold(0u128, |acc, v| acc.checked_add(*v))471 .ok_or(ArithmeticError::Overflow)?)472 })473 .collect::<Result<Vec<_>, DispatchError>>()?;474 for total in &totals {475 ensure!(476 *total <= MAX_REFUNGIBLE_PIECES,477 <Error<T>>::WrongRefungiblePieces478 );479 }480481 let first_token_id = <TokensMinted<T>>::get(collection.id);482 let tokens_minted = first_token_id483 .checked_add(data.len() as u32)484 .ok_or(ArithmeticError::Overflow)?;485 ensure!(486 tokens_minted < collection.limits.token_limit(),487 <CommonError<T>>::CollectionTokenLimitExceeded488 );489490 let mut balances = BTreeMap::new();491 for data in &data {492 for owner in data.users.keys() {493 let balance = balances494 .entry(owner)495 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));496 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;497498 ensure!(499 *balance <= collection.limits.account_token_ownership_limit(),500 <CommonError<T>>::AccountTokenLimitExceeded,501 );502 }503 }504505 for (i, token) in data.iter().enumerate() {506 let token_id = TokenId(first_token_id + i as u32 + 1);507 for (to, _) in token.users.iter() {508 <PalletStructure<T>>::check_nesting(509 sender.clone(),510 to,511 collection.id,512 token_id,513 nesting_budget,514 )?;515 }516 }517518 519520 <TokensMinted<T>>::insert(collection.id, tokens_minted);521 for (account, balance) in balances {522 <AccountBalance<T>>::insert((collection.id, account), balance);523 }524 for (i, token) in data.into_iter().enumerate() {525 let token_id = first_token_id + i as u32 + 1;526 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);527528 <TokenData<T>>::insert(529 (collection.id, token_id),530 ItemData {531 const_data: token.const_data,532 },533 );534535 for (user, amount) in token.users.into_iter() {536 if amount == 0 {537 continue;538 }539 <Balance<T>>::insert((collection.id, token_id, &user), amount);540 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);541 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(542 &user,543 collection.id,544 TokenId(token_id),545 );546547 548 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(549 collection.id,550 TokenId(token_id),551 user,552 amount,553 ));554 }555 }556 Ok(())557 }558559 pub fn set_allowance_unchecked(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 spender: &T::CrossAccountId,563 token: TokenId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, token, sender, spender));568 } else {569 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);570 }571 572 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(573 collection.id,574 token,575 sender.clone(),576 spender.clone(),577 amount,578 ))579 }580581 pub fn set_allowance(582 collection: &RefungibleHandle<T>,583 sender: &T::CrossAccountId,584 spender: &T::CrossAccountId,585 token: TokenId,586 amount: u128,587 ) -> DispatchResult {588 if collection.permissions.access() == AccessMode::AllowList {589 collection.check_allowlist(sender)?;590 collection.check_allowlist(spender)?;591 }592593 <PalletCommon<T>>::ensure_correct_receiver(spender)?;594595 if <Balance<T>>::get((collection.id, token, sender)) < amount {596 ensure!(597 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),598 <CommonError<T>>::CantApproveMoreThanOwned599 );600 }601602 603604 Self::set_allowance_unchecked(collection, sender, spender, token, amount);605 Ok(())606 }607608 609 fn check_allowed(610 collection: &RefungibleHandle<T>,611 spender: &T::CrossAccountId,612 from: &T::CrossAccountId,613 token: TokenId,614 amount: u128,615 nesting_budget: &dyn Budget,616 ) -> Result<Option<u128>, DispatchError> {617 if spender.conv_eq(from) {618 return Ok(None);619 }620 if collection.permissions.access() == AccessMode::AllowList {621 622 collection.check_allowlist(spender)?;623 }624 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {625 626 ensure!(627 <PalletStructure<T>>::check_indirectly_owned(628 spender.clone(),629 source.0,630 source.1,631 None,632 nesting_budget633 )?,634 <CommonError<T>>::ApprovedValueTooLow,635 );636 return Ok(None);637 }638 let allowance =639 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);640 if allowance.is_none() {641 ensure!(642 collection.ignores_allowance(spender),643 <CommonError<T>>::ApprovedValueTooLow644 );645 }646 Ok(allowance)647 }648649 pub fn transfer_from(650 collection: &RefungibleHandle<T>,651 spender: &T::CrossAccountId,652 from: &T::CrossAccountId,653 to: &T::CrossAccountId,654 token: TokenId,655 amount: u128,656 nesting_budget: &dyn Budget,657 ) -> DispatchResult {658 let allowance =659 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;660661 662663 Self::transfer(collection, from, to, token, amount, nesting_budget)?;664 if let Some(allowance) = allowance {665 Self::set_allowance_unchecked(collection, from, spender, token, allowance);666 }667 Ok(())668 }669670 pub fn burn_from(671 collection: &RefungibleHandle<T>,672 spender: &T::CrossAccountId,673 from: &T::CrossAccountId,674 token: TokenId,675 amount: u128,676 nesting_budget: &dyn Budget,677 ) -> DispatchResult {678 let allowance =679 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;680681 682683 Self::burn(collection, from, token, amount)?;684 if let Some(allowance) = allowance {685 Self::set_allowance_unchecked(collection, from, spender, token, allowance);686 }687 Ok(())688 }689690 691 pub fn create_item(692 collection: &RefungibleHandle<T>,693 sender: &T::CrossAccountId,694 data: CreateRefungibleExData<T::CrossAccountId>,695 nesting_budget: &dyn Budget,696 ) -> DispatchResult {697 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)698 }699700 pub fn repartition(701 collection: &RefungibleHandle<T>,702 owner: &T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResult {706 ensure!(707 amount <= MAX_REFUNGIBLE_PIECES,708 <Error<T>>::WrongRefungiblePieces709 );710 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);711 712 let total_supply = <TotalSupply<T>>::get((collection.id, token));713 let balance = <Balance<T>>::get((collection.id, token, owner));714 ensure!(715 total_supply == balance,716 <Error<T>>::RepartitionWhileNotOwningAllPieces717 );718719 <Balance<T>>::insert((collection.id, token, owner), amount);720 <TotalSupply<T>>::insert((collection.id, token), amount);721 Ok(())722 }723724 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {725 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()726 }727}