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};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 124 FungibleItemsHaveNoId,125 126 FungibleItemsDontHaveData,127 128 FungibleDisallowsNesting,129 130 SettingPropertiesNotAllowed,131 }132133 #[pallet::config]134 pub trait Config:135 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config136 {137 type WeightInfo: WeightInfo;138 }139140 #[pallet::pallet]141 #[pallet::generate_store(pub(super) trait Store)]142 pub struct Pallet<T>(_);143144 145 #[pallet::storage]146 pub type TotalSupply<T: Config> =147 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;148149 150 #[pallet::storage]151 pub type Balance<T: Config> = StorageNMap<152 Key = (153 Key<Twox64Concat, CollectionId>,154 Key<Blake2_128Concat, T::CrossAccountId>,155 ),156 Value = u128,157 QueryKind = ValueQuery,158 >;159160 161 #[pallet::storage]162 pub type Allowance<T: Config> = StorageNMap<163 Key = (164 Key<Twox64Concat, CollectionId>,165 Key<Blake2_128, T::CrossAccountId>,166 Key<Blake2_128Concat, T::CrossAccountId>,167 ),168 Value = u128,169 QueryKind = ValueQuery,170 >;171}172173174175176pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);177178179impl<T: Config> FungibleHandle<T> {180 181 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {182 Self(inner)183 }184185 186 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {187 self.0188 }189 190 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {191 &mut self.0192 }193}194impl<T: Config> WithRecorder<T> for FungibleHandle<T> {195 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {196 self.0.recorder()197 }198 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {199 self.0.into_recorder()200 }201}202impl<T: Config> Deref for FungibleHandle<T> {203 type Target = pallet_common::CollectionHandle<T>;204205 fn deref(&self) -> &Self::Target {206 &self.0207 }208}209210211impl<T: Config> Pallet<T> {212 213 pub fn init_collection(214 owner: T::CrossAccountId,215 data: CreateCollectionData<T::AccountId>,216 ) -> Result<CollectionId, DispatchError> {217 <PalletCommon<T>>::init_collection(owner, data, false)218 }219220 221 pub fn destroy_collection(222 collection: FungibleHandle<T>,223 sender: &T::CrossAccountId,224 ) -> DispatchResult {225 let id = collection.id;226227 if Self::collection_has_tokens(id) {228 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());229 }230231 232233 PalletCommon::destroy_collection(collection.0, sender)?;234235 <TotalSupply<T>>::remove(id);236 <Balance<T>>::remove_prefix((id,), None);237 <Allowance<T>>::remove_prefix((id,), None);238 Ok(())239 }240241 242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TotalSupply<T>>::get(collection_id) != 0244 }245246 247 248 249 pub fn burn(250 collection: &FungibleHandle<T>,251 owner: &T::CrossAccountId,252 amount: u128,253 ) -> DispatchResult {254 let total_supply = <TotalSupply<T>>::get(collection.id)255 .checked_sub(amount)256 .ok_or(<CommonError<T>>::TokenValueTooLow)?;257258 let balance = <Balance<T>>::get((collection.id, owner))259 .checked_sub(amount)260 .ok_or(<CommonError<T>>::TokenValueTooLow)?;261262 if collection.permissions.access() == AccessMode::AllowList {263 collection.check_allowlist(owner)?;264 }265266 267268 if balance == 0 {269 <Balance<T>>::remove((collection.id, owner));270 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());271 } else {272 <Balance<T>>::insert((collection.id, owner), balance);273 }274 <TotalSupply<T>>::insert(collection.id, total_supply);275276 <PalletEvm<T>>::deposit_log(277 ERC20Events::Transfer {278 from: *owner.as_eth(),279 to: H160::default(),280 value: amount.into(),281 }282 .to_log(collection_id_to_address(collection.id)),283 );284 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(285 collection.id,286 TokenId::default(),287 owner.clone(),288 amount,289 ));290 Ok(())291 }292293 294 295 296 297 298 299 300 pub fn transfer(301 collection: &FungibleHandle<T>,302 from: &T::CrossAccountId,303 to: &T::CrossAccountId,304 amount: u128,305 nesting_budget: &dyn Budget,306 ) -> DispatchResult {307 ensure!(308 collection.limits.transfers_enabled(),309 <CommonError<T>>::TransferNotAllowed,310 );311312 if collection.permissions.access() == AccessMode::AllowList {313 collection.check_allowlist(from)?;314 collection.check_allowlist(to)?;315 }316 <PalletCommon<T>>::ensure_correct_receiver(to)?;317318 let balance_from = <Balance<T>>::get((collection.id, from))319 .checked_sub(amount)320 .ok_or(<CommonError<T>>::TokenValueTooLow)?;321 let balance_to = if from != to {322 Some(323 <Balance<T>>::get((collection.id, to))324 .checked_add(amount)325 .ok_or(ArithmeticError::Overflow)?,326 )327 } else {328 None329 };330331 332333 <PalletStructure<T>>::nest_if_sent_to_token(334 from.clone(),335 to,336 collection.id,337 TokenId::default(),338 nesting_budget,339 )?;340341 if let Some(balance_to) = balance_to {342 343 if balance_from == 0 {344 <Balance<T>>::remove((collection.id, from));345 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());346 } else {347 <Balance<T>>::insert((collection.id, from), balance_from);348 }349 <Balance<T>>::insert((collection.id, to), balance_to);350 }351352 <PalletEvm<T>>::deposit_log(353 ERC20Events::Transfer {354 from: *from.as_eth(),355 to: *to.as_eth(),356 value: amount.into(),357 }358 .to_log(collection_id_to_address(collection.id)),359 );360 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(361 collection.id,362 TokenId::default(),363 from.clone(),364 to.clone(),365 amount,366 ));367 Ok(())368 }369370 371 372 pub fn create_multiple_items(373 collection: &FungibleHandle<T>,374 sender: &T::CrossAccountId,375 data: BTreeMap<T::CrossAccountId, u128>,376 nesting_budget: &dyn Budget,377 ) -> DispatchResult {378 if !collection.is_owner_or_admin(sender) {379 ensure!(380 collection.permissions.mint_mode(),381 <CommonError<T>>::PublicMintingNotAllowed382 );383 collection.check_allowlist(sender)?;384385 for (owner, _) in data.iter() {386 collection.check_allowlist(owner)?;387 }388 }389390 let total_supply = data391 .iter()392 .map(|(_, v)| *v)393 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {394 acc.checked_add(v)395 })396 .ok_or(ArithmeticError::Overflow)?;397398 let mut balances = data;399 for (k, v) in balances.iter_mut() {400 *v = <Balance<T>>::get((collection.id, &k))401 .checked_add(*v)402 .ok_or(ArithmeticError::Overflow)?;403 }404405 for (to, _) in balances.iter() {406 <PalletStructure<T>>::check_nesting(407 sender.clone(),408 to,409 collection.id,410 TokenId::default(),411 nesting_budget,412 )?;413 }414415 416417 <TotalSupply<T>>::insert(collection.id, total_supply);418 for (user, amount) in balances {419 <Balance<T>>::insert((collection.id, &user), amount);420 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(421 &user,422 collection.id,423 TokenId::default(),424 );425 <PalletEvm<T>>::deposit_log(426 ERC20Events::Transfer {427 from: H160::default(),428 to: *user.as_eth(),429 value: amount.into(),430 }431 .to_log(collection_id_to_address(collection.id)),432 );433 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(434 collection.id,435 TokenId::default(),436 user.clone(),437 amount,438 ));439 }440441 Ok(())442 }443444 fn set_allowance_unchecked(445 collection: &FungibleHandle<T>,446 owner: &T::CrossAccountId,447 spender: &T::CrossAccountId,448 amount: u128,449 ) {450 if amount == 0 {451 <Allowance<T>>::remove((collection.id, owner, spender));452 } else {453 <Allowance<T>>::insert((collection.id, owner, spender), amount);454 }455456 <PalletEvm<T>>::deposit_log(457 ERC20Events::Approval {458 owner: *owner.as_eth(),459 spender: *spender.as_eth(),460 value: amount.into(),461 }462 .to_log(collection_id_to_address(collection.id)),463 );464 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(465 collection.id,466 TokenId(0),467 owner.clone(),468 spender.clone(),469 amount,470 ));471 }472473 474 475 476 477 478 479 pub fn set_allowance(480 collection: &FungibleHandle<T>,481 owner: &T::CrossAccountId,482 spender: &T::CrossAccountId,483 amount: u128,484 ) -> DispatchResult {485 if collection.permissions.access() == AccessMode::AllowList {486 collection.check_allowlist(owner)?;487 collection.check_allowlist(spender)?;488 }489490 if <Balance<T>>::get((collection.id, owner)) < amount {491 ensure!(492 collection.ignores_owned_amount(owner),493 <CommonError<T>>::CantApproveMoreThanOwned494 );495 }496497 498499 Self::set_allowance_unchecked(collection, owner, spender, amount);500 Ok(())501 }502503 504 505 506 507 508 509 510 fn check_allowed(511 collection: &FungibleHandle<T>,512 spender: &T::CrossAccountId,513 from: &T::CrossAccountId,514 amount: u128,515 nesting_budget: &dyn Budget,516 ) -> Result<Option<u128>, DispatchError> {517 if spender.conv_eq(from) {518 return Ok(None);519 }520 if collection.permissions.access() == AccessMode::AllowList {521 522 collection.check_allowlist(spender)?;523 }524 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {525 526 ensure!(527 <PalletStructure<T>>::check_indirectly_owned(528 spender.clone(),529 source.0,530 source.1,531 None,532 nesting_budget533 )?,534 <CommonError<T>>::ApprovedValueTooLow,535 );536 return Ok(None);537 }538 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);539 if allowance.is_none() {540 ensure!(541 collection.ignores_allowance(spender),542 <CommonError<T>>::ApprovedValueTooLow543 );544 }545546 Ok(allowance)547 }548549 550 551 552 553554 pub fn transfer_from(555 collection: &FungibleHandle<T>,556 spender: &T::CrossAccountId,557 from: &T::CrossAccountId,558 to: &T::CrossAccountId,559 amount: u128,560 nesting_budget: &dyn Budget,561 ) -> DispatchResult {562 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;563564 565566 Self::transfer(collection, from, to, amount, nesting_budget)?;567 if let Some(allowance) = allowance {568 Self::set_allowance_unchecked(collection, from, spender, allowance);569 }570 Ok(())571 }572573 574 575 576 577 578 pub fn burn_from(579 collection: &FungibleHandle<T>,580 spender: &T::CrossAccountId,581 from: &T::CrossAccountId,582 amount: u128,583 nesting_budget: &dyn Budget,584 ) -> DispatchResult {585 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;586587 588589 Self::burn(collection, from, amount)?;590 if let Some(allowance) = allowance {591 Self::set_allowance_unchecked(collection, from, spender, allowance);592 }593 Ok(())594 }595596 597 598 599 600 601 602 603 pub fn create_item(604 collection: &FungibleHandle<T>,605 sender: &T::CrossAccountId,606 data: CreateItemData<T>,607 nesting_budget: &dyn Budget,608 ) -> DispatchResult {609 Self::create_multiple_items(610 collection,611 sender,612 [(data.0, data.1)].into_iter().collect(),613 nesting_budget,614 )615 }616}