1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use frame_support::{BoundedVec, ensure};21use up_data_structs::{22 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,23};24use pallet_common::{Error as CommonError, Pallet as PalletCommon, Event as CommonEvent};25use pallet_evm::account::CrossAccountId;26use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};27use sp_core::H160;28use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};29use sp_std::{vec::Vec, vec};30use core::ops::Deref;31use sp_std::collections::btree_map::BTreeMap;32use codec::{Encode, Decode, MaxEncodedLen};33use scale_info::TypeInfo;3435pub use pallet::*;36#[cfg(feature = "runtime-benchmarks")]37pub mod benchmarking;38pub mod common;39pub mod erc;40pub mod weights;4142pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;43pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4445#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]46pub struct ItemData<CrossAccountId> {47 pub const_data: BoundedVec<u8, CustomDataLimit>,48 pub variable_data: BoundedVec<u8, CustomDataLimit>,49 pub owner: CrossAccountId,50}5152#[frame_support::pallet]53pub mod pallet {54 use super::*;55 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};56 use up_data_structs::{CollectionId, TokenId};57 use super::weights::WeightInfo;5859 #[pallet::error]60 pub enum Error<T> {61 62 NotNonfungibleDataUsedToMintFungibleCollectionToken,63 64 NonfungibleItemsHaveNoAmount,65 }6667 #[pallet::config]68 pub trait Config: frame_system::Config + pallet_common::Config {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 TokensMinted<T: Config> =78 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;79 #[pallet::storage]80 pub type TokensBurnt<T: Config> =81 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;8283 #[pallet::storage]84 pub type TokenData<T: Config> = StorageNMap<85 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),86 Value = ItemData<T::CrossAccountId>,87 QueryKind = OptionQuery,88 >;8990 91 #[pallet::storage]92 pub type Owned<T: Config> = StorageNMap<93 Key = (94 Key<Twox64Concat, CollectionId>,95 Key<Blake2_128Concat, T::CrossAccountId>,96 Key<Twox64Concat, TokenId>,97 ),98 Value = bool,99 QueryKind = ValueQuery,100 >;101102 #[pallet::storage]103 pub type AccountBalance<T: Config> = StorageNMap<104 Key = (105 Key<Twox64Concat, CollectionId>,106 Key<Blake2_128Concat, T::CrossAccountId>,107 ),108 Value = u32,109 QueryKind = ValueQuery,110 >;111112 #[pallet::storage]113 pub type Allowance<T: Config> = StorageNMap<114 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),115 Value = T::CrossAccountId,116 QueryKind = OptionQuery,117 >;118}119120pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);121impl<T: Config> NonfungibleHandle<T> {122 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {123 Self(inner)124 }125 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {126 self.0127 }128}129impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {130 fn recorder(&self) -> &SubstrateRecorder<T> {131 self.0.recorder()132 }133 fn into_recorder(self) -> SubstrateRecorder<T> {134 self.0.into_recorder()135 }136}137impl<T: Config> Deref for NonfungibleHandle<T> {138 type Target = pallet_common::CollectionHandle<T>;139140 fn deref(&self) -> &Self::Target {141 &self.0142 }143}144145impl<T: Config> Pallet<T> {146 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {147 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)148 }149 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {150 <TokenData<T>>::contains_key((collection.id, token))151 }152}153154155impl<T: Config> Pallet<T> {156 pub fn init_collection(157 owner: T::AccountId,158 data: CreateCollectionData<T::AccountId>,159 ) -> Result<CollectionId, DispatchError> {160 <PalletCommon<T>>::init_collection(owner, data)161 }162 pub fn destroy_collection(163 collection: NonfungibleHandle<T>,164 sender: &T::CrossAccountId,165 ) -> DispatchResult {166 let id = collection.id;167168 169170 PalletCommon::destroy_collection(collection.0, sender)?;171172 <TokenData<T>>::remove_prefix((id,), None);173 <Owned<T>>::remove_prefix((id,), None);174 <TokensMinted<T>>::remove(id);175 <TokensBurnt<T>>::remove(id);176 <Allowance<T>>::remove_prefix((id,), None);177 <AccountBalance<T>>::remove_prefix((id,), None);178 Ok(())179 }180181 pub fn burn(182 collection: &NonfungibleHandle<T>,183 sender: &T::CrossAccountId,184 token: TokenId,185 ) -> DispatchResult {186 let token_data =187 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;188 ensure!(189 &token_data.owner == sender190 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),191 <CommonError<T>>::NoPermission192 );193194 if collection.access == AccessMode::AllowList {195 collection.check_allowlist(sender)?;196 }197198 let burnt = <TokensBurnt<T>>::get(collection.id)199 .checked_add(1)200 .ok_or(ArithmeticError::Overflow)?;201202 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))203 .checked_sub(1)204 .ok_or(ArithmeticError::Overflow)?;205206 if balance == 0 {207 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));208 } else {209 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);210 }211 212213 <Owned<T>>::remove((collection.id, &token_data.owner, token));214 <TokensBurnt<T>>::insert(collection.id, burnt);215 <TokenData<T>>::remove((collection.id, token));216 let old_spender = <Allowance<T>>::take((collection.id, token));217218 if let Some(old_spender) = old_spender {219 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(220 collection.id,221 token,222 sender.clone(),223 old_spender,224 0,225 ));226 }227228 collection.log_mirrored(ERC721Events::Transfer {229 from: *token_data.owner.as_eth(),230 to: H160::default(),231 token_id: token.into(),232 });233 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(234 collection.id,235 token,236 token_data.owner,237 1,238 ));239 Ok(())240 }241242 pub fn transfer(243 collection: &NonfungibleHandle<T>,244 from: &T::CrossAccountId,245 to: &T::CrossAccountId,246 token: TokenId,247 ) -> DispatchResult {248 ensure!(249 collection.limits.transfers_enabled(),250 <CommonError<T>>::TransferNotAllowed251 );252253 let token_data =254 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;255 ensure!(256 &token_data.owner == from257 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),258 <CommonError<T>>::NoPermission259 );260261 if collection.access == AccessMode::AllowList {262 collection.check_allowlist(from)?;263 collection.check_allowlist(to)?;264 }265 <PalletCommon<T>>::ensure_correct_receiver(to)?;266267 let balance_from = <AccountBalance<T>>::get((collection.id, from))268 .checked_sub(1)269 .ok_or(<CommonError<T>>::TokenValueTooLow)?;270 let balance_to = if from != to {271 let balance_to = <AccountBalance<T>>::get((collection.id, to))272 .checked_add(1)273 .ok_or(ArithmeticError::Overflow)?;274275 ensure!(276 balance_to < collection.limits.account_token_ownership_limit(),277 <CommonError<T>>::AccountTokenLimitExceeded,278 );279280 Some(balance_to)281 } else {282 None283 };284285 286287 <TokenData<T>>::insert(288 (collection.id, token),289 ItemData {290 owner: to.clone(),291 ..token_data292 },293 );294295 if let Some(balance_to) = balance_to {296 297 if balance_from == 0 {298 <AccountBalance<T>>::remove((collection.id, from));299 } else {300 <AccountBalance<T>>::insert((collection.id, from), balance_from);301 }302 <AccountBalance<T>>::insert((collection.id, to), balance_to);303 <Owned<T>>::remove((collection.id, from, token));304 <Owned<T>>::insert((collection.id, to, token), true);305 }306 Self::set_allowance_unchecked(collection, from, token, None, true);307308 collection.log_mirrored(ERC721Events::Transfer {309 from: *from.as_eth(),310 to: *to.as_eth(),311 token_id: token.into(),312 });313 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(314 collection.id,315 token,316 from.clone(),317 to.clone(),318 1,319 ));320 Ok(())321 }322323 pub fn create_multiple_items(324 collection: &NonfungibleHandle<T>,325 sender: &T::CrossAccountId,326 data: Vec<CreateItemData<T>>,327 ) -> DispatchResult {328 if !collection.is_owner_or_admin(sender) {329 ensure!(330 collection.mint_mode,331 <CommonError<T>>::PublicMintingNotAllowed332 );333 collection.check_allowlist(sender)?;334335 for item in data.iter() {336 collection.check_allowlist(&item.owner)?;337 }338 }339340 for data in data.iter() {341 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;342 }343344 let first_token = <TokensMinted<T>>::get(collection.id);345 let tokens_minted = first_token346 .checked_add(data.len() as u32)347 .ok_or(ArithmeticError::Overflow)?;348 ensure!(349 tokens_minted <= collection.limits.token_limit(),350 <CommonError<T>>::CollectionTokenLimitExceeded351 );352353 let mut balances = BTreeMap::new();354 for data in &data {355 let balance = balances356 .entry(&data.owner)357 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));358 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;359360 ensure!(361 *balance <= collection.limits.account_token_ownership_limit(),362 <CommonError<T>>::AccountTokenLimitExceeded,363 );364 }365366 367368 <TokensMinted<T>>::insert(collection.id, tokens_minted);369 for (account, balance) in balances {370 <AccountBalance<T>>::insert((collection.id, account), balance);371 }372 for (i, data) in data.into_iter().enumerate() {373 let token = first_token + i as u32 + 1;374375 <TokenData<T>>::insert(376 (collection.id, token),377 ItemData {378 const_data: data.const_data,379 variable_data: data.variable_data,380 owner: data.owner.clone(),381 },382 );383 <Owned<T>>::insert((collection.id, &data.owner, token), true);384385 collection.log_mirrored(ERC721Events::Transfer {386 from: H160::default(),387 to: *data.owner.as_eth(),388 token_id: token.into(),389 });390 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(391 collection.id,392 TokenId(token),393 data.owner.clone(),394 1,395 ));396 }397 Ok(())398 }399400 pub fn set_allowance_unchecked(401 collection: &NonfungibleHandle<T>,402 sender: &T::CrossAccountId,403 token: TokenId,404 spender: Option<&T::CrossAccountId>,405 assume_implicit_eth: bool,406 ) {407 if let Some(spender) = spender {408 let old_spender = <Allowance<T>>::get((collection.id, token));409 <Allowance<T>>::insert((collection.id, token), spender);410 411 412 collection.log_mirrored(ERC721Events::Approval {413 owner: *sender.as_eth(),414 approved: *spender.as_eth(),415 token_id: token.into(),416 });417 418 419 if old_spender.as_ref() != Some(spender) {420 if let Some(old_owner) = old_spender {421 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(422 collection.id,423 token,424 sender.clone(),425 old_owner,426 0,427 ));428 }429 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(430 collection.id,431 token,432 sender.clone(),433 spender.clone(),434 1,435 ));436 }437 } else {438 let old_spender = <Allowance<T>>::take((collection.id, token));439 if !assume_implicit_eth {440 441 442 collection.log_mirrored(ERC721Events::Approval {443 owner: *sender.as_eth(),444 approved: H160::default(),445 token_id: token.into(),446 });447 }448 449 450 if let Some(old_spender) = old_spender {451 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(452 collection.id,453 token,454 sender.clone(),455 old_spender,456 0,457 ));458 }459 }460 }461462 pub fn set_allowance(463 collection: &NonfungibleHandle<T>,464 sender: &T::CrossAccountId,465 token: TokenId,466 spender: Option<&T::CrossAccountId>,467 ) -> DispatchResult {468 if collection.access == AccessMode::AllowList {469 collection.check_allowlist(sender)?;470 if let Some(spender) = spender {471 collection.check_allowlist(spender)?;472 }473 }474475 if let Some(spender) = spender {476 <PalletCommon<T>>::ensure_correct_receiver(spender)?;477 }478 let token_data =479 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;480 if &token_data.owner != sender {481 ensure!(482 collection.ignores_owned_amount(sender),483 <CommonError<T>>::CantApproveMoreThanOwned484 );485 }486487 488489 Self::set_allowance_unchecked(collection, sender, token, spender, false);490 Ok(())491 }492493 pub fn transfer_from(494 collection: &NonfungibleHandle<T>,495 spender: &T::CrossAccountId,496 from: &T::CrossAccountId,497 to: &T::CrossAccountId,498 token: TokenId,499 ) -> DispatchResult {500 if spender.conv_eq(from) {501 return Self::transfer(collection, from, to, token);502 }503 if collection.access == AccessMode::AllowList {504 505 collection.check_allowlist(spender)?;506 }507508 if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {509 ensure!(510 collection.ignores_allowance(spender),511 <CommonError<T>>::ApprovedValueTooLow512 );513 }514515 516517 Self::transfer(collection, from, to, token)?;518 519 Ok(())520 }521522 pub fn burn_from(523 collection: &NonfungibleHandle<T>,524 spender: &T::CrossAccountId,525 from: &T::CrossAccountId,526 token: TokenId,527 ) -> DispatchResult {528 if spender.conv_eq(from) {529 return Self::burn(collection, from, token);530 }531 if collection.access == AccessMode::AllowList {532 533 collection.check_allowlist(spender)?;534 }535536 if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {537 ensure!(538 collection.ignores_allowance(spender),539 <CommonError<T>>::ApprovedValueTooLow540 );541 }542543 544545 Self::burn(collection, from, token)546 }547548 pub fn set_variable_metadata(549 collection: &NonfungibleHandle<T>,550 sender: &T::CrossAccountId,551 token: TokenId,552 data: BoundedVec<u8, CustomDataLimit>,553 ) -> DispatchResult {554 let token_data =555 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;556 collection.check_can_update_meta(sender, &token_data.owner)?;557558 559560 <TokenData<T>>::insert(561 (collection.id, token),562 ItemData {563 variable_data: data,564 ..token_data565 },566 );567 Ok(())568 }569570 571 pub fn create_item(572 collection: &NonfungibleHandle<T>,573 sender: &T::CrossAccountId,574 data: CreateItemData<T>,575 ) -> DispatchResult {576 Self::create_multiple_items(collection, sender, vec![data])577 }578}