difftreelog
Merge pull request #149 from usetech-llc/feature/NFTPAR-418
in: master
Scheduler
27 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3433,7 +3433,7 @@
"pallet-proxy",
"pallet-randomness-collective-flip",
"pallet-recovery",
- "pallet-scheduler",
+ "pallet-scheduler 3.0.0 (git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3)",
"pallet-session",
"pallet-society",
"pallet-staking",
@@ -4517,6 +4517,7 @@
"futures 0.3.14",
"jsonrpc-core",
"log",
+ "nft-data-structs",
"nft-runtime",
"pallet-contracts",
"pallet-contracts-rpc",
@@ -4568,6 +4569,19 @@
]
[[package]]
+name = "nft-data-structs"
+version = "0.9.0"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-contracts",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-runtime",
+]
+
+[[package]]
name = "nft-runtime"
version = "3.0.0"
dependencies = [
@@ -4585,6 +4599,7 @@
"frame-system-benchmarking",
"frame-system-rpc-runtime-api",
"hex-literal",
+ "nft-data-structs",
"pallet-aura",
"pallet-balances",
"pallet-contracts",
@@ -4592,7 +4607,10 @@
"pallet-contracts-rpc-runtime-api",
"pallet-inflation",
"pallet-nft",
+ "pallet-nft-charge-transaction",
+ "pallet-nft-transaction-payment",
"pallet-randomness-collective-flip",
+ "pallet-scheduler 3.0.0",
"pallet-sudo",
"pallet-timestamp",
"pallet-transaction-payment",
@@ -5245,8 +5263,32 @@
"frame-benchmarking",
"frame-support",
"frame-system",
+ "nft-data-structs",
+ "pallet-balances",
+ "pallet-contracts",
+ "pallet-randomness-collective-flip",
+ "pallet-timestamp",
+ "pallet-transaction-payment",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "pallet-nft-charge-transaction"
+version = "3.0.0"
+dependencies = [
+ "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",
@@ -5259,6 +5301,28 @@
]
[[package]]
+name = "pallet-nft-transaction-payment"
+version = "3.0.0"
+dependencies = [
+ "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",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
name = "pallet-nicks"
version = "3.0.0"
source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
@@ -5344,6 +5408,27 @@
]
[[package]]
+name = "pallet-scheduler"
+version = "3.0.0"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "log",
+ "nft-data-structs",
+ "pallet-contracts",
+ "pallet-nft",
+ "pallet-nft-transaction-payment",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "substrate-test-utils",
+]
+
+[[package]]
name = "pallet-session"
version = "3.0.0"
source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
@@ -6639,7 +6724,7 @@
"pallet-offences",
"pallet-proxy",
"pallet-randomness-collective-flip",
- "pallet-scheduler",
+ "pallet-scheduler 3.0.0 (git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3)",
"pallet-session",
"pallet-staking",
"pallet-staking-reward-curve",
@@ -10141,6 +10226,26 @@
]
[[package]]
+name = "substrate-test-utils"
+version = "3.0.0"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
+dependencies = [
+ "futures 0.3.14",
+ "substrate-test-utils-derive",
+ "tokio 0.2.25",
+]
+
+[[package]]
+name = "substrate-test-utils-derive"
+version = "0.9.0"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
+dependencies = [
+ "proc-macro-crate 1.0.0",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "substrate-wasm-builder"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -10404,6 +10509,7 @@
"pin-project-lite 0.1.12",
"signal-hook-registry",
"slab",
+ "tokio-macros",
"winapi 0.3.9",
]
@@ -10472,6 +10578,17 @@
]
[[package]]
+name = "tokio-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "tokio-named-pipes"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -11476,7 +11593,7 @@
"pallet-proxy",
"pallet-randomness-collective-flip",
"pallet-recovery",
- "pallet-scheduler",
+ "pallet-scheduler 3.0.0 (git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3)",
"pallet-session",
"pallet-society",
"pallet-staking",
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
'node',
'pallets/*',
+ 'primitives',
'runtime',
]
resolver = "2"
node/Cargo.tomldiffbeforeafterboth--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -283,6 +283,10 @@
path = '../runtime'
version = '3.0.0'
+[dependencies.nft-data-structs]
+path="../primitives"
+default-features = false
+
################################################################################
# Package
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -5,6 +5,7 @@
use cumulus_primitives_core::ParaId;
use nft_runtime::*;
+use nft_data_structs::*;
use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};
use sc_service::ChainType;
use sp_core::{sr25519, Pair, Public};
@@ -161,7 +162,7 @@
balances: endowed_accounts
.iter()
.cloned()
- .map(|k| (k, 1 << 100))
+ .map(|k| (k, 1 << 70))
.collect(),
},
pallet_treasury: Default::default(),
pallets/nft-charge-transaction/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-charge-transaction/Cargo.toml
@@ -0,0 +1,59 @@
+[package]
+authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
+description = 'Unqiue pallet nft specific transaction payment'
+edition = '2018'
+homepage = 'https://substrate.io'
+license = 'Unlicense'
+name = 'pallet-nft-charge-transaction'
+repository = 'https://github.com/usetech-llc/nft_private/'
+version = '3.0.0'
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+# alias "parity-scale-code" to "codec"
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '2.0.0'
+
+[dependencies]
+serde = { version = "1.0.119" }
+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']
+std = [
+ 'codec/std',
+ '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',
+ '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"]
pallets/nft-charge-transaction/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-charge-transaction/src/lib.rs
@@ -0,0 +1,221 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "std")]
+pub use std::*;
+
+#[cfg(feature = "std")]
+pub use serde::*;
+
+#[cfg(feature = "runtime-benchmarks")]
+mod benchmarking;
+
+#[cfg(test)]
+mod tests;
+
+use codec::{Decode, Encode};
+use frame_support::traits::{ Get};
+use frame_support::{
+ decl_module, decl_storage,
+ traits::{
+ IsSubType,
+ },
+ weights::{
+ DispatchInfo, PostDispatchInfo, DispatchClass
+ }
+};
+use sp_runtime::{
+ traits::{
+ DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,
+ },
+ transaction_validity::{
+ TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction,
+ },
+ FixedPointOperand, DispatchResult
+};
+use pallet_contracts::chain_extension::UncheckedFrom;
+use pallet_transaction_payment::OnChargeTransaction;
+use sp_std::prelude::*;
+
+
+pub trait Config: frame_system::Config +
+ pallet_nft_transaction_payment::Config
+{
+
+}
+
+decl_storage! {
+ trait Store for Module<T: Config> as NftTransactionPayment
+ {}
+}
+
+decl_module! {
+
+ pub struct Module<T: Config> for enum Call
+ where
+ origin: T::Origin,
+ {}
+}
+
+type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
+
+/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
+/// in the queue.
+#[derive(Encode, Decode, Clone, Eq, PartialEq)]
+pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
+
+impl<T: Config + Send + Sync> sp_std::fmt::Debug
+ for ChargeTransactionPayment<T>
+{
+ #[cfg(feature = "std")]
+ fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+ write!(f, "ChargeTransactionPayment<{:?}>", self.0)
+ }
+ #[cfg(not(feature = "std"))]
+ fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+ Ok(())
+ }
+}
+
+impl<T: Config> ChargeTransactionPayment<T>
+where
+ T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
+ BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>,
+{
+ fn traditional_fee(
+ len: usize,
+ info: &DispatchInfoOf<T::Call>,
+ tip: BalanceOf<T>,
+ ) -> BalanceOf<T>
+ where
+ T::Call: Dispatchable<Info = DispatchInfo>,
+ {
+ <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)
+ }
+
+ fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {
+ let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);
+ let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);
+ let len_saturation = max_block_length as u64 / (len as u64).max(1);
+ let coefficient: BalanceOf<T> = weight_saturation
+ .min(len_saturation)
+ .saturated_into::<BalanceOf<T>>();
+ final_fee
+ .saturating_mul(coefficient)
+ .saturated_into::<TransactionPriority>()
+ }
+
+ fn withdraw_fee(
+ &self,
+ who: &T::AccountId,
+ call: &T::Call,
+ info: &DispatchInfoOf<T::Call>,
+ len: usize,
+ ) -> Result<
+ (
+ BalanceOf<T>,
+ <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
+ ),
+ TransactionValidityError,
+ > {
+ let tip = self.0;
+
+ let fee = Self::traditional_fee(len, info, tip);
+
+ // Only mess with balances if fee is not zero.
+ if fee.is_zero() {
+ return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)
+ .map(|i| (fee, i));
+ }
+
+ // check errors
+ let _error = <pallet_nft_transaction_payment::Module<T>>::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 = <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
+
+ let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());
+
+ <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)
+ .map(|i| (fee, i))
+ }
+}
+
+impl<T: Config + Send + Sync> SignedExtension
+ for ChargeTransactionPayment<T>
+where
+ BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
+ T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>,
+{
+ const IDENTIFIER: &'static str = "ChargeTransactionPayment";
+ type AccountId = T::AccountId;
+ type Call = T::Call;
+ type AdditionalSigned = ();
+ type Pre = (
+ // tip
+ BalanceOf<T>,
+ // who pays fee
+ Self::AccountId,
+ // imbalance resulting from withdrawing the fee
+ <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
+ );
+ fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
+ Ok(())
+ }
+
+ fn validate(
+ &self,
+ who: &Self::AccountId,
+ call: &Self::Call,
+ info: &DispatchInfoOf<Self::Call>,
+ len: usize,
+ ) -> TransactionValidity {
+ let (fee, _) = self.withdraw_fee(who, call, info, len)?;
+ Ok(ValidTransaction {
+ priority: Self::get_priority(len, info, fee),
+ ..Default::default()
+ })
+ }
+
+ fn pre_dispatch(
+ self,
+ who: &Self::AccountId,
+ call: &Self::Call,
+ info: &DispatchInfoOf<Self::Call>,
+ len: usize,
+ ) -> Result<Self::Pre, TransactionValidityError> {
+ let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;
+ Ok((self.0, who.clone(), imbalance))
+ }
+
+ fn post_dispatch(
+ pre: Self::Pre,
+ info: &DispatchInfoOf<Self::Call>,
+ post_info: &PostDispatchInfoOf<Self::Call>,
+ len: usize,
+ _result: &DispatchResult,
+ ) -> Result<(), TransactionValidityError> {
+ let (tip, who, imbalance) = pre;
+ let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(
+ len as u32,
+ info,
+ post_info,
+ tip,
+ );
+ <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;
+ Ok(())
+ }
+}
\ No newline at end of file
pallets/nft-transaction-payment/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/Cargo.toml
@@ -0,0 +1,57 @@
+[package]
+authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
+description = 'Unqiue pallet nft specific transaction payment'
+edition = '2018'
+homepage = 'https://substrate.io'
+license = 'Unlicense'
+name = 'pallet-nft-transaction-payment'
+repository = 'https://github.com/usetech-llc/nft_private/'
+version = '3.0.0'
+
+[package.metadata.docs.rs]
+targets = ['x86_64-unknown-linux-gnu']
+
+# alias "parity-scale-code" to "codec"
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '2.0.0'
+
+[dependencies]
+serde = { version = "1.0.119" }
+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" }
+
+[features]
+default = ['std']
+std = [
+ 'codec/std',
+ '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',
+ 'pallet-transaction-payment/std',
+ 'sp-std/std',
+ 'sp-runtime/std',
+ 'nft-data-structs/std',
+ 'frame-benchmarking/std',
+]
+runtime-benchmarks = ["frame-benchmarking"]
pallets/nft-transaction-payment/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/README.md
@@ -0,0 +1,13 @@
+# Nft Transaction Payment
+
+## Overview
+
+A module containing the sponsoring logic for paying for sponsored collections
+
+**NOTE:** The scheduled calls will be dispatched with the default filter
+for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
+except root which will get no filter. And not the filter contained in origin
+use to call `fn schedule`.
+
+If a call is scheduled using proxy or whatever mecanism which adds filter,
+then those filter will not be used when dispatching the schedule call.
pallets/nft-transaction-payment/src/benchmarking.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/src/benchmarking.rs
@@ -0,0 +1,19 @@
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+use crate::Module as NftTransactionPayment;
+
+use sp_std::prelude::*;
+use frame_system::RawOrigin;
+use frame_benchmarking::{benchmarks};
+use frame_support::traits::OnInitialize;
+
+benchmarks! {
+
+ // on_initialize {
+ // let block1: T::BlockNumber = T::BlockNumber::from(1u32);
+ // let block2: T::BlockNumber = T::BlockNumber::from(2u32);
+ // Inflation::<T>::on_initialize(block1); // Create Treasury account
+ // }: { Inflation::<T>::on_initialize(block2); } // Benchmark deposit_into_existing path
+
+}
pallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/src/lib.rs
@@ -0,0 +1,401 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "std")]
+pub use std::*;
+
+#[cfg(feature = "std")]
+pub use serde::*;
+
+#[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 sp_std::prelude::*;
+use nft_data_structs::{
+ CreateItemData,
+ CollectionId, CollectionMode, TokenId
+};
+
+type CodeHash<T> = <T as frame_system::Config>::Hash;
+
+ 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<T: Config> {
+ /// 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,
+ }
+}
+
+ decl_storage! {
+ trait Store for Module<T: Config> as NftTransactionPayment{
+
+ }
+}
+
+decl_module! {
+
+ pub struct Module<T: Config> for enum Call
+ where
+ origin: T::Origin,
+ {
+ }
+}
+
+impl<T: Config> Module<T>
+{
+
+ pub fn check_error(
+ who: &T::AccountId,
+ call: &T::Call
+ ) -> Result<bool, TransactionValidityError> where
+ T::Call: Dispatchable<Info=DispatchInfo>,
+ T::Call: IsSubType<pallet_nft::Call<T>>,
+ T::Call: IsSubType<pallet_contracts::Call<T>>,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>
+ {
+
+ match IsSubType::<pallet_contracts::Call<T>>::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::<T>::get(called_contract.clone()).as_ref() == Some(who);
+ let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
+
+ if !owned_contract && white_list_enabled {
+ if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
+ return Err(InvalidTransaction::Call.into());
+ }
+ }
+ Ok(true)
+ },
+ _ => { Ok(true) },
+ }
+ }
+
+ pub fn withdraw_type(
+ who: &T::AccountId,
+ call: &T::Call
+ ) -> Option<T::AccountId> where
+ T::Call: Dispatchable<Info=DispatchInfo>,
+ T::Call: IsSubType<pallet_nft::Call<T>>,
+ T::Call: IsSubType<pallet_contracts::Call<T>>,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>
+ {
+
+ let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::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::<pallet_contracts::Call<T>>::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<T::AccountId> {
+
+ let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+ let limit = collection.limits.sponsor_transfer_timeout;
+ if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {
+ let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ return None;
+ }
+ }
+ pallet_nft::CreateItemBasket::<T>::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<T::AccountId> {
+
+ let collection = pallet_nft::CollectionById::<T>::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 = <frame_system::Pallet<T>>::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::<T>::contains_key(collection_id, item_id) {
+ let last_tx_block = pallet_nft::NftTransferBasket::<T>::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::<T>::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 = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let mut sponsored = true;
+ if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {
+ let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
+ }
+ if sponsored {
+ pallet_nft::FungibleTransferBasket::<T>::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::<T>::contains_key(collection_id, item_id) {
+ let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::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::<T>::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<u8>,
+ ) -> Option<T::AccountId> {
+
+ let mut sponsor_metadata_changes = false;
+
+ let collection = pallet_nft::CollectionById::<T>::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 = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+ if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)
+ .map(|last_block| block_number - last_block > rate_limit)
+ .unwrap_or(true)
+ {
+ sponsor_metadata_changes = true;
+ pallet_nft::VariableMetaDataBasket::<T>::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: &<T::Lookup as StaticLookup>::Source
+ ) -> Option<T::AccountId> {
+
+ let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());
+
+ let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);
+ let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
+
+ // ???
+ if !owned_contract && white_list_enabled {
+ if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
+ return Some(who.clone())
+ // return Err(InvalidTransaction::Call.into());
+ }
+ }
+
+ let mut sponsor_transfer = false;
+ if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {
+ let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);
+ let limit_time = last_tx_block + rate_limit;
+
+ if block_number >= limit_time {
+ pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);
+ sponsor_transfer = true;
+ }
+ } else {
+ sponsor_transfer = false;
+ }
+
+ if sponsor_transfer {
+ if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {
+ if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {
+ return Some(called_contract);
+ }
+ }
+ }
+
+ None
+ }
+
+ pub fn withdraw_contract_instantiate(
+ who: &T::AccountId,
+ code_hash: &CodeHash<T>,
+ salt: &[u8],
+ ) -> Option<T::AccountId> where
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>
+ {
+
+ let new_contract_address = <pallet_contracts::Pallet<T>>::contract_address(
+ &who,
+ code_hash,
+ salt,
+ );
+ pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());
+
+ None
+ }
+}
\ No newline at end of file
pallets/nft-transaction-payment/src/tests.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/src/tests.rs
@@ -0,0 +1,241 @@
+#[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<Test>;
+ type Block = frame_system::mocking::MockBlock<Test>;
+
+ 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<T>},
+ 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<Self::AccountId>;
+ type Header = Header;
+ type Event = ();
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData<u64>;
+ 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::<Test>().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 _ = <Balances as Currency<_>>::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 _ = <Balances as Currency<_>>::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 _ = <Balances as Currency<_>>::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 _ = <Balances as Currency<_>>::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 _ = <Balances as Currency<_>>::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 _ = <Balances as Currency<_>>::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]);
+ }
+ });
+ }
+}
pallets/nft/Cargo.tomldiffbeforeafterboth--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -27,6 +27,7 @@
'pallet-contracts/std',
'pallet-randomness-collective-flip/std',
'pallet-transaction-payment/std',
+ 'nft-data-structs/std',
'sp-std/std',
'sp-runtime/std',
'frame-benchmarking/std',
@@ -118,3 +119,10 @@
branch = 'polkadot-v0.9.3'
version = '3.0.0'
+################################################################################
+# Local Dependencies
+
+[dependencies.nft-data-structs]
+default-features = false
+path = '../../primitives'
+version = '0.9.0'
\ No newline at end of file
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516pub use frame_support::{17 construct_runtime, decl_event, decl_module, decl_storage, decl_error,18 dispatch::DispatchResult,19 ensure, fail, parameter_types,20 traits::{21 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,22 Randomness, IsSubType, WithdrawReasons,23 },24 weights::{25 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},26 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,27 WeightToFeePolynomial, DispatchClass,28 },29 StorageValue,30 transactional,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use core::ops::{Deref, DerefMut};36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39 CollectionId, CollectionMode, TokenId, 40 SchemaVersion, SponsorshipState, Ownership,41 NftItemType, FungibleItemType, ReFungibleItemType42};4344#[cfg(test)]45mod mock;4647#[cfg(test)]48mod tests;4950mod default_weights;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;5455pub trait WeightInfo {56 fn create_collection() -> Weight;57 fn destroy_collection() -> Weight;58 fn add_to_white_list() -> Weight;59 fn remove_from_white_list() -> Weight;60 fn set_public_access_mode() -> Weight;61 fn set_mint_permission() -> Weight;62 fn change_collection_owner() -> Weight;63 fn add_collection_admin() -> Weight;64 fn remove_collection_admin() -> Weight;65 fn set_collection_sponsor() -> Weight;66 fn confirm_sponsorship() -> Weight;67 fn remove_collection_sponsor() -> Weight;68 fn create_item(s: usize) -> Weight;69 fn burn_item() -> Weight;70 fn transfer() -> Weight;71 fn approve() -> Weight;72 fn transfer_from() -> Weight;73 fn set_offchain_schema() -> Weight;74 fn set_const_on_chain_schema() -> Weight;75 fn set_variable_on_chain_schema() -> Weight;76 fn set_variable_meta_data() -> Weight;77 fn enable_contract_sponsoring() -> Weight;78 fn set_schema_version() -> Weight;79 fn set_chain_limits() -> Weight;80 fn set_contract_sponsoring_rate_limit() -> Weight;81 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;82 fn toggle_contract_white_list() -> Weight;83 fn add_to_contract_white_list() -> Weight;84 fn remove_from_contract_white_list() -> Weight;85 fn set_collection_limits() -> Weight;86}8788decl_error! {89 /// Error for non-fungible-token module.90 pub enum Error for Module<T: Config> {91 /// Total collections bound exceeded.92 TotalCollectionsLimitExceeded,93 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.94 CollectionDecimalPointLimitExceeded, 95 /// Collection name can not be longer than 63 char.96 CollectionNameLimitExceeded, 97 /// Collection description can not be longer than 255 char.98 CollectionDescriptionLimitExceeded, 99 /// Token prefix can not be longer than 15 char.100 CollectionTokenPrefixLimitExceeded,101 /// This collection does not exist.102 CollectionNotFound,103 /// Item not exists.104 TokenNotFound,105 /// Admin not found106 AdminNotFound,107 /// Arithmetic calculation overflow.108 NumOverflow, 109 /// Account already has admin role.110 AlreadyAdmin, 111 /// You do not own this collection.112 NoPermission,113 /// This address is not set as sponsor, use setCollectionSponsor first.114 ConfirmUnsetSponsorFail,115 /// Collection is not in mint mode.116 PublicMintingNotAllowed,117 /// Sender parameter and item owner must be equal.118 MustBeTokenOwner,119 /// Item balance not enough.120 TokenValueTooLow,121 /// Size of item is too large.122 NftSizeLimitExceeded,123 /// No approve found124 ApproveNotFound,125 /// Requested value more than approved.126 TokenValueNotEnough,127 /// Only approved addresses can call this method.128 ApproveRequired,129 /// Address is not in white list.130 AddresNotInWhiteList,131 /// Number of collection admins bound exceeded.132 CollectionAdminsLimitExceeded,133 /// Owned tokens by a single address bound exceeded.134 AddressOwnershipLimitExceeded,135 /// Length of items properties must be greater than 0.136 EmptyArgument,137 /// const_data exceeded data limit.138 TokenConstDataLimitExceeded,139 /// variable_data exceeded data limit.140 TokenVariableDataLimitExceeded,141 /// Not NFT item data used to mint in NFT collection.142 NotNftDataUsedToMintNftCollectionToken,143 /// Not Fungible item data used to mint in Fungible collection.144 NotFungibleDataUsedToMintFungibleCollectionToken,145 /// Not Re Fungible item data used to mint in Re Fungible collection.146 NotReFungibleDataUsedToMintReFungibleCollectionToken,147 /// Unexpected collection type.148 UnexpectedCollectionType,149 /// Can't store metadata in fungible tokens.150 CantStoreMetadataInFungibleTokens,151 /// Collection token limit exceeded152 CollectionTokenLimitExceeded,153 /// Account token limit exceeded per collection154 AccountTokenLimitExceeded,155 /// Collection limit bounds per collection exceeded156 CollectionLimitBoundsExceeded,157 /// Tried to enable permissions which are only permitted to be disabled158 OwnerPermissionsCantBeReverted,159 /// Schema data size limit bound exceeded160 SchemaDataLimitExceeded,161 /// Maximum refungibility exceeded162 WrongRefungiblePieces,163 /// createRefungible should be called with one owner164 BadCreateRefungibleCall,165 }166}167168pub struct CollectionHandle<T: system::Config> {169 pub id: CollectionId,170 pub collection: Collection<T>,171}172173impl<T: frame_system::Config> Deref for CollectionHandle<T> {174 type Target = Collection<T>;175176 fn deref(&self) -> &Self::Target {177 &self.collection178 }179}180181impl<T: frame_system::Config> DerefMut for CollectionHandle<T> {182 fn deref_mut(&mut self) -> &mut Self::Target {183 &mut self.collection184 }185}186187pub trait Config: system::Config + Sized {188 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;189190 /// Weight information for extrinsics in this pallet.191 type WeightInfo: WeightInfo;192193 type Currency: Currency<Self::AccountId>;194 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;195 type TreasuryAccountId: Get<Self::AccountId>;196}197198// # Used definitions199//200// ## User control levels201//202// chain-controlled - key is uncontrolled by user203// i.e autoincrementing index204// can use non-cryptographic hash205// real - key is controlled by user206// but it is hard to generate enough colliding values, i.e owner of signed txs207// can use non-cryptographic hash208// controlled - key is completly controlled by users209// i.e maps with mutable keys210// should use cryptographic hash211//212// ## User control level downgrade reasons213//214// ?1 - chain-controlled -> controlled215// collections/tokens can be destroyed, resulting in massive holes216// ?2 - chain-controlled -> controlled217// same as ?1, but can be only added, resulting in easier exploitation218// ?3 - real -> controlled219// no confirmation required, so addresses can be easily generated220decl_storage! {221 trait Store for Module<T: Config> as Nft {222223 //#region Private members224 /// Id of next collection225 CreatedCollectionCount: u32;226 /// Used for migrations227 ChainVersion: u64;228 /// Id of last collection token229 /// Collection id (controlled?1)230 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;231 //#endregion232233 //#region Chain limits struct234 pub ChainLimit get(fn chain_limit) config(): ChainLimits;235 //#endregion236237 //#region Bound counters238 /// Amount of collections destroyed, used for total amount tracking with239 /// CreatedCollectionCount240 DestroyedCollectionCount: u32;241 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)242 /// Account id (real)243 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;244 //#endregion245246 //#region Basic collections247 /// Collection info248 /// Collection id (controlled?1)249 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;250 /// List of collection admins251 /// Collection id (controlled?2)252 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;253 /// Whitelisted collection users254 /// Collection id (controlled?2), user id (controlled?3)255 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;256 //#endregion257258 /// How many of collection items user have259 /// Collection id (controlled?2), account id (real)260 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;261262 /// Amount of items which spender can transfer out of owners account (via transferFrom)263 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))264 /// TODO: Off chain worker should remove from this map when token gets removed265 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;266267 //#region Item collections268 /// Collection id (controlled?2), token id (controlled?1)269 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::AccountId>>;270 /// Collection id (controlled?2), owner (controlled?2)271 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;272 /// Collection id (controlled?2), token id (controlled?1)273 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::AccountId>>;274 //#endregion275276 //#region Index list277 /// Collection id (controlled?2), tokens owner (controlled?2)278 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;279 //#endregion280281 //#region Tokens transfer rate limit baskets282 /// (Collection id (controlled?2), who created (real))283 /// TODO: Off chain worker should remove from this map when collection gets removed284 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;285 /// Collection id (controlled?2), token id (controlled?2)286 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;287 /// Collection id (controlled?2), owning user (real)288 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;289 /// Collection id (controlled?2), token id (controlled?2)290 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;291 //#endregion292293 /// Variable metadata sponsoring294 /// Collection id (controlled?2), token id (controlled?2)295 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;296 297 //#region Contract Sponsorship and Ownership298 /// Contract address (real)299 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;300 /// Contract address (real)301 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;302 /// (Contract address(real), caller (real))303 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;304 /// Contract address (real)305 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;306 /// Contract address (real)307 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 308 /// Contract address (real) => Whitelisted user (controlled?3)309 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 310 //#endregion311 }312 add_extra_genesis {313 build(|config: &GenesisConfig<T>| {314 // Modification of storage315 for (_num, _c) in &config.collection_id {316 <Module<T>>::init_collection(_c);317 }318319 for (_num, _c, _i) in &config.nft_item_id {320 <Module<T>>::init_nft_token(*_c, _i);321 }322323 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {324 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);325 }326327 for (_num, _c, _i) in &config.refungible_item_id {328 <Module<T>>::init_refungible_token(*_c, _i);329 }330 })331 }332}333334decl_event!(335 pub enum Event<T>336 where337 AccountId = <T as system::Config>::AccountId,338 {339 /// New collection was created340 /// 341 /// # Arguments342 /// 343 /// * collection_id: Globally unique identifier of newly created collection.344 /// 345 /// * mode: [CollectionMode] converted into u8.346 /// 347 /// * account_id: Collection owner.348 CollectionCreated(CollectionId, u8, AccountId),349350 /// New item was created.351 /// 352 /// # Arguments353 /// 354 /// * collection_id: Id of the collection where item was created.355 /// 356 /// * item_id: Id of an item. Unique within the collection.357 ///358 /// * recipient: Owner of newly created item 359 ItemCreated(CollectionId, TokenId, AccountId),360361 /// Collection item was burned.362 /// 363 /// # Arguments364 /// 365 /// collection_id.366 /// 367 /// item_id: Identifier of burned NFT.368 ItemDestroyed(CollectionId, TokenId),369370 /// Item was transferred371 ///372 /// * collection_id: Id of collection to which item is belong373 ///374 /// * item_id: Id of an item375 ///376 /// * sender: Original owner of item377 ///378 /// * recipient: New owner of item379 ///380 /// * amount: Always 1 for NFT381 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),382383 /// * collection_id384 ///385 /// * item_id386 ///387 /// * sender388 ///389 /// * spender390 ///391 /// * amount392 Approved(CollectionId, TokenId, AccountId, AccountId, u128),393 }394);395396decl_module! {397 pub struct Module<T: Config> for enum Call 398 where 399 origin: T::Origin400 {401 fn deposit_event() = default;402 type Error = Error<T>;403404 fn on_initialize(_now: T::BlockNumber) -> Weight {405 0406 }407408 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.409 /// 410 /// # Permissions411 /// 412 /// * Anyone.413 /// 414 /// # Arguments415 /// 416 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.417 /// 418 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.419 /// 420 /// * token_prefix: UTF-8 string with token prefix.421 /// 422 /// * mode: [CollectionMode] collection type and type dependent data.423 // returns collection ID424 #[weight = <T as Config>::WeightInfo::create_collection()]425 #[transactional]426 pub fn create_collection(origin,427 collection_name: Vec<u16>,428 collection_description: Vec<u16>,429 token_prefix: Vec<u8>,430 mode: CollectionMode) -> DispatchResult {431432 // Anyone can create a collection433 let who = ensure_signed(origin)?;434435 // Take a (non-refundable) deposit of collection creation436 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();437 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(438 &T::TreasuryAccountId::get(),439 T::CollectionCreationPrice::get(),440 ));441 <T as Config>::Currency::settle(442 &who,443 imbalance,444 WithdrawReasons::TRANSFER,445 ExistenceRequirement::KeepAlive,446 ).map_err(|_| Error::<T>::NoPermission)?;447448 let decimal_points = match mode {449 CollectionMode::Fungible(points) => points,450 _ => 0451 };452453 let chain_limit = ChainLimit::get();454455 let created_count = CreatedCollectionCount::get();456 let destroyed_count = DestroyedCollectionCount::get();457458 // bound Total number of collections459 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);460461 // check params462 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);463 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);464 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);465 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);466467 // Generate next collection ID468 let next_id = created_count469 .checked_add(1)470 .ok_or(Error::<T>::NumOverflow)?;471472 CreatedCollectionCount::put(next_id);473474 let limits = CollectionLimits {475 sponsored_data_size: chain_limit.custom_data_limit,476 ..Default::default()477 };478479 // Create new collection480 let new_collection = Collection {481 owner: who.clone(),482 name: collection_name,483 mode: mode.clone(),484 mint_mode: false,485 access: AccessMode::Normal,486 description: collection_description,487 decimal_points: decimal_points,488 token_prefix: token_prefix,489 offchain_schema: Vec::new(),490 schema_version: SchemaVersion::ImageURL,491 sponsorship: SponsorshipState::Disabled,492 variable_on_chain_schema: Vec::new(),493 const_on_chain_schema: Vec::new(),494 limits,495 };496497 // Add new collection to map498 <CollectionById<T>>::insert(next_id, new_collection);499500 // call event501 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who.clone()));502503 Ok(())504 }505506 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.507 /// 508 /// # Permissions509 /// 510 /// * Collection Owner.511 /// 512 /// # Arguments513 /// 514 /// * collection_id: collection to destroy.515 #[weight = <T as Config>::WeightInfo::destroy_collection()]516 #[transactional]517 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {518519 let sender = ensure_signed(origin)?;520 let collection = Self::get_collection(collection_id)?;521 Self::check_owner_permissions(&collection, sender)?;522 if !collection.limits.owner_can_destroy {523 fail!(Error::<T>::NoPermission);524 }525526 <AddressTokens<T>>::remove_prefix(collection_id);527 <Allowances<T>>::remove_prefix(collection_id);528 <Balance<T>>::remove_prefix(collection_id);529 <ItemListIndex>::remove(collection_id);530 <AdminList<T>>::remove(collection_id);531 <CollectionById<T>>::remove(collection_id);532 <WhiteList<T>>::remove_prefix(collection_id);533534 <NftItemList<T>>::remove_prefix(collection_id);535 <FungibleItemList<T>>::remove_prefix(collection_id);536 <ReFungibleItemList<T>>::remove_prefix(collection_id);537538 <NftTransferBasket<T>>::remove_prefix(collection_id);539 <FungibleTransferBasket<T>>::remove_prefix(collection_id);540 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);541542 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);543544 DestroyedCollectionCount::put(DestroyedCollectionCount::get()545 .checked_add(1)546 .ok_or(Error::<T>::NumOverflow)?);547548 Ok(())549 }550551 /// Add an address to white list.552 /// 553 /// # Permissions554 /// 555 /// * Collection Owner556 /// * Collection Admin557 /// 558 /// # Arguments559 /// 560 /// * collection_id.561 /// 562 /// * address.563 #[weight = <T as Config>::WeightInfo::add_to_white_list()]564 #[transactional]565 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{566567 let sender = ensure_signed(origin)?;568 let collection = Self::get_collection(collection_id)?;569 Self::check_owner_or_admin_permissions(&collection, sender)?;570571 <WhiteList<T>>::insert(collection_id, address, true);572 573 Ok(())574 }575576 /// Remove an address from white list.577 /// 578 /// # Permissions579 /// 580 /// * Collection Owner581 /// * Collection Admin582 /// 583 /// # Arguments584 /// 585 /// * collection_id.586 /// 587 /// * address.588 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]589 #[transactional]590 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{591592 let sender = ensure_signed(origin)?;593 let collection = Self::get_collection(collection_id)?;594 Self::check_owner_or_admin_permissions(&collection, sender)?;595596 <WhiteList<T>>::remove(collection_id, address);597598 Ok(())599 }600601 /// Toggle between normal and white list access for the methods with access for `Anyone`.602 /// 603 /// # Permissions604 /// 605 /// * Collection Owner.606 /// 607 /// # Arguments608 /// 609 /// * collection_id.610 /// 611 /// * mode: [AccessMode]612 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]613 #[transactional]614 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult615 {616 let sender = ensure_signed(origin)?;617618 let mut target_collection = Self::get_collection(collection_id)?;619 Self::check_owner_permissions(&target_collection, sender)?;620 target_collection.access = mode;621 Self::save_collection(target_collection);622623 Ok(())624 }625626 /// Allows Anyone to create tokens if:627 /// * White List is enabled, and628 /// * Address is added to white list, and629 /// * This method was called with True parameter630 /// 631 /// # Permissions632 /// * Collection Owner633 ///634 /// # Arguments635 /// 636 /// * collection_id.637 /// 638 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.639 #[weight = <T as Config>::WeightInfo::set_mint_permission()]640 #[transactional]641 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult642 {643 let sender = ensure_signed(origin)?;644645 let mut target_collection = Self::get_collection(collection_id)?;646 Self::check_owner_permissions(&target_collection, sender)?;647 target_collection.mint_mode = mint_permission;648 Self::save_collection(target_collection);649650 Ok(())651 }652653 /// Change the owner of the collection.654 /// 655 /// # Permissions656 /// 657 /// * Collection Owner.658 /// 659 /// # Arguments660 /// 661 /// * collection_id.662 /// 663 /// * new_owner.664 #[weight = <T as Config>::WeightInfo::change_collection_owner()]665 #[transactional]666 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {667668 let sender = ensure_signed(origin)?;669 let mut target_collection = Self::get_collection(collection_id)?;670 Self::check_owner_permissions(&target_collection, sender)?;671 target_collection.owner = new_owner;672 Self::save_collection(target_collection);673674 Ok(())675 }676677 /// Adds an admin of the Collection.678 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 679 /// 680 /// # Permissions681 /// 682 /// * Collection Owner.683 /// * Collection Admin.684 /// 685 /// # Arguments686 /// 687 /// * collection_id: ID of the Collection to add admin for.688 /// 689 /// * new_admin_id: Address of new admin to add.690 #[weight = <T as Config>::WeightInfo::add_collection_admin()]691 #[transactional]692 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {693694 let sender = ensure_signed(origin)?;695 let collection = Self::get_collection(collection_id)?;696 Self::check_owner_or_admin_permissions(&collection, sender)?;697 let mut admin_arr = <AdminList<T>>::get(collection_id);698699 match admin_arr.binary_search(&new_admin_id) {700 Ok(_) => {},701 Err(idx) => {702 let limits = ChainLimit::get();703 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);704 admin_arr.insert(idx, new_admin_id);705 <AdminList<T>>::insert(collection_id, admin_arr);706 }707 }708 Ok(())709 }710711 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.712 ///713 /// # Permissions714 /// 715 /// * Collection Owner.716 /// * Collection Admin.717 /// 718 /// # Arguments719 /// 720 /// * collection_id: ID of the Collection to remove admin for.721 /// 722 /// * account_id: Address of admin to remove.723 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]724 #[transactional]725 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {726727 let sender = ensure_signed(origin)?;728 let collection = Self::get_collection(collection_id)?;729 Self::check_owner_or_admin_permissions(&collection, sender)?;730 let mut admin_arr = <AdminList<T>>::get(collection_id);731732 match admin_arr.binary_search(&account_id) {733 Ok(idx) => {734 admin_arr.remove(idx);735 <AdminList<T>>::insert(collection_id, admin_arr);736 },737 Err(_) => {}738 }739 Ok(())740 }741742 /// # Permissions743 /// 744 /// * Collection Owner745 /// 746 /// # Arguments747 /// 748 /// * collection_id.749 /// 750 /// * new_sponsor.751 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]752 #[transactional]753 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {754755 let sender = ensure_signed(origin)?;756 let mut target_collection = Self::get_collection(collection_id)?;757 Self::check_owner_permissions(&target_collection, sender)?;758759 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);760 Self::save_collection(target_collection);761762 Ok(())763 }764765 /// # Permissions766 /// 767 /// * Sponsor.768 /// 769 /// # Arguments770 /// 771 /// * collection_id.772 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]773 #[transactional]774 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {775776 let sender = ensure_signed(origin)?;777778 let mut target_collection = Self::get_collection(collection_id)?;779 ensure!(780 target_collection.sponsorship.pending_sponsor() == Some(&sender),781 Error::<T>::ConfirmUnsetSponsorFail782 );783784 target_collection.sponsorship = SponsorshipState::Confirmed(sender);785 Self::save_collection(target_collection);786787 Ok(())788 }789790 /// Switch back to pay-per-own-transaction model.791 ///792 /// # Permissions793 ///794 /// * Collection owner.795 /// 796 /// # Arguments797 /// 798 /// * collection_id.799 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]800 #[transactional]801 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {802803 let sender = ensure_signed(origin)?;804805 let mut target_collection = Self::get_collection(collection_id)?;806 Self::check_owner_permissions(&target_collection, sender)?;807808 target_collection.sponsorship = SponsorshipState::Disabled;809 Self::save_collection(target_collection);810811 Ok(())812 }813814 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.815 /// 816 /// # Permissions817 /// 818 /// * Collection Owner.819 /// * Collection Admin.820 /// * Anyone if821 /// * White List is enabled, and822 /// * Address is added to white list, and823 /// * MintPermission is enabled (see SetMintPermission method)824 /// 825 /// # Arguments826 /// 827 /// * collection_id: ID of the collection.828 /// 829 /// * owner: Address, initial owner of the NFT.830 ///831 /// * data: Token data to store on chain.832 // #[weight =833 // (130_000_000 as Weight)834 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))835 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))836 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]837838 #[weight = <T as Config>::WeightInfo::create_item(data.len())]839 #[transactional]840 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {841 let sender = ensure_signed(origin)?;842 Self::create_item_internal(sender, collection_id, owner, data)843 }844845 /// This method creates multiple items in a collection created with CreateCollection method.846 /// 847 /// # Permissions848 /// 849 /// * Collection Owner.850 /// * Collection Admin.851 /// * Anyone if852 /// * White List is enabled, and853 /// * Address is added to white list, and854 /// * MintPermission is enabled (see SetMintPermission method)855 /// 856 /// # Arguments857 /// 858 /// * collection_id: ID of the collection.859 /// 860 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].861 /// 862 /// * owner: Address, initial owner of the NFT.863 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()864 .map(|data| { data.len() })865 .sum())]866 #[transactional]867 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {868869 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);870 let sender = ensure_signed(origin)?;871872 let target_collection = Self::get_collection(collection_id)?;873874 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;875876 for data in &items_data {877 Self::validate_create_item_args(&target_collection, data)?;878 }879 for data in &items_data {880 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;881 }882883 Ok(())884 }885886 /// Destroys a concrete instance of NFT.887 /// 888 /// # Permissions889 /// 890 /// * Collection Owner.891 /// * Collection Admin.892 /// * Current NFT Owner.893 /// 894 /// # Arguments895 /// 896 /// * collection_id: ID of the collection.897 /// 898 /// * item_id: ID of NFT to burn.899 #[weight = <T as Config>::WeightInfo::burn_item()]900 #[transactional]901 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {902903 let sender = ensure_signed(origin)?;904905 // Transfer permissions check906 let target_collection = Self::get_collection(collection_id)?;907 ensure!(908 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||909 (910 target_collection.limits.owner_can_transfer &&911 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())912 ),913 Error::<T>::NoPermission914 );915916 if target_collection.access == AccessMode::WhiteList {917 Self::check_white_list(&target_collection, &sender)?;918 }919920 match target_collection.mode921 {922 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,923 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,924 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,925 _ => ()926 };927928 // call event929 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));930931 Ok(())932 }933934 /// Change ownership of the token.935 /// 936 /// # Permissions937 /// 938 /// * Collection Owner939 /// * Collection Admin940 /// * Current NFT owner941 ///942 /// # Arguments943 /// 944 /// * recipient: Address of token recipient.945 /// 946 /// * collection_id.947 /// 948 /// * item_id: ID of the item949 /// * Non-Fungible Mode: Required.950 /// * Fungible Mode: Ignored.951 /// * Re-Fungible Mode: Required.952 /// 953 /// * value: Amount to transfer.954 /// * Non-Fungible Mode: Ignored955 /// * Fungible Mode: Must specify transferred amount956 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)957 #[weight = <T as Config>::WeightInfo::transfer()]958 #[transactional]959 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {960 let sender = ensure_signed(origin)?;961 let collection = Self::get_collection(collection_id)?;962963 Self::transfer_internal(sender, recipient, &collection, item_id, value)964 }965966 /// Set, change, or remove approved address to transfer the ownership of the NFT.967 /// 968 /// # Permissions969 /// 970 /// * Collection Owner971 /// * Collection Admin972 /// * Current NFT owner973 /// 974 /// # Arguments975 /// 976 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).977 /// 978 /// * collection_id.979 /// 980 /// * item_id: ID of the item.981 #[weight = <T as Config>::WeightInfo::approve()]982 #[transactional]983 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {984985 let sender = ensure_signed(origin)?;986 let target_collection = Self::get_collection(collection_id)?;987988 Self::token_exists(&target_collection, item_id)?;989990 // Transfer permissions check991 let bypasses_limits = target_collection.limits.owner_can_transfer &&992 Self::is_owner_or_admin_permissions(993 &target_collection,994 sender.clone(),995 );996997 let allowance_limit = if bypasses_limits {998 None999 } else if let Some(amount) = Self::owned_amount(1000 sender.clone(),1001 &target_collection,1002 item_id,1003 ) {1004 Some(amount)1005 } else {1006 fail!(Error::<T>::NoPermission);1007 };10081009 if target_collection.access == AccessMode::WhiteList {1010 Self::check_white_list(&target_collection, &sender)?;1011 Self::check_white_list(&target_collection, &spender)?;1012 }10131014 let allowance: u128 = amount1015 .checked_add(<Allowances<T>>::get(collection_id, (item_id, &sender, &spender)))1016 .ok_or(Error::<T>::NumOverflow)?;1017 if let Some(limit) = allowance_limit {1018 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1019 }1020 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);10211022 Self::deposit_event(RawEvent::Approved(target_collection.id, item_id, sender, spender, allowance));1023 Ok(())1024 }1025 1026 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1027 /// 1028 /// # Permissions1029 /// * Collection Owner1030 /// * Collection Admin1031 /// * Current NFT owner1032 /// * Address approved by current NFT owner1033 /// 1034 /// # Arguments1035 /// 1036 /// * from: Address that owns token.1037 /// 1038 /// * recipient: Address of token recipient.1039 /// 1040 /// * collection_id.1041 /// 1042 /// * item_id: ID of the item.1043 /// 1044 /// * value: Amount to transfer.1045 #[weight = <T as Config>::WeightInfo::transfer_from()]1046 #[transactional]1047 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {10481049 let sender = ensure_signed(origin)?;1050 let target_collection = Self::get_collection(collection_id)?;10511052 // Check approval1053 let approval: u128 = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));10541055 // Limits check1056 Self::is_correct_transfer(&target_collection, &recipient)?;10571058 // Transfer permissions check 1059 ensure!(1060 approval >= value || 1061 (1062 target_collection.limits.owner_can_transfer &&1063 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())1064 ),1065 Error::<T>::NoPermission1066 );10671068 if target_collection.access == AccessMode::WhiteList {1069 Self::check_white_list(&target_collection, &sender)?;1070 Self::check_white_list(&target_collection, &recipient)?;1071 }10721073 // Reduce approval by transferred amount or remove if remaining approval drops to 01074 if approval.saturating_sub(value) > 0 {1075 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1076 }1077 else {1078 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1079 }10801081 match target_collection.mode1082 {1083 CollectionMode::NFT => Self::transfer_nft(&target_collection, item_id, from.clone(), recipient.clone())?,1084 CollectionMode::Fungible(_) => Self::transfer_fungible(&target_collection, value, &from, &recipient)?,1085 CollectionMode::ReFungible => Self::transfer_refungible(&target_collection, item_id, value, from.clone(), recipient.clone())?,1086 _ => ()1087 };10881089 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, from, recipient, value));1090 Ok(())1091 }10921093 // #[weight = 0]1094 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {10951096 // // let no_perm_mes = "You do not have permissions to modify this collection";1097 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1098 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1099 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11001101 // // // on_nft_received call11021103 // // Self::transfer(origin, collection_id, item_id, new_owner)?;11041105 // Ok(())1106 // }11071108 /// Set off-chain data schema.1109 /// 1110 /// # Permissions1111 /// 1112 /// * Collection Owner1113 /// * Collection Admin1114 /// 1115 /// # Arguments1116 /// 1117 /// * collection_id.1118 /// 1119 /// * schema: String representing the offchain data schema.1120 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1121 #[transactional]1122 pub fn set_variable_meta_data (1123 origin,1124 collection_id: CollectionId,1125 item_id: TokenId,1126 data: Vec<u8>1127 ) -> DispatchResult {1128 let sender = ensure_signed(origin)?;1129 1130 let target_collection = Self::get_collection(collection_id)?;1131 Self::token_exists(&target_collection, item_id)?;11321133 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);11341135 // Modify permissions check1136 ensure!(Self::is_item_owner(sender.clone(), &target_collection, item_id) ||1137 Self::is_owner_or_admin_permissions(&target_collection, sender.clone()),1138 Error::<T>::NoPermission);11391140 match target_collection.mode1141 {1142 CollectionMode::NFT => Self::set_nft_variable_data(&target_collection, item_id, data)?,1143 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&target_collection, item_id, data)?,1144 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1145 _ => fail!(Error::<T>::UnexpectedCollectionType)1146 };11471148 Ok(())1149 }1150 1151 /// Set schema standard1152 /// ImageURL1153 /// Unique1154 /// 1155 /// # Permissions1156 /// 1157 /// * Collection Owner1158 /// * Collection Admin1159 /// 1160 /// # Arguments1161 /// 1162 /// * collection_id.1163 /// 1164 /// * schema: SchemaVersion: enum1165 #[weight = <T as Config>::WeightInfo::set_schema_version()]1166 #[transactional]1167 pub fn set_schema_version(1168 origin,1169 collection_id: CollectionId,1170 version: SchemaVersion1171 ) -> DispatchResult {1172 let sender = ensure_signed(origin)?;1173 let mut target_collection = Self::get_collection(collection_id)?;1174 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1175 target_collection.schema_version = version;1176 Self::save_collection(target_collection);11771178 Ok(())1179 }11801181 /// Set off-chain data schema.1182 /// 1183 /// # Permissions1184 /// 1185 /// * Collection Owner1186 /// * Collection Admin1187 /// 1188 /// # Arguments1189 /// 1190 /// * collection_id.1191 /// 1192 /// * schema: String representing the offchain data schema.1193 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1194 #[transactional]1195 pub fn set_offchain_schema(1196 origin,1197 collection_id: CollectionId,1198 schema: Vec<u8>1199 ) -> DispatchResult {1200 let sender = ensure_signed(origin)?;1201 let mut target_collection = Self::get_collection(collection_id)?;1202 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12031204 // check schema limit1205 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");12061207 target_collection.offchain_schema = schema;1208 Self::save_collection(target_collection);12091210 Ok(())1211 }12121213 /// Set const on-chain data schema.1214 /// 1215 /// # Permissions1216 /// 1217 /// * Collection Owner1218 /// * Collection Admin1219 /// 1220 /// # Arguments1221 /// 1222 /// * collection_id.1223 /// 1224 /// * schema: String representing the const on-chain data schema.1225 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1226 #[transactional]1227 pub fn set_const_on_chain_schema (1228 origin,1229 collection_id: CollectionId,1230 schema: Vec<u8>1231 ) -> DispatchResult {1232 let sender = ensure_signed(origin)?;1233 let mut target_collection = Self::get_collection(collection_id)?;1234 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12351236 // check schema limit1237 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");12381239 target_collection.const_on_chain_schema = schema;1240 Self::save_collection(target_collection);12411242 Ok(())1243 }12441245 /// Set variable on-chain data schema.1246 /// 1247 /// # Permissions1248 /// 1249 /// * Collection Owner1250 /// * Collection Admin1251 /// 1252 /// # Arguments1253 /// 1254 /// * collection_id.1255 /// 1256 /// * schema: String representing the variable on-chain data schema.1257 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1258 #[transactional]1259 pub fn set_variable_on_chain_schema (1260 origin,1261 collection_id: CollectionId,1262 schema: Vec<u8>1263 ) -> DispatchResult {1264 let sender = ensure_signed(origin)?;1265 let mut target_collection = Self::get_collection(collection_id)?;1266 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12671268 // check schema limit1269 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12701271 target_collection.variable_on_chain_schema = schema;1272 Self::save_collection(target_collection);12731274 Ok(())1275 }12761277 // Sudo permissions function1278 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1279 #[transactional]1280 pub fn set_chain_limits(1281 origin,1282 limits: ChainLimits1283 ) -> DispatchResult {12841285 #[cfg(not(feature = "runtime-benchmarks"))]1286 ensure_root(origin)?;12871288 <ChainLimit>::put(limits);1289 Ok(())1290 }12911292 /// Enable smart contract self-sponsoring.1293 /// 1294 /// # Permissions1295 /// 1296 /// * Contract Owner1297 /// 1298 /// # Arguments1299 /// 1300 /// * contract address1301 /// * enable flag1302 /// 1303 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1304 #[transactional]1305 pub fn enable_contract_sponsoring(1306 origin,1307 contract_address: T::AccountId,1308 enable: bool1309 ) -> DispatchResult {13101311 let sender = ensure_signed(origin)?;13121313 #[cfg(feature = "runtime-benchmarks")]1314 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13151316 Self::ensure_contract_owned(sender, &contract_address)?;13171318 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1319 Ok(())1320 }13211322 /// Set the rate limit for contract sponsoring to specified number of blocks.1323 /// 1324 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1325 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1326 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1327 /// from contract endowment if there are at least B blocks between such transactions. 1328 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1329 /// 1330 /// # Permissions1331 /// 1332 /// * Contract Owner1333 /// 1334 /// # Arguments1335 /// 1336 /// -`contract_address`: Address of the contract to sponsor1337 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1338 /// 1339 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1340 #[transactional]1341 pub fn set_contract_sponsoring_rate_limit(1342 origin,1343 contract_address: T::AccountId,1344 rate_limit: T::BlockNumber1345 ) -> DispatchResult {1346 let sender = ensure_signed(origin)?;13471348 #[cfg(feature = "runtime-benchmarks")]1349 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13501351 Self::ensure_contract_owned(sender, &contract_address)?;1352 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1353 Ok(())1354 }13551356 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1357 /// 1358 /// # Permissions1359 /// 1360 /// * Address that deployed smart contract.1361 /// 1362 /// # Arguments1363 /// 1364 /// -`contract_address`: Address of the contract.1365 /// 1366 /// - `enable`: . 1367 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1368 #[transactional]1369 pub fn toggle_contract_white_list(1370 origin,1371 contract_address: T::AccountId,1372 enable: bool1373 ) -> DispatchResult {1374 let sender = ensure_signed(origin)?;13751376 #[cfg(feature = "runtime-benchmarks")]1377 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13781379 Self::ensure_contract_owned(sender, &contract_address)?;1380 if enable {1381 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1382 } else {1383 <ContractWhiteListEnabled<T>>::remove(contract_address);1384 }1385 Ok(())1386 }1387 1388 /// Add an address to smart contract white list.1389 /// 1390 /// # Permissions1391 /// 1392 /// * Address that deployed smart contract.1393 /// 1394 /// # Arguments1395 /// 1396 /// -`contract_address`: Address of the contract.1397 ///1398 /// -`account_address`: Address to add.1399 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1400 #[transactional]1401 pub fn add_to_contract_white_list(1402 origin,1403 contract_address: T::AccountId,1404 account_address: T::AccountId1405 ) -> DispatchResult {1406 let sender = ensure_signed(origin)?;14071408 #[cfg(feature = "runtime-benchmarks")]1409 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1410 1411 Self::ensure_contract_owned(sender, &contract_address)?; 1412 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1413 Ok(())1414 }14151416 /// Remove an address from smart contract white list.1417 /// 1418 /// # Permissions1419 /// 1420 /// * Address that deployed smart contract.1421 /// 1422 /// # Arguments1423 /// 1424 /// -`contract_address`: Address of the contract.1425 ///1426 /// -`account_address`: Address to remove.1427 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1428 #[transactional]1429 pub fn remove_from_contract_white_list(1430 origin,1431 contract_address: T::AccountId,1432 account_address: T::AccountId1433 ) -> DispatchResult {1434 let sender = ensure_signed(origin)?;14351436 #[cfg(feature = "runtime-benchmarks")]1437 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14381439 Self::ensure_contract_owned(sender, &contract_address)?;1440 <ContractWhiteList<T>>::remove(contract_address, account_address);1441 Ok(())1442 }14431444 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1445 #[transactional]1446 pub fn set_collection_limits(1447 origin,1448 collection_id: u32,1449 new_limits: CollectionLimits<T::BlockNumber>,1450 ) -> DispatchResult {1451 let sender = ensure_signed(origin)?;1452 let mut target_collection = Self::get_collection(collection_id)?;1453 Self::check_owner_permissions(&target_collection, sender.clone())?;1454 let old_limits = &target_collection.limits;1455 let chain_limits = ChainLimit::get();14561457 // collection bounds1458 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1459 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1460 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1461 Error::<T>::CollectionLimitBoundsExceeded);14621463 // token_limit check prev1464 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1465 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);14661467 ensure!(1468 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1469 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1470 Error::<T>::OwnerPermissionsCantBeReverted,1471 );14721473 target_collection.limits = new_limits;1474 Self::save_collection(target_collection);14751476 Ok(())1477 } 1478 }1479}14801481impl<T: Config> Module<T> {1482 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1483 let target_collection = Self::get_collection(collection_id)?;14841485 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1486 Self::validate_create_item_args(&target_collection, &data)?;1487 Self::create_item_no_validation(&target_collection, owner, data)?;14881489 Ok(())1490 }14911492 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1493 // Limits check1494 Self::is_correct_transfer(target_collection, &recipient)?;14951496 // Transfer permissions check1497 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1498 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1499 Error::<T>::NoPermission);15001501 if target_collection.access == AccessMode::WhiteList {1502 Self::check_white_list(target_collection, &sender)?;1503 Self::check_white_list(target_collection, &recipient)?;1504 }15051506 match target_collection.mode1507 {1508 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1509 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1510 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1511 _ => ()1512 };15131514 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));15151516 Ok(())1517 }151815191520 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1521 let collection_id = collection.id;15221523 // check token limit and account token limit1524 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1525 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1526 1527 Ok(())1528 }15291530 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1531 let collection_id = collection.id;15321533 // check token limit and account token limit1534 let total_items: u32 = ItemListIndex::get(collection_id)1535 .checked_add(amount)1536 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1537 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1538 .checked_add(amount)1539 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1540 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1541 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);15421543 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1544 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1545 Self::check_white_list(collection, owner)?;1546 Self::check_white_list(collection, sender)?;1547 }15481549 Ok(())1550 }15511552 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1553 match target_collection.mode1554 {1555 CollectionMode::NFT => {1556 if let CreateItemData::NFT(data) = data {1557 // check sizes1558 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1559 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1560 } else {1561 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1562 }1563 },1564 CollectionMode::Fungible(_) => {1565 if let CreateItemData::Fungible(_) = data {1566 } else {1567 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1568 }1569 },1570 CollectionMode::ReFungible => {1571 if let CreateItemData::ReFungible(data) = data {15721573 // check sizes1574 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1575 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15761577 // Check refungibility limits1578 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1579 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1580 } else {1581 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1582 }1583 },1584 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1585 };15861587 Ok(())1588 }15891590 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1591 match data1592 {1593 CreateItemData::NFT(data) => {1594 let item = NftItemType {1595 owner: owner.clone(),1596 const_data: data.const_data,1597 variable_data: data.variable_data1598 };15991600 Self::add_nft_item(collection, item)?;1601 },1602 CreateItemData::Fungible(data) => {1603 Self::add_fungible_item(collection, &owner, data.value)?;1604 },1605 CreateItemData::ReFungible(data) => {1606 let mut owner_list = Vec::new();1607 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16081609 let item = ReFungibleItemType {1610 owner: owner_list,1611 const_data: data.const_data,1612 variable_data: data.variable_data1613 };16141615 Self::add_refungible_item(collection, item)?;1616 }1617 };16181619 Ok(())1620 }16211622 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1623 let collection_id = collection.id;16241625 // Does new owner already have an account?1626 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;16271628 // Mint 1629 let item = FungibleItemType {1630 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1631 };1632 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16331634 // Update balance1635 let new_balance = <Balance<T>>::get(collection_id, owner)1636 .checked_add(value)1637 .ok_or(Error::<T>::NumOverflow)?;1638 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16391640 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1641 Ok(())1642 }16431644 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1645 let collection_id = collection.id;16461647 let current_index = <ItemListIndex>::get(collection_id)1648 .checked_add(1)1649 .ok_or(Error::<T>::NumOverflow)?;1650 let itemcopy = item.clone();16511652 ensure!(1653 item.owner.len() == 1,1654 Error::<T>::BadCreateRefungibleCall,1655 );1656 let item_owner = item.owner.first().expect("only one owner is defined");16571658 let value = item_owner.fraction;1659 let owner = item_owner.owner.clone();16601661 Self::add_token_index(collection_id, current_index, &owner)?;16621663 <ItemListIndex>::insert(collection_id, current_index);1664 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16651666 // Update balance1667 let new_balance = <Balance<T>>::get(collection_id, &owner)1668 .checked_add(value)1669 .ok_or(Error::<T>::NumOverflow)?;1670 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16711672 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1673 Ok(())1674 }16751676 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {1677 let collection_id = collection.id;16781679 let current_index = <ItemListIndex>::get(collection_id)1680 .checked_add(1)1681 .ok_or(Error::<T>::NumOverflow)?;16821683 let item_owner = item.owner.clone();1684 Self::add_token_index(collection_id, current_index, &item.owner)?;16851686 <ItemListIndex>::insert(collection_id, current_index);1687 <NftItemList<T>>::insert(collection_id, current_index, item);16881689 // Update balance1690 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1691 .checked_add(1)1692 .ok_or(Error::<T>::NumOverflow)?;1693 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16941695 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1696 Ok(())1697 }16981699 fn burn_refungible_item(1700 collection: &CollectionHandle<T>,1701 item_id: TokenId,1702 owner: &T::AccountId,1703 ) -> DispatchResult {1704 let collection_id = collection.id;17051706 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1707 .ok_or(Error::<T>::TokenNotFound)?;1708 let rft_balance = token1709 .owner1710 .iter()1711 .find(|&i| i.owner == *owner)1712 .ok_or(Error::<T>::TokenNotFound)?;1713 Self::remove_token_index(collection_id, item_id, owner)?;17141715 // update balance1716 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1717 .checked_sub(rft_balance.fraction)1718 .ok_or(Error::<T>::NumOverflow)?;1719 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17201721 // Re-create owners list with sender removed1722 let index = token1723 .owner1724 .iter()1725 .position(|i| i.owner == *owner)1726 .expect("owned item is exists");1727 token.owner.remove(index);1728 let owner_count = token.owner.len();17291730 // Burn the token completely if this was the last (only) owner1731 if owner_count == 0 {1732 <ReFungibleItemList<T>>::remove(collection_id, item_id);1733 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1734 }1735 else {1736 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1737 }17381739 Ok(())1740 }17411742 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1743 let collection_id = collection.id;17441745 let item = <NftItemList<T>>::get(collection_id, item_id)1746 .ok_or(Error::<T>::TokenNotFound)?;1747 Self::remove_token_index(collection_id, item_id, &item.owner)?;17481749 // update balance1750 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1751 .checked_sub(1)1752 .ok_or(Error::<T>::NumOverflow)?;1753 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1754 <NftItemList<T>>::remove(collection_id, item_id);1755 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17561757 Ok(())1758 }17591760 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1761 let collection_id = collection.id;17621763 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1764 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17651766 // update balance1767 let new_balance = <Balance<T>>::get(collection_id, owner)1768 .checked_sub(value)1769 .ok_or(Error::<T>::NumOverflow)?;1770 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17711772 if balance.value - value > 0 {1773 balance.value -= value;1774 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1775 }1776 else {1777 <FungibleItemList<T>>::remove(collection_id, owner);1778 }17791780 Ok(())1781 }17821783 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1784 Ok(<CollectionById<T>>::get(collection_id)1785 .map(|collection| CollectionHandle {1786 id: collection_id,1787 collection1788 })1789 .ok_or(Error::<T>::CollectionNotFound)?)1790 }17911792 fn save_collection(collection: CollectionHandle<T>) {1793 <CollectionById<T>>::insert(collection.id, collection.collection);1794 }17951796 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {1797 ensure!(1798 subject == target_collection.owner,1799 Error::<T>::NoPermission1800 );18011802 Ok(())1803 }18041805 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {1806 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1807 }18081809 fn check_owner_or_admin_permissions(1810 collection: &CollectionHandle<T>,1811 subject: T::AccountId,1812 ) -> DispatchResult {1813 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18141815 Ok(())1816 }18171818 fn owned_amount(1819 subject: T::AccountId,1820 target_collection: &CollectionHandle<T>,1821 item_id: TokenId,1822 ) -> Option<u128> {1823 let collection_id = target_collection.id;18241825 match target_collection.mode {1826 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)1827 .then(|| 1),1828 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)1829 .value),1830 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1831 .owner1832 .iter()1833 .find(|i| i.owner == subject)1834 .map(|i| i.fraction),1835 CollectionMode::Invalid => None,1836 }1837 }18381839 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1840 match target_collection.mode {1841 CollectionMode::Fungible(_) => true,1842 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1843 }1844 }18451846 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {1847 let collection_id = collection.id;18481849 let mes = Error::<T>::AddresNotInWhiteList;1850 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18511852 Ok(())1853 }18541855 /// Check if token exists. In case of Fungible, check if there is an entry for 1856 /// the owner in fungible balances double map1857 fn token_exists(1858 target_collection: &CollectionHandle<T>,1859 item_id: TokenId,1860 ) -> DispatchResult {1861 let collection_id = target_collection.id;1862 let exists = match target_collection.mode1863 {1864 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1865 CollectionMode::Fungible(_) => true,1866 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1867 _ => false1868 };18691870 ensure!(exists == true, Error::<T>::TokenNotFound);1871 Ok(())1872 }18731874 fn transfer_fungible(1875 collection: &CollectionHandle<T>,1876 value: u128,1877 owner: &T::AccountId,1878 recipient: &T::AccountId,1879 ) -> DispatchResult {1880 let collection_id = collection.id;18811882 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1883 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18841885 // Send balance to recipient (updates balanceOf of recipient)1886 Self::add_fungible_item(collection, recipient, value)?;18871888 // update balanceOf of sender1889 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18901891 // Reduce or remove sender1892 if balance.value == value {1893 <FungibleItemList<T>>::remove(collection_id, owner);1894 }1895 else {1896 balance.value -= value;1897 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1898 }18991900 Ok(())1901 }19021903 fn transfer_refungible(1904 collection: &CollectionHandle<T>,1905 item_id: TokenId,1906 value: u128,1907 owner: T::AccountId,1908 new_owner: T::AccountId,1909 ) -> DispatchResult {1910 let collection_id = collection.id;1911 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1912 .ok_or(Error::<T>::TokenNotFound)?;19131914 let item = full_item1915 .owner1916 .iter()1917 .filter(|i| i.owner == owner)1918 .next()1919 .ok_or(Error::<T>::TokenNotFound)?;1920 let amount = item.fraction;19211922 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19231924 // update balance1925 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1926 .checked_sub(value)1927 .ok_or(Error::<T>::NumOverflow)?;1928 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19291930 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1931 .checked_add(value)1932 .ok_or(Error::<T>::NumOverflow)?;1933 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19341935 let old_owner = item.owner.clone();1936 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19371938 // transfer1939 if amount == value && !new_owner_has_account {1940 // change owner1941 // new owner do not have account1942 let mut new_full_item = full_item.clone();1943 new_full_item1944 .owner1945 .iter_mut()1946 .find(|i| i.owner == owner)1947 .expect("old owner does present in refungible")1948 .owner = new_owner.clone();1949 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19501951 // update index collection1952 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1953 } else {1954 let mut new_full_item = full_item.clone();1955 new_full_item1956 .owner1957 .iter_mut()1958 .find(|i| i.owner == owner)1959 .expect("old owner does present in refungible")1960 .fraction -= value;19611962 // separate amount1963 if new_owner_has_account {1964 // new owner has account1965 new_full_item1966 .owner1967 .iter_mut()1968 .find(|i| i.owner == new_owner)1969 .expect("new owner has account")1970 .fraction += value;1971 } else {1972 // new owner do not have account1973 new_full_item.owner.push(Ownership {1974 owner: new_owner.clone(),1975 fraction: value,1976 });1977 Self::add_token_index(collection_id, item_id, &new_owner)?;1978 }19791980 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1981 }19821983 Ok(())1984 }19851986 fn transfer_nft(1987 collection: &CollectionHandle<T>,1988 item_id: TokenId,1989 sender: T::AccountId,1990 new_owner: T::AccountId,1991 ) -> DispatchResult {1992 let collection_id = collection.id;1993 let mut item = <NftItemList<T>>::get(collection_id, item_id)1994 .ok_or(Error::<T>::TokenNotFound)?;19951996 ensure!(1997 sender == item.owner,1998 Error::<T>::MustBeTokenOwner1999 );20002001 // update balance2002 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2003 .checked_sub(1)2004 .ok_or(Error::<T>::NumOverflow)?;2005 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20062007 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2008 .checked_add(1)2009 .ok_or(Error::<T>::NumOverflow)?;2010 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20112012 // change owner2013 let old_owner = item.owner.clone();2014 item.owner = new_owner.clone();2015 <NftItemList<T>>::insert(collection_id, item_id, item);20162017 // update index collection2018 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20192020 Ok(())2021 }2022 2023 fn set_re_fungible_variable_data(2024 collection: &CollectionHandle<T>,2025 item_id: TokenId,2026 data: Vec<u8>2027 ) -> DispatchResult {2028 let collection_id = collection.id;2029 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2030 .ok_or(Error::<T>::TokenNotFound)?;20312032 item.variable_data = data;20332034 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20352036 Ok(())2037 }20382039 fn set_nft_variable_data(2040 collection: &CollectionHandle<T>,2041 item_id: TokenId,2042 data: Vec<u8>2043 ) -> DispatchResult {2044 let collection_id = collection.id;2045 let mut item = <NftItemList<T>>::get(collection_id, item_id)2046 .ok_or(Error::<T>::TokenNotFound)?;2047 2048 item.variable_data = data;20492050 <NftItemList<T>>::insert(collection_id, item_id, item);2051 2052 Ok(())2053 }20542055 #[allow(dead_code)]2056 fn init_collection(item: &Collection<T>) {2057 // check params2058 assert!(2059 item.decimal_points <= MAX_DECIMAL_POINTS,2060 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2061 );2062 assert!(2063 item.name.len() <= 64,2064 "Collection name can not be longer than 63 char"2065 );2066 assert!(2067 item.name.len() <= 256,2068 "Collection description can not be longer than 255 char"2069 );2070 assert!(2071 item.token_prefix.len() <= 16,2072 "Token prefix can not be longer than 15 char"2073 );20742075 // Generate next collection ID2076 let next_id = CreatedCollectionCount::get()2077 .checked_add(1)2078 .unwrap();20792080 CreatedCollectionCount::put(next_id);2081 }20822083 #[allow(dead_code)]2084 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2085 let current_index = <ItemListIndex>::get(collection_id)2086 .checked_add(1)2087 .unwrap();20882089 let item_owner = item.owner.clone();2090 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();20912092 <ItemListIndex>::insert(collection_id, current_index);20932094 // Update balance2095 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2096 .checked_add(1)2097 .unwrap();2098 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2099 }21002101 #[allow(dead_code)]2102 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2103 let current_index = <ItemListIndex>::get(collection_id)2104 .checked_add(1)2105 .unwrap();21062107 Self::add_token_index(collection_id, current_index, owner).unwrap();21082109 <ItemListIndex>::insert(collection_id, current_index);21102111 // Update balance2112 let new_balance = <Balance<T>>::get(collection_id, owner)2113 .checked_add(item.value)2114 .unwrap();2115 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2116 }21172118 #[allow(dead_code)]2119 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2120 let current_index = <ItemListIndex>::get(collection_id)2121 .checked_add(1)2122 .unwrap();21232124 let value = item.owner.first().unwrap().fraction;2125 let owner = item.owner.first().unwrap().owner.clone();21262127 Self::add_token_index(collection_id, current_index, &owner).unwrap();21282129 <ItemListIndex>::insert(collection_id, current_index);21302131 // Update balance2132 let new_balance = <Balance<T>>::get(collection_id, &owner)2133 .checked_add(value)2134 .unwrap();2135 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2136 }21372138 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2139 // add to account limit2140 if <AccountItemCount<T>>::contains_key(owner) {21412142 // bound Owned tokens by a single address2143 let count = <AccountItemCount<T>>::get(owner);2144 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21452146 <AccountItemCount<T>>::insert(owner.clone(), count2147 .checked_add(1)2148 .ok_or(Error::<T>::NumOverflow)?);2149 }2150 else {2151 <AccountItemCount<T>>::insert(owner.clone(), 1);2152 }21532154 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2155 if list_exists {2156 let mut list = <AddressTokens<T>>::get(collection_id, owner);2157 let item_contains = list.contains(&item_index.clone());21582159 if !item_contains {2160 list.push(item_index.clone());2161 }21622163 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2164 } else {2165 let mut itm = Vec::new();2166 itm.push(item_index.clone());2167 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2168 }21692170 Ok(())2171 }21722173 fn remove_token_index(2174 collection_id: CollectionId,2175 item_index: TokenId,2176 owner: &T::AccountId,2177 ) -> DispatchResult {21782179 // update counter2180 <AccountItemCount<T>>::insert(owner.clone(), 2181 <AccountItemCount<T>>::get(owner)2182 .checked_sub(1)2183 .ok_or(Error::<T>::NumOverflow)?);218421852186 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2187 if list_exists {2188 let mut list = <AddressTokens<T>>::get(collection_id, owner);2189 let item_contains = list.contains(&item_index.clone());21902191 if item_contains {2192 list.retain(|&item| item != item_index);2193 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2194 }2195 }21962197 Ok(())2198 }21992200 fn move_token_index(2201 collection_id: CollectionId,2202 item_index: TokenId,2203 old_owner: &T::AccountId,2204 new_owner: &T::AccountId,2205 ) -> DispatchResult {2206 Self::remove_token_index(collection_id, item_index, old_owner)?;2207 Self::add_token_index(collection_id, item_index, new_owner)?;22082209 Ok(())2210 }2211 2212 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2213 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22142215 Ok(())2216 }2217}pallets/nft/src/types.rsdiffbeforeafterboth--- a/pallets/nft/src/types.rs
+++ /dev/null
@@ -1,234 +0,0 @@
-#[cfg(feature = "std")]
-pub use std::*;
-
-#[cfg(feature = "std")]
-pub use serde::*;
-
-use codec::{Decode, Encode};
-
-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),
- // decimal points
- ReFungible(DecimalPoints),
-}
-
-impl Into<u8> for CollectionMode {
- fn into(self) -> u8 {
- match self {
- CollectionMode::Invalid => 0,
- CollectionMode::NFT => 1,
- CollectionMode::Fungible(_) => 2,
- CollectionMode::ReFungible(_) => 3,
- }
- }
-}
-
-#[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
- }
-}
-
-impl Default for CollectionMode {
- fn default() -> Self {
- Self::Invalid
- }
-}
-
-#[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<AccountId> {
- pub owner: AccountId,
- pub fraction: u128,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CollectionType<AccountId> {
- pub owner: AccountId,
- pub mode: CollectionMode,
- pub access: AccessMode,
- pub decimal_points: DecimalPoints,
- pub name: Vec<u16>, // 64 include null escape char
- pub description: Vec<u16>, // 256 include null escape char
- pub token_prefix: Vec<u8>, // 16 include null escape char
- pub mint_mode: bool,
- pub offchain_schema: Vec<u8>,
- pub schema_version: SchemaVersion,
- pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender
- pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship
- pub limits: CollectionLimits, // Collection private restrictions
- pub variable_on_chain_schema: Vec<u8>, //
- pub const_on_chain_schema: Vec<u8>, //
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct NftItemType<AccountId> {
- pub collection: CollectionId,
- pub owner: AccountId,
- pub const_data: Vec<u8>,
- pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct FungibleItemType<AccountId> {
- pub collection: CollectionId,
- pub owner: AccountId,
- pub value: u128,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ReFungibleItemType<AccountId> {
- pub collection: CollectionId,
- pub owner: Vec<Ownership<AccountId>>,
- pub const_data: Vec<u8>,
- pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ApprovePermissions<AccountId> {
- pub approved: AccountId,
- pub amount: u128,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct VestingItem<AccountId, Moment> {
- pub sender: AccountId,
- pub recipient: AccountId,
- pub collection_id: CollectionId,
- pub item_id: TokenId,
- pub amount: u64,
- pub vesting_date: Moment,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct BasketItem<AccountId, BlockNumber> {
- pub address: AccountId,
- pub start_block: BlockNumber,
-}
-
-#[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,
- pub token_limit: u32,
-
- // Timeouts for item types in passed blocks
- pub sponsor_transfer_timeout: u32,
-}
-
-impl Default for CollectionLimits {
- fn default() -> CollectionLimits {
- CollectionLimits {
- account_token_ownership_limit: 10_000_000,
- token_limit: u32::max_value(),
- sponsored_data_size: u32::max_value(),
- sponsor_transfer_timeout: 14400 }
- }
-}
-
-#[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,
-}
-
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateNftData {
- pub const_data: Vec<u8>,
- pub variable_data: Vec<u8>,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateFungibleData {
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct CreateReFungibleData {
- pub const_data: Vec<u8>,
- pub variable_data: Vec<u8>,
-}
-
-#[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<CreateNftData> for CreateItemData {
- fn from(item: CreateNftData) -> Self {
- CreateItemData::NFT(item)
- }
-}
-
-impl From<CreateReFungibleData> for CreateItemData {
- fn from(item: CreateReFungibleData) -> Self {
- CreateItemData::ReFungible(item)
- }
-}
-
-impl From<CreateFungibleData> for CreateItemData {
- fn from(item: CreateFungibleData) -> Self {
- CreateItemData::Fungible(item)
- }
-}
pallets/scheduler/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler/Cargo.toml
@@ -0,0 +1,53 @@
+[package]
+name = "pallet-scheduler"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Unlicense"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "FRAME example pallet"
+readme = "README.md"
+
+[dependencies]
+serde = { version = "1.0.119" }
+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 }
+log = { version = "0.4.14", default-features = false }
+
+[dev-dependencies]
+sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+substrate-test-utils = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+ "codec/std",
+ "sp-runtime/std",
+ "frame-benchmarking/std",
+ "frame-support/std",
+ "frame-system/std",
+ "pallet-nft-transaction-payment/std",
+ "pallet-nft/std",
+ "pallet-contracts/std",
+ "nft-data-structs/std",
+ "sp-io/std",
+ "sp-std/std",
+ "log/std",
+]
+runtime-benchmarks = [
+ "frame-benchmarking",
+ "frame-support/runtime-benchmarks",
+ "frame-system/runtime-benchmarks",
+]
+#try-runtime = ["frame-support/try-runtime"]
pallets/scheduler/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler/README.md
@@ -0,0 +1,34 @@
+# Scheduler
+A module for scheduling dispatches.
+
+- [`scheduler::Trait`](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/trait.Trait.html)
+- [`Call`](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/enum.Call.html)
+- [`Module`](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/struct.Module.html)
+
+## Overview
+
+This module exposes capabilities for scheduling dispatches to occur at a
+specified block number or at a specified period. These scheduled dispatches
+may be named or anonymous and may be canceled.
+
+**NOTE:** The scheduled calls will be dispatched with the default filter
+for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
+except root which will get no filter. And not the filter contained in origin
+use to call `fn schedule`.
+
+If a call is scheduled using proxy or whatever mecanism which adds filter,
+then those filter will not be used when dispatching the schedule call.
+
+## Interface
+
+### Dispatchable Functions
+
+* `schedule` - schedule a dispatch, which may be periodic, to occur at a
+ specified block and with a specified priority.
+* `cancel` - cancel a scheduled dispatch, specified by block number and
+ index.
+* `schedule_named` - augments the `schedule` interface with an additional
+ `Vec<u8>` parameter that can be used for identification.
+* `cancel_named` - the named complement to the cancel function.
+
+License: Unlicense
pallets/scheduler/src/benchmarking.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler/src/benchmarking.rs
@@ -0,0 +1,148 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Scheduler pallet benchmarking.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+use sp_std::{vec, prelude::*};
+use frame_system::RawOrigin;
+use frame_support::{ensure, traits::OnInitialize};
+use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
+
+use crate::Module as Scheduler;
+use frame_system::Pallet as System;
+
+const BLOCK_NUMBER: u32 = 2;
+
+// Add `n` named items to the schedule
+fn fill_schedule<T: Config> (when: T::BlockNumber, n: u32) -> Result<(), &'static str> {
+ // Essentially a no-op call.
+ let call = frame_system::Call::set_storage(vec![]);
+ for i in 0..n {
+ // Named schedule is strictly heavier than anonymous
+ Scheduler::<T>::do_schedule_named(
+ i.encode(),
+ DispatchTime::At(when),
+ // Add periodicity
+ Some((T::BlockNumber::one(), 100)),
+ // HARD_DEADLINE priority means it gets executed no matter what
+ 0,
+ frame_system::RawOrigin::Root.into(),
+ call.clone().into(),
+ )?;
+ }
+ ensure!(Agenda::<T>::get(when).len() == n as usize, "didn't fill schedule");
+ Ok(())
+}
+
+benchmarks! {
+ schedule {
+ let s in 0 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(frame_system::Call::set_storage(vec![]).into());
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::<T>::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::<T>(when, s)?;
+ assert_eq!(Agenda::<T>::get(when).len(), s as usize);
+ }: _(RawOrigin::Root, when, 0)
+ verify {
+ ensure!(
+ Lookup::<T>::get(0.encode()).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::<T>::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ schedule_named {
+ let s in 0 .. T::MaxScheduledPerBlock::get();
+ let id = s.encode();
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(frame_system::Call::set_storage(vec![]).into());
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, id, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::<T>::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel_named {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::<T>(when, s)?;
+ }: _(RawOrigin::Root, 0.encode())
+ verify {
+ ensure!(
+ Lookup::<T>::get(0.encode()).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::<T>::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ // TODO [#7141]: Make this more complex and flexible so it can be used in automation.
+ #[extra]
+ on_initialize {
+ let s in 0 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+ fill_schedule::<T>(when, s)?;
+ }: { Scheduler::<T>::on_initialize(BLOCK_NUMBER.into()); }
+ verify {
+ assert_eq!(System::<T>::event_count(), s);
+ // Next block should have all the schedules again
+ ensure!(
+ Agenda::<T>::get(when + T::BlockNumber::one()).len() == s as usize,
+ "didn't append schedule"
+ );
+ }
+}
+
+impl_benchmark_test_suite!(
+ Scheduler,
+ crate::tests::new_test_ext(),
+ crate::tests::Test,
+);
pallets/scheduler/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler/src/lib.rs
@@ -0,0 +1,1610 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! # Scheduler
+//! A module for scheduling dispatches.
+//!
+//! - [`Config`]
+//! - [`Call`]
+//! - [`Module`]
+//!
+//! ## Overview
+//!
+//! This module exposes capabilities for scheduling dispatches to occur at a
+//! specified block number or at a specified period. These scheduled dispatches
+//! may be named or anonymous and may be canceled.
+//!
+//! **NOTE:** The scheduled calls will be dispatched with the default filter
+//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
+//! except root which will get no filter. And not the filter contained in origin
+//! use to call `fn schedule`.
+//!
+//! If a call is scheduled using proxy or whatever mecanism which adds filter,
+//! then those filter will not be used when dispatching the schedule call.
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a
+//! specified block and with a specified priority.
+//! * `cancel` - cancel a scheduled dispatch, specified by block number and
+//! index.
+//! * `schedule_named` - augments the `schedule` interface with an additional
+//! `Vec<u8>` parameter that can be used for identification.
+//! * `cancel_named` - the named complement to the cancel function.
+
+// Ensure we're `no_std` when compiling for Wasm.
+#![cfg_attr(not(feature = "std"), no_std)]
+
+mod benchmarking;
+pub mod weights;
+
+use sp_std::{prelude::*, marker::PhantomData, borrow::Borrow};
+use codec::{Encode, Decode, Codec};
+use sp_runtime::{RuntimeDebug, traits::{Zero, One, BadOrigin, Saturating}};
+use frame_support::{
+ decl_module, decl_storage, decl_event, decl_error, IterableStorageMap,
+ dispatch::{Dispatchable, DispatchError, DispatchResult, Parameter},
+ traits::{Get, schedule::{self, DispatchTime}, OriginTrait, EnsureOrigin, IsType},
+ 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;
+
+/// 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
+/// should be added to our implied traits list.
+///
+/// `system::Config` should always be included in our implied traits.
+/// //
+pub trait Config: system::Config
+{
+
+ /// The overarching event type.
+ type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;
+
+ /// The aggregated origin which the dispatch will take.
+ type Origin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
+ + From<Self::PalletsOrigin> + IsType<<Self as system::Config>::Origin>;
+
+ /// The caller origin, overarching type of all pallets origins.
+ type PalletsOrigin: From<system::RawOrigin<Self::AccountId>> + Codec + Clone + Eq;
+
+ /// The aggregated call type.
+ type Call: Parameter + Dispatchable<Origin=<Self as Config>::Origin> + GetDispatchInfo + From<system::Call<Self>>;
+
+ /// The maximum weight that may be scheduled per block for any dispatchables of less priority
+ /// than `schedule::HARD_DEADLINE`.
+ type MaximumWeight: Get<Weight>;
+
+ /// Required origin to schedule or cancel calls.
+ type ScheduleOrigin: EnsureOrigin<<Self as system::Config>::Origin>;
+
+ /// The maximum number of scheduled calls in the queue for a single block.
+ /// Not strictly enforced, but used for weight estimation.
+ type MaxScheduledPerBlock: Get<u32>;
+
+ /// Sponsoring function
+ type Sponsoring: SponsoringResolve<Self::AccountId, <Self as Config>::Call>;
+
+ /// Weight information for extrinsics in this pallet.
+ type WeightInfo: WeightInfo;
+}
+
+// pub type SelfWeightInfo<T> = <T as system::Config>::WeightInfo;
+
+/// Just a simple index for naming period tasks.
+pub type PeriodicIndex = u32;
+/// The location of a scheduled task that can be used to remove it.
+pub type TaskAddress<BlockNumber> = (BlockNumber, u32);
+
+#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]
+#[derive(Clone, RuntimeDebug, Encode, Decode)]
+struct ScheduledV1<Call, BlockNumber> {
+ maybe_id: Option<Vec<u8>>,
+ priority: schedule::Priority,
+ call: Call,
+ maybe_periodic: Option<schedule::Period<BlockNumber>>,
+}
+
+/// Information regarding an item to be executed in the future.
+#[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))]
+#[derive(Clone, RuntimeDebug, Encode, Decode)]
+pub struct ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId> {
+ /// The unique identity for this task, if there is one.
+ maybe_id: Option<Vec<u8>>,
+ /// This task's priority.
+ priority: schedule::Priority,
+ /// The call to be dispatched.
+ call: Call,
+ /// If the call is periodic, then this points to the information concerning that.
+ maybe_periodic: Option<schedule::Period<BlockNumber>>,
+ /// The origin to dispatch the call.
+ origin: PalletsOrigin,
+ _phantom: PhantomData<AccountId>,
+}
+
+/// The current version of Scheduled struct.
+pub type Scheduled<Call, BlockNumber, PalletsOrigin, AccountId> = ScheduledV2<Call, BlockNumber, PalletsOrigin, AccountId>;
+
+// A value placed in storage that represents the current version of the Scheduler storage.
+// This value is used by the `on_runtime_upgrade` logic to determine whether we run
+// storage migration logic.
+#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
+enum Releases {
+ V1,
+ V2,
+}
+
+impl Default for Releases {
+ fn default() -> Self {
+ Releases::V1
+ }
+}
+
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
+pub struct CallSpec {
+
+ module: u32,
+ method: u32,
+}
+
+decl_storage! {
+ trait Store for Module<T: Config> as Scheduler {
+ /// Items to be executed, indexed by the block number that they should be executed on.
+ pub Agenda: map hasher(twox_64_concat) T::BlockNumber
+ => Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, T::PalletsOrigin, T::AccountId>>>;
+
+ pub SpecAgenda: map hasher(twox_64_concat) T::BlockNumber
+ => Vec<Option<CallSpec>>;
+
+ /// Lookup from identity to the block number and index of the task.
+ Lookup: map hasher(twox_64_concat) Vec<u8> => Option<TaskAddress<T::BlockNumber>>;
+
+ /// Storage version of the pallet.
+ ///
+ /// New networks start with last version.
+ StorageVersion build(|_| Releases::V2): Releases;
+ }
+}
+
+decl_event!(
+ pub enum Event<T> where <T as system::Config>::BlockNumber {
+ /// Scheduled some task. \[when, index\]
+ Scheduled(BlockNumber, u32),
+ /// Canceled some task. \[when, index\]
+ Canceled(BlockNumber, u32),
+ /// Dispatched some task. \[task, id, result\]
+ Dispatched(TaskAddress<BlockNumber>, Option<Vec<u8>>, DispatchResult),
+ }
+);
+
+decl_error! {
+ pub enum Error for Module<T: Config> {
+ /// Failed to schedule a call
+ FailedToSchedule,
+ /// Cannot find the scheduled call.
+ NotFound,
+ /// Given target block number is in the past.
+ TargetBlockNumberInPast,
+ /// Reschedule failed because it does not change scheduled time.
+ RescheduleNoChange,
+ }
+}
+
+decl_module! {
+ /// Scheduler module declaration.
+ pub struct Module<T: Config> for enum Call
+ where
+ origin: <T as system::Config>::Origin
+ {
+ type Error = Error<T>;
+ fn deposit_event() = default;
+
+
+ /// Anonymously schedule a task.
+ ///
+ /// # <weight>
+ /// - S = Number of already scheduled calls
+ /// - Base Weight: 22.29 + .126 * S µs
+ /// - DB Weight:
+ /// - Read: Agenda
+ /// - Write: Agenda
+ /// - Will use base weight of 25 which should be good for up to 30 scheduled calls
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
+ fn schedule(origin,
+ when: T::BlockNumber,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ call: Box<<T as Config>::Call>,
+ )
+ {
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_schedule(DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call)?;
+ }
+
+ /// Cancel an anonymously scheduled task.
+ ///
+ /// # <weight>
+ /// - S = Number of already scheduled calls
+ /// - Base Weight: 22.15 + 2.869 * S µs
+ /// - DB Weight:
+ /// - Read: Agenda
+ /// - Write: Agenda, Lookup
+ /// - Will use base weight of 100 which should be good for up to 30 scheduled calls
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get())]
+ fn cancel(origin, when: T::BlockNumber, index: u32) {
+ T::ScheduleOrigin::ensure_origin(origin.clone())?;
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_cancel(Some(origin.caller().clone()), (when, index))?;
+ }
+
+ /// Schedule a named task.
+ ///
+ /// # <weight>
+ /// - S = Number of already scheduled calls
+ /// - Base Weight: 29.6 + .159 * S µs
+ /// - DB Weight:
+ /// - Read: Agenda, Lookup
+ /// - Write: Agenda, Lookup
+ /// - Will use base weight of 35 which should be good for more than 30 scheduled calls
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]
+ fn schedule_named(origin,
+ id: Vec<u8>,
+ when: T::BlockNumber,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ call: Box<<T as Config>::Call>,
+ ) {
+ T::ScheduleOrigin::ensure_origin(origin.clone())?;
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_schedule_named(
+ id, DispatchTime::At(when), maybe_periodic, priority, origin.caller().clone(), *call
+ )?;
+ }
+
+ /// Cancel a named scheduled task.
+ ///
+ /// # <weight>
+ /// - S = Number of already scheduled calls
+ /// - Base Weight: 24.91 + 2.907 * S µs
+ /// - DB Weight:
+ /// - Read: Agenda, Lookup
+ /// - Write: Agenda, Lookup
+ /// - Will use base weight of 100 which should be good for up to 30 scheduled calls
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::cancel_named(T::MaxScheduledPerBlock::get())]
+ fn cancel_named(origin, id: Vec<u8>) {
+ T::ScheduleOrigin::ensure_origin(origin.clone())?;
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_cancel_named(Some(origin.caller().clone()), id)?;
+ }
+
+ /// Anonymously schedule a task after a delay.
+ ///
+ /// # <weight>
+ /// Same as [`schedule`].
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
+ fn schedule_after(origin,
+ after: T::BlockNumber,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ call: Box<<T as Config>::Call>,
+ ) {
+ T::ScheduleOrigin::ensure_origin(origin.clone())?;
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_schedule(
+ DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call
+ )?;
+ }
+
+ /// Schedule a named task after a delay.
+ ///
+ /// # <weight>
+ /// Same as [`schedule_named`].
+ /// # </weight>
+ #[weight = <T as Config>::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get())]
+ fn schedule_named_after(origin,
+ id: Vec<u8>,
+ after: T::BlockNumber,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ call: Box<<T as Config>::Call>,
+ ) {
+ T::ScheduleOrigin::ensure_origin(origin.clone())?;
+ let origin = <T as Config>::Origin::from(origin);
+ Self::do_schedule_named(
+ id, DispatchTime::After(after), maybe_periodic, priority, origin.caller().clone(), *call
+ )?;
+ }
+
+ /// Execute the scheduled calls
+ ///
+ /// # <weight>
+ /// - S = Number of already scheduled calls
+ /// - N = Named scheduled calls
+ /// - P = Periodic Calls
+ /// - Base Weight: 9.243 + 23.45 * S µs
+ /// - DB Weight:
+ /// - Read: Agenda + Lookup * N + Agenda(Future) * P
+ /// - Write: Agenda + Lookup * N + Agenda(future) * P
+ /// # </weight>
+ fn on_initialize(now: T::BlockNumber) -> Weight {
+ let limit = T::MaximumWeight::get();
+ let mut queued = Agenda::<T>::take(now).into_iter()
+ .enumerate()
+ .filter_map(|(index, s)| s.map(|inner| (index as u32, inner)))
+ .collect::<Vec<_>>();
+ if queued.len() as u32 > T::MaxScheduledPerBlock::get() {
+ log::warn!(
+ target: "runtime::scheduler",
+ "Warning: This block has more items queued in Scheduler than \
+ expected from the runtime configuration. An update might be needed."
+ );
+ }
+ queued.sort_by_key(|(_, s)| s.priority);
+ let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)
+ let mut total_weight: Weight = 0;
+ queued.into_iter()
+ .enumerate()
+ .scan(base_weight, |cumulative_weight, (order, (index, s))| {
+ *cumulative_weight = cumulative_weight
+ .saturating_add(s.call.get_dispatch_info().weight);
+
+ let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
+ s.origin.clone()
+ ).into();
+
+ if ensure_signed(origin).is_ok() {
+ // AccountData for inner call origin accountdata.
+ *cumulative_weight = cumulative_weight
+ .saturating_add(T::DbWeight::get().reads_writes(1, 1));
+ }
+
+ if s.maybe_id.is_some() {
+ // Remove/Modify Lookup
+ *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().writes(1));
+ }
+ if s.maybe_periodic.is_some() {
+ // Read/Write Agenda for future block
+ *cumulative_weight = cumulative_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
+ }
+
+ Some((order, index, *cumulative_weight, s))
+ })
+ .filter_map(|(order, index, cumulative_weight, mut s)| {
+ // We allow a scheduled call if any is true:
+ // - It's priority is `HARD_DEADLINE`
+ // - It does not push the weight past the limit.
+ // - It is the first item in the schedule
+ if s.priority <= schedule::HARD_DEADLINE || cumulative_weight <= limit || order == 0 {
+
+ let origin = <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
+ 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 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();
+ if let &Some((period, count)) = &s.maybe_periodic {
+ if count > 1 {
+ s.maybe_periodic = Some((period, count - 1));
+ } else {
+ s.maybe_periodic = None;
+ }
+ let next = now + period;
+ // If scheduled is named, place it's information in `Lookup`
+ if let Some(ref id) = s.maybe_id {
+ let next_index = Agenda::<T>::decode_len(now + period).unwrap_or(0);
+ Lookup::<T>::insert(id, (next, next_index as u32));
+ }
+ Agenda::<T>::append(next, Some(s));
+ } else {
+ if let Some(ref id) = s.maybe_id {
+ Lookup::<T>::remove(id);
+ }
+ }
+ Self::deposit_event(RawEvent::Dispatched(
+ (now, index),
+ maybe_id,
+ r.map(|_| ()).map_err(|e| e.error)
+ ));
+ total_weight = cumulative_weight;
+ None
+ } else {
+ Some(Some(s))
+ }
+ })
+ .for_each(|unused| {
+ let next = now + One::one();
+ Agenda::<T>::append(next, unused);
+ });
+
+ total_weight
+ }
+ }
+}
+
+impl<T: Config> Module<T> {
+ /// Migrate storage format from V1 to V2.
+ /// Return true if migration is performed.
+ pub fn migrate_v1_to_t2() -> bool {
+ if StorageVersion::get() == Releases::V1 {
+ StorageVersion::put(Releases::V2);
+
+ Agenda::<T>::translate::<
+ Vec<Option<ScheduledV1<<T as Config>::Call, T::BlockNumber>>>, _
+ >(|_, agenda| Some(
+ agenda
+ .into_iter()
+ .map(|schedule| schedule.map(|schedule| ScheduledV2 {
+ maybe_id: schedule.maybe_id,
+ priority: schedule.priority,
+ call: schedule.call,
+ maybe_periodic: schedule.maybe_periodic,
+ origin: system::RawOrigin::Root.into(),
+ _phantom: Default::default(),
+ }))
+ .collect::<Vec<_>>()
+ ));
+
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Helper to migrate scheduler when the pallet origin type has changed.
+ pub fn migrate_origin<OldOrigin: Into<T::PalletsOrigin> + codec::Decode>() {
+ Agenda::<T>::translate::<
+ Vec<Option<Scheduled<<T as Config>::Call, T::BlockNumber, OldOrigin, T::AccountId>>>, _
+ >(|_, agenda| Some(
+ agenda
+ .into_iter()
+ .map(|schedule| schedule.map(|schedule| Scheduled {
+ maybe_id: schedule.maybe_id,
+ priority: schedule.priority,
+ call: schedule.call,
+ maybe_periodic: schedule.maybe_periodic,
+ origin: schedule.origin.into(),
+ _phantom: Default::default(),
+ }))
+ .collect::<Vec<_>>()
+ ));
+ }
+
+ fn resolve_time(when: DispatchTime<T::BlockNumber>) -> Result<T::BlockNumber, DispatchError> {
+ let now = frame_system::Pallet::<T>::block_number();
+
+ let when = match when {
+ DispatchTime::At(x) => x,
+ // The current block has already completed it's scheduled tasks, so
+ // Schedule the task at lest one block after this current block.
+ DispatchTime::After(x) => now.saturating_add(x).saturating_add(One::one())
+ };
+
+ if when <= now {
+ return Err(Error::<T>::TargetBlockNumberInPast.into())
+ }
+
+ Ok(when)
+ }
+
+ fn do_schedule(
+ when: DispatchTime<T::BlockNumber>,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ origin: T::PalletsOrigin,
+ call: <T as Config>::Call
+ ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
+ let when = Self::resolve_time(when)?;
+
+ // sanitize maybe_periodic
+ let maybe_periodic = maybe_periodic
+ .filter(|p| p.1 > 1 && !p.0.is_zero())
+ // Remove one from the number of repetitions since we will schedule one now.
+ .map(|(p, c)| (p, c - 1));
+ let s = Some(Scheduled {
+ maybe_id: None, priority, call, maybe_periodic, origin, _phantom: PhantomData::<T::AccountId>::default(),
+ });
+ Agenda::<T>::append(when, s);
+ let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
+ if index > T::MaxScheduledPerBlock::get() {
+ log::warn!(
+ target: "runtime::scheduler",
+ "Warning: There are more items queued in the Scheduler than \
+ expected from the runtime configuration. An update might be needed.",
+ );
+ }
+ Self::deposit_event(RawEvent::Scheduled(when, index));
+
+ Ok((when, index))
+ }
+
+ fn do_cancel(
+ origin: Option<T::PalletsOrigin>,
+ (when, index): TaskAddress<T::BlockNumber>
+ ) -> Result<(), DispatchError> {
+ let scheduled = Agenda::<T>::try_mutate(
+ when,
+ |agenda| {
+ agenda.get_mut(index as usize)
+ .map_or(Ok(None), |s| -> Result<Option<Scheduled<_, _, _, _>>, DispatchError> {
+ if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
+ if *o != s.origin {
+ return Err(BadOrigin.into());
+ }
+ };
+ Ok(s.take())
+ })
+ },
+ )?;
+ if let Some(s) = scheduled {
+ if let Some(id) = s.maybe_id {
+ Lookup::<T>::remove(id);
+ }
+ Self::deposit_event(RawEvent::Canceled(when, index));
+ Ok(())
+ } else {
+ Err(Error::<T>::NotFound)?
+ }
+ }
+
+ fn do_reschedule(
+ (when, index): TaskAddress<T::BlockNumber>,
+ new_time: DispatchTime<T::BlockNumber>,
+ ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
+ let new_time = Self::resolve_time(new_time)?;
+
+ if new_time == when {
+ return Err(Error::<T>::RescheduleNoChange.into());
+ }
+
+ Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
+ let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
+ let task = task.take().ok_or(Error::<T>::NotFound)?;
+ Agenda::<T>::append(new_time, Some(task));
+ Ok(())
+ })?;
+
+ let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;
+ Self::deposit_event(RawEvent::Canceled(when, index));
+ Self::deposit_event(RawEvent::Scheduled(new_time, new_index));
+
+ Ok((new_time, new_index))
+ }
+
+ fn do_schedule_named(
+ id: Vec<u8>,
+ when: DispatchTime<T::BlockNumber>,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ origin: T::PalletsOrigin,
+ call: <T as Config>::Call,
+ ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
+ // ensure id it is unique
+ if Lookup::<T>::contains_key(&id) {
+ return Err(Error::<T>::FailedToSchedule)?
+ }
+
+ let when = Self::resolve_time(when)?;
+
+ // sanitize maybe_periodic
+ let maybe_periodic = maybe_periodic
+ .filter(|p| p.1 > 1 && !p.0.is_zero())
+ // Remove one from the number of repetitions since we will schedule one now.
+ .map(|(p, c)| (p, c - 1));
+
+ let s = Scheduled {
+ maybe_id: Some(id.clone()), priority, call, maybe_periodic, origin, _phantom: Default::default()
+ };
+ Agenda::<T>::append(when, Some(s));
+ let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
+ if index > T::MaxScheduledPerBlock::get() {
+ log::warn!(
+ target: "runtime::scheduler",
+ "Warning: There are more items queued in the Scheduler than \
+ expected from the runtime configuration. An update might be needed.",
+ );
+ }
+ let address = (when, index);
+ Lookup::<T>::insert(&id, &address);
+ Self::deposit_event(RawEvent::Scheduled(when, index));
+
+ Ok(address)
+ }
+
+ fn do_cancel_named(origin: Option<T::PalletsOrigin>, id: Vec<u8>) -> DispatchResult {
+ Lookup::<T>::try_mutate_exists(id, |lookup| -> DispatchResult {
+ if let Some((when, index)) = lookup.take() {
+ let i = index as usize;
+ Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
+ if let Some(s) = agenda.get_mut(i) {
+ if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) {
+ if *o != s.origin {
+ return Err(BadOrigin.into());
+ }
+ }
+ *s = None;
+ }
+ Ok(())
+ })?;
+ Self::deposit_event(RawEvent::Canceled(when, index));
+ Ok(())
+ } else {
+ Err(Error::<T>::NotFound)?
+ }
+ })
+ }
+
+ fn do_reschedule_named(
+ id: Vec<u8>,
+ new_time: DispatchTime<T::BlockNumber>,
+ ) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
+ let new_time = Self::resolve_time(new_time)?;
+
+ Lookup::<T>::try_mutate_exists(id, |lookup| -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
+ let (when, index) = lookup.ok_or(Error::<T>::NotFound)?;
+
+ if new_time == when {
+ return Err(Error::<T>::RescheduleNoChange.into());
+ }
+
+ Agenda::<T>::try_mutate(when, |agenda| -> DispatchResult {
+ let task = agenda.get_mut(index as usize).ok_or(Error::<T>::NotFound)?;
+ let task = task.take().ok_or(Error::<T>::NotFound)?;
+ Agenda::<T>::append(new_time, Some(task));
+
+ Ok(())
+ })?;
+
+ let new_index = Agenda::<T>::decode_len(new_time).unwrap_or(1) as u32 - 1;
+ Self::deposit_event(RawEvent::Canceled(when, index));
+ Self::deposit_event(RawEvent::Scheduled(new_time, new_index));
+
+ *lookup = Some((new_time, new_index));
+
+ Ok((new_time, new_index))
+ })
+ }
+}
+
+impl<T: Config> schedule::Anon<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin> for Module<T> {
+ type Address = TaskAddress<T::BlockNumber>;
+
+ fn schedule(
+ when: DispatchTime<T::BlockNumber>,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ origin: T::PalletsOrigin,
+ call: <T as Config>::Call
+ ) -> Result<Self::Address, DispatchError> {
+ Self::do_schedule(when, maybe_periodic, priority, origin, call)
+ }
+
+ fn cancel((when, index): Self::Address) -> Result<(), ()> {
+ Self::do_cancel(None, (when, index)).map_err(|_| ())
+ }
+
+ fn reschedule(
+ address: Self::Address,
+ when: DispatchTime<T::BlockNumber>,
+ ) -> Result<Self::Address, DispatchError> {
+ Self::do_reschedule(address, when)
+ }
+
+ fn next_dispatch_time((when, index): Self::Address) -> Result<T::BlockNumber, ()> {
+ Agenda::<T>::get(when).get(index as usize).ok_or(()).map(|_| when)
+ }
+}
+
+impl<T: Config> schedule::Named<T::BlockNumber, <T as Config>::Call, T::PalletsOrigin> for Module<T> {
+ type Address = TaskAddress<T::BlockNumber>;
+
+ fn schedule_named(
+ id: Vec<u8>,
+ when: DispatchTime<T::BlockNumber>,
+ maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
+ priority: schedule::Priority,
+ origin: T::PalletsOrigin,
+ call: <T as Config>::Call,
+ ) -> Result<Self::Address, ()> {
+ Self::do_schedule_named(id, when, maybe_periodic, priority, origin, call).map_err(|_| ())
+ }
+
+ fn cancel_named(id: Vec<u8>) -> Result<(), ()> {
+ Self::do_cancel_named(None, id).map_err(|_| ())
+ }
+
+ fn reschedule_named(
+ id: Vec<u8>,
+ when: DispatchTime<T::BlockNumber>,
+ ) -> Result<Self::Address, DispatchError> {
+ Self::do_reschedule_named(id, when)
+ }
+
+ fn next_dispatch_time(id: Vec<u8>) -> Result<T::BlockNumber, ()> {
+ Lookup::<T>::get(id).and_then(|(when, index)| Agenda::<T>::get(when).get(index as usize).map(|_| when)).ok_or(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use frame_support::{
+ parameter_types, assert_ok, ord_parameter_types,
+ assert_noop, assert_err, Hashable,
+ traits::{OnInitialize, OnFinalize, Filter},
+ weights::constants::RocksDbWeight,
+ };
+ use sp_core::H256;
+ use sp_runtime::{
+ Perbill,
+ testing::Header,
+ traits::{BlakeTwo256, IdentityLookup},
+ };
+ use frame_system::{EnsureOneOf, EnsureRoot, EnsureSignedBy};
+ use substrate_test_utils::assert_eq_uvec;
+ use crate as scheduler;
+
+ mod logger {
+ use super::*;
+ use std::cell::RefCell;
+
+ thread_local! {
+ static LOG: RefCell<Vec<(OriginCaller, u32)>> = RefCell::new(Vec::new());
+ }
+ pub fn log() -> Vec<(OriginCaller, u32)> {
+ LOG.with(|log| log.borrow().clone())
+ }
+ pub trait Config: system::Config {
+ type Event: From<Event> + Into<<Self as system::Config>::Event>;
+ }
+ decl_event! {
+ pub enum Event {
+ Logged(u32, Weight),
+ }
+ }
+ decl_module! {
+ pub struct Module<T: Config> for enum Call
+ where
+ origin: <T as system::Config>::Origin,
+ <T as system::Config>::Origin: OriginTrait<PalletsOrigin = OriginCaller>
+ {
+ fn deposit_event() = default;
+
+ #[weight = *weight]
+ fn log(origin, i: u32, weight: Weight) {
+ Self::deposit_event(Event::Logged(i, weight));
+ LOG.with(|log| {
+ log.borrow_mut().push((origin.caller().clone(), i));
+ })
+ }
+
+ #[weight = *weight]
+ fn log_without_filter(origin, i: u32, weight: Weight) {
+ Self::deposit_event(Event::Logged(i, weight));
+ LOG.with(|log| {
+ log.borrow_mut().push((origin.caller().clone(), i));
+ })
+ }
+ }
+ }
+ }
+
+ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+ type Block = frame_system::mocking::MockBlock<Test>;
+
+ frame_support::construct_runtime!(
+ pub enum Test where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic,
+ {
+ System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+ Logger: logger::{Pallet, Call, Event},
+ Scheduler: scheduler::{Pallet, Call, Storage, Event<T>},
+ }
+ );
+
+ // Scheduler must dispatch with root and no filter, this tests base filter is indeed not used.
+ pub struct BaseFilter;
+ impl Filter<Call> for BaseFilter {
+ fn filter(call: &Call) -> bool {
+ !matches!(call, Call::Logger(logger::Call::log(_, _)))
+ }
+ }
+
+ parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+ pub BlockWeights: frame_system::limits::BlockWeights =
+ frame_system::limits::BlockWeights::simple_max(2_000_000_000_000);
+ }
+ impl system::Config for Test {
+ type BaseCallFilter = BaseFilter;
+ type BlockWeights = ();
+ type BlockLength = ();
+ type DbWeight = RocksDbWeight;
+ type Origin = Origin;
+ type Call = Call;
+ type Index = u64;
+ type BlockNumber = u64;
+ type Hash = H256;
+ type Hashing = BlakeTwo256;
+ type AccountId = u64;
+ type Lookup = IdentityLookup<Self::AccountId>;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = ();
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+ }
+ impl logger::Config for Test {
+ type Event = Event;
+ }
+ parameter_types! {
+ pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block;
+ pub const MaxScheduledPerBlock: u32 = 10;
+ }
+ ord_parameter_types! {
+ pub const One: u64 = 1;
+ }
+
+ impl Config for Test {
+ type Event = Event;
+ type Origin = Origin;
+ type PalletsOrigin = OriginCaller;
+ type Call = Call;
+ type MaximumWeight = MaximumSchedulerWeight;
+ type ScheduleOrigin = EnsureOneOf<u64, EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
+ type MaxScheduledPerBlock = MaxScheduledPerBlock;
+ type WeightInfo = ();
+ }
+
+ pub fn new_test_ext() -> sp_io::TestExternalities {
+ let t = system::GenesisConfig::default().build_storage::<Test>().unwrap();
+ t.into()
+ }
+
+ fn run_to_block(n: u64) {
+ while System::block_number() < n {
+ Scheduler::on_finalize(System::block_number());
+ System::set_block_number(System::block_number() + 1);
+ Scheduler::on_initialize(System::block_number());
+ }
+ }
+
+ fn root() -> OriginCaller {
+ system::RawOrigin::Root.into()
+ }
+
+ #[test]
+ fn basic_scheduling_works() {
+ new_test_ext().execute_with(|| {
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call));
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn schedule_after_works() {
+ new_test_ext().execute_with(|| {
+ run_to_block(2);
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6
+ assert_ok!(Scheduler::do_schedule(DispatchTime::After(3), None, 127, root(), call));
+ run_to_block(5);
+ assert!(logger::log().is_empty());
+ run_to_block(6);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn schedule_after_zero_works() {
+ new_test_ext().execute_with(|| {
+ run_to_block(2);
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ assert_ok!(Scheduler::do_schedule(DispatchTime::After(0), None, 127, root(), call));
+ // Will trigger on the next block.
+ run_to_block(3);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn periodic_scheduling_works() {
+ new_test_ext().execute_with(|| {
+ // at #4, every 3 blocks, 3 times.
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4), Some((3, 3)), 127, root(), Call::Logger(logger::Call::log(42, 1000))
+ ));
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(6);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(7);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
+ run_to_block(9);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
+ run_to_block(10);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn reschedule_works() {
+ new_test_ext().execute_with(|| {
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ assert_eq!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), call).unwrap(), (4, 0));
+
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+
+ assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0));
+
+ assert_noop!(Scheduler::do_reschedule((6, 0), DispatchTime::At(6)), Error::<Test>::RescheduleNoChange);
+
+ run_to_block(4);
+ assert!(logger::log().is_empty());
+
+ run_to_block(6);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn reschedule_named_works() {
+ new_test_ext().execute_with(|| {
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ assert_eq!(Scheduler::do_schedule_named(
+ 1u32.encode(), DispatchTime::At(4), None, 127, root(), call
+ ).unwrap(), (4, 0));
+
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+
+ assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0));
+
+ assert_noop!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)), Error::<Test>::RescheduleNoChange);
+
+ run_to_block(4);
+ assert!(logger::log().is_empty());
+
+ run_to_block(6);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn reschedule_named_perodic_works() {
+ new_test_ext().execute_with(|| {
+ let call = Call::Logger(logger::Call::log(42, 1000));
+ assert!(!<Test as frame_system::Config>::BaseCallFilter::filter(&call));
+ assert_eq!(Scheduler::do_schedule_named(
+ 1u32.encode(), DispatchTime::At(4), Some((3, 3)), 127, root(), call
+ ).unwrap(), (4, 0));
+
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+
+ assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(5)).unwrap(), (5, 0));
+ assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(6)).unwrap(), (6, 0));
+
+ run_to_block(5);
+ assert!(logger::log().is_empty());
+
+ run_to_block(6);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+
+ assert_eq!(Scheduler::do_reschedule_named(1u32.encode(), DispatchTime::At(10)).unwrap(), (10, 0));
+
+ run_to_block(9);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+
+ run_to_block(10);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
+
+ run_to_block(13);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
+
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn cancel_named_scheduling_works_with_normal_cancel() {
+ new_test_ext().execute_with(|| {
+ // at #4.
+ Scheduler::do_schedule_named(
+ 1u32.encode(), DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(69, 1000))
+ ).unwrap();
+ let i = Scheduler::do_schedule(
+ DispatchTime::At(4), None, 127, root(), Call::Logger(logger::Call::log(42, 1000))
+ ).unwrap();
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+ assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));
+ assert_ok!(Scheduler::do_cancel(None, i));
+ run_to_block(100);
+ assert!(logger::log().is_empty());
+ });
+ }
+
+ #[test]
+ fn cancel_named_periodic_scheduling_works() {
+ new_test_ext().execute_with(|| {
+ // at #4, every 3 blocks, 3 times.
+ Scheduler::do_schedule_named(
+ 1u32.encode(),
+ DispatchTime::At(4),
+ Some((3, 3)),
+ 127,
+ root(),
+ Call::Logger(logger::Call::log(42, 1000))
+ ).unwrap();
+ // same id results in error.
+ assert!(Scheduler::do_schedule_named(
+ 1u32.encode(),
+ DispatchTime::At(4),
+ None,
+ 127,
+ root(),
+ Call::Logger(logger::Call::log(69, 1000))
+ ).is_err());
+ // different id is ok.
+ Scheduler::do_schedule_named(
+ 2u32.encode(), DispatchTime::At(8), None, 127, root(), Call::Logger(logger::Call::log(69, 1000))
+ ).unwrap();
+ run_to_block(3);
+ assert!(logger::log().is_empty());
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(6);
+ assert_ok!(Scheduler::do_cancel_named(None, 1u32.encode()));
+ run_to_block(100);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
+ });
+ }
+
+ #[test]
+ fn scheduler_respects_weight_limits() {
+ new_test_ext().execute_with(|| {
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 127,
+ root(),
+ Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
+ ));
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 127,
+ root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
+ ));
+ // 69 and 42 do not fit together
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 42u32)]);
+ run_to_block(5);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
+ });
+ }
+
+ #[test]
+ fn scheduler_respects_hard_deadlines_more() {
+ new_test_ext().execute_with(|| {
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 0,
+ root(),
+ Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
+ ));
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 0,
+ root(),
+ Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
+ ));
+ // With base weights, 69 and 42 should not fit together, but do because of hard deadlines
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
+ });
+ }
+
+ #[test]
+ fn scheduler_respects_priority_ordering() {
+ new_test_ext().execute_with(|| {
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 1,
+ root(),
+ Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 2))
+ ));
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 0,
+ root(),
+ Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
+ ));
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn scheduler_respects_priority_ordering_with_soft_deadlines() {
+ new_test_ext().execute_with(|| {
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 255,
+ root(), Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))
+ ));
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 127,
+ root(), Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
+ ));
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(4),
+ None,
+ 126,
+ root(), Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2))
+ ));
+
+ // 2600 does not fit with 69 or 42, but has higher priority, so will go through
+ run_to_block(4);
+ assert_eq!(logger::log(), vec![(root(), 2600u32)]);
+ // 69 and 42 fit together
+ run_to_block(5);
+ assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);
+ });
+ }
+
+ #[test]
+ fn on_initialize_weight_is_correct() {
+ new_test_ext().execute_with(|| {
+ let base_weight: Weight = <Test as frame_system::Config>::DbWeight::get().reads_writes(1, 2);
+ let base_multiplier = 0;
+ let named_multiplier = <Test as frame_system::Config>::DbWeight::get().writes(1);
+ let periodic_multiplier = <Test as frame_system::Config>::DbWeight::get().reads_writes(1, 1);
+
+ // Named
+ assert_ok!(
+ Scheduler::do_schedule_named(
+ 1u32.encode(), DispatchTime::At(1), None, 255, root(),
+ Call::Logger(logger::Call::log(3, MaximumSchedulerWeight::get() / 3))
+ )
+ );
+ // Anon Periodic
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(1),
+ Some((1000, 3)),
+ 128,
+ root(),
+ Call::Logger(logger::Call::log(42, MaximumSchedulerWeight::get() / 3))
+ ));
+ // Anon
+ assert_ok!(Scheduler::do_schedule(
+ DispatchTime::At(1),
+ None,
+ 127,
+ root(),
+ Call::Logger(logger::Call::log(69, MaximumSchedulerWeight::get() / 2))
+ ));
+ // Named Periodic
+ assert_ok!(Scheduler::do_schedule_named(
+ 2u32.encode(), DispatchTime::At(1), Some((1000, 3)), 126, root(),
+ Call::Logger(logger::Call::log(2600, MaximumSchedulerWeight::get() / 2)))
+ );
+
+ // Will include the named periodic only
+ let actual_weight = Scheduler::on_initialize(1);
+ let call_weight = MaximumSchedulerWeight::get() / 2;
+ assert_eq!(
+ actual_weight, call_weight + base_weight + base_multiplier + named_multiplier + periodic_multiplier
+ );
+ assert_eq!(logger::log(), vec![(root(), 2600u32)]);
+
+ // Will include anon and anon periodic
+ let actual_weight = Scheduler::on_initialize(2);
+ let call_weight = MaximumSchedulerWeight::get() / 2 + MaximumSchedulerWeight::get() / 3;
+ assert_eq!(actual_weight, call_weight + base_weight + base_multiplier * 2 + periodic_multiplier);
+ assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);
+
+ // Will include named only
+ let actual_weight = Scheduler::on_initialize(3);
+ let call_weight = MaximumSchedulerWeight::get() / 3;
+ assert_eq!(actual_weight, call_weight + base_weight + base_multiplier + named_multiplier);
+ assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)]);
+
+ // Will contain none
+ let actual_weight = Scheduler::on_initialize(4);
+ assert_eq!(actual_weight, 0);
+ });
+ }
+
+ #[test]
+ fn root_calls_works() {
+ new_test_ext().execute_with(|| {
+ let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
+ let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
+ assert_ok!(Scheduler::schedule_named(Origin::root(), 1u32.encode(), 4, None, 127, call));
+ assert_ok!(Scheduler::schedule(Origin::root(), 4, None, 127, call2));
+ run_to_block(3);
+ // Scheduled calls are in the agenda.
+ assert_eq!(Agenda::<Test>::get(4).len(), 2);
+ assert!(logger::log().is_empty());
+ assert_ok!(Scheduler::cancel_named(Origin::root(), 1u32.encode()));
+ assert_ok!(Scheduler::cancel(Origin::root(), 4, 1));
+ // Scheduled calls are made NONE, so should not effect state
+ run_to_block(100);
+ assert!(logger::log().is_empty());
+ });
+ }
+
+ #[test]
+ fn fails_to_schedule_task_in_the_past() {
+ new_test_ext().execute_with(|| {
+ run_to_block(3);
+
+ let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
+ let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
+
+ assert_err!(
+ Scheduler::schedule_named(Origin::root(), 1u32.encode(), 2, None, 127, call),
+ Error::<Test>::TargetBlockNumberInPast,
+ );
+
+ assert_err!(
+ Scheduler::schedule(Origin::root(), 2, None, 127, call2.clone()),
+ Error::<Test>::TargetBlockNumberInPast,
+ );
+
+ assert_err!(
+ Scheduler::schedule(Origin::root(), 3, None, 127, call2),
+ Error::<Test>::TargetBlockNumberInPast,
+ );
+ });
+ }
+
+ #[test]
+ fn should_use_orign() {
+ new_test_ext().execute_with(|| {
+ let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
+ let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
+ assert_ok!(
+ Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call)
+ );
+ assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2));
+ run_to_block(3);
+ // Scheduled calls are in the agenda.
+ assert_eq!(Agenda::<Test>::get(4).len(), 2);
+ assert!(logger::log().is_empty());
+ assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), 1u32.encode()));
+ assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1));
+ // Scheduled calls are made NONE, so should not effect state
+ run_to_block(100);
+ assert!(logger::log().is_empty());
+ });
+ }
+
+ #[test]
+ fn should_check_orign() {
+ new_test_ext().execute_with(|| {
+ let call = Box::new(Call::Logger(logger::Call::log(69, 1000)));
+ let call2 = Box::new(Call::Logger(logger::Call::log(42, 1000)));
+ assert_noop!(
+ Scheduler::schedule_named(system::RawOrigin::Signed(2).into(), 1u32.encode(), 4, None, 127, call),
+ BadOrigin
+ );
+ assert_noop!(Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2), BadOrigin);
+ });
+ }
+
+ #[test]
+ fn should_check_orign_for_cancel() {
+ new_test_ext().execute_with(|| {
+ let call = Box::new(Call::Logger(logger::Call::log_without_filter(69, 1000)));
+ let call2 = Box::new(Call::Logger(logger::Call::log_without_filter(42, 1000)));
+ assert_ok!(
+ Scheduler::schedule_named(system::RawOrigin::Signed(1).into(), 1u32.encode(), 4, None, 127, call)
+ );
+ assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2));
+ run_to_block(3);
+ // Scheduled calls are in the agenda.
+ assert_eq!(Agenda::<Test>::get(4).len(), 2);
+ assert!(logger::log().is_empty());
+ assert_noop!(Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), 1u32.encode()), BadOrigin);
+ assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin);
+ assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), 1u32.encode()), BadOrigin);
+ assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin);
+ run_to_block(5);
+ assert_eq!(
+ logger::log(),
+ vec![(system::RawOrigin::Signed(1).into(), 69u32), (system::RawOrigin::Signed(1).into(), 42u32)]
+ );
+ });
+ }
+
+ #[test]
+ fn migration_to_v2_works() {
+ new_test_ext().execute_with(|| {
+ for i in 0..3u64 {
+ let k = i.twox_64_concat();
+ let old = vec![
+ Some(ScheduledV1 {
+ maybe_id: None,
+ priority: i as u8 + 10,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ }),
+ None,
+ Some(ScheduledV1 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ }),
+ ];
+ frame_support::migration::put_storage_value(
+ b"Scheduler",
+ b"Agenda",
+ &k,
+ old,
+ );
+ }
+
+ assert_eq!(StorageVersion::get(), Releases::V1);
+
+ assert!(Scheduler::migrate_v1_to_t2());
+
+ assert_eq_uvec!(Agenda::<Test>::iter().collect::<Vec<_>>(), vec![
+ (
+ 0,
+ vec![
+ Some(ScheduledV2 {
+ maybe_id: None,
+ priority: 10,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]),
+ (
+ 1,
+ vec![
+ Some(ScheduledV2 {
+ maybe_id: None,
+ priority: 11,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]
+ ),
+ (
+ 2,
+ vec![
+ Some(ScheduledV2 {
+ maybe_id: None,
+ priority: 12,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: root(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]
+ )
+ ]);
+
+ assert_eq!(StorageVersion::get(), Releases::V2);
+ });
+ }
+
+ #[test]
+ fn test_migrate_origin() {
+ new_test_ext().execute_with(|| {
+ for i in 0..3u64 {
+ let k = i.twox_64_concat();
+ let old: Vec<Option<Scheduled<_, _, u32, u64>>> = vec![
+ Some(Scheduled {
+ maybe_id: None,
+ priority: i as u8 + 10,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ origin: 3u32,
+ maybe_periodic: None,
+ _phantom: Default::default(),
+ }),
+ None,
+ Some(Scheduled {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ origin: 2u32,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ _phantom: Default::default(),
+ }),
+ ];
+ frame_support::migration::put_storage_value(
+ b"Scheduler",
+ b"Agenda",
+ &k,
+ old,
+ );
+ }
+
+ impl Into<OriginCaller> for u32 {
+ fn into(self) -> OriginCaller {
+ match self {
+ 3u32 => system::RawOrigin::Root.into(),
+ 2u32 => system::RawOrigin::None.into(),
+ _ => unreachable!("test make no use of it"),
+ }
+ }
+ }
+
+ Scheduler::migrate_origin::<u32>();
+
+ assert_eq_uvec!(Agenda::<Test>::iter().collect::<Vec<_>>(), vec![
+ (
+ 0,
+ vec![
+ Some(ScheduledV2::<_, _, OriginCaller, u64> {
+ maybe_id: None,
+ priority: 10,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: system::RawOrigin::Root.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: system::RawOrigin::None.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]),
+ (
+ 1,
+ vec![
+ Some(ScheduledV2 {
+ maybe_id: None,
+ priority: 11,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: system::RawOrigin::Root.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: system::RawOrigin::None.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]
+ ),
+ (
+ 2,
+ vec![
+ Some(ScheduledV2 {
+ maybe_id: None,
+ priority: 12,
+ call: Call::Logger(logger::Call::log(96, 100)),
+ maybe_periodic: None,
+ origin: system::RawOrigin::Root.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ None,
+ Some(ScheduledV2 {
+ maybe_id: Some(b"test".to_vec()),
+ priority: 123,
+ call: Call::Logger(logger::Call::log(69, 1000)),
+ maybe_periodic: Some((456u64, 10)),
+ origin: system::RawOrigin::None.into(),
+ _phantom: PhantomData::<u64>::default(),
+ }),
+ ]
+ )
+ ]);
+ });
+ }
+}
pallets/scheduler/src/weights.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/scheduler/src/weights.rs
@@ -0,0 +1,118 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Weights for pallet_scheduler
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0
+//! DATE: 2020-10-27, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
+
+// Executed Command:
+// target/release/substrate
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_scheduler
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./frame/scheduler/src/weights.rs
+// --template=./.maintain/frame-weight-template.hbs
+
+
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet_scheduler.
+pub trait WeightInfo {
+ fn schedule(s: u32, ) -> Weight;
+ fn cancel(s: u32, ) -> Weight;
+ fn schedule_named(s: u32, ) -> Weight;
+ fn cancel_named(s: u32, ) -> Weight;
+
+}
+
+/// Weights for pallet_scheduler using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+ fn schedule(s: u32, ) -> Weight {
+ (35_029_000 as Weight)
+ .saturating_add((77_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+
+ }
+ fn cancel(s: u32, ) -> Weight {
+ (31_419_000 as Weight)
+ .saturating_add((4_015_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(2 as Weight))
+
+ }
+ fn schedule_named(s: u32, ) -> Weight {
+ (44_752_000 as Weight)
+ .saturating_add((123_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
+ .saturating_add(T::DbWeight::get().writes(2 as Weight))
+
+ }
+ fn cancel_named(s: u32, ) -> Weight {
+ (35_712_000 as Weight)
+ .saturating_add((4_008_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
+ .saturating_add(T::DbWeight::get().writes(2 as Weight))
+
+ }
+
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+ fn schedule(s: u32, ) -> Weight {
+ (35_029_000 as Weight)
+ .saturating_add((77_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+
+ }
+ fn cancel(s: u32, ) -> Weight {
+ (31_419_000 as Weight)
+ .saturating_add((4_015_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+
+ }
+ fn schedule_named(s: u32, ) -> Weight {
+ (44_752_000 as Weight)
+ .saturating_add((123_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+
+ }
+ fn cancel_named(s: u32, ) -> Weight {
+ (35_712_000 as Weight)
+ .saturating_add((4_008_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+
+ }
+
+}
primitives/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/primitives/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "nft-data-structs"
+authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
+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", optional = true, features = ['derive'] }
+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",
+ "codec/std",
+ "frame-system/std",
+ "frame-support/std",
+ "sp-runtime/std",
+ "sp-core/std",
+ "pallet-contracts/std",
+]
\ No newline at end of file
primitives/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/src/lib.rs
@@ -0,0 +1,286 @@
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(feature = "std")]
+pub use serde::*;
+
+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<u8> for CollectionMode {
+ fn into(self) -> u8 {
+ match self {
+ CollectionMode::Invalid => 0,
+ CollectionMode::NFT => 1,
+ CollectionMode::Fungible(_) => 2,
+ CollectionMode::ReFungible => 3,
+ }
+ }
+}
+
+pub trait SponsoringResolve<AccountId, Call> {
+ fn resolve(
+ who: &AccountId,
+ call: &Call) -> Option<AccountId>;
+}
+
+#[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<AccountId> {
+ pub owner: AccountId,
+ pub fraction: u128,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum SponsorshipState<AccountId> {
+ /// The fees are applied to the transaction sender
+ Disabled,
+ Unconfirmed(AccountId),
+ /// Transactions are sponsored by specified account
+ Confirmed(AccountId),
+}
+
+impl<AccountId> SponsorshipState<AccountId> {
+ 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<T> Default for SponsorshipState<T> {
+ fn default() -> Self {
+ Self::Disabled
+ }
+}
+
+#[derive(Encode, Decode, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct Collection<T: frame_system::Config> {
+ pub owner: T::AccountId,
+ pub mode: CollectionMode,
+ pub access: AccessMode,
+ pub decimal_points: DecimalPoints,
+ pub name: Vec<u16>, // 64 include null escape char
+ pub description: Vec<u16>, // 256 include null escape char
+ pub token_prefix: Vec<u8>, // 16 include null escape char
+ pub mint_mode: bool,
+ pub offchain_schema: Vec<u8>,
+ pub schema_version: SchemaVersion,
+ pub sponsorship: SponsorshipState<T::AccountId>,
+ pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions
+ pub variable_on_chain_schema: Vec<u8>, //
+ pub const_on_chain_schema: Vec<u8>, //
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct NftItemType<AccountId> {
+ pub owner: AccountId,
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[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<AccountId> {
+ pub owner: Vec<Ownership<AccountId>>,
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CollectionLimits<BlockNumber: Encode + Decode> {
+ 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<BlockNumber>,
+ 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<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {
+ 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<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[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<u8>,
+ pub variable_data: Vec<u8>,
+ 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<CreateNftData> for CreateItemData {
+ fn from(item: CreateNftData) -> Self {
+ CreateItemData::NFT(item)
+ }
+}
+
+impl From<CreateReFungibleData> for CreateItemData {
+ fn from(item: CreateReFungibleData) -> Self {
+ CreateItemData::ReFungible(item)
+ }
+}
+
+impl From<CreateFungibleData> for CreateItemData {
+ fn from(item: CreateFungibleData) -> Self {
+ CreateItemData::Fungible(item)
+ }
+}
\ No newline at end of file
runtime/Cargo.tomldiffbeforeafterboth--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -55,6 +55,12 @@
'pallet-vesting/std',
'parachain-info/std',
'serde',
+ 'pallet-inflation/std',
+ 'pallet-nft/std',
+ 'pallet-scheduler/std',
+ 'pallet-nft-charge-transaction/std',
+ 'pallet-nft-transaction-payment/std',
+ 'nft-data-structs/std',
'sp-api/std',
'sp-block-builder/std',
"sp-consensus-aura/std",
@@ -70,9 +76,6 @@
'xcm/std',
'xcm-builder/std',
'xcm-executor/std',
-
- 'pallet-inflation/std',
- 'pallet-nft/std',
]
################################################################################
@@ -365,6 +368,10 @@
[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' }
+pallet-scheduler = { path = '../pallets/scheduler', default-features = false, version = '3.0.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' }
################################################################################
# Build Dependencies
runtime/src/chain_extension.rsdiffbeforeafterboth--- a/runtime/src/chain_extension.rs
+++ b/runtime/src/chain_extension.rs
@@ -17,6 +17,7 @@
extern crate pallet_nft;
pub use pallet_nft::*;
+use nft_data_structs::*;
/// Create item parameters
#[derive(Debug, PartialEq, Encode, Decode)]
@@ -38,7 +39,7 @@
/// The chain Extension of NFT pallet
pub struct NFTExtension;
-impl<C: Config> ChainExtension<C> for NFTExtension {
+impl<C: Config + pallet_contracts::Config> ChainExtension<C> for NFTExtension {
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
where
E: Ext<T = C>,
runtime/src/lib.rsdiffbeforeafterboth--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -7,7 +7,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
-#![recursion_limit = "256"]
+#![recursion_limit = "1024"]
// Make the WASM binary available.
#[cfg(feature = "std")]
@@ -17,12 +17,14 @@
use sp_core::{ crypto::KeyTypeId, OpaqueMetadata };
// #[cfg(any(feature = "std", test))]
// pub use sp_runtime::BuildStorage;
-use sp_runtime::traits::{
- AccountIdLookup, AccountIdConversion, BlakeTwo256, Block as BlockT, ConvertInto, IdentifyAccount, Verify,
-};
+
use sp_runtime::{
Permill, Perbill, Percent,
create_runtime_str, generic, impl_opaque_keys,
+ traits::{
+ AccountIdLookup, ConvertInto, BlakeTwo256, Block as BlockT, IdentifyAccount,
+ Verify, AccountIdConversion,
+ },
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult, MultiSignature,
};
@@ -51,16 +53,25 @@
WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients
},
};
+use nft_data_structs::*;
use pallet_contracts::weights::WeightInfo;
// #[cfg(any(feature = "std", test))]
use frame_system::{
self as system,
- EnsureRoot,
+ EnsureRoot, EnsureSigned,
limits::{BlockWeights, BlockLength},
};
use sp_arithmetic::{traits::{BaseArithmetic, Unsigned}};
use smallvec::smallvec;
+use sp_runtime::{
+ traits::{
+ Dispatchable,
+ },
+};
+use pallet_contracts::chain_extension::UncheckedFrom;
+
+
pub use pallet_timestamp::Call as TimestampCall;
pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
@@ -193,21 +204,6 @@
}
}
-// impl OnUnbalanced<NegativeImbalance> for DealWithFees {
-// fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item=NegativeImbalance>) {
-// if let Some(fees) = fees_then_tips.next() {
-// // for fees, 100% to treasury
-// let mut split = fees.ration(100, 0);
-// if let Some(tips) = fees_then_tips.next() {
-// // for tips, if any, 100% to treasury
-// tips.ration_merge_into(100, 0, &mut split);
-// }
-// Treasury::on_unbalanced(split.0);
-// // Author::on_unbalanced(split.1);
-// }
-// }
-// }
-
/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers.
/// This is used to limit the maximal weight of a single extrinsic.
const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
@@ -657,6 +653,45 @@
type InflationBlockInterval = InflationBlockInterval;
}
+parameter_types! {
+ pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
+ RuntimeBlockWeights::get().max_block;
+ pub const MaxScheduledPerBlock: u32 = 50;
+}
+
+pub struct Sponsoring;
+impl SponsoringResolve<AccountId, Call> for Sponsoring {
+
+ fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>
+ where
+ Call: Dispatchable<Info=DispatchInfo>,
+ Call: IsSubType<pallet_nft::Call<Runtime>>,
+ Call: IsSubType<pallet_contracts::Call<Runtime>>,
+ AccountId: AsRef<[u8]>,
+ AccountId: UncheckedFrom<Hash>
+ {
+ pallet_nft_transaction_payment::Module::<Runtime>::withdraw_type(who, call)
+ }
+}
+
+impl pallet_scheduler::Config for Runtime {
+ type Event = Event;
+ type Origin = Origin;
+ type PalletsOrigin = OriginCaller;
+ type Call = Call;
+ type MaximumWeight = MaximumSchedulerWeight;
+ type ScheduleOrigin = EnsureSigned<AccountId>;
+ type MaxScheduledPerBlock = MaxScheduledPerBlock;
+ type Sponsoring = Sponsoring;
+ type WeightInfo = ();
+}
+
+impl pallet_nft_transaction_payment::Config for Runtime {
+}
+
+impl pallet_nft_charge_transaction::Config for Runtime {
+}
+
construct_runtime!(
pub enum Runtime where
Block = Block,
@@ -688,7 +723,10 @@
// Unique Pallets
Inflation: pallet_inflation::{Pallet, Call, Storage},
- Nft: pallet_nft::{Pallet, Call, Config<T>, Storage, Event<T>},
+ Nft: pallet_nft::{Pallet, Call, Config<T>, Storage, Event<T>},
+ Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>},
+ NftPayment: pallet_nft_transaction_payment::{Pallet, Call, Storage},
+ Charging: pallet_nft_charge_transaction::{Pallet, Call, Storage },
}
);
@@ -710,7 +748,7 @@
system::CheckEra<Runtime>,
system::CheckNonce<Runtime>,
system::CheckWeight<Runtime>,
- pallet_nft::ChargeTransactionPayment<Runtime>,
+ pallet_nft_charge_transaction::ChargeTransactionPayment<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -13,7 +13,8 @@
// Pallets that must always be present
const requiredPallets = [
- 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting'
+ 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting',
+ 'scheduler', 'nftpayment', 'charging'
];
// Pallets that depend on consensus and governance configuration
tests/src/scheduler.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/scheduler.test.ts
@@ -0,0 +1,94 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ createItemExpectSuccess,
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ findNotExistingCollection,
+ queryCollectionExpectSuccess,
+ setOffchainSchemaExpectFailure,
+ setOffchainSchemaExpectSuccess,
+ transferExpectSuccess,
+ scheduleTransferExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ confirmSponsorshipExpectSuccess
+} from './util/helpers';
+
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const DATA = [1, 2, 3, 4];
+
+describe('Integration Test scheduler base transaction', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ });
+ });
+
+ it('User can transfer owned token with delay (scheduler)', async () => {
+ await usingApi(async (api) => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(nftCollectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(nftCollectionId);
+
+ await scheduleTransferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1, 'NFT');
+ });
+ });
+
+
+});
+
+// describe('Negative Integration Test setOffchainSchema', () => {
+// let alice: IKeyringPair;
+// let bob: IKeyringPair;
+
+// let validCollectionId: number;
+
+// before(async () => {
+// await usingApi(async () => {
+// alice = privateKey('//Alice');
+// bob = privateKey('//Bob');
+
+// validCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// });
+// });
+
+// it('fails on not existing collection id', async () => {
+// const nonExistingCollectionId = await usingApi(findNotExistingCollection);
+
+// await setOffchainSchemaExpectFailure(alice, nonExistingCollectionId, DATA);
+// });
+
+// it('fails on destroyed collection id', async () => {
+// const destroyedCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// await destroyCollectionExpectSuccess(destroyedCollectionId);
+
+// await setOffchainSchemaExpectFailure(alice, destroyedCollectionId, DATA);
+// });
+
+// it('fails on too long data', async () => {
+// const tooLongData = new Array(4097).fill(0xff);
+
+// await setOffchainSchemaExpectFailure(alice, validCollectionId, tooLongData);
+// });
+
+// it('fails on execution by non-owner', async () => {
+// await setOffchainSchemaExpectFailure(bob, validCollectionId, DATA);
+// });
+// });
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -5,7 +5,7 @@
import { ApiPromise, Keyring } from '@polkadot/api';
import { Enum, Struct } from '@polkadot/types/codec';
-import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
+import type { AccountId, BlockNumber, Call, EventRecord } from '@polkadot/types/interfaces';
import { u128 } from '@polkadot/types/primitive';
import { IKeyringPair } from '@polkadot/types/types';
import { BigNumber } from 'bignumber.js';
@@ -17,6 +17,8 @@
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from '../substrate/substrate-api';
import { ICollectionInterface } from '../types';
import { hexToStr, strToUTF16, utf16ToStr } from './util';
+import { Compact, Option, Raw, Vec } from '@polkadot/types/codec';
+// import { AccountId, AccountIdOf, AccountIndex, Address, AssetId, Balance, BalanceOf, Block, BlockNumber, Call, ChangesTrieConfiguration, Consensus, ConsensusEngineId, Digest, DigestItem, DispatchClass, DispatchInfo, DispatchInfoTo190, EcdsaSignature, Ed25519Signature, Extrinsic, ExtrinsicEra, ExtrinsicPayload, ExtrinsicPayloadUnknown, ExtrinsicPayloadV1, ExtrinsicPayloadV2, ExtrinsicPayloadV3, ExtrinsicPayloadV4, ExtrinsicSignatureV1, ExtrinsicSignatureV2, ExtrinsicSignatureV3, ExtrinsicSignatureV4, ExtrinsicUnknown, ExtrinsicV1, ExtrinsicV2, ExtrinsicV3, ExtrinsicV4, Hash, Header, ImmortalEra, Index, Justification, KeyTypeId, KeyValue, LockIdentifier, LookupSource, LookupTarget, Moment, MortalEra, MultiSignature, Origin, Perbill, Percent, Permill, Perquintill, Phantom, PhantomData, PreRuntime, Seal, SealV0, Signature, SignedBlock, SignerPayload, Sr25519Signature, ValidatorId, Weight, WeightMultiplier } from '@polkadot/types/interfaces/runtime';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -702,6 +704,54 @@
});
}
+async function getBlockNumber(api: ApiPromise): Promise<number> {
+ return new Promise<number>(async (resolve, reject) => {
+ const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => {
+ unsubscribe();
+ resolve(head.number.toNumber());
+ });
+ });
+}
+
+export async function
+scheduleTransferExpectSuccess(collectionId: number,
+ tokenId: number,
+ sender: IKeyringPair,
+ recipient: IKeyringPair,
+ value: number | bigint = 1,
+ type: string = 'NFT') {
+ await usingApi(async (api: ApiPromise) => {
+ let balanceBefore = new BN(0);
+
+
+ let blockNumber: number | undefined = await getBlockNumber(api);
+ let expectedBlockNumber = blockNumber + 2;
+
+ expect(blockNumber).to.be.greaterThan(0);
+ const transferTx = await api.tx.nft.transfer(recipient.address, collectionId, tokenId, value);
+ const scheduleTx = await api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx);
+
+ const events = await submitTransactionAsync(sender, scheduleTx);
+
+ const sponsorBalanceBefore = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceBefore = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemDataBefore = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemDataBefore.Owner.toString()).to.be.equal(sender.address);
+
+ // sleep for 2 blocks
+ await new Promise(resolve => setTimeout(resolve, 6000 * 2));
+
+ const sponsorBalanceAfter = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceAfter = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemData = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemData.Owner.toString()).to.be.equal(recipient.address);
+ expect(recipientBalanceAfter.toNumber()).to.be.equal(recipientBalanceBefore.toNumber());
+ });
+}
+
+
export async function
transferExpectSuccess(collectionId: number,
tokenId: number,