difftreelog
fix(rmrk) sending nft
in: master
5 files changed
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -25,7 +25,7 @@
Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,
};
use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};
-use pallet_structure::Pallet as PalletStructure;
+use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm::account::CrossAccountId;
use core::convert::AsRef;
@@ -128,8 +128,10 @@
NoAvailableNftId,
CollectionUnknown,
NoPermission,
+ NonTransferable,
CollectionFullOrLocked,
ResourceDoesntExist,
+ CannotSendToDescendentOrSelf,
}
#[pallet::call]
@@ -207,7 +209,7 @@
);
<PalletNft<T>>::destroy_collection(collection, &cross_sender)
- .map_err(Self::map_common_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?;
Self::deposit_event(Event::CollectionDestroyed {
issuer: sender,
@@ -283,6 +285,7 @@
recipient: Option<T::AccountId>,
royalty_amount: Option<Permill>,
metadata: RmrkString,
+ transferable: bool,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sender = T::CrossAccountId::from_sub(sender);
@@ -304,6 +307,8 @@
&collection,
[
Self::rmrk_property(TokenType, &NftType::Regular)?,
+ Self::rmrk_property(Transferable, &transferable)?,
+ Self::rmrk_property(PendingNftAccept, &false)?,
Self::rmrk_property(RoyaltyInfo, &royalty_info)?,
Self::rmrk_property(Metadata, &metadata)?,
Self::rmrk_property(Equipped, &false)?,
@@ -327,7 +332,7 @@
)
.map_err(|err| match err {
DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),
- err => Self::map_common_err_to_proxy(err),
+ err => Self::map_unique_err_to_proxy(err),
})?;
Self::deposit_event(Event::NftMinted {
@@ -373,7 +378,7 @@
new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,
) -> DispatchResult {
let sender = ensure_signed(origin.clone())?;
- let cross_sender = T::CrossAccountId::from_sub(sender.clone());
+ let cross_sender = T::CrossAccountId::from_sub(sender);
let collection_id = Self::unique_collection_id(rmrk_collection_id)?;
let nft_id = rmrk_nft_id.into();
@@ -388,8 +393,14 @@
misc::CollectionType::Regular,
)?;
- let budget = budget::Value::new(NESTING_BUDGET);
+ if !Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)? {
+ return Err(<Error<T>>::NonTransferable.into());
+ }
+ if Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)? {
+ return Err(<Error<T>>::NoPermission.into());
+ }
+
let target_owner;
match new_owner {
@@ -399,43 +410,45 @@
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 target_nft_budget = budget::Value::new(NESTING_BUDGET);
- let spender = <PalletStructure<T>>::get_indirect_owner(
+ let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(
target_collection_id,
target_nft_id.into(),
Some((collection_id, nft_id)),
- &budget,
- )?;
+ &target_nft_budget,
+ ).map_err(Self::map_unique_err_to_proxy)?;
- let is_approval_required = cross_sender != spender;
+ let is_approval_required = cross_sender != target_nft_owner;
if is_approval_required {
- // FIXME
- // <PalletNft<T>>::set_allowance(
- // &collection,
- // &cross_sender,
- // nft_id,
- // Some(&spender),
- // &budget
- // ).map_err(Self::map_common_err_to_proxy)?;
+ target_owner = target_nft_owner;
- return Ok(());
+ <PalletNft<T>>::set_scoped_token_property(
+ collection.id,
+ nft_id,
+ PropertyScope::Rmrk,
+ Self::rmrk_property(PendingNftAccept, &is_approval_required)?,
+ )?;
+ } else {
+ target_owner = T::CrossTokenAddressMapping::token_to_address(
+ target_collection_id,
+ target_nft_id.into(),
+ );
}
}
}
+ let src_nft_budget = budget::Value::new(NESTING_BUDGET);
+
<PalletNft<T>>::transfer_from(
&collection,
&cross_sender,
&from,
&target_owner,
nft_id,
- &budget
- ).map_err(Self::map_common_err_to_proxy)?;
+ &src_nft_budget
+ ).map_err(Self::map_unique_err_to_proxy)?;
Ok(())
}
@@ -719,7 +732,7 @@
let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;
<PalletNft<T>>::burn(&collection, &sender, token_id)
- .map_err(Self::map_common_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?;
Ok(())
}
@@ -762,7 +775,7 @@
)
.map_err(|err| match err {
DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),
- err => Self::map_common_err_to_proxy(err),
+ err => Self::map_unique_err_to_proxy(err),
})?;
Ok(resource_id.0)
@@ -794,7 +807,7 @@
let sender = T::CrossAccountId::from_sub(sender);
if topmost_owner == sender {
<PalletNft<T>>::burn(&resource_collection, &sender, resource_id)
- .map_err(Self::map_common_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?;
} else {
<PalletNft<T>>::set_scoped_token_property(
resource_collection_id,
@@ -828,7 +841,7 @@
) -> DispatchResult {
collection
.check_is_owner(account)
- .map_err(Self::map_common_err_to_proxy)
+ .map_err(Self::map_unique_err_to_proxy)
}
pub fn last_collection_idx() -> RmrkCollectionId {
@@ -1064,14 +1077,16 @@
Ok(properties)
}
- fn map_common_err_to_proxy(err: DispatchError) -> DispatchError {
- map_common_err_to_proxy! {
+ fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {
+ map_unique_err_to_proxy! {
match err {
- NoPermission => NoPermission,
- CollectionTokenLimitExceeded => CollectionFullOrLocked,
- PublicMintingNotAllowed => NoPermission,
- TokenNotFound => NoAvailableNftId,
- ApprovedValueTooLow => NoPermission
+ CommonError::NoPermission => NoPermission,
+ CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,
+ CommonError::PublicMintingNotAllowed => NoPermission,
+ CommonError::TokenNotFound => NoAvailableNftId,
+ CommonError::ApprovedValueTooLow => NoPermission,
+ StructureError::TokenNotFound => NoAvailableNftId,
+ StructureError::OuroborosDetected => CannotSendToDescendentOrSelf
}
}
}
pallets/proxy-rmrk-core/src/misc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/misc.rs
+++ b/pallets/proxy-rmrk-core/src/misc.rs
@@ -2,10 +2,10 @@
use codec::{Encode, Decode, Error};
#[macro_export]
-macro_rules! map_common_err_to_proxy {
- (match $err:ident { $($common_err:ident => $proxy_err:ident),+ }) => {
+macro_rules! map_unique_err_to_proxy {
+ (match $err:ident { $($unique_err_ty:ident :: $unique_err:ident => $proxy_err:ident),+ }) => {
$(
- if $err == <CommonError<T>>::$common_err.into() {
+ if $err == <$unique_err_ty<T>>::$unique_err.into() {
return <Error<T>>::$proxy_err.into()
} else
)+ {
pallets/proxy-rmrk-core/src/property.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/property.rs
+++ b/pallets/proxy-rmrk-core/src/property.rs
@@ -5,11 +5,13 @@
Metadata,
CollectionType,
TokenType,
+ Transferable,
RoyaltyInfo,
Equipped,
ResourceCollection,
ResourcePriorities,
ResourceType,
+ PendingNftAccept,
PendingResourceAccept,
PendingResourceRemoval,
Parts,
@@ -49,13 +51,15 @@
Self::Metadata => key!("metadata"),
Self::CollectionType => key!("collection-type"),
Self::TokenType => key!("token-type"),
+ Self::Transferable => key!("transferable"),
Self::RoyaltyInfo => key!("royalty-info"),
Self::Equipped => key!("equipped"),
Self::ResourceCollection => key!("resource-collection"),
Self::ResourcePriorities => key!("resource-priorities"),
Self::ResourceType => key!("resource-type"),
- Self::PendingResourceAccept => key!("pending-accept"),
- Self::PendingResourceRemoval => key!("pending-removal"),
+ Self::PendingNftAccept => key!("pending-nft-accept"),
+ Self::PendingResourceAccept => key!("pending-resource-accept"),
+ Self::PendingResourceRemoval => key!("pending-resource-removal"),
Self::Parts => key!("parts"),
Self::Base => key!("base"),
Self::Src => key!("src"),
pallets/structure/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use pallet_common::CommonCollectionOperations;4use sp_std::collections::btree_set::BTreeSet;56use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};7use frame_support::fail;8pub use pallet::*;9use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};10use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};1112#[cfg(feature = "runtime-benchmarks")]13pub mod benchmarking;14pub mod weights;1516pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;1718#[frame_support::pallet]19pub mod pallet {20 use frame_support::Parameter;21 use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};22 use frame_support::pallet_prelude::*;2324 use super::*;2526 #[pallet::error]27 pub enum Error<T> {28 /// While searched for owner, got already checked account29 OuroborosDetected,30 /// While searched for owner, encountered depth limit31 DepthLimit,32 /// While iterating over children, encountered breadth limit33 BreadthLimit,34 /// While searched for owner, found token owner by not-yet-existing token35 TokenNotFound,36 }3738 #[pallet::event]39 pub enum Event<T> {40 /// Executed call on behalf of token41 Executed(DispatchResult),42 }4344 #[pallet::config]45 pub trait Config: frame_system::Config + pallet_common::Config {46 type WeightInfo: weights::WeightInfo;47 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;48 type Call: Parameter + UnfilteredDispatchable<Origin = Self::Origin> + GetDispatchInfo;49 }5051 #[pallet::pallet]52 pub struct Pallet<T>(_);5354 #[pallet::call]55 impl<T: Config> Pallet<T> {56 // #[pallet::weight({57 // let dispatch_info = call.get_dispatch_info();5859 // (60 // dispatch_info.weight61 // // Cost of dereferencing parent62 // .saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))63 // .saturating_add(4000 * *max_depth as Weight),64 // dispatch_info.class)65 // })]66 // pub fn execute(67 // origin: OriginFor<T>,68 // call: Box<<T as Config>::Call>,69 // max_depth: u32,70 // ) -> DispatchResult {71 }72}7374#[derive(PartialEq)]75pub enum Parent<CrossAccountId> {76 /// Token owned by normal account77 User(CrossAccountId),78 /// Passed token not found79 TokenNotFound,80 /// Token owner is another token (target token still may not exist)81 Token(CollectionId, TokenId),82}8384impl<T: Config> Pallet<T> {85 pub fn find_parent(86 collection: CollectionId,87 token: TokenId,88 ) -> Result<Parent<T::CrossAccountId>, DispatchError> {89 // TODO: Reduce cost by not reading collection config90 let handle = match CollectionHandle::try_get(collection) {91 Ok(v) => v,92 Err(_) => return Ok(Parent::TokenNotFound),93 };94 let handle = T::CollectionDispatch::dispatch(handle);95 let handle = handle.as_dyn();9697 Ok(match handle.token_owner(token) {98 Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {99 Some((collection, token)) => Parent::Token(collection, token),100 None => Parent::User(owner),101 },102 None => Parent::TokenNotFound,103 })104 }105106 pub fn parent_chain(107 mut collection: CollectionId,108 mut token: TokenId,109 ) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {110 let mut finished = false;111 let mut visited = BTreeSet::new();112 visited.insert((collection, token));113 core::iter::from_fn(move || {114 if finished {115 return None;116 }117 let parent = Self::find_parent(collection, token);118 match parent {119 Ok(Parent::Token(new_collection, new_token)) => {120 collection = new_collection;121 token = new_token;122 if !visited.insert((new_collection, new_token)) {123 finished = true;124 return Some(Err(<Error<T>>::OuroborosDetected.into()));125 }126 }127 _ => finished = true,128 }129 Some(parent as Result<_, DispatchError>)130 })131 }132133 /// Try to dereference address, until finding top level owner134 ///135 /// May return token address if parent token not yet exists136 pub fn find_topmost_owner(137 collection: CollectionId,138 token: TokenId,139 budget: &dyn Budget,140 ) -> Result<T::CrossAccountId, DispatchError> {141 let owner = Self::parent_chain(collection, token)142 .take_while(|_| budget.consume())143 .find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))144 .ok_or(<Error<T>>::DepthLimit)??;145146 Ok(match owner {147 Parent::User(v) => v,148 _ => fail!(<Error<T>>::TokenNotFound),149 })150 }151152 pub fn get_indirect_owner(153 collection: CollectionId,154 token: TokenId,155 for_nest: Option<(CollectionId, TokenId)>,156 budget: &dyn Budget,157 ) -> Result<T::CrossAccountId, DispatchError> {158 // Tried to nest token in itself159 if Some((collection, token)) == for_nest {160 return Err(<Error<T>>::OuroborosDetected.into());161 }162163 for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {164 match parent? {165 // Tried to nest token in chain, which has this token as one of parents166 Parent::Token(collection, token) if Some((collection, token)) == for_nest => {167 return Err(<Error<T>>::OuroborosDetected.into())168 }169 // Token is owned by other user170 Parent::User(user) => return Ok(user),171 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),172 // Continue parent chain173 Parent::Token(_, _) => {}174 }175 }176177 Err(<Error<T>>::DepthLimit.into())178 }179180 pub fn burn_item_recursively(181 from: T::CrossAccountId,182 collection: CollectionId,183 token: TokenId,184 self_budget: &dyn Budget,185 breadth_budget: &dyn Budget,186 ) -> DispatchResultWithPostInfo {187 let handle = <CollectionHandle<T>>::try_get(collection)?;188 let dispatch = T::CollectionDispatch::dispatch(handle);189 let dispatch = dispatch.as_dyn();190 dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)191 }192193 /// Check if token indirectly owned by specified user194 pub fn check_indirectly_owned(195 user: T::CrossAccountId,196 collection: CollectionId,197 token: TokenId,198 for_nest: Option<(CollectionId, TokenId)>,199 budget: &dyn Budget,200 ) -> Result<bool, DispatchError> {201 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {202 Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,203 None => user,204 };205206 Self::get_indirect_owner(207 collection,208 token,209 for_nest,210 budget211 ).map(|indirect_owner| indirect_owner == target_parent)212 }213214 pub fn check_nesting(215 from: T::CrossAccountId,216 under: &T::CrossAccountId,217 collection_id: CollectionId,218 token_id: TokenId,219 nesting_budget: &dyn Budget,220 ) -> DispatchResult {221 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {222 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)223 })224 }225226 pub fn nest_if_sent_to_token(227 from: T::CrossAccountId,228 under: &T::CrossAccountId,229 collection_id: CollectionId,230 token_id: TokenId,231 nesting_budget: &dyn Budget,232 ) -> DispatchResult {233 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {234 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;235236 collection.nest(parent_id, (collection_id, token_id));237238 Ok(())239 })240 }241242 pub fn nest_if_sent_to_token_unchecked(243 owner: &T::CrossAccountId,244 collection_id: CollectionId,245 token_id: TokenId,246 ) {247 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {248 collection.nest(parent_id, (collection_id, token_id))249 });250 }251252 pub fn unnest_if_nested(253 owner: &T::CrossAccountId,254 collection_id: CollectionId,255 token_id: TokenId,256 ) {257 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {258 collection.unnest(parent_id, (collection_id, token_id))259 });260 }261262 fn exec_if_owner_is_valid_nft(263 account: &T::CrossAccountId,264 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),265 ) {266 Self::try_exec_if_owner_is_valid_nft(account, |collection, id| {267 action(collection, id);268 Ok(())269 })270 .unwrap();271 }272273 fn try_exec_if_owner_is_valid_nft(274 account: &T::CrossAccountId,275 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,276 ) -> DispatchResult {277 let account = T::CrossTokenAddressMapping::address_to_token(account);278279 if account.is_none() {280 return Ok(());281 }282283 let account = account.unwrap();284285 let handle = <CollectionHandle<T>>::try_get(account.0);286287 if handle.is_err() {288 return Ok(());289 }290291 let handle = handle.unwrap();292293 let dispatch = T::CollectionDispatch::dispatch(handle);294 let dispatch = dispatch.as_dyn();295296 action(dispatch, account.1)297 }298}runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -188,19 +188,17 @@
None => return Ok(None)
};
- let allowance = pallet_nonfungible::Allowance::<Runtime>::get((collection_id, nft_id));
-
Ok(Some(RmrkInstanceInfo {
owner: owner,
royalty: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::RoyaltyInfo)?,
metadata: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Metadata)?,
equipped: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Equipped)?,
- pending: allowance.is_some(),
+ pending: RmrkCore::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::PendingNftAccept)?,
}))
}
fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- use pallet_proxy_rmrk_core::misc::CollectionType;
+ use pallet_proxy_rmrk_core::{RmrkProperty, misc::CollectionType};
use pallet_common::CommonCollectionOperations;
let cross_account_id = CrossAccountId::from_sub(account_id);
@@ -210,15 +208,26 @@
Err(_) => return Ok(Vec::new()),
};
- Ok(
- collection.account_tokens(cross_account_id)
- .into_iter()
- .map(|token| token.0)
- .collect()
- )
+ let tokens = collection.account_tokens(cross_account_id)
+ .into_iter()
+ .filter(|token| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ collection_id,
+ *token,
+ RmrkProperty::PendingNftAccept
+ ).unwrap_or(true);
+
+ !is_pending
+ })
+ .map(|token| token.0)
+ .collect();
+
+ Ok(tokens)
}
fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ use pallet_proxy_rmrk_core::RmrkProperty;
+
let collection_id = match RmrkCore::unique_collection_id(collection_id) {
Ok(id) => id,
Err(_) => return Ok(Vec::new())
@@ -229,6 +238,16 @@
Ok(
pallet_nonfungible::TokenChildren::<Runtime>::iter_prefix((collection_id, nft_id))
.filter_map(|((child_collection, child_token), _)| {
+ let is_pending = RmrkCore::get_nft_property_decoded(
+ child_collection,
+ child_token,
+ RmrkProperty::PendingNftAccept
+ ).ok()?;
+
+ if is_pending {
+ return None;
+ }
+
let rmrk_child_collection = RmrkCore::rmrk_collection_id(
child_collection
).ok()?;
@@ -398,7 +417,7 @@
Ok(c) => c,
Err(_) => return Ok(Vec::new()),
};
-
+
let parts = collection.collection_tokens()
.into_iter()
.filter_map(|token_id| {