difftreelog
fix revert set_allowance
in: master
8 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1298,7 +1298,6 @@
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,7 +226,6 @@
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,15 +308,14 @@
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), nesting_budget)
+ <Pallet<T>>::set_allowance(self, &sender, token, Some(&spender))
} else {
- <Pallet<T>>::set_allowance(self, &sender, token, None, nesting_budget)
+ <Pallet<T>>::set_allowance(self, &sender, token, None)
},
<CommonWeights<T>>::approve(),
)
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -269,11 +269,8 @@
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), &budget)
+ <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
.map_err(dispatch_to_evm::<T>)?;
Ok(())
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -878,7 +878,6 @@
sender: &T::CrossAccountId,
token: TokenId,
spender: Option<&T::CrossAccountId>,
- nesting_budget: &dyn Budget,
) -> DispatchResult {
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
@@ -890,16 +889,10 @@
if let Some(spender) = spender {
<PalletCommon<T>>::ensure_correct_receiver(spender)?;
}
-
- let is_owned = <PalletStructure<T>>::check_indirectly_owned(
- sender.clone(),
- collection.id,
- token,
- None,
- nesting_budget
- )?;
- if !is_owned {
+ let token_data =
+ <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+ if &token_data.owner != sender {
ensure!(
collection.ignores_owned_amount(sender),
<CommonError<T>>::CantApproveMoreThanOwned
@@ -926,9 +919,6 @@
// `from`, `to` checked in [`transfer`]
collection.check_allowlist(spender)?;
}
- if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
- return Ok(());
- }
if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {
// TODO: should collection owner be allowed to perform this transfer?
ensure!(
@@ -943,6 +933,9 @@
);
return Ok(());
}
+ if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
+ return Ok(());
+ }
ensure!(
collection.ignores_allowance(spender),
<CommonError<T>>::ApprovedValueTooLow
pallets/proxy-rmrk-core/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 frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 CollectionFullOrLocked,132 ResourceDoesntExist,133 }134135 #[pallet::call]136 impl<T: Config> Pallet<T> {137 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138 #[transactional]139 pub fn create_collection(140 origin: OriginFor<T>,141 metadata: RmrkString,142 max: Option<u32>,143 symbol: RmrkCollectionSymbol,144 ) -> DispatchResult {145 let sender = ensure_signed(origin)?;146147 let limits = CollectionLimits {148 owner_can_transfer: Some(false),149 token_limit: max,150 ..Default::default()151 };152153 let data = CreateCollectionData {154 limits: Some(limits),155 token_prefix: symbol156 .into_inner()157 .try_into()158 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159 permissions: Some(CollectionPermissions {160 nesting: Some(NestingRule::Owner),161 ..Default::default()162 }),163 ..Default::default()164 };165166 let unique_collection_id = Self::init_collection(167 T::CrossAccountId::from_sub(sender.clone()),168 data,169 [170 Self::rmrk_property(Metadata, &metadata)?,171 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172 ]173 .into_iter(),174 )?;175 let rmrk_collection_id = <CollectionIndex<T>>::get();176177 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180 <CollectionIndex<T>>::mutate(|n| *n += 1);181182 Self::deposit_event(Event::CollectionCreated {183 issuer: sender,184 collection_id: rmrk_collection_id,185 });186187 Ok(())188 }189190 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191 #[transactional]192 pub fn destroy_collection(193 origin: OriginFor<T>,194 collection_id: RmrkCollectionId,195 ) -> DispatchResult {196 let sender = ensure_signed(origin)?;197 let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199 let collection = Self::get_typed_nft_collection(200 Self::unique_collection_id(collection_id)?,201 misc::CollectionType::Regular,202 )?;203204 ensure!(205 collection.total_supply() == 0,206 <Error<T>>::CollectionNotEmpty207 );208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_common_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 ) -> DispatchResult {287 let sender = ensure_signed(origin)?;288 let sender = T::CrossAccountId::from_sub(sender);289 let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292 recipient: recipient.unwrap_or_else(|| owner.clone()),293 amount,294 });295296 let collection = Self::get_typed_nft_collection(297 Self::unique_collection_id(collection_id)?,298 misc::CollectionType::Regular,299 )?;300301 let nft_id = Self::create_nft(302 &sender,303 &cross_owner,304 &collection,305 [306 Self::rmrk_property(TokenType, &NftType::Regular)?,307 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308 Self::rmrk_property(Metadata, &metadata)?,309 Self::rmrk_property(Equipped, &false)?,310 Self::rmrk_property(311 ResourceCollection,312 &Self::init_collection(313 sender.clone(),314 CreateCollectionData {315 ..Default::default()316 },317 [Self::rmrk_property(318 CollectionType,319 &misc::CollectionType::Resource,320 )?]321 .into_iter(),322 )?,323 )?, // todo possibly add limits to the collection if rmrk warrants them324 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325 ]326 .into_iter(),327 )328 .map_err(|err| match err {329 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330 err => Self::map_common_err_to_proxy(err),331 })?;332333 Self::deposit_event(Event::NftMinted {334 owner,335 collection_id,336 nft_id: nft_id.0,337 });338339 Ok(())340 }341342 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343 #[transactional]344 pub fn burn_nft(345 origin: OriginFor<T>,346 collection_id: RmrkCollectionId,347 nft_id: RmrkNftId,348 ) -> DispatchResult {349 let sender = ensure_signed(origin)?;350 let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352 Self::destroy_nft(353 cross_sender,354 Self::unique_collection_id(collection_id)?,355 misc::CollectionType::Regular,356 nft_id.into(),357 )?;358359 Self::deposit_event(Event::NFTBurned {360 owner: sender,361 nft_id,362 });363364 Ok(())365 }366367 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368 #[transactional]369 pub fn send(370 origin: OriginFor<T>,371 rmrk_collection_id: RmrkCollectionId,372 rmrk_nft_id: RmrkNftId,373 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374 ) -> DispatchResult {375 let sender = ensure_signed(origin.clone())?;376 let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379 let nft_id = rmrk_nft_id.into();380381 let token_data = <TokenData<T>>::get((collection_id, nft_id))382 .ok_or(<Error<T>>::NoAvailableNftId)?;383384 let from = token_data.owner;385386 let collection = Self::get_typed_nft_collection(387 collection_id,388 misc::CollectionType::Regular,389 )?;390391 let budget = budget::Value::new(NESTING_BUDGET);392393 let target_owner;394395 match new_owner {396 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397 target_owner = T::CrossAccountId::from_sub(account_id);398 },399 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400 let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402 target_owner = T::CrossTokenAddressMapping::token_to_address(403 target_collection_id,404 target_nft_id.into(),405 );406407 let spender = <PalletStructure<T>>::get_indirect_owner(408 target_collection_id,409 target_nft_id.into(),410 Some((collection_id, nft_id)),411 &budget,412 )?;413414 let is_approval_required = cross_sender != spender;415416 if is_approval_required {417 <PalletNft<T>>::set_allowance(418 &collection,419 &cross_sender,420 nft_id,421 Some(&spender),422 &budget423 ).map_err(Self::map_common_err_to_proxy)?;424425 return Ok(());426 }427 }428 }429430 <PalletNft<T>>::transfer_from(431 &collection,432 &cross_sender,433 &from,434 &target_owner,435 nft_id,436 &budget437 ).map_err(Self::map_common_err_to_proxy)?;438439 Ok(())440 }441442 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]443 #[transactional]444 pub fn set_property(445 origin: OriginFor<T>,446 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,447 maybe_nft_id: Option<RmrkNftId>,448 key: RmrkKeyString,449 value: RmrkValueString,450 ) -> DispatchResult {451 let sender = ensure_signed(origin)?;452 let sender = T::CrossAccountId::from_sub(sender);453454 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;455 let budget = budget::Value::new(NESTING_BUDGET);456457 match maybe_nft_id {458 Some(nft_id) => {459 let token_id: TokenId = nft_id.into();460461 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;462 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;463464 <PalletNft<T>>::set_scoped_token_property(465 collection_id,466 token_id,467 PropertyScope::Rmrk,468 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,469 )?;470 }471 None => {472 let collection = Self::get_typed_nft_collection(473 collection_id,474 misc::CollectionType::Regular,475 )?;476477 Self::check_collection_owner(&collection, &sender)?;478479 <PalletCommon<T>>::set_scoped_collection_property(480 collection_id,481 PropertyScope::Rmrk,482 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,483 )?;484 }485 }486487 Self::deposit_event(Event::PropertySet {488 collection_id: rmrk_collection_id,489 maybe_nft_id,490 key,491 value,492 });493494 Ok(())495 }496497 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]498 #[transactional]499 pub fn add_basic_resource(500 origin: OriginFor<T>,501 collection_id: RmrkCollectionId,502 nft_id: RmrkNftId,503 resource: RmrkBasicResource,504 ) -> DispatchResult {505 let sender = ensure_signed(origin.clone())?;506507 let resource_id = Self::resource_add(508 sender,509 Self::unique_collection_id(collection_id)?,510 nft_id.into(),511 [512 Self::rmrk_property(TokenType, &NftType::Resource)?,513 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,514 Self::rmrk_property(Src, &resource.src)?,515 Self::rmrk_property(Metadata, &resource.metadata)?,516 Self::rmrk_property(License, &resource.license)?,517 Self::rmrk_property(Thumb, &resource.thumb)?,518 ]519 .into_iter(),520 )?;521522 Self::deposit_event(Event::ResourceAdded {523 nft_id,524 resource_id,525 });526 Ok(())527 }528529 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]530 #[transactional]531 pub fn add_composable_resource(532 origin: OriginFor<T>,533 collection_id: RmrkCollectionId,534 nft_id: RmrkNftId,535 _resource_id: RmrkBoundedResource,536 resource: RmrkComposableResource,537 ) -> DispatchResult {538 let sender = ensure_signed(origin.clone())?;539540 let resource_id = Self::resource_add(541 sender,542 Self::unique_collection_id(collection_id)?,543 nft_id.into(),544 [545 Self::rmrk_property(TokenType, &NftType::Resource)?,546 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,547 Self::rmrk_property(Parts, &resource.parts)?,548 Self::rmrk_property(Base, &resource.base)?,549 Self::rmrk_property(Src, &resource.src)?,550 Self::rmrk_property(Metadata, &resource.metadata)?,551 Self::rmrk_property(License, &resource.license)?,552 Self::rmrk_property(Thumb, &resource.thumb)?,553 ]554 .into_iter(),555 )?;556557 Self::deposit_event(Event::ResourceAdded {558 nft_id,559 resource_id,560 });561 Ok(())562 }563564 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]565 #[transactional]566 pub fn add_slot_resource(567 origin: OriginFor<T>,568 collection_id: RmrkCollectionId,569 nft_id: RmrkNftId,570 resource: RmrkSlotResource,571 ) -> DispatchResult {572 let sender = ensure_signed(origin.clone())?;573574 let resource_id = Self::resource_add(575 sender,576 Self::unique_collection_id(collection_id)?,577 nft_id.into(),578 [579 Self::rmrk_property(TokenType, &NftType::Resource)?,580 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,581 Self::rmrk_property(Base, &resource.base)?,582 Self::rmrk_property(Src, &resource.src)?,583 Self::rmrk_property(Metadata, &resource.metadata)?,584 Self::rmrk_property(Slot, &resource.slot)?,585 Self::rmrk_property(License, &resource.license)?,586 Self::rmrk_property(Thumb, &resource.thumb)?,587 ]588 .into_iter(),589 )?;590591 Self::deposit_event(Event::ResourceAdded {592 nft_id,593 resource_id,594 });595 Ok(())596 }597598 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]599 #[transactional]600 pub fn remove_resource(601 origin: OriginFor<T>,602 collection_id: RmrkCollectionId,603 nft_id: RmrkNftId,604 resource_id: RmrkResourceId,605 ) -> DispatchResult {606 let sender = ensure_signed(origin.clone())?;607608 Self::resource_remove(609 sender,610 Self::unique_collection_id(collection_id)?,611 nft_id.into(),612 resource_id.into(),613 )?;614615 Self::deposit_event(Event::ResourceRemoval {616 nft_id,617 resource_id,618 });619 Ok(())620 }621 }622}623624impl<T: Config> Pallet<T> {625 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {626 let key = rmrk_key.to_key::<T>()?;627628 let scoped_key = PropertyScope::Rmrk629 .apply(key)630 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;631632 Ok(scoped_key)633 }634635 // todo think about renaming these636 pub fn rmrk_property<E: Encode>(637 rmrk_key: RmrkProperty,638 value: &E,639 ) -> Result<Property, DispatchError> {640 let key = rmrk_key.to_key::<T>()?;641642 let value = value643 .encode()644 .try_into()645 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;646647 let property = Property { key, value };648649 Ok(property)650 }651652 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {653 vec.decode()654 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())655 }656657 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>658 where659 BoundedVec<u8, S>: TryFrom<Vec<u8>>,660 {661 vec.rebind()662 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())663 }664665 fn init_collection(666 sender: T::CrossAccountId,667 data: CreateCollectionData<T::AccountId>,668 properties: impl Iterator<Item = Property>,669 ) -> Result<CollectionId, DispatchError> {670 let collection_id = <PalletNft<T>>::init_collection(sender, data);671672 if let Err(DispatchError::Arithmetic(_)) = &collection_id {673 return Err(<Error<T>>::NoAvailableCollectionId.into());674 }675676 <PalletCommon<T>>::set_scoped_collection_properties(677 collection_id?,678 PropertyScope::Rmrk,679 properties,680 )?;681682 collection_id683 }684685 pub fn create_nft(686 sender: &T::CrossAccountId,687 owner: &T::CrossAccountId,688 collection: &NonfungibleHandle<T>,689 properties: impl Iterator<Item = Property>,690 ) -> Result<TokenId, DispatchError> {691 let data = CreateNftExData {692 properties: BoundedVec::default(),693 owner: owner.clone(),694 };695696 let budget = budget::Value::new(NESTING_BUDGET);697698 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;699700 let nft_id = <PalletNft<T>>::current_token_id(collection.id);701702 <PalletNft<T>>::set_scoped_token_properties(703 collection.id,704 nft_id,705 PropertyScope::Rmrk,706 properties,707 )?;708709 Ok(nft_id)710 }711712 fn destroy_nft(713 sender: T::CrossAccountId,714 collection_id: CollectionId,715 collection_type: misc::CollectionType,716 token_id: TokenId,717 ) -> DispatchResult {718 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;719720 <PalletNft<T>>::burn(&collection, &sender, token_id)721 .map_err(Self::map_common_err_to_proxy)?;722723 Ok(())724 }725726 fn resource_add(727 sender: T::AccountId,728 collection_id: CollectionId,729 token_id: TokenId,730 resource_properties: impl Iterator<Item = Property>,731 ) -> Result<RmrkResourceId, DispatchError> {732 let collection =733 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;734 ensure!(collection.owner == sender, Error::<T>::NoPermission);735736 // Check NFT lock status // todo depends on market, maybe later737 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);738739 let sender = T::CrossAccountId::from_sub(sender);740 let budget = budget::Value::new(NESTING_BUDGET);741 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();742743 let resource_collection_id: CollectionId =744 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;745 let resource_collection =746 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;747748 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them749750 let resource_id = Self::create_nft(751 &sender, // todo owner of the nft?752 &sender,753 &resource_collection,754 resource_properties.chain(755 [756 Self::rmrk_property(PendingResourceAccept, &pending)?,757 Self::rmrk_property(PendingResourceRemoval, &false)?,758 ]759 .into_iter(),760 ),761 )762 .map_err(|err| match err {763 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),764 err => Self::map_common_err_to_proxy(err),765 })?;766767 Ok(resource_id.0)768 }769770 fn resource_remove(771 sender: T::AccountId,772 collection_id: CollectionId,773 nft_id: TokenId,774 resource_id: TokenId,775 ) -> DispatchResult {776 let collection =777 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;778 ensure!(collection.owner == sender, Error::<T>::NoPermission);779780 let resource_collection_id: CollectionId =781 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;782 let resource_collection =783 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;784 ensure!(785 <PalletNft<T>>::token_exists(&resource_collection, resource_id),786 Error::<T>::ResourceDoesntExist787 );788789 let budget = up_data_structs::budget::Value::new(10);790 let topmost_owner =791 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;792793 let sender = T::CrossAccountId::from_sub(sender);794 if topmost_owner == sender {795 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)796 .map_err(Self::map_common_err_to_proxy)?;797 } else {798 <PalletNft<T>>::set_scoped_token_property(799 resource_collection_id,800 resource_id,801 PropertyScope::Rmrk,802 Self::rmrk_property(PendingResourceRemoval, &true)?,803 )?;804 }805806 Ok(())807 }808809 fn change_collection_owner(810 collection_id: CollectionId,811 collection_type: misc::CollectionType,812 sender: T::AccountId,813 new_owner: T::AccountId,814 ) -> DispatchResult {815 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;816 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;817818 let mut collection = collection.into_inner();819820 collection.owner = new_owner;821 collection.save()822 }823824 fn check_collection_owner(825 collection: &NonfungibleHandle<T>,826 account: &T::CrossAccountId,827 ) -> DispatchResult {828 collection829 .check_is_owner(account)830 .map_err(Self::map_common_err_to_proxy)831 }832833 pub fn last_collection_idx() -> RmrkCollectionId {834 <CollectionIndex<T>>::get()835 }836837 pub fn unique_collection_id(838 rmrk_collection_id: RmrkCollectionId,839 ) -> Result<CollectionId, DispatchError> {840 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)841 .map_err(|_| <Error<T>>::CollectionUnknown.into())842 }843844 pub fn rmrk_collection_id(845 unique_collection_id: CollectionId846 ) -> Result<RmrkCollectionId, DispatchError> {847 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)848 .map_err(|_| <Error<T>>::CollectionUnknown.into())849 }850851 pub fn get_nft_collection(852 collection_id: CollectionId,853 ) -> Result<NonfungibleHandle<T>, DispatchError> {854 let collection = <CollectionHandle<T>>::try_get(collection_id)855 .map_err(|_| <Error<T>>::CollectionUnknown)?;856857 match collection.mode {858 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),859 _ => Err(<Error<T>>::CollectionUnknown.into()),860 }861 }862863 pub fn collection_exists(collection_id: CollectionId) -> bool {864 <CollectionHandle<T>>::try_get(collection_id).is_ok()865 }866867 pub fn get_collection_property(868 collection_id: CollectionId,869 key: RmrkProperty,870 ) -> Result<PropertyValue, DispatchError> {871 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)872 .get(&Self::rmrk_property_key(key)?)873 .ok_or(<Error<T>>::CollectionUnknown)?874 .clone();875876 Ok(collection_property)877 }878879 pub fn get_collection_property_decoded<V: Decode>(880 collection_id: CollectionId,881 key: RmrkProperty,882 ) -> Result<V, DispatchError> {883 Self::decode_property(Self::get_collection_property(collection_id, key)?)884 }885886 pub fn get_collection_type(887 collection_id: CollectionId,888 ) -> Result<misc::CollectionType, DispatchError> {889 Self::get_collection_property_decoded(collection_id, CollectionType)890 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())891 }892893 pub fn ensure_collection_type(894 collection_id: CollectionId,895 collection_type: misc::CollectionType,896 ) -> DispatchResult {897 let actual_type = Self::get_collection_type(collection_id)?;898 ensure!(899 actual_type == collection_type,900 <CommonError<T>>::NoPermission901 );902903 Ok(())904 }905906 pub fn get_typed_nft_collection(907 collection_id: CollectionId,908 collection_type: misc::CollectionType,909 ) -> Result<NonfungibleHandle<T>, DispatchError> {910 Self::ensure_collection_type(collection_id, collection_type)?;911912 Self::get_nft_collection(collection_id)913 }914915 pub fn get_typed_nft_collection_mapped(916 rmrk_collection_id: RmrkCollectionId,917 collection_type: misc::CollectionType,918 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {919 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;920921 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;922923 Ok((collection, unique_collection_id))924 }925926 pub fn get_nft_property(927 collection_id: CollectionId,928 nft_id: TokenId,929 key: RmrkProperty,930 ) -> Result<PropertyValue, DispatchError> {931 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))932 .get(&Self::rmrk_property_key(key)?)933 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?934 .clone();935936 Ok(nft_property)937 }938939 pub fn get_nft_property_decoded<V: Decode>(940 collection_id: CollectionId,941 nft_id: TokenId,942 key: RmrkProperty,943 ) -> Result<V, DispatchError> {944 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)945 }946947 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {948 <TokenData<T>>::contains_key((collection_id, nft_id))949 }950951 pub fn get_nft_type(952 collection_id: CollectionId,953 token_id: TokenId,954 ) -> Result<NftType, DispatchError> {955 Self::get_nft_property_decoded(collection_id, token_id, TokenType)956 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())957 }958959 pub fn ensure_nft_type(960 collection_id: CollectionId,961 token_id: TokenId,962 nft_type: NftType,963 ) -> DispatchResult {964 let actual_type = Self::get_nft_type(collection_id, token_id)?;965 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);966967 Ok(())968 }969970 pub fn ensure_nft_owner(971 collection_id: CollectionId,972 token_id: TokenId,973 possible_owner: &T::CrossAccountId,974 nesting_budget: &dyn budget::Budget975 ) -> DispatchResult {976 let is_owned = <PalletStructure<T>>::check_indirectly_owned(977 possible_owner.clone(),978 collection_id,979 token_id,980 None,981 nesting_budget,982 )?;983984 ensure!(985 is_owned,986 <Error<T>>::NoPermission987 );988989 Ok(())990 }991992 pub fn filter_user_properties<Key, Value, R, Mapper>(993 collection_id: CollectionId,994 token_id: Option<TokenId>,995 filter_keys: Option<Vec<RmrkPropertyKey>>,996 mapper: Mapper,997 ) -> Result<Vec<R>, DispatchError>998 where999 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1000 Value: Decode + Default,1001 Mapper: Fn(Key, Value) -> R,1002 {1003 filter_keys1004 .map(|keys| {1005 let properties = keys1006 .into_iter()1007 .filter_map(|key| {1008 let key: Key = key.try_into().ok()?;10091010 let value = match token_id {1011 Some(token_id) => Self::get_nft_property_decoded(1012 collection_id,1013 token_id,1014 UserProperty(key.as_ref()),1015 ),1016 None => Self::get_collection_property_decoded(1017 collection_id,1018 UserProperty(key.as_ref()),1019 ),1020 }1021 .ok()?;10221023 Some(mapper(key, value))1024 })1025 .collect();10261027 Ok(properties)1028 })1029 .unwrap_or_else(|| {1030 let properties =1031 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10321033 Ok(properties)1034 })1035 }10361037 pub fn iterate_user_properties<Key, Value, R, Mapper>(1038 collection_id: CollectionId,1039 token_id: Option<TokenId>,1040 mapper: Mapper,1041 ) -> Result<impl Iterator<Item = R>, DispatchError>1042 where1043 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1044 Value: Decode + Default,1045 Mapper: Fn(Key, Value) -> R,1046 {1047 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10481049 let properties = match token_id {1050 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1051 None => <PalletCommon<T>>::collection_properties(collection_id),1052 };10531054 let properties = properties.into_iter().filter_map(move |(key, value)| {1055 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10561057 let key: Key = key.to_vec().try_into().ok()?;1058 let value: Value = value.decode().ok()?;10591060 Some(mapper(key, value))1061 });10621063 Ok(properties)1064 }10651066 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1067 map_common_err_to_proxy! {1068 match err {1069 NoPermission => NoPermission,1070 CollectionTokenLimitExceeded => CollectionFullOrLocked,1071 PublicMintingNotAllowed => NoPermission,1072 TokenNotFound => NoAvailableNftId,1073 ApprovedValueTooLow => NoPermission1074 }1075 }1076 }1077}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 frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::Pallet as PalletStructure;29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 CollectionFullOrLocked,132 ResourceDoesntExist,133 }134135 #[pallet::call]136 impl<T: Config> Pallet<T> {137 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]138 #[transactional]139 pub fn create_collection(140 origin: OriginFor<T>,141 metadata: RmrkString,142 max: Option<u32>,143 symbol: RmrkCollectionSymbol,144 ) -> DispatchResult {145 let sender = ensure_signed(origin)?;146147 let limits = CollectionLimits {148 owner_can_transfer: Some(false),149 token_limit: max,150 ..Default::default()151 };152153 let data = CreateCollectionData {154 limits: Some(limits),155 token_prefix: symbol156 .into_inner()157 .try_into()158 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,159 permissions: Some(CollectionPermissions {160 nesting: Some(NestingRule::Owner),161 ..Default::default()162 }),163 ..Default::default()164 };165166 let unique_collection_id = Self::init_collection(167 T::CrossAccountId::from_sub(sender.clone()),168 data,169 [170 Self::rmrk_property(Metadata, &metadata)?,171 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,172 ]173 .into_iter(),174 )?;175 let rmrk_collection_id = <CollectionIndex<T>>::get();176177 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);178 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);179180 <CollectionIndex<T>>::mutate(|n| *n += 1);181182 Self::deposit_event(Event::CollectionCreated {183 issuer: sender,184 collection_id: rmrk_collection_id,185 });186187 Ok(())188 }189190 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]191 #[transactional]192 pub fn destroy_collection(193 origin: OriginFor<T>,194 collection_id: RmrkCollectionId,195 ) -> DispatchResult {196 let sender = ensure_signed(origin)?;197 let cross_sender = T::CrossAccountId::from_sub(sender.clone());198199 let collection = Self::get_typed_nft_collection(200 Self::unique_collection_id(collection_id)?,201 misc::CollectionType::Regular,202 )?;203204 ensure!(205 collection.total_supply() == 0,206 <Error<T>>::CollectionNotEmpty207 );208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_common_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 ) -> DispatchResult {287 let sender = ensure_signed(origin)?;288 let sender = T::CrossAccountId::from_sub(sender);289 let cross_owner = T::CrossAccountId::from_sub(owner.clone());290291 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {292 recipient: recipient.unwrap_or_else(|| owner.clone()),293 amount,294 });295296 let collection = Self::get_typed_nft_collection(297 Self::unique_collection_id(collection_id)?,298 misc::CollectionType::Regular,299 )?;300301 let nft_id = Self::create_nft(302 &sender,303 &cross_owner,304 &collection,305 [306 Self::rmrk_property(TokenType, &NftType::Regular)?,307 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,308 Self::rmrk_property(Metadata, &metadata)?,309 Self::rmrk_property(Equipped, &false)?,310 Self::rmrk_property(311 ResourceCollection,312 &Self::init_collection(313 sender.clone(),314 CreateCollectionData {315 ..Default::default()316 },317 [Self::rmrk_property(318 CollectionType,319 &misc::CollectionType::Resource,320 )?]321 .into_iter(),322 )?,323 )?, // todo possibly add limits to the collection if rmrk warrants them324 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,325 ]326 .into_iter(),327 )328 .map_err(|err| match err {329 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),330 err => Self::map_common_err_to_proxy(err),331 })?;332333 Self::deposit_event(Event::NftMinted {334 owner,335 collection_id,336 nft_id: nft_id.0,337 });338339 Ok(())340 }341342 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]343 #[transactional]344 pub fn burn_nft(345 origin: OriginFor<T>,346 collection_id: RmrkCollectionId,347 nft_id: RmrkNftId,348 ) -> DispatchResult {349 let sender = ensure_signed(origin)?;350 let cross_sender = T::CrossAccountId::from_sub(sender.clone());351352 Self::destroy_nft(353 cross_sender,354 Self::unique_collection_id(collection_id)?,355 misc::CollectionType::Regular,356 nft_id.into(),357 )?;358359 Self::deposit_event(Event::NFTBurned {360 owner: sender,361 nft_id,362 });363364 Ok(())365 }366367 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]368 #[transactional]369 pub fn send(370 origin: OriginFor<T>,371 rmrk_collection_id: RmrkCollectionId,372 rmrk_nft_id: RmrkNftId,373 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,374 ) -> DispatchResult {375 let sender = ensure_signed(origin.clone())?;376 let cross_sender = T::CrossAccountId::from_sub(sender.clone());377378 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;379 let nft_id = rmrk_nft_id.into();380381 let token_data = <TokenData<T>>::get((collection_id, nft_id))382 .ok_or(<Error<T>>::NoAvailableNftId)?;383384 let from = token_data.owner;385386 let collection = Self::get_typed_nft_collection(387 collection_id,388 misc::CollectionType::Regular,389 )?;390391 let budget = budget::Value::new(NESTING_BUDGET);392393 let target_owner;394395 match new_owner {396 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {397 target_owner = T::CrossAccountId::from_sub(account_id);398 },399 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(target_collection_id, target_nft_id) => {400 let target_collection_id = Self::unique_collection_id(target_collection_id)?;401402 target_owner = T::CrossTokenAddressMapping::token_to_address(403 target_collection_id,404 target_nft_id.into(),405 );406407 let spender = <PalletStructure<T>>::get_indirect_owner(408 target_collection_id,409 target_nft_id.into(),410 Some((collection_id, nft_id)),411 &budget,412 )?;413414 let is_approval_required = cross_sender != spender;415416 if is_approval_required {417 // FIXME418 // <PalletNft<T>>::set_allowance(419 // &collection,420 // &cross_sender,421 // nft_id,422 // Some(&spender),423 // &budget424 // ).map_err(Self::map_common_err_to_proxy)?;425426 return Ok(());427 }428 }429 }430431 <PalletNft<T>>::transfer_from(432 &collection,433 &cross_sender,434 &from,435 &target_owner,436 nft_id,437 &budget438 ).map_err(Self::map_common_err_to_proxy)?;439440 Ok(())441 }442443 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]444 #[transactional]445 pub fn set_property(446 origin: OriginFor<T>,447 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,448 maybe_nft_id: Option<RmrkNftId>,449 key: RmrkKeyString,450 value: RmrkValueString,451 ) -> DispatchResult {452 let sender = ensure_signed(origin)?;453 let sender = T::CrossAccountId::from_sub(sender);454455 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;456 let budget = budget::Value::new(NESTING_BUDGET);457458 match maybe_nft_id {459 Some(nft_id) => {460 let token_id: TokenId = nft_id.into();461462 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;463 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;464465 <PalletNft<T>>::set_scoped_token_property(466 collection_id,467 token_id,468 PropertyScope::Rmrk,469 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,470 )?;471 }472 None => {473 let collection = Self::get_typed_nft_collection(474 collection_id,475 misc::CollectionType::Regular,476 )?;477478 Self::check_collection_owner(&collection, &sender)?;479480 <PalletCommon<T>>::set_scoped_collection_property(481 collection_id,482 PropertyScope::Rmrk,483 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,484 )?;485 }486 }487488 Self::deposit_event(Event::PropertySet {489 collection_id: rmrk_collection_id,490 maybe_nft_id,491 key,492 value,493 });494495 Ok(())496 }497498 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]499 #[transactional]500 pub fn add_basic_resource(501 origin: OriginFor<T>,502 collection_id: RmrkCollectionId,503 nft_id: RmrkNftId,504 resource: RmrkBasicResource,505 ) -> DispatchResult {506 let sender = ensure_signed(origin.clone())?;507508 let resource_id = Self::resource_add(509 sender,510 Self::unique_collection_id(collection_id)?,511 nft_id.into(),512 [513 Self::rmrk_property(TokenType, &NftType::Resource)?,514 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,515 Self::rmrk_property(Src, &resource.src)?,516 Self::rmrk_property(Metadata, &resource.metadata)?,517 Self::rmrk_property(License, &resource.license)?,518 Self::rmrk_property(Thumb, &resource.thumb)?,519 ]520 .into_iter(),521 )?;522523 Self::deposit_event(Event::ResourceAdded {524 nft_id,525 resource_id,526 });527 Ok(())528 }529530 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]531 #[transactional]532 pub fn add_composable_resource(533 origin: OriginFor<T>,534 collection_id: RmrkCollectionId,535 nft_id: RmrkNftId,536 _resource_id: RmrkBoundedResource,537 resource: RmrkComposableResource,538 ) -> DispatchResult {539 let sender = ensure_signed(origin.clone())?;540541 let resource_id = Self::resource_add(542 sender,543 Self::unique_collection_id(collection_id)?,544 nft_id.into(),545 [546 Self::rmrk_property(TokenType, &NftType::Resource)?,547 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,548 Self::rmrk_property(Parts, &resource.parts)?,549 Self::rmrk_property(Base, &resource.base)?,550 Self::rmrk_property(Src, &resource.src)?,551 Self::rmrk_property(Metadata, &resource.metadata)?,552 Self::rmrk_property(License, &resource.license)?,553 Self::rmrk_property(Thumb, &resource.thumb)?,554 ]555 .into_iter(),556 )?;557558 Self::deposit_event(Event::ResourceAdded {559 nft_id,560 resource_id,561 });562 Ok(())563 }564565 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]566 #[transactional]567 pub fn add_slot_resource(568 origin: OriginFor<T>,569 collection_id: RmrkCollectionId,570 nft_id: RmrkNftId,571 resource: RmrkSlotResource,572 ) -> DispatchResult {573 let sender = ensure_signed(origin.clone())?;574575 let resource_id = Self::resource_add(576 sender,577 Self::unique_collection_id(collection_id)?,578 nft_id.into(),579 [580 Self::rmrk_property(TokenType, &NftType::Resource)?,581 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,582 Self::rmrk_property(Base, &resource.base)?,583 Self::rmrk_property(Src, &resource.src)?,584 Self::rmrk_property(Metadata, &resource.metadata)?,585 Self::rmrk_property(Slot, &resource.slot)?,586 Self::rmrk_property(License, &resource.license)?,587 Self::rmrk_property(Thumb, &resource.thumb)?,588 ]589 .into_iter(),590 )?;591592 Self::deposit_event(Event::ResourceAdded {593 nft_id,594 resource_id,595 });596 Ok(())597 }598599 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]600 #[transactional]601 pub fn remove_resource(602 origin: OriginFor<T>,603 collection_id: RmrkCollectionId,604 nft_id: RmrkNftId,605 resource_id: RmrkResourceId,606 ) -> DispatchResult {607 let sender = ensure_signed(origin.clone())?;608609 Self::resource_remove(610 sender,611 Self::unique_collection_id(collection_id)?,612 nft_id.into(),613 resource_id.into(),614 )?;615616 Self::deposit_event(Event::ResourceRemoval {617 nft_id,618 resource_id,619 });620 Ok(())621 }622 }623}624625impl<T: Config> Pallet<T> {626 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {627 let key = rmrk_key.to_key::<T>()?;628629 let scoped_key = PropertyScope::Rmrk630 .apply(key)631 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;632633 Ok(scoped_key)634 }635636 // todo think about renaming these637 pub fn rmrk_property<E: Encode>(638 rmrk_key: RmrkProperty,639 value: &E,640 ) -> Result<Property, DispatchError> {641 let key = rmrk_key.to_key::<T>()?;642643 let value = value644 .encode()645 .try_into()646 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;647648 let property = Property { key, value };649650 Ok(property)651 }652653 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {654 vec.decode()655 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())656 }657658 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>659 where660 BoundedVec<u8, S>: TryFrom<Vec<u8>>,661 {662 vec.rebind()663 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())664 }665666 fn init_collection(667 sender: T::CrossAccountId,668 data: CreateCollectionData<T::AccountId>,669 properties: impl Iterator<Item = Property>,670 ) -> Result<CollectionId, DispatchError> {671 let collection_id = <PalletNft<T>>::init_collection(sender, data);672673 if let Err(DispatchError::Arithmetic(_)) = &collection_id {674 return Err(<Error<T>>::NoAvailableCollectionId.into());675 }676677 <PalletCommon<T>>::set_scoped_collection_properties(678 collection_id?,679 PropertyScope::Rmrk,680 properties,681 )?;682683 collection_id684 }685686 pub fn create_nft(687 sender: &T::CrossAccountId,688 owner: &T::CrossAccountId,689 collection: &NonfungibleHandle<T>,690 properties: impl Iterator<Item = Property>,691 ) -> Result<TokenId, DispatchError> {692 let data = CreateNftExData {693 properties: BoundedVec::default(),694 owner: owner.clone(),695 };696697 let budget = budget::Value::new(NESTING_BUDGET);698699 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;700701 let nft_id = <PalletNft<T>>::current_token_id(collection.id);702703 <PalletNft<T>>::set_scoped_token_properties(704 collection.id,705 nft_id,706 PropertyScope::Rmrk,707 properties,708 )?;709710 Ok(nft_id)711 }712713 fn destroy_nft(714 sender: T::CrossAccountId,715 collection_id: CollectionId,716 collection_type: misc::CollectionType,717 token_id: TokenId,718 ) -> DispatchResult {719 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;720721 <PalletNft<T>>::burn(&collection, &sender, token_id)722 .map_err(Self::map_common_err_to_proxy)?;723724 Ok(())725 }726727 fn resource_add(728 sender: T::AccountId,729 collection_id: CollectionId,730 token_id: TokenId,731 resource_properties: impl Iterator<Item = Property>,732 ) -> Result<RmrkResourceId, DispatchError> {733 let collection =734 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;735 ensure!(collection.owner == sender, Error::<T>::NoPermission);736737 // Check NFT lock status // todo depends on market, maybe later738 //ensure!(!Pallet::<T>::is_locked(collection_id, nft_id), pallet_uniques::Error::<T>::Locked);739740 let sender = T::CrossAccountId::from_sub(sender);741 let budget = budget::Value::new(NESTING_BUDGET);742 let pending = Self::ensure_nft_owner(collection_id, token_id, &sender, &budget).is_err();743744 let resource_collection_id: CollectionId =745 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;746 let resource_collection =747 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;748749 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them750751 let resource_id = Self::create_nft(752 &sender, // todo owner of the nft?753 &sender,754 &resource_collection,755 resource_properties.chain(756 [757 Self::rmrk_property(PendingResourceAccept, &pending)?,758 Self::rmrk_property(PendingResourceRemoval, &false)?,759 ]760 .into_iter(),761 ),762 )763 .map_err(|err| match err {764 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),765 err => Self::map_common_err_to_proxy(err),766 })?;767768 Ok(resource_id.0)769 }770771 fn resource_remove(772 sender: T::AccountId,773 collection_id: CollectionId,774 nft_id: TokenId,775 resource_id: TokenId,776 ) -> DispatchResult {777 let collection =778 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;779 ensure!(collection.owner == sender, Error::<T>::NoPermission);780781 let resource_collection_id: CollectionId =782 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;783 let resource_collection =784 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;785 ensure!(786 <PalletNft<T>>::token_exists(&resource_collection, resource_id),787 Error::<T>::ResourceDoesntExist788 );789790 let budget = up_data_structs::budget::Value::new(10);791 let topmost_owner =792 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;793794 let sender = T::CrossAccountId::from_sub(sender);795 if topmost_owner == sender {796 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)797 .map_err(Self::map_common_err_to_proxy)?;798 } else {799 <PalletNft<T>>::set_scoped_token_property(800 resource_collection_id,801 resource_id,802 PropertyScope::Rmrk,803 Self::rmrk_property(PendingResourceRemoval, &true)?,804 )?;805 }806807 Ok(())808 }809810 fn change_collection_owner(811 collection_id: CollectionId,812 collection_type: misc::CollectionType,813 sender: T::AccountId,814 new_owner: T::AccountId,815 ) -> DispatchResult {816 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;817 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;818819 let mut collection = collection.into_inner();820821 collection.owner = new_owner;822 collection.save()823 }824825 fn check_collection_owner(826 collection: &NonfungibleHandle<T>,827 account: &T::CrossAccountId,828 ) -> DispatchResult {829 collection830 .check_is_owner(account)831 .map_err(Self::map_common_err_to_proxy)832 }833834 pub fn last_collection_idx() -> RmrkCollectionId {835 <CollectionIndex<T>>::get()836 }837838 pub fn unique_collection_id(839 rmrk_collection_id: RmrkCollectionId,840 ) -> Result<CollectionId, DispatchError> {841 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)842 .map_err(|_| <Error<T>>::CollectionUnknown.into())843 }844845 pub fn rmrk_collection_id(846 unique_collection_id: CollectionId847 ) -> Result<RmrkCollectionId, DispatchError> {848 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)849 .map_err(|_| <Error<T>>::CollectionUnknown.into())850 }851852 pub fn get_nft_collection(853 collection_id: CollectionId,854 ) -> Result<NonfungibleHandle<T>, DispatchError> {855 let collection = <CollectionHandle<T>>::try_get(collection_id)856 .map_err(|_| <Error<T>>::CollectionUnknown)?;857858 match collection.mode {859 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),860 _ => Err(<Error<T>>::CollectionUnknown.into()),861 }862 }863864 pub fn collection_exists(collection_id: CollectionId) -> bool {865 <CollectionHandle<T>>::try_get(collection_id).is_ok()866 }867868 pub fn get_collection_property(869 collection_id: CollectionId,870 key: RmrkProperty,871 ) -> Result<PropertyValue, DispatchError> {872 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)873 .get(&Self::rmrk_property_key(key)?)874 .ok_or(<Error<T>>::CollectionUnknown)?875 .clone();876877 Ok(collection_property)878 }879880 pub fn get_collection_property_decoded<V: Decode>(881 collection_id: CollectionId,882 key: RmrkProperty,883 ) -> Result<V, DispatchError> {884 Self::decode_property(Self::get_collection_property(collection_id, key)?)885 }886887 pub fn get_collection_type(888 collection_id: CollectionId,889 ) -> Result<misc::CollectionType, DispatchError> {890 Self::get_collection_property_decoded(collection_id, CollectionType)891 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())892 }893894 pub fn ensure_collection_type(895 collection_id: CollectionId,896 collection_type: misc::CollectionType,897 ) -> DispatchResult {898 let actual_type = Self::get_collection_type(collection_id)?;899 ensure!(900 actual_type == collection_type,901 <CommonError<T>>::NoPermission902 );903904 Ok(())905 }906907 pub fn get_typed_nft_collection(908 collection_id: CollectionId,909 collection_type: misc::CollectionType,910 ) -> Result<NonfungibleHandle<T>, DispatchError> {911 Self::ensure_collection_type(collection_id, collection_type)?;912913 Self::get_nft_collection(collection_id)914 }915916 pub fn get_typed_nft_collection_mapped(917 rmrk_collection_id: RmrkCollectionId,918 collection_type: misc::CollectionType,919 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {920 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;921922 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;923924 Ok((collection, unique_collection_id))925 }926927 pub fn get_nft_property(928 collection_id: CollectionId,929 nft_id: TokenId,930 key: RmrkProperty,931 ) -> Result<PropertyValue, DispatchError> {932 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))933 .get(&Self::rmrk_property_key(key)?)934 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?935 .clone();936937 Ok(nft_property)938 }939940 pub fn get_nft_property_decoded<V: Decode>(941 collection_id: CollectionId,942 nft_id: TokenId,943 key: RmrkProperty,944 ) -> Result<V, DispatchError> {945 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)946 }947948 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {949 <TokenData<T>>::contains_key((collection_id, nft_id))950 }951952 pub fn get_nft_type(953 collection_id: CollectionId,954 token_id: TokenId,955 ) -> Result<NftType, DispatchError> {956 Self::get_nft_property_decoded(collection_id, token_id, TokenType)957 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())958 }959960 pub fn ensure_nft_type(961 collection_id: CollectionId,962 token_id: TokenId,963 nft_type: NftType,964 ) -> DispatchResult {965 let actual_type = Self::get_nft_type(collection_id, token_id)?;966 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);967968 Ok(())969 }970971 pub fn ensure_nft_owner(972 collection_id: CollectionId,973 token_id: TokenId,974 possible_owner: &T::CrossAccountId,975 nesting_budget: &dyn budget::Budget976 ) -> DispatchResult {977 let is_owned = <PalletStructure<T>>::check_indirectly_owned(978 possible_owner.clone(),979 collection_id,980 token_id,981 None,982 nesting_budget,983 )?;984985 ensure!(986 is_owned,987 <Error<T>>::NoPermission988 );989990 Ok(())991 }992993 pub fn filter_user_properties<Key, Value, R, Mapper>(994 collection_id: CollectionId,995 token_id: Option<TokenId>,996 filter_keys: Option<Vec<RmrkPropertyKey>>,997 mapper: Mapper,998 ) -> Result<Vec<R>, DispatchError>999 where1000 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1001 Value: Decode + Default,1002 Mapper: Fn(Key, Value) -> R,1003 {1004 filter_keys1005 .map(|keys| {1006 let properties = keys1007 .into_iter()1008 .filter_map(|key| {1009 let key: Key = key.try_into().ok()?;10101011 let value = match token_id {1012 Some(token_id) => Self::get_nft_property_decoded(1013 collection_id,1014 token_id,1015 UserProperty(key.as_ref()),1016 ),1017 None => Self::get_collection_property_decoded(1018 collection_id,1019 UserProperty(key.as_ref()),1020 ),1021 }1022 .ok()?;10231024 Some(mapper(key, value))1025 })1026 .collect();10271028 Ok(properties)1029 })1030 .unwrap_or_else(|| {1031 let properties =1032 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();10331034 Ok(properties)1035 })1036 }10371038 pub fn iterate_user_properties<Key, Value, R, Mapper>(1039 collection_id: CollectionId,1040 token_id: Option<TokenId>,1041 mapper: Mapper,1042 ) -> Result<impl Iterator<Item = R>, DispatchError>1043 where1044 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1045 Value: Decode + Default,1046 Mapper: Fn(Key, Value) -> R,1047 {1048 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;10491050 let properties = match token_id {1051 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1052 None => <PalletCommon<T>>::collection_properties(collection_id),1053 };10541055 let properties = properties.into_iter().filter_map(move |(key, value)| {1056 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;10571058 let key: Key = key.to_vec().try_into().ok()?;1059 let value: Value = value.decode().ok()?;10601061 Some(mapper(key, value))1062 });10631064 Ok(properties)1065 }10661067 fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {1068 map_common_err_to_proxy! {1069 match err {1070 NoPermission => NoPermission,1071 CollectionTokenLimitExceeded => CollectionFullOrLocked,1072 PublicMintingNotAllowed => NoPermission,1073 TokenNotFound => NoAvailableNftId,1074 ApprovedValueTooLow => NoPermission1075 }1076 }1077 }1078}pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -254,7 +254,6 @@
spender: T::CrossAccountId,
token: TokenId,
amount: u128,
- _nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
with_weight(
<Pallet<T>>::set_allowance(self, &sender, &spender, token, amount),
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -815,9 +815,8 @@
#[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, &budget))
+ dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))
}
/// 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.