--- a/Cargo.lock +++ b/Cargo.lock @@ -5113,6 +5113,7 @@ "nft-data-structs", "pallet-aura", "pallet-balances", + "pallet-contract-helpers", "pallet-contracts", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", @@ -5481,6 +5482,19 @@ ] [[package]] +name = "pallet-contract-helpers" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-contracts", + "parity-scale-codec 2.1.3", + "sp-runtime", + "sp-std", + "up-sponsorship", +] + +[[package]] name = "pallet-contracts" version = "3.0.0" source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7" @@ -5871,6 +5885,7 @@ "sp-io", "sp-runtime", "sp-std", + "up-sponsorship", ] [[package]] @@ -5880,13 +5895,8 @@ "frame-benchmarking", "frame-support", "frame-system", - "nft-data-structs", "pallet-balances", - "pallet-contracts", - "pallet-nft", "pallet-nft-transaction-payment", - "pallet-randomness-collective-flip", - "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec 2.1.3", "serde", @@ -5903,12 +5913,6 @@ "frame-benchmarking", "frame-support", "frame-system", - "nft-data-structs", - "pallet-balances", - "pallet-contracts", - "pallet-nft", - "pallet-randomness-collective-flip", - "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec 2.1.3", "serde", @@ -5916,6 +5920,7 @@ "sp-io", "sp-runtime", "sp-std", + "up-sponsorship", ] [[package]] @@ -5996,10 +6001,6 @@ "frame-support", "frame-system", "log", - "nft-data-structs", - "pallet-contracts", - "pallet-nft", - "pallet-nft-transaction-payment", "parity-scale-codec 2.1.3", "serde", "sp-core", @@ -6007,6 +6008,7 @@ "sp-runtime", "sp-std", "substrate-test-utils", + "up-sponsorship", ] [[package]] @@ -11760,6 +11762,13 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] +name = "up-sponsorship" +version = "0.1.0" +dependencies = [ + "impl-trait-for-tuples 0.2.1", +] + +[[package]] name = "url" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ 'node/*', 'pallets/*', - 'primitives', + 'primitives/*', 'runtime', 'crates/evm-coder', 'crates/evm-coder-macros', --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -284,7 +284,7 @@ version = '3.0.0' [dependencies.nft-data-structs] -path="../../primitives" +path="../../primitives/nft" default-features = false ################################################################################ --- /dev/null +++ b/pallets/contract-helpers/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pallet-contract-helpers" +version = "0.1.0" +edition = "2018" + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '2.0.0' + +[dependencies.up-sponsorship] +default-features = false +path = '../../primitives/sponsorship' +version = '0.1.0' + +[dependencies] +frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "pallet-contracts/std", + "sp-runtime/std", + "sp-std/std", +] \ No newline at end of file --- /dev/null +++ b/pallets/contract-helpers/src/lib.rs @@ -0,0 +1,261 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::sp_runtime::traits::StaticLookup; + use frame_support::{pallet_prelude::*, traits::IsSubType}; + use frame_system::pallet_prelude::*; + use pallet_contracts::chain_extension::UncheckedFrom; + use sp_runtime::{ + traits::{DispatchInfoOf, Hash, PostDispatchInfoOf, SignedExtension}, + transaction_validity, + }; + use sp_std::vec::Vec; + use up_sponsorship::SponsorshipHandler; + + #[pallet::error] + pub enum Error { + /// Should be contract owner + NoPermission, + } + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_contracts::Config {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type Owner = StorageMap< + Hasher = Twox128, + Key = T::AccountId, + Value = T::AccountId, + QueryKind = ValueQuery, + >; + + #[pallet::storage] + pub(super) type AllowlistEnabled = + StorageMap; + + #[pallet::storage] + pub(super) type Allowlist = StorageDoubleMap< + Hasher1 = Twox128, + Key1 = T::AccountId, + Hasher2 = Twox64Concat, + Key2 = T::AccountId, + Value = bool, + QueryKind = ValueQuery, + >; + + #[pallet::storage] + pub(super) type SelfSponsoring = + StorageMap; + + #[pallet::storage] + pub(super) type SponsoringRateLimit = StorageMap< + Hasher = Twox128, + Key = T::AccountId, + Value = T::BlockNumber, + QueryKind = ValueQuery, + >; + + #[pallet::storage] + pub(super) type SponsorBasket = StorageDoubleMap< + Hasher1 = Twox128, + Key1 = T::AccountId, + Hasher2 = Twox128, + Key2 = T::AccountId, + Value = T::BlockNumber, + QueryKind = ValueQuery, + >; + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + fn toggle_sponsoring( + origin: OriginFor, + contract: T::AccountId, + sponsoring: bool, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(>::get(&contract) == sender, >::NoPermission); + + if sponsoring { + >::insert(contract, true); + } else { + >::remove(contract); + } + Ok(()) + } + + #[pallet::weight(0)] + fn toggle_allowlist( + origin: OriginFor, + contract: T::AccountId, + enabled: bool, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(>::get(&contract) == sender, >::NoPermission); + + if enabled { + >::insert(contract, true); + } else { + >::remove(contract); + } + Ok(()) + } + + #[pallet::weight(0)] + fn toggle_allowed( + origin: OriginFor, + contract: T::AccountId, + user: T::AccountId, + allowed: bool, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(>::get(&contract) == sender, >::NoPermission); + + if allowed { + >::insert(contract, user, true); + } else { + >::remove(contract, user); + } + Ok(()) + } + + #[pallet::weight(0)] + fn set_sponsoring_rate_limit( + origin: OriginFor, + contract: T::AccountId, + rate_limit: T::BlockNumber, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(>::get(&contract) == sender, >::NoPermission); + + >::insert(contract, rate_limit); + Ok(()) + } + } + + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub struct ContractHelpersExtension(PhantomData); + impl core::fmt::Debug for ContractHelpersExtension { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + fmt.debug_struct("ContractHelpersExtension").finish() + } + } + + type CodeHash = ::Hash; + impl SignedExtension for ContractHelpersExtension + where + T: Config + Send + Sync, + T::Call: sp_runtime::traits::Dispatchable, + T::Call: IsSubType>, + T::AccountId: UncheckedFrom, + T::AccountId: AsRef<[u8]>, + { + const IDENTIFIER: &'static str = "ContractHelpers"; + type AccountId = T::AccountId; + type Call = T::Call; + type AdditionalSigned = (); + type Pre = Option<(Self::AccountId, CodeHash, Vec)>; + + fn additional_signed(&self) -> Result<(), transaction_validity::TransactionValidityError> { + Ok(()) + } + + fn validate( + &self, + who: &T::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> transaction_validity::TransactionValidity { + match IsSubType::>::is_sub_type(call) { + Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { + let called_contract: T::AccountId = + T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default()); + if >::get(&called_contract) { + if !>::get(&called_contract, who) + && &>::get(&called_contract) != who + { + return Err(transaction_validity::InvalidTransaction::Call.into()); + } + } + } + _ => {} + } + Ok(transaction_validity::ValidTransaction::default()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result { + match IsSubType::>::is_sub_type(call) { + Some(pallet_contracts::Call::instantiate(_, _, code_hash, _, salt)) => { + Ok(Some((who.clone(), code_hash.clone(), salt.clone()))) + } + Some(pallet_contracts::Call::instantiate_with_code(_, _, code, _, salt)) => { + let code_hash = &T::Hashing::hash(&code); + Ok(Some((who.clone(), code_hash.clone(), salt.clone()))) + } + _ => Ok(None), + } + } + + fn post_dispatch( + pre: Self::Pre, + _info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if let Some((who, code_hash, salt)) = pre { + let new_contract_address = + >::contract_address(&who, &code_hash, &salt); + >::insert(&new_contract_address, &who); + } + + Ok(()) + } + } + + pub struct ContractSponsorshipHandler(PhantomData); + impl SponsorshipHandler for ContractSponsorshipHandler + where + T: Config, + C: IsSubType>, + T::AccountId: UncheckedFrom, + T::AccountId: AsRef<[u8]>, + { + fn get_sponsor(who: &T::AccountId, call: &C) -> Option { + match IsSubType::>::is_sub_type(call) { + Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { + let called_contract: T::AccountId = + T::Lookup::lookup((*dest).clone()).unwrap_or_default(); + if >::get(&called_contract) { + let last_tx_block = SponsorBasket::::get(&called_contract, &who); + let block_number = + >::block_number() as T::BlockNumber; + let rate_limit = SponsoringRateLimit::::get(&called_contract); + let limit_time = last_tx_block + rate_limit; + + if block_number >= limit_time { + SponsorBasket::::insert(&called_contract, who, block_number); + return Some(called_contract); + } + } + } + _ => {} + } + None + } + } +} --- a/pallets/nft-charge-transaction/Cargo.toml +++ b/pallets/nft-charge-transaction/Cargo.toml @@ -23,19 +23,14 @@ frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } pallet-balances = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-timestamp = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } pallet-transaction-payment = { default-features = false, version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-randomness-collective-flip = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-benchmarking = { default-features = false, version = "3.0.0", optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-nft = { default-features = false, path="../nft" } pallet-nft-transaction-payment = { default-features = false, path="../nft-transaction-payment" } -nft-data-structs = { default-features = false, path="../../primitives", version = "0.9.0" } [features] default = ['std'] @@ -45,15 +40,10 @@ 'frame-support/std', 'frame-system/std', 'pallet-balances/std', - 'pallet-timestamp/std', - 'pallet-randomness-collective-flip/std', - 'pallet-contracts/std', - 'pallet-nft/std', 'pallet-transaction-payment/std', 'pallet-nft-transaction-payment/std', 'sp-std/std', 'sp-runtime/std', - 'nft-data-structs/std', 'frame-benchmarking/std', ] runtime-benchmarks = ["frame-benchmarking"] --- a/pallets/nft-charge-transaction/src/lib.rs +++ b/pallets/nft-charge-transaction/src/lib.rs @@ -14,16 +14,10 @@ #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -#[cfg(test)] -mod tests; - use codec::{Decode, Encode}; -use frame_support::traits::{ Get}; +use frame_support::traits::Get; use frame_support::{ decl_module, decl_storage, - traits::{ - IsSubType, - }, weights::{ DispatchInfo, PostDispatchInfo, DispatchClass } @@ -37,7 +31,6 @@ }, FixedPointOperand, DispatchResult }; -use pallet_contracts::chain_extension::UncheckedFrom; use pallet_transaction_payment::OnChargeTransaction; use sp_std::prelude::*; @@ -83,10 +76,8 @@ impl ChargeTransactionPayment where - T::Call: Dispatchable + IsSubType> + IsSubType>, + T::Call: Dispatchable, BalanceOf: Send + Sync + From + FixedPointOperand, - T::AccountId: AsRef<[u8]>, - T::AccountId: UncheckedFrom, { fn traditional_fee( len: usize, @@ -134,13 +125,6 @@ .map(|i| (fee, i)); } - // check errors - let _error = >::check_error(who, call); - match _error { - Err(_error) => return Err(_error), - Ok(_error) => {} - }; - // Determine who is paying transaction fee based on ecnomic model // Parse call to extract collection ID and access collection sponsor let sponsor = >::withdraw_type(who, call); @@ -156,9 +140,7 @@ for ChargeTransactionPayment where BalanceOf: Send + Sync + From + FixedPointOperand, - T::Call: Dispatchable + IsSubType> + IsSubType>, - T::AccountId: AsRef<[u8]>, - T::AccountId: UncheckedFrom, + T::Call: Dispatchable, { const IDENTIFIER: &'static str = "ChargeTransactionPayment"; type AccountId = T::AccountId; @@ -209,7 +191,7 @@ _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { let (tip, who, imbalance) = pre; - let actual_fee = pallet_transaction_payment::Module::::compute_actual_fee( + let actual_fee = pallet_transaction_payment::Pallet::::compute_actual_fee( len as u32, info, post_info, --- a/pallets/nft-transaction-payment/Cargo.toml +++ b/pallets/nft-transaction-payment/Cargo.toml @@ -22,19 +22,14 @@ serde = { version = "1.0.119", default-features = false } frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-balances = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-timestamp = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } pallet-transaction-payment = { default-features = false, version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-randomness-collective-flip = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-benchmarking = { default-features = false, version = "3.0.0", optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-nft = { default-features = false, path="../nft" } -nft-data-structs = { default-features = false, path="../../primitives", version = "0.9.0" } +up-sponsorship = { default-features = false, path = "../../primitives/sponsorship", version = "0.1.0" } [features] default = ['std'] @@ -43,15 +38,13 @@ 'serde/std', 'frame-support/std', 'frame-system/std', - 'pallet-balances/std', - 'pallet-timestamp/std', - 'pallet-randomness-collective-flip/std', - 'pallet-contracts/std', - 'pallet-nft/std', + 'sp-core/std', + 'sp-io/std', 'pallet-transaction-payment/std', 'sp-std/std', 'sp-runtime/std', - 'nft-data-structs/std', 'frame-benchmarking/std', + + 'up-sponsorship/std', ] runtime-benchmarks = ["frame-benchmarking"] --- a/pallets/nft-transaction-payment/src/benchmarking.rs +++ b/pallets/nft-transaction-payment/src/benchmarking.rs @@ -1,7 +1,6 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use crate::Module as NftTransactionPayment; use sp_std::prelude::*; use frame_system::RawOrigin; --- a/pallets/nft-transaction-payment/src/lib.rs +++ b/pallets/nft-transaction-payment/src/lib.rs @@ -14,388 +14,32 @@ #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -#[cfg(test)] -mod tests; - -use frame_support::{ - decl_error, decl_module, decl_storage, - traits::{ - IsSubType, - }, - weights::{ - DispatchInfo - } -}; -use sp_runtime::traits::StaticLookup; -use sp_runtime::{ - traits::{ - Hash, Dispatchable, - }, - transaction_validity::{ - InvalidTransaction, TransactionValidityError, - }, -}; -use pallet_contracts::chain_extension::UncheckedFrom; +use frame_support::{decl_module, decl_storage}; use sp_std::prelude::*; -use nft_data_structs::{ - CreateItemData, - CollectionId, CollectionMode, TokenId -}; - -type CodeHash = ::Hash; +use up_sponsorship::SponsorshipHandler; - pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config { - } - - // Error for non-fungible-token module. - - decl_error! { - /// Error for non-fungible-token module. - pub enum Error for Module { - /// No available class ID - NoAvailableClassId, - /// No available token ID - NoAvailableTokenId, - /// Token(ClassId, TokenId) not found - TokenNotFound, - /// Class not found - CollectionNotFound, - /// The operator is not the owner of the token and has no permission - NoPermission, - /// Arithmetic calculation overflow - NumOverflow, - /// Can not destroy class - /// Total issuance is not 0 - CannotDestroyClass, - } +pub trait Config: frame_system::Config + pallet_transaction_payment::Config { + type SponsorshipHandler: SponsorshipHandler; } - - decl_storage! { - trait Store for Module as NftTransactionPayment{ +decl_storage! { + trait Store for Module as NftTransactionPayment{ } } decl_module! { - pub struct Module for enum Call where origin: T::Origin, { } } - -impl Module -{ - pub fn check_error( - who: &T::AccountId, - call: &T::Call - ) -> Result where - T::Call: Dispatchable, - T::Call: IsSubType>, - T::Call: IsSubType>, - T::AccountId: AsRef<[u8]>, - T::AccountId: UncheckedFrom - { - - match IsSubType::>::is_sub_type(call) { - Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { - - let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default()); - - let owned_contract = pallet_nft::ContractOwner::::get(called_contract.clone()).as_ref() == Some(who); - let white_list_enabled = pallet_nft::ContractWhiteListEnabled::::contains_key(called_contract.clone()); - - if !owned_contract && white_list_enabled { - if !pallet_nft::ContractWhiteList::::contains_key(called_contract.clone(), who) { - return Err(InvalidTransaction::Call.into()); - } - } - Ok(true) - }, - _ => { Ok(true) }, - } - } - +impl Module { pub fn withdraw_type( who: &T::AccountId, call: &T::Call - ) -> Option where - T::Call: Dispatchable, - T::Call: IsSubType>, - T::Call: IsSubType>, - T::AccountId: AsRef<[u8]>, - T::AccountId: UncheckedFrom - { - - let mut sponsor: Option = match IsSubType::>::is_sub_type(call) { - Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => { - - Self::withdraw_create_item(who, collection_id, &_properties) - }, - Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => { - - Self::withdraw_transfer(who, collection_id, item_id) - }, - Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => { - - Self::withdraw_set_variable_meta_data(collection_id, item_id, &data) - }, - _ => None, - }; - - sponsor = sponsor.or_else(|| match IsSubType::>::is_sub_type(call) { - Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => { - - Self::withdraw_contract_call(who, dest) - }, - Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => { - - Self::withdraw_contract_instantiate(&who, code_hash, salt) - }, - Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => { - - Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt) - }, - _ => None, - }); - - sponsor - } - - - - pub fn withdraw_create_item( - who: &T::AccountId, - collection_id: &CollectionId, - _properties: &CreateItemData, ) -> Option { - - let collection = pallet_nft::CollectionById::::get(collection_id)?; - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - - let limit = collection.limits.sponsor_transfer_timeout; - if pallet_nft::CreateItemBasket::::contains_key((collection_id, &who)) { - let last_tx_block = pallet_nft::CreateItemBasket::::get((collection_id, &who)); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - return None; - } - } - pallet_nft::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 = pallet_nft::CollectionById::::get(collection_id)?; - let limits = pallet_nft::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 pallet_nft::NftTransferBasket::::contains_key(collection_id, item_id) { - let last_tx_block = pallet_nft::NftTransferBasket::::get(collection_id, item_id); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - pallet_nft::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 pallet_nft::FungibleTransferBasket::::contains_key(collection_id, who) { - let last_tx_block = pallet_nft::FungibleTransferBasket::::get(collection_id, who); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - pallet_nft::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 pallet_nft::ReFungibleTransferBasket::::contains_key(collection_id, item_id) { - let last_tx_block = pallet_nft::ReFungibleTransferBasket::::get(collection_id, item_id); - let limit_time = last_tx_block + limit.into(); - if block_number <= limit_time { - sponsored = false; - } - } - if sponsored { - pallet_nft::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 = pallet_nft::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 pallet_nft::VariableMetaDataBasket::::get(collection_id, item_id) - .map(|last_block| block_number - last_block > rate_limit) - .unwrap_or(true) - { - sponsor_metadata_changes = true; - pallet_nft::VariableMetaDataBasket::::insert(collection_id, item_id, block_number); - } - } - } - - if !sponsor_metadata_changes { - None - } else { - collection.sponsorship.sponsor().cloned() - } - - } - - pub fn withdraw_contract_call( - who: &T::AccountId, - dest: &::Source - ) -> Option { - - let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default()); - - let owned_contract = pallet_nft::ContractOwner::::get(called_contract.clone()).as_ref() == Some(who); - let white_list_enabled = pallet_nft::ContractWhiteListEnabled::::contains_key(called_contract.clone()); - - // ??? - if !owned_contract && white_list_enabled { - if !pallet_nft::ContractWhiteList::::contains_key(called_contract.clone(), who) { - return Some(who.clone()) - // return Err(InvalidTransaction::Call.into()); - } - } - - let mut sponsor_transfer = false; - if pallet_nft::ContractSponsoringRateLimit::::contains_key(called_contract.clone()) { - let last_tx_block = pallet_nft::ContractSponsorBasket::::get((&called_contract, &who)); - let block_number = >::block_number() as T::BlockNumber; - let rate_limit = pallet_nft::ContractSponsoringRateLimit::::get(&called_contract); - let limit_time = last_tx_block + rate_limit; - - if block_number >= limit_time { - pallet_nft::ContractSponsorBasket::::insert((called_contract.clone(), who.clone()), block_number); - sponsor_transfer = true; - } - } else { - sponsor_transfer = false; - } - - if sponsor_transfer { - if pallet_nft::ContractSelfSponsoring::::contains_key(called_contract.clone()) { - if pallet_nft::ContractSelfSponsoring::::get(called_contract.clone()) { - return Some(called_contract); - } - } - } - - None - } - - pub fn withdraw_contract_instantiate( - who: &T::AccountId, - code_hash: &CodeHash, - salt: &[u8], - ) -> Option where - T::AccountId: AsRef<[u8]>, - T::AccountId: UncheckedFrom - { - - let new_contract_address = >::contract_address( - &who, - code_hash, - salt, - ); - pallet_nft::ContractOwner::::insert(new_contract_address.clone(), who.clone()); - - None + T::SponsorshipHandler::get_sponsor(who, call) } } \ No newline at end of file --- a/pallets/nft-transaction-payment/src/tests.rs +++ /dev/null @@ -1,241 +0,0 @@ -#[cfg(test)] -mod tests { - use crate as pallet_inflation; - - use frame_system; - use frame_support::{traits::{Currency}, parameter_types}; - use frame_support::{traits::OnInitialize}; - use sp_core::H256; - use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - const YEAR: u64 = 5_259_600; - - parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxLocks: u32 = 50; - } - - impl pallet_balances::Config for Test { - type AccountStore = System; - type Balance = u64; - type DustRemoval = (); - type Event = (); - type ExistentialDeposit = ExistentialDeposit; - type WeightInfo = (); - type MaxLocks = MaxLocks; - } - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - Balances: pallet_balances::{Module, Call, Storage}, - System: frame_system::{Module, Call, Config, Storage, Event}, - Inflation: pallet_inflation::{Module, Call, Storage}, - } - ); - - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); - pub const SS58Prefix: u8 = 42; - } - - impl frame_system::Config for Test { - type BaseCallFilter = (); - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - } - - parameter_types! { - pub TreasuryAccountId: u64 = 1234; - pub const InflationBlockInterval: u32 = 100; // every time per how many blocks inflation is applied - } - - impl pallet_inflation::Config for Test { - type Currency = Balances; - type TreasuryAccountId = TreasuryAccountId; - type InflationBlockInterval = InflationBlockInterval; - } - - // Build genesis storage according to the mock runtime. - pub fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::default().build_storage::().unwrap().into() - } - - #[test] - fn inflation_works() { - new_test_ext().execute_with(|| { - // Total issuance = 1_000_000_000 - let initial_issuance: u64 = 1_000_000_000; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - - // BlockInflation should be set after 1st block and - // first inflation deposit should be equal to BlockInflation - Inflation::on_initialize(1); - assert!(Inflation::block_inflation() > 0); - assert_eq!(Balances::free_balance(1234) - initial_issuance, Inflation::block_inflation()); - }); - } - - #[test] - fn inflation_second_deposit() { - new_test_ext().execute_with(|| { - // Total issuance = 1_000_000_000 - let initial_issuance: u64 = 1_000_000_000; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - Inflation::on_initialize(1); - - // Next inflation deposit happens when block is multiple of InflationBlockInterval - let mut block: u32 = 2; - let balance_before: u64 = Balances::free_balance(1234); - while block % InflationBlockInterval::get() != 0 { - Inflation::on_initialize(block as u64); - block += 1; - } - let balance_just_before: u64 = Balances::free_balance(1234); - assert_eq!(balance_before, balance_just_before); - - // The block with inflation - Inflation::on_initialize(block as u64); - let balance_after: u64 = Balances::free_balance(1234); - assert_eq!(balance_after - balance_just_before, Inflation::block_inflation()); - }); - } - - #[test] - fn inflation_in_1_year() { - new_test_ext().execute_with(|| { - // Total issuance = 1_000_000_000 - let initial_issuance: u64 = 1_000_000_000; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - Inflation::on_initialize(1); - let block_inflation_year_0 = Inflation::block_inflation(); - - Inflation::on_initialize(YEAR); - let block_inflation_year_1 = Inflation::block_inflation(); - - // Assert that year 1 inflation is less than year 0 - assert!(block_inflation_year_0 > block_inflation_year_1); - }); - } - - #[test] - fn inflation_in_1_to_9_years() { - new_test_ext().execute_with(|| { - // Total issuance = 1_000_000_000 - let initial_issuance: u64 = 1_000_000_000; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - Inflation::on_initialize(1); - - for year in 1..=9 { - let block_inflation_year_before = Inflation::block_inflation(); - Inflation::on_initialize(YEAR * year); - let block_inflation_year_after = Inflation::block_inflation(); - - // Assert that next year inflation is less than previous year inflation - assert!(block_inflation_year_before > block_inflation_year_after); - } - - }); - } - - #[test] - fn inflation_after_year_10_is_flat() { - new_test_ext().execute_with(|| { - // Total issuance = 1_000_000_000 - let initial_issuance: u64 = 1_000_000_000; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - Inflation::on_initialize(YEAR * 9); - - for year in 10..=20 { - let block_inflation_year_before = Inflation::block_inflation(); - Inflation::on_initialize(YEAR * year); - let block_inflation_year_after = Inflation::block_inflation(); - - // Assert that next year inflation is equal to previous year inflation - assert_eq!(block_inflation_year_before, block_inflation_year_after); - } - }); - } - - #[test] - fn inflation_rate_by_year() { - new_test_ext().execute_with(|| { - let payouts: u64 = YEAR / InflationBlockInterval::get() as u64; - - // Inflation starts at 10% and does down by 2/3% every year until year 9 (included), - // then it is flat. - let payout_by_year: [u64; 11] = [ - 1000, - 933, - 867, - 800, - 733, - 667, - 600, - 533, - 467, - 400, - 400 - ]; - - // For accuracy total issuance = payout0 * payouts * 10; - let initial_issuance: u64 = payout_by_year[0] * payouts * 10; - let _ = >::deposit_creating(&1234, initial_issuance); - assert_eq!(Balances::free_balance(1234), initial_issuance); - - for year in 0..=10 { - // Year first block - Inflation::on_initialize(year*YEAR); - let mut actual_payout = Inflation::block_inflation(); - assert_eq!(actual_payout, payout_by_year[year as usize]); - - // Year second block - Inflation::on_initialize(year*YEAR+1); - actual_payout = Inflation::block_inflation(); - assert_eq!(actual_payout, payout_by_year[year as usize]); - - // Year middle block - Inflation::on_initialize(year*YEAR + YEAR/2); - actual_payout = Inflation::block_inflation(); - assert_eq!(actual_payout, payout_by_year[year as usize]); - - // Year last block - Inflation::on_initialize((year + 1)*YEAR-1); - actual_payout = Inflation::block_inflation(); - assert_eq!(actual_payout, payout_by_year[year as usize]); - } - }); - } -} --- 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::*; @@ -343,21 +345,6 @@ /// Variable metadata sponsoring /// Collection id (controlled?2), token id (controlled?2) pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option = None; - - //#region Contract Sponsorship and Ownership - /// Contract address (real) - pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option; - /// Contract address (real) - pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool; - /// (Contract address(real), caller (real)) - pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber; - /// Contract address (real) - pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber; - /// Contract address (real) - pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; - /// Contract address (real) => Whitelisted user (controlled?3) - pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; - //#endregion } add_extra_genesis { build(|config: &GenesisConfig| { @@ -1233,161 +1220,9 @@ ensure_root(origin)?; ::put(limits); - Ok(()) - } - - /// Enable smart contract self-sponsoring. - /// - /// # Permissions - /// - /// * Contract Owner - /// - /// # Arguments - /// - /// * contract address - /// * enable flag - /// - #[weight = ::WeightInfo::enable_contract_sponsoring()] - #[transactional] - pub fn enable_contract_sponsoring( - origin, - contract_address: T::AccountId, - enable: bool - ) -> DispatchResult { - - let sender = ensure_signed(origin)?; - - #[cfg(feature = "runtime-benchmarks")] - >::insert(contract_address.clone(), sender.clone()); - - Self::ensure_contract_owned(sender, &contract_address)?; - - >::insert(contract_address, enable); - Ok(()) - } - - /// Set the rate limit for contract sponsoring to specified number of blocks. - /// - /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. - /// If set to the number B (for blocks), the transactions will be sponsored with a rate - /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid - /// from contract endowment if there are at least B blocks between such transactions. - /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender. - /// - /// # Permissions - /// - /// * Contract Owner - /// - /// # Arguments - /// - /// -`contract_address`: Address of the contract to sponsor - /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed - /// - #[weight = ::WeightInfo::set_contract_sponsoring_rate_limit()] - #[transactional] - pub fn set_contract_sponsoring_rate_limit( - origin, - contract_address: T::AccountId, - rate_limit: T::BlockNumber - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - #[cfg(feature = "runtime-benchmarks")] - >::insert(contract_address.clone(), sender.clone()); - - Self::ensure_contract_owned(sender, &contract_address)?; - >::insert(contract_address, rate_limit); Ok(()) } - /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract. - /// - /// # Permissions - /// - /// * Address that deployed smart contract. - /// - /// # Arguments - /// - /// -`contract_address`: Address of the contract. - /// - /// - `enable`: . - #[weight = ::WeightInfo::toggle_contract_white_list()] - #[transactional] - pub fn toggle_contract_white_list( - origin, - contract_address: T::AccountId, - enable: bool - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - #[cfg(feature = "runtime-benchmarks")] - >::insert(contract_address.clone(), sender.clone()); - - Self::ensure_contract_owned(sender, &contract_address)?; - if enable { - >::insert(contract_address, true); - } else { - >::remove(contract_address); - } - Ok(()) - } - - /// Add an address to smart contract white list. - /// - /// # Permissions - /// - /// * Address that deployed smart contract. - /// - /// # Arguments - /// - /// -`contract_address`: Address of the contract. - /// - /// -`account_address`: Address to add. - #[weight = ::WeightInfo::add_to_contract_white_list()] - #[transactional] - pub fn add_to_contract_white_list( - origin, - contract_address: T::AccountId, - account_address: T::AccountId - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - #[cfg(feature = "runtime-benchmarks")] - >::insert(contract_address.clone(), sender.clone()); - - Self::ensure_contract_owned(sender, &contract_address)?; - >::insert(contract_address, account_address, true); - Ok(()) - } - - /// Remove an address from smart contract white list. - /// - /// # Permissions - /// - /// * Address that deployed smart contract. - /// - /// # Arguments - /// - /// -`contract_address`: Address of the contract. - /// - /// -`account_address`: Address to remove. - #[weight = ::WeightInfo::remove_from_contract_white_list()] - #[transactional] - pub fn remove_from_contract_white_list( - origin, - contract_address: T::AccountId, - account_address: T::AccountId - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - - #[cfg(feature = "runtime-benchmarks")] - >::insert(contract_address.clone(), sender.clone()); - - Self::ensure_contract_owned(sender, &contract_address)?; - >::remove(contract_address, account_address); - Ok(()) - } - #[weight = ::WeightInfo::set_collection_limits()] #[transactional] pub fn set_collection_limits( @@ -2399,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 --- a/pallets/scheduler/Cargo.toml +++ b/pallets/scheduler/Cargo.toml @@ -14,15 +14,12 @@ codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-runtime = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } sp-io = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } frame-benchmarking = { default-features = false, version = '3.0.0', optional = true, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-nft-transaction-payment = { default-features = false, path = "../nft-transaction-payment" } -pallet-nft = { default-features = false, path = "../nft" } -nft-data-structs = { path = '../../primitives', default-features = false } +up-sponsorship = { default-features = false, path = "../../primitives/sponsorship", version = "0.1.0" } log = { version = "0.4.14", default-features = false } [dev-dependencies] @@ -37,10 +34,7 @@ "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "pallet-nft-transaction-payment/std", - "pallet-nft/std", - "pallet-contracts/std", - "nft-data-structs/std", + "up-sponsorship/std", "sp-io/std", "sp-std/std", "log/std", --- a/pallets/scheduler/src/lib.rs +++ b/pallets/scheduler/src/lib.rs @@ -64,10 +64,8 @@ weights::{GetDispatchInfo, Weight}, }; use frame_system::{self as system, ensure_signed}; -use pallet_nft::*; -// use pallet_nft_transaction_payment::{self as nft_transaction_payment}; -use nft_data_structs::*; pub use weights::WeightInfo; +use up_sponsorship::SponsorshipHandler; /// Our pallet's configuration trait. All our types and constants go in here. If the /// pallet is dependent on specific other pallets, then their configuration traits @@ -103,7 +101,7 @@ type MaxScheduledPerBlock: Get; /// Sponsoring function - type Sponsoring: SponsoringResolve::Call>; + type SponsorshipHandler: SponsorshipHandler::Call>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -405,8 +403,7 @@ s.origin.clone() ).into(); let sender = ensure_signed(origin).unwrap_or(T::AccountId::default()); - let who_will_pay = T::Sponsoring::resolve(&sender, &s.call.clone()).unwrap_or( - sender); + let who_will_pay = T::SponsorshipHandler::get_sponsor(&sender, &s.call).unwrap_or(sender); let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay)); let r = s.call.clone().dispatch(sponsor.into()); let maybe_id = s.maybe_id.clone(); --- a/primitives/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "nft-data-structs" -authors = ['Substrate DevHub '] -description = "Nft data structs definitions" -edition = "2018" -license = 'GPL-3.0' -homepage = "https://substrate.dev" -repository = 'https://github.com/clover-network/clover' -version = '0.9.0' - -[dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ['derive'] } -serde = { version = "1.0.119", features = ['derive'], default-features = false } -frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -sp-core = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } -sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } - -[features] -default = ["std"] -std = [ - "serde/std", - "codec/std", - "frame-system/std", - "frame-support/std", - "sp-runtime/std", - "sp-core/std", - "pallet-contracts/std", -] \ No newline at end of file --- /dev/null +++ b/primitives/nft/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "nft-data-structs" +authors = ['Substrate DevHub '] +description = "Nft data structs definitions" +edition = "2018" +license = 'GPL-3.0' +homepage = "https://substrate.dev" +repository = 'https://github.com/clover-network/clover' +version = '0.9.0' + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ['derive'] } +serde = { version = "1.0.119", features = ['derive'], default-features = false } +frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +pallet-contracts = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +sp-core = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } +sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' } + +[features] +default = ["std"] +std = [ + "serde/std", + "codec/std", + "frame-system/std", + "frame-support/std", + "sp-runtime/std", + "sp-core/std", + "pallet-contracts/std", +] \ No newline at end of file --- /dev/null +++ b/primitives/nft/src/lib.rs @@ -0,0 +1,285 @@ + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use serde::{Serialize, Deserialize}; + +use frame_system; +use sp_runtime::sp_std::prelude::Vec; +use codec::{Decode, Encode}; +pub use frame_support::{ + construct_runtime, decl_event, decl_module, decl_storage, decl_error, + dispatch::DispatchResult, + ensure, fail, parameter_types, + traits::{ + Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, + Randomness, IsSubType, WithdrawReasons, + }, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, + WeightToFeePolynomial, DispatchClass, + }, + StorageValue, + transactional, +}; + +pub const MAX_DECIMAL_POINTS: DecimalPoints = 30; +pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000; +pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000; +pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000; + +pub type CollectionId = u32; +pub type TokenId = u32; +pub type DecimalPoints = u8; + +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum CollectionMode { + Invalid, + NFT, + // decimal points + Fungible(DecimalPoints), + ReFungible, +} + +impl Default for CollectionMode { + fn default() -> Self { + Self::Invalid + } +} + +impl Into for CollectionMode { + fn into(self) -> u8 { + match self { + CollectionMode::Invalid => 0, + CollectionMode::NFT => 1, + CollectionMode::Fungible(_) => 2, + CollectionMode::ReFungible => 3, + } + } +} + +pub trait SponsoringResolve { + fn resolve( + who: &AccountId, + call: &Call) -> Option; +} + +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum AccessMode { + Normal, + WhiteList, +} +impl Default for AccessMode { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum SchemaVersion { + ImageURL, + Unique, +} +impl Default for SchemaVersion { + fn default() -> Self { + Self::ImageURL + } +} + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Ownership { + pub owner: AccountId, + pub fraction: u128, +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum SponsorshipState { + /// The fees are applied to the transaction sender + Disabled, + Unconfirmed(AccountId), + /// Transactions are sponsored by specified account + Confirmed(AccountId), +} + +impl SponsorshipState { + pub fn sponsor(&self) -> Option<&AccountId> { + match self { + Self::Confirmed(sponsor) => Some(sponsor), + _ => None, + } + } + + pub fn pending_sponsor(&self) -> Option<&AccountId> { + match self { + Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor), + _ => None, + } + } + + pub fn confirmed(&self) -> bool { + matches!(self, Self::Confirmed(_)) + } +} + +impl Default for SponsorshipState { + fn default() -> Self { + Self::Disabled + } +} + +#[derive(Encode, Decode, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Collection { + pub owner: T::AccountId, + pub mode: CollectionMode, + pub access: AccessMode, + pub decimal_points: DecimalPoints, + pub name: Vec, // 64 include null escape char + pub description: Vec, // 256 include null escape char + pub token_prefix: Vec, // 16 include null escape char + pub mint_mode: bool, + pub offchain_schema: Vec, + pub schema_version: SchemaVersion, + pub sponsorship: SponsorshipState, + pub limits: CollectionLimits, // Collection private restrictions + pub variable_on_chain_schema: Vec, // + pub const_on_chain_schema: Vec, // +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct NftItemType { + pub owner: AccountId, + pub const_data: Vec, + pub variable_data: Vec, +} + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct FungibleItemType { + pub value: u128, +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ReFungibleItemType { + pub owner: Vec>, + pub const_data: Vec, + pub variable_data: Vec, +} + + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CollectionLimits { + pub account_token_ownership_limit: u32, + pub sponsored_data_size: u32, + /// None - setVariableMetadata is not sponsored + /// Some(v) - setVariableMetadata is sponsored + /// if there is v block between txs + pub sponsored_data_rate_limit: Option, + pub token_limit: u32, + + // Timeouts for item types in passed blocks + pub sponsor_transfer_timeout: u32, + pub owner_can_transfer: bool, + pub owner_can_destroy: bool, +} + +impl Default for CollectionLimits { + fn default() -> Self { + Self { + account_token_ownership_limit: 10_000_000, + token_limit: u32::max_value(), + sponsored_data_size: u32::MAX, + sponsored_data_rate_limit: None, + sponsor_transfer_timeout: 14400, + owner_can_transfer: true, + owner_can_destroy: true + } + } +} + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ChainLimits { + pub collection_numbers_limit: u32, + pub account_token_ownership_limit: u32, + pub collections_admins_limit: u64, + pub custom_data_limit: u32, + + // Timeouts for item types in passed blocks + pub nft_sponsor_transfer_timeout: u32, + pub fungible_sponsor_transfer_timeout: u32, + pub refungible_sponsor_transfer_timeout: u32, + + // Schema limits + pub offchain_schema_limit: u32, + pub variable_on_chain_schema_limit: u32, + pub const_on_chain_schema_limit: u32, +} + + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CreateNftData { + pub const_data: Vec, + pub variable_data: Vec, +} + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CreateFungibleData { + pub value: u128, +} + +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct CreateReFungibleData { + pub const_data: Vec, + pub variable_data: Vec, + pub pieces: u128, +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum CreateItemData { + NFT(CreateNftData), + Fungible(CreateFungibleData), + ReFungible(CreateReFungibleData), +} + +impl CreateItemData { + pub fn len(&self) -> usize { + let len = match self { + CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(), + CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(), + _ => 0 + }; + + return len; + } +} + +impl From for CreateItemData { + fn from(item: CreateNftData) -> Self { + CreateItemData::NFT(item) + } +} + +impl From for CreateItemData { + fn from(item: CreateReFungibleData) -> Self { + CreateItemData::ReFungible(item) + } +} + +impl From for CreateItemData { + fn from(item: CreateFungibleData) -> Self { + CreateItemData::Fungible(item) + } +} --- /dev/null +++ b/primitives/sponsorship/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "up-sponsorship" +version = "0.1.0" +edition = "2018" + +[dependencies] +impl-trait-for-tuples = "0.2.1" + +[features] +default = ["std"] +std = [] \ No newline at end of file --- /dev/null +++ b/primitives/sponsorship/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] + +pub trait SponsorshipHandler { + fn get_sponsor(who: &AccountId, call: &Call) -> Option; +} + +impl SponsorshipHandler for () { + fn get_sponsor(_who: &A, _call: &C) -> Option { + None + } +} + +macro_rules! impl_tuples { + ($($ident:ident)+) => { + impl SponsorshipHandler for ($($ident,)+) + where + $( + $ident: SponsorshipHandler + ),+ + { + fn get_sponsor(who: &AccountId, call: &Call) -> Option { + $( + if let Some(account) = $ident::get_sponsor(who, call) { + return Some(account); + } + )+ + None + } + } + } +} + +impl_tuples! {A} +impl_tuples! {A B} +impl_tuples! {A B C} +impl_tuples! {A B C D} +impl_tuples! {A B C D E} +impl_tuples! {A B C D E F} +impl_tuples! {A B C D E F G} +impl_tuples! {A B C D E F G H} +impl_tuples! {A B C D E F G H I} +impl_tuples! {A B C D E F G H I J} --- a/primitives/src/lib.rs +++ /dev/null @@ -1,285 +0,0 @@ - -#![cfg_attr(not(feature = "std"), no_std)] - -pub use serde::{Serialize, Deserialize}; - -use frame_system; -use sp_runtime::sp_std::prelude::Vec; -use codec::{Decode, Encode}; -pub use frame_support::{ - construct_runtime, decl_event, decl_module, decl_storage, decl_error, - dispatch::DispatchResult, - ensure, fail, parameter_types, - traits::{ - Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, - Randomness, IsSubType, WithdrawReasons, - }, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, - WeightToFeePolynomial, DispatchClass, - }, - StorageValue, - transactional, -}; - -pub const MAX_DECIMAL_POINTS: DecimalPoints = 30; -pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000; -pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000; -pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000; - -pub type CollectionId = u32; -pub type TokenId = u32; -pub type DecimalPoints = u8; - -#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum CollectionMode { - Invalid, - NFT, - // decimal points - Fungible(DecimalPoints), - ReFungible, -} - -impl Default for CollectionMode { - fn default() -> Self { - Self::Invalid - } -} - -impl Into for CollectionMode { - fn into(self) -> u8 { - match self { - CollectionMode::Invalid => 0, - CollectionMode::NFT => 1, - CollectionMode::Fungible(_) => 2, - CollectionMode::ReFungible => 3, - } - } -} - -pub trait SponsoringResolve { - fn resolve( - who: &AccountId, - call: &Call) -> Option; -} - -#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum AccessMode { - Normal, - WhiteList, -} -impl Default for AccessMode { - fn default() -> Self { - Self::Normal - } -} - -#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum SchemaVersion { - ImageURL, - Unique, -} -impl Default for SchemaVersion { - fn default() -> Self { - Self::ImageURL - } -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Ownership { - pub owner: AccountId, - pub fraction: u128, -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum SponsorshipState { - /// The fees are applied to the transaction sender - Disabled, - Unconfirmed(AccountId), - /// Transactions are sponsored by specified account - Confirmed(AccountId), -} - -impl SponsorshipState { - pub fn sponsor(&self) -> Option<&AccountId> { - match self { - Self::Confirmed(sponsor) => Some(sponsor), - _ => None, - } - } - - pub fn pending_sponsor(&self) -> Option<&AccountId> { - match self { - Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor), - _ => None, - } - } - - pub fn confirmed(&self) -> bool { - matches!(self, Self::Confirmed(_)) - } -} - -impl Default for SponsorshipState { - fn default() -> Self { - Self::Disabled - } -} - -#[derive(Encode, Decode, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct Collection { - pub owner: T::AccountId, - pub mode: CollectionMode, - pub access: AccessMode, - pub decimal_points: DecimalPoints, - pub name: Vec, // 64 include null escape char - pub description: Vec, // 256 include null escape char - pub token_prefix: Vec, // 16 include null escape char - pub mint_mode: bool, - pub offchain_schema: Vec, - pub schema_version: SchemaVersion, - pub sponsorship: SponsorshipState, - pub limits: CollectionLimits, // Collection private restrictions - pub variable_on_chain_schema: Vec, // - pub const_on_chain_schema: Vec, // -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct NftItemType { - pub owner: AccountId, - pub const_data: Vec, - pub variable_data: Vec, -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct FungibleItemType { - pub value: u128, -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ReFungibleItemType { - pub owner: Vec>, - pub const_data: Vec, - pub variable_data: Vec, -} - - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CollectionLimits { - pub account_token_ownership_limit: u32, - pub sponsored_data_size: u32, - /// None - setVariableMetadata is not sponsored - /// Some(v) - setVariableMetadata is sponsored - /// if there is v block between txs - pub sponsored_data_rate_limit: Option, - pub token_limit: u32, - - // Timeouts for item types in passed blocks - pub sponsor_transfer_timeout: u32, - pub owner_can_transfer: bool, - pub owner_can_destroy: bool, -} - -impl Default for CollectionLimits { - fn default() -> Self { - Self { - account_token_ownership_limit: 10_000_000, - token_limit: u32::max_value(), - sponsored_data_size: u32::MAX, - sponsored_data_rate_limit: None, - sponsor_transfer_timeout: 14400, - owner_can_transfer: true, - owner_can_destroy: true - } - } -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct ChainLimits { - pub collection_numbers_limit: u32, - pub account_token_ownership_limit: u32, - pub collections_admins_limit: u64, - pub custom_data_limit: u32, - - // Timeouts for item types in passed blocks - pub nft_sponsor_transfer_timeout: u32, - pub fungible_sponsor_transfer_timeout: u32, - pub refungible_sponsor_transfer_timeout: u32, - - // Schema limits - pub offchain_schema_limit: u32, - pub variable_on_chain_schema_limit: u32, - pub const_on_chain_schema_limit: u32, -} - - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CreateNftData { - pub const_data: Vec, - pub variable_data: Vec, -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CreateFungibleData { - pub value: u128, -} - -#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CreateReFungibleData { - pub const_data: Vec, - pub variable_data: Vec, - pub pieces: u128, -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum CreateItemData { - NFT(CreateNftData), - Fungible(CreateFungibleData), - ReFungible(CreateReFungibleData), -} - -impl CreateItemData { - pub fn len(&self) -> usize { - let len = match self { - CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(), - CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(), - _ => 0 - }; - - return len; - } -} - -impl From for CreateItemData { - fn from(item: CreateNftData) -> Self { - CreateItemData::NFT(item) - } -} - -impl From for CreateItemData { - fn from(item: CreateReFungibleData) -> Self { - CreateItemData::ReFungible(item) - } -} - -impl From for CreateItemData { - fn from(item: CreateFungibleData) -> Self { - CreateItemData::Fungible(item) - } -} \ No newline at end of file --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -46,6 +46,7 @@ 'pallet-contracts/std', 'pallet-contracts-primitives/std', 'pallet-contracts-rpc-runtime-api/std', + 'pallet-contract-helpers/std', 'pallet-randomness-collective-flip/std', 'pallet-sudo/std', 'pallet-timestamp/std', @@ -372,8 +373,9 @@ [dependencies] pallet-nft = { path = '../pallets/nft', default-features = false, version = '3.0.0' } pallet-inflation = { path = '../pallets/inflation', default-features = false, version = '3.0.0' } -nft-data-structs = { path = '../primitives', default-features = false, version = '0.9.0' } +nft-data-structs = { path = '../primitives/nft', default-features = false, version = '0.9.0' } pallet-scheduler = { path = '../pallets/scheduler', default-features = false, version = '3.0.0' } +pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' } pallet-nft-transaction-payment = { path = '../pallets/nft-transaction-payment', default-features = false, version = '3.0.0' } pallet-nft-charge-transaction = { path = '../pallets/nft-charge-transaction', default-features = false, version = '3.0.0' } --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -734,6 +734,11 @@ } } +type SponsorshipHandler = ( + pallet_nft::NftSponsorshipHandler, + pallet_contract_helpers::ContractSponsorshipHandler, +); + impl pallet_scheduler::Config for Runtime { type Event = Event; type Origin = Origin; @@ -742,16 +747,18 @@ type MaximumWeight = MaximumSchedulerWeight; type ScheduleOrigin = EnsureSigned; type MaxScheduledPerBlock = MaxScheduledPerBlock; - type Sponsoring = Sponsoring; + type SponsorshipHandler = SponsorshipHandler; type WeightInfo = (); } impl pallet_nft_transaction_payment::Config for Runtime { + type SponsorshipHandler = SponsorshipHandler; } -impl pallet_nft_charge_transaction::Config for Runtime { -} +impl pallet_nft_charge_transaction::Config for Runtime {} +impl pallet_contract_helpers::Config for Runtime {} + construct_runtime!( pub enum Runtime where Block = Block, @@ -791,6 +798,7 @@ Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, NftPayment: pallet_nft_transaction_payment::{Pallet, Call, Storage}, Charging: pallet_nft_charge_transaction::{Pallet, Call, Storage }, + ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage}, } ); @@ -829,6 +837,7 @@ system::CheckNonce, system::CheckWeight, pallet_nft_charge_transaction::ChargeTransactionPayment, + pallet_contract_helpers::ContractHelpersExtension, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic;