1234567891011121314151617use core::marker::PhantomData;18use up_sponsorship::SponsorshipHandler;19use frame_support::{20 traits::{IsSubType},21 storage::{StorageMap, StorageDoubleMap, StorageNMap},22};23use up_data_structs::{24 CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,25 NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode,26 CreateItemData,27};28use sp_runtime::traits::Saturating;29use pallet_common::{CollectionHandle};30use pallet_evm::account::CrossAccountId;31use pallet_unique::{32 Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,33 NftApproveBasket, VariableMetaDataBasket, CreateItemBasket, ReFungibleTransferBasket,34 FungibleTransferBasket, NftTransferBasket,35};36use pallet_fungible::Config as FungibleConfig;37use pallet_nonfungible::Config as NonfungibleConfig;38use pallet_refungible::Config as RefungibleConfig;3940pub fn withdraw_transfer<41 T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,42>(43 collection: &CollectionHandle<T>,44 who: &T::CrossAccountId,45 item_id: &TokenId,46) -> Option<()> {47 48 match collection.mode {49 CollectionMode::NFT => {50 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;51 if !owner.conv_eq(who) {52 return None;53 }54 }55 CollectionMode::Fungible(_) => {56 if item_id != &TokenId::default() {57 return None;58 }59 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {60 return None;61 }62 }63 CollectionMode::ReFungible => {64 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {65 return None;66 }67 }68 }6970 71 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;72 let limit = collection73 .limits74 .sponsor_transfer_timeout(match collection.mode {75 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,76 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,78 });7980 let last_tx_block = match collection.mode {81 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),82 CollectionMode::Fungible(_) => {83 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())84 }85 CollectionMode::ReFungible => {86 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))87 }88 };8990 if let Some(last_tx_block) = last_tx_block {91 let timeout = last_tx_block + limit.into();92 if block_number < timeout {93 return None;94 }95 }9697 match collection.mode {98 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),99 CollectionMode::Fungible(_) => {100 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)101 }102 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(103 (collection.id, item_id, who.as_sub()),104 block_number,105 ),106 };107108 Some(())109}110111pub fn withdraw_create_item<112 T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,113>(114 collection: &CollectionHandle<T>,115 who: &T::CrossAccountId,116 _properties: &CreateItemData,117) -> Option<()> {118 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {119 return None;120 }121122 123 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;124 let limit = collection125 .limits126 .sponsor_transfer_timeout(match _properties {127 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,128 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,129 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,130 });131132 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {133 let timeout = last_tx_block + limit.into();134 if block_number < timeout {135 return None;136 }137 }138139 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);140141 Some(())142}143144pub fn withdraw_set_variable_meta_data<145 T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,146>(147 who: &T::CrossAccountId,148 collection: &CollectionHandle<T>,149 item_id: &TokenId,150 data: &[u8],151) -> Option<()> {152 153 if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {154 return None;155 }156 157 match collection.mode {158 CollectionMode::NFT => {159 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;160 if !owner.conv_eq(who) {161 return None;162 }163 }164 CollectionMode::Fungible(_) => {165 if item_id != &TokenId::default() {166 return None;167 }168 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {169 return None;170 }171 }172 CollectionMode::ReFungible => {173 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {174 return None;175 }176 }177 }178179 180 181 if matches!(collection.mode, CollectionMode::Fungible(_)) {182 return None;183 }184 if data.len() > collection.limits.sponsored_data_size() as usize {185 return None;186 }187188 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;189 let limit = collection.limits.sponsored_data_rate_limit()?;190191 if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {192 let timeout = last_tx_block + limit.into();193 if block_number < timeout {194 return None;195 }196 }197198 <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);199200 Some(())201}202203pub fn withdraw_approve<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>(204 collection: &CollectionHandle<T>,205 who: &T::AccountId,206 item_id: &TokenId,207) -> Option<()> {208 209 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;210 let limit = collection.limits.sponsor_approve_timeout();211212 let last_tx_block = match collection.mode {213 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),214 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),215 CollectionMode::ReFungible => {216 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))217 }218 };219220 if let Some(last_tx_block) = last_tx_block {221 let timeout = last_tx_block + limit.into();222 if block_number < timeout {223 return None;224 }225 }226227 match collection.mode {228 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),229 CollectionMode::Fungible(_) => {230 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)231 }232 CollectionMode::ReFungible => {233 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)234 }235 };236237 Some(())238}239240fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {241 let collection = CollectionHandle::new(id)?;242 let sponsor = collection.sponsorship.sponsor().cloned()?;243 Some((sponsor, collection))244}245246pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);247impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>248where249 T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,250 C: IsSubType<UniqueCall<T>>,251{252 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {253 match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {254 UniqueCall::create_item {255 collection_id,256 data,257 ..258 } => {259 let (sponsor, collection) = load(*collection_id)?;260 withdraw_create_item::<T>(261 &collection,262 &T::CrossAccountId::from_sub(who.clone()),263 data,264 )265 .map(|()| sponsor)266 }267 UniqueCall::transfer {268 collection_id,269 item_id,270 ..271 } => {272 let (sponsor, collection) = load(*collection_id)?;273 withdraw_transfer::<T>(274 &collection,275 &T::CrossAccountId::from_sub(who.clone()),276 item_id,277 )278 .map(|()| sponsor)279 }280 UniqueCall::transfer_from {281 collection_id,282 item_id,283 from,284 ..285 } => {286 let (sponsor, collection) = load(*collection_id)?;287 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)288 }289 UniqueCall::approve {290 collection_id,291 item_id,292 ..293 } => {294 let (sponsor, collection) = load(*collection_id)?;295 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)296 }297 UniqueCall::set_variable_meta_data {298 collection_id,299 item_id,300 data,301 } => {302 let (sponsor, collection) = load(*collection_id)?;303 withdraw_set_variable_meta_data::<T>(304 &T::CrossAccountId::from_sub(who.clone()),305 &collection,306 item_id,307 data,308 )309 .map(|()| sponsor)310 }311 _ => None,312 }313 }314}315316use crate::SponsorshipPredict;317pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);318319impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>320where321 T: Config,322{323 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>324 where325 u64: From<<T as frame_system::Config>::BlockNumber>,326 {327 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;328 let _ = collection.sponsorship.sponsor()?;329330 331 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;332 let limit = collection333 .limits334 .sponsor_transfer_timeout(match collection.mode {335 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,336 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,337 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,338 });339340 let last_tx_block = match collection.mode {341 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),342 CollectionMode::Fungible(_) => {343 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())344 }345 CollectionMode::ReFungible => {346 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))347 }348 };349350 if let Some(last_tx_block) = last_tx_block {351 return Some(352 last_tx_block353 .saturating_add(limit.into())354 .saturating_sub(block_number)355 .into(),356 );357 }358359 let token_exists = match collection.mode {360 CollectionMode::NFT => {361 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))362 }363 CollectionMode::Fungible(_) => true,364 CollectionMode::ReFungible => {365 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))366 }367 };368369 if token_exists {370 Some(0)371 } else {372 None373 }374 }375}