1234567891011121314151617use core::marker::PhantomData;18use up_sponsorship::SponsorshipHandler;19use frame_support::{20 traits::{IsSubType},21};22use up_data_structs::{23 CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT,24 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData,25};26use sp_runtime::traits::Saturating;27use pallet_common::{CollectionHandle};28use pallet_evm::account::CrossAccountId;29use pallet_unique::{30 Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,31 NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket,32 NftTransferBasket, TokenPropertyBasket,33};34use pallet_fungible::Config as FungibleConfig;35use pallet_nonfungible::Config as NonfungibleConfig;36use pallet_refungible::Config as RefungibleConfig;3738pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}39impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}404142pub fn withdraw_set_token_property<T: Config>(43 collection: &CollectionHandle<T>,44 who: &T::CrossAccountId,45 item_id: &TokenId,46 data_size: usize,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 58 return None;59 }60 CollectionMode::ReFungible => {61 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {62 return None;63 }64 }65 }6667 if data_size > collection.limits.sponsored_data_size() as usize {68 return None;69 }7071 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;72 let limit = collection.limits.sponsored_data_rate_limit()?;7374 if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {75 let timeout = last_tx_block + limit.into();76 if block_number < timeout {77 return None;78 }79 }8081 <TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);8283 Some(())84}8586pub fn withdraw_transfer<T: Config>(87 collection: &CollectionHandle<T>,88 who: &T::CrossAccountId,89 item_id: &TokenId,90) -> Option<()> {91 92 match collection.mode {93 CollectionMode::NFT => {94 let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;95 if !owner.conv_eq(who) {96 return None;97 }98 }99 CollectionMode::Fungible(_) => {100 if item_id != &TokenId::default() {101 return None;102 }103 if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {104 return None;105 }106 }107 CollectionMode::ReFungible => {108 if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {109 return None;110 }111 }112 }113114 115 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;116 let limit = collection117 .limits118 .sponsor_transfer_timeout(match collection.mode {119 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,120 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,121 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,122 });123124 let last_tx_block = match collection.mode {125 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),126 CollectionMode::Fungible(_) => {127 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())128 }129 CollectionMode::ReFungible => {130 <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))131 }132 };133134 if let Some(last_tx_block) = last_tx_block {135 let timeout = last_tx_block + limit.into();136 if block_number < timeout {137 return None;138 }139 }140141 match collection.mode {142 CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),143 CollectionMode::Fungible(_) => {144 <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)145 }146 CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(147 (collection.id, item_id, who.as_sub()),148 block_number,149 ),150 };151152 Some(())153}154155pub fn withdraw_create_item<T: Config>(156 collection: &CollectionHandle<T>,157 who: &T::CrossAccountId,158 properties: &CreateItemData,159) -> Option<()> {160 161 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;162 let limit = collection163 .limits164 .sponsor_transfer_timeout(match properties {165 CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,166 CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,167 CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,168 });169170 if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {171 let timeout = last_tx_block + limit.into();172 if block_number < timeout {173 return None;174 }175 }176177 CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);178179 Some(())180}181182pub fn withdraw_approve<T: Config>(183 collection: &CollectionHandle<T>,184 who: &T::AccountId,185 item_id: &TokenId,186) -> Option<()> {187 188 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;189 let limit = collection.limits.sponsor_approve_timeout();190191 let last_tx_block = match collection.mode {192 CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),193 CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),194 CollectionMode::ReFungible => {195 <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))196 }197 };198199 if let Some(last_tx_block) = last_tx_block {200 let timeout = last_tx_block + limit.into();201 if block_number < timeout {202 return None;203 }204 }205206 match collection.mode {207 CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),208 CollectionMode::Fungible(_) => {209 <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)210 }211 CollectionMode::ReFungible => {212 <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)213 }214 };215216 Some(())217}218219fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {220 let collection = CollectionHandle::new(id)?;221 let sponsor = collection.sponsorship.sponsor().cloned()?;222 Some((sponsor, collection))223}224225pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);226impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>227where228 T: Config,229 C: IsSubType<UniqueCall<T>>,230{231 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {232 match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {233 UniqueCall::set_token_properties {234 collection_id,235 token_id,236 properties,237 ..238 } => {239 let (sponsor, collection) = load::<T>(*collection_id)?;240 withdraw_set_token_property(241 &collection,242 &T::CrossAccountId::from_sub(who.clone()),243 token_id,244 245 properties.iter().map(|p| p.key.len() + p.value.len()).sum(),246 )247 .map(|()| sponsor)248 }249 UniqueCall::create_item {250 collection_id,251 data,252 ..253 } => {254 let (sponsor, collection) = load(*collection_id)?;255 withdraw_create_item::<T>(256 &collection,257 &T::CrossAccountId::from_sub(who.clone()),258 data,259 )260 .map(|()| sponsor)261 }262 UniqueCall::transfer {263 collection_id,264 item_id,265 ..266 } => {267 let (sponsor, collection) = load(*collection_id)?;268 withdraw_transfer::<T>(269 &collection,270 &T::CrossAccountId::from_sub(who.clone()),271 item_id,272 )273 .map(|()| sponsor)274 }275 UniqueCall::transfer_from {276 collection_id,277 item_id,278 from,279 ..280 } => {281 let (sponsor, collection) = load(*collection_id)?;282 withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)283 }284 UniqueCall::approve {285 collection_id,286 item_id,287 ..288 } => {289 let (sponsor, collection) = load(*collection_id)?;290 withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)291 }292 _ => None,293 }294 }295}296297pub trait SponsorshipPredict<T: Config> {298 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>299 where300 u64: From<<T as frame_system::Config>::BlockNumber>;301}302303pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);304305impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {306 fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>307 where308 u64: From<<T as frame_system::Config>::BlockNumber>,309 {310 let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;311 let _ = collection.sponsorship.sponsor()?;312313 314 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;315 let limit = collection316 .limits317 .sponsor_transfer_timeout(match collection.mode {318 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,319 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,320 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,321 });322323 let last_tx_block = match collection.mode {324 CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),325 CollectionMode::Fungible(_) => {326 <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())327 }328 CollectionMode::ReFungible => {329 <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))330 }331 };332333 if let Some(last_tx_block) = last_tx_block {334 return Some(335 last_tx_block336 .saturating_add(limit.into())337 .saturating_sub(block_number)338 .into(),339 );340 }341342 let token_exists = match collection.mode {343 CollectionMode::NFT => {344 <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))345 }346 CollectionMode::Fungible(_) => token == TokenId::default(),347 CollectionMode::ReFungible => {348 <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))349 }350 };351352 if token_exists {353 Some(0)354 } else {355 None356 }357 }358}