1234567891011121314151617use crate::{18 Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket,19 FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket,20 FungibleApproveBasket, RefungibleApproveBasket,21};22use core::marker::PhantomData;23use up_sponsorship::SponsorshipHandler;24use frame_support::{25 traits::{IsSubType},26 storage::{StorageMap, StorageDoubleMap, StorageNMap},27};28use up_data_structs::{29 CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission,30 NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,31};32use sp_runtime::traits::Saturating;33use pallet_common::{CollectionHandle};34use pallet_evm::account::CrossAccountId;3536pub fn withdraw_transfer<T: Config>(37 collection: &CollectionHandle<T>,38 who: &T::CrossAccountId,39 item_id: &TokenId,40) -> Option<()> {41 42 match collection.mode {43 CollectionMode::NFT => {44 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;45 if !owner.conv_eq(who) {46 return None;47 }48 }49 CollectionMode::Fungible(_) => {50 if item_id != &TokenId::default() {51 return None;52 }53 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {54 return None;55 }56 }57 CollectionMode::ReFungible => {58 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {59 return None;60 }61 }62 }6364 65 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;66 let limit = collection67 .limits68 .sponsor_transfer_timeout(match collection.mode {69 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,70 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,71 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,72 });7374 let last_tx_block = match collection.mode {75 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),76 CollectionMode::Fungible(_) => {77 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())78 }79 CollectionMode::ReFungible => {80 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))81 }82 };8384 if let Some(last_tx_block) = last_tx_block {85 let timeout = last_tx_block + limit.into();86 if block_number < timeout {87 return None;88 }89 }9091 match collection.mode {92 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),93 CollectionMode::Fungible(_) => {94 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)95 }96 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(97 (collection.id, item_id, who.as_sub()),98 block_number,99 ),100 };101102 Some(())103}104105pub fn withdraw_create_item<T: Config>(106 collection: &CollectionHandle<T>,107 who: &T::CrossAccountId,108 _properties: &CreateItemData,109) -> Option<()> {110 if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {111 return None;112 }113114 115 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;116 let limit = collection117 .limits118 .sponsor_transfer_timeout(match _properties {119 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,120 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,121 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,122 });123124 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {125 let timeout = last_tx_block + limit.into();126 if block_number < timeout {127 return None;128 }129 }130131 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);132133 Some(())134}135136pub fn withdraw_set_variable_meta_data<T: Config>(137 who: &T::CrossAccountId,138 collection: &CollectionHandle<T>,139 item_id: &TokenId,140 data: &[u8],141) -> Option<()> {142 143 if collection.meta_update_permission != MetaUpdatePermission::ItemOwner {144 return None;145 }146 147 match collection.mode {148 CollectionMode::NFT => {149 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;150 if !owner.conv_eq(who) {151 return None;152 }153 }154 CollectionMode::Fungible(_) => {155 if item_id != &TokenId::default() {156 return None;157 }158 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {159 return None;160 }161 }162 CollectionMode::ReFungible => {163 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {164 return None;165 }166 }167 }168169 170 171 if matches!(collection.mode, CollectionMode::Fungible(_)) {172 return None;173 }174 if data.len() > collection.limits.sponsored_data_size() as usize {175 return None;176 }177178 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;179 let limit = collection.limits.sponsored_data_rate_limit()?;180181 if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {182 let timeout = last_tx_block + limit.into();183 if block_number < timeout {184 return None;185 }186 }187188 <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);189190 Some(())191}192193pub fn withdraw_approve<T: Config>(194 collection: &CollectionHandle<T>,195 who: &T::AccountId,196 item_id: &TokenId,197) -> Option<()> {198 199 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;200 let limit = collection.limits.sponsor_approve_timeout();201202 let last_tx_block = match collection.mode {203 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),204 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),205 CollectionMode::ReFungible => {206 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))207 }208 };209210 if let Some(last_tx_block) = last_tx_block {211 let timeout = last_tx_block + limit.into();212 if block_number < timeout {213 return None;214 }215 }216217 match collection.mode {218 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),219 CollectionMode::Fungible(_) => {220 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)221 }222 CollectionMode::ReFungible => {223 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)224 }225 };226227 Some(())228}229230fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {231 let collection = CollectionHandle::new(id)?;232 let sponsor = collection.sponsorship.sponsor().cloned()?;233 Some((sponsor, collection))234}235236pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);237impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>238where239 T: Config,240 C: IsSubType<Call<T>>,241{242 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {243 match IsSubType::<Call<T>>::is_sub_type(call)? {244 Call::create_item {245 collection_id,246 data,247 ..248 } => {249 let (sponsor, collection) = load(*collection_id)?;250 withdraw_create_item::<T>(251 &collection,252 &T::CrossAccountId::from_sub(who.clone()),253 data,254 )255 .map(|()| sponsor)256 }257 Call::transfer {258 collection_id,259 item_id,260 ..261 } => {262 let (sponsor, collection) = load(*collection_id)?;263 withdraw_transfer::<T>(264 &collection,265 &T::CrossAccountId::from_sub(who.clone()),266 item_id,267 )268 .map(|()| sponsor)269 }270 Call::transfer_from {271 collection_id,272 item_id,273 from,274 ..275 } => {276 let (sponsor, collection) = load(*collection_id)?;277 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)278 }279 Call::approve {280 collection_id,281 item_id,282 ..283 } => {284 let (sponsor, collection) = load(*collection_id)?;285 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)286 }287 Call::set_variable_meta_data {288 collection_id,289 item_id,290 data,291 } => {292 let (sponsor, collection) = load(*collection_id)?;293 withdraw_set_variable_meta_data::<T>(294 &T::CrossAccountId::from_sub(who.clone()),295 &collection,296 item_id,297 data,298 )299 .map(|()| sponsor)300 }301 _ => None,302 }303 }304}305306use crate::SponsorshipPredict;307pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);308309impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>310where311 T: Config,312{313 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>314 where315 u64: From<<T as frame_system::Config>::BlockNumber>,316 {317 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;318 let _ = collection.sponsorship.sponsor()?;319320 321 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;322 let limit = collection323 .limits324 .sponsor_transfer_timeout(match collection.mode {325 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,326 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,327 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,328 });329330 let last_tx_block = match collection.mode {331 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),332 CollectionMode::Fungible(_) => {333 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())334 }335 CollectionMode::ReFungible => {336 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))337 }338 };339340 if let Some(last_tx_block) = last_tx_block {341 return Some(342 last_tx_block343 .saturating_add(limit.into())344 .saturating_sub(block_number)345 .into(),346 );347 }348349 let token_exists = match collection.mode {350 CollectionMode::NFT => {351 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))352 }353 CollectionMode::Fungible(_) => true,354 CollectionMode::ReFungible => {355 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))356 }357 };358359 if token_exists {360 Some(0)361 } else {362 None363 }364 }365}