1#![cfg_attr(not(feature = "std"), no_std)]23use sp_std::collections::btree_set::BTreeSet;45use frame_support::dispatch::DispatchError;6use frame_support::fail;7pub use pallet::*;8use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};9use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};1011#[cfg(feature = "runtime-benchmarks")]12pub mod benchmarking;13pub mod weights;1415pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;1617#[frame_support::pallet]18pub mod pallet {19 use frame_support::Parameter;20 use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};21 use frame_support::pallet_prelude::*;22 use frame_system::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 Normal(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::Normal(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::Normal(_) | Parent::TokenNotFound)))142 .ok_or(<Error<T>>::DepthLimit)??;143144 Ok(match owner {145 Parent::Normal(v) => v,146 _ => fail!(<Error<T>>::TokenNotFound),147 })148 }149150 151 pub fn indirectly_owned(152 user: T::CrossAccountId,153 collection: CollectionId,154 token: TokenId,155 budget: &dyn Budget,156 ) -> Result<bool, DispatchError> {157 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {158 Some((collection, token)) => Parent::Token(collection, token),159 None => Parent::Normal(user),160 };161162 Ok(Self::parent_chain(collection, token)163 .take_while(|_| budget.consume())164 .any(|parent| Ok(&target_parent) == parent.as_ref()))165 }166}