git.delta.rocks / unique-network / refs/commits / cfe76c116514

difftreelog

fix skip eth allowance event on token transfer

Yaroslav Bolyukin2021-11-04parent: #0aec5e9.patch.diff
in: master

1 file changed

modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
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};17use scale_info::TypeInfo;1819pub use pallet::*;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod common;23pub mod erc;24pub mod weights;2526pub struct CreateItemData<T: Config> {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29	pub owner: T::CrossAccountId,30}31pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3233#[derive(Encode, Decode, TypeInfo)]34pub struct ItemData<T: Config> {35	pub const_data: Vec<u8>,36	pub variable_data: Vec<u8>,37	pub owner: T::CrossAccountId,38}3940#[frame_support::pallet]41pub mod pallet {42	use super::*;43	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};44	use nft_data_structs::{CollectionId, TokenId};45	use super::weights::WeightInfo;4647	#[pallet::error]48	pub enum Error<T> {49		/// Not Nonfungible item data used to mint in Nonfungible collection.50		NotNonfungibleDataUsedToMintFungibleCollectionToken,51		/// Used amount > 1 with NFT52		NonfungibleItemsHaveNoAmount,53	}5455	#[pallet::config]56	pub trait Config: frame_system::Config + pallet_common::Config {57		type WeightInfo: WeightInfo;58	}5960	#[pallet::pallet]61	#[pallet::generate_store(pub(super) trait Store)]62	pub struct Pallet<T>(_);6364	#[pallet::storage]65	pub(super) type TokensMinted<T: Config> =66		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;67	#[pallet::storage]68	pub(super) type TokensBurnt<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub(super) type TokenData<T: Config> = StorageNMap<73		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),74		Value = ItemData<T>,75		QueryKind = OptionQuery,76	>;7778	/// Used to enumerate tokens owned by account79	#[pallet::storage]80	pub(super) type Owned<T: Config> = StorageNMap<81		Key = (82			Key<Twox64Concat, CollectionId>,83			Key<Blake2_128Concat, T::AccountId>,84			Key<Twox64Concat, TokenId>,85		),86		Value = bool,87		QueryKind = ValueQuery,88	>;8990	#[pallet::storage]91	pub(super) type AccountBalance<T: Config> = StorageNMap<92		Key = (93			Key<Twox64Concat, CollectionId>,94			Key<Blake2_128Concat, T::AccountId>,95		),96		Value = u32,97		QueryKind = ValueQuery,98	>;99100	#[pallet::storage]101	pub(super) type Allowance<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = T::CrossAccountId,104		QueryKind = OptionQuery,105	>;106}107108pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);109impl<T: Config> NonfungibleHandle<T> {110	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {111		Self(inner)112	}113	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {114		self.0115	}116}117impl<T: Config> Deref for NonfungibleHandle<T> {118	type Target = pallet_common::CollectionHandle<T>;119120	fn deref(&self) -> &Self::Target {121		&self.0122	}123}124125impl<T: Config> Pallet<T> {126	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {127		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)128	}129	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {130		<TokenData<T>>::contains_key((collection.id, token))131	}132}133134// unchecked calls skips any permission checks135impl<T: Config> Pallet<T> {136	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {137		PalletCommon::init_collection(data)138	}139	pub fn destroy_collection(140		collection: NonfungibleHandle<T>,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		let id = collection.id;144145		// =========146147		PalletCommon::destroy_collection(collection.0, sender)?;148149		<TokenData<T>>::remove_prefix((id,), None);150		<Owned<T>>::remove_prefix((id,), None);151		<TokensMinted<T>>::remove(id);152		<TokensBurnt<T>>::remove(id);153		<Allowance<T>>::remove_prefix((id,), None);154		<AccountBalance<T>>::remove_prefix((id,), None);155		Ok(())156	}157158	pub fn burn(159		collection: &NonfungibleHandle<T>,160		sender: &T::CrossAccountId,161		token: TokenId,162	) -> DispatchResult {163		let token_data = <TokenData<T>>::get((collection.id, token))164			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;165		ensure!(166			&token_data.owner == sender167				|| (collection.limits.owner_can_transfer168					&& collection.is_owner_or_admin(sender)?),169			<CommonError<T>>::NoPermission170		);171172		if collection.access == AccessMode::WhiteList {173			collection.check_allowlist(sender)?;174		}175176		let burnt = <TokensBurnt<T>>::get(collection.id)177			.checked_add(1)178			.ok_or(ArithmeticError::Overflow)?;179180		// =========181182		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));183		<TokensBurnt<T>>::insert(collection.id, burnt);184		<TokenData<T>>::remove((collection.id, token));185		let old_spender = <Allowance<T>>::take((collection.id, token));186187		if let Some(old_spender) = old_spender {188			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(189				collection.id,190				token,191				sender.clone(),192				old_spender.clone(),193				0,194			));195		}196197		collection.log_infallible(ERC721Events::Transfer {198			from: *token_data.owner.as_eth(),199			to: H160::default(),200			token_id: token.into(),201		});202		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(203			collection.id,204			token,205			token_data.owner,206			1,207		));208		return Ok(());209	}210211	pub fn transfer(212		collection: &NonfungibleHandle<T>,213		from: &T::CrossAccountId,214		to: &T::CrossAccountId,215		token: TokenId,216	) -> DispatchResult {217		ensure!(218			collection.transfers_enabled,219			<CommonError<T>>::TransferNotAllowed220		);221222		let token_data = <TokenData<T>>::get((collection.id, token))223			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;224		ensure!(225			&token_data.owner == from226				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),227			<CommonError<T>>::NoPermission228		);229230		if collection.access == AccessMode::WhiteList {231			collection.check_allowlist(from)?;232			collection.check_allowlist(to)?;233		}234		<PalletCommon<T>>::ensure_correct_receiver(to)?;235236		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))237			.checked_sub(1)238			.ok_or(<CommonError<T>>::TokenValueTooLow)?;239		let balance_to = if from != to {240			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))241				.checked_add(1)242				.ok_or(ArithmeticError::Overflow)?;243244			ensure!(245				balance_to < collection.limits.account_token_ownership_limit(),246				<CommonError<T>>::AccountTokenLimitExceeded,247			);248249			Some(balance_to)250		} else {251			None252		};253254		collection.consume_sstores(4)?;255		collection.consume_log(3, 0)?;256257		// =========258259		<TokenData<T>>::insert(260			(collection.id, token),261			ItemData {262				owner: to.clone(),263				..token_data264			},265		);266267		if let Some(balance_to) = balance_to {268			// from != to269			if balance_from == 0 {270				<AccountBalance<T>>::remove((collection.id, from.as_sub()));271			} else {272				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);273			}274			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);275			<Owned<T>>::remove((collection.id, from.as_sub(), token));276			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);277		}278		Self::set_allowance_unchecked(collection, from, token, None);279280		collection.log_infallible(ERC721Events::Transfer {281			from: *from.as_eth(),282			to: *to.as_eth(),283			token_id: token.into(),284		});285		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(286			collection.id,287			token,288			from.clone(),289			to.clone(),290			1,291		));292		Ok(())293	}294295	pub fn create_multiple_items(296		collection: &NonfungibleHandle<T>,297		sender: &T::CrossAccountId,298		data: Vec<CreateItemData<T>>,299	) -> DispatchResult {300		let unrestricted_minting = collection.is_owner_or_admin(sender)?;301		if !unrestricted_minting {302			ensure!(303				collection.mint_mode,304				<CommonError<T>>::PublicMintingNotAllowed305			);306			collection.check_allowlist(sender)?;307308			for item in data.iter() {309				collection.check_allowlist(&item.owner)?;310			}311		}312313		for data in data.iter() {314			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;315			if !data.const_data.is_empty() {316				collection.consume_sstore()?;317			}318			if !data.variable_data.is_empty() {319				collection.consume_sstore()?;320			}321			collection.consume_sstore()?;322			collection.consume_log(3, 0)?;323		}324325		let first_token = <TokensMinted<T>>::get(collection.id);326		let tokens_minted = first_token327			.checked_add(data.len() as u32)328			.ok_or(ArithmeticError::Overflow)?;329		ensure!(330			tokens_minted < collection.limits.token_limit,331			<CommonError<T>>::CollectionTokenLimitExceeded332		);333		collection.consume_sstore()?;334335		let mut balances = BTreeMap::new();336		for data in &data {337			let balance = balances338				.entry(data.owner.as_sub())339				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));340			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;341342			ensure!(343				*balance <= collection.limits.account_token_ownership_limit(),344				<CommonError<T>>::AccountTokenLimitExceeded,345			);346		}347		collection.consume_sstores(balances.len())?;348349		// =========350351		<TokensMinted<T>>::insert(collection.id, tokens_minted);352		for (account, balance) in balances {353			<AccountBalance<T>>::insert((collection.id, account), balance);354		}355		for (i, data) in data.into_iter().enumerate() {356			let token = first_token + i as u32 + 1;357358			<TokenData<T>>::insert(359				(collection.id, token),360				ItemData {361					const_data: data.const_data.into(),362					variable_data: data.variable_data.into(),363					owner: data.owner.clone(),364				},365			);366			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);367368			collection.log_infallible(ERC721Events::Transfer {369				from: H160::default(),370				to: *data.owner.as_eth(),371				token_id: token.into(),372			});373			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(374				collection.id,375				TokenId(token),376				data.owner.clone(),377				1,378			));379		}380		Ok(())381	}382383	pub fn set_allowance_unchecked(384		collection: &NonfungibleHandle<T>,385		sender: &T::CrossAccountId,386		token: TokenId,387		spender: Option<&T::CrossAccountId>,388	) {389		if let Some(spender) = spender {390			let old_spender = <Allowance<T>>::get((collection.id, token));391			<Allowance<T>>::insert((collection.id, token), spender);392			// In ERC721 there is only one possible approved user of token, so we set393			// approved user to spender394			collection.log_infallible(ERC721Events::Approval {395				owner: *sender.as_eth(),396				approved: *spender.as_eth(),397				token_id: token.into(),398			});399			// In Unique chain, any token can have any amount of approved users, so we need to400			// set allowance of old owner to 0, and allowance of new owner to 1401			if old_spender.as_ref() != Some(spender) {402				if let Some(old_owner) = old_spender {403					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(404						collection.id,405						token,406						sender.clone(),407						old_owner.clone(),408						0,409					));410				}411				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(412					collection.id,413					token,414					sender.clone(),415					spender.clone(),416					1,417				));418			}419		} else {420			let old_spender = <Allowance<T>>::take((collection.id, token));421			// In ERC721 there is only one possible approved user of token, so we set422			// approved user to zero address423			collection.log_infallible(ERC721Events::Approval {424				owner: *sender.as_eth(),425				approved: H160::default(),426				token_id: token.into(),427			});428			// In Unique chain, any token can have any amount of approved users, so we need to429			// set allowance of old owner to 0430			if let Some(old_spender) = old_spender {431				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(432					collection.id,433					token,434					sender.clone(),435					old_spender.clone(),436					0,437				));438			}439		}440	}441442	pub fn set_allowance(443		collection: &NonfungibleHandle<T>,444		sender: &T::CrossAccountId,445		token: TokenId,446		spender: Option<&T::CrossAccountId>,447	) -> DispatchResult {448		if collection.access == AccessMode::WhiteList {449			collection.check_allowlist(&sender)?;450			if let Some(spender) = spender {451				collection.check_allowlist(&spender)?;452			}453		}454455		if let Some(spender) = spender {456			<PalletCommon<T>>::ensure_correct_receiver(spender)?;457		}458		let token_data =459			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;460		if &token_data.owner != sender {461			ensure!(462				collection.ignores_owned_amount(sender)?,463				<CommonError<T>>::CantApproveMoreThanOwned464			);465		}466467		// =========468469		Self::set_allowance_unchecked(collection, sender, token, spender);470		Ok(())471	}472473	pub fn transfer_from(474		collection: &NonfungibleHandle<T>,475		spender: &T::CrossAccountId,476		from: &T::CrossAccountId,477		to: &T::CrossAccountId,478		token: TokenId,479	) -> DispatchResult {480		if spender == from {481			return Self::transfer(collection, from, to, token);482		}483		if collection.access == AccessMode::WhiteList {484			// `from`, `to` checked in [`transfer`]485			collection.check_allowlist(spender)?;486		}487488		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {489			ensure!(490				collection.ignores_allowance(spender)?,491				<CommonError<T>>::TokenValueNotEnough492			);493		}494495		// =========496497		Self::transfer(collection, &from, to, token)?;498		// Allowance is reset in [`transfer`]499		Ok(())500	}501502	pub fn burn_from(503		collection: &NonfungibleHandle<T>,504		spender: &T::CrossAccountId,505		from: &T::CrossAccountId,506		token: TokenId,507	) -> DispatchResult {508		if spender == from {509			return Self::burn(collection, from, token);510		}511		if collection.access == AccessMode::WhiteList {512			// `from` checked in [`burn`]513			collection.check_allowlist(spender)?;514		}515516		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {517			ensure!(518				collection.ignores_allowance(spender)?,519				<CommonError<T>>::TokenValueNotEnough520			);521		}522523		// =========524525		Self::burn(collection, &from, token)526	}527528	pub fn set_variable_metadata(529		collection: &NonfungibleHandle<T>,530		sender: &T::CrossAccountId,531		token: TokenId,532		data: Vec<u8>,533	) -> DispatchResult {534		ensure!(535			data.len() as u32 <= CUSTOM_DATA_LIMIT,536			<CommonError<T>>::TokenVariableDataLimitExceeded537		);538		let token_data =539			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;540		collection.check_can_update_meta(sender, &token_data.owner)?;541542		collection.consume_sstore()?;543544		// =========545546		<TokenData<T>>::insert(547			(collection.id, token),548			ItemData {549				variable_data: data,550				..token_data551			},552		);553		Ok(())554	}555556	/// Delegated to `create_multiple_items`557	pub fn create_item(558		collection: &NonfungibleHandle<T>,559		sender: &T::CrossAccountId,560		data: CreateItemData<T>,561	) -> DispatchResult {562		Self::create_multiple_items(collection, sender, vec![data])563	}564}