difftreelog
feat initial rmrk send impl
in: master
10 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1298,6 +1298,7 @@
spender: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
fn transfer_from(
&self,
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -226,6 +226,7 @@
spender: T::CrossAccountId,
token: TokenId,
amount: u128,
+ _nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(
token == TokenId::default(),
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -308,14 +308,15 @@
spender: T::CrossAccountId,
token: TokenId,
amount: u128,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
with_weight(
if amount == 1 {
- <Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))
+ <Pallet<T>>::set_allowance(self, &sender, token, Some(&spender), nesting_budget)
} else {
- <Pallet<T>>::set_allowance(self, &sender, token, None)
+ <Pallet<T>>::set_allowance(self, &sender, token, None, nesting_budget)
},
<CommonWeights<T>>::approve(),
)
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -269,8 +269,11 @@
let caller = T::CrossAccountId::from_eth(caller);
let approved = T::CrossAccountId::from_eth(approved);
let token = token_id.try_into()?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
+ <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved), &budget)
.map_err(dispatch_to_evm::<T>)?;
Ok(())
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22 BoundedVec, ensure, fail, transactional,23 storage::with_transaction,24 pallet_prelude::DispatchResultWithPostInfo,25 pallet_prelude::Weight,26 weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30 mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,31 PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32};33use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};34use pallet_common::{35 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,36 eth::collection_id_to_address,37};38use pallet_structure::{Pallet as PalletStructure, Error as StructureError};39use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};40use sp_core::H160;41use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};42use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};43use core::ops::Deref;44use codec::{Encode, Decode, MaxEncodedLen};45use scale_info::TypeInfo;4647pub use pallet::*;48use weights::WeightInfo;49#[cfg(feature = "runtime-benchmarks")]50pub mod benchmarking;51pub mod common;52pub mod erc;53pub mod weights;5455pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;56pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5758#[struct_versioning::versioned(version = 2, upper)]59#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]60pub struct ItemData<CrossAccountId> {61 #[version(..2)]62 pub const_data: BoundedVec<u8, CustomDataLimit>,6364 #[version(..2)]65 pub variable_data: BoundedVec<u8, CustomDataLimit>,6667 pub owner: CrossAccountId,68}6970#[frame_support::pallet]71pub mod pallet {72 use super::*;73 use frame_support::{74 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,75 };76 use frame_system::pallet_prelude::*;77 use up_data_structs::{CollectionId, TokenId};78 use super::weights::WeightInfo;7980 #[pallet::error]81 pub enum Error<T> {82 /// Not Nonfungible item data used to mint in Nonfungible collection.83 NotNonfungibleDataUsedToMintFungibleCollectionToken,84 /// Used amount > 1 with NFT85 NonfungibleItemsHaveNoAmount,86 /// Unable to burn NFT with children87 CantBurnNftWithChildren,88 }8990 #[pallet::config]91 pub trait Config:92 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config93 {94 type WeightInfo: WeightInfo;95 }9697 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);9899 #[pallet::pallet]100 #[pallet::storage_version(STORAGE_VERSION)]101 #[pallet::generate_store(pub(super) trait Store)]102 pub struct Pallet<T>(_);103104 #[pallet::storage]105 pub type TokensMinted<T: Config> =106 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;107 #[pallet::storage]108 pub type TokensBurnt<T: Config> =109 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;110111 #[pallet::storage]112 pub type TokenData<T: Config> = StorageNMap<113 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),114 Value = ItemData<T::CrossAccountId>,115 QueryKind = OptionQuery,116 >;117118 #[pallet::storage]119 #[pallet::getter(fn token_properties)]120 pub type TokenProperties<T: Config> = StorageNMap<121 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),122 Value = Properties,123 QueryKind = ValueQuery,124 OnEmpty = up_data_structs::TokenProperties,125 >;126127 /// Used to enumerate tokens owned by account128 #[pallet::storage]129 pub type Owned<T: Config> = StorageNMap<130 Key = (131 Key<Twox64Concat, CollectionId>,132 Key<Blake2_128Concat, T::CrossAccountId>,133 Key<Twox64Concat, TokenId>,134 ),135 Value = bool,136 QueryKind = ValueQuery,137 >;138139 /// Used to enumerate token's children140 #[pallet::storage]141 #[pallet::getter(fn token_children)]142 pub type TokenChildren<T: Config> = StorageNMap<143 Key = (144 Key<Twox64Concat, CollectionId>,145 Key<Twox64Concat, TokenId>,146 Key<Twox64Concat, (CollectionId, TokenId)>,147 ),148 Value = bool,149 QueryKind = ValueQuery,150 >;151152 #[pallet::storage]153 pub type AccountBalance<T: Config> = StorageNMap<154 Key = (155 Key<Twox64Concat, CollectionId>,156 Key<Blake2_128Concat, T::CrossAccountId>,157 ),158 Value = u32,159 QueryKind = ValueQuery,160 >;161162 #[pallet::storage]163 pub type Allowance<T: Config> = StorageNMap<164 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),165 Value = T::CrossAccountId,166 QueryKind = OptionQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 let mut had_consts = BTreeSet::new();174 <TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(175 |(collection, token), v| {176 let mut props = vec![];177 if !v.const_data.is_empty() {178 props.push(Property {179 key: b"_old_constData".to_vec().try_into().unwrap(),180 value: v181 .const_data182 .clone()183 .into_inner()184 .try_into()185 .expect("const too long"),186 });187 had_consts.insert(collection);188 }189 if !v.variable_data.is_empty() {190 props.push(Property {191 key: b"_old_variableData".to_vec().try_into().unwrap(),192 value: v193 .variable_data194 .clone()195 .into_inner()196 .try_into()197 .expect("variable too long"),198 })199 }200 if !props.is_empty() {201 Self::set_scoped_token_properties(202 collection,203 token,204 PropertyScope::None,205 props.into_iter(),206 )207 .expect("existing token data exceeds property storage");208 }209 Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))210 },211 );212 for collection in had_consts {213 <PalletCommon<T>>::set_property_permission_unchecked(214 collection,215 PropertyKeyPermission {216 key: b"_old_constData".to_vec().try_into().unwrap(),217 permission: PropertyPermission {218 mutable: false,219 collection_admin: true,220 token_owner: false,221 },222 },223 )224 .expect("failed to configure permission");225 }226 }227228 0229 }230 }231}232233pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);234impl<T: Config> NonfungibleHandle<T> {235 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {236 Self(inner)237 }238 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {239 self.0240 }241 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {242 &mut self.0243 }244}245impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {246 fn recorder(&self) -> &SubstrateRecorder<T> {247 self.0.recorder()248 }249 fn into_recorder(self) -> SubstrateRecorder<T> {250 self.0.into_recorder()251 }252}253impl<T: Config> Deref for NonfungibleHandle<T> {254 type Target = pallet_common::CollectionHandle<T>;255256 fn deref(&self) -> &Self::Target {257 &self.0258 }259}260261impl<T: Config> Pallet<T> {262 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {263 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)264 }265 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {266 <TokenData<T>>::contains_key((collection.id, token))267 }268269 pub fn set_scoped_token_property(270 collection_id: CollectionId,271 token_id: TokenId,272 scope: PropertyScope,273 property: Property,274 ) -> DispatchResult {275 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {276 properties.try_scoped_set(scope, property.key, property.value)277 })278 .map_err(<CommonError<T>>::from)?;279280 Ok(())281 }282283 pub fn set_scoped_token_properties(284 collection_id: CollectionId,285 token_id: TokenId,286 scope: PropertyScope,287 properties: impl Iterator<Item = Property>,288 ) -> DispatchResult {289 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {290 stored_properties.try_scoped_set_from_iter(scope, properties)291 })292 .map_err(<CommonError<T>>::from)?;293294 Ok(())295 }296297 pub fn current_token_id(collection_id: CollectionId) -> TokenId {298 TokenId(<TokensMinted<T>>::get(collection_id))299 }300}301302// unchecked calls skips any permission checks303impl<T: Config> Pallet<T> {304 pub fn init_collection(305 owner: T::CrossAccountId,306 data: CreateCollectionData<T::AccountId>,307 ) -> Result<CollectionId, DispatchError> {308 <PalletCommon<T>>::init_collection(owner, data)309 }310 pub fn destroy_collection(311 collection: NonfungibleHandle<T>,312 sender: &T::CrossAccountId,313 ) -> DispatchResult {314 let id = collection.id;315316 if Self::collection_has_tokens(id) {317 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());318 }319320 // =========321322 PalletCommon::destroy_collection(collection.0, sender)?;323324 <TokenData<T>>::remove_prefix((id,), None);325 <TokenChildren<T>>::remove_prefix((id,), None);326 <Owned<T>>::remove_prefix((id,), None);327 <TokensMinted<T>>::remove(id);328 <TokensBurnt<T>>::remove(id);329 <Allowance<T>>::remove_prefix((id,), None);330 <AccountBalance<T>>::remove_prefix((id,), None);331 Ok(())332 }333334 pub fn burn(335 collection: &NonfungibleHandle<T>,336 sender: &T::CrossAccountId,337 token: TokenId,338 ) -> DispatchResult {339 let token_data =340 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;341 ensure!(342 &token_data.owner == sender343 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),344 <CommonError<T>>::NoPermission345 );346347 if collection.permissions.access() == AccessMode::AllowList {348 collection.check_allowlist(sender)?;349 }350351 if Self::token_has_children(collection.id, token) {352 return Err(<Error<T>>::CantBurnNftWithChildren.into());353 }354355 let burnt = <TokensBurnt<T>>::get(collection.id)356 .checked_add(1)357 .ok_or(ArithmeticError::Overflow)?;358359 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))360 .checked_sub(1)361 .ok_or(ArithmeticError::Overflow)?;362363 // =========364365 if balance == 0 {366 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));367 } else {368 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);369 }370371 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);372373 <Owned<T>>::remove((collection.id, &token_data.owner, token));374 <TokensBurnt<T>>::insert(collection.id, burnt);375 <TokenData<T>>::remove((collection.id, token));376 <TokenProperties<T>>::remove((collection.id, token));377 let old_spender = <Allowance<T>>::take((collection.id, token));378379 if let Some(old_spender) = old_spender {380 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(381 collection.id,382 token,383 token_data.owner.clone(),384 old_spender,385 0,386 ));387 }388389 <PalletEvm<T>>::deposit_log(390 ERC721Events::Transfer {391 from: *token_data.owner.as_eth(),392 to: H160::default(),393 token_id: token.into(),394 }395 .to_log(collection_id_to_address(collection.id)),396 );397 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(398 collection.id,399 token,400 token_data.owner,401 1,402 ));403 Ok(())404 }405406 #[transactional]407 pub fn burn_recursively(408 collection: &NonfungibleHandle<T>,409 sender: &T::CrossAccountId,410 token: TokenId,411 self_budget: &dyn Budget,412 breadth_budget: &dyn Budget,413 ) -> DispatchResultWithPostInfo {414 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);415416 let current_token_account =417 T::CrossTokenAddressMapping::token_to_address(collection.id, token);418419 let mut weight = 0 as Weight;420421 // This method is transactional, if user in fact doesn't have permissions to remove token -422 // tokens removed here will be restored after rejected transaction423 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {424 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);425 let PostDispatchInfo { actual_weight, .. } =426 <PalletStructure<T>>::burn_item_recursively(427 current_token_account.clone(),428 collection,429 token,430 self_budget,431 breadth_budget,432 )?;433 if let Some(actual_weight) = actual_weight {434 weight = weight.saturating_add(actual_weight);435 }436 }437438 Self::burn(collection, sender, token)?;439 DispatchResultWithPostInfo::Ok(PostDispatchInfo {440 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),441 pays_fee: Pays::Yes,442 })443 }444445 pub fn set_token_property(446 collection: &NonfungibleHandle<T>,447 sender: &T::CrossAccountId,448 token_id: TokenId,449 property: Property,450 ) -> DispatchResult {451 Self::check_token_change_permission(collection, sender, token_id, &property.key)?;452453 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {454 let property = property.clone();455 properties.try_set(property.key, property.value)456 })457 .map_err(<CommonError<T>>::from)?;458459 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(460 collection.id,461 token_id,462 property.key,463 ));464465 Ok(())466 }467468 #[transactional]469 pub fn set_token_properties(470 collection: &NonfungibleHandle<T>,471 sender: &T::CrossAccountId,472 token_id: TokenId,473 properties: Vec<Property>,474 ) -> DispatchResult {475 for property in properties {476 Self::set_token_property(collection, sender, token_id, property)?;477 }478479 Ok(())480 }481482 pub fn delete_token_property(483 collection: &NonfungibleHandle<T>,484 sender: &T::CrossAccountId,485 token_id: TokenId,486 property_key: PropertyKey,487 ) -> DispatchResult {488 Self::check_token_change_permission(collection, sender, token_id, &property_key)?;489490 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {491 properties.remove(&property_key)492 })493 .map_err(<CommonError<T>>::from)?;494495 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(496 collection.id,497 token_id,498 property_key,499 ));500501 Ok(())502 }503504 fn check_token_change_permission(505 collection: &NonfungibleHandle<T>,506 sender: &T::CrossAccountId,507 token_id: TokenId,508 property_key: &PropertyKey,509 ) -> DispatchResult {510 let permission = <PalletCommon<T>>::property_permissions(collection.id)511 .get(property_key)512 .cloned()513 .unwrap_or_else(PropertyPermission::none);514515 let token_data = <TokenData<T>>::get((collection.id, token_id))516 .ok_or(<CommonError<T>>::TokenNotFound)?;517518 let check_token_owner = || -> DispatchResult {519 ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);520 Ok(())521 };522523 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))524 .get(property_key)525 .is_some();526527 match permission {528 PropertyPermission { mutable: false, .. } if is_property_exists => {529 Err(<CommonError<T>>::NoPermission.into())530 }531532 PropertyPermission {533 collection_admin,534 token_owner,535 ..536 } => {537 let mut check_result = Err(<CommonError<T>>::NoPermission.into());538539 if collection_admin {540 check_result = collection.check_is_owner_or_admin(sender);541 }542543 if token_owner {544 check_result.or_else(|_| check_token_owner())545 } else {546 check_result547 }548 }549 }550 }551552 #[transactional]553 pub fn delete_token_properties(554 collection: &NonfungibleHandle<T>,555 sender: &T::CrossAccountId,556 token_id: TokenId,557 property_keys: Vec<PropertyKey>,558 ) -> DispatchResult {559 for key in property_keys {560 Self::delete_token_property(collection, sender, token_id, key)?;561 }562563 Ok(())564 }565566 pub fn set_collection_properties(567 collection: &NonfungibleHandle<T>,568 sender: &T::CrossAccountId,569 properties: Vec<Property>,570 ) -> DispatchResult {571 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)572 }573574 pub fn delete_collection_properties(575 collection: &CollectionHandle<T>,576 sender: &T::CrossAccountId,577 property_keys: Vec<PropertyKey>,578 ) -> DispatchResult {579 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)580 }581582 pub fn set_property_permissions(583 collection: &CollectionHandle<T>,584 sender: &T::CrossAccountId,585 property_permissions: Vec<PropertyKeyPermission>,586 ) -> DispatchResult {587 <PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)588 }589590 pub fn set_property_permission(591 collection: &CollectionHandle<T>,592 sender: &T::CrossAccountId,593 permission: PropertyKeyPermission,594 ) -> DispatchResult {595 <PalletCommon<T>>::set_property_permission(collection, sender, permission)596 }597598 pub fn transfer(599 collection: &NonfungibleHandle<T>,600 from: &T::CrossAccountId,601 to: &T::CrossAccountId,602 token: TokenId,603 nesting_budget: &dyn Budget,604 ) -> DispatchResult {605 ensure!(606 collection.limits.transfers_enabled(),607 <CommonError<T>>::TransferNotAllowed608 );609610 let token_data =611 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;612 // TODO: require sender to be token, owner, require admins to go through transfer_from613 ensure!(614 &token_data.owner == from615 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),616 <CommonError<T>>::NoPermission617 );618619 if collection.permissions.access() == AccessMode::AllowList {620 collection.check_allowlist(from)?;621 collection.check_allowlist(to)?;622 }623 <PalletCommon<T>>::ensure_correct_receiver(to)?;624625 let balance_from = <AccountBalance<T>>::get((collection.id, from))626 .checked_sub(1)627 .ok_or(<CommonError<T>>::TokenValueTooLow)?;628 let balance_to = if from != to {629 let balance_to = <AccountBalance<T>>::get((collection.id, to))630 .checked_add(1)631 .ok_or(ArithmeticError::Overflow)?;632633 ensure!(634 balance_to < collection.limits.account_token_ownership_limit(),635 <CommonError<T>>::AccountTokenLimitExceeded,636 );637638 Some(balance_to)639 } else {640 None641 };642643 <PalletStructure<T>>::nest_if_sent_to_token(644 from.clone(),645 to,646 collection.id,647 token,648 nesting_budget,649 )?;650651 // =========652653 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);654655 <TokenData<T>>::insert(656 (collection.id, token),657 ItemData {658 owner: to.clone(),659 ..token_data660 },661 );662663 if let Some(balance_to) = balance_to {664 // from != to665 if balance_from == 0 {666 <AccountBalance<T>>::remove((collection.id, from));667 } else {668 <AccountBalance<T>>::insert((collection.id, from), balance_from);669 }670 <AccountBalance<T>>::insert((collection.id, to), balance_to);671 <Owned<T>>::remove((collection.id, from, token));672 <Owned<T>>::insert((collection.id, to, token), true);673 }674 Self::set_allowance_unchecked(collection, from, token, None, true);675676 <PalletEvm<T>>::deposit_log(677 ERC721Events::Transfer {678 from: *from.as_eth(),679 to: *to.as_eth(),680 token_id: token.into(),681 }682 .to_log(collection_id_to_address(collection.id)),683 );684 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(685 collection.id,686 token,687 from.clone(),688 to.clone(),689 1,690 ));691 Ok(())692 }693694 pub fn create_multiple_items(695 collection: &NonfungibleHandle<T>,696 sender: &T::CrossAccountId,697 data: Vec<CreateItemData<T>>,698 nesting_budget: &dyn Budget,699 ) -> DispatchResult {700 if !collection.is_owner_or_admin(sender) {701 ensure!(702 collection.permissions.mint_mode(),703 <CommonError<T>>::PublicMintingNotAllowed704 );705 collection.check_allowlist(sender)?;706707 for item in data.iter() {708 collection.check_allowlist(&item.owner)?;709 }710 }711712 for data in data.iter() {713 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;714 }715716 let first_token = <TokensMinted<T>>::get(collection.id);717 let tokens_minted = first_token718 .checked_add(data.len() as u32)719 .ok_or(ArithmeticError::Overflow)?;720 ensure!(721 tokens_minted <= collection.limits.token_limit(),722 <CommonError<T>>::CollectionTokenLimitExceeded723 );724725 let mut balances = BTreeMap::new();726 for data in &data {727 let balance = balances728 .entry(&data.owner)729 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));730 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;731732 ensure!(733 *balance <= collection.limits.account_token_ownership_limit(),734 <CommonError<T>>::AccountTokenLimitExceeded,735 );736 }737738 for (i, data) in data.iter().enumerate() {739 let token = TokenId(first_token + i as u32 + 1);740741 <PalletStructure<T>>::check_nesting(742 sender.clone(),743 &data.owner,744 collection.id,745 token,746 nesting_budget,747 )?;748 }749750 // =========751752 with_transaction(|| {753 for (i, data) in data.iter().enumerate() {754 let token = first_token + i as u32 + 1;755756 <TokenData<T>>::insert(757 (collection.id, token),758 ItemData {759 // const_data: data.const_data.clone(),760 owner: data.owner.clone(),761 },762 );763764 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(765 &data.owner,766 collection.id,767 TokenId(token),768 );769770 if let Err(e) = Self::set_token_properties(771 collection,772 sender,773 TokenId(token),774 data.properties.clone().into_inner(),775 ) {776 return TransactionOutcome::Rollback(Err(e));777 }778 }779 TransactionOutcome::Commit(Ok(()))780 })?;781782 <TokensMinted<T>>::insert(collection.id, tokens_minted);783 for (account, balance) in balances {784 <AccountBalance<T>>::insert((collection.id, account), balance);785 }786 for (i, data) in data.into_iter().enumerate() {787 let token = first_token + i as u32 + 1;788 <Owned<T>>::insert((collection.id, &data.owner, token), true);789790 <PalletEvm<T>>::deposit_log(791 ERC721Events::Transfer {792 from: H160::default(),793 to: *data.owner.as_eth(),794 token_id: token.into(),795 }796 .to_log(collection_id_to_address(collection.id)),797 );798 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(799 collection.id,800 TokenId(token),801 data.owner.clone(),802 1,803 ));804 }805 Ok(())806 }807808 pub fn set_allowance_unchecked(809 collection: &NonfungibleHandle<T>,810 sender: &T::CrossAccountId,811 token: TokenId,812 spender: Option<&T::CrossAccountId>,813 assume_implicit_eth: bool,814 ) {815 if let Some(spender) = spender {816 let old_spender = <Allowance<T>>::get((collection.id, token));817 <Allowance<T>>::insert((collection.id, token), spender);818 // In ERC721 there is only one possible approved user of token, so we set819 // approved user to spender820 <PalletEvm<T>>::deposit_log(821 ERC721Events::Approval {822 owner: *sender.as_eth(),823 approved: *spender.as_eth(),824 token_id: token.into(),825 }826 .to_log(collection_id_to_address(collection.id)),827 );828 // In Unique chain, any token can have any amount of approved users, so we need to829 // set allowance of old owner to 0, and allowance of new owner to 1830 if old_spender.as_ref() != Some(spender) {831 if let Some(old_owner) = old_spender {832 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(833 collection.id,834 token,835 sender.clone(),836 old_owner,837 0,838 ));839 }840 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(841 collection.id,842 token,843 sender.clone(),844 spender.clone(),845 1,846 ));847 }848 } else {849 let old_spender = <Allowance<T>>::take((collection.id, token));850 if !assume_implicit_eth {851 // In ERC721 there is only one possible approved user of token, so we set852 // approved user to zero address853 <PalletEvm<T>>::deposit_log(854 ERC721Events::Approval {855 owner: *sender.as_eth(),856 approved: H160::default(),857 token_id: token.into(),858 }859 .to_log(collection_id_to_address(collection.id)),860 );861 }862 // In Unique chain, any token can have any amount of approved users, so we need to863 // set allowance of old owner to 0864 if let Some(old_spender) = old_spender {865 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(866 collection.id,867 token,868 sender.clone(),869 old_spender,870 0,871 ));872 }873 }874 }875876 pub fn set_allowance(877 collection: &NonfungibleHandle<T>,878 sender: &T::CrossAccountId,879 token: TokenId,880 spender: Option<&T::CrossAccountId>,881 ) -> DispatchResult {882 if collection.permissions.access() == AccessMode::AllowList {883 collection.check_allowlist(sender)?;884 if let Some(spender) = spender {885 collection.check_allowlist(spender)?;886 }887 }888889 if let Some(spender) = spender {890 <PalletCommon<T>>::ensure_correct_receiver(spender)?;891 }892 let token_data =893 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;894 if &token_data.owner != sender {895 ensure!(896 collection.ignores_owned_amount(sender),897 <CommonError<T>>::CantApproveMoreThanOwned898 );899 }900901 // =========902903 Self::set_allowance_unchecked(collection, sender, token, spender, false);904 Ok(())905 }906907 fn check_allowed(908 collection: &NonfungibleHandle<T>,909 spender: &T::CrossAccountId,910 from: &T::CrossAccountId,911 token: TokenId,912 nesting_budget: &dyn Budget,913 ) -> DispatchResult {914 if spender.conv_eq(from) {915 return Ok(());916 }917 if collection.permissions.access() == AccessMode::AllowList {918 // `from`, `to` checked in [`transfer`]919 collection.check_allowlist(spender)?;920 }921 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {922 // TODO: should collection owner be allowed to perform this transfer?923 ensure!(924 <PalletStructure<T>>::check_indirectly_owned(925 spender.clone(),926 source.0,927 source.1,928 None,929 nesting_budget930 )?,931 <CommonError<T>>::ApprovedValueTooLow,932 );933 return Ok(());934 }935 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {936 return Ok(());937 }938 ensure!(939 collection.ignores_allowance(spender),940 <CommonError<T>>::ApprovedValueTooLow941 );942 Ok(())943 }944945 pub fn transfer_from(946 collection: &NonfungibleHandle<T>,947 spender: &T::CrossAccountId,948 from: &T::CrossAccountId,949 to: &T::CrossAccountId,950 token: TokenId,951 nesting_budget: &dyn Budget,952 ) -> DispatchResult {953 Self::check_allowed(collection, spender, from, token, nesting_budget)?;954955 // =========956957 // Allowance is reset in [`transfer`]958 Self::transfer(collection, from, to, token, nesting_budget)959 }960961 pub fn burn_from(962 collection: &NonfungibleHandle<T>,963 spender: &T::CrossAccountId,964 from: &T::CrossAccountId,965 token: TokenId,966 nesting_budget: &dyn Budget,967 ) -> DispatchResult {968 Self::check_allowed(collection, spender, from, token, nesting_budget)?;969970 // =========971972 Self::burn(collection, from, token)973 }974975 pub fn check_nesting(976 handle: &NonfungibleHandle<T>,977 sender: T::CrossAccountId,978 from: (CollectionId, TokenId),979 under: TokenId,980 nesting_budget: &dyn Budget,981 ) -> DispatchResult {982 fn ensure_sender_allowed<T: Config>(983 collection: CollectionId,984 token: TokenId,985 for_nest: (CollectionId, TokenId),986 sender: T::CrossAccountId,987 budget: &dyn Budget,988 ) -> DispatchResult {989 ensure!(990 <PalletStructure<T>>::check_indirectly_owned(991 sender,992 collection,993 token,994 Some(for_nest),995 budget996 )?,997 <CommonError<T>>::OnlyOwnerAllowedToNest,998 );999 Ok(())1000 }1001 match handle.permissions.nesting() {1002 NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),1003 NestingRule::Owner => {1004 ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1005 }1006 NestingRule::OwnerRestricted(whitelist) => {1007 ensure!(1008 whitelist.contains(&from.0),1009 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1010 );1011 ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1012 }1013 NestingRule::Permissive => {}1014 }1015 Ok(())1016 }10171018 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1019 <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1020 }10211022 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1023 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1024 }10251026 fn collection_has_tokens(collection_id: CollectionId) -> bool {1027 <TokenData<T>>::iter_prefix((collection_id,))1028 .next()1029 .is_some()1030 }10311032 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1033 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1034 .next()1035 .is_some()1036 }10371038 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1039 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1040 .map(|((child_collection_id, child_id), _)| TokenChild {1041 collection: child_collection_id,1042 token: child_id,1043 })1044 .collect()1045 }10461047 /// Delegated to `create_multiple_items`1048 pub fn create_item(1049 collection: &NonfungibleHandle<T>,1050 sender: &T::CrossAccountId,1051 data: CreateItemData<T>,1052 nesting_budget: &dyn Budget,1053 ) -> DispatchResult {1054 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1055 }1056}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use erc::ERC721Events;20use evm_coder::ToLog;21use frame_support::{22 BoundedVec, ensure, fail, transactional,23 storage::with_transaction,24 pallet_prelude::DispatchResultWithPostInfo,25 pallet_prelude::Weight,26 weights::{PostDispatchInfo, Pays},27};28use up_data_structs::{29 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,30 mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,31 PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,32};33use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};34use pallet_common::{35 Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,36 eth::collection_id_to_address,37};38use pallet_structure::{Pallet as PalletStructure, Error as StructureError};39use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};40use sp_core::H160;41use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};42use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap, collections::btree_set::BTreeSet};43use core::ops::Deref;44use codec::{Encode, Decode, MaxEncodedLen};45use scale_info::TypeInfo;4647pub use pallet::*;48use weights::WeightInfo;49#[cfg(feature = "runtime-benchmarks")]50pub mod benchmarking;51pub mod common;52pub mod erc;53pub mod weights;5455pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;56pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;5758#[struct_versioning::versioned(version = 2, upper)]59#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]60pub struct ItemData<CrossAccountId> {61 #[version(..2)]62 pub const_data: BoundedVec<u8, CustomDataLimit>,6364 #[version(..2)]65 pub variable_data: BoundedVec<u8, CustomDataLimit>,6667 pub owner: CrossAccountId,68}6970#[frame_support::pallet]71pub mod pallet {72 use super::*;73 use frame_support::{74 Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,75 };76 use frame_system::pallet_prelude::*;77 use up_data_structs::{CollectionId, TokenId};78 use super::weights::WeightInfo;7980 #[pallet::error]81 pub enum Error<T> {82 /// Not Nonfungible item data used to mint in Nonfungible collection.83 NotNonfungibleDataUsedToMintFungibleCollectionToken,84 /// Used amount > 1 with NFT85 NonfungibleItemsHaveNoAmount,86 /// Unable to burn NFT with children87 CantBurnNftWithChildren,88 }8990 #[pallet::config]91 pub trait Config:92 frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config93 {94 type WeightInfo: WeightInfo;95 }9697 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);9899 #[pallet::pallet]100 #[pallet::storage_version(STORAGE_VERSION)]101 #[pallet::generate_store(pub(super) trait Store)]102 pub struct Pallet<T>(_);103104 #[pallet::storage]105 pub type TokensMinted<T: Config> =106 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;107 #[pallet::storage]108 pub type TokensBurnt<T: Config> =109 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;110111 #[pallet::storage]112 pub type TokenData<T: Config> = StorageNMap<113 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),114 Value = ItemData<T::CrossAccountId>,115 QueryKind = OptionQuery,116 >;117118 #[pallet::storage]119 #[pallet::getter(fn token_properties)]120 pub type TokenProperties<T: Config> = StorageNMap<121 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),122 Value = Properties,123 QueryKind = ValueQuery,124 OnEmpty = up_data_structs::TokenProperties,125 >;126127 /// Used to enumerate tokens owned by account128 #[pallet::storage]129 pub type Owned<T: Config> = StorageNMap<130 Key = (131 Key<Twox64Concat, CollectionId>,132 Key<Blake2_128Concat, T::CrossAccountId>,133 Key<Twox64Concat, TokenId>,134 ),135 Value = bool,136 QueryKind = ValueQuery,137 >;138139 /// Used to enumerate token's children140 #[pallet::storage]141 #[pallet::getter(fn token_children)]142 pub type TokenChildren<T: Config> = StorageNMap<143 Key = (144 Key<Twox64Concat, CollectionId>,145 Key<Twox64Concat, TokenId>,146 Key<Twox64Concat, (CollectionId, TokenId)>,147 ),148 Value = bool,149 QueryKind = ValueQuery,150 >;151152 #[pallet::storage]153 pub type AccountBalance<T: Config> = StorageNMap<154 Key = (155 Key<Twox64Concat, CollectionId>,156 Key<Blake2_128Concat, T::CrossAccountId>,157 ),158 Value = u32,159 QueryKind = ValueQuery,160 >;161162 #[pallet::storage]163 pub type Allowance<T: Config> = StorageNMap<164 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),165 Value = T::CrossAccountId,166 QueryKind = OptionQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 let mut had_consts = BTreeSet::new();174 <TokenData<T>>::translate::<ItemDataVersion1<T::CrossAccountId>, _>(175 |(collection, token), v| {176 let mut props = vec![];177 if !v.const_data.is_empty() {178 props.push(Property {179 key: b"_old_constData".to_vec().try_into().unwrap(),180 value: v181 .const_data182 .clone()183 .into_inner()184 .try_into()185 .expect("const too long"),186 });187 had_consts.insert(collection);188 }189 if !v.variable_data.is_empty() {190 props.push(Property {191 key: b"_old_variableData".to_vec().try_into().unwrap(),192 value: v193 .variable_data194 .clone()195 .into_inner()196 .try_into()197 .expect("variable too long"),198 })199 }200 if !props.is_empty() {201 Self::set_scoped_token_properties(202 collection,203 token,204 PropertyScope::None,205 props.into_iter(),206 )207 .expect("existing token data exceeds property storage");208 }209 Some(<ItemDataVersion2<T::CrossAccountId>>::from(v))210 },211 );212 for collection in had_consts {213 <PalletCommon<T>>::set_property_permission_unchecked(214 collection,215 PropertyKeyPermission {216 key: b"_old_constData".to_vec().try_into().unwrap(),217 permission: PropertyPermission {218 mutable: false,219 collection_admin: true,220 token_owner: false,221 },222 },223 )224 .expect("failed to configure permission");225 }226 }227228 0229 }230 }231}232233pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);234impl<T: Config> NonfungibleHandle<T> {235 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {236 Self(inner)237 }238 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {239 self.0240 }241 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {242 &mut self.0243 }244}245impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {246 fn recorder(&self) -> &SubstrateRecorder<T> {247 self.0.recorder()248 }249 fn into_recorder(self) -> SubstrateRecorder<T> {250 self.0.into_recorder()251 }252}253impl<T: Config> Deref for NonfungibleHandle<T> {254 type Target = pallet_common::CollectionHandle<T>;255256 fn deref(&self) -> &Self::Target {257 &self.0258 }259}260261impl<T: Config> Pallet<T> {262 pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {263 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)264 }265 pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {266 <TokenData<T>>::contains_key((collection.id, token))267 }268269 pub fn set_scoped_token_property(270 collection_id: CollectionId,271 token_id: TokenId,272 scope: PropertyScope,273 property: Property,274 ) -> DispatchResult {275 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {276 properties.try_scoped_set(scope, property.key, property.value)277 })278 .map_err(<CommonError<T>>::from)?;279280 Ok(())281 }282283 pub fn set_scoped_token_properties(284 collection_id: CollectionId,285 token_id: TokenId,286 scope: PropertyScope,287 properties: impl Iterator<Item = Property>,288 ) -> DispatchResult {289 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {290 stored_properties.try_scoped_set_from_iter(scope, properties)291 })292 .map_err(<CommonError<T>>::from)?;293294 Ok(())295 }296297 pub fn current_token_id(collection_id: CollectionId) -> TokenId {298 TokenId(<TokensMinted<T>>::get(collection_id))299 }300}301302// unchecked calls skips any permission checks303impl<T: Config> Pallet<T> {304 pub fn init_collection(305 owner: T::CrossAccountId,306 data: CreateCollectionData<T::AccountId>,307 ) -> Result<CollectionId, DispatchError> {308 <PalletCommon<T>>::init_collection(owner, data)309 }310 pub fn destroy_collection(311 collection: NonfungibleHandle<T>,312 sender: &T::CrossAccountId,313 ) -> DispatchResult {314 let id = collection.id;315316 if Self::collection_has_tokens(id) {317 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());318 }319320 // =========321322 PalletCommon::destroy_collection(collection.0, sender)?;323324 <TokenData<T>>::remove_prefix((id,), None);325 <TokenChildren<T>>::remove_prefix((id,), None);326 <Owned<T>>::remove_prefix((id,), None);327 <TokensMinted<T>>::remove(id);328 <TokensBurnt<T>>::remove(id);329 <Allowance<T>>::remove_prefix((id,), None);330 <AccountBalance<T>>::remove_prefix((id,), None);331 Ok(())332 }333334 pub fn burn(335 collection: &NonfungibleHandle<T>,336 sender: &T::CrossAccountId,337 token: TokenId,338 ) -> DispatchResult {339 let token_data =340 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;341 ensure!(342 &token_data.owner == sender343 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),344 <CommonError<T>>::NoPermission345 );346347 if collection.permissions.access() == AccessMode::AllowList {348 collection.check_allowlist(sender)?;349 }350351 if Self::token_has_children(collection.id, token) {352 return Err(<Error<T>>::CantBurnNftWithChildren.into());353 }354355 let burnt = <TokensBurnt<T>>::get(collection.id)356 .checked_add(1)357 .ok_or(ArithmeticError::Overflow)?;358359 let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))360 .checked_sub(1)361 .ok_or(ArithmeticError::Overflow)?;362363 // =========364365 if balance == 0 {366 <AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));367 } else {368 <AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);369 }370371 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);372373 <Owned<T>>::remove((collection.id, &token_data.owner, token));374 <TokensBurnt<T>>::insert(collection.id, burnt);375 <TokenData<T>>::remove((collection.id, token));376 <TokenProperties<T>>::remove((collection.id, token));377 let old_spender = <Allowance<T>>::take((collection.id, token));378379 if let Some(old_spender) = old_spender {380 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(381 collection.id,382 token,383 token_data.owner.clone(),384 old_spender,385 0,386 ));387 }388389 <PalletEvm<T>>::deposit_log(390 ERC721Events::Transfer {391 from: *token_data.owner.as_eth(),392 to: H160::default(),393 token_id: token.into(),394 }395 .to_log(collection_id_to_address(collection.id)),396 );397 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(398 collection.id,399 token,400 token_data.owner,401 1,402 ));403 Ok(())404 }405406 #[transactional]407 pub fn burn_recursively(408 collection: &NonfungibleHandle<T>,409 sender: &T::CrossAccountId,410 token: TokenId,411 self_budget: &dyn Budget,412 breadth_budget: &dyn Budget,413 ) -> DispatchResultWithPostInfo {414 ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);415416 let current_token_account =417 T::CrossTokenAddressMapping::token_to_address(collection.id, token);418419 let mut weight = 0 as Weight;420421 // This method is transactional, if user in fact doesn't have permissions to remove token -422 // tokens removed here will be restored after rejected transaction423 for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {424 ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);425 let PostDispatchInfo { actual_weight, .. } =426 <PalletStructure<T>>::burn_item_recursively(427 current_token_account.clone(),428 collection,429 token,430 self_budget,431 breadth_budget,432 )?;433 if let Some(actual_weight) = actual_weight {434 weight = weight.saturating_add(actual_weight);435 }436 }437438 Self::burn(collection, sender, token)?;439 DispatchResultWithPostInfo::Ok(PostDispatchInfo {440 actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),441 pays_fee: Pays::Yes,442 })443 }444445 pub fn set_token_property(446 collection: &NonfungibleHandle<T>,447 sender: &T::CrossAccountId,448 token_id: TokenId,449 property: Property,450 ) -> DispatchResult {451 Self::check_token_change_permission(collection, sender, token_id, &property.key)?;452453 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {454 let property = property.clone();455 properties.try_set(property.key, property.value)456 })457 .map_err(<CommonError<T>>::from)?;458459 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(460 collection.id,461 token_id,462 property.key,463 ));464465 Ok(())466 }467468 #[transactional]469 pub fn set_token_properties(470 collection: &NonfungibleHandle<T>,471 sender: &T::CrossAccountId,472 token_id: TokenId,473 properties: Vec<Property>,474 ) -> DispatchResult {475 for property in properties {476 Self::set_token_property(collection, sender, token_id, property)?;477 }478479 Ok(())480 }481482 pub fn delete_token_property(483 collection: &NonfungibleHandle<T>,484 sender: &T::CrossAccountId,485 token_id: TokenId,486 property_key: PropertyKey,487 ) -> DispatchResult {488 Self::check_token_change_permission(collection, sender, token_id, &property_key)?;489490 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {491 properties.remove(&property_key)492 })493 .map_err(<CommonError<T>>::from)?;494495 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(496 collection.id,497 token_id,498 property_key,499 ));500501 Ok(())502 }503504 fn check_token_change_permission(505 collection: &NonfungibleHandle<T>,506 sender: &T::CrossAccountId,507 token_id: TokenId,508 property_key: &PropertyKey,509 ) -> DispatchResult {510 let permission = <PalletCommon<T>>::property_permissions(collection.id)511 .get(property_key)512 .cloned()513 .unwrap_or_else(PropertyPermission::none);514515 let token_data = <TokenData<T>>::get((collection.id, token_id))516 .ok_or(<CommonError<T>>::TokenNotFound)?;517518 let check_token_owner = || -> DispatchResult {519 ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);520 Ok(())521 };522523 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))524 .get(property_key)525 .is_some();526527 match permission {528 PropertyPermission { mutable: false, .. } if is_property_exists => {529 Err(<CommonError<T>>::NoPermission.into())530 }531532 PropertyPermission {533 collection_admin,534 token_owner,535 ..536 } => {537 let mut check_result = Err(<CommonError<T>>::NoPermission.into());538539 if collection_admin {540 check_result = collection.check_is_owner_or_admin(sender);541 }542543 if token_owner {544 check_result.or_else(|_| check_token_owner())545 } else {546 check_result547 }548 }549 }550 }551552 #[transactional]553 pub fn delete_token_properties(554 collection: &NonfungibleHandle<T>,555 sender: &T::CrossAccountId,556 token_id: TokenId,557 property_keys: Vec<PropertyKey>,558 ) -> DispatchResult {559 for key in property_keys {560 Self::delete_token_property(collection, sender, token_id, key)?;561 }562563 Ok(())564 }565566 pub fn set_collection_properties(567 collection: &NonfungibleHandle<T>,568 sender: &T::CrossAccountId,569 properties: Vec<Property>,570 ) -> DispatchResult {571 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)572 }573574 pub fn delete_collection_properties(575 collection: &CollectionHandle<T>,576 sender: &T::CrossAccountId,577 property_keys: Vec<PropertyKey>,578 ) -> DispatchResult {579 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)580 }581582 pub fn set_property_permissions(583 collection: &CollectionHandle<T>,584 sender: &T::CrossAccountId,585 property_permissions: Vec<PropertyKeyPermission>,586 ) -> DispatchResult {587 <PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)588 }589590 pub fn set_property_permission(591 collection: &CollectionHandle<T>,592 sender: &T::CrossAccountId,593 permission: PropertyKeyPermission,594 ) -> DispatchResult {595 <PalletCommon<T>>::set_property_permission(collection, sender, permission)596 }597598 pub fn transfer(599 collection: &NonfungibleHandle<T>,600 from: &T::CrossAccountId,601 to: &T::CrossAccountId,602 token: TokenId,603 nesting_budget: &dyn Budget,604 ) -> DispatchResult {605 ensure!(606 collection.limits.transfers_enabled(),607 <CommonError<T>>::TransferNotAllowed608 );609610 let token_data =611 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;612 // TODO: require sender to be token, owner, require admins to go through transfer_from613 ensure!(614 &token_data.owner == from615 || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),616 <CommonError<T>>::NoPermission617 );618619 if collection.permissions.access() == AccessMode::AllowList {620 collection.check_allowlist(from)?;621 collection.check_allowlist(to)?;622 }623 <PalletCommon<T>>::ensure_correct_receiver(to)?;624625 let balance_from = <AccountBalance<T>>::get((collection.id, from))626 .checked_sub(1)627 .ok_or(<CommonError<T>>::TokenValueTooLow)?;628 let balance_to = if from != to {629 let balance_to = <AccountBalance<T>>::get((collection.id, to))630 .checked_add(1)631 .ok_or(ArithmeticError::Overflow)?;632633 ensure!(634 balance_to < collection.limits.account_token_ownership_limit(),635 <CommonError<T>>::AccountTokenLimitExceeded,636 );637638 Some(balance_to)639 } else {640 None641 };642643 <PalletStructure<T>>::nest_if_sent_to_token(644 from.clone(),645 to,646 collection.id,647 token,648 nesting_budget,649 )?;650651 // =========652653 <PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);654655 <TokenData<T>>::insert(656 (collection.id, token),657 ItemData {658 owner: to.clone(),659 ..token_data660 },661 );662663 if let Some(balance_to) = balance_to {664 // from != to665 if balance_from == 0 {666 <AccountBalance<T>>::remove((collection.id, from));667 } else {668 <AccountBalance<T>>::insert((collection.id, from), balance_from);669 }670 <AccountBalance<T>>::insert((collection.id, to), balance_to);671 <Owned<T>>::remove((collection.id, from, token));672 <Owned<T>>::insert((collection.id, to, token), true);673 }674 Self::set_allowance_unchecked(collection, from, token, None, true);675676 <PalletEvm<T>>::deposit_log(677 ERC721Events::Transfer {678 from: *from.as_eth(),679 to: *to.as_eth(),680 token_id: token.into(),681 }682 .to_log(collection_id_to_address(collection.id)),683 );684 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(685 collection.id,686 token,687 from.clone(),688 to.clone(),689 1,690 ));691 Ok(())692 }693694 pub fn create_multiple_items(695 collection: &NonfungibleHandle<T>,696 sender: &T::CrossAccountId,697 data: Vec<CreateItemData<T>>,698 nesting_budget: &dyn Budget,699 ) -> DispatchResult {700 if !collection.is_owner_or_admin(sender) {701 ensure!(702 collection.permissions.mint_mode(),703 <CommonError<T>>::PublicMintingNotAllowed704 );705 collection.check_allowlist(sender)?;706707 for item in data.iter() {708 collection.check_allowlist(&item.owner)?;709 }710 }711712 for data in data.iter() {713 <PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;714 }715716 let first_token = <TokensMinted<T>>::get(collection.id);717 let tokens_minted = first_token718 .checked_add(data.len() as u32)719 .ok_or(ArithmeticError::Overflow)?;720 ensure!(721 tokens_minted <= collection.limits.token_limit(),722 <CommonError<T>>::CollectionTokenLimitExceeded723 );724725 let mut balances = BTreeMap::new();726 for data in &data {727 let balance = balances728 .entry(&data.owner)729 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));730 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;731732 ensure!(733 *balance <= collection.limits.account_token_ownership_limit(),734 <CommonError<T>>::AccountTokenLimitExceeded,735 );736 }737738 for (i, data) in data.iter().enumerate() {739 let token = TokenId(first_token + i as u32 + 1);740741 <PalletStructure<T>>::check_nesting(742 sender.clone(),743 &data.owner,744 collection.id,745 token,746 nesting_budget,747 )?;748 }749750 // =========751752 with_transaction(|| {753 for (i, data) in data.iter().enumerate() {754 let token = first_token + i as u32 + 1;755756 <TokenData<T>>::insert(757 (collection.id, token),758 ItemData {759 // const_data: data.const_data.clone(),760 owner: data.owner.clone(),761 },762 );763764 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(765 &data.owner,766 collection.id,767 TokenId(token),768 );769770 if let Err(e) = Self::set_token_properties(771 collection,772 sender,773 TokenId(token),774 data.properties.clone().into_inner(),775 ) {776 return TransactionOutcome::Rollback(Err(e));777 }778 }779 TransactionOutcome::Commit(Ok(()))780 })?;781782 <TokensMinted<T>>::insert(collection.id, tokens_minted);783 for (account, balance) in balances {784 <AccountBalance<T>>::insert((collection.id, account), balance);785 }786 for (i, data) in data.into_iter().enumerate() {787 let token = first_token + i as u32 + 1;788 <Owned<T>>::insert((collection.id, &data.owner, token), true);789790 <PalletEvm<T>>::deposit_log(791 ERC721Events::Transfer {792 from: H160::default(),793 to: *data.owner.as_eth(),794 token_id: token.into(),795 }796 .to_log(collection_id_to_address(collection.id)),797 );798 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(799 collection.id,800 TokenId(token),801 data.owner.clone(),802 1,803 ));804 }805 Ok(())806 }807808 pub fn set_allowance_unchecked(809 collection: &NonfungibleHandle<T>,810 sender: &T::CrossAccountId,811 token: TokenId,812 spender: Option<&T::CrossAccountId>,813 assume_implicit_eth: bool,814 ) {815 if let Some(spender) = spender {816 let old_spender = <Allowance<T>>::get((collection.id, token));817 <Allowance<T>>::insert((collection.id, token), spender);818 // In ERC721 there is only one possible approved user of token, so we set819 // approved user to spender820 <PalletEvm<T>>::deposit_log(821 ERC721Events::Approval {822 owner: *sender.as_eth(),823 approved: *spender.as_eth(),824 token_id: token.into(),825 }826 .to_log(collection_id_to_address(collection.id)),827 );828 // In Unique chain, any token can have any amount of approved users, so we need to829 // set allowance of old owner to 0, and allowance of new owner to 1830 if old_spender.as_ref() != Some(spender) {831 if let Some(old_owner) = old_spender {832 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(833 collection.id,834 token,835 sender.clone(),836 old_owner,837 0,838 ));839 }840 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(841 collection.id,842 token,843 sender.clone(),844 spender.clone(),845 1,846 ));847 }848 } else {849 let old_spender = <Allowance<T>>::take((collection.id, token));850 if !assume_implicit_eth {851 // In ERC721 there is only one possible approved user of token, so we set852 // approved user to zero address853 <PalletEvm<T>>::deposit_log(854 ERC721Events::Approval {855 owner: *sender.as_eth(),856 approved: H160::default(),857 token_id: token.into(),858 }859 .to_log(collection_id_to_address(collection.id)),860 );861 }862 // In Unique chain, any token can have any amount of approved users, so we need to863 // set allowance of old owner to 0864 if let Some(old_spender) = old_spender {865 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(866 collection.id,867 token,868 sender.clone(),869 old_spender,870 0,871 ));872 }873 }874 }875876 pub fn set_allowance(877 collection: &NonfungibleHandle<T>,878 sender: &T::CrossAccountId,879 token: TokenId,880 spender: Option<&T::CrossAccountId>,881 nesting_budget: &dyn Budget,882 ) -> DispatchResult {883 if collection.permissions.access() == AccessMode::AllowList {884 collection.check_allowlist(sender)?;885 if let Some(spender) = spender {886 collection.check_allowlist(spender)?;887 }888 }889890 if let Some(spender) = spender {891 <PalletCommon<T>>::ensure_correct_receiver(spender)?;892 }893894 let is_owned = <PalletStructure<T>>::check_indirectly_owned(895 sender.clone(),896 collection.id,897 token,898 None,899 nesting_budget900 )?;901902 if !is_owned {903 ensure!(904 collection.ignores_owned_amount(sender),905 <CommonError<T>>::CantApproveMoreThanOwned906 );907 }908909 // =========910911 Self::set_allowance_unchecked(collection, sender, token, spender, false);912 Ok(())913 }914915 fn check_allowed(916 collection: &NonfungibleHandle<T>,917 spender: &T::CrossAccountId,918 from: &T::CrossAccountId,919 token: TokenId,920 nesting_budget: &dyn Budget,921 ) -> DispatchResult {922 if spender.conv_eq(from) {923 return Ok(());924 }925 if collection.permissions.access() == AccessMode::AllowList {926 // `from`, `to` checked in [`transfer`]927 collection.check_allowlist(spender)?;928 }929 if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {930 return Ok(());931 }932 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {933 // TODO: should collection owner be allowed to perform this transfer?934 ensure!(935 <PalletStructure<T>>::check_indirectly_owned(936 spender.clone(),937 source.0,938 source.1,939 None,940 nesting_budget941 )?,942 <CommonError<T>>::ApprovedValueTooLow,943 );944 return Ok(());945 }946 ensure!(947 collection.ignores_allowance(spender),948 <CommonError<T>>::ApprovedValueTooLow949 );950 Ok(())951 }952953 pub fn transfer_from(954 collection: &NonfungibleHandle<T>,955 spender: &T::CrossAccountId,956 from: &T::CrossAccountId,957 to: &T::CrossAccountId,958 token: TokenId,959 nesting_budget: &dyn Budget,960 ) -> DispatchResult {961 Self::check_allowed(collection, spender, from, token, nesting_budget)?;962963 // =========964965 // Allowance is reset in [`transfer`]966 Self::transfer(collection, from, to, token, nesting_budget)967 }968969 pub fn burn_from(970 collection: &NonfungibleHandle<T>,971 spender: &T::CrossAccountId,972 from: &T::CrossAccountId,973 token: TokenId,974 nesting_budget: &dyn Budget,975 ) -> DispatchResult {976 Self::check_allowed(collection, spender, from, token, nesting_budget)?;977978 // =========979980 Self::burn(collection, from, token)981 }982983 pub fn check_nesting(984 handle: &NonfungibleHandle<T>,985 sender: T::CrossAccountId,986 from: (CollectionId, TokenId),987 under: TokenId,988 nesting_budget: &dyn Budget,989 ) -> DispatchResult {990 fn ensure_sender_allowed<T: Config>(991 collection: CollectionId,992 token: TokenId,993 for_nest: (CollectionId, TokenId),994 sender: T::CrossAccountId,995 budget: &dyn Budget,996 ) -> DispatchResult {997 ensure!(998 <PalletStructure<T>>::check_indirectly_owned(999 sender,1000 collection,1001 token,1002 Some(for_nest),1003 budget1004 )?,1005 <CommonError<T>>::OnlyOwnerAllowedToNest,1006 );1007 Ok(())1008 }1009 match handle.permissions.nesting() {1010 NestingRule::Disabled => fail!(<CommonError<T>>::NestingIsDisabled),1011 NestingRule::Owner => {1012 ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1013 }1014 NestingRule::OwnerRestricted(whitelist) => {1015 ensure!(1016 whitelist.contains(&from.0),1017 <CommonError<T>>::SourceCollectionIsNotAllowedToNest1018 );1019 ensure_sender_allowed::<T>(handle.id, under, from, sender, nesting_budget)?1020 }1021 NestingRule::Permissive => {}1022 }1023 Ok(())1024 }10251026 fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1027 <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);1028 }10291030 fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1031 <TokenChildren<T>>::remove((under.0, under.1, to_unnest));1032 }10331034 fn collection_has_tokens(collection_id: CollectionId) -> bool {1035 <TokenData<T>>::iter_prefix((collection_id,))1036 .next()1037 .is_some()1038 }10391040 fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1041 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1042 .next()1043 .is_some()1044 }10451046 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1047 <TokenChildren<T>>::iter_prefix((collection_id, token_id))1048 .map(|((child_collection_id, child_id), _)| TokenChild {1049 collection: child_collection_id,1050 token: child_id,1051 })1052 .collect()1053 }10541055 /// Delegated to `create_multiple_items`1056 pub fn create_item(1057 collection: &NonfungibleHandle<T>,1058 sender: &T::CrossAccountId,1059 data: CreateItemData<T>,1060 nesting_budget: &dyn Budget,1061 ) -> DispatchResult {1062 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1063 }1064}pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -20,7 +20,7 @@
use frame_system::{pallet_prelude::*, ensure_signed};
use sp_runtime::{DispatchError, Permill, traits::StaticLookup};
use sp_std::vec::Vec;
-use up_data_structs::*;
+use up_data_structs::{*, mapping::TokenAddressMapping};
use pallet_common::{
Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,
};
@@ -39,6 +39,8 @@
use RmrkProperty::*;
+const NESTING_BUDGET: u32 = 5;
+
#[frame_support::pallet]
pub mod pallet {
use super::*;
@@ -56,10 +58,13 @@
pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;
#[pallet::storage]
- #[pallet::getter(fn collection_index_map)]
- pub type CollectionIndexMap<T: Config> =
+ pub type UniqueCollectionId<T: Config> =
StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;
+ #[pallet::storage]
+ pub type RmrkInernalCollectionId<T: Config> =
+ StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;
+
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
@@ -151,10 +156,12 @@
.into_inner()
.try_into()
.map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,
+ permissions: Some(CollectionPermissions {
+ nesting: Some(NestingRule::Owner),
+ ..Default::default()
+ }),
..Default::default()
};
-
- <CollectionIndex<T>>::mutate(|n| *n += 1);
let unique_collection_id = Self::init_collection(
T::CrossAccountId::from_sub(sender.clone()),
@@ -167,7 +174,10 @@
)?;
let rmrk_collection_id = <CollectionIndex<T>>::get();
- <CollectionIndexMap<T>>::insert(rmrk_collection_id, unique_collection_id);
+ <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);
+ <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);
+
+ <CollectionIndex<T>>::mutate(|n| *n += 1);
Self::deposit_event(Event::CollectionCreated {
issuer: sender,
@@ -356,6 +366,81 @@
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
#[transactional]
+ pub fn send(
+ origin: OriginFor<T>,
+ rmrk_collection_id: RmrkCollectionId,
+ rmrk_nft_id: RmrkNftId,
+ new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,
+ ) -> DispatchResult {
+ let sender = ensure_signed(origin.clone())?;
+ let cross_sender = T::CrossAccountId::from_sub(sender.clone());
+
+ let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+ let nft_id = rmrk_nft_id.into();
+
+ let token_data = <TokenData<T>>::get((collection_id, nft_id))
+ .ok_or(<Error<T>>::NoAvailableNftId)?;
+
+ let from = token_data.owner;
+
+ let collection = Self::get_typed_nft_collection(
+ collection_id,
+ misc::CollectionType::Regular,
+ )?;
+
+ let budget = budget::Value::new(NESTING_BUDGET);
+
+ let target_owner;
+
+ match new_owner {
+ RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {
+ target_owner = T::CrossAccountId::from_sub(account_id);
+ },
+ RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {
+ let target_collection_id = Self::unique_collection_id(target_collection_id)?;
+
+ target_owner = T::CrossTokenAddressMapping::token_to_address(
+ target_collection_id,
+ target_nft_id.into(),
+ );
+
+ let spender = <PalletStructure<T>>::get_indirect_owner(
+ target_collection_id,
+ target_nft_id.into(),
+ Some((collection_id, nft_id)),
+ &budget,
+ )?;
+
+ let is_approval_required = cross_sender != spender;
+
+ if is_approval_required {
+ <PalletNft<T>>::set_allowance(
+ &collection,
+ &cross_sender,
+ nft_id,
+ Some(&spender),
+ &budget
+ ).map_err(Self::map_common_err_to_proxy)?;
+
+ return Ok(());
+ }
+ }
+ }
+
+ <PalletNft<T>>::transfer_from(
+ &collection,
+ &cross_sender,
+ &from,
+ &target_owner,
+ nft_id,
+ &budget
+ ).map_err(Self::map_common_err_to_proxy)?;
+
+ Ok(())
+ }
+
+ #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
+ #[transactional]
pub fn set_property(
origin: OriginFor<T>,
#[pallet::compact] rmrk_collection_id: RmrkCollectionId,
@@ -367,12 +452,13 @@
let sender = T::CrossAccountId::from_sub(sender);
let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
+ let budget = budget::Value::new(NESTING_BUDGET);
match maybe_nft_id {
Some(nft_id) => {
let token_id: TokenId = nft_id.into();
- Self::ensure_nft_owner(collection_id, token_id, &sender)?;
+ Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;
Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;
<PalletNft<T>>::set_scoped_token_property(
@@ -607,7 +693,7 @@
owner: owner.clone(),
};
- let budget = budget::Value::new(2);
+ let budget = budget::Value::new(NESTING_BUDGET);
<PalletNft<T>>::create_item(collection, sender, data, &budget)?;
@@ -651,14 +737,8 @@
//ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);
let sender = T::CrossAccountId::from_sub(sender);
- let budget = budget::Value::new(10);
- let pending = !<PalletStructure<T>>::check_indirectly_owned(
- sender.clone(),
- collection_id,
- token_id,
- None,
- &budget,
- )?;
+ let budget = budget::Value::new(NESTING_BUDGET);
+ let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();
let resource_collection_id: CollectionId =
Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;
@@ -757,7 +837,14 @@
pub fn unique_collection_id(
rmrk_collection_id: RmrkCollectionId,
) -> Result<CollectionId, DispatchError> {
- <CollectionIndexMap<T>>::try_get(rmrk_collection_id)
+ <UniqueCollectionId<T>>::try_get(rmrk_collection_id)
+ .map_err(|_| <Error<T>>::CollectionUnknown.into())
+ }
+
+ pub fn rmrk_collection_id(
+ unique_collection_id: CollectionId
+ ) -> Result<RmrkCollectionId, DispatchError> {
+ <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)
.map_err(|_| <Error<T>>::CollectionUnknown.into())
}
@@ -873,12 +960,18 @@
collection_id: CollectionId,
token_id: TokenId,
possible_owner: &T::CrossAccountId,
+ nesting_budget: &dyn budget::Budget
) -> DispatchResult {
- let token_data =
- <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;
+ let is_owned = <PalletStructure<T>>::check_indirectly_owned(
+ possible_owner.clone(),
+ collection_id,
+ token_id,
+ None,
+ nesting_budget,
+ )?;
ensure!(
- token_data.owner == *possible_owner,
+ is_owned,
<Error<T>>::NoPermission
);
@@ -965,7 +1058,8 @@
NoPermission => NoPermission,
CollectionTokenLimitExceeded => CollectionFullOrLocked,
PublicMintingNotAllowed => NoPermission,
- TokenNotFound => NoAvailableNftId
+ TokenNotFound => NoAvailableNftId,
+ ApprovedValueTooLow => NoPermission
}
}
}
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -254,6 +254,7 @@
spender: T::CrossAccountId,
token: TokenId,
amount: u128,
+ _nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
with_weight(
<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -149,19 +149,12 @@
})
}
- /// Check if token indirectly owned by specified user
- pub fn check_indirectly_owned(
- user: T::CrossAccountId,
+ pub fn get_indirect_owner(
collection: CollectionId,
token: TokenId,
for_nest: Option<(CollectionId, TokenId)>,
budget: &dyn Budget,
- ) -> Result<bool, DispatchError> {
- let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
- Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
- None => user,
- };
-
+ ) -> Result<T::CrossAccountId, DispatchError> {
// Tried to nest token in itself
if Some((collection, token)) == for_nest {
return Err(<Error<T>>::OuroborosDetected.into());
@@ -173,10 +166,8 @@
Parent::Token(collection, token) if Some((collection, token)) == for_nest => {
return Err(<Error<T>>::OuroborosDetected.into())
}
- // Found needed parent, token is indirecty owned
- Parent::User(user) if user == target_parent => return Ok(true),
// Token is owned by other user
- Parent::User(_) => return Ok(false),
+ Parent::User(user) => return Ok(user),
Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
// Continue parent chain
Parent::Token(_, _) => {}
@@ -199,6 +190,27 @@
dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
}
+ /// Check if token indirectly owned by specified user
+ pub fn check_indirectly_owned(
+ user: T::CrossAccountId,
+ collection: CollectionId,
+ token: TokenId,
+ for_nest: Option<(CollectionId, TokenId)>,
+ budget: &dyn Budget,
+ ) -> Result<bool, DispatchError> {
+ let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
+ Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+ None => user,
+ };
+
+ Self::get_indirect_owner(
+ collection,
+ token,
+ for_nest,
+ budget
+ ).map(|indirect_owner| indirect_owner == target_parent)
+ }
+
pub fn check_nesting(
from: T::CrossAccountId,
under: &T::CrossAccountId,
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -815,8 +815,9 @@
#[transactional]
pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
+ let budget = budget::Value::new(NESTING_BUDGET);
- dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
+ dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount, &budget))
}
/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -147,7 +147,11 @@
use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
+ };
+
let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
Ok(c) => c,
Err(_) => return Ok(None),
@@ -169,7 +173,10 @@
use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
+ };
let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
Ok(c) => c,
Err(_) => return Ok(None),
@@ -180,7 +187,11 @@
let owner = match collection.token_owner(nft_id) {
Some(owner) => match <Runtime as pallet_common::Config>::CrossTokenAddressMapping::address_to_token(&owner) {
- Some((col, tok)) => RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(col.0, tok.0),
+ Some((col, tok)) => {
+ let rmrk_collection = RmrkCore::rmrk_collection_id(col)?;
+
+ RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(rmrk_collection, tok.0)
+ }
None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone())
},
None => return Ok(None)
@@ -202,11 +213,11 @@
use pallet_common::CommonCollectionOperations;
let cross_account_id = CrossAccountId::from_sub(account_id);
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
- let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Regular) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
};
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
Ok(
collection.account_tokens(cross_account_id)
@@ -217,28 +228,35 @@
}
fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
let nft_id = TokenId(nft_id);
if !RmrkCore::nft_exists(collection_id, nft_id) { return Ok(Vec::new()); }
Ok(
pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
- .filter_map(|(child_id, is_child)|
- match is_child {
- true => Some(RmrkNftChild {
- collection_id: child_id.0.0,
- nft_id: child_id.1.0,
- }),
- false => None,
- }
- ).collect()
+ .filter_map(|((child_collection, child_token), _)| {
+ let rmrk_child_collection = RmrkCore::rmrk_collection_id(
+ child_collection
+ ).ok()?;
+
+ Some(RmrkNftChild {
+ collection_id: rmrk_child_collection,
+ nft_id: child_token.0,
+ })
+ }).collect()
)
}
fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
use pallet_proxy_rmrk_core::misc::CollectionType;
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() {
return Ok(Vec::new());
}
@@ -259,7 +277,10 @@
fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
use pallet_proxy_rmrk_core::misc::NftType;
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
let token_id = TokenId(nft_id);
if RmrkCore::ensure_nft_type(collection_id, token_id, NftType::Regular).is_err() {
@@ -283,7 +304,10 @@
use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType, ResourceType}};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
let nft_id = TokenId(nft_id);
@@ -332,7 +356,10 @@
fn nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceId>, DispatchError> {
use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
- let collection_id = RmrkCore::unique_collection_id(collection_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(collection_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
+ };
if RmrkCore::ensure_collection_type(collection_id, CollectionType::Regular).is_err() { return Ok(Vec::new()); }
let nft_id = TokenId(nft_id);
@@ -360,7 +387,10 @@
RmrkProperty, misc::{CollectionType},
};
- let collection_id = RmrkCore::unique_collection_id(base_id)?;
+ let collection_id = match RmrkCore::unique_collection_id(base_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
+ };
let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
Ok(c) => c,
Err(_) => return Ok(None),
@@ -377,12 +407,11 @@
use pallet_proxy_rmrk_core::{RmrkProperty, misc::{CollectionType, NftType}};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(base_id)?;
- let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
+ let collection_id = match RmrkCore::unique_collection_id(base_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
};
-
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() { return Ok(Vec::new()); }
let parts = collection.collection_tokens()
.into_iter()
@@ -413,11 +442,13 @@
use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(base_id)?;
- let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(Vec::new()),
+ let collection_id = match RmrkCore::unique_collection_id(base_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(Vec::new())
};
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
+ return Ok(Vec::new());
+ }
let theme_names = collection.collection_tokens()
@@ -444,11 +475,13 @@
};
use pallet_common::CommonCollectionOperations;
- let collection_id = RmrkCore::unique_collection_id(base_id)?;
- let collection = match RmrkCore::get_typed_nft_collection(collection_id, CollectionType::Base) {
- Ok(c) => c,
- Err(_) => return Ok(None),
+ let collection_id = match RmrkCore::unique_collection_id(base_id) {
+ Ok(id) => id,
+ Err(_) => return Ok(None)
};
+ if RmrkCore::ensure_collection_type(collection_id, CollectionType::Base).is_err() {
+ return Ok(None);
+ }
let theme_info = collection.collection_tokens()
.into_iter()