123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#![cfg_attr(not(feature = "std"), no_std)]5556use pallet_common::CommonCollectionOperations;57use pallet_common::{erc::CrossAccountId, eth::is_collection};58use sp_std::collections::btree_set::BTreeSet;5960use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};61use frame_support::fail;62pub use pallet::*;63use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};64use up_data_structs::{65 CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,66};6768#[cfg(feature = "runtime-benchmarks")]69pub mod benchmarking;70pub mod weights;7172pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;7374#[frame_support::pallet]75pub mod pallet {76 use frame_support::Parameter;77 use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};78 use frame_support::pallet_prelude::*;7980 use super::*;8182 #[pallet::error]83 pub enum Error<T> {84 85 OuroborosDetected,86 87 DepthLimit,88 89 BreadthLimit,90 91 TokenNotFound,92 93 CantNestTokenUnderCollection,94 }9596 #[pallet::event]97 pub enum Event<T> {98 99 Executed(DispatchResult),100 }101102 #[pallet::config]103 pub trait Config: frame_system::Config + pallet_common::Config {104 type WeightInfo: weights::WeightInfo;105 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;106 type RuntimeCall: Parameter107 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>108 + GetDispatchInfo;109 }110111 #[pallet::pallet]112 pub struct Pallet<T>(_);113114 #[pallet::call]115 impl<T: Config> Pallet<T> {116 117 118119 120 121 122 123 124 125 126 127 128 129 130 131 }132}133134#[derive(PartialEq)]135pub enum Parent<CrossAccountId> {136 137 User(CrossAccountId),138 139 TokenNotFound,140 141 MultipleOwners,142 143 Token(CollectionId, TokenId),144}145146impl<T: Config> Pallet<T> {147 148 149 150 151 152 153 pub fn find_parent(154 collection: CollectionId,155 token: TokenId,156 ) -> Result<Parent<T::CrossAccountId>, DispatchError> {157 158 let handle = match CollectionHandle::try_get(collection) {159 Ok(v) => v,160 Err(_) => return Ok(Parent::TokenNotFound),161 };162 let handle = T::CollectionDispatch::dispatch(handle);163 let handle = handle.as_dyn();164165 Ok(match handle.token_owner(token) {166 Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {167 Some((collection, token)) => Parent::Token(collection, token),168 None => Parent::User(owner),169 },170 Err(TokenOwnerError::MultipleOwners) => Parent::MultipleOwners,171 Err(TokenOwnerError::NotFound) => Parent::TokenNotFound,172 })173 }174175 176 177 178 179 180 pub fn parent_chain(181 mut collection: CollectionId,182 mut token: TokenId,183 ) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {184 let mut finished = false;185 let mut visited = BTreeSet::new();186 visited.insert((collection, token));187 core::iter::from_fn(move || {188 if finished {189 return None;190 }191 let parent = Self::find_parent(collection, token);192 match parent {193 Ok(Parent::Token(new_collection, new_token)) => {194 collection = new_collection;195 token = new_token;196 if !visited.insert((new_collection, new_token)) {197 finished = true;198 return Some(Err(<Error<T>>::OuroborosDetected.into()));199 }200 }201 _ => finished = true,202 }203 Some(parent as Result<_, DispatchError>)204 })205 }206207 208 209 210 211 212 213 214 pub fn find_topmost_owner(215 collection: CollectionId,216 token: TokenId,217 budget: &dyn Budget,218 ) -> Result<Option<T::CrossAccountId>, DispatchError> {219 let owner = Self::parent_chain(collection, token)220 .take_while(|_| budget.consume())221 .find(|p| {222 matches!(223 p,224 Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)225 )226 })227 .ok_or(<Error<T>>::DepthLimit)??;228229 Ok(match owner {230 Parent::User(v) => Some(v),231 Parent::MultipleOwners => None,232 _ => fail!(<Error<T>>::TokenNotFound),233 })234 }235236 237 238 239 240 241 242 pub fn get_checked_topmost_owner(243 collection: CollectionId,244 token: TokenId,245 for_nest: Option<(CollectionId, TokenId)>,246 budget: &dyn Budget,247 ) -> Result<Option<T::CrossAccountId>, DispatchError> {248 249 if Some((collection, token)) == for_nest {250 return Err(<Error<T>>::OuroborosDetected.into());251 }252253 for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {254 match parent? {255 256 Parent::Token(collection, token) if Some((collection, token)) == for_nest => {257 return Err(<Error<T>>::OuroborosDetected.into())258 }259 260 Parent::User(user) => return Ok(Some(user)),261 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),262 Parent::MultipleOwners => return Ok(None),263 264 Parent::Token(_, _) => {}265 }266 }267268 Err(<Error<T>>::DepthLimit.into())269 }270271 272 273 274 275 pub fn burn_item_recursively(276 from: T::CrossAccountId,277 collection: CollectionId,278 token: TokenId,279 self_budget: &dyn Budget,280 breadth_budget: &dyn Budget,281 ) -> DispatchResultWithPostInfo {282 let handle = <CollectionHandle<T>>::try_get(collection)?;283 let dispatch = T::CollectionDispatch::dispatch(handle);284 let dispatch = dispatch.as_dyn();285 dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)286 }287288 289 290 291 292 293 294 295 pub fn check_indirectly_owned(296 user: T::CrossAccountId,297 collection: CollectionId,298 token: TokenId,299 for_nest: Option<(CollectionId, TokenId)>,300 budget: &dyn Budget,301 ) -> Result<bool, DispatchError> {302 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {303 Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?304 {305 Some(topmost_owner) => topmost_owner,306 None => return Ok(false),307 },308 None => user,309 };310311 Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {312 indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)313 })314 }315316 317 318 319 320 321 322 pub fn check_nesting(323 from: T::CrossAccountId,324 under: &T::CrossAccountId,325 collection_id: CollectionId,326 token_id: TokenId,327 nesting_budget: &dyn Budget,328 ) -> DispatchResult {329 Self::try_exec_if_token(under, |collection, parent_id| {330 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)331 })332 }333334 335 336 337 338 339 pub fn nest_if_sent_to_token(340 from: T::CrossAccountId,341 under: &T::CrossAccountId,342 collection_id: CollectionId,343 token_id: TokenId,344 nesting_budget: &dyn Budget,345 ) -> DispatchResult {346 Self::try_exec_if_token(under, |collection, parent_id| {347 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;348349 collection.nest(parent_id, (collection_id, token_id));350351 Ok(())352 })353 }354355 356 357 358 pub fn nest_if_sent_to_token_unchecked(359 owner: &T::CrossAccountId,360 collection_id: CollectionId,361 token_id: TokenId,362 ) {363 Self::exec_if_token(owner, |collection, parent_id| {364 collection.nest(parent_id, (collection_id, token_id))365 });366 }367368 369 pub fn unnest_if_nested(370 owner: &T::CrossAccountId,371 collection_id: CollectionId,372 token_id: TokenId,373 ) {374 if let Err(e) = Self::try_exec_if_token(owner, |collection, parent_id| {375 collection.unnest(parent_id, (collection_id, token_id));376 Ok(())377 }) {378 log::warn!("unnest precondition failed: {e:?}")379 }380 }381382 383 384 fn exec_if_token(385 account: &T::CrossAccountId,386 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),387 ) {388 Self::try_exec_if_token(account, |collection, id| {389 action(collection, id);390 Ok(())391 })392 .unwrap();393 }394395 396 397 fn try_exec_if_token(398 account: &T::CrossAccountId,399 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,400 ) -> DispatchResult {401 if is_collection(&account.as_eth()) {402 fail!(<Error<T>>::CantNestTokenUnderCollection);403 }404 let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {405 return Ok(())406 };407408 let handle = <CollectionHandle<T>>::try_get(collection)?;409410 let dispatch = T::CollectionDispatch::dispatch(handle);411 let dispatch = dispatch.as_dyn();412413 action(dispatch, token)414 }415}