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 budget::Budget,25};26use pallet_common::{27 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,28 dispatch::CollectionDispatch,29};30use pallet_structure::Pallet as PalletStructure;31use pallet_evm_coder_substrate::WithRecorder;32use sp_core::H160;33use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};34use sp_std::collections::btree_map::BTreeMap;3536pub use pallet::*;3738use crate::erc::ERC20Events;39#[cfg(feature = "runtime-benchmarks")]40pub mod benchmarking;41pub mod common;42pub mod erc;43pub mod weights;4445pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);46pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4748#[frame_support::pallet]49pub mod pallet {50 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};51 use up_data_structs::CollectionId;52 use super::weights::WeightInfo;5354 #[pallet::error]55 pub enum Error<T> {56 57 NotFungibleDataUsedToMintFungibleCollectionToken,58 59 FungibleItemsHaveNoId,60 61 FungibleItemsDontHaveData,62 63 FungibleDisallowsNesting,64 65 SettingPropertiesNotAllowed,66 }6768 #[pallet::config]69 pub trait Config:70 frame_system::Config + pallet_common::Config + pallet_structure::Config71 {72 type WeightInfo: WeightInfo;73 }7475 #[pallet::pallet]76 #[pallet::generate_store(pub(super) trait Store)]77 pub struct Pallet<T>(_);7879 #[pallet::storage]80 pub type TotalSupply<T: Config> =81 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;8283 #[pallet::storage]84 pub type Balance<T: Config> = StorageNMap<85 Key = (86 Key<Twox64Concat, CollectionId>,87 Key<Blake2_128Concat, T::CrossAccountId>,88 ),89 Value = u128,90 QueryKind = ValueQuery,91 >;9293 #[pallet::storage]94 pub type Allowance<T: Config> = StorageNMap<95 Key = (96 Key<Twox64Concat, CollectionId>,97 Key<Blake2_128, T::CrossAccountId>,98 Key<Blake2_128Concat, T::CrossAccountId>,99 ),100 Value = u128,101 QueryKind = ValueQuery,102 >;103}104105pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);106impl<T: Config> FungibleHandle<T> {107 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {108 Self(inner)109 }110 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {111 self.0112 }113 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {114 &mut self.0115 }116}117impl<T: Config> WithRecorder<T> for FungibleHandle<T> {118 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {119 self.0.recorder()120 }121 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {122 self.0.into_recorder()123 }124}125impl<T: Config> Deref for FungibleHandle<T> {126 type Target = pallet_common::CollectionHandle<T>;127128 fn deref(&self) -> &Self::Target {129 &self.0130 }131}132133impl<T: Config> Pallet<T> {134 pub fn init_collection(135 owner: T::AccountId,136 data: CreateCollectionData<T::AccountId>,137 ) -> Result<CollectionId, DispatchError> {138 <PalletCommon<T>>::init_collection(owner, data)139 }140 pub fn destroy_collection(141 collection: FungibleHandle<T>,142 sender: &T::CrossAccountId,143 ) -> DispatchResult {144 let id = collection.id;145146 147148 PalletCommon::destroy_collection(collection.0, sender)?;149150 <TotalSupply<T>>::remove(id);151 <Balance<T>>::remove_prefix((id,), None);152 <Allowance<T>>::remove_prefix((id,), None);153 Ok(())154 }155156 pub fn burn(157 collection: &FungibleHandle<T>,158 owner: &T::CrossAccountId,159 amount: u128,160 ) -> DispatchResult {161 let total_supply = <TotalSupply<T>>::get(collection.id)162 .checked_sub(amount)163 .ok_or(<CommonError<T>>::TokenValueTooLow)?;164165 let balance = <Balance<T>>::get((collection.id, owner))166 .checked_sub(amount)167 .ok_or(<CommonError<T>>::TokenValueTooLow)?;168169 if collection.access == AccessMode::AllowList {170 collection.check_allowlist(owner)?;171 }172173 174175 if balance == 0 {176 <Balance<T>>::remove((collection.id, owner));177 } else {178 <Balance<T>>::insert((collection.id, owner), balance);179 }180 <TotalSupply<T>>::insert(collection.id, total_supply);181182 collection.log_mirrored(ERC20Events::Transfer {183 from: *owner.as_eth(),184 to: H160::default(),185 value: amount.into(),186 });187 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(188 collection.id,189 TokenId::default(),190 owner.clone(),191 amount,192 ));193 Ok(())194 }195196 pub fn transfer(197 collection: &FungibleHandle<T>,198 from: &T::CrossAccountId,199 to: &T::CrossAccountId,200 amount: u128,201 nesting_budget: &dyn Budget,202 ) -> DispatchResult {203 ensure!(204 collection.limits.transfers_enabled(),205 <CommonError<T>>::TransferNotAllowed,206 );207208 if collection.access == AccessMode::AllowList {209 collection.check_allowlist(from)?;210 collection.check_allowlist(to)?;211 }212 <PalletCommon<T>>::ensure_correct_receiver(to)?;213214 let balance_from = <Balance<T>>::get((collection.id, from))215 .checked_sub(amount)216 .ok_or(<CommonError<T>>::TokenValueTooLow)?;217 let balance_to = if from != to {218 Some(219 <Balance<T>>::get((collection.id, to))220 .checked_add(amount)221 .ok_or(ArithmeticError::Overflow)?,222 )223 } else {224 None225 };226227 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {228 let handle = <CollectionHandle<T>>::try_get(target.0)?;229 let dispatch = T::CollectionDispatch::dispatch(handle);230 let dispatch = dispatch.as_dyn();231232 dispatch.check_nesting(233 from.clone(),234 (collection.id, TokenId::default()),235 target.1,236 nesting_budget,237 )?;238 }239240 241242 if let Some(balance_to) = balance_to {243 244 if balance_from == 0 {245 <Balance<T>>::remove((collection.id, from));246 } else {247 <Balance<T>>::insert((collection.id, from), balance_from);248 }249 <Balance<T>>::insert((collection.id, to), balance_to);250 }251252 collection.log_mirrored(ERC20Events::Transfer {253 from: *from.as_eth(),254 to: *to.as_eth(),255 value: amount.into(),256 });257 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(258 collection.id,259 TokenId::default(),260 from.clone(),261 to.clone(),262 amount,263 ));264 Ok(())265 }266267 pub fn create_multiple_items(268 collection: &FungibleHandle<T>,269 sender: &T::CrossAccountId,270 data: BTreeMap<T::CrossAccountId, u128>,271 nesting_budget: &dyn Budget,272 ) -> DispatchResult {273 if !collection.is_owner_or_admin(sender) {274 ensure!(275 collection.mint_mode,276 <CommonError<T>>::PublicMintingNotAllowed277 );278 collection.check_allowlist(sender)?;279280 for (owner, _) in data.iter() {281 collection.check_allowlist(owner)?;282 }283 }284285 let total_supply = data286 .iter()287 .map(|(_, v)| *v)288 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {289 acc.checked_add(v)290 })291 .ok_or(ArithmeticError::Overflow)?;292293 let mut balances = data;294 for (k, v) in balances.iter_mut() {295 *v = <Balance<T>>::get((collection.id, &k))296 .checked_add(*v)297 .ok_or(ArithmeticError::Overflow)?;298 }299300 for (to, _) in balances.iter() {301 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {302 let handle = <CollectionHandle<T>>::try_get(target.0)?;303 let dispatch = T::CollectionDispatch::dispatch(handle);304 let dispatch = dispatch.as_dyn();305306 dispatch.check_nesting(307 sender.clone(),308 (collection.id, TokenId::default()),309 target.1,310 nesting_budget,311 )?;312 }313 }314315 316317 <TotalSupply<T>>::insert(collection.id, total_supply);318 for (user, amount) in balances {319 <Balance<T>>::insert((collection.id, &user), amount);320321 collection.log_mirrored(ERC20Events::Transfer {322 from: H160::default(),323 to: *user.as_eth(),324 value: amount.into(),325 });326 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(327 collection.id,328 TokenId::default(),329 user.clone(),330 amount,331 ));332 }333334 Ok(())335 }336337 fn set_allowance_unchecked(338 collection: &FungibleHandle<T>,339 owner: &T::CrossAccountId,340 spender: &T::CrossAccountId,341 amount: u128,342 ) {343 if amount == 0 {344 <Allowance<T>>::remove((collection.id, owner, spender));345 } else {346 <Allowance<T>>::insert((collection.id, owner, spender), amount);347 }348349 collection.log_mirrored(ERC20Events::Approval {350 owner: *owner.as_eth(),351 spender: *spender.as_eth(),352 value: amount.into(),353 });354 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(355 collection.id,356 TokenId(0),357 owner.clone(),358 spender.clone(),359 amount,360 ));361 }362363 pub fn set_allowance(364 collection: &FungibleHandle<T>,365 owner: &T::CrossAccountId,366 spender: &T::CrossAccountId,367 amount: u128,368 ) -> DispatchResult {369 if collection.access == AccessMode::AllowList {370 collection.check_allowlist(owner)?;371 collection.check_allowlist(spender)?;372 }373374 if <Balance<T>>::get((collection.id, owner)) < amount {375 ensure!(376 collection.ignores_owned_amount(owner),377 <CommonError<T>>::CantApproveMoreThanOwned378 );379 }380381 382383 Self::set_allowance_unchecked(collection, owner, spender, amount);384 Ok(())385 }386387 fn check_allowed(388 collection: &FungibleHandle<T>,389 spender: &T::CrossAccountId,390 from: &T::CrossAccountId,391 amount: u128,392 nesting_budget: &dyn Budget,393 ) -> Result<Option<u128>, DispatchError> {394 if spender.conv_eq(from) {395 return Ok(None);396 }397 if collection.access == AccessMode::AllowList {398 399 collection.check_allowlist(spender)?;400 }401 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {402 403 ensure!(404 <PalletStructure<T>>::check_indirectly_owned(405 spender.clone(),406 source.0,407 source.1,408 None,409 nesting_budget410 )?,411 <CommonError<T>>::ApprovedValueTooLow,412 );413 return Ok(None);414 }415 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);416 if allowance.is_none() {417 ensure!(418 collection.ignores_allowance(spender),419 <CommonError<T>>::ApprovedValueTooLow420 );421 }422423 Ok(allowance)424 }425426 pub fn transfer_from(427 collection: &FungibleHandle<T>,428 spender: &T::CrossAccountId,429 from: &T::CrossAccountId,430 to: &T::CrossAccountId,431 amount: u128,432 nesting_budget: &dyn Budget,433 ) -> DispatchResult {434 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;435436 437438 Self::transfer(collection, from, to, amount, nesting_budget)?;439 if let Some(allowance) = allowance {440 Self::set_allowance_unchecked(collection, from, spender, allowance);441 }442 Ok(())443 }444445 pub fn burn_from(446 collection: &FungibleHandle<T>,447 spender: &T::CrossAccountId,448 from: &T::CrossAccountId,449 amount: u128,450 nesting_budget: &dyn Budget,451 ) -> DispatchResult {452 let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?;453454 455456 Self::burn(collection, from, amount)?;457 if let Some(allowance) = allowance {458 Self::set_allowance_unchecked(collection, from, spender, allowance);459 }460 Ok(())461 }462463 464 pub fn create_item(465 collection: &FungibleHandle<T>,466 sender: &T::CrossAccountId,467 data: CreateItemData<T>,468 nesting_budget: &dyn Budget,469 ) -> DispatchResult {470 Self::create_multiple_items(471 collection,472 sender,473 [(data.0, data.1)].into_iter().collect(),474 nesting_budget,475 )476 }477}