12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#![cfg_attr(not(feature = "std"), no_std)]8081use core::ops::Deref;82use evm_coder::ToLog;83use frame_support::ensure;84use pallet_evm::account::CrossAccountId;85use up_data_structs::{86 AccessMode, CollectionId, CollectionFlags, TokenId, CreateCollectionData,87 mapping::TokenAddressMapping, budget::Budget, PropertyKey, Property,88};89use pallet_common::{90 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,91 eth::collection_id_to_address,92};93use pallet_evm::Pallet as PalletEvm;94use pallet_structure::Pallet as PalletStructure;95use pallet_evm_coder_substrate::WithRecorder;96use sp_core::H160;97use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};98use sp_std::{collections::btree_map::BTreeMap, vec::Vec};99100pub use pallet::*;101102use crate::erc::ERC20Events;103#[cfg(feature = "runtime-benchmarks")]104pub mod benchmarking;105pub mod common;106pub mod erc;107pub mod weights;108109pub type CreateItemData<T> = (<T as pallet_evm::Config>::CrossAccountId, u128);110pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;111112#[frame_support::pallet]113pub mod pallet {114 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};115 use up_data_structs::CollectionId;116 use super::weights::WeightInfo;117118 #[pallet::error]119 pub enum Error<T> {120 121 NotFungibleDataUsedToMintFungibleCollectionToken,122 123 FungibleItemsHaveNoId,124 125 FungibleItemsDontHaveData,126 127 FungibleDisallowsNesting,128 129 SettingPropertiesNotAllowed,130 131 SettingAllowanceForAllNotAllowed,132 133 FungibleTokensAreAlwaysValid,134 }135136 #[pallet::config]137 pub trait Config:138 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config139 {140 type WeightInfo: WeightInfo;141 }142143 #[pallet::pallet]144 pub struct Pallet<T>(_);145146 147 #[pallet::storage]148 pub type TotalSupply<T: Config> =149 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;150151 152 #[pallet::storage]153 pub type Balance<T: Config> = StorageNMap<154 Key = (155 Key<Twox64Concat, CollectionId>,156 Key<Blake2_128Concat, T::CrossAccountId>,157 ),158 Value = u128,159 QueryKind = ValueQuery,160 >;161162 163 #[pallet::storage]164 pub type Allowance<T: Config> = StorageNMap<165 Key = (166 Key<Twox64Concat, CollectionId>,167 Key<Blake2_128, T::CrossAccountId>, 168 Key<Blake2_128Concat, T::CrossAccountId>, 169 ),170 Value = u128,171 QueryKind = ValueQuery,172 >;173}174175176177pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);178179180impl<T: Config> FungibleHandle<T> {181 182 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {183 Self(inner)184 }185186 187 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {188 self.0189 }190 191 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {192 &mut self.0193 }194}195impl<T: Config> WithRecorder<T> for FungibleHandle<T> {196 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {197 self.0.recorder()198 }199 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {200 self.0.into_recorder()201 }202}203impl<T: Config> Deref for FungibleHandle<T> {204 type Target = pallet_common::CollectionHandle<T>;205206 fn deref(&self) -> &Self::Target {207 &self.0208 }209}210211212impl<T: Config> Pallet<T> {213 214 pub fn init_collection(215 owner: T::CrossAccountId,216 payer: T::CrossAccountId,217 data: CreateCollectionData<T::AccountId>,218 flags: CollectionFlags,219 ) -> Result<CollectionId, DispatchError> {220 <PalletCommon<T>>::init_collection(owner, payer, data, flags)221 }222223 224 pub fn init_foreign_collection(225 owner: T::CrossAccountId,226 payer: T::CrossAccountId,227 data: CreateCollectionData<T::AccountId>,228 ) -> Result<CollectionId, DispatchError> {229 let id = <PalletCommon<T>>::init_collection(230 owner,231 payer,232 data,233 CollectionFlags {234 foreign: true,235 ..Default::default()236 },237 )?;238 Ok(id)239 }240241 242 pub fn destroy_collection(243 collection: FungibleHandle<T>,244 sender: &T::CrossAccountId,245 ) -> DispatchResult {246 let id = collection.id;247248 if Self::collection_has_tokens(id) {249 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());250 }251252 253254 PalletCommon::destroy_collection(collection.0, sender)?;255256 <TotalSupply<T>>::remove(id);257 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);258 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);259 Ok(())260 }261262 263 pub fn set_collection_properties(264 collection: &FungibleHandle<T>,265 sender: &T::CrossAccountId,266 properties: Vec<Property>,267 ) -> DispatchResult {268 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())269 }270271 272 pub fn delete_collection_properties(273 collection: &FungibleHandle<T>,274 sender: &T::CrossAccountId,275 property_keys: Vec<PropertyKey>,276 ) -> DispatchResult {277 <PalletCommon<T>>::delete_collection_properties(278 collection,279 sender,280 property_keys.into_iter(),281 )282 }283284 285 fn collection_has_tokens(collection_id: CollectionId) -> bool {286 <TotalSupply<T>>::get(collection_id) != 0287 }288289 290 291 292 pub fn burn(293 collection: &FungibleHandle<T>,294 owner: &T::CrossAccountId,295 amount: u128,296 ) -> DispatchResult {297 let total_supply = <TotalSupply<T>>::get(collection.id)298 .checked_sub(amount)299 .ok_or(<CommonError<T>>::TokenValueTooLow)?;300301 let balance = <Balance<T>>::get((collection.id, owner))302 .checked_sub(amount)303 .ok_or(<CommonError<T>>::TokenValueTooLow)?;304305 306 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);307308 if collection.permissions.access() == AccessMode::AllowList {309 collection.check_allowlist(owner)?;310 }311312 313314 if balance == 0 {315 <Balance<T>>::remove((collection.id, owner));316 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());317 } else {318 <Balance<T>>::insert((collection.id, owner), balance);319 }320 <TotalSupply<T>>::insert(collection.id, total_supply);321322 <PalletEvm<T>>::deposit_log(323 ERC20Events::Transfer {324 from: *owner.as_eth(),325 to: H160::default(),326 value: amount.into(),327 }328 .to_log(collection_id_to_address(collection.id)),329 );330 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(331 collection.id,332 TokenId::default(),333 owner.clone(),334 amount,335 ));336 Ok(())337 }338339 340 pub fn burn_foreign(341 collection: &FungibleHandle<T>,342 owner: &T::CrossAccountId,343 amount: u128,344 ) -> DispatchResult {345 let total_supply = <TotalSupply<T>>::get(collection.id)346 .checked_sub(amount)347 .ok_or(<CommonError<T>>::TokenValueTooLow)?;348349 let balance = <Balance<T>>::get((collection.id, owner))350 .checked_sub(amount)351 .ok_or(<CommonError<T>>::TokenValueTooLow)?;352 353354 if balance == 0 {355 <Balance<T>>::remove((collection.id, owner));356 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());357 } else {358 <Balance<T>>::insert((collection.id, owner), balance);359 }360 <TotalSupply<T>>::insert(collection.id, total_supply);361362 <PalletEvm<T>>::deposit_log(363 ERC20Events::Transfer {364 from: *owner.as_eth(),365 to: H160::default(),366 value: amount.into(),367 }368 .to_log(collection_id_to_address(collection.id)),369 );370 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(371 collection.id,372 TokenId::default(),373 owner.clone(),374 amount,375 ));376 Ok(())377 }378379 380 381 382 383 384 385 386 pub fn transfer(387 collection: &FungibleHandle<T>,388 from: &T::CrossAccountId,389 to: &T::CrossAccountId,390 amount: u128,391 nesting_budget: &dyn Budget,392 ) -> DispatchResult {393 ensure!(394 collection.limits.transfers_enabled(),395 <CommonError<T>>::TransferNotAllowed,396 );397398 if collection.permissions.access() == AccessMode::AllowList {399 collection.check_allowlist(from)?;400 collection.check_allowlist(to)?;401 }402 <PalletCommon<T>>::ensure_correct_receiver(to)?;403404 let balance_from = <Balance<T>>::get((collection.id, from))405 .checked_sub(amount)406 .ok_or(<CommonError<T>>::TokenValueTooLow)?;407 let balance_to = if from != to && amount != 0 {408 Some(409 <Balance<T>>::get((collection.id, to))410 .checked_add(amount)411 .ok_or(ArithmeticError::Overflow)?,412 )413 } else {414 None415 };416417 418419 if let Some(balance_to) = balance_to {420 421422 <PalletStructure<T>>::nest_if_sent_to_token(423 from.clone(),424 to,425 collection.id,426 TokenId::default(),427 nesting_budget,428 )?;429430 if balance_from == 0 {431 <Balance<T>>::remove((collection.id, from));432 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());433 } else {434 <Balance<T>>::insert((collection.id, from), balance_from);435 }436 <Balance<T>>::insert((collection.id, to), balance_to);437 }438439 <PalletEvm<T>>::deposit_log(440 ERC20Events::Transfer {441 from: *from.as_eth(),442 to: *to.as_eth(),443 value: amount.into(),444 }445 .to_log(collection_id_to_address(collection.id)),446 );447 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(448 collection.id,449 TokenId::default(),450 from.clone(),451 to.clone(),452 amount,453 ));454 Ok(())455 }456457 458 459 460 pub fn create_multiple_items_common(461 collection: &FungibleHandle<T>,462 sender: &T::CrossAccountId,463 data: BTreeMap<T::CrossAccountId, u128>,464 nesting_budget: &dyn Budget,465 ) -> DispatchResult {466 let total_supply = data467 .iter()468 .map(|(_, v)| *v)469 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {470 acc.checked_add(v)471 })472 .ok_or(ArithmeticError::Overflow)?;473474 for (to, _) in data.iter() {475 <PalletStructure<T>>::check_nesting(476 sender.clone(),477 to,478 collection.id,479 TokenId::default(),480 nesting_budget,481 )?;482 }483484 let updated_balances = data485 .into_iter()486 .map(|(user, amount)| {487 let updated_balance = <Balance<T>>::get((collection.id, &user))488 .checked_add(amount)489 .ok_or(ArithmeticError::Overflow)?;490 Ok((user, amount, updated_balance))491 })492 .collect::<Result<Vec<_>, DispatchError>>()?;493494 495496 <TotalSupply<T>>::insert(collection.id, total_supply);497 for (user, amount, updated_balance) in updated_balances {498 <Balance<T>>::insert((collection.id, &user), updated_balance);499 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(500 &user,501 collection.id,502 TokenId::default(),503 );504 <PalletEvm<T>>::deposit_log(505 ERC20Events::Transfer {506 from: H160::default(),507 to: *user.as_eth(),508 value: amount.into(),509 }510 .to_log(collection_id_to_address(collection.id)),511 );512 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(513 collection.id,514 TokenId::default(),515 user.clone(),516 amount,517 ));518 }519520 Ok(())521 }522523 524 525 pub fn create_multiple_items(526 collection: &FungibleHandle<T>,527 sender: &T::CrossAccountId,528 data: BTreeMap<T::CrossAccountId, u128>,529 nesting_budget: &dyn Budget,530 ) -> DispatchResult {531 532 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);533534 if !collection.is_owner_or_admin(sender) {535 ensure!(536 collection.permissions.mint_mode(),537 <CommonError<T>>::PublicMintingNotAllowed538 );539 collection.check_allowlist(sender)?;540541 for (owner, _) in data.iter() {542 collection.check_allowlist(owner)?;543 }544 }545546 Self::create_multiple_items_common(collection, sender, data, nesting_budget)547 }548549 550 551 pub fn create_multiple_items_foreign(552 collection: &FungibleHandle<T>,553 sender: &T::CrossAccountId,554 data: BTreeMap<T::CrossAccountId, u128>,555 nesting_budget: &dyn Budget,556 ) -> DispatchResult {557 Self::create_multiple_items_common(collection, sender, data, nesting_budget)558 }559560 fn set_allowance_unchecked(561 collection: &FungibleHandle<T>,562 owner: &T::CrossAccountId,563 spender: &T::CrossAccountId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, owner, spender));568 } else {569 <Allowance<T>>::insert((collection.id, owner, spender), amount);570 }571572 <PalletEvm<T>>::deposit_log(573 ERC20Events::Approval {574 owner: *owner.as_eth(),575 spender: *spender.as_eth(),576 value: amount.into(),577 }578 .to_log(collection_id_to_address(collection.id)),579 );580 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(581 collection.id,582 TokenId(0),583 owner.clone(),584 spender.clone(),585 amount,586 ));587 }588589 590 591 592 593 594 595 pub fn set_allowance(596 collection: &FungibleHandle<T>,597 owner: &T::CrossAccountId,598 spender: &T::CrossAccountId,599 amount: u128,600 ) -> DispatchResult {601 if collection.permissions.access() == AccessMode::AllowList {602 collection.check_allowlist(owner)?;603 collection.check_allowlist(spender)?;604 }605606 if <Balance<T>>::get((collection.id, owner)) < amount {607 ensure!(608 collection.ignores_owned_amount(owner),609 <CommonError<T>>::CantApproveMoreThanOwned610 );611 }612613 614615 Self::set_allowance_unchecked(collection, owner, spender, amount);616 Ok(())617 }618619 620 621 622 623 624 625 626 pub fn set_allowance_from(627 collection: &FungibleHandle<T>,628 sender: &T::CrossAccountId,629 from: &T::CrossAccountId,630 to: &T::CrossAccountId,631 amount: u128,632 ) -> DispatchResult {633 if collection.permissions.access() == AccessMode::AllowList {634 collection.check_allowlist(sender)?;635 collection.check_allowlist(from)?;636 collection.check_allowlist(to)?;637 }638639 ensure!(640 sender.conv_eq(from),641 <CommonError<T>>::AddressIsNotEthMirror642 );643644 if <Balance<T>>::get((collection.id, from)) < amount {645 ensure!(646 collection.limits.owner_can_transfer()647 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),648 <CommonError<T>>::CantApproveMoreThanOwned649 );650 }651652 653654 Self::set_allowance_unchecked(collection, from, to, amount);655 Ok(())656 }657658 659 660 661 662 663 664 665 fn check_allowed(666 collection: &FungibleHandle<T>,667 spender: &T::CrossAccountId,668 from: &T::CrossAccountId,669 amount: u128,670 nesting_budget: &dyn Budget,671 ) -> Result<Option<u128>, DispatchError> {672 if spender.conv_eq(from) {673 return Ok(None);674 }675 if collection.permissions.access() == AccessMode::AllowList {676 677 collection.check_allowlist(spender)?;678 }679680 if collection.ignores_token_restrictions(spender) {681 return Ok(Self::compute_allowance_decrease(682 collection, from, spender, amount,683 ));684 }685686 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {687 ensure!(688 <PalletStructure<T>>::check_indirectly_owned(689 spender.clone(),690 source.0,691 source.1,692 None,693 nesting_budget694 )?,695 <CommonError<T>>::ApprovedValueTooLow,696 );697 return Ok(None);698 }699700 let allowance = Self::compute_allowance_decrease(collection, from, spender, amount);701 ensure!(allowance.is_some(), <CommonError<T>>::ApprovedValueTooLow);702703 Ok(allowance)704 }705706 707 708 fn compute_allowance_decrease(709 collection: &FungibleHandle<T>,710 from: &T::CrossAccountId,711 spender: &T::CrossAccountId,712 amount: u128,713 ) -> Option<u128> {714 <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount)715 }716717 718 719 720 721722 pub fn transfer_from(723 collection: &FungibleHandle<T>,724 spender: &T::CrossAccountId,725 from: &T::CrossAccountId,726 to: &T::CrossAccountId,727 amount: u128,728 nesting_budget: &dyn Budget,729 ) -> DispatchResult {730 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;731732 733734 Self::transfer(collection, from, to, amount, nesting_budget)?;735 if let Some(allowance) = allowance {736 Self::set_allowance_unchecked(collection, from, spender, allowance);737 }738 Ok(())739 }740741 742 743 744 745 746 pub fn burn_from(747 collection: &FungibleHandle<T>,748 spender: &T::CrossAccountId,749 from: &T::CrossAccountId,750 amount: u128,751 nesting_budget: &dyn Budget,752 ) -> DispatchResult {753 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;754755 756757 Self::burn(collection, from, amount)?;758 if let Some(allowance) = allowance {759 Self::set_allowance_unchecked(collection, from, spender, allowance);760 }761 Ok(())762 }763764 765 766 767 768 769 770 771 pub fn create_item(772 collection: &FungibleHandle<T>,773 sender: &T::CrossAccountId,774 data: CreateItemData<T>,775 nesting_budget: &dyn Budget,776 ) -> DispatchResult {777 Self::create_multiple_items(778 collection,779 sender,780 [(data.0, data.1)].into_iter().collect(),781 nesting_budget,782 )783 }784785 786 787 788 789 pub fn create_item_foreign(790 collection: &FungibleHandle<T>,791 sender: &T::CrossAccountId,792 data: CreateItemData<T>,793 nesting_budget: &dyn Budget,794 ) -> DispatchResult {795 Self::create_multiple_items_foreign(796 collection,797 sender,798 [(data.0, data.1)].into_iter().collect(),799 nesting_budget,800 )801 }802803 804 805 806 807 808 809 pub fn token_owners(810 collection: CollectionId,811 _token: TokenId,812 ) -> Option<Vec<T::CrossAccountId>> {813 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))814 .map(|(owner, _amount)| owner)815 .take(10)816 .collect();817818 if res.is_empty() {819 None820 } else {821 Some(res)822 }823 }824}