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, NFT_SPONSOR_TRANSFER_TIMEOUT,25 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData,26};27use sp_runtime::traits::Saturating;28use pallet_common::{CollectionHandle};29use pallet_evm::account::CrossAccountId;30use pallet_unique::{31 Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,32 NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket,33 NftTransferBasket, TokenPropertyBasket,34};35use pallet_fungible::Config as FungibleConfig;36use pallet_nonfungible::Config as NonfungibleConfig;37use pallet_refungible::Config as RefungibleConfig;3839pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}40impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}414243pub fn withdraw_set_token_property<T: Config>(44 collection: &CollectionHandle<T>,45 who: &T::CrossAccountId,46 item_id: &TokenId,47 data_size: usize,48) -> Option<()> {49 50 match collection.mode {51 CollectionMode::NFT => {52 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;53 if !owner.conv_eq(who) {54 return None;55 }56 }57 CollectionMode::Fungible(_) => {58 59 return None;60 }61 CollectionMode::ReFungible => {62 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {63 return None;64 }65 }66 }6768 if data_size > collection.limits.sponsored_data_size() as usize {69 return None;70 }7172 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;73 let limit = collection.limits.sponsored_data_rate_limit()?;7475 if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {76 let timeout = last_tx_block + limit.into();77 if block_number < timeout {78 return None;79 }80 }8182 <TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);8384 Some(())85}8687pub fn withdraw_transfer<T: Config>(88 collection: &CollectionHandle<T>,89 who: &T::CrossAccountId,90 item_id: &TokenId,91) -> Option<()> {92 93 match collection.mode {94 CollectionMode::NFT => {95 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;96 if !owner.conv_eq(who) {97 return None;98 }99 }100 CollectionMode::Fungible(_) => {101 if item_id != &TokenId::default() {102 return None;103 }104 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {105 return None;106 }107 }108 CollectionMode::ReFungible => {109 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {110 return None;111 }112 }113 }114115 116 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;117 let limit = collection118 .limits119 .sponsor_transfer_timeout(match collection.mode {120 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,121 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,122 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,123 });124125 let last_tx_block = match collection.mode {126 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),127 CollectionMode::Fungible(_) => {128 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())129 }130 CollectionMode::ReFungible => {131 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))132 }133 };134135 if let Some(last_tx_block) = last_tx_block {136 let timeout = last_tx_block + limit.into();137 if block_number < timeout {138 return None;139 }140 }141142 match collection.mode {143 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),144 CollectionMode::Fungible(_) => {145 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)146 }147 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(148 (collection.id, item_id, who.as_sub()),149 block_number,150 ),151 };152153 Some(())154}155156pub fn withdraw_create_item<T: Config>(157 collection: &CollectionHandle<T>,158 who: &T::CrossAccountId,159 _properties: &CreateItemData,160) -> Option<()> {161 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {162 return None;163 }164165 166 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;167 let limit = collection168 .limits169 .sponsor_transfer_timeout(match _properties {170 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,171 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,172 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,173 });174175 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {176 let timeout = last_tx_block + limit.into();177 if block_number < timeout {178 return None;179 }180 }181182 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);183184 Some(())185}186187pub fn withdraw_approve<T: Config>(188 collection: &CollectionHandle<T>,189 who: &T::AccountId,190 item_id: &TokenId,191) -> Option<()> {192 193 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;194 let limit = collection.limits.sponsor_approve_timeout();195196 let last_tx_block = match collection.mode {197 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),198 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),199 CollectionMode::ReFungible => {200 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))201 }202 };203204 if let Some(last_tx_block) = last_tx_block {205 let timeout = last_tx_block + limit.into();206 if block_number < timeout {207 return None;208 }209 }210211 match collection.mode {212 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),213 CollectionMode::Fungible(_) => {214 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)215 }216 CollectionMode::ReFungible => {217 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)218 }219 };220221 Some(())222}223224fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {225 let collection = CollectionHandle::new(id)?;226 let sponsor = collection.sponsorship.sponsor().cloned()?;227 Some((sponsor, collection))228}229230pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);231impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>232where233 T: Config,234 C: IsSubType<UniqueCall<T>>,235{236 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {237 match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {238 UniqueCall::set_token_properties {239 collection_id,240 token_id,241 properties,242 ..243 } => {244 let (sponsor, collection) = load::<T>(*collection_id)?;245 withdraw_set_token_property(246 &collection,247 &T::CrossAccountId::from_sub(who.clone()),248 &token_id,249 250 properties.iter().map(|p| p.key.len() + p.value.len()).sum(),251 )252 .map(|()| sponsor)253 }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 _ => None,298 }299 }300}301302pub trait SponsorshipPredict<T: Config> {303 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>304 where305 u64: From<<T as frame_system::Config>::BlockNumber>;306}307308pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);309310impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {311 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>312 where313 u64: From<<T as frame_system::Config>::BlockNumber>,314 {315 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;316 let _ = collection.sponsorship.sponsor()?;317318 319 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;320 let limit = collection321 .limits322 .sponsor_transfer_timeout(match collection.mode {323 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,324 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,325 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,326 });327328 let last_tx_block = match collection.mode {329 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),330 CollectionMode::Fungible(_) => {331 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())332 }333 CollectionMode::ReFungible => {334 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))335 }336 };337338 if let Some(last_tx_block) = last_tx_block {339 return Some(340 last_tx_block341 .saturating_add(limit.into())342 .saturating_sub(block_number)343 .into(),344 );345 }346347 let token_exists = match collection.mode {348 CollectionMode::NFT => {349 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))350 }351 CollectionMode::Fungible(_) => token == TokenId::default(),352 CollectionMode::ReFungible => {353 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))354 }355 };356357 if token_exists {358 Some(0)359 } else {360 None361 }362 }363}