From afcf32ad6db41d185696af985b0345c8478526c8 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 24 Jun 2021 19:43:39 +0000 Subject: [PATCH] feat: implement sponsoring primitive for nft --- --- a/pallets/nft/Cargo.toml +++ b/pallets/nft/Cargo.toml @@ -30,6 +30,7 @@ 'pallet-transaction-payment/std', 'fp-evm/std', 'nft-data-structs/std', + 'up-sponsorship/std', 'sp-std/std', 'sp-api/std', 'sp-runtime/std', @@ -135,9 +136,14 @@ [dependencies.nft-data-structs] default-features = false -path = '../../primitives' +path = '../../primitives/nft' version = '0.9.0' +[dependencies.up-sponsorship] +default-features = false +path = '../../primitives/sponsorship' +version = '0.1.0' + [dependencies] ethereum-tx-sign = { version = "3.0.4", optional = true } --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -50,6 +50,8 @@ mod default_weights; mod eth; +mod sponsorship; +pub use sponsorship::NftSponsorshipHandler; pub use eth::NftErcSupport; pub use eth::account::*; @@ -2232,12 +2234,6 @@ ) -> DispatchResult { Self::remove_token_index(collection_id, item_index, old_owner)?; Self::add_token_index(collection_id, item_index, new_owner)?; - - Ok(()) - } - - fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult { - ensure!(>::get(contract) == Some(account), Error::::NoPermission); Ok(()) } --- /dev/null +++ b/pallets/nft/src/sponsorship.rs @@ -0,0 +1,203 @@ +use crate::{Config, Call, CollectionById, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, FungibleTransferBasket, NftTransferBasket, ChainLimit, CreateItemData, CollectionMode}; +use core::marker::PhantomData; +use up_sponsorship::SponsorshipHandler; +use frame_support::{ + traits::IsSubType, + storage::{StorageMap, StorageDoubleMap, StorageValue}, +}; +use nft_data_structs::{TokenId, CollectionId}; +use alloc::vec::Vec; + +pub struct NftSponsorshipHandler(PhantomData); +impl NftSponsorshipHandler { + pub fn withdraw_create_item( + who: &T::AccountId, + collection_id: &CollectionId, + _properties: &CreateItemData, + ) -> Option { + + let collection = CollectionById::::get(collection_id)?; + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + + let limit = collection.limits.sponsor_transfer_timeout; + if CreateItemBasket::::contains_key((collection_id, &who)) { + let last_tx_block = CreateItemBasket::::get((collection_id, &who)); + let limit_time = last_tx_block + limit.into(); + if block_number <= limit_time { + return None; + } + } + CreateItemBasket::::insert((collection_id, who.clone()), block_number); + + // check free create limit + if collection.limits.sponsored_data_size >= (_properties.len() as u32) { + collection.sponsorship.sponsor() + .cloned() + } else { + None + } + } + + pub fn withdraw_transfer( + who: &T::AccountId, + collection_id: &CollectionId, + item_id: &TokenId, + ) -> Option { + + let collection = CollectionById::::get(collection_id)?; + let limits = ChainLimit::get(); + + let mut sponsor_transfer = false; + if collection.sponsorship.confirmed() { + + let collection_limits = collection.limits.clone(); + let collection_mode = collection.mode.clone(); + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + sponsor_transfer = match collection_mode { + CollectionMode::NFT => { + + // get correct limit + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout + } else { + limits.nft_sponsor_transfer_timeout + }; + + let mut sponsored = true; + if NftTransferBasket::::contains_key(collection_id, item_id) { + let last_tx_block = NftTransferBasket::::get(collection_id, item_id); + let limit_time = last_tx_block + limit.into(); + if block_number <= limit_time { + sponsored = false; + } + } + if sponsored { + NftTransferBasket::::insert(collection_id, item_id, block_number); + } + + sponsored + } + CollectionMode::Fungible(_) => { + + // get correct limit + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout + } else { + limits.fungible_sponsor_transfer_timeout + }; + + let block_number = >::block_number() as T::BlockNumber; + let mut sponsored = true; + if FungibleTransferBasket::::contains_key(collection_id, who) { + let last_tx_block = FungibleTransferBasket::::get(collection_id, who); + let limit_time = last_tx_block + limit.into(); + if block_number <= limit_time { + sponsored = false; + } + } + if sponsored { + FungibleTransferBasket::::insert(collection_id, who, block_number); + } + + sponsored + } + CollectionMode::ReFungible => { + + // get correct limit + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout + } else { + limits.refungible_sponsor_transfer_timeout + }; + + let mut sponsored = true; + if ReFungibleTransferBasket::::contains_key(collection_id, item_id) { + let last_tx_block = ReFungibleTransferBasket::::get(collection_id, item_id); + let limit_time = last_tx_block + limit.into(); + if block_number <= limit_time { + sponsored = false; + } + } + if sponsored { + ReFungibleTransferBasket::::insert(collection_id, item_id, block_number); + } + + sponsored + } + _ => { + false + }, + }; + } + + if !sponsor_transfer { + None + } else { + collection.sponsorship.sponsor() + .cloned() + } + } + + pub fn withdraw_set_variable_meta_data( + collection_id: &CollectionId, + item_id: &TokenId, + data: &Vec, + ) -> Option { + + let mut sponsor_metadata_changes = false; + + let collection = CollectionById::::get(collection_id)?; + + if + collection.sponsorship.confirmed() && + // Can't sponsor fungible collection, this tx will be rejected + // as invalid + !matches!(collection.mode, CollectionMode::Fungible(_)) && + data.len() <= collection.limits.sponsored_data_size as usize + { + if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit { + let block_number = >::block_number() as T::BlockNumber; + + if VariableMetaDataBasket::::get(collection_id, item_id) + .map(|last_block| block_number - last_block > rate_limit) + .unwrap_or(true) + { + sponsor_metadata_changes = true; + VariableMetaDataBasket::::insert(collection_id, item_id, block_number); + } + } + } + + if !sponsor_metadata_changes { + None + } else { + collection.sponsorship.sponsor().cloned() + } + + } +} + +impl SponsorshipHandler for NftSponsorshipHandler +where + T: Config, + C: IsSubType> +{ + fn get_sponsor(who: &T::AccountId, call: &C) -> Option { + match IsSubType::>::is_sub_type(call)? { + Call::create_item(collection_id, _owner, _properties) => { + Self::withdraw_create_item(who, collection_id, &_properties) + }, + Call::transfer(_new_owner, collection_id, item_id, _value) => { + Self::withdraw_transfer(who, collection_id, item_id) + }, + Call::set_variable_meta_data(collection_id, item_id, data) => { + Self::withdraw_set_variable_meta_data(collection_id, item_id, &data) + }, + _ => None, + } + } +} \ No newline at end of file -- gitstuff