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,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::account::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 }131132 #[pallet::config]133 pub trait Config:134 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config135 {136 type WeightInfo: WeightInfo;137 }138139 #[pallet::pallet]140 #[pallet::generate_store(pub(super) trait Store)]141 pub struct Pallet<T>(_);142143 144 #[pallet::storage]145 pub type TotalSupply<T: Config> =146 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;147148 149 #[pallet::storage]150 pub type Balance<T: Config> = StorageNMap<151 Key = (152 Key<Twox64Concat, CollectionId>,153 Key<Blake2_128Concat, T::CrossAccountId>,154 ),155 Value = u128,156 QueryKind = ValueQuery,157 >;158159 160 #[pallet::storage]161 pub type Allowance<T: Config> = StorageNMap<162 Key = (163 Key<Twox64Concat, CollectionId>,164 Key<Blake2_128, T::CrossAccountId>,165 Key<Blake2_128Concat, T::CrossAccountId>,166 ),167 Value = u128,168 QueryKind = ValueQuery,169 >;170}171172173174pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);175176177impl<T: Config> FungibleHandle<T> {178 179 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {180 Self(inner)181 }182183 184 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {185 self.0186 }187 188 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {189 &mut self.0190 }191}192impl<T: Config> WithRecorder<T> for FungibleHandle<T> {193 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {194 self.0.recorder()195 }196 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {197 self.0.into_recorder()198 }199}200impl<T: Config> Deref for FungibleHandle<T> {201 type Target = pallet_common::CollectionHandle<T>;202203 fn deref(&self) -> &Self::Target {204 &self.0205 }206}207208209impl<T: Config> Pallet<T> {210 211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, CollectionFlags::default())216 }217218 219 pub fn init_foreign_collection(220 owner: T::CrossAccountId,221 data: CreateCollectionData<T::AccountId>,222 ) -> Result<CollectionId, DispatchError> {223 let id = <PalletCommon<T>>::init_collection(224 owner,225 data,226 CollectionFlags {227 foreign: true,228 ..Default::default()229 },230 )?;231 Ok(id)232 }233234 235 pub fn destroy_collection(236 collection: FungibleHandle<T>,237 sender: &T::CrossAccountId,238 ) -> DispatchResult {239 let id = collection.id;240241 if Self::collection_has_tokens(id) {242 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());243 }244245 246247 PalletCommon::destroy_collection(collection.0, sender)?;248249 <TotalSupply<T>>::remove(id);250 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);251 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);252 Ok(())253 }254255 256 fn collection_has_tokens(collection_id: CollectionId) -> bool {257 <TotalSupply<T>>::get(collection_id) != 0258 }259260 261 262 263 pub fn burn(264 collection: &FungibleHandle<T>,265 owner: &T::CrossAccountId,266 amount: u128,267 ) -> DispatchResult {268 let total_supply = <TotalSupply<T>>::get(collection.id)269 .checked_sub(amount)270 .ok_or(<CommonError<T>>::TokenValueTooLow)?;271272 let balance = <Balance<T>>::get((collection.id, owner))273 .checked_sub(amount)274 .ok_or(<CommonError<T>>::TokenValueTooLow)?;275276 277 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);278279 if collection.permissions.access() == AccessMode::AllowList {280 collection.check_allowlist(owner)?;281 }282283 284285 if balance == 0 {286 <Balance<T>>::remove((collection.id, owner));287 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());288 } else {289 <Balance<T>>::insert((collection.id, owner), balance);290 }291 <TotalSupply<T>>::insert(collection.id, total_supply);292293 <PalletEvm<T>>::deposit_log(294 ERC20Events::Transfer {295 from: *owner.as_eth(),296 to: H160::default(),297 value: amount.into(),298 }299 .to_log(collection_id_to_address(collection.id)),300 );301 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(302 collection.id,303 TokenId::default(),304 owner.clone(),305 amount,306 ));307 Ok(())308 }309310 311 pub fn burn_foreign(312 collection: &FungibleHandle<T>,313 owner: &T::CrossAccountId,314 amount: u128,315 ) -> DispatchResult {316 let total_supply = <TotalSupply<T>>::get(collection.id)317 .checked_sub(amount)318 .ok_or(<CommonError<T>>::TokenValueTooLow)?;319320 let balance = <Balance<T>>::get((collection.id, owner))321 .checked_sub(amount)322 .ok_or(<CommonError<T>>::TokenValueTooLow)?;323 324325 if balance == 0 {326 <Balance<T>>::remove((collection.id, owner));327 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());328 } else {329 <Balance<T>>::insert((collection.id, owner), balance);330 }331 <TotalSupply<T>>::insert(collection.id, total_supply);332333 <PalletEvm<T>>::deposit_log(334 ERC20Events::Transfer {335 from: *owner.as_eth(),336 to: H160::default(),337 value: amount.into(),338 }339 .to_log(collection_id_to_address(collection.id)),340 );341 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(342 collection.id,343 TokenId::default(),344 owner.clone(),345 amount,346 ));347 Ok(())348 }349350 351 352 353 354 355 356 357 pub fn transfer(358 collection: &FungibleHandle<T>,359 from: &T::CrossAccountId,360 to: &T::CrossAccountId,361 amount: u128,362 nesting_budget: &dyn Budget,363 ) -> DispatchResult {364 ensure!(365 collection.limits.transfers_enabled(),366 <CommonError<T>>::TransferNotAllowed,367 );368369 if collection.permissions.access() == AccessMode::AllowList {370 collection.check_allowlist(from)?;371 collection.check_allowlist(to)?;372 }373 <PalletCommon<T>>::ensure_correct_receiver(to)?;374375 let balance_from = <Balance<T>>::get((collection.id, from))376 .checked_sub(amount)377 .ok_or(<CommonError<T>>::TokenValueTooLow)?;378 let balance_to = if from != to {379 Some(380 <Balance<T>>::get((collection.id, to))381 .checked_add(amount)382 .ok_or(ArithmeticError::Overflow)?,383 )384 } else {385 None386 };387388 389390 <PalletStructure<T>>::nest_if_sent_to_token(391 from.clone(),392 to,393 collection.id,394 TokenId::default(),395 nesting_budget,396 )?;397398 if let Some(balance_to) = balance_to {399 400 if balance_from == 0 {401 <Balance<T>>::remove((collection.id, from));402 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());403 } else {404 <Balance<T>>::insert((collection.id, from), balance_from);405 }406 <Balance<T>>::insert((collection.id, to), balance_to);407 }408409 <PalletEvm<T>>::deposit_log(410 ERC20Events::Transfer {411 from: *from.as_eth(),412 to: *to.as_eth(),413 value: amount.into(),414 }415 .to_log(collection_id_to_address(collection.id)),416 );417 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(418 collection.id,419 TokenId::default(),420 from.clone(),421 to.clone(),422 amount,423 ));424 Ok(())425 }426427 428 429 430 pub fn create_multiple_items_common(431 collection: &FungibleHandle<T>,432 sender: &T::CrossAccountId,433 data: BTreeMap<T::CrossAccountId, u128>,434 nesting_budget: &dyn Budget,435 ) -> DispatchResult {436 let total_supply = data437 .iter()438 .map(|(_, v)| *v)439 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {440 acc.checked_add(v)441 })442 .ok_or(ArithmeticError::Overflow)?;443444 for (to, _) in data.iter() {445 <PalletStructure<T>>::check_nesting(446 sender.clone(),447 to,448 collection.id,449 TokenId::default(),450 nesting_budget,451 )?;452 }453454 let updated_balances = data455 .into_iter()456 .map(|(user, amount)| {457 let updated_balance = <Balance<T>>::get((collection.id, &user))458 .checked_add(amount)459 .ok_or(ArithmeticError::Overflow)?;460 Ok((user, amount, updated_balance))461 })462 .collect::<Result<Vec<_>, DispatchError>>()?;463464 465466 <TotalSupply<T>>::insert(collection.id, total_supply);467 for (user, amount, updated_balance) in updated_balances {468 <Balance<T>>::insert((collection.id, &user), updated_balance);469 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(470 &user,471 collection.id,472 TokenId::default(),473 );474 <PalletEvm<T>>::deposit_log(475 ERC20Events::Transfer {476 from: H160::default(),477 to: *user.as_eth(),478 value: amount.into(),479 }480 .to_log(collection_id_to_address(collection.id)),481 );482 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(483 collection.id,484 TokenId::default(),485 user.clone(),486 amount,487 ));488 }489490 Ok(())491 }492493 494 495 pub fn create_multiple_items(496 collection: &FungibleHandle<T>,497 sender: &T::CrossAccountId,498 data: BTreeMap<T::CrossAccountId, u128>,499 nesting_budget: &dyn Budget,500 ) -> DispatchResult {501 502 ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);503504 if !collection.is_owner_or_admin(sender) {505 ensure!(506 collection.permissions.mint_mode(),507 <CommonError<T>>::PublicMintingNotAllowed508 );509 collection.check_allowlist(sender)?;510511 for (owner, _) in data.iter() {512 collection.check_allowlist(owner)?;513 }514 }515516 Self::create_multiple_items_common(collection, sender, data, nesting_budget)517 }518519 520 521 pub fn create_multiple_items_foreign(522 collection: &FungibleHandle<T>,523 sender: &T::CrossAccountId,524 data: BTreeMap<T::CrossAccountId, u128>,525 nesting_budget: &dyn Budget,526 ) -> DispatchResult {527 Self::create_multiple_items_common(collection, sender, data, nesting_budget)528 }529530 fn set_allowance_unchecked(531 collection: &FungibleHandle<T>,532 owner: &T::CrossAccountId,533 spender: &T::CrossAccountId,534 amount: u128,535 ) {536 if amount == 0 {537 <Allowance<T>>::remove((collection.id, owner, spender));538 } else {539 <Allowance<T>>::insert((collection.id, owner, spender), amount);540 }541542 <PalletEvm<T>>::deposit_log(543 ERC20Events::Approval {544 owner: *owner.as_eth(),545 spender: *spender.as_eth(),546 value: amount.into(),547 }548 .to_log(collection_id_to_address(collection.id)),549 );550 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(551 collection.id,552 TokenId(0),553 owner.clone(),554 spender.clone(),555 amount,556 ));557 }558559 560 561 562 563 564 565 pub fn set_allowance(566 collection: &FungibleHandle<T>,567 owner: &T::CrossAccountId,568 spender: &T::CrossAccountId,569 amount: u128,570 ) -> DispatchResult {571 if collection.permissions.access() == AccessMode::AllowList {572 collection.check_allowlist(owner)?;573 collection.check_allowlist(spender)?;574 }575576 if <Balance<T>>::get((collection.id, owner)) < amount {577 ensure!(578 collection.ignores_owned_amount(owner),579 <CommonError<T>>::CantApproveMoreThanOwned580 );581 }582583 584585 Self::set_allowance_unchecked(collection, owner, spender, amount);586 Ok(())587 }588589 590 591 592 593 594 595 596 fn check_allowed(597 collection: &FungibleHandle<T>,598 spender: &T::CrossAccountId,599 from: &T::CrossAccountId,600 amount: u128,601 nesting_budget: &dyn Budget,602 ) -> Result<Option<u128>, DispatchError> {603 if spender.conv_eq(from) {604 return Ok(None);605 }606 if collection.permissions.access() == AccessMode::AllowList {607 608 collection.check_allowlist(spender)?;609 }610 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {611 612 ensure!(613 <PalletStructure<T>>::check_indirectly_owned(614 spender.clone(),615 source.0,616 source.1,617 None,618 nesting_budget619 )?,620 <CommonError<T>>::ApprovedValueTooLow,621 );622 return Ok(None);623 }624 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);625 if allowance.is_none() {626 ensure!(627 collection.ignores_allowance(spender),628 <CommonError<T>>::ApprovedValueTooLow629 );630 }631632 Ok(allowance)633 }634635 636 637 638 639640 pub fn transfer_from(641 collection: &FungibleHandle<T>,642 spender: &T::CrossAccountId,643 from: &T::CrossAccountId,644 to: &T::CrossAccountId,645 amount: u128,646 nesting_budget: &dyn Budget,647 ) -> DispatchResult {648 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;649650 651652 Self::transfer(collection, from, to, amount, nesting_budget)?;653 if let Some(allowance) = allowance {654 Self::set_allowance_unchecked(collection, from, spender, allowance);655 }656 Ok(())657 }658659 660 661 662 663 664 pub fn burn_from(665 collection: &FungibleHandle<T>,666 spender: &T::CrossAccountId,667 from: &T::CrossAccountId,668 amount: u128,669 nesting_budget: &dyn Budget,670 ) -> DispatchResult {671 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;672673 674675 Self::burn(collection, from, amount)?;676 if let Some(allowance) = allowance {677 Self::set_allowance_unchecked(collection, from, spender, allowance);678 }679 Ok(())680 }681682 683 684 685 686 687 688 689 pub fn create_item(690 collection: &FungibleHandle<T>,691 sender: &T::CrossAccountId,692 data: CreateItemData<T>,693 nesting_budget: &dyn Budget,694 ) -> DispatchResult {695 Self::create_multiple_items(696 collection,697 sender,698 [(data.0, data.1)].into_iter().collect(),699 nesting_budget,700 )701 }702703 704 705 706 707 pub fn create_item_foreign(708 collection: &FungibleHandle<T>,709 sender: &T::CrossAccountId,710 data: CreateItemData<T>,711 nesting_budget: &dyn Budget,712 ) -> DispatchResult {713 Self::create_multiple_items_foreign(714 collection,715 sender,716 [(data.0, data.1)].into_iter().collect(),717 nesting_budget,718 )719 }720721 722 723 724 725 726 727 pub fn token_owners(728 collection: CollectionId,729 _token: TokenId,730 ) -> Option<Vec<T::CrossAccountId>> {731 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))732 .map(|(owner, _amount)| owner)733 .take(10)734 .collect();735736 if res.is_empty() {737 None738 } else {739 Some(res)740 }741 }742}