git.delta.rocks / unique-network / refs/commits / 750f6e16db9b

difftreelog

source

runtime/common/sponsoring.rs10.8 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use core::marker::PhantomData;1819use frame_support::traits::IsSubType;20use frame_system::pallet_prelude::*;21use pallet_common::CollectionHandle;22use pallet_evm::account::CrossAccountId;23use pallet_fungible::Config as FungibleConfig;24use pallet_nonfungible::Config as NonfungibleConfig;25use pallet_refungible::Config as RefungibleConfig;26use pallet_unique::{27	Call as UniqueCall, Config as UniqueConfig, CreateItemBasket, FungibleApproveBasket,28	FungibleTransferBasket, NftApproveBasket, NftTransferBasket, ReFungibleTransferBasket,29	RefungibleApproveBasket, TokenPropertyBasket,30};31use sp_runtime::traits::Saturating;32use up_data_structs::{33	CollectionId, CollectionMode, CreateItemData, TokenId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,34	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35};36use up_sponsorship::SponsorshipHandler;3738pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}39impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}4041// TODO: permission check?42pub fn withdraw_set_existing_token_property<T: Config>(43	collection: &CollectionHandle<T>,44	who: &T::CrossAccountId,45	item_id: &TokenId,46	data_size: usize,47) -> Option<()> {48	// preliminary sponsoring correctness check49	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			// Fungible tokens have no properties58			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	withdraw_set_token_property(collection, item_id, data_size)68}6970pub fn withdraw_set_token_property<T: Config>(71	collection: &CollectionHandle<T>,72	item_id: &TokenId,73	data_size: usize,74) -> Option<()> {75	if data_size == 0 {76		return Some(());77	}78	if data_size > collection.limits.sponsored_data_size() as usize {79		return None;80	}8182	let block_number = <frame_system::Pallet<T>>::block_number() as BlockNumberFor<T>;83	let limit = collection.limits.sponsored_data_rate_limit()?;8485	if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {86		let timeout = last_tx_block + limit.into();87		if block_number < timeout {88			return None;89		}90	}9192	<TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);9394	Some(())95}9697pub fn withdraw_transfer<T: Config>(98	collection: &CollectionHandle<T>,99	who: &T::CrossAccountId,100	item_id: &TokenId,101) -> Option<()> {102	// preliminary sponsoring correctness check103	match collection.mode {104		CollectionMode::NFT => {105			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;106			if !owner.conv_eq(who) {107				return None;108			}109		}110		CollectionMode::Fungible(_) => {111			if item_id != &TokenId::default() {112				return None;113			}114			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {115				return None;116			}117		}118		CollectionMode::ReFungible => {119			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {120				return None;121			}122		}123	}124125	// sponsor timeout126	let block_number = <frame_system::Pallet<T>>::block_number() as BlockNumberFor<T>;127	let limit = collection128		.limits129		.sponsor_transfer_timeout(match collection.mode {130			CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,131			CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,132			CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,133		});134135	let last_tx_block = match collection.mode {136		CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),137		CollectionMode::Fungible(_) => {138			<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())139		}140		CollectionMode::ReFungible => {141			<ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))142		}143	};144145	if let Some(last_tx_block) = last_tx_block {146		let timeout = last_tx_block + limit.into();147		if block_number < timeout {148			return None;149		}150	}151152	match collection.mode {153		CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),154		CollectionMode::Fungible(_) => {155			<FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)156		}157		CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(158			(collection.id, item_id, who.as_sub()),159			block_number,160		),161	};162163	Some(())164}165166pub fn withdraw_create_item<T: Config>(167	collection: &CollectionHandle<T>,168	who: &T::CrossAccountId,169	properties: &CreateItemData,170) -> Option<()> {171	// sponsor timeout172	let block_number = <frame_system::Pallet<T>>::block_number() as BlockNumberFor<T>;173	let limit = collection174		.limits175		.sponsor_transfer_timeout(match properties {176			CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,177			CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,178			CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,179		});180181	if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {182		let timeout = last_tx_block + limit.into();183		if block_number < timeout {184			return None;185		}186	}187	CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);188189	Some(())190}191192pub fn withdraw_approve<T: Config>(193	collection: &CollectionHandle<T>,194	who: &T::AccountId,195	item_id: &TokenId,196) -> Option<()> {197	// sponsor timeout198	let block_number = <frame_system::Pallet<T>>::block_number() as BlockNumberFor<T>;199	let limit = collection.limits.sponsor_approve_timeout();200201	let last_tx_block = match collection.mode {202		CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),203		CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),204		CollectionMode::ReFungible => {205			<RefungibleApproveBasket<T>>::get((collection.id, item_id, who))206		}207	};208209	if let Some(last_tx_block) = last_tx_block {210		let timeout = last_tx_block + limit.into();211		if block_number < timeout {212			return None;213		}214	}215216	match collection.mode {217		CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),218		CollectionMode::Fungible(_) => {219			<FungibleApproveBasket<T>>::insert(collection.id, who, block_number)220		}221		CollectionMode::ReFungible => {222			<RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)223		}224	};225226	Some(())227}228229fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {230	let collection = CollectionHandle::new(id)?;231	let sponsor = collection.sponsorship.sponsor().cloned()?;232	Some((sponsor, collection))233}234235pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);236impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>237where238	T: Config,239	C: IsSubType<UniqueCall<T>>,240{241	fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {242		match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {243			UniqueCall::set_token_properties {244				collection_id,245				token_id,246				properties,247				..248			} => {249				let (sponsor, collection) = load::<T>(*collection_id)?;250				withdraw_set_existing_token_property(251					&collection,252					&T::CrossAccountId::from_sub(who.clone()),253					token_id,254					// No overflow may happen, as data larger than usize can't reach here255					properties.iter().map(|p| p.key.len() + p.value.len()).sum(),256				)257				.map(|()| sponsor)258			}259			UniqueCall::create_item {260				collection_id,261				data,262				..263			} => {264				let (sponsor, collection) = load(*collection_id)?;265				withdraw_create_item::<T>(266					&collection,267					&T::CrossAccountId::from_sub(who.clone()),268					data,269				)270				.map(|()| sponsor)271			}272			UniqueCall::transfer {273				collection_id,274				item_id,275				..276			} => {277				let (sponsor, collection) = load(*collection_id)?;278				withdraw_transfer::<T>(279					&collection,280					&T::CrossAccountId::from_sub(who.clone()),281					item_id,282				)283				.map(|()| sponsor)284			}285			UniqueCall::transfer_from {286				collection_id,287				item_id,288				from,289				..290			} => {291				let (sponsor, collection) = load(*collection_id)?;292				withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)293			}294			UniqueCall::approve {295				collection_id,296				item_id,297				..298			} => {299				let (sponsor, collection) = load(*collection_id)?;300				withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)301			}302			_ => None,303		}304	}305}306307pub trait SponsorshipPredict<T: Config> {308	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>309	where310		u64: From<BlockNumberFor<T>>;311}312313pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);314315impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {316	fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>317	where318		u64: From<BlockNumberFor<T>>,319	{320		let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;321		let _ = collection.sponsorship.sponsor()?;322323		// sponsor timeout324		let block_number = <frame_system::Pallet<T>>::block_number() as BlockNumberFor<T>;325		let limit = collection326			.limits327			.sponsor_transfer_timeout(match collection.mode {328				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,329				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,330				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,331			});332333		let last_tx_block = match collection.mode {334			CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),335			CollectionMode::Fungible(_) => {336				<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())337			}338			CollectionMode::ReFungible => {339				<ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))340			}341		};342343		if let Some(last_tx_block) = last_tx_block {344			return Some(345				last_tx_block346					.saturating_add(limit.into())347					.saturating_sub(block_number)348					.into(),349			);350		}351352		let token_exists = match collection.mode {353			CollectionMode::NFT => {354				<pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))355			}356			CollectionMode::Fungible(_) => token == TokenId::default(),357			CollectionMode::ReFungible => {358				<pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))359			}360		};361362		if token_exists {363			Some(0)364		} else {365			None366		}367	}368}