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 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);402 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);403 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);404 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);405 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, 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<<<<<<< HEAD428 <Balance<T>>::remove_prefix((collection.id, token_id), None);429 <Allowance<T>>::remove_prefix((collection.id, token_id), None);430431 <PalletEvm<T>>::deposit_log(432 ERC721Events::Transfer {433 from: *owner.as_eth(),434 to: H160::default(),435 token_id: token_id.into(),436 }437 .to_log(collection_id_to_address(collection.id)),438 );439=======440 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);441 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);442 443>>>>>>> 5d9665e0... refactor: switch to new prefix removal methods444 Ok(())445 }446447 448 449 450 451 452 453 454 455 456 457 458 pub fn burn(459 collection: &RefungibleHandle<T>,460 owner: &T::CrossAccountId,461 token: TokenId,462 amount: u128,463 ) -> DispatchResult {464 let total_supply = <TotalSupply<T>>::get((collection.id, token))465 .checked_sub(amount)466 .ok_or(<CommonError<T>>::TokenValueTooLow)?;467468 469 if total_supply == 0 {470 471 ensure!(472 <Balance<T>>::get((collection.id, token, owner)) == amount,473 <CommonError<T>>::TokenValueTooLow474 );475 let account_balance = <AccountBalance<T>>::get((collection.id, owner))476 .checked_sub(1)477 478 .ok_or(ArithmeticError::Underflow)?;479480 481482 <Owned<T>>::remove((collection.id, owner, token));483 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);484 <AccountBalance<T>>::insert((collection.id, owner), account_balance);485 Self::burn_token_unchecked(collection, owner, token)?;486 <PalletEvm<T>>::deposit_log(487 ERC20Events::Transfer {488 from: *owner.as_eth(),489 to: H160::default(),490 value: amount.into(),491 }492 .to_log(collection_id_to_address(collection.id)),493 );494 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(495 collection.id,496 token,497 owner.clone(),498 amount,499 ));500 return Ok(());501 }502503 let balance = <Balance<T>>::get((collection.id, token, owner))504 .checked_sub(amount)505 .ok_or(<CommonError<T>>::TokenValueTooLow)?;506 let account_balance = if balance == 0 {507 <AccountBalance<T>>::get((collection.id, owner))508 .checked_sub(1)509 510 .ok_or(ArithmeticError::Underflow)?511 } else {512 0513 };514515 516517 if balance == 0 {518 <Owned<T>>::remove((collection.id, owner, token));519 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);520 <Balance<T>>::remove((collection.id, token, owner));521 <AccountBalance<T>>::insert((collection.id, owner), account_balance);522523 if let Some(user) = Self::token_owner(collection.id, token) {524 <PalletEvm<T>>::deposit_log(525 ERC721Events::Transfer {526 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,527 to: *user.as_eth(),528 token_id: token.into(),529 }530 .to_log(collection_id_to_address(collection.id)),531 );532 }533 } else {534 <Balance<T>>::insert((collection.id, token, owner), balance);535 }536 <TotalSupply<T>>::insert((collection.id, token), total_supply);537538 <PalletEvm<T>>::deposit_log(539 ERC20Events::Transfer {540 from: *owner.as_eth(),541 to: H160::default(),542 value: amount.into(),543 }544 .to_log(T::EvmTokenAddressMapping::token_to_address(545 collection.id,546 token,547 )),548 );549 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(550 collection.id,551 token,552 owner.clone(),553 amount,554 ));555 Ok(())556 }557558 #[transactional]559 fn modify_token_properties(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 token_id: TokenId,563 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,564 is_token_create: bool,565 nesting_budget: &dyn Budget,566 ) -> DispatchResult {567 let is_collection_admin = || collection.is_owner_or_admin(sender);568 let is_token_owner = || -> Result<bool, DispatchError> {569 let balance = collection.balance(sender.clone(), token_id);570 let total_pieces: u128 =571 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);572 if balance != total_pieces {573 return Ok(false);574 }575576 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(577 sender.clone(),578 collection.id,579 token_id,580 None,581 nesting_budget,582 )?;583584 Ok(is_bundle_owner)585 };586587 for (key, value) in properties {588 let permission = <PalletCommon<T>>::property_permissions(collection.id)589 .get(&key)590 .cloned()591 .unwrap_or_else(PropertyPermission::none);592593 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))594 .get(&key)595 .is_some();596597 match permission {598 PropertyPermission { mutable: false, .. } if is_property_exists => {599 return Err(<CommonError<T>>::NoPermission.into());600 }601602 PropertyPermission {603 collection_admin,604 token_owner,605 ..606 } => {607 608 let is_token_create =609 is_token_create && (collection_admin || token_owner) && value.is_some();610 if !(is_token_create611 || (collection_admin && is_collection_admin())612 || (token_owner && is_token_owner()?))613 {614 fail!(<CommonError<T>>::NoPermission);615 }616 }617 }618619 match value {620 Some(value) => {621 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {622 properties.try_set(key.clone(), value)623 })624 .map_err(<CommonError<T>>::from)?;625626 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(627 collection.id,628 token_id,629 key,630 ));631 }632 None => {633 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {634 properties.remove(&key)635 })636 .map_err(<CommonError<T>>::from)?;637638 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(639 collection.id,640 token_id,641 key,642 ));643 }644 }645 }646647 Ok(())648 }649650 pub fn set_token_properties(651 collection: &RefungibleHandle<T>,652 sender: &T::CrossAccountId,653 token_id: TokenId,654 properties: impl Iterator<Item = Property>,655 is_token_create: bool,656 nesting_budget: &dyn Budget,657 ) -> DispatchResult {658 Self::modify_token_properties(659 collection,660 sender,661 token_id,662 properties.map(|p| (p.key, Some(p.value))),663 is_token_create,664 nesting_budget,665 )666 }667668 pub fn set_token_property(669 collection: &RefungibleHandle<T>,670 sender: &T::CrossAccountId,671 token_id: TokenId,672 property: Property,673 nesting_budget: &dyn Budget,674 ) -> DispatchResult {675 let is_token_create = false;676677 Self::set_token_properties(678 collection,679 sender,680 token_id,681 [property].into_iter(),682 is_token_create,683 nesting_budget,684 )685 }686687 pub fn delete_token_properties(688 collection: &RefungibleHandle<T>,689 sender: &T::CrossAccountId,690 token_id: TokenId,691 property_keys: impl Iterator<Item = PropertyKey>,692 nesting_budget: &dyn Budget,693 ) -> DispatchResult {694 let is_token_create = false;695696 Self::modify_token_properties(697 collection,698 sender,699 token_id,700 property_keys.into_iter().map(|key| (key, None)),701 is_token_create,702 nesting_budget,703 )704 }705706 pub fn delete_token_property(707 collection: &RefungibleHandle<T>,708 sender: &T::CrossAccountId,709 token_id: TokenId,710 property_key: PropertyKey,711 nesting_budget: &dyn Budget,712 ) -> DispatchResult {713 Self::delete_token_properties(714 collection,715 sender,716 token_id,717 [property_key].into_iter(),718 nesting_budget,719 )720 }721722 723 724 725 726 727 728 729 730 731 pub fn transfer(732 collection: &RefungibleHandle<T>,733 from: &T::CrossAccountId,734 to: &T::CrossAccountId,735 token: TokenId,736 amount: u128,737 nesting_budget: &dyn Budget,738 ) -> DispatchResult {739 ensure!(740 collection.limits.transfers_enabled(),741 <CommonError<T>>::TransferNotAllowed742 );743744 if collection.permissions.access() == AccessMode::AllowList {745 collection.check_allowlist(from)?;746 collection.check_allowlist(to)?;747 }748 <PalletCommon<T>>::ensure_correct_receiver(to)?;749750 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));751 let updated_balance_from = initial_balance_from752 .checked_sub(amount)753 .ok_or(<CommonError<T>>::TokenValueTooLow)?;754 let mut create_target = false;755 let from_to_differ = from != to;756 let updated_balance_to = if from != to {757 let old_balance = <Balance<T>>::get((collection.id, token, to));758 if old_balance == 0 {759 create_target = true;760 }761 Some(762 old_balance763 .checked_add(amount)764 .ok_or(ArithmeticError::Overflow)?,765 )766 } else {767 None768 };769770 let account_balance_from = if updated_balance_from == 0 {771 Some(772 <AccountBalance<T>>::get((collection.id, from))773 .checked_sub(1)774 775 .ok_or(ArithmeticError::Underflow)?,776 )777 } else {778 None779 };780 781 782 let account_balance_to = if create_target && from_to_differ {783 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))784 .checked_add(1)785 .ok_or(ArithmeticError::Overflow)?;786 ensure!(787 account_balance_to < collection.limits.account_token_ownership_limit(),788 <CommonError<T>>::AccountTokenLimitExceeded,789 );790791 Some(account_balance_to)792 } else {793 None794 };795796 797798 <PalletStructure<T>>::nest_if_sent_to_token(799 from.clone(),800 to,801 collection.id,802 token,803 nesting_budget,804 )?;805806 if let Some(updated_balance_to) = updated_balance_to {807 808 if updated_balance_from == 0 {809 <Balance<T>>::remove((collection.id, token, from));810 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);811 } else {812 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);813 }814 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);815 if let Some(account_balance_from) = account_balance_from {816 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);817 <Owned<T>>::remove((collection.id, from, token));818 }819 if let Some(account_balance_to) = account_balance_to {820 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);821 <Owned<T>>::insert((collection.id, to, token), true);822 }823 }824825 <PalletEvm<T>>::deposit_log(826 ERC20Events::Transfer {827 from: *from.as_eth(),828 to: *to.as_eth(),829 value: amount.into(),830 }831 .to_log(T::EvmTokenAddressMapping::token_to_address(832 collection.id,833 token,834 )),835 );836837 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(838 collection.id,839 token,840 from.clone(),841 to.clone(),842 amount,843 ));844845 let total_supply = <TotalSupply<T>>::get((collection.id, token));846847 if amount == total_supply {848 849 <PalletEvm<T>>::deposit_log(850 ERC721Events::Transfer {851 from: *from.as_eth(),852 to: *to.as_eth(),853 token_id: token.into(),854 }855 .to_log(collection_id_to_address(collection.id)),856 );857 } else if let Some(updated_balance_to) = updated_balance_to {858 859 860 if initial_balance_from == total_supply {861 862 863 <PalletEvm<T>>::deposit_log(864 ERC721Events::Transfer {865 from: *from.as_eth(),866 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,867 token_id: token.into(),868 }869 .to_log(collection_id_to_address(collection.id)),870 );871 } else if updated_balance_to == total_supply {872 873 <PalletEvm<T>>::deposit_log(874 ERC721Events::Transfer {875 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,876 to: *to.as_eth(),877 token_id: token.into(),878 }879 .to_log(collection_id_to_address(collection.id)),880 );881 }882 }883884 Ok(())885 }886887 888 889 890 891 892 pub fn create_multiple_items(893 collection: &RefungibleHandle<T>,894 sender: &T::CrossAccountId,895 data: Vec<CreateItemData<T::CrossAccountId>>,896 nesting_budget: &dyn Budget,897 ) -> DispatchResult {898 if !collection.is_owner_or_admin(sender) {899 ensure!(900 collection.permissions.mint_mode(),901 <CommonError<T>>::PublicMintingNotAllowed902 );903 collection.check_allowlist(sender)?;904905 for item in data.iter() {906 for user in item.users.keys() {907 collection.check_allowlist(user)?;908 }909 }910 }911912 for item in data.iter() {913 for (owner, _) in item.users.iter() {914 <PalletCommon<T>>::ensure_correct_receiver(owner)?;915 }916 }917918 919 let totals = data920 .iter()921 .map(|data| {922 Ok(data923 .users924 .iter()925 .map(|u| u.1)926 .try_fold(0u128, |acc, v| acc.checked_add(*v))927 .ok_or(ArithmeticError::Overflow)?)928 })929 .collect::<Result<Vec<_>, DispatchError>>()?;930 for total in &totals {931 ensure!(932 *total <= MAX_REFUNGIBLE_PIECES,933 <Error<T>>::WrongRefungiblePieces934 );935 }936937 let first_token_id = <TokensMinted<T>>::get(collection.id);938 let tokens_minted = first_token_id939 .checked_add(data.len() as u32)940 .ok_or(ArithmeticError::Overflow)?;941 ensure!(942 tokens_minted < collection.limits.token_limit(),943 <CommonError<T>>::CollectionTokenLimitExceeded944 );945946 let mut balances = BTreeMap::new();947 for data in &data {948 for owner in data.users.keys() {949 let balance = balances950 .entry(owner)951 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));952 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;953954 ensure!(955 *balance <= collection.limits.account_token_ownership_limit(),956 <CommonError<T>>::AccountTokenLimitExceeded,957 );958 }959 }960961 for (i, token) in data.iter().enumerate() {962 let token_id = TokenId(first_token_id + i as u32 + 1);963 for (to, _) in token.users.iter() {964 <PalletStructure<T>>::check_nesting(965 sender.clone(),966 to,967 collection.id,968 token_id,969 nesting_budget,970 )?;971 }972 }973974 975976 with_transaction(|| {977 for (i, data) in data.iter().enumerate() {978 let token_id = first_token_id + i as u32 + 1;979 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);980981 for (user, amount) in data.users.iter() {982 if *amount == 0 {983 continue;984 }985 <Balance<T>>::insert((collection.id, token_id, &user), amount);986 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);987 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(988 user,989 collection.id,990 TokenId(token_id),991 );992 }993994 if let Err(e) = Self::set_token_properties(995 collection,996 sender,997 TokenId(token_id),998 data.properties.clone().into_iter(),999 true,1000 nesting_budget,1001 ) {1002 return TransactionOutcome::Rollback(Err(e));1003 }1004 }1005 TransactionOutcome::Commit(Ok(()))1006 })?;10071008 <TokensMinted<T>>::insert(collection.id, tokens_minted);10091010 for (account, balance) in balances {1011 <AccountBalance<T>>::insert((collection.id, account), balance);1012 }10131014 for (i, token) in data.into_iter().enumerate() {1015 let token_id = first_token_id + i as u32 + 1;10161017 let receivers = token1018 .users1019 .into_iter()1020 .filter(|(_, amount)| *amount > 0)1021 .collect::<Vec<_>>();10221023 if let [(user, _)] = receivers.as_slice() {1024 1025 <PalletEvm<T>>::deposit_log(1026 ERC721Events::Transfer {1027 from: H160::default(),1028 to: *user.as_eth(),1029 token_id: token_id.into(),1030 }1031 .to_log(collection_id_to_address(collection.id)),1032 );1033 } else if let [_, ..] = receivers.as_slice() {1034 1035 <PalletEvm<T>>::deposit_log(1036 ERC721Events::Transfer {1037 from: H160::default(),1038 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1039 token_id: token_id.into(),1040 }1041 .to_log(collection_id_to_address(collection.id)),1042 );1043 }10441045 for (user, amount) in receivers.into_iter() {1046 <PalletEvm<T>>::deposit_log(1047 ERC20Events::Transfer {1048 from: H160::default(),1049 to: *user.as_eth(),1050 value: amount.into(),1051 }1052 .to_log(T::EvmTokenAddressMapping::token_to_address(1053 collection.id,1054 TokenId(token_id),1055 )),1056 );1057 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1058 collection.id,1059 TokenId(token_id),1060 user,1061 amount,1062 ));1063 }1064 }1065 Ok(())1066 }10671068 pub fn set_allowance_unchecked(1069 collection: &RefungibleHandle<T>,1070 sender: &T::CrossAccountId,1071 spender: &T::CrossAccountId,1072 token: TokenId,1073 amount: u128,1074 ) {1075 if amount == 0 {1076 <Allowance<T>>::remove((collection.id, token, sender, spender));1077 } else {1078 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1079 }10801081 <PalletEvm<T>>::deposit_log(1082 ERC20Events::Approval {1083 owner: *sender.as_eth(),1084 spender: *spender.as_eth(),1085 value: amount.into(),1086 }1087 .to_log(T::EvmTokenAddressMapping::token_to_address(1088 collection.id,1089 token,1090 )),1091 );1092 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1093 collection.id,1094 token,1095 sender.clone(),1096 spender.clone(),1097 amount,1098 ))1099 }11001101 1102 1103 1104 pub fn set_allowance(1105 collection: &RefungibleHandle<T>,1106 sender: &T::CrossAccountId,1107 spender: &T::CrossAccountId,1108 token: TokenId,1109 amount: u128,1110 ) -> DispatchResult {1111 if collection.permissions.access() == AccessMode::AllowList {1112 collection.check_allowlist(sender)?;1113 collection.check_allowlist(spender)?;1114 }11151116 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11171118 if <Balance<T>>::get((collection.id, token, sender)) < amount {1119 ensure!(1120 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1121 <CommonError<T>>::CantApproveMoreThanOwned1122 );1123 }11241125 11261127 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1128 Ok(())1129 }11301131 1132 fn check_allowed(1133 collection: &RefungibleHandle<T>,1134 spender: &T::CrossAccountId,1135 from: &T::CrossAccountId,1136 token: TokenId,1137 amount: u128,1138 nesting_budget: &dyn Budget,1139 ) -> Result<Option<u128>, DispatchError> {1140 if spender.conv_eq(from) {1141 return Ok(None);1142 }1143 if collection.permissions.access() == AccessMode::AllowList {1144 1145 collection.check_allowlist(spender)?;1146 }1147 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1148 1149 ensure!(1150 <PalletStructure<T>>::check_indirectly_owned(1151 spender.clone(),1152 source.0,1153 source.1,1154 None,1155 nesting_budget1156 )?,1157 <CommonError<T>>::ApprovedValueTooLow,1158 );1159 return Ok(None);1160 }1161 let allowance =1162 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1163 if allowance.is_none() {1164 ensure!(1165 collection.ignores_allowance(spender),1166 <CommonError<T>>::ApprovedValueTooLow1167 );1168 }1169 Ok(allowance)1170 }11711172 1173 1174 1175 1176 1177 1178 pub fn transfer_from(1179 collection: &RefungibleHandle<T>,1180 spender: &T::CrossAccountId,1181 from: &T::CrossAccountId,1182 to: &T::CrossAccountId,1183 token: TokenId,1184 amount: u128,1185 nesting_budget: &dyn Budget,1186 ) -> DispatchResult {1187 let allowance =1188 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11891190 11911192 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1193 if let Some(allowance) = allowance {1194 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1195 }1196 Ok(())1197 }11981199 1200 1201 1202 1203 1204 1205 pub fn burn_from(1206 collection: &RefungibleHandle<T>,1207 spender: &T::CrossAccountId,1208 from: &T::CrossAccountId,1209 token: TokenId,1210 amount: u128,1211 nesting_budget: &dyn Budget,1212 ) -> DispatchResult {1213 let allowance =1214 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12151216 12171218 Self::burn(collection, from, token, amount)?;1219 if let Some(allowance) = allowance {1220 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1221 }1222 Ok(())1223 }12241225 1226 1227 1228 1229 1230 1231 1232 pub fn create_item(1233 collection: &RefungibleHandle<T>,1234 sender: &T::CrossAccountId,1235 data: CreateItemData<T::CrossAccountId>,1236 nesting_budget: &dyn Budget,1237 ) -> DispatchResult {1238 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1239 }12401241 1242 1243 1244 1245 1246 1247 1248 pub fn repartition(1249 collection: &RefungibleHandle<T>,1250 owner: &T::CrossAccountId,1251 token: TokenId,1252 amount: u128,1253 ) -> DispatchResult {1254 ensure!(1255 amount <= MAX_REFUNGIBLE_PIECES,1256 <Error<T>>::WrongRefungiblePieces1257 );1258 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1259 1260 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1261 let balance = <Balance<T>>::get((collection.id, token, owner));1262 ensure!(1263 total_pieces == balance,1264 <Error<T>>::RepartitionWhileNotOwningAllPieces1265 );12661267 <Balance<T>>::insert((collection.id, token, owner), amount);1268 <TotalSupply<T>>::insert((collection.id, token), amount);12691270 if amount > total_pieces {1271 let mint_amount = amount - total_pieces;1272 <PalletEvm<T>>::deposit_log(1273 ERC20Events::Transfer {1274 from: H160::default(),1275 to: *owner.as_eth(),1276 value: mint_amount.into(),1277 }1278 .to_log(T::EvmTokenAddressMapping::token_to_address(1279 collection.id,1280 token,1281 )),1282 );1283 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1284 collection.id,1285 token,1286 owner.clone(),1287 mint_amount,1288 ));1289 } else if total_pieces > amount {1290 let burn_amount = total_pieces - amount;1291 <PalletEvm<T>>::deposit_log(1292 ERC20Events::Transfer {1293 from: *owner.as_eth(),1294 to: H160::default(),1295 value: burn_amount.into(),1296 }1297 .to_log(T::EvmTokenAddressMapping::token_to_address(1298 collection.id,1299 token,1300 )),1301 );1302 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1303 collection.id,1304 token,1305 owner.clone(),1306 burn_amount,1307 ));1308 }13091310 Ok(())1311 }13121313 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1314 let mut owner = None;1315 let mut count = 0;1316 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1317 count += 1;1318 if count > 1 {1319 return None;1320 }1321 owner = Some(key);1322 }1323 owner1324 }13251326 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1327 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1328 }13291330 pub fn set_collection_properties(1331 collection: &RefungibleHandle<T>,1332 sender: &T::CrossAccountId,1333 properties: Vec<Property>,1334 ) -> DispatchResult {1335 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1336 }13371338 pub fn delete_collection_properties(1339 collection: &RefungibleHandle<T>,1340 sender: &T::CrossAccountId,1341 property_keys: Vec<PropertyKey>,1342 ) -> DispatchResult {1343 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1344 }13451346 pub fn set_token_property_permissions(1347 collection: &RefungibleHandle<T>,1348 sender: &T::CrossAccountId,1349 property_permissions: Vec<PropertyKeyPermission>,1350 ) -> DispatchResult {1351 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1352 }13531354 pub fn set_scoped_token_property_permissions(1355 collection: &RefungibleHandle<T>,1356 sender: &T::CrossAccountId,1357 scope: PropertyScope,1358 property_permissions: Vec<PropertyKeyPermission>,1359 ) -> DispatchResult {1360 <PalletCommon<T>>::set_scoped_token_property_permissions(1361 collection,1362 sender,1363 scope,1364 property_permissions,1365 )1366 }13671368 1369 1370 1371 1372 1373 1374 pub fn token_owners(1375 collection_id: CollectionId,1376 token: TokenId,1377 ) -> Option<Vec<T::CrossAccountId>> {1378 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1379 .map(|(owner, _amount)| owner)1380 .take(10)1381 .collect();13821383 if res.is_empty() {1384 None1385 } else {1386 Some(res)1387 }1388 }13891390 1391 1392 1393 1394 pub fn set_parent_nft(1395 collection: &RefungibleHandle<T>,1396 rft_token_id: TokenId,1397 sender: T::CrossAccountId,1398 nft_collection: CollectionId,1399 nft_token: TokenId,1400 ) -> DispatchResult {1401 let handle = <CollectionHandle<T>>::try_get(nft_collection)?;1402 if handle.mode != CollectionMode::NFT {1403 return Err("Only NFT token could be parent to RFT".into());1404 }1405 let dispatch = T::CollectionDispatch::dispatch(handle);1406 let dispatch = dispatch.as_dyn();14071408 let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?;1409 if owner != sender {1410 return Err("Only owned token could be set as parent".into());1411 }14121413 let nft_token_address =1414 T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token);14151416 Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address)1417 }14181419 1420 1421 1422 1423 pub fn set_parent_nft_unchecked(1424 collection: &RefungibleHandle<T>,1425 rft_token_id: TokenId,1426 sender: T::CrossAccountId,1427 nft_token_address: T::CrossAccountId,1428 ) -> DispatchResult {1429 let owner_balance = <Balance<T>>::get((collection.id, rft_token_id, &sender));1430 let total_supply = <TotalSupply<T>>::get((collection.id, rft_token_id));1431 if total_supply != owner_balance {1432 return Err("token has multiple owners".into());1433 }14341435 let parent_nft_property_key = key::parent_nft();14361437 let parent_nft_property_value =1438 property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes())1439 .expect("address should fit in value length limit");14401441 <Pallet<T>>::set_scoped_token_property(1442 collection.id,1443 rft_token_id,1444 PropertyScope::Eth,1445 Property {1446 key: parent_nft_property_key,1447 value: parent_nft_property_value,1448 },1449 )?;14501451 Ok(())1452 }1453}