difftreelog
refactor(nonfungible) merge TokenData & Owner
in: master
3 files changed
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -190,13 +190,19 @@
}
fn token_owner(&self, token: TokenId) -> T::CrossAccountId {
- <Owner<T>>::get((self.id, token))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.owner)
+ .unwrap_or_default()
}
fn const_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token, DataKind::Constant))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.const_data.clone())
+ .unwrap_or_default()
}
fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
- <TokenData<T>>::get((self.id, token, DataKind::Variable))
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.variable_data.clone())
+ .unwrap_or_default()
}
fn collection_tokens(&self) -> u32 {
@@ -208,7 +214,10 @@
}
fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
- if <Owner<T>>::get((self.id, token)) == account {
+ if <TokenData<T>>::get((self.id, token))
+ .map(|a| a.owner == account)
+ .unwrap_or(false)
+ {
1
} else {
0
@@ -221,7 +230,10 @@
spender: T::CrossAccountId,
token: TokenId,
) -> u128 {
- if <Owner<T>>::get((self.id, token)) != sender {
+ if <TokenData<T>>::get((self.id, token))
+ .map(|a| a.owner == sender)
+ .unwrap_or(false)
+ {
0
} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {
1
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -13,8 +13,7 @@
use pallet_common::erc::PrecompileOutput;
use crate::{
- AccountBalance, Config, CreateItemData, DataKind, NonfungibleHandle, Owner, Pallet, TokenData,
- TokensMinted,
+ AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,
};
#[derive(ToLog)]
@@ -65,11 +64,11 @@
#[solidity(rename_selector = "tokenURI")]
fn token_uri(&self, token_id: uint256) -> Result<string> {
let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
- Ok(string::from_utf8_lossy(&<TokenData<T>>::get((
- self.id,
- token_id,
- DataKind::Constant,
- )))
+ Ok(string::from_utf8_lossy(
+ &<TokenData<T>>::get((self.id, token_id))
+ .ok_or("token not found")?
+ .const_data,
+ )
.into())
}
}
@@ -99,7 +98,10 @@
}
fn owner_of(&self, token_id: uint256) -> Result<address> {
let token: TokenId = token_id.try_into()?;
- Ok(*<Owner<T>>::get((self.id, token)).as_eth())
+ Ok(*<TokenData<T>>::get((self.id, token))
+ .ok_or("token not found")?
+ .owner
+ .as_eth())
}
fn safe_transfer_from_with_data(
&mut self,
@@ -302,7 +304,9 @@
fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
let token: TokenId = token_id.try_into()?;
- Ok(<TokenData<T>>::get((self.id, token, DataKind::Variable)))
+ Ok(<TokenData<T>>::get((self.id, token))
+ .ok_or("token not found")?
+ .variable_data)
}
fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
pallets/nonfungible/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6 AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;1617pub use pallet::*;18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;2223pub struct CreateItemData<T: Config> {24 pub const_data: BoundedVec<u8, CustomDataLimit>,25 pub variable_data: BoundedVec<u8, CustomDataLimit>,26 pub owner: T::CrossAccountId,27}28pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2930#[frame_support::pallet]31pub mod pallet {32 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};33 use sp_std::vec::Vec;34 use nft_data_structs::{CollectionId, TokenId};35 use super::weights::WeightInfo;3637 #[pallet::error]38 pub enum Error<T> {39 /// Not Nonfungible item data used to mint in Nonfungible collection.40 NotNonfungibleDataUsedToMintFungibleCollectionToken,41 /// Used amount > 1 with NFT42 NonfungibleItemsHaveNoAmount,43 }4445 #[pallet::config]46 pub trait Config: frame_system::Config + pallet_common::Config {47 type WeightInfo: WeightInfo;48 }4950 #[pallet::pallet]51 #[pallet::generate_store(pub(super) trait Store)]52 pub struct Pallet<T>(_);5354 #[pallet::storage]55 pub(super) type TokensMinted<T: Config> =56 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;57 #[pallet::storage]58 pub(super) type TokensBurnt<T: Config> =59 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6061 #[derive(Encode, Decode)]62 pub enum DataKind {63 Constant,64 Variable,65 }6667 #[pallet::storage]68 pub(super) type TokenData<T: Config> = StorageNMap<69 Key = (70 Key<Twox64Concat, CollectionId>,71 Key<Twox64Concat, TokenId>,72 Key<Identity, DataKind>,73 ),74 Value = Vec<u8>,75 QueryKind = ValueQuery,76 >;7778 #[pallet::storage]79 pub(super) type Owner<T: Config> = StorageNMap<80 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),81 Value = T::CrossAccountId,82 QueryKind = ValueQuery,83 >;84 /// Used to enumerate tokens owned by account85 #[pallet::storage]86 pub(super) type Owned<T: Config> = StorageNMap<87 Key = (88 Key<Twox64Concat, CollectionId>,89 Key<Blake2_128Concat, T::AccountId>,90 Key<Twox64Concat, TokenId>,91 ),92 Value = bool,93 QueryKind = ValueQuery,94 >;9596 #[pallet::storage]97 pub(super) type AccountBalance<T: Config> = StorageNMap<98 Key = (99 Key<Twox64Concat, CollectionId>,100 Key<Blake2_128Concat, T::AccountId>,101 ),102 Value = u32,103 QueryKind = ValueQuery,104 >;105106 #[pallet::storage]107 pub(super) type Allowance<T: Config> = StorageNMap<108 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),109 Value = T::CrossAccountId,110 QueryKind = OptionQuery,111 >;112}113114pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);115impl<T: Config> NonfungibleHandle<T> {116 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {117 Self(inner)118 }119 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {120 self.0121 }122}123impl<T: Config> Deref for NonfungibleHandle<T> {124 type Target = pallet_common::CollectionHandle<T>;125126 fn deref(&self) -> &Self::Target {127 &self.0128 }129}130131impl<T: Config> Pallet<T> {132 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {133 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)134 }135 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {136 <Owner<T>>::contains_key((collection.id, token))137 }138 pub fn ensure_owner(139 collection: &NonfungibleHandle<T>,140 token: TokenId,141 sender: &T::CrossAccountId,142 ) -> DispatchResult {143 ensure!(144 &<Owner<T>>::get((collection.id, token)) == sender,145 <CommonError<T>>::NoPermission146 );147 Ok(())148 }149 pub fn item_owner(150 collection: &NonfungibleHandle<T>,151 token: TokenId,152 ) -> Result<T::CrossAccountId, DispatchError> {153 let owner = <Owner<T>>::get((collection.id, token));154 ensure!(155 owner != T::CrossAccountId::default(),156 <CommonError<T>>::TokenNotFound157 );158 Ok(owner)159 }160}161162// unchecked calls skips any permission checks163impl<T: Config> Pallet<T> {164 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {165 PalletCommon::init_collection(data)166 }167 pub fn destroy_collection(168 collection: NonfungibleHandle<T>,169 sender: &T::CrossAccountId,170 ) -> DispatchResult {171 let id = collection.id;172173 // =========174175 PalletCommon::destroy_collection(collection.0, sender)?;176177 <Owner<T>>::remove_prefix((id,), None);178 <Owned<T>>::remove_prefix((id,), None);179 <TokensMinted<T>>::remove(id);180 <TokensBurnt<T>>::remove(id);181 <TokenData<T>>::remove_prefix((id,), None);182 <Allowance<T>>::remove_prefix((id,), None);183 <AccountBalance<T>>::remove_prefix((id,), None);184 Ok(())185 }186187 pub fn burn(188 collection: &NonfungibleHandle<T>,189 sender: &T::CrossAccountId,190 token: TokenId,191 ) -> DispatchResult {192 let token_owner = <Pallet<T>>::item_owner(collection, token)?;193 ensure!(194 &token_owner == sender195 || (collection.limits.owner_can_transfer196 && collection.is_owner_or_admin(sender)?),197 <CommonError<T>>::NoPermission198 );199200 if collection.access == AccessMode::WhiteList {201 collection.check_allowlist(sender)?;202 }203204 let burnt = <TokensBurnt<T>>::get(collection.id)205 .checked_add(1)206 .ok_or(ArithmeticError::Overflow)?;207208 // =========209210 <Owner<T>>::remove((collection.id, token));211 <Owned<T>>::remove((collection.id, token_owner.as_sub(), token));212 <TokensBurnt<T>>::insert(collection.id, burnt);213 <TokenData<T>>::remove_prefix((collection.id, token), None);214 <Allowance<T>>::remove((collection.id, token));215216 collection.log_infallible(ERC721Events::Transfer {217 from: *token_owner.as_eth(),218 to: H160::default(),219 token_id: token.into(),220 });221 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(222 collection.id,223 token,224 token_owner,225 1,226 ));227 return Ok(());228 }229230 pub fn transfer(231 collection: &NonfungibleHandle<T>,232 from: &T::CrossAccountId,233 to: &T::CrossAccountId,234 token: TokenId,235 ) -> DispatchResult {236 ensure!(237 collection.transfers_enabled,238 <CommonError<T>>::TransferNotAllowed239 );240241 let token_owner = <Pallet<T>>::item_owner(collection, token)?;242 ensure!(243 &token_owner == from244 || (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),245 <CommonError<T>>::NoPermission246 );247248 if collection.access == AccessMode::WhiteList {249 collection.check_allowlist(from)?;250 collection.check_allowlist(to)?;251 }252 <PalletCommon<T>>::ensure_correct_receiver(to)?;253254 let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))255 .checked_sub(1)256 .ok_or(<CommonError<T>>::TokenValueTooLow)?;257 let balance_to = if from != to {258 let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))259 .checked_add(1)260 .ok_or(ArithmeticError::Overflow)?;261262 ensure!(263 balance_to < collection.limits.account_token_ownership_limit(),264 <CommonError<T>>::AccountTokenLimitExceeded,265 );266267 Some(balance_to)268 } else {269 None270 };271272 collection.consume_sstores(4)?;273 collection.consume_log(3, 0)?;274275 // =========276277 if let Some(balance_to) = balance_to {278 // from != to279 if balance_from == 0 {280 <AccountBalance<T>>::remove((collection.id, from.as_sub()));281 } else {282 <AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);283 }284 <AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);285 <Owned<T>>::remove((collection.id, from.as_sub(), token));286 <Owned<T>>::insert((collection.id, to.as_sub(), token), true);287 }288 Self::set_allowance_unchecked(collection, from, token, None);289 <Owner<T>>::insert((collection.id, token), &to);290291 collection.log_infallible(ERC721Events::Transfer {292 from: *from.as_eth(),293 to: *to.as_eth(),294 token_id: token.into(),295 });296 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(297 collection.id,298 token,299 from.clone(),300 to.clone(),301 1,302 ));303 Ok(())304 }305306 pub fn create_multiple_items(307 collection: &NonfungibleHandle<T>,308 sender: &T::CrossAccountId,309 data: Vec<CreateItemData<T>>,310 ) -> DispatchResult {311 let unrestricted_minting = collection.is_owner_or_admin(sender)?;312 if !unrestricted_minting {313 ensure!(314 collection.mint_mode,315 <CommonError<T>>::PublicMintingNotAllowed316 );317 collection.check_allowlist(sender)?;318319 for item in data.iter() {320 collection.check_allowlist(&item.owner)?;321 }322 }323324 for data in data.iter() {325 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;326 if !data.const_data.is_empty() {327 collection.consume_sstore()?;328 }329 if !data.variable_data.is_empty() {330 collection.consume_sstore()?;331 }332 collection.consume_sstore()?;333 collection.consume_log(3, 0)?;334 }335336 let first_token = <TokensMinted<T>>::get(collection.id);337 let tokens_minted = first_token338 .checked_add(data.len() as u32)339 .ok_or(ArithmeticError::Overflow)?;340 ensure!(341 tokens_minted < collection.limits.token_limit,342 <CommonError<T>>::CollectionTokenLimitExceeded343 );344 collection.consume_sstore()?;345346 let mut balances = BTreeMap::new();347 for data in &data {348 let balance = balances349 .entry(data.owner.as_sub())350 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));351 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;352353 ensure!(354 *balance <= collection.limits.account_token_ownership_limit(),355 <CommonError<T>>::AccountTokenLimitExceeded,356 );357 }358 collection.consume_sstores(balances.len())?;359360 // =========361362 <TokensMinted<T>>::insert(collection.id, tokens_minted);363 for (account, balance) in balances {364 <AccountBalance<T>>::insert((collection.id, account), balance);365 }366 for (i, data) in data.into_iter().enumerate() {367 let token = first_token + i as u32;368369 if !data.const_data.is_empty() {370 <TokenData<T>>::insert((collection.id, token, DataKind::Constant), data.const_data);371 }372 if !data.variable_data.is_empty() {373 <TokenData<T>>::insert(374 (collection.id, token, DataKind::Variable),375 data.variable_data,376 );377 }378 <Owner<T>>::insert((collection.id, token), &data.owner);379 <Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);380381 collection.log_infallible(ERC721Events::Transfer {382 from: H160::default(),383 to: *data.owner.as_eth(),384 token_id: token.into(),385 });386 }387 Ok(())388 }389390 pub fn set_allowance_unchecked(391 collection: &NonfungibleHandle<T>,392 sender: &T::CrossAccountId,393 token: TokenId,394 spender: Option<&T::CrossAccountId>,395 ) {396 if let Some(spender) = spender {397 let old_spender = <Allowance<T>>::get((collection.id, token));398 <Allowance<T>>::insert((collection.id, token), spender);399 // In ERC721 there is only one possible approved user of token, so we set400 // approved user to spender401 collection.log_infallible(ERC721Events::Approval {402 owner: *sender.as_eth(),403 approved: *spender.as_eth(),404 token_id: token.into(),405 });406 // In Unique chain, any token can have any amount of approved users, so we need to407 // set allowance of old owner to 0, and allowance of new owner to 1408 if old_spender.as_ref() != Some(spender) {409 if let Some(old_owner) = old_spender {410 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(411 collection.id,412 token,413 sender.clone(),414 old_owner.clone(),415 0,416 ));417 }418 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(419 collection.id,420 token,421 sender.clone(),422 spender.clone(),423 1,424 ));425 }426 } else {427 let old_spender = <Allowance<T>>::take((collection.id, token));428 // In ERC721 there is only one possible approved user of token, so we set429 // approved user to zero address430 collection.log_infallible(ERC721Events::Approval {431 owner: *sender.as_eth(),432 approved: H160::default(),433 token_id: token.into(),434 });435 // In Unique chain, any token can have any amount of approved users, so we need to436 // set allowance of old owner to 0437 if let Some(old_spender) = old_spender {438 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(439 collection.id,440 token,441 sender.clone(),442 old_spender.clone(),443 0,444 ));445 }446 }447 }448449 pub fn set_allowance(450 collection: &NonfungibleHandle<T>,451 sender: &T::CrossAccountId,452 token: TokenId,453 spender: Option<&T::CrossAccountId>,454 ) -> DispatchResult {455 if collection.access == AccessMode::WhiteList {456 collection.check_allowlist(&sender)?;457 if let Some(spender) = spender {458 collection.check_allowlist(&spender)?;459 }460 }461462 if let Some(spender) = spender {463 <PalletCommon<T>>::ensure_correct_receiver(spender)?;464 }465 let token_owner = Self::item_owner(collection, token)?;466 if &token_owner != sender {467 ensure!(468 collection.ignores_owned_amount(sender)?,469 <CommonError<T>>::CantApproveMoreThanOwned470 );471 }472473 // =========474475 Self::set_allowance_unchecked(collection, sender, token, spender);476 Ok(())477 }478479 pub fn transfer_from(480 collection: &NonfungibleHandle<T>,481 spender: &T::CrossAccountId,482 from: &T::CrossAccountId,483 to: &T::CrossAccountId,484 token: TokenId,485 ) -> DispatchResult {486 if spender == from {487 return Self::transfer(collection, from, to, token);488 }489 if collection.access == AccessMode::WhiteList {490 // `from`, `to` checked in [`transfer`]491 collection.check_allowlist(spender)?;492 }493494 if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {495 ensure!(496 collection.ignores_allowance(spender)?,497 <CommonError<T>>::TokenValueNotEnough498 );499 }500501 // =========502503 Self::transfer(collection, &from, to, token)?;504 // Allowance is reset in [`transfer`]505 Ok(())506 }507508 pub fn set_variable_metadata(509 collection: &NonfungibleHandle<T>,510 sender: &T::CrossAccountId,511 token: TokenId,512 data: Vec<u8>,513 ) -> DispatchResult {514 ensure!(515 data.len() as u32 <= CUSTOM_DATA_LIMIT,516 <CommonError<T>>::TokenVariableDataLimitExceeded517 );518 let item_owner = Self::item_owner(collection, token)?;519 collection.check_can_update_meta(sender, &item_owner)?;520521 collection.consume_sstore()?;522523 // =========524525 <TokenData<T>>::insert((collection.id, token, DataKind::Variable), data);526 Ok(())527 }528529 /// Delegated to `create_multiple_items`530 pub fn create_item(531 collection: &NonfungibleHandle<T>,532 sender: &T::CrossAccountId,533 data: CreateItemData<T>,534 ) -> DispatchResult {535 Self::create_multiple_items(collection, sender, vec![data])536 }537}1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6 AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};1718pub use pallet::*;19pub mod benchmarking;20pub mod common;21pub mod erc;22pub mod weights;2324pub struct CreateItemData<T: Config> {25 pub const_data: BoundedVec<u8, CustomDataLimit>,26 pub variable_data: BoundedVec<u8, CustomDataLimit>,27 pub owner: T::CrossAccountId,28}29pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3031#[derive(Encode, Decode)]32pub struct ItemData<T: Config> {33 pub const_data: Vec<u8>,34 pub variable_data: Vec<u8>,35 pub owner: T::CrossAccountId,36}3738#[frame_support::pallet]39pub mod pallet {40 use super::*;41 use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};42 use nft_data_structs::{CollectionId, TokenId};43 use super::weights::WeightInfo;4445 #[pallet::error]46 pub enum Error<T> {47 /// Not Nonfungible item data used to mint in Nonfungible collection.48 NotNonfungibleDataUsedToMintFungibleCollectionToken,49 /// Used amount > 1 with NFT50 NonfungibleItemsHaveNoAmount,51 }5253 #[pallet::config]54 pub trait Config: frame_system::Config + pallet_common::Config {55 type WeightInfo: WeightInfo;56 }5758 #[pallet::pallet]59 #[pallet::generate_store(pub(super) trait Store)]60 pub struct Pallet<T>(_);6162 #[pallet::storage]63 pub(super) type TokensMinted<T: Config> =64 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;65 #[pallet::storage]66 pub(super) type TokensBurnt<T: Config> =67 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6869 #[pallet::storage]70 pub(super) type TokenData<T: Config> = StorageNMap<71 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),72 Value = ItemData<T>,73 QueryKind = OptionQuery,74 >;7576 /// Used to enumerate tokens owned by account77 #[pallet::storage]78 pub(super) type Owned<T: Config> = StorageNMap<79 Key = (80 Key<Twox64Concat, CollectionId>,81 Key<Blake2_128Concat, T::AccountId>,82 Key<Twox64Concat, TokenId>,83 ),84 Value = bool,85 QueryKind = ValueQuery,86 >;8788 #[pallet::storage]89 pub(super) type AccountBalance<T: Config> = StorageNMap<90 Key = (91 Key<Twox64Concat, CollectionId>,92 Key<Blake2_128Concat, T::AccountId>,93 ),94 Value = u32,95 QueryKind = ValueQuery,96 >;9798 #[pallet::storage]99 pub(super) type Allowance<T: Config> = StorageNMap<100 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),101 Value = T::CrossAccountId,102 QueryKind = OptionQuery,103 >;104}105106pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);107impl<T: Config> NonfungibleHandle<T> {108 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {109 Self(inner)110 }111 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {112 self.0113 }114}115impl<T: Config> Deref for NonfungibleHandle<T> {116 type Target = pallet_common::CollectionHandle<T>;117118 fn deref(&self) -> &Self::Target {119 &self.0120 }121}122123impl<T: Config> Pallet<T> {124 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {125 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)126 }127 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {128 <TokenData<T>>::contains_key((collection.id, token))129 }130}131132// unchecked calls skips any permission checks133impl<T: Config> Pallet<T> {134 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {135 PalletCommon::init_collection(data)136 }137 pub fn destroy_collection(138 collection: NonfungibleHandle<T>,139 sender: &T::CrossAccountId,140 ) -> DispatchResult {141 let id = collection.id;142143 // =========144145 PalletCommon::destroy_collection(collection.0, sender)?;146147 <TokenData<T>>::remove_prefix((id,), None);148 <Owned<T>>::remove_prefix((id,), None);149 <TokensMinted<T>>::remove(id);150 <TokensBurnt<T>>::remove(id);151 <Allowance<T>>::remove_prefix((id,), None);152 <AccountBalance<T>>::remove_prefix((id,), None);153 Ok(())154 }155156 pub fn burn(157 collection: &NonfungibleHandle<T>,158 sender: &T::CrossAccountId,159 token: TokenId,160 ) -> DispatchResult {161 let token_data = <TokenData<T>>::get((collection.id, token))162 .ok_or_else(|| <CommonError<T>>::TokenNotFound)?;163 ensure!(164 &token_data.owner == sender165 || (collection.limits.owner_can_transfer166 && collection.is_owner_or_admin(sender)?),167 <CommonError<T>>::NoPermission168 );169170 if collection.access == AccessMode::WhiteList {171 collection.check_allowlist(sender)?;172 }173174 let burnt = <TokensBurnt<T>>::get(collection.id)175 .checked_add(1)176 .ok_or(ArithmeticError::Overflow)?;177178 // =========179180 <Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));181 <TokensBurnt<T>>::insert(collection.id, burnt);182 <TokenData<T>>::remove((collection.id, token));183 let old_spender = <Allowance<T>>::take((collection.id, token));184185 if let Some(old_spender) = old_spender {186 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(187 collection.id,188 token,189 sender.clone(),190 old_spender.clone(),191 0,192 ));193 }194195 collection.log_infallible(ERC721Events::Transfer {196 from: *token_data.owner.as_eth(),197 to: H160::default(),198 token_id: token.into(),199 });200 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(201 collection.id,202 token,203 token_data.owner,204 1,205 ));206 return Ok(());207 }208209 pub fn transfer(210 collection: &NonfungibleHandle<T>,211 from: &T::CrossAccountId,212 to: &T::CrossAccountId,213 token: TokenId,214 ) -> DispatchResult {215 ensure!(216 collection.transfers_enabled,217 <CommonError<T>>::TransferNotAllowed218 );219220 let token_data = <TokenData<T>>::get((collection.id, token))221 .ok_or_else(|| <CommonError<T>>::TokenNotFound)?;222 ensure!(223 &token_data.owner == from224 || (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),225 <CommonError<T>>::NoPermission226 );227228 if collection.access == AccessMode::WhiteList {229 collection.check_allowlist(from)?;230 collection.check_allowlist(to)?;231 }232 <PalletCommon<T>>::ensure_correct_receiver(to)?;233234 let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))235 .checked_sub(1)236 .ok_or(<CommonError<T>>::TokenValueTooLow)?;237 let balance_to = if from != to {238 let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))239 .checked_add(1)240 .ok_or(ArithmeticError::Overflow)?;241242 ensure!(243 balance_to < collection.limits.account_token_ownership_limit(),244 <CommonError<T>>::AccountTokenLimitExceeded,245 );246247 Some(balance_to)248 } else {249 None250 };251252 collection.consume_sstores(4)?;253 collection.consume_log(3, 0)?;254255 // =========256257 <TokenData<T>>::insert(258 (collection.id, token),259 ItemData {260 owner: to.clone(),261 ..token_data262 },263 );264265 if let Some(balance_to) = balance_to {266 // from != to267 if balance_from == 0 {268 <AccountBalance<T>>::remove((collection.id, from.as_sub()));269 } else {270 <AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);271 }272 <AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);273 <Owned<T>>::remove((collection.id, from.as_sub(), token));274 <Owned<T>>::insert((collection.id, to.as_sub(), token), true);275 }276 Self::set_allowance_unchecked(collection, from, token, None);277278 collection.log_infallible(ERC721Events::Transfer {279 from: *from.as_eth(),280 to: *to.as_eth(),281 token_id: token.into(),282 });283 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(284 collection.id,285 token,286 from.clone(),287 to.clone(),288 1,289 ));290 Ok(())291 }292293 pub fn create_multiple_items(294 collection: &NonfungibleHandle<T>,295 sender: &T::CrossAccountId,296 data: Vec<CreateItemData<T>>,297 ) -> DispatchResult {298 let unrestricted_minting = collection.is_owner_or_admin(sender)?;299 if !unrestricted_minting {300 ensure!(301 collection.mint_mode,302 <CommonError<T>>::PublicMintingNotAllowed303 );304 collection.check_allowlist(sender)?;305306 for item in data.iter() {307 collection.check_allowlist(&item.owner)?;308 }309 }310311 for data in data.iter() {312 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;313 if !data.const_data.is_empty() {314 collection.consume_sstore()?;315 }316 if !data.variable_data.is_empty() {317 collection.consume_sstore()?;318 }319 collection.consume_sstore()?;320 collection.consume_log(3, 0)?;321 }322323 let first_token = <TokensMinted<T>>::get(collection.id);324 let tokens_minted = first_token325 .checked_add(data.len() as u32)326 .ok_or(ArithmeticError::Overflow)?;327 ensure!(328 tokens_minted < collection.limits.token_limit,329 <CommonError<T>>::CollectionTokenLimitExceeded330 );331 collection.consume_sstore()?;332333 let mut balances = BTreeMap::new();334 for data in &data {335 let balance = balances336 .entry(data.owner.as_sub())337 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));338 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;339340 ensure!(341 *balance <= collection.limits.account_token_ownership_limit(),342 <CommonError<T>>::AccountTokenLimitExceeded,343 );344 }345 collection.consume_sstores(balances.len())?;346347 // =========348349 <TokensMinted<T>>::insert(collection.id, tokens_minted);350 for (account, balance) in balances {351 <AccountBalance<T>>::insert((collection.id, account), balance);352 }353 for (i, data) in data.into_iter().enumerate() {354 let token = first_token + i as u32 + 1;355356 <TokenData<T>>::insert(357 (collection.id, token),358 ItemData {359 const_data: data.const_data.into(),360 variable_data: data.variable_data.into(),361 owner: data.owner.clone(),362 },363 );364 <Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);365366 collection.log_infallible(ERC721Events::Transfer {367 from: H160::default(),368 to: *data.owner.as_eth(),369 token_id: token.into(),370 });371 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(372 collection.id,373 TokenId(token),374 data.owner.clone(),375 1,376 ));377 }378 Ok(())379 }380381 pub fn set_allowance_unchecked(382 collection: &NonfungibleHandle<T>,383 sender: &T::CrossAccountId,384 token: TokenId,385 spender: Option<&T::CrossAccountId>,386 ) {387 if let Some(spender) = spender {388 let old_spender = <Allowance<T>>::get((collection.id, token));389 <Allowance<T>>::insert((collection.id, token), spender);390 // In ERC721 there is only one possible approved user of token, so we set391 // approved user to spender392 collection.log_infallible(ERC721Events::Approval {393 owner: *sender.as_eth(),394 approved: *spender.as_eth(),395 token_id: token.into(),396 });397 // In Unique chain, any token can have any amount of approved users, so we need to398 // set allowance of old owner to 0, and allowance of new owner to 1399 if old_spender.as_ref() != Some(spender) {400 if let Some(old_owner) = old_spender {401 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(402 collection.id,403 token,404 sender.clone(),405 old_owner.clone(),406 0,407 ));408 }409 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(410 collection.id,411 token,412 sender.clone(),413 spender.clone(),414 1,415 ));416 }417 } else {418 let old_spender = <Allowance<T>>::take((collection.id, token));419 // In ERC721 there is only one possible approved user of token, so we set420 // approved user to zero address421 collection.log_infallible(ERC721Events::Approval {422 owner: *sender.as_eth(),423 approved: H160::default(),424 token_id: token.into(),425 });426 // In Unique chain, any token can have any amount of approved users, so we need to427 // set allowance of old owner to 0428 if let Some(old_spender) = old_spender {429 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(430 collection.id,431 token,432 sender.clone(),433 old_spender.clone(),434 0,435 ));436 }437 }438 }439440 pub fn set_allowance(441 collection: &NonfungibleHandle<T>,442 sender: &T::CrossAccountId,443 token: TokenId,444 spender: Option<&T::CrossAccountId>,445 ) -> DispatchResult {446 if collection.access == AccessMode::WhiteList {447 collection.check_allowlist(&sender)?;448 if let Some(spender) = spender {449 collection.check_allowlist(&spender)?;450 }451 }452453 if let Some(spender) = spender {454 <PalletCommon<T>>::ensure_correct_receiver(spender)?;455 }456 let token_data =457 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;458 if &token_data.owner != sender {459 ensure!(460 collection.ignores_owned_amount(sender)?,461 <CommonError<T>>::CantApproveMoreThanOwned462 );463 }464465 // =========466467 Self::set_allowance_unchecked(collection, sender, token, spender);468 Ok(())469 }470471 pub fn transfer_from(472 collection: &NonfungibleHandle<T>,473 spender: &T::CrossAccountId,474 from: &T::CrossAccountId,475 to: &T::CrossAccountId,476 token: TokenId,477 ) -> DispatchResult {478 if spender == from {479 return Self::transfer(collection, from, to, token);480 }481 if collection.access == AccessMode::WhiteList {482 // `from`, `to` checked in [`transfer`]483 collection.check_allowlist(spender)?;484 }485486 if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {487 ensure!(488 collection.ignores_allowance(spender)?,489 <CommonError<T>>::TokenValueNotEnough490 );491 }492493 // =========494495 Self::transfer(collection, &from, to, token)?;496 // Allowance is reset in [`transfer`]497 Ok(())498 }499500 pub fn set_variable_metadata(501 collection: &NonfungibleHandle<T>,502 sender: &T::CrossAccountId,503 token: TokenId,504 data: Vec<u8>,505 ) -> DispatchResult {506 ensure!(507 data.len() as u32 <= CUSTOM_DATA_LIMIT,508 <CommonError<T>>::TokenVariableDataLimitExceeded509 );510 let token_data =511 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;512 collection.check_can_update_meta(sender, &token_data.owner)?;513514 collection.consume_sstore()?;515516 // =========517518 <TokenData<T>>::insert(519 (collection.id, token),520 ItemData {521 variable_data: data,522 ..token_data523 },524 );525 Ok(())526 }527528 /// Delegated to `create_multiple_items`529 pub fn create_item(530 collection: &NonfungibleHandle<T>,531 sender: &T::CrossAccountId,532 data: CreateItemData<T>,533 ) -> DispatchResult {534 Self::create_multiple_items(collection, sender, vec![data])535 }536}