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}