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 transaction_validity::{35 InvalidTransaction, TransactionValidityError,36 },37};38use pallet_contracts::chain_extension::UncheckedFrom;39use sp_std::prelude::*;40use nft_data_structs::{41 CreateItemData,42 CollectionId, CollectionMode, TokenId43};4445type CodeHash<T> = <T as frame_system::Config>::Hash;4647 pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config {48 }4950 51 52 decl_error! {53 54 pub enum Error for Module<T: Config> {55 56 NoAvailableClassId,57 58 NoAvailableTokenId,59 60 TokenNotFound,61 62 CollectionNotFound,63 64 NoPermission,65 66 NumOverflow,67 68 69 CannotDestroyClass,70 }71}7273 decl_storage! {74 trait Store for Module<T: Config> as NftTransactionPayment{7576 }77}7879decl_module! {8081 pub struct Module<T: Config> for enum Call 82 where 83 origin: T::Origin,84 {85 }86}8788impl<T: Config> Module<T> 89{9091 pub fn check_error(92 who: &T::AccountId,93 call: &T::Call94 ) -> Result<bool, TransactionValidityError> where 95 T::Call: Dispatchable<Info=DispatchInfo>,96 T::Call: IsSubType<pallet_nft::Call<T>>, 97 T::Call: IsSubType<pallet_contracts::Call<T>>,98 T::AccountId: AsRef<[u8]>,99 T::AccountId: UncheckedFrom<T::Hash>100 {101102 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {103 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {104 105 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());106 107 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);108 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());109 110 if !owned_contract && white_list_enabled {111 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {112 return Err(InvalidTransaction::Call.into());113 }114 }115 Ok(true)116 },117 _ => { Ok(true) },118 }119 }120121 pub fn withdraw_type(122 who: &T::AccountId,123 call: &T::Call124 ) -> Option<T::AccountId> where 125 T::Call: Dispatchable<Info=DispatchInfo>,126 T::Call: IsSubType<pallet_nft::Call<T>>, 127 T::Call: IsSubType<pallet_contracts::Call<T>>,128 T::AccountId: AsRef<[u8]>,129 T::AccountId: UncheckedFrom<T::Hash>130 {131132 let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::is_sub_type(call) {133 Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => {134135 Self::withdraw_create_item(who, collection_id, &_properties)136 },137 Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => {138139 Self::withdraw_transfer(who, collection_id, item_id)140 },141 Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => {142143 Self::withdraw_set_variable_meta_data(collection_id, item_id, &data)144 },145 _ => None,146 };147148 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {149 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {150151 Self::withdraw_contract_call(who, dest)152 },153 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {154155 Self::withdraw_contract_instantiate(&who, code_hash, salt)156 },157 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {158159 Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt)160 },161 _ => None,162 });163164 sponsor165 }166167168169 pub fn withdraw_create_item(170 who: &T::AccountId,171 collection_id: &CollectionId,172 _properties: &CreateItemData,173 ) -> Option<T::AccountId> {174 175 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;176177 178 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;179180 let limit = collection.limits.sponsor_transfer_timeout;181 if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {182 let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));183 let limit_time = last_tx_block + limit.into();184 if block_number <= limit_time {185 return None;186 }187 }188 pallet_nft::CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);189190 191 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {192 collection.sponsorship.sponsor()193 .cloned()194 } else {195 None196 }197 }198199 pub fn withdraw_transfer(200 who: &T::AccountId,201 collection_id: &CollectionId,202 item_id: &TokenId,203 ) -> Option<T::AccountId> {204205 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;206 let limits = pallet_nft::ChainLimit::get();207208 let mut sponsor_transfer = false;209 if collection.sponsorship.confirmed() {210211 let collection_limits = collection.limits.clone();212 let collection_mode = collection.mode.clone();213214 215 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;216 sponsor_transfer = match collection_mode {217 CollectionMode::NFT => {218219 220 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {221 collection_limits.sponsor_transfer_timeout222 } else {223 limits.nft_sponsor_transfer_timeout224 };225226 let mut sponsored = true;227 if pallet_nft::NftTransferBasket::<T>::contains_key(collection_id, item_id) {228 let last_tx_block = pallet_nft::NftTransferBasket::<T>::get(collection_id, item_id);229 let limit_time = last_tx_block + limit.into();230 if block_number <= limit_time {231 sponsored = false;232 }233 }234 if sponsored {235 pallet_nft::NftTransferBasket::<T>::insert(collection_id, item_id, block_number);236 }237238 sponsored239 }240 CollectionMode::Fungible(_) => {241242 243 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {244 collection_limits.sponsor_transfer_timeout245 } else {246 limits.fungible_sponsor_transfer_timeout247 };248249 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;250 let mut sponsored = true;251 if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {252 let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);253 let limit_time = last_tx_block + limit.into();254 if block_number <= limit_time {255 sponsored = false;256 }257 }258 if sponsored {259 pallet_nft::FungibleTransferBasket::<T>::insert(collection_id, who, block_number);260 }261262 sponsored263 }264 CollectionMode::ReFungible => {265266 267 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {268 collection_limits.sponsor_transfer_timeout269 } else {270 limits.refungible_sponsor_transfer_timeout271 };272273 let mut sponsored = true;274 if pallet_nft::ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {275 let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::get(collection_id, item_id);276 let limit_time = last_tx_block + limit.into();277 if block_number <= limit_time {278 sponsored = false;279 }280 }281 if sponsored {282 pallet_nft::ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);283 }284285 sponsored286 }287 _ => {288 false289 },290 };291 }292293 if !sponsor_transfer {294 None295 } else {296 collection.sponsorship.sponsor()297 .cloned()298 }299 }300 301 pub fn withdraw_set_variable_meta_data(302 collection_id: &CollectionId,303 item_id: &TokenId,304 data: &Vec<u8>,305 ) -> Option<T::AccountId> {306307 let mut sponsor_metadata_changes = false;308309 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;310311 if312 collection.sponsorship.confirmed() &&313 314 315 !matches!(collection.mode, CollectionMode::Fungible(_)) &&316 data.len() <= collection.limits.sponsored_data_size as usize317 {318 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {319 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;320321 if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)322 .map(|last_block| block_number - last_block > rate_limit)323 .unwrap_or(true) 324 {325 sponsor_metadata_changes = true;326 pallet_nft::VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);327 }328 }329 }330331 if !sponsor_metadata_changes {332 None333 } else {334 collection.sponsorship.sponsor().cloned()335 }336337 }338339 pub fn withdraw_contract_call(340 who: &T::AccountId,341 dest: &<T::Lookup as StaticLookup>::Source342 ) -> Option<T::AccountId> {343344 let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());345346 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);347 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());348 349 350 if !owned_contract && white_list_enabled {351 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {352 return Some(who.clone())353 354 }355 }356357 let mut sponsor_transfer = false;358 if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {359 let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));360 let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;361 let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);362 let limit_time = last_tx_block + rate_limit;363364 if block_number >= limit_time {365 pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);366 sponsor_transfer = true;367 }368 } else {369 sponsor_transfer = false;370 }371 372 if sponsor_transfer {373 if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {374 if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {375 return Some(called_contract);376 }377 }378 }379380 None381 }382383 pub fn withdraw_contract_instantiate(384 who: &T::AccountId,385 code_hash: &CodeHash<T>,386 salt: &[u8],387 ) -> Option<T::AccountId> where388 T::AccountId: AsRef<[u8]>,389 T::AccountId: UncheckedFrom<T::Hash>390 {391392 let new_contract_address = <pallet_contracts::Pallet<T>>::contract_address(393 &who,394 code_hash,395 salt,396 );397 pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());398399 None400 }401}