12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use evm_coder::ToLog;96use frame_support::{97 BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,98};99use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};100use pallet_evm_coder_substrate::WithRecorder;101use pallet_common::{102 CollectionHandle, CommonCollectionOperations,103 dispatch::CollectionDispatch,104 erc::static_property::{key, property_value_from_bytes},105 Error as CommonError,106 eth::collection_id_to_address,107 Event as CommonEvent, Pallet as PalletCommon,108};109use pallet_structure::Pallet as PalletStructure;110use scale_info::TypeInfo;111use sp_core::H160;112use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};113use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};114use up_data_structs::{115 AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec,116 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,117 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,118 PropertyScope, PropertyValue, TokenId, TrySetProperty,119};120use frame_support::BoundedBTreeMap;121use derivative::Derivative;122123pub use pallet::*;124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod common;127pub mod erc;128pub mod erc_token;129pub mod weights;130131#[derive(Derivative, Clone)]132pub struct CreateItemData<CrossAccountId> {133 #[derivative(Debug(format_with = "bounded::map_debug"))]134 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,135 #[derivative(Debug(format_with = "bounded::vec_debug"))]136 pub properties: CollectionPropertiesVec,137}138pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;139140141142#[struct_versioning::versioned(version = 2, upper)]143#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]144#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]145pub struct ItemData {146 pub const_data: BoundedVec<u8, CustomDataLimit>,147148 #[version(..2)]149 pub variable_data: BoundedVec<u8, CustomDataLimit>,150}151152#[frame_support::pallet]153pub mod pallet {154 use super::*;155 use frame_support::{156 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,157 traits::StorageVersion,158 };159 use frame_system::pallet_prelude::*;160 use up_data_structs::{CollectionId, TokenId};161 use super::weights::WeightInfo;162163 #[pallet::error]164 pub enum Error<T> {165 166 NotRefungibleDataUsedToMintFungibleCollectionToken,167 168 WrongRefungiblePieces,169 170 RepartitionWhileNotOwningAllPieces,171 172 RefungibleDisallowsNesting,173 174 SettingPropertiesNotAllowed,175 }176177 #[pallet::config]178 pub trait Config:179 frame_system::Config + pallet_common::Config + pallet_structure::Config180 {181 type WeightInfo: WeightInfo;182 }183184 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);185186 #[pallet::pallet]187 #[pallet::storage_version(STORAGE_VERSION)]188 #[pallet::generate_store(pub(super) trait Store)]189 pub struct Pallet<T>(_);190191 192 #[pallet::storage]193 pub type TokensMinted<T: Config> =194 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;195196 197 #[pallet::storage]198 pub type TokensBurnt<T: Config> =199 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;200201 202 203 #[pallet::storage]204 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]205 pub type TokenData<T: Config> = StorageNMap<206 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),207 Value = ItemData,208 QueryKind = ValueQuery,209 >;210211 212 #[pallet::storage]213 #[pallet::getter(fn token_properties)]214 pub type TokenProperties<T: Config> = StorageNMap<215 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),216 Value = up_data_structs::Properties,217 QueryKind = ValueQuery,218 OnEmpty = up_data_structs::TokenProperties,219 >;220221 222 #[pallet::storage]223 pub type TotalSupply<T: Config> = StorageNMap<224 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),225 Value = u128,226 QueryKind = ValueQuery,227 >;228229 230 #[pallet::storage]231 pub type Owned<T: Config> = StorageNMap<232 Key = (233 Key<Twox64Concat, CollectionId>,234 Key<Blake2_128Concat, T::CrossAccountId>,235 Key<Twox64Concat, TokenId>,236 ),237 Value = bool,238 QueryKind = ValueQuery,239 >;240241 242 #[pallet::storage]243 pub type AccountBalance<T: Config> = StorageNMap<244 Key = (245 Key<Twox64Concat, CollectionId>,246 247 Key<Blake2_128Concat, T::CrossAccountId>,248 ),249 Value = u32,250 QueryKind = ValueQuery,251 >;252253 254 #[pallet::storage]255 pub type Balance<T: Config> = StorageNMap<256 Key = (257 Key<Twox64Concat, CollectionId>,258 Key<Twox64Concat, TokenId>,259 260 Key<Blake2_128Concat, T::CrossAccountId>,261 ),262 Value = u128,263 QueryKind = ValueQuery,264 >;265266 267 #[pallet::storage]268 pub type Allowance<T: Config> = StorageNMap<269 Key = (270 Key<Twox64Concat, CollectionId>,271 Key<Twox64Concat, TokenId>,272 273 Key<Blake2_128, T::CrossAccountId>,274 275 Key<Blake2_128Concat, T::CrossAccountId>,276 ),277 Value = u128,278 QueryKind = ValueQuery,279 >;280281 #[pallet::hooks]282 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {283 fn on_runtime_upgrade() -> Weight {284 let storage_version = StorageVersion::get::<Pallet<T>>();285 if storage_version < StorageVersion::new(2) {286 <TokenData<T>>::remove_all(None);287 }288 StorageVersion::new(2).put::<Pallet<T>>();289290 0291 }292 }293}294295pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);296impl<T: Config> RefungibleHandle<T> {297 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {298 Self(inner)299 }300 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {301 self.0302 }303 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {304 &mut self.0305 }306}307308impl<T: Config> Deref for RefungibleHandle<T> {309 type Target = pallet_common::CollectionHandle<T>;310311 fn deref(&self) -> &Self::Target {312 &self.0313 }314}315316impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {317 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {318 self.0.recorder()319 }320 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {321 self.0.into_recorder()322 }323}324325impl<T: Config> Pallet<T> {326 327 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {328 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)329 }330331 332 333 334 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {335 <TotalSupply<T>>::contains_key((collection.id, token))336 }337338 pub fn set_scoped_token_property(339 collection_id: CollectionId,340 token_id: TokenId,341 scope: PropertyScope,342 property: Property,343 ) -> DispatchResult {344 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {345 properties.try_scoped_set(scope, property.key, property.value)346 })347 .map_err(<CommonError<T>>::from)?;348349 Ok(())350 }351352 pub fn set_scoped_token_properties(353 collection_id: CollectionId,354 token_id: TokenId,355 scope: PropertyScope,356 properties: impl Iterator<Item = Property>,357 ) -> DispatchResult {358 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {359 stored_properties.try_scoped_set_from_iter(scope, properties)360 })361 .map_err(<CommonError<T>>::from)?;362363 Ok(())364 }365}366367368impl<T: Config> Pallet<T> {369 370 371 372 373 374 pub fn init_collection(375 owner: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 ) -> Result<CollectionId, DispatchError> {378 <PalletCommon<T>>::init_collection(owner, data, false)379 }380381 382 383 384 385 pub fn destroy_collection(386 collection: RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 ) -> DispatchResult {389 let id = collection.id;390391 if Self::collection_has_tokens(id) {392 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());393 }394395 396397 PalletCommon::destroy_collection(collection.0, sender)?;398399 <TokensMinted<T>>::remove(id);400 <TokensBurnt<T>>::remove(id);401 <TotalSupply<T>>::remove_prefix((id,), None);402 <Balance<T>>::remove_prefix((id,), None);403 <Allowance<T>>::remove_prefix((id,), None);404 <Owned<T>>::remove_prefix((id,), None);405 <AccountBalance<T>>::remove_prefix((id,), None);406 Ok(())407 }408409 fn collection_has_tokens(collection_id: CollectionId) -> bool {410 <TotalSupply<T>>::iter_prefix((collection_id,))411 .next()412 .is_some()413 }414415 pub fn burn_token_unchecked(416 collection: &RefungibleHandle<T>,417 owner: &T::CrossAccountId,418 token_id: TokenId,419 ) -> DispatchResult {420 let burnt = <TokensBurnt<T>>::get(collection.id)421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423424 <TokensBurnt<T>>::insert(collection.id, burnt);425 <TokenProperties<T>>::remove((collection.id, token_id));426 <TotalSupply<T>>::remove((collection.id, token_id));427 <Balance<T>>::remove_prefix((collection.id, token_id), None);428 <Allowance<T>>::remove_prefix((collection.id, token_id), None);429430 <PalletEvm<T>>::deposit_log(431 ERC721Events::Transfer {432 from: *owner.as_eth(),433 to: H160::default(),434 token_id: token_id.into(),435 }436 .to_log(collection_id_to_address(collection.id)),437 );438 Ok(())439 }440441 442 443 444 445 446 447 448 449 450 451 452 pub fn burn(453 collection: &RefungibleHandle<T>,454 owner: &T::CrossAccountId,455 token: TokenId,456 amount: u128,457 ) -> DispatchResult {458 let total_supply = <TotalSupply<T>>::get((collection.id, token))459 .checked_sub(amount)460 .ok_or(<CommonError<T>>::TokenValueTooLow)?;461462 463 if total_supply == 0 {464 465 ensure!(466 <Balance<T>>::get((collection.id, token, owner)) == amount,467 <CommonError<T>>::TokenValueTooLow468 );469 let account_balance = <AccountBalance<T>>::get((collection.id, owner))470 .checked_sub(1)471 472 .ok_or(ArithmeticError::Underflow)?;473474 475476 <Owned<T>>::remove((collection.id, owner, token));477 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);478 <AccountBalance<T>>::insert((collection.id, owner), account_balance);479 Self::burn_token_unchecked(collection, owner, token)?;480 <PalletEvm<T>>::deposit_log(481 ERC20Events::Transfer {482 from: *owner.as_eth(),483 to: H160::default(),484 value: amount.into(),485 }486 .to_log(collection_id_to_address(collection.id)),487 );488 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(489 collection.id,490 token,491 owner.clone(),492 amount,493 ));494 return Ok(());495 }496497 let balance = <Balance<T>>::get((collection.id, token, owner))498 .checked_sub(amount)499 .ok_or(<CommonError<T>>::TokenValueTooLow)?;500 let account_balance = if balance == 0 {501 <AccountBalance<T>>::get((collection.id, owner))502 .checked_sub(1)503 504 .ok_or(ArithmeticError::Underflow)?505 } else {506 0507 };508509 510511 if balance == 0 {512 <Owned<T>>::remove((collection.id, owner, token));513 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);514 <Balance<T>>::remove((collection.id, token, owner));515 <AccountBalance<T>>::insert((collection.id, owner), account_balance);516517 if let Some(user) = Self::token_owner(collection.id, token) {518 <PalletEvm<T>>::deposit_log(519 ERC721Events::Transfer {520 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,521 to: *user.as_eth(),522 token_id: token.into(),523 }524 .to_log(collection_id_to_address(collection.id)),525 );526 }527 } else {528 <Balance<T>>::insert((collection.id, token, owner), balance);529 }530 <TotalSupply<T>>::insert((collection.id, token), total_supply);531532 <PalletEvm<T>>::deposit_log(533 ERC20Events::Transfer {534 from: *owner.as_eth(),535 to: H160::default(),536 value: amount.into(),537 }538 .to_log(T::EvmTokenAddressMapping::token_to_address(539 collection.id,540 token,541 )),542 );543 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(544 collection.id,545 token,546 owner.clone(),547 amount,548 ));549 Ok(())550 }551552 #[transactional]553 fn modify_token_properties(554 collection: &RefungibleHandle<T>,555 sender: &T::CrossAccountId,556 token_id: TokenId,557 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,558 is_token_create: bool,559 nesting_budget: &dyn Budget,560 ) -> DispatchResult {561 let is_collection_admin = || collection.is_owner_or_admin(sender);562 let is_token_owner = || -> Result<bool, DispatchError> {563 let balance = collection.balance(sender.clone(), token_id);564 let total_pieces: u128 =565 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);566 if balance != total_pieces {567 return Ok(false);568 }569570 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(571 sender.clone(),572 collection.id,573 token_id,574 None,575 nesting_budget,576 )?;577578 Ok(is_bundle_owner)579 };580581 for (key, value) in properties {582 let permission = <PalletCommon<T>>::property_permissions(collection.id)583 .get(&key)584 .cloned()585 .unwrap_or_else(PropertyPermission::none);586587 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))588 .get(&key)589 .is_some();590591 match permission {592 PropertyPermission { mutable: false, .. } if is_property_exists => {593 return Err(<CommonError<T>>::NoPermission.into());594 }595596 PropertyPermission {597 collection_admin,598 token_owner,599 ..600 } => {601 602 let is_token_create =603 is_token_create && (collection_admin || token_owner) && value.is_some();604 if !(is_token_create605 || (collection_admin && is_collection_admin())606 || (token_owner && is_token_owner()?))607 {608 fail!(<CommonError<T>>::NoPermission);609 }610 }611 }612613 match value {614 Some(value) => {615 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {616 properties.try_set(key.clone(), value)617 })618 .map_err(<CommonError<T>>::from)?;619620 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(621 collection.id,622 token_id,623 key,624 ));625 }626 None => {627 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {628 properties.remove(&key)629 })630 .map_err(<CommonError<T>>::from)?;631632 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(633 collection.id,634 token_id,635 key,636 ));637 }638 }639 }640641 Ok(())642 }643644 pub fn set_token_properties(645 collection: &RefungibleHandle<T>,646 sender: &T::CrossAccountId,647 token_id: TokenId,648 properties: impl Iterator<Item = Property>,649 is_token_create: bool,650 nesting_budget: &dyn Budget,651 ) -> DispatchResult {652 Self::modify_token_properties(653 collection,654 sender,655 token_id,656 properties.map(|p| (p.key, Some(p.value))),657 is_token_create,658 nesting_budget,659 )660 }661662 pub fn set_token_property(663 collection: &RefungibleHandle<T>,664 sender: &T::CrossAccountId,665 token_id: TokenId,666 property: Property,667 nesting_budget: &dyn Budget,668 ) -> DispatchResult {669 let is_token_create = false;670671 Self::set_token_properties(672 collection,673 sender,674 token_id,675 [property].into_iter(),676 is_token_create,677 nesting_budget,678 )679 }680681 pub fn delete_token_properties(682 collection: &RefungibleHandle<T>,683 sender: &T::CrossAccountId,684 token_id: TokenId,685 property_keys: impl Iterator<Item = PropertyKey>,686 nesting_budget: &dyn Budget,687 ) -> DispatchResult {688 let is_token_create = false;689690 Self::modify_token_properties(691 collection,692 sender,693 token_id,694 property_keys.into_iter().map(|key| (key, None)),695 is_token_create,696 nesting_budget,697 )698 }699700 pub fn delete_token_property(701 collection: &RefungibleHandle<T>,702 sender: &T::CrossAccountId,703 token_id: TokenId,704 property_key: PropertyKey,705 nesting_budget: &dyn Budget,706 ) -> DispatchResult {707 Self::delete_token_properties(708 collection,709 sender,710 token_id,711 [property_key].into_iter(),712 nesting_budget,713 )714 }715716 717 718 719 720 721 722 723 724 725 pub fn transfer(726 collection: &RefungibleHandle<T>,727 from: &T::CrossAccountId,728 to: &T::CrossAccountId,729 token: TokenId,730 amount: u128,731 nesting_budget: &dyn Budget,732 ) -> DispatchResult {733 ensure!(734 collection.limits.transfers_enabled(),735 <CommonError<T>>::TransferNotAllowed736 );737738 if collection.permissions.access() == AccessMode::AllowList {739 collection.check_allowlist(from)?;740 collection.check_allowlist(to)?;741 }742 <PalletCommon<T>>::ensure_correct_receiver(to)?;743744 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));745 let updated_balance_from = initial_balance_from746 .checked_sub(amount)747 .ok_or(<CommonError<T>>::TokenValueTooLow)?;748 let mut create_target = false;749 let from_to_differ = from != to;750 let updated_balance_to = if from != to {751 let old_balance = <Balance<T>>::get((collection.id, token, to));752 if old_balance == 0 {753 create_target = true;754 }755 Some(756 old_balance757 .checked_add(amount)758 .ok_or(ArithmeticError::Overflow)?,759 )760 } else {761 None762 };763764 let account_balance_from = if updated_balance_from == 0 {765 Some(766 <AccountBalance<T>>::get((collection.id, from))767 .checked_sub(1)768 769 .ok_or(ArithmeticError::Underflow)?,770 )771 } else {772 None773 };774 775 776 let account_balance_to = if create_target && from_to_differ {777 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))778 .checked_add(1)779 .ok_or(ArithmeticError::Overflow)?;780 ensure!(781 account_balance_to < collection.limits.account_token_ownership_limit(),782 <CommonError<T>>::AccountTokenLimitExceeded,783 );784785 Some(account_balance_to)786 } else {787 None788 };789790 791792 <PalletStructure<T>>::nest_if_sent_to_token(793 from.clone(),794 to,795 collection.id,796 token,797 nesting_budget,798 )?;799800 if let Some(updated_balance_to) = updated_balance_to {801 802 if updated_balance_from == 0 {803 <Balance<T>>::remove((collection.id, token, from));804 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);805 } else {806 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);807 }808 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);809 if let Some(account_balance_from) = account_balance_from {810 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);811 <Owned<T>>::remove((collection.id, from, token));812 }813 if let Some(account_balance_to) = account_balance_to {814 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);815 <Owned<T>>::insert((collection.id, to, token), true);816 }817 }818819 <PalletEvm<T>>::deposit_log(820 ERC20Events::Transfer {821 from: *from.as_eth(),822 to: *to.as_eth(),823 value: amount.into(),824 }825 .to_log(T::EvmTokenAddressMapping::token_to_address(826 collection.id,827 token,828 )),829 );830831 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(832 collection.id,833 token,834 from.clone(),835 to.clone(),836 amount,837 ));838839 let total_supply = <TotalSupply<T>>::get((collection.id, token));840841 if amount == total_supply {842 843 <PalletEvm<T>>::deposit_log(844 ERC721Events::Transfer {845 from: *from.as_eth(),846 to: *to.as_eth(),847 token_id: token.into(),848 }849 .to_log(collection_id_to_address(collection.id)),850 );851 } else if let Some(updated_balance_to) = updated_balance_to {852 853 854 if initial_balance_from == total_supply {855 856 857 <PalletEvm<T>>::deposit_log(858 ERC721Events::Transfer {859 from: *from.as_eth(),860 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,861 token_id: token.into(),862 }863 .to_log(collection_id_to_address(collection.id)),864 );865 } else if updated_balance_to == total_supply {866 867 <PalletEvm<T>>::deposit_log(868 ERC721Events::Transfer {869 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,870 to: *to.as_eth(),871 token_id: token.into(),872 }873 .to_log(collection_id_to_address(collection.id)),874 );875 }876 }877878 Ok(())879 }880881 882 883 884 885 886 pub fn create_multiple_items(887 collection: &RefungibleHandle<T>,888 sender: &T::CrossAccountId,889 data: Vec<CreateItemData<T::CrossAccountId>>,890 nesting_budget: &dyn Budget,891 ) -> DispatchResult {892 if !collection.is_owner_or_admin(sender) {893 ensure!(894 collection.permissions.mint_mode(),895 <CommonError<T>>::PublicMintingNotAllowed896 );897 collection.check_allowlist(sender)?;898899 for item in data.iter() {900 for user in item.users.keys() {901 collection.check_allowlist(user)?;902 }903 }904 }905906 for item in data.iter() {907 for (owner, _) in item.users.iter() {908 <PalletCommon<T>>::ensure_correct_receiver(owner)?;909 }910 }911912 913 let totals = data914 .iter()915 .map(|data| {916 Ok(data917 .users918 .iter()919 .map(|u| u.1)920 .try_fold(0u128, |acc, v| acc.checked_add(*v))921 .ok_or(ArithmeticError::Overflow)?)922 })923 .collect::<Result<Vec<_>, DispatchError>>()?;924 for total in &totals {925 ensure!(926 *total <= MAX_REFUNGIBLE_PIECES,927 <Error<T>>::WrongRefungiblePieces928 );929 }930931 let first_token_id = <TokensMinted<T>>::get(collection.id);932 let tokens_minted = first_token_id933 .checked_add(data.len() as u32)934 .ok_or(ArithmeticError::Overflow)?;935 ensure!(936 tokens_minted < collection.limits.token_limit(),937 <CommonError<T>>::CollectionTokenLimitExceeded938 );939940 let mut balances = BTreeMap::new();941 for data in &data {942 for owner in data.users.keys() {943 let balance = balances944 .entry(owner)945 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));946 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;947948 ensure!(949 *balance <= collection.limits.account_token_ownership_limit(),950 <CommonError<T>>::AccountTokenLimitExceeded,951 );952 }953 }954955 for (i, token) in data.iter().enumerate() {956 let token_id = TokenId(first_token_id + i as u32 + 1);957 for (to, _) in token.users.iter() {958 <PalletStructure<T>>::check_nesting(959 sender.clone(),960 to,961 collection.id,962 token_id,963 nesting_budget,964 )?;965 }966 }967968 969970 with_transaction(|| {971 for (i, data) in data.iter().enumerate() {972 let token_id = first_token_id + i as u32 + 1;973 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);974975 for (user, amount) in data.users.iter() {976 if *amount == 0 {977 continue;978 }979 <Balance<T>>::insert((collection.id, token_id, &user), amount);980 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);981 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(982 user,983 collection.id,984 TokenId(token_id),985 );986 }987988 if let Err(e) = Self::set_token_properties(989 collection,990 sender,991 TokenId(token_id),992 data.properties.clone().into_iter(),993 true,994 nesting_budget,995 ) {996 return TransactionOutcome::Rollback(Err(e));997 }998 }999 TransactionOutcome::Commit(Ok(()))1000 })?;10011002 <TokensMinted<T>>::insert(collection.id, tokens_minted);10031004 for (account, balance) in balances {1005 <AccountBalance<T>>::insert((collection.id, account), balance);1006 }10071008 for (i, token) in data.into_iter().enumerate() {1009 let token_id = first_token_id + i as u32 + 1;10101011 let receivers = token1012 .users1013 .into_iter()1014 .filter(|(_, amount)| *amount > 0)1015 .collect::<Vec<_>>();10161017 if let [(user, _)] = receivers.as_slice() {1018 1019 <PalletEvm<T>>::deposit_log(1020 ERC721Events::Transfer {1021 from: H160::default(),1022 to: *user.as_eth(),1023 token_id: token_id.into(),1024 }1025 .to_log(collection_id_to_address(collection.id)),1026 );1027 } else if let [_, ..] = receivers.as_slice() {1028 1029 <PalletEvm<T>>::deposit_log(1030 ERC721Events::Transfer {1031 from: H160::default(),1032 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1033 token_id: token_id.into(),1034 }1035 .to_log(collection_id_to_address(collection.id)),1036 );1037 }10381039 for (user, amount) in receivers.into_iter() {1040 <PalletEvm<T>>::deposit_log(1041 ERC20Events::Transfer {1042 from: H160::default(),1043 to: *user.as_eth(),1044 value: amount.into(),1045 }1046 .to_log(T::EvmTokenAddressMapping::token_to_address(1047 collection.id,1048 TokenId(token_id),1049 )),1050 );1051 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1052 collection.id,1053 TokenId(token_id),1054 user,1055 amount,1056 ));1057 }1058 }1059 Ok(())1060 }10611062 pub fn set_allowance_unchecked(1063 collection: &RefungibleHandle<T>,1064 sender: &T::CrossAccountId,1065 spender: &T::CrossAccountId,1066 token: TokenId,1067 amount: u128,1068 ) {1069 if amount == 0 {1070 <Allowance<T>>::remove((collection.id, token, sender, spender));1071 } else {1072 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1073 }10741075 <PalletEvm<T>>::deposit_log(1076 ERC20Events::Approval {1077 owner: *sender.as_eth(),1078 spender: *spender.as_eth(),1079 value: amount.into(),1080 }1081 .to_log(T::EvmTokenAddressMapping::token_to_address(1082 collection.id,1083 token,1084 )),1085 );1086 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1087 collection.id,1088 token,1089 sender.clone(),1090 spender.clone(),1091 amount,1092 ))1093 }10941095 1096 1097 1098 pub fn set_allowance(1099 collection: &RefungibleHandle<T>,1100 sender: &T::CrossAccountId,1101 spender: &T::CrossAccountId,1102 token: TokenId,1103 amount: u128,1104 ) -> DispatchResult {1105 if collection.permissions.access() == AccessMode::AllowList {1106 collection.check_allowlist(sender)?;1107 collection.check_allowlist(spender)?;1108 }11091110 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11111112 if <Balance<T>>::get((collection.id, token, sender)) < amount {1113 ensure!(1114 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1115 <CommonError<T>>::CantApproveMoreThanOwned1116 );1117 }11181119 11201121 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1122 Ok(())1123 }11241125 1126 fn check_allowed(1127 collection: &RefungibleHandle<T>,1128 spender: &T::CrossAccountId,1129 from: &T::CrossAccountId,1130 token: TokenId,1131 amount: u128,1132 nesting_budget: &dyn Budget,1133 ) -> Result<Option<u128>, DispatchError> {1134 if spender.conv_eq(from) {1135 return Ok(None);1136 }1137 if collection.permissions.access() == AccessMode::AllowList {1138 1139 collection.check_allowlist(spender)?;1140 }1141 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1142 1143 ensure!(1144 <PalletStructure<T>>::check_indirectly_owned(1145 spender.clone(),1146 source.0,1147 source.1,1148 None,1149 nesting_budget1150 )?,1151 <CommonError<T>>::ApprovedValueTooLow,1152 );1153 return Ok(None);1154 }1155 let allowance =1156 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1157 if allowance.is_none() {1158 ensure!(1159 collection.ignores_allowance(spender),1160 <CommonError<T>>::ApprovedValueTooLow1161 );1162 }1163 Ok(allowance)1164 }11651166 1167 1168 1169 1170 1171 1172 pub fn transfer_from(1173 collection: &RefungibleHandle<T>,1174 spender: &T::CrossAccountId,1175 from: &T::CrossAccountId,1176 to: &T::CrossAccountId,1177 token: TokenId,1178 amount: u128,1179 nesting_budget: &dyn Budget,1180 ) -> DispatchResult {1181 let allowance =1182 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11831184 11851186 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1187 if let Some(allowance) = allowance {1188 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1189 }1190 Ok(())1191 }11921193 1194 1195 1196 1197 1198 1199 pub fn burn_from(1200 collection: &RefungibleHandle<T>,1201 spender: &T::CrossAccountId,1202 from: &T::CrossAccountId,1203 token: TokenId,1204 amount: u128,1205 nesting_budget: &dyn Budget,1206 ) -> DispatchResult {1207 let allowance =1208 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12091210 12111212 Self::burn(collection, from, token, amount)?;1213 if let Some(allowance) = allowance {1214 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1215 }1216 Ok(())1217 }12181219 1220 1221 1222 1223 1224 1225 1226 pub fn create_item(1227 collection: &RefungibleHandle<T>,1228 sender: &T::CrossAccountId,1229 data: CreateItemData<T::CrossAccountId>,1230 nesting_budget: &dyn Budget,1231 ) -> DispatchResult {1232 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1233 }12341235 1236 1237 1238 1239 1240 1241 1242 pub fn repartition(1243 collection: &RefungibleHandle<T>,1244 owner: &T::CrossAccountId,1245 token: TokenId,1246 amount: u128,1247 ) -> DispatchResult {1248 ensure!(1249 amount <= MAX_REFUNGIBLE_PIECES,1250 <Error<T>>::WrongRefungiblePieces1251 );1252 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1253 1254 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1255 let balance = <Balance<T>>::get((collection.id, token, owner));1256 ensure!(1257 total_pieces == balance,1258 <Error<T>>::RepartitionWhileNotOwningAllPieces1259 );12601261 <Balance<T>>::insert((collection.id, token, owner), amount);1262 <TotalSupply<T>>::insert((collection.id, token), amount);12631264 if amount > total_pieces {1265 let mint_amount = amount - total_pieces;1266 <PalletEvm<T>>::deposit_log(1267 ERC20Events::Transfer {1268 from: H160::default(),1269 to: *owner.as_eth(),1270 value: mint_amount.into(),1271 }1272 .to_log(T::EvmTokenAddressMapping::token_to_address(1273 collection.id,1274 token,1275 )),1276 );1277 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1278 collection.id,1279 token,1280 owner.clone(),1281 mint_amount,1282 ));1283 } else if total_pieces > amount {1284 let burn_amount = total_pieces - amount;1285 <PalletEvm<T>>::deposit_log(1286 ERC20Events::Transfer {1287 from: *owner.as_eth(),1288 to: H160::default(),1289 value: burn_amount.into(),1290 }1291 .to_log(T::EvmTokenAddressMapping::token_to_address(1292 collection.id,1293 token,1294 )),1295 );1296 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1297 collection.id,1298 token,1299 owner.clone(),1300 burn_amount,1301 ));1302 }13031304 Ok(())1305 }13061307 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1308 let mut owner = None;1309 let mut count = 0;1310 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1311 count += 1;1312 if count > 1 {1313 return None;1314 }1315 owner = Some(key);1316 }1317 owner1318 }13191320 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1321 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1322 }13231324 pub fn set_collection_properties(1325 collection: &RefungibleHandle<T>,1326 sender: &T::CrossAccountId,1327 properties: Vec<Property>,1328 ) -> DispatchResult {1329 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1330 }13311332 pub fn delete_collection_properties(1333 collection: &RefungibleHandle<T>,1334 sender: &T::CrossAccountId,1335 property_keys: Vec<PropertyKey>,1336 ) -> DispatchResult {1337 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1338 }13391340 pub fn set_token_property_permissions(1341 collection: &RefungibleHandle<T>,1342 sender: &T::CrossAccountId,1343 property_permissions: Vec<PropertyKeyPermission>,1344 ) -> DispatchResult {1345 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1346 }13471348 pub fn set_scoped_token_property_permissions(1349 collection: &RefungibleHandle<T>,1350 sender: &T::CrossAccountId,1351 scope: PropertyScope,1352 property_permissions: Vec<PropertyKeyPermission>,1353 ) -> DispatchResult {1354 <PalletCommon<T>>::set_scoped_token_property_permissions(1355 collection,1356 sender,1357 scope,1358 property_permissions,1359 )1360 }13611362 1363 1364 1365 1366 1367 1368 pub fn token_owners(1369 collection_id: CollectionId,1370 token: TokenId,1371 ) -> Option<Vec<T::CrossAccountId>> {1372 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1373 .map(|(owner, _amount)| owner)1374 .take(10)1375 .collect();13761377 if res.is_empty() {1378 None1379 } else {1380 Some(res)1381 }1382 }13831384 1385 1386 1387 1388 pub fn set_parent_nft(1389 collection: &RefungibleHandle<T>,1390 rft_token_id: TokenId,1391 sender: T::CrossAccountId,1392 nft_collection: CollectionId,1393 nft_token: TokenId,1394 ) -> DispatchResult {1395 let handle = <CollectionHandle<T>>::try_get(nft_collection)?;1396 if handle.mode != CollectionMode::NFT {1397 return Err("Only NFT token could be parent to RFT".into());1398 }1399 let dispatch = T::CollectionDispatch::dispatch(handle);1400 let dispatch = dispatch.as_dyn();14011402 let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?;1403 if owner != sender {1404 return Err("Only owned token could be set as parent".into());1405 }14061407 let nft_token_address =1408 T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token);14091410 Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address)1411 }14121413 1414 1415 1416 1417 pub fn set_parent_nft_unchecked(1418 collection: &RefungibleHandle<T>,1419 rft_token_id: TokenId,1420 sender: T::CrossAccountId,1421 nft_token_address: T::CrossAccountId,1422 ) -> DispatchResult {1423 let owner_balance = <Balance<T>>::get((collection.id, rft_token_id, &sender));1424 let total_supply = <TotalSupply<T>>::get((collection.id, rft_token_id));1425 if total_supply != owner_balance {1426 return Err("token has multiple owners".into());1427 }14281429 let parent_nft_property_key = key::parent_nft();14301431 let parent_nft_property_value =1432 property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes())1433 .expect("address should fit in value length limit");14341435 <Pallet<T>>::set_scoped_token_property(1436 collection.id,1437 rft_token_id,1438 PropertyScope::Eth,1439 Property {1440 key: parent_nft_property_key,1441 value: parent_nft_property_value,1442 },1443 )?;14441445 Ok(())1446 }1447}