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, TokenId, CreateCollectionData, mapping::TokenAddressMapping,87 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, false)216 }217218 219 pub fn destroy_collection(220 collection: FungibleHandle<T>,221 sender: &T::CrossAccountId,222 ) -> DispatchResult {223 let id = collection.id;224225 if Self::collection_has_tokens(id) {226 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());227 }228229 230231 PalletCommon::destroy_collection(collection.0, sender)?;232233 <TotalSupply<T>>::remove(id);234 <Balance<T>>::remove_prefix((id,), None);235 <Allowance<T>>::remove_prefix((id,), None);236 Ok(())237 }238239 240 fn collection_has_tokens(collection_id: CollectionId) -> bool {241 <TotalSupply<T>>::get(collection_id) != 0242 }243244 245 246 247 pub fn burn(248 collection: &FungibleHandle<T>,249 owner: &T::CrossAccountId,250 amount: u128,251 ) -> DispatchResult {252 let total_supply = <TotalSupply<T>>::get(collection.id)253 .checked_sub(amount)254 .ok_or(<CommonError<T>>::TokenValueTooLow)?;255256 let balance = <Balance<T>>::get((collection.id, owner))257 .checked_sub(amount)258 .ok_or(<CommonError<T>>::TokenValueTooLow)?;259260 if collection.permissions.access() == AccessMode::AllowList {261 collection.check_allowlist(owner)?;262 }263264 265266 if balance == 0 {267 <Balance<T>>::remove((collection.id, owner));268 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());269 } else {270 <Balance<T>>::insert((collection.id, owner), balance);271 }272 <TotalSupply<T>>::insert(collection.id, total_supply);273274 <PalletEvm<T>>::deposit_log(275 ERC20Events::Transfer {276 from: *owner.as_eth(),277 to: H160::default(),278 value: amount.into(),279 }280 .to_log(collection_id_to_address(collection.id)),281 );282 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(283 collection.id,284 TokenId::default(),285 owner.clone(),286 amount,287 ));288 Ok(())289 }290291 292 293 294 295 296 297 298 pub fn transfer(299 collection: &FungibleHandle<T>,300 from: &T::CrossAccountId,301 to: &T::CrossAccountId,302 amount: u128,303 nesting_budget: &dyn Budget,304 ) -> DispatchResult {305 ensure!(306 collection.limits.transfers_enabled(),307 <CommonError<T>>::TransferNotAllowed,308 );309310 if collection.permissions.access() == AccessMode::AllowList {311 collection.check_allowlist(from)?;312 collection.check_allowlist(to)?;313 }314 <PalletCommon<T>>::ensure_correct_receiver(to)?;315316 let balance_from = <Balance<T>>::get((collection.id, from))317 .checked_sub(amount)318 .ok_or(<CommonError<T>>::TokenValueTooLow)?;319 let balance_to = if from != to {320 Some(321 <Balance<T>>::get((collection.id, to))322 .checked_add(amount)323 .ok_or(ArithmeticError::Overflow)?,324 )325 } else {326 None327 };328329 330331 <PalletStructure<T>>::nest_if_sent_to_token(332 from.clone(),333 to,334 collection.id,335 TokenId::default(),336 nesting_budget,337 )?;338339 if let Some(balance_to) = balance_to {340 341 if balance_from == 0 {342 <Balance<T>>::remove((collection.id, from));343 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());344 } else {345 <Balance<T>>::insert((collection.id, from), balance_from);346 }347 <Balance<T>>::insert((collection.id, to), balance_to);348 }349350 <PalletEvm<T>>::deposit_log(351 ERC20Events::Transfer {352 from: *from.as_eth(),353 to: *to.as_eth(),354 value: amount.into(),355 }356 .to_log(collection_id_to_address(collection.id)),357 );358 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(359 collection.id,360 TokenId::default(),361 from.clone(),362 to.clone(),363 amount,364 ));365 Ok(())366 }367368 369 370 pub fn create_multiple_items(371 collection: &FungibleHandle<T>,372 sender: &T::CrossAccountId,373 data: BTreeMap<T::CrossAccountId, u128>,374 nesting_budget: &dyn Budget,375 ) -> DispatchResult {376 if !collection.is_owner_or_admin(sender) {377 ensure!(378 collection.permissions.mint_mode(),379 <CommonError<T>>::PublicMintingNotAllowed380 );381 collection.check_allowlist(sender)?;382383 for (owner, _) in data.iter() {384 collection.check_allowlist(owner)?;385 }386 }387388 let total_supply = data389 .iter()390 .map(|(_, v)| *v)391 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {392 acc.checked_add(v)393 })394 .ok_or(ArithmeticError::Overflow)?;395396 for (to, _) in data.iter() {397 <PalletStructure<T>>::check_nesting(398 sender.clone(),399 to,400 collection.id,401 TokenId::default(),402 nesting_budget,403 )?;404 }405406 407408 <TotalSupply<T>>::insert(collection.id, total_supply);409 for (user, amount) in data {410 let updated_balance = <Balance<T>>::get((collection.id, &user))411 .checked_add(amount)412 .ok_or(ArithmeticError::Overflow)?;413 <Balance<T>>::insert((collection.id, &user), updated_balance);414 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(415 &user,416 collection.id,417 TokenId::default(),418 );419 <PalletEvm<T>>::deposit_log(420 ERC20Events::Transfer {421 from: H160::default(),422 to: *user.as_eth(),423 value: amount.into(),424 }425 .to_log(collection_id_to_address(collection.id)),426 );427 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(428 collection.id,429 TokenId::default(),430 user.clone(),431 amount,432 ));433 }434435 Ok(())436 }437438 fn set_allowance_unchecked(439 collection: &FungibleHandle<T>,440 owner: &T::CrossAccountId,441 spender: &T::CrossAccountId,442 amount: u128,443 ) {444 if amount == 0 {445 <Allowance<T>>::remove((collection.id, owner, spender));446 } else {447 <Allowance<T>>::insert((collection.id, owner, spender), amount);448 }449450 <PalletEvm<T>>::deposit_log(451 ERC20Events::Approval {452 owner: *owner.as_eth(),453 spender: *spender.as_eth(),454 value: amount.into(),455 }456 .to_log(collection_id_to_address(collection.id)),457 );458 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(459 collection.id,460 TokenId(0),461 owner.clone(),462 spender.clone(),463 amount,464 ));465 }466467 468 469 470 471 472 473 pub fn set_allowance(474 collection: &FungibleHandle<T>,475 owner: &T::CrossAccountId,476 spender: &T::CrossAccountId,477 amount: u128,478 ) -> DispatchResult {479 if collection.permissions.access() == AccessMode::AllowList {480 collection.check_allowlist(owner)?;481 collection.check_allowlist(spender)?;482 }483484 if <Balance<T>>::get((collection.id, owner)) < amount {485 ensure!(486 collection.ignores_owned_amount(owner),487 <CommonError<T>>::CantApproveMoreThanOwned488 );489 }490491 492493 Self::set_allowance_unchecked(collection, owner, spender, amount);494 Ok(())495 }496497 498 499 500 501 502 503 504 fn check_allowed(505 collection: &FungibleHandle<T>,506 spender: &T::CrossAccountId,507 from: &T::CrossAccountId,508 amount: u128,509 nesting_budget: &dyn Budget,510 ) -> Result<Option<u128>, DispatchError> {511 if spender.conv_eq(from) {512 return Ok(None);513 }514 if collection.permissions.access() == AccessMode::AllowList {515 516 collection.check_allowlist(spender)?;517 }518 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {519 520 ensure!(521 <PalletStructure<T>>::check_indirectly_owned(522 spender.clone(),523 source.0,524 source.1,525 None,526 nesting_budget527 )?,528 <CommonError<T>>::ApprovedValueTooLow,529 );530 return Ok(None);531 }532 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);533 if allowance.is_none() {534 ensure!(535 collection.ignores_allowance(spender),536 <CommonError<T>>::ApprovedValueTooLow537 );538 }539540 Ok(allowance)541 }542543 544 545 546 547548 pub fn transfer_from(549 collection: &FungibleHandle<T>,550 spender: &T::CrossAccountId,551 from: &T::CrossAccountId,552 to: &T::CrossAccountId,553 amount: u128,554 nesting_budget: &dyn Budget,555 ) -> DispatchResult {556 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;557558 559560 Self::transfer(collection, from, to, amount, nesting_budget)?;561 if let Some(allowance) = allowance {562 Self::set_allowance_unchecked(collection, from, spender, allowance);563 }564 Ok(())565 }566567 568 569 570 571 572 pub fn burn_from(573 collection: &FungibleHandle<T>,574 spender: &T::CrossAccountId,575 from: &T::CrossAccountId,576 amount: u128,577 nesting_budget: &dyn Budget,578 ) -> DispatchResult {579 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;580581 582583 Self::burn(collection, from, amount)?;584 if let Some(allowance) = allowance {585 Self::set_allowance_unchecked(collection, from, spender, allowance);586 }587 Ok(())588 }589590 591 592 593 594 595 596 597 pub fn create_item(598 collection: &FungibleHandle<T>,599 sender: &T::CrossAccountId,600 data: CreateItemData<T>,601 nesting_budget: &dyn Budget,602 ) -> DispatchResult {603 Self::create_multiple_items(604 collection,605 sender,606 [(data.0, data.1)].into_iter().collect(),607 nesting_budget,608 )609 }610611 612 613 614 615 616 617 pub fn token_owners(618 collection: CollectionId,619 _token: TokenId,620 ) -> Option<Vec<T::CrossAccountId>> {621 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))622 .map(|(owner, _amount)| owner)623 .take(10)624 .collect();625626 if res.is_empty() {627 None628 } else {629 Some(res)630 }631 }632}