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 derivative::Derivative;96use evm_coder::ToLog;97use frame_support::{98 BoundedBTreeMap, BoundedVec, ensure, fail, storage::with_transaction, transactional,99 pallet_prelude::ConstU32,100};101use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};102use pallet_evm_coder_substrate::WithRecorder;103use pallet_common::{104 CommonCollectionOperations,105 erc::static_property::{key, value},106 Error as CommonError,107 eth::collection_id_to_address,108 Event as CommonEvent, Pallet as PalletCommon,109};110use pallet_structure::Pallet as PalletStructure;111use scale_info::TypeInfo;112use sp_core::H160;113use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};114use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};115use up_data_structs::{116 AccessMode, budget::Budget, CollectionId, CollectionFlags, CollectionPropertiesVec,117 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,118 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,119 PropertyScope, PropertyValue, TokenId, TrySetProperty,120};121122pub use pallet::*;123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod common;126pub mod erc;127pub mod erc_token;128pub mod weights;129130#[derive(Derivative, Clone)]131pub struct CreateItemData<CrossAccountId> {132 #[derivative(Debug(format_with = "bounded::map_debug"))]133 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,134 #[derivative(Debug(format_with = "bounded::vec_debug"))]135 pub properties: CollectionPropertiesVec,136}137pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;138139140141#[struct_versioning::versioned(version = 2, upper)]142#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]143#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]144pub struct ItemData {145 pub const_data: BoundedVec<u8, CustomDataLimit>,146147 #[version(..2)]148 pub variable_data: BoundedVec<u8, CustomDataLimit>,149}150151#[frame_support::pallet]152pub mod pallet {153 use super::*;154 use frame_support::{155 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,156 traits::StorageVersion,157 };158 use frame_system::pallet_prelude::*;159 use up_data_structs::{CollectionId, TokenId};160 use super::weights::WeightInfo;161162 #[pallet::error]163 pub enum Error<T> {164 165 NotRefungibleDataUsedToMintFungibleCollectionToken,166 167 WrongRefungiblePieces,168 169 RepartitionWhileNotOwningAllPieces,170 171 RefungibleDisallowsNesting,172 173 SettingPropertiesNotAllowed,174 }175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_common::Config + pallet_structure::Config179 {180 type WeightInfo: WeightInfo;181 }182183 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);184185 #[pallet::pallet]186 #[pallet::storage_version(STORAGE_VERSION)]187 #[pallet::generate_store(pub(super) trait Store)]188 pub struct Pallet<T>(_);189190 191 #[pallet::storage]192 pub type TokensMinted<T: Config> =193 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;194195 196 #[pallet::storage]197 pub type TokensBurnt<T: Config> =198 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;199200 201 202 #[pallet::storage]203 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]204 pub type TokenData<T: Config> = StorageNMap<205 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),206 Value = ItemData,207 QueryKind = ValueQuery,208 >;209210 211 #[pallet::storage]212 #[pallet::getter(fn token_properties)]213 pub type TokenProperties<T: Config> = StorageNMap<214 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),215 Value = up_data_structs::Properties,216 QueryKind = ValueQuery,217 OnEmpty = up_data_structs::TokenProperties,218 >;219220 221 #[pallet::storage]222 pub type TotalSupply<T: Config> = StorageNMap<223 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),224 Value = u128,225 QueryKind = ValueQuery,226 >;227228 229 #[pallet::storage]230 pub type Owned<T: Config> = StorageNMap<231 Key = (232 Key<Twox64Concat, CollectionId>,233 Key<Blake2_128Concat, T::CrossAccountId>,234 Key<Twox64Concat, TokenId>,235 ),236 Value = bool,237 QueryKind = ValueQuery,238 >;239240 241 #[pallet::storage]242 pub type AccountBalance<T: Config> = StorageNMap<243 Key = (244 Key<Twox64Concat, CollectionId>,245 246 Key<Blake2_128Concat, T::CrossAccountId>,247 ),248 Value = u32,249 QueryKind = ValueQuery,250 >;251252 253 #[pallet::storage]254 pub type Balance<T: Config> = StorageNMap<255 Key = (256 Key<Twox64Concat, CollectionId>,257 Key<Twox64Concat, TokenId>,258 259 Key<Blake2_128Concat, T::CrossAccountId>,260 ),261 Value = u128,262 QueryKind = ValueQuery,263 >;264265 266 #[pallet::storage]267 pub type Allowance<T: Config> = StorageNMap<268 Key = (269 Key<Twox64Concat, CollectionId>,270 Key<Twox64Concat, TokenId>,271 272 Key<Blake2_128, T::CrossAccountId>,273 274 Key<Blake2_128Concat, T::CrossAccountId>,275 ),276 Value = u128,277 QueryKind = ValueQuery,278 >;279280 #[pallet::hooks]281 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {282 fn on_runtime_upgrade() -> Weight {283 let storage_version = StorageVersion::get::<Pallet<T>>();284 if storage_version < StorageVersion::new(2) {285 <TokenData<T>>::remove_all(None);286 }287 StorageVersion::new(2).put::<Pallet<T>>();288289 Weight::zero()290 }291 }292}293294pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);295impl<T: Config> RefungibleHandle<T> {296 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {297 Self(inner)298 }299 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {300 self.0301 }302 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {303 &mut self.0304 }305}306307impl<T: Config> RefungibleHandle<T> {308 pub fn supports_metadata(&self) -> bool {309 if let Some(erc721_metadata) =310 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())311 {312 *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED313 } else {314 false315 }316 }317}318319impl<T: Config> Deref for RefungibleHandle<T> {320 type Target = pallet_common::CollectionHandle<T>;321322 fn deref(&self) -> &Self::Target {323 &self.0324 }325}326327impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {328 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {329 self.0.recorder()330 }331 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {332 self.0.into_recorder()333 }334}335336impl<T: Config> Pallet<T> {337 338 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {339 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)340 }341342 343 344 345 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {346 <TotalSupply<T>>::contains_key((collection.id, token))347 }348349 pub fn set_scoped_token_property(350 collection_id: CollectionId,351 token_id: TokenId,352 scope: PropertyScope,353 property: Property,354 ) -> DispatchResult {355 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {356 properties.try_scoped_set(scope, property.key, property.value)357 })358 .map_err(<CommonError<T>>::from)?;359360 Ok(())361 }362363 pub fn set_scoped_token_properties(364 collection_id: CollectionId,365 token_id: TokenId,366 scope: PropertyScope,367 properties: impl Iterator<Item = Property>,368 ) -> DispatchResult {369 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {370 stored_properties.try_scoped_set_from_iter(scope, properties)371 })372 .map_err(<CommonError<T>>::from)?;373374 Ok(())375 }376}377378379impl<T: Config> Pallet<T> {380 381 382 383 384 385 pub fn init_collection(386 owner: T::CrossAccountId,387 payer: T::CrossAccountId,388 data: CreateCollectionData<T::AccountId>,389 ) -> Result<CollectionId, DispatchError> {390 <PalletCommon<T>>::init_collection(owner, payer, data, CollectionFlags::default())391 }392393 394 395 396 397 pub fn destroy_collection(398 collection: RefungibleHandle<T>,399 sender: &T::CrossAccountId,400 ) -> DispatchResult {401 let id = collection.id;402403 if Self::collection_has_tokens(id) {404 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());405 }406407 408409 PalletCommon::destroy_collection(collection.0, sender)?;410411 <TokensMinted<T>>::remove(id);412 <TokensBurnt<T>>::remove(id);413 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);414 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);415 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);416 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);417 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);418 Ok(())419 }420421 fn collection_has_tokens(collection_id: CollectionId) -> bool {422 <TotalSupply<T>>::iter_prefix((collection_id,))423 .next()424 .is_some()425 }426427 pub fn burn_token_unchecked(428 collection: &RefungibleHandle<T>,429 owner: &T::CrossAccountId,430 token_id: TokenId,431 ) -> DispatchResult {432 let burnt = <TokensBurnt<T>>::get(collection.id)433 .checked_add(1)434 .ok_or(ArithmeticError::Overflow)?;435436 <TokensBurnt<T>>::insert(collection.id, burnt);437 <TokenProperties<T>>::remove((collection.id, token_id));438 <TotalSupply<T>>::remove((collection.id, token_id));439 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);440 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);441 <PalletEvm<T>>::deposit_log(442 ERC721Events::Transfer {443 from: *owner.as_eth(),444 to: H160::default(),445 token_id: token_id.into(),446 }447 .to_log(collection_id_to_address(collection.id)),448 );449 Ok(())450 }451452 453 454 455 456 457 458 459 460 461 462 463 pub fn burn(464 collection: &RefungibleHandle<T>,465 owner: &T::CrossAccountId,466 token: TokenId,467 amount: u128,468 ) -> DispatchResult {469 let total_supply = <TotalSupply<T>>::get((collection.id, token))470 .checked_sub(amount)471 .ok_or(<CommonError<T>>::TokenValueTooLow)?;472473 474 if total_supply == 0 {475 476 ensure!(477 <Balance<T>>::get((collection.id, token, owner)) == amount,478 <CommonError<T>>::TokenValueTooLow479 );480 let account_balance = <AccountBalance<T>>::get((collection.id, owner))481 .checked_sub(1)482 483 .ok_or(ArithmeticError::Underflow)?;484485 486487 <Owned<T>>::remove((collection.id, owner, token));488 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);489 <AccountBalance<T>>::insert((collection.id, owner), account_balance);490 Self::burn_token_unchecked(collection, owner, token)?;491 <PalletEvm<T>>::deposit_log(492 ERC20Events::Transfer {493 from: *owner.as_eth(),494 to: H160::default(),495 value: amount.into(),496 }497 .to_log(collection_id_to_address(collection.id)),498 );499 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(500 collection.id,501 token,502 owner.clone(),503 amount,504 ));505 return Ok(());506 }507508 let balance = <Balance<T>>::get((collection.id, token, owner))509 .checked_sub(amount)510 .ok_or(<CommonError<T>>::TokenValueTooLow)?;511 let account_balance = if balance == 0 {512 <AccountBalance<T>>::get((collection.id, owner))513 .checked_sub(1)514 515 .ok_or(ArithmeticError::Underflow)?516 } else {517 0518 };519520 521522 if balance == 0 {523 <Owned<T>>::remove((collection.id, owner, token));524 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);525 <Balance<T>>::remove((collection.id, token, owner));526 <AccountBalance<T>>::insert((collection.id, owner), account_balance);527528 if let Some(user) = Self::token_owner(collection.id, token) {529 <PalletEvm<T>>::deposit_log(530 ERC721Events::Transfer {531 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,532 to: *user.as_eth(),533 token_id: token.into(),534 }535 .to_log(collection_id_to_address(collection.id)),536 );537 }538 } else {539 <Balance<T>>::insert((collection.id, token, owner), balance);540 }541 <TotalSupply<T>>::insert((collection.id, token), total_supply);542543 <PalletEvm<T>>::deposit_log(544 ERC20Events::Transfer {545 from: *owner.as_eth(),546 to: H160::default(),547 value: amount.into(),548 }549 .to_log(T::EvmTokenAddressMapping::token_to_address(550 collection.id,551 token,552 )),553 );554 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(555 collection.id,556 token,557 owner.clone(),558 amount,559 ));560 Ok(())561 }562563 #[transactional]564 fn modify_token_properties(565 collection: &RefungibleHandle<T>,566 sender: &T::CrossAccountId,567 token_id: TokenId,568 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,569 is_token_create: bool,570 nesting_budget: &dyn Budget,571 ) -> DispatchResult {572 let is_collection_admin = || collection.is_owner_or_admin(sender);573 let is_token_owner = || -> Result<bool, DispatchError> {574 let balance = collection.balance(sender.clone(), token_id);575 let total_pieces: u128 =576 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);577 if balance != total_pieces {578 return Ok(false);579 }580581 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(582 sender.clone(),583 collection.id,584 token_id,585 None,586 nesting_budget,587 )?;588589 Ok(is_bundle_owner)590 };591592 for (key, value) in properties {593 let permission = <PalletCommon<T>>::property_permissions(collection.id)594 .get(&key)595 .cloned()596 .unwrap_or_else(PropertyPermission::none);597598 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))599 .get(&key)600 .is_some();601602 match permission {603 PropertyPermission { mutable: false, .. } if is_property_exists => {604 return Err(<CommonError<T>>::NoPermission.into());605 }606607 PropertyPermission {608 collection_admin,609 token_owner,610 ..611 } => {612 613 let is_token_create =614 is_token_create && (collection_admin || token_owner) && value.is_some();615 if !(is_token_create616 || (collection_admin && is_collection_admin())617 || (token_owner && is_token_owner()?))618 {619 fail!(<CommonError<T>>::NoPermission);620 }621 }622 }623624 match value {625 Some(value) => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.try_set(key.clone(), value)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(632 collection.id,633 token_id,634 key,635 ));636 }637 None => {638 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {639 properties.remove(&key)640 })641 .map_err(<CommonError<T>>::from)?;642643 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(644 collection.id,645 token_id,646 key,647 ));648 }649 }650 }651652 Ok(())653 }654655 pub fn set_token_properties(656 collection: &RefungibleHandle<T>,657 sender: &T::CrossAccountId,658 token_id: TokenId,659 properties: impl Iterator<Item = Property>,660 is_token_create: bool,661 nesting_budget: &dyn Budget,662 ) -> DispatchResult {663 Self::modify_token_properties(664 collection,665 sender,666 token_id,667 properties.map(|p| (p.key, Some(p.value))),668 is_token_create,669 nesting_budget,670 )671 }672673 pub fn set_token_property(674 collection: &RefungibleHandle<T>,675 sender: &T::CrossAccountId,676 token_id: TokenId,677 property: Property,678 nesting_budget: &dyn Budget,679 ) -> DispatchResult {680 let is_token_create = false;681682 Self::set_token_properties(683 collection,684 sender,685 token_id,686 [property].into_iter(),687 is_token_create,688 nesting_budget,689 )690 }691692 pub fn delete_token_properties(693 collection: &RefungibleHandle<T>,694 sender: &T::CrossAccountId,695 token_id: TokenId,696 property_keys: impl Iterator<Item = PropertyKey>,697 nesting_budget: &dyn Budget,698 ) -> DispatchResult {699 let is_token_create = false;700701 Self::modify_token_properties(702 collection,703 sender,704 token_id,705 property_keys.into_iter().map(|key| (key, None)),706 is_token_create,707 nesting_budget,708 )709 }710711 pub fn delete_token_property(712 collection: &RefungibleHandle<T>,713 sender: &T::CrossAccountId,714 token_id: TokenId,715 property_key: PropertyKey,716 nesting_budget: &dyn Budget,717 ) -> DispatchResult {718 Self::delete_token_properties(719 collection,720 sender,721 token_id,722 [property_key].into_iter(),723 nesting_budget,724 )725 }726727 728 729 730 731 732 733 734 735 736 pub fn transfer(737 collection: &RefungibleHandle<T>,738 from: &T::CrossAccountId,739 to: &T::CrossAccountId,740 token: TokenId,741 amount: u128,742 nesting_budget: &dyn Budget,743 ) -> DispatchResult {744 ensure!(745 collection.limits.transfers_enabled(),746 <CommonError<T>>::TransferNotAllowed747 );748749 if collection.permissions.access() == AccessMode::AllowList {750 collection.check_allowlist(from)?;751 collection.check_allowlist(to)?;752 }753 <PalletCommon<T>>::ensure_correct_receiver(to)?;754755 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));756 let updated_balance_from = initial_balance_from757 .checked_sub(amount)758 .ok_or(<CommonError<T>>::TokenValueTooLow)?;759 let mut create_target = false;760 let from_to_differ = from != to;761 let updated_balance_to = if from != to {762 let old_balance = <Balance<T>>::get((collection.id, token, to));763 if old_balance == 0 {764 create_target = true;765 }766 Some(767 old_balance768 .checked_add(amount)769 .ok_or(ArithmeticError::Overflow)?,770 )771 } else {772 None773 };774775 let account_balance_from = if updated_balance_from == 0 {776 Some(777 <AccountBalance<T>>::get((collection.id, from))778 .checked_sub(1)779 780 .ok_or(ArithmeticError::Underflow)?,781 )782 } else {783 None784 };785 786 787 let account_balance_to = if create_target && from_to_differ {788 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))789 .checked_add(1)790 .ok_or(ArithmeticError::Overflow)?;791 ensure!(792 account_balance_to < collection.limits.account_token_ownership_limit(),793 <CommonError<T>>::AccountTokenLimitExceeded,794 );795796 Some(account_balance_to)797 } else {798 None799 };800801 802803 <PalletStructure<T>>::nest_if_sent_to_token(804 from.clone(),805 to,806 collection.id,807 token,808 nesting_budget,809 )?;810811 if let Some(updated_balance_to) = updated_balance_to {812 813 if updated_balance_from == 0 {814 <Balance<T>>::remove((collection.id, token, from));815 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);816 } else {817 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);818 }819 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);820 if let Some(account_balance_from) = account_balance_from {821 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);822 <Owned<T>>::remove((collection.id, from, token));823 }824 if let Some(account_balance_to) = account_balance_to {825 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);826 <Owned<T>>::insert((collection.id, to, token), true);827 }828 }829830 <PalletEvm<T>>::deposit_log(831 ERC20Events::Transfer {832 from: *from.as_eth(),833 to: *to.as_eth(),834 value: amount.into(),835 }836 .to_log(T::EvmTokenAddressMapping::token_to_address(837 collection.id,838 token,839 )),840 );841842 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(843 collection.id,844 token,845 from.clone(),846 to.clone(),847 amount,848 ));849850 let total_supply = <TotalSupply<T>>::get((collection.id, token));851852 if amount == total_supply {853 854 <PalletEvm<T>>::deposit_log(855 ERC721Events::Transfer {856 from: *from.as_eth(),857 to: *to.as_eth(),858 token_id: token.into(),859 }860 .to_log(collection_id_to_address(collection.id)),861 );862 } else if let Some(updated_balance_to) = updated_balance_to {863 864 865 if initial_balance_from == total_supply {866 867 868 <PalletEvm<T>>::deposit_log(869 ERC721Events::Transfer {870 from: *from.as_eth(),871 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,872 token_id: token.into(),873 }874 .to_log(collection_id_to_address(collection.id)),875 );876 } else if updated_balance_to == total_supply {877 878 <PalletEvm<T>>::deposit_log(879 ERC721Events::Transfer {880 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,881 to: *to.as_eth(),882 token_id: token.into(),883 }884 .to_log(collection_id_to_address(collection.id)),885 );886 }887 }888889 Ok(())890 }891892 893 894 895 896 897 pub fn create_multiple_items(898 collection: &RefungibleHandle<T>,899 sender: &T::CrossAccountId,900 data: Vec<CreateItemData<T::CrossAccountId>>,901 nesting_budget: &dyn Budget,902 ) -> DispatchResult {903 if !collection.is_owner_or_admin(sender) {904 ensure!(905 collection.permissions.mint_mode(),906 <CommonError<T>>::PublicMintingNotAllowed907 );908 collection.check_allowlist(sender)?;909910 for item in data.iter() {911 for user in item.users.keys() {912 collection.check_allowlist(user)?;913 }914 }915 }916917 for item in data.iter() {918 for (owner, _) in item.users.iter() {919 <PalletCommon<T>>::ensure_correct_receiver(owner)?;920 }921 }922923 924 let totals = data925 .iter()926 .map(|data| {927 Ok(data928 .users929 .iter()930 .map(|u| u.1)931 .try_fold(0u128, |acc, v| acc.checked_add(*v))932 .ok_or(ArithmeticError::Overflow)?)933 })934 .collect::<Result<Vec<_>, DispatchError>>()?;935 for total in &totals {936 ensure!(937 *total <= MAX_REFUNGIBLE_PIECES,938 <Error<T>>::WrongRefungiblePieces939 );940 }941942 let first_token_id = <TokensMinted<T>>::get(collection.id);943 let tokens_minted = first_token_id944 .checked_add(data.len() as u32)945 .ok_or(ArithmeticError::Overflow)?;946 ensure!(947 tokens_minted < collection.limits.token_limit(),948 <CommonError<T>>::CollectionTokenLimitExceeded949 );950951 let mut balances = BTreeMap::new();952 for data in &data {953 for owner in data.users.keys() {954 let balance = balances955 .entry(owner)956 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));957 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;958959 ensure!(960 *balance <= collection.limits.account_token_ownership_limit(),961 <CommonError<T>>::AccountTokenLimitExceeded,962 );963 }964 }965966 for (i, token) in data.iter().enumerate() {967 let token_id = TokenId(first_token_id + i as u32 + 1);968 for (to, _) in token.users.iter() {969 <PalletStructure<T>>::check_nesting(970 sender.clone(),971 to,972 collection.id,973 token_id,974 nesting_budget,975 )?;976 }977 }978979 980981 with_transaction(|| {982 for (i, data) in data.iter().enumerate() {983 let token_id = first_token_id + i as u32 + 1;984 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);985986 for (user, amount) in data.users.iter() {987 if *amount == 0 {988 continue;989 }990 <Balance<T>>::insert((collection.id, token_id, &user), amount);991 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);992 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(993 user,994 collection.id,995 TokenId(token_id),996 );997 }998999 if let Err(e) = Self::set_token_properties(1000 collection,1001 sender,1002 TokenId(token_id),1003 data.properties.clone().into_iter(),1004 true,1005 nesting_budget,1006 ) {1007 return TransactionOutcome::Rollback(Err(e));1008 }1009 }1010 TransactionOutcome::Commit(Ok(()))1011 })?;10121013 <TokensMinted<T>>::insert(collection.id, tokens_minted);10141015 for (account, balance) in balances {1016 <AccountBalance<T>>::insert((collection.id, account), balance);1017 }10181019 for (i, token) in data.into_iter().enumerate() {1020 let token_id = first_token_id + i as u32 + 1;10211022 let receivers = token1023 .users1024 .into_iter()1025 .filter(|(_, amount)| *amount > 0)1026 .collect::<Vec<_>>();10271028 if let [(user, _)] = receivers.as_slice() {1029 1030 <PalletEvm<T>>::deposit_log(1031 ERC721Events::Transfer {1032 from: H160::default(),1033 to: *user.as_eth(),1034 token_id: token_id.into(),1035 }1036 .to_log(collection_id_to_address(collection.id)),1037 );1038 } else if let [_, ..] = receivers.as_slice() {1039 1040 <PalletEvm<T>>::deposit_log(1041 ERC721Events::Transfer {1042 from: H160::default(),1043 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1044 token_id: token_id.into(),1045 }1046 .to_log(collection_id_to_address(collection.id)),1047 );1048 }10491050 for (user, amount) in receivers.into_iter() {1051 <PalletEvm<T>>::deposit_log(1052 ERC20Events::Transfer {1053 from: H160::default(),1054 to: *user.as_eth(),1055 value: amount.into(),1056 }1057 .to_log(T::EvmTokenAddressMapping::token_to_address(1058 collection.id,1059 TokenId(token_id),1060 )),1061 );1062 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1063 collection.id,1064 TokenId(token_id),1065 user,1066 amount,1067 ));1068 }1069 }1070 Ok(())1071 }10721073 pub fn set_allowance_unchecked(1074 collection: &RefungibleHandle<T>,1075 sender: &T::CrossAccountId,1076 spender: &T::CrossAccountId,1077 token: TokenId,1078 amount: u128,1079 ) {1080 if amount == 0 {1081 <Allowance<T>>::remove((collection.id, token, sender, spender));1082 } else {1083 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1084 }10851086 <PalletEvm<T>>::deposit_log(1087 ERC20Events::Approval {1088 owner: *sender.as_eth(),1089 spender: *spender.as_eth(),1090 value: amount.into(),1091 }1092 .to_log(T::EvmTokenAddressMapping::token_to_address(1093 collection.id,1094 token,1095 )),1096 );1097 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1098 collection.id,1099 token,1100 sender.clone(),1101 spender.clone(),1102 amount,1103 ))1104 }11051106 1107 1108 1109 pub fn set_allowance(1110 collection: &RefungibleHandle<T>,1111 sender: &T::CrossAccountId,1112 spender: &T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 ) -> DispatchResult {1116 if collection.permissions.access() == AccessMode::AllowList {1117 collection.check_allowlist(sender)?;1118 collection.check_allowlist(spender)?;1119 }11201121 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11221123 if <Balance<T>>::get((collection.id, token, sender)) < amount {1124 ensure!(1125 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1126 <CommonError<T>>::CantApproveMoreThanOwned1127 );1128 }11291130 11311132 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1133 Ok(())1134 }11351136 1137 fn check_allowed(1138 collection: &RefungibleHandle<T>,1139 spender: &T::CrossAccountId,1140 from: &T::CrossAccountId,1141 token: TokenId,1142 amount: u128,1143 nesting_budget: &dyn Budget,1144 ) -> Result<Option<u128>, DispatchError> {1145 if spender.conv_eq(from) {1146 return Ok(None);1147 }1148 if collection.permissions.access() == AccessMode::AllowList {1149 1150 collection.check_allowlist(spender)?;1151 }1152 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1153 1154 ensure!(1155 <PalletStructure<T>>::check_indirectly_owned(1156 spender.clone(),1157 source.0,1158 source.1,1159 None,1160 nesting_budget1161 )?,1162 <CommonError<T>>::ApprovedValueTooLow,1163 );1164 return Ok(None);1165 }1166 let allowance =1167 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1168 if allowance.is_none() {1169 ensure!(1170 collection.ignores_allowance(spender),1171 <CommonError<T>>::ApprovedValueTooLow1172 );1173 }1174 Ok(allowance)1175 }11761177 1178 1179 1180 1181 1182 1183 pub fn transfer_from(1184 collection: &RefungibleHandle<T>,1185 spender: &T::CrossAccountId,1186 from: &T::CrossAccountId,1187 to: &T::CrossAccountId,1188 token: TokenId,1189 amount: u128,1190 nesting_budget: &dyn Budget,1191 ) -> DispatchResult {1192 let allowance =1193 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11941195 11961197 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1198 if let Some(allowance) = allowance {1199 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1200 }1201 Ok(())1202 }12031204 1205 1206 1207 1208 1209 1210 pub fn burn_from(1211 collection: &RefungibleHandle<T>,1212 spender: &T::CrossAccountId,1213 from: &T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResult {1218 let allowance =1219 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12201221 12221223 Self::burn(collection, from, token, amount)?;1224 if let Some(allowance) = allowance {1225 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1226 }1227 Ok(())1228 }12291230 1231 1232 1233 1234 1235 1236 1237 pub fn create_item(1238 collection: &RefungibleHandle<T>,1239 sender: &T::CrossAccountId,1240 data: CreateItemData<T::CrossAccountId>,1241 nesting_budget: &dyn Budget,1242 ) -> DispatchResult {1243 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1244 }12451246 1247 1248 1249 1250 1251 1252 1253 pub fn repartition(1254 collection: &RefungibleHandle<T>,1255 owner: &T::CrossAccountId,1256 token: TokenId,1257 amount: u128,1258 ) -> DispatchResult {1259 ensure!(1260 amount <= MAX_REFUNGIBLE_PIECES,1261 <Error<T>>::WrongRefungiblePieces1262 );1263 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1264 1265 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1266 let balance = <Balance<T>>::get((collection.id, token, owner));1267 ensure!(1268 total_pieces == balance,1269 <Error<T>>::RepartitionWhileNotOwningAllPieces1270 );12711272 <Balance<T>>::insert((collection.id, token, owner), amount);1273 <TotalSupply<T>>::insert((collection.id, token), amount);12741275 if amount > total_pieces {1276 let mint_amount = amount - total_pieces;1277 <PalletEvm<T>>::deposit_log(1278 ERC20Events::Transfer {1279 from: H160::default(),1280 to: *owner.as_eth(),1281 value: mint_amount.into(),1282 }1283 .to_log(T::EvmTokenAddressMapping::token_to_address(1284 collection.id,1285 token,1286 )),1287 );1288 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1289 collection.id,1290 token,1291 owner.clone(),1292 mint_amount,1293 ));1294 } else if total_pieces > amount {1295 let burn_amount = total_pieces - amount;1296 <PalletEvm<T>>::deposit_log(1297 ERC20Events::Transfer {1298 from: *owner.as_eth(),1299 to: H160::default(),1300 value: burn_amount.into(),1301 }1302 .to_log(T::EvmTokenAddressMapping::token_to_address(1303 collection.id,1304 token,1305 )),1306 );1307 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1308 collection.id,1309 token,1310 owner.clone(),1311 burn_amount,1312 ));1313 }13141315 Ok(())1316 }13171318 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1319 let mut owner = None;1320 let mut count = 0;1321 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1322 count += 1;1323 if count > 1 {1324 return None;1325 }1326 owner = Some(key);1327 }1328 owner1329 }13301331 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1332 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1333 }13341335 pub fn set_collection_properties(1336 collection: &RefungibleHandle<T>,1337 sender: &T::CrossAccountId,1338 properties: Vec<Property>,1339 ) -> DispatchResult {1340 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1341 }13421343 pub fn delete_collection_properties(1344 collection: &RefungibleHandle<T>,1345 sender: &T::CrossAccountId,1346 property_keys: Vec<PropertyKey>,1347 ) -> DispatchResult {1348 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1349 }13501351 pub fn set_token_property_permissions(1352 collection: &RefungibleHandle<T>,1353 sender: &T::CrossAccountId,1354 property_permissions: Vec<PropertyKeyPermission>,1355 ) -> DispatchResult {1356 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1357 }13581359 pub fn set_scoped_token_property_permissions(1360 collection: &RefungibleHandle<T>,1361 sender: &T::CrossAccountId,1362 scope: PropertyScope,1363 property_permissions: Vec<PropertyKeyPermission>,1364 ) -> DispatchResult {1365 <PalletCommon<T>>::set_scoped_token_property_permissions(1366 collection,1367 sender,1368 scope,1369 property_permissions,1370 )1371 }13721373 1374 1375 1376 1377 1378 1379 pub fn token_owners(1380 collection_id: CollectionId,1381 token: TokenId,1382 ) -> Option<Vec<T::CrossAccountId>> {1383 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1384 .map(|(owner, _amount)| owner)1385 .take(10)1386 .collect();13871388 if res.is_empty() {1389 None1390 } else {1391 Some(res)1392 }1393 }1394}