123456#![cfg_attr(not(feature = "std"), no_std)]78#[cfg(feature = "std")]9pub use std::*;1011#[cfg(feature = "std")]12pub use serde::*;1314#[cfg(feature = "runtime-benchmarks")]15mod benchmarking;1617#[cfg(test)]18mod tests;1920use frame_support::{21 decl_error, decl_module, decl_storage,22 traits::{23 IsSubType, 24 },25 weights::{26 DispatchInfo27 }28};29use sp_runtime::traits::StaticLookup;30use sp_runtime::{31 traits::{ 32 Hash, Dispatchable,33 },34};35use pallet_contracts::chain_extension::UncheckedFrom;36use sp_std::prelude::*;37use nft_data_structs::{38 CreateItemData,39 CollectionId, CollectionMode, TokenId40};4142type CodeHash<T> = <T as frame_system::Config>::Hash;4344 pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config {45 }4647 48 49 decl_error! {50 51 pub enum Error for Module<T: Config> {52 53 NoAvailableClassId,54 55 NoAvailableTokenId,56 57 TokenNotFound,58 59 CollectionNotFound,60 61 NoPermission,62 63 NumOverflow,64 65 66 CannotDestroyClass,67 }68}6970 decl_storage! {71 trait Store for Module<T: Config> as NftTransactionPayment{7273 }74}7576decl_module! {7778 pub struct Module<T: Config> for enum Call 79 where 80 origin: T::Origin,81 {82 }83}8485impl<T: Config> Module<T> 86{8788 pub fn withdraw_type(89 who: &T::AccountId,90 call: &T::Call91 ) -> Option<T::AccountId> where 92 T::Call: Dispatchable<Info=DispatchInfo>,93 T::Call: IsSubType<pallet_nft::Call<T>>, 94 T::Call: IsSubType<pallet_contracts::Call<T>>,95 T::AccountId: AsRef<[u8]>,96 T::AccountId: UncheckedFrom<T::Hash>97 {9899 let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::is_sub_type(call) {100 Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => {101102 Self::withdraw_create_item(who, collection_id, &_properties)103 },104 Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => {105106 Self::withdraw_transfer(who, collection_id, item_id)107 },108 Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => {109110 Self::withdraw_set_variable_meta_data(who, collection_id, item_id, &data)111 },112 _ => None,113 };114115 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {116 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {117118 Self::withdraw_contract_call(who, dest)119 },120 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {121122 Self::withdraw_contract_instantiate(&who, code_hash, salt)123 },124 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {125126 Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt)127 },128 _ => None,129 });130131 sponsor132 }133134135136 pub fn withdraw_create_item(137 who: &T::AccountId,138 collection_id: &CollectionId,139 _properties: &CreateItemData,140 ) -> Option<T::AccountId> {141 142 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;143144 145 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;146147 let limit = collection.limits.sponsor_transfer_timeout;148 if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {149 let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));150 let limit_time = last_tx_block + limit.into();151 if block_number <= limit_time {152 return None;153 }154 }155 pallet_nft::CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);156157 158 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {159 collection.sponsorship.sponsor()160 .cloned()161 } else {162 None163 }164 }165166 pub fn withdraw_transfer(167 who: &T::AccountId,168 collection_id: &CollectionId,169 item_id: &TokenId,170 ) -> Option<T::AccountId> {171172 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;173 let limits = pallet_nft::ChainLimit::get();174175 let mut sponsor_transfer = false;176 if collection.sponsorship.confirmed() {177178 let collection_limits = collection.limits.clone();179 let collection_mode = collection.mode.clone();180181 182 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;183 sponsor_transfer = match collection_mode {184 CollectionMode::NFT => {185186 187 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {188 collection_limits.sponsor_transfer_timeout189 } else {190 limits.nft_sponsor_transfer_timeout191 };192193 let mut sponsored = true;194 if pallet_nft::NftTransferBasket::<T>::contains_key(collection_id, item_id) {195 let last_tx_block = pallet_nft::NftTransferBasket::<T>::get(collection_id, item_id);196 let limit_time = last_tx_block + limit.into();197 if block_number <= limit_time {198 sponsored = false;199 }200 }201 if sponsored {202 pallet_nft::NftTransferBasket::<T>::insert(collection_id, item_id, block_number);203 }204205 sponsored206 }207 CollectionMode::Fungible(_) => {208209 210 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {211 collection_limits.sponsor_transfer_timeout212 } else {213 limits.fungible_sponsor_transfer_timeout214 };215216 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;217 let mut sponsored = true;218 if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {219 let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);220 let limit_time = last_tx_block + limit.into();221 if block_number <= limit_time {222 sponsored = false;223 }224 }225 if sponsored {226 pallet_nft::FungibleTransferBasket::<T>::insert(collection_id, who, block_number);227 }228229 sponsored230 }231 CollectionMode::ReFungible => {232233 234 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {235 collection_limits.sponsor_transfer_timeout236 } else {237 limits.refungible_sponsor_transfer_timeout238 };239240 let mut sponsored = true;241 if pallet_nft::ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {242 let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::get(collection_id, item_id);243 let limit_time = last_tx_block + limit.into();244 if block_number <= limit_time {245 sponsored = false;246 }247 }248 if sponsored {249 pallet_nft::ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);250 }251252 sponsored253 }254 _ => {255 false256 },257 };258 }259260 if !sponsor_transfer {261 None262 } else {263 collection.sponsorship.sponsor()264 .cloned()265 }266 }267 268 pub fn withdraw_set_variable_meta_data(269 who: &T::AccountId,270 collection_id: &CollectionId,271 item_id: &TokenId,272 data: &Vec<u8>,273 ) -> Option<T::AccountId> {274275 let mut sponsor_metadata_changes = false;276277 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;278279 if280 collection.sponsorship.confirmed() &&281 282 283 !matches!(collection.mode, CollectionMode::Fungible(_)) &&284 data.len() <= collection.limits.sponsored_data_size as usize285 {286 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {287 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;288289 if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)290 .map(|last_block| block_number - last_block > rate_limit)291 .unwrap_or(true) 292 {293 sponsor_metadata_changes = true;294 pallet_nft::VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);295 }296 }297 }298299 if !sponsor_metadata_changes {300 None301 } else {302 collection.sponsorship.sponsor().cloned()303 }304305 }306307 pub fn withdraw_contract_call(308 who: &T::AccountId,309 dest: &<T::Lookup as StaticLookup>::Source310 ) -> Option<T::AccountId> {311312 let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());313314 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);315 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());316 317 318 if !owned_contract && white_list_enabled {319 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {320 return Some(who.clone())321 322 }323 }324325 let mut sponsor_transfer = false;326 if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {327 let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));328 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;329 let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);330 let limit_time = last_tx_block + rate_limit;331332 if block_number >= limit_time {333 pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);334 sponsor_transfer = true;335 }336 } else {337 sponsor_transfer = false;338 }339 340 if sponsor_transfer {341 if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {342 if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {343 return Some(called_contract);344 }345 }346 }347348 None349 }350351 pub fn withdraw_contract_instantiate(352 who: &T::AccountId,353 code_hash: &CodeHash<T>,354 salt: &[u8],355 ) -> Option<T::AccountId> where356 T::AccountId: AsRef<[u8]>,357 T::AccountId: UncheckedFrom<T::Hash>358 {359360 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(361 &who,362 code_hash,363 salt,364 );365 pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());366367 None368 }369}370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525