1#![cfg_attr(not(feature = "std"), no_std)]23use pallet_common::CommonCollectionOperations;4use sp_std::collections::btree_set::BTreeSet;56use frame_support::dispatch::{DispatchError, DispatchResult};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 29 OuroborosDetected,30 31 DepthLimit,32 33 TokenNotFound,34 }3536 #[pallet::event]37 pub enum Event<T> {38 39 Executed(DispatchResult),40 }4142 #[pallet::config]43 pub trait Config: frame_system::Config + pallet_common::Config {44 type WeightInfo: weights::WeightInfo;45 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;46 type Call: Parameter + UnfilteredDispatchable<Origin = Self::Origin> + GetDispatchInfo;47 }4849 #[pallet::pallet]50 pub struct Pallet<T>(_);5152 #[pallet::call]53 impl<T: Config> Pallet<T> {54 55 5657 58 59 60 61 62 63 64 65 66 67 68 69 }70}7172#[derive(PartialEq)]73pub enum Parent<CrossAccountId> {74 75 User(CrossAccountId),76 77 TokenNotFound,78 79 Token(CollectionId, TokenId),80}8182impl<T: Config> Pallet<T> {83 pub fn find_parent(84 collection: CollectionId,85 token: TokenId,86 ) -> Result<Parent<T::CrossAccountId>, DispatchError> {87 88 let handle = match CollectionHandle::try_get(collection) {89 Ok(v) => v,90 Err(_) => return Ok(Parent::TokenNotFound),91 };92 let handle = T::CollectionDispatch::dispatch(handle);93 let handle = handle.as_dyn();9495 Ok(match handle.token_owner(token) {96 Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {97 Some((collection, token)) => Parent::Token(collection, token),98 None => Parent::User(owner),99 },100 None => Parent::TokenNotFound,101 })102 }103104 pub fn parent_chain(105 mut collection: CollectionId,106 mut token: TokenId,107 ) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {108 let mut finished = false;109 let mut visited = BTreeSet::new();110 visited.insert((collection, token));111 core::iter::from_fn(move || {112 if finished {113 return None;114 }115 let parent = Self::find_parent(collection, token);116 match parent {117 Ok(Parent::Token(new_collection, new_token)) => {118 collection = new_collection;119 token = new_token;120 if !visited.insert((new_collection, new_token)) {121 finished = true;122 return Some(Err(<Error<T>>::OuroborosDetected.into()));123 }124 }125 _ => finished = true,126 }127 Some(parent as Result<_, DispatchError>)128 })129 }130131 132 133 134 pub fn find_topmost_owner(135 collection: CollectionId,136 token: TokenId,137 budget: &dyn Budget,138 ) -> Result<T::CrossAccountId, DispatchError> {139 let owner = Self::parent_chain(collection, token)140 .take_while(|_| budget.consume())141 .find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))142 .ok_or(<Error<T>>::DepthLimit)??;143144 Ok(match owner {145 Parent::User(v) => v,146 _ => fail!(<Error<T>>::TokenNotFound),147 })148 }149150 151 pub fn check_indirectly_owned(152 user: T::CrossAccountId,153 collection: CollectionId,154 token: TokenId,155 for_nest: Option<(CollectionId, TokenId)>,156 budget: &dyn Budget,157 ) -> Result<bool, DispatchError> {158 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {159 Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,160 None => user,161 };162163 164 if Some((collection, token)) == for_nest {165 return Err(<Error<T>>::OuroborosDetected.into());166 }167168 for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {169 match parent? {170 171 Parent::Token(collection, token) if Some((collection, token)) == for_nest => {172 return Err(<Error<T>>::OuroborosDetected.into())173 }174 175 Parent::User(user) if user == target_parent => return Ok(true),176 177 Parent::User(_) => return Ok(false),178 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),179 180 Parent::Token(_, _) => {}181 }182 }183184 Err(<Error<T>>::DepthLimit.into())185 }186187 pub fn check_nesting(188 from: T::CrossAccountId,189 under: &T::CrossAccountId,190 collection_id: CollectionId,191 token_id: TokenId,192 nesting_budget: &dyn Budget,193 ) -> DispatchResult {194 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {195 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)196 })197 }198199 pub fn nest_if_sent_to_token(200 from: T::CrossAccountId,201 under: &T::CrossAccountId,202 collection_id: CollectionId,203 token_id: TokenId,204 nesting_budget: &dyn Budget,205 ) -> DispatchResult {206 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {207 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;208209 collection.nest(parent_id, (collection_id, token_id));210211 Ok(())212 })213 }214215 pub fn nest_if_sent_to_token_unchecked(216 owner: &T::CrossAccountId,217 collection_id: CollectionId,218 token_id: TokenId,219 ) {220 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {221 collection.nest(parent_id, (collection_id, token_id))222 });223 }224225 pub fn unnest_if_nested(226 owner: &T::CrossAccountId,227 collection_id: CollectionId,228 token_id: TokenId,229 ) {230 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {231 collection.unnest(parent_id, (collection_id, token_id))232 });233 }234235 fn exec_if_owner_is_valid_nft(236 account: &T::CrossAccountId,237 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),238 ) {239 Self::try_exec_if_owner_is_valid_nft(account, |collection, id| {240 action(collection, id);241 Ok(())242 })243 .unwrap();244 }245246 fn try_exec_if_owner_is_valid_nft(247 account: &T::CrossAccountId,248 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,249 ) -> DispatchResult {250 let account = T::CrossTokenAddressMapping::address_to_token(account);251252 if account.is_none() {253 return Ok(());254 }255256 let account = account.unwrap();257258 let handle = <CollectionHandle<T>>::try_get(account.0);259260 if handle.is_err() {261 return Ok(());262 }263264 let handle = handle.unwrap();265266 let dispatch = T::CollectionDispatch::dispatch(handle);267 let dispatch = dispatch.as_dyn();268269 action(dispatch, account.1)270 }271}