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 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_set_variable_meta_data<T: Config>(144 who: &T::CrossAccountId,145 collection: &CollectionHandle<T>,146 item_id: &TokenId,147 data: &[u8],148) -> Option<()> {149 150 if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {151 return None;152 }153 154 match collection.mode {155 CollectionMode::NFT => {156 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;157 if !owner.conv_eq(who) {158 return None;159 }160 }161 CollectionMode::Fungible(_) => {162 if item_id != &TokenId::default() {163 return None;164 }165 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {166 return None;167 }168 }169 CollectionMode::ReFungible => {170 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {171 return None;172 }173 }174 }175176 177 178 if matches!(collection.mode, CollectionMode::Fungible(_)) {179 return None;180 }181 if data.len() > collection.limits.sponsored_data_size() as usize {182 return None;183 }184185 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;186 let limit = collection.limits.sponsored_data_rate_limit()?;187188 if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {189 let timeout = last_tx_block + limit.into();190 if block_number < timeout {191 return None;192 }193 }194195 <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);196197 Some(())198}199200pub fn withdraw_approve<T: Config>(201 collection: &CollectionHandle<T>,202 who: &T::AccountId,203 item_id: &TokenId,204) -> Option<()> {205 206 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;207 let limit = collection.limits.sponsor_approve_timeout();208209 let last_tx_block = match collection.mode {210 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),211 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),212 CollectionMode::ReFungible => {213 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))214 }215 };216217 if let Some(last_tx_block) = last_tx_block {218 let timeout = last_tx_block + limit.into();219 if block_number < timeout {220 return None;221 }222 }223224 match collection.mode {225 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),226 CollectionMode::Fungible(_) => {227 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)228 }229 CollectionMode::ReFungible => {230 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)231 }232 };233234 Some(())235}236237fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {238 let collection = CollectionHandle::new(id)?;239 let sponsor = collection.sponsorship.sponsor().cloned()?;240 Some((sponsor, collection))241}242243pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);244impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>245where246 T: Config,247 C: IsSubType<UniqueCall<T>>,248{249 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {250 match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {251 UniqueCall::create_item {252 collection_id,253 data,254 ..255 } => {256 let (sponsor, collection) = load(*collection_id)?;257 withdraw_create_item::<T>(258 &collection,259 &T::CrossAccountId::from_sub(who.clone()),260 data,261 )262 .map(|()| sponsor)263 }264 UniqueCall::transfer {265 collection_id,266 item_id,267 ..268 } => {269 let (sponsor, collection) = load(*collection_id)?;270 withdraw_transfer::<T>(271 &collection,272 &T::CrossAccountId::from_sub(who.clone()),273 item_id,274 )275 .map(|()| sponsor)276 }277 UniqueCall::transfer_from {278 collection_id,279 item_id,280 from,281 ..282 } => {283 let (sponsor, collection) = load(*collection_id)?;284 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)285 }286 UniqueCall::approve {287 collection_id,288 item_id,289 ..290 } => {291 let (sponsor, collection) = load(*collection_id)?;292 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)293 }294 UniqueCall::set_variable_meta_data {295 collection_id,296 item_id,297 data,298 } => {299 let (sponsor, collection) = load(*collection_id)?;300 withdraw_set_variable_meta_data::<T>(301 &T::CrossAccountId::from_sub(who.clone()),302 &collection,303 item_id,304 data,305 )306 .map(|()| sponsor)307 }308 _ => None,309 }310 }311}312313pub trait SponsorshipPredict<T: Config> {314 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>315 where316 u64: From<<T as frame_system::Config>::BlockNumber>;317}318319pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);320321impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {322 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>323 where324 u64: From<<T as frame_system::Config>::BlockNumber>,325 {326 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;327 let _ = collection.sponsorship.sponsor()?;328329 330 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;331 let limit = collection332 .limits333 .sponsor_transfer_timeout(match collection.mode {334 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,335 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,336 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,337 });338339 let last_tx_block = match collection.mode {340 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),341 CollectionMode::Fungible(_) => {342 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())343 }344 CollectionMode::ReFungible => {345 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))346 }347 };348349 if let Some(last_tx_block) = last_tx_block {350 return Some(351 last_tx_block352 .saturating_add(limit.into())353 .saturating_sub(block_number)354 .into(),355 );356 }357358 let token_exists = match collection.mode {359 CollectionMode::NFT => {360 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))361 }362 CollectionMode::Fungible(_) => token == TokenId::default(),363 CollectionMode::ReFungible => {364 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))365 }366 };367368 if token_exists {369 Some(0)370 } else {371 None372 }373 }374}