1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::Deref;20use frame_support::{ensure};21use pallet_evm::account::CrossAccountId;22use up_data_structs::{23 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,24};25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 CollectionHandle, dispatch::CollectionDispatch,28};29use pallet_structure::Pallet as PalletStructure;30use pallet_evm_coder_substrate::WithRecorder;31use sp_core::H160;32use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};33use sp_std::collections::btree_map::BTreeMap;3435pub use pallet::*;3637use crate::erc::ERC20Events;38#[cfg(feature = "runtime-benchmarks")]39pub mod benchmarking;40pub mod common;41pub mod erc;42pub mod weights;4344pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);45pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4647#[frame_support::pallet]48pub mod pallet {49 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};50 use up_data_structs::CollectionId;51 use super::weights::WeightInfo;5253 #[pallet::error]54 pub enum Error<T> {55 56 NotFungibleDataUsedToMintFungibleCollectionToken,57 58 FungibleItemsHaveNoId,59 60 FungibleItemsDontHaveData,61 62 FungibleDisallowsNesting,63 }6465 #[pallet::config]66 pub trait Config:67 frame_system::Config + pallet_common::Config + pallet_structure::Config68 {69 type WeightInfo: WeightInfo;70 }7172 #[pallet::pallet]73 #[pallet::generate_store(pub(super) trait Store)]74 pub struct Pallet<T>(_);7576 #[pallet::storage]77 pub type TotalSupply<T: Config> =78 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;7980 #[pallet::storage]81 pub type Balance<T: Config> = StorageNMap<82 Key = (83 Key<Twox64Concat, CollectionId>,84 Key<Blake2_128Concat, T::CrossAccountId>,85 ),86 Value = u128,87 QueryKind = ValueQuery,88 >;8990 #[pallet::storage]91 pub type Allowance<T: Config> = StorageNMap<92 Key = (93 Key<Twox64Concat, CollectionId>,94 Key<Blake2_128, T::CrossAccountId>,95 Key<Blake2_128Concat, T::CrossAccountId>,96 ),97 Value = u128,98 QueryKind = ValueQuery,99 >;100}101102pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);103impl<T: Config> FungibleHandle<T> {104 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {105 Self(inner)106 }107 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {108 self.0109 }110}111impl<T: Config> WithRecorder<T> for FungibleHandle<T> {112 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {113 self.0.recorder()114 }115 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {116 self.0.into_recorder()117 }118}119impl<T: Config> Deref for FungibleHandle<T> {120 type Target = pallet_common::CollectionHandle<T>;121122 fn deref(&self) -> &Self::Target {123 &self.0124 }125}126127impl<T: Config> Pallet<T> {128 pub fn init_collection(129 owner: T::AccountId,130 data: CreateCollectionData<T::AccountId>,131 ) -> Result<CollectionId, DispatchError> {132 <PalletCommon<T>>::init_collection(owner, data)133 }134 pub fn destroy_collection(135 collection: FungibleHandle<T>,136 sender: &T::CrossAccountId,137 ) -> DispatchResult {138 let id = collection.id;139140 141142 PalletCommon::destroy_collection(collection.0, sender)?;143144 <TotalSupply<T>>::remove(id);145 <Balance<T>>::remove_prefix((id,), None);146 <Allowance<T>>::remove_prefix((id,), None);147 Ok(())148 }149150 pub fn burn(151 collection: &FungibleHandle<T>,152 owner: &T::CrossAccountId,153 amount: u128,154 ) -> DispatchResult {155 let total_supply = <TotalSupply<T>>::get(collection.id)156 .checked_sub(amount)157 .ok_or(<CommonError<T>>::TokenValueTooLow)?;158159 let balance = <Balance<T>>::get((collection.id, owner))160 .checked_sub(amount)161 .ok_or(<CommonError<T>>::TokenValueTooLow)?;162163 if collection.access == AccessMode::AllowList {164 collection.check_allowlist(owner)?;165 }166167 168169 if balance == 0 {170 <Balance<T>>::remove((collection.id, owner));171 } else {172 <Balance<T>>::insert((collection.id, owner), balance);173 }174 <TotalSupply<T>>::insert(collection.id, total_supply);175176 collection.log_mirrored(ERC20Events::Transfer {177 from: *owner.as_eth(),178 to: H160::default(),179 value: amount.into(),180 });181 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(182 collection.id,183 TokenId::default(),184 owner.clone(),185 amount,186 ));187 Ok(())188 }189190 pub fn transfer(191 collection: &FungibleHandle<T>,192 from: &T::CrossAccountId,193 to: &T::CrossAccountId,194 amount: u128,195 ) -> DispatchResult {196 ensure!(197 collection.limits.transfers_enabled(),198 <CommonError<T>>::TransferNotAllowed,199 );200201 if collection.access == AccessMode::AllowList {202 collection.check_allowlist(from)?;203 collection.check_allowlist(to)?;204 }205 <PalletCommon<T>>::ensure_correct_receiver(to)?;206207 let balance_from = <Balance<T>>::get((collection.id, from))208 .checked_sub(amount)209 .ok_or(<CommonError<T>>::TokenValueTooLow)?;210 let balance_to = if from != to {211 Some(212 <Balance<T>>::get((collection.id, to))213 .checked_add(amount)214 .ok_or(ArithmeticError::Overflow)?,215 )216 } else {217 None218 };219220 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {221 let handle = <CollectionHandle<T>>::try_get(target.0)?;222 let dispatch = T::CollectionDispatch::dispatch(handle);223 let dispatch = dispatch.as_dyn();224225 226227 dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;228 }229230 if let Some(balance_to) = balance_to {231 232 if balance_from == 0 {233 <Balance<T>>::remove((collection.id, from));234 } else {235 <Balance<T>>::insert((collection.id, from), balance_from);236 }237 <Balance<T>>::insert((collection.id, to), balance_to);238 }239240 collection.log_mirrored(ERC20Events::Transfer {241 from: *from.as_eth(),242 to: *to.as_eth(),243 value: amount.into(),244 });245 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(246 collection.id,247 TokenId::default(),248 from.clone(),249 to.clone(),250 amount,251 ));252 Ok(())253 }254255 pub fn create_multiple_items(256 collection: &FungibleHandle<T>,257 sender: &T::CrossAccountId,258 data: BTreeMap<T::CrossAccountId, u128>,259 ) -> DispatchResult {260 if !collection.is_owner_or_admin(sender) {261 ensure!(262 collection.mint_mode,263 <CommonError<T>>::PublicMintingNotAllowed264 );265 collection.check_allowlist(sender)?;266267 for (owner, _) in data.iter() {268 collection.check_allowlist(owner)?;269 }270 }271272 let total_supply = data273 .iter()274 .map(|(_, v)| *v)275 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {276 acc.checked_add(v)277 })278 .ok_or(ArithmeticError::Overflow)?;279280 let mut balances = data;281 for (k, v) in balances.iter_mut() {282 *v = <Balance<T>>::get((collection.id, &k))283 .checked_add(*v)284 .ok_or(ArithmeticError::Overflow)?;285 }286287 288289 <TotalSupply<T>>::insert(collection.id, total_supply);290 for (user, amount) in balances {291 <Balance<T>>::insert((collection.id, &user), amount);292293 collection.log_mirrored(ERC20Events::Transfer {294 from: H160::default(),295 to: *user.as_eth(),296 value: amount.into(),297 });298 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(299 collection.id,300 TokenId::default(),301 user.clone(),302 amount,303 ));304 }305306 Ok(())307 }308309 fn set_allowance_unchecked(310 collection: &FungibleHandle<T>,311 owner: &T::CrossAccountId,312 spender: &T::CrossAccountId,313 amount: u128,314 ) {315 if amount == 0 {316 <Allowance<T>>::remove((collection.id, owner, spender));317 } else {318 <Allowance<T>>::insert((collection.id, owner, spender), amount);319 }320321 collection.log_mirrored(ERC20Events::Approval {322 owner: *owner.as_eth(),323 spender: *spender.as_eth(),324 value: amount.into(),325 });326 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(327 collection.id,328 TokenId(0),329 owner.clone(),330 spender.clone(),331 amount,332 ));333 }334335 pub fn set_allowance(336 collection: &FungibleHandle<T>,337 owner: &T::CrossAccountId,338 spender: &T::CrossAccountId,339 amount: u128,340 ) -> DispatchResult {341 if collection.access == AccessMode::AllowList {342 collection.check_allowlist(owner)?;343 collection.check_allowlist(spender)?;344 }345346 if <Balance<T>>::get((collection.id, owner)) < amount {347 ensure!(348 collection.ignores_owned_amount(owner),349 <CommonError<T>>::CantApproveMoreThanOwned350 );351 }352353 354355 Self::set_allowance_unchecked(collection, owner, spender, amount);356 Ok(())357 }358359 fn check_allowed(360 collection: &FungibleHandle<T>,361 spender: &T::CrossAccountId,362 from: &T::CrossAccountId,363 amount: u128,364 ) -> Result<Option<u128>, DispatchError> {365 if spender.conv_eq(from) {366 return Ok(None);367 }368 if collection.access == AccessMode::AllowList {369 370 collection.check_allowlist(spender)?;371 }372 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {373 374 ensure!(375 <PalletStructure<T>>::indirectly_owned(spender.clone(), source.0, source.1, 1)?,376 <CommonError<T>>::ApprovedValueTooLow,377 );378 return Ok(None);379 }380 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);381 if allowance.is_none() {382 ensure!(383 collection.ignores_allowance(spender),384 <CommonError<T>>::ApprovedValueTooLow385 );386 }387388 Ok(allowance)389 }390391 pub fn transfer_from(392 collection: &FungibleHandle<T>,393 spender: &T::CrossAccountId,394 from: &T::CrossAccountId,395 to: &T::CrossAccountId,396 amount: u128,397 ) -> DispatchResult {398 let allowance = Self::check_allowed(collection, spender, from, amount)?;399400 401402 Self::transfer(collection, from, to, amount)?;403 if let Some(allowance) = allowance {404 Self::set_allowance_unchecked(collection, from, spender, allowance);405 }406 Ok(())407 }408409 pub fn burn_from(410 collection: &FungibleHandle<T>,411 spender: &T::CrossAccountId,412 from: &T::CrossAccountId,413 amount: u128,414 ) -> DispatchResult {415 let allowance = Self::check_allowed(collection, spender, from, amount)?;416417 418419 Self::burn(collection, from, amount)?;420 if let Some(allowance) = allowance {421 Self::set_allowance_unchecked(collection, from, spender, allowance);422 }423 Ok(())424 }425426 427 pub fn create_item(428 collection: &FungibleHandle<T>,429 sender: &T::CrossAccountId,430 data: CreateItemData<T>,431 ) -> DispatchResult {432 Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())433 }434}