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,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, 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 trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}41impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}4243pub fn withdraw_transfer<T: Config>(44 collection: &CollectionHandle<T>,45 who: &T::CrossAccountId,46 item_id: &TokenId,47) -> Option<()> {48 49 match collection.mode {50 CollectionMode::NFT => {51 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;52 if !owner.conv_eq(who) {53 return None;54 }55 }56 CollectionMode::Fungible(_) => {57 if item_id != &TokenId::default() {58 return None;59 }60 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {61 return None;62 }63 }64 CollectionMode::ReFungible => {65 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {66 return None;67 }68 }69 }7071 72 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;73 let limit = collection74 .limits75 .sponsor_transfer_timeout(match collection.mode {76 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,77 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,78 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,79 });8081 let last_tx_block = match collection.mode {82 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),83 CollectionMode::Fungible(_) => {84 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())85 }86 CollectionMode::ReFungible => {87 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))88 }89 };9091 if let Some(last_tx_block) = last_tx_block {92 let timeout = last_tx_block + limit.into();93 if block_number < timeout {94 return None;95 }96 }9798 match collection.mode {99 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),100 CollectionMode::Fungible(_) => {101 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)102 }103 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(104 (collection.id, item_id, who.as_sub()),105 block_number,106 ),107 };108109 Some(())110}111112pub fn withdraw_create_item<T: Config>(113 collection: &CollectionHandle<T>,114 who: &T::CrossAccountId,115 _properties: &CreateItemData,116) -> Option<()> {117 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {118 return None;119 }120121 122 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;123 let limit = collection124 .limits125 .sponsor_transfer_timeout(match _properties {126 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,127 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,128 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,129 });130131 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {132 let timeout = last_tx_block + limit.into();133 if block_number < timeout {134 return None;135 }136 }137138 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);139140 Some(())141}142143pub fn withdraw_approve<T: Config>(144 collection: &CollectionHandle<T>,145 who: &T::AccountId,146 item_id: &TokenId,147) -> Option<()> {148 149 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;150 let limit = collection.limits.sponsor_approve_timeout();151152 let last_tx_block = match collection.mode {153 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),154 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),155 CollectionMode::ReFungible => {156 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))157 }158 };159160 if let Some(last_tx_block) = last_tx_block {161 let timeout = last_tx_block + limit.into();162 if block_number < timeout {163 return None;164 }165 }166167 match collection.mode {168 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),169 CollectionMode::Fungible(_) => {170 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)171 }172 CollectionMode::ReFungible => {173 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)174 }175 };176177 Some(())178}179180fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {181 let collection = CollectionHandle::new(id)?;182 let sponsor = collection.sponsorship.sponsor().cloned()?;183 Some((sponsor, collection))184}185186pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);187impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>188where189 T: Config,190 C: IsSubType<UniqueCall<T>>,191{192 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {193 match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {194 UniqueCall::create_item {195 collection_id,196 data,197 ..198 } => {199 let (sponsor, collection) = load(*collection_id)?;200 withdraw_create_item::<T>(201 &collection,202 &T::CrossAccountId::from_sub(who.clone()),203 data,204 )205 .map(|()| sponsor)206 }207 UniqueCall::transfer {208 collection_id,209 item_id,210 ..211 } => {212 let (sponsor, collection) = load(*collection_id)?;213 withdraw_transfer::<T>(214 &collection,215 &T::CrossAccountId::from_sub(who.clone()),216 item_id,217 )218 .map(|()| sponsor)219 }220 UniqueCall::transfer_from {221 collection_id,222 item_id,223 from,224 ..225 } => {226 let (sponsor, collection) = load(*collection_id)?;227 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)228 }229 UniqueCall::approve {230 collection_id,231 item_id,232 ..233 } => {234 let (sponsor, collection) = load(*collection_id)?;235 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)236 }237 _ => None,238 }239 }240}241242pub trait SponsorshipPredict<T: Config> {243 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>244 where245 u64: From<<T as frame_system::Config>::BlockNumber>;246}247248pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);249250impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {251 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>252 where253 u64: From<<T as frame_system::Config>::BlockNumber>,254 {255 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;256 let _ = collection.sponsorship.sponsor()?;257258 259 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;260 let limit = collection261 .limits262 .sponsor_transfer_timeout(match collection.mode {263 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,264 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,265 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,266 });267268 let last_tx_block = match collection.mode {269 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),270 CollectionMode::Fungible(_) => {271 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())272 }273 CollectionMode::ReFungible => {274 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))275 }276 };277278 if let Some(last_tx_block) = last_tx_block {279 return Some(280 last_tx_block281 .saturating_add(limit.into())282 .saturating_sub(block_number)283 .into(),284 );285 }286287 let token_exists = match collection.mode {288 CollectionMode::NFT => {289 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))290 }291 CollectionMode::Fungible(_) => token == TokenId::default(),292 CollectionMode::ReFungible => {293 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))294 }295 };296297 if token_exists {298 Some(0)299 } else {300 None301 }302 }303}