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)) => Parent::Token(collection, token),160 None => Parent::User(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 v if v == 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 Budget193 ) -> DispatchResult {194 Self::try_dispatched(195 under,196 |d, parent_id| d.check_nesting(197 from,198 (collection_id, token_id),199 parent_id,200 nesting_budget201 )202 )203 }204205 pub fn try_nest_if_sent_to_token(206 from: T::CrossAccountId,207 under: &T::CrossAccountId,208 collection_id: CollectionId,209 token_id: TokenId,210 nesting_budget: &dyn Budget211 ) -> DispatchResult {212 Self::try_dispatched(213 under,214 |d, parent_id| {215 d.check_nesting(216 from,217 (collection_id, token_id),218 parent_id,219 nesting_budget220 )?;221222 d.nest(parent_id, (collection_id, token_id));223224 Ok(())225 }226 )227 }228229 pub fn nest_if_sent_to_token(230 owner: &T::CrossAccountId,231 collection_id: CollectionId,232 token_id: TokenId233 ) {234 Self::dispatched(235 owner,236 |d, parent_id| d.nest(237 parent_id,238 (collection_id, token_id)239 )240 );241 }242243 pub fn unnest_if_nested(244 owner: &T::CrossAccountId,245 collection_id: CollectionId,246 token_id: TokenId247 ) {248 Self::dispatched(249 owner,250 |d, parent_id| d.unnest(251 parent_id,252 (collection_id, token_id)253 )254 );255 }256257 fn dispatched(258 account: &T::CrossAccountId,259 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId)260 ) {261 Self::try_dispatched(262 account,263 |d, id| {264 action(d, id);265 Ok(())266 }267 ).unwrap();268 }269270 fn try_dispatched(271 account: &T::CrossAccountId,272 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult273 ) -> DispatchResult {274 let account = T::CrossTokenAddressMapping::address_to_token(account);275276 if account.is_none() {277 return Ok(());278 }279280 let account = account.unwrap();281282 let handle = <CollectionHandle<T>>::try_get(account.0);283284 if handle.is_err() {285 return Ok(());286 }287288 let handle = handle.unwrap();289290 let dispatch = T::CollectionDispatch::dispatch(handle);291 let dispatch = dispatch.as_dyn();292293 action(dispatch, account.1)294 }295}