difftreelog
Merge branch 'develop' into feature/NFTPAR-373_chain_extensions
in: master
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"
@@ -5331,6 +5395,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-scheduler"
+version = "3.0.0"
source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
dependencies = [
"frame-benchmarking",
@@ -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::*;1516use core::ops::{Deref, DerefMut};17use codec::{Decode, Encode};18pub use frame_support::{19 construct_runtime, decl_event, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24 Randomness, IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue,32 transactional,33};3435use frame_system::{self as system, ensure_signed, ensure_root};36use sp_runtime::sp_std::prelude::Vec;37use sp_runtime::{38 traits::{39 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,40 },41 transaction_validity::{42 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,43 },44 FixedPointOperand, FixedU128,45};46use sp_runtime::traits::StaticLookup;47use pallet_contracts::chain_extension::UncheckedFrom;48use pallet_transaction_payment::OnChargeTransaction;4950#[cfg(test)]51mod mock;5253#[cfg(test)]54mod tests;5556mod default_weights;5758pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;59pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;60pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;61pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6263// Structs64// #region6566pub type CollectionId = u32;67pub type TokenId = u32;68pub type DecimalPoints = u8;6970#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]71#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]72pub enum CollectionMode {73 Invalid,74 NFT,75 // decimal points76 Fungible(DecimalPoints),77 ReFungible,78}7980impl Default for CollectionMode {81 fn default() -> Self {82 Self::Invalid83 }84}8586impl Into<u8> for CollectionMode {87 fn into(self) -> u8 {88 match self {89 CollectionMode::Invalid => 0,90 CollectionMode::NFT => 1,91 CollectionMode::Fungible(_) => 2,92 CollectionMode::ReFungible => 3,93 }94 }95}9697#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]98#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]99pub enum AccessMode {100 Normal,101 WhiteList,102}103impl Default for AccessMode {104 fn default() -> Self {105 Self::Normal106 }107}108109#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]110#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]111pub enum SchemaVersion {112 ImageURL,113 Unique,114}115impl Default for SchemaVersion {116 fn default() -> Self {117 Self::ImageURL118 }119}120121#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]122#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]123pub struct Ownership<AccountId> {124 pub owner: AccountId,125 pub fraction: u128,126}127128#[derive(Encode, Decode, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]130pub enum SponsorshipState<AccountId> {131 /// The fees are applied to the transaction sender132 Disabled,133 Unconfirmed(AccountId),134 /// Transactions are sponsored by specified account135 Confirmed(AccountId),136}137138impl<AccountId> SponsorshipState<AccountId> {139 fn sponsor(&self) -> Option<&AccountId> {140 match self {141 Self::Confirmed(sponsor) => Some(sponsor),142 _ => None,143 }144 }145146 fn pending_sponsor(&self) -> Option<&AccountId> {147 match self {148 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),149 _ => None,150 }151 }152153 fn confirmed(&self) -> bool {154 matches!(self, Self::Confirmed(_))155 }156}157158impl<T> Default for SponsorshipState<T> {159 fn default() -> Self {160 Self::Disabled161 }162}163164#[derive(Encode, Decode, Clone, PartialEq)]165#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]166pub struct Collection<T: Config> {167 pub owner: T::AccountId,168 pub mode: CollectionMode,169 pub access: AccessMode,170 pub decimal_points: DecimalPoints,171 pub name: Vec<u16>, // 64 include null escape char172 pub description: Vec<u16>, // 256 include null escape char173 pub token_prefix: Vec<u8>, // 16 include null escape char174 pub mint_mode: bool,175 pub offchain_schema: Vec<u8>,176 pub schema_version: SchemaVersion,177 pub sponsorship: SponsorshipState<T::AccountId>,178 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 179 pub variable_on_chain_schema: Vec<u8>, //180 pub const_on_chain_schema: Vec<u8>, //181}182183pub struct CollectionHandle<T: Config> {184 pub id: CollectionId,185 collection: Collection<T>,186}187188impl<T: Config> Deref for CollectionHandle<T> {189 type Target = Collection<T>;190191 fn deref(&self) -> &Self::Target {192 &self.collection193 }194}195196impl<T: Config> DerefMut for CollectionHandle<T> {197 fn deref_mut(&mut self) -> &mut Self::Target {198 &mut self.collection199 }200}201202#[derive(Encode, Decode, Debug, Clone, PartialEq)]203#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]204pub struct NftItemType<AccountId> {205 pub owner: AccountId,206 pub const_data: Vec<u8>,207 pub variable_data: Vec<u8>,208}209210#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]211#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]212pub struct FungibleItemType {213 pub value: u128,214}215216#[derive(Encode, Decode, Debug, Clone, PartialEq)]217#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]218pub struct ReFungibleItemType<AccountId> {219 pub owner: Vec<Ownership<AccountId>>,220 pub const_data: Vec<u8>,221 pub variable_data: Vec<u8>,222}223224// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]225// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]226// pub struct VestingItem<AccountId, Moment> {227// pub sender: AccountId,228// pub recipient: AccountId,229// pub collection_id: CollectionId,230// pub item_id: TokenId,231// pub amount: u64,232// pub vesting_date: Moment,233// }234235#[derive(Encode, Decode, Debug, Clone, PartialEq)]236#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]237pub struct CollectionLimits<BlockNumber: Encode + Decode> {238 pub account_token_ownership_limit: u32,239 pub sponsored_data_size: u32,240 /// None - setVariableMetadata is not sponsored241 /// Some(v) - setVariableMetadata is sponsored 242 /// if there is v block between txs243 pub sponsored_data_rate_limit: Option<BlockNumber>,244 pub token_limit: u32,245246 // Timeouts for item types in passed blocks247 pub sponsor_transfer_timeout: u32,248 pub owner_can_transfer: bool,249 pub owner_can_destroy: bool,250}251252impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {253 fn default() -> Self {254 Self { 255 account_token_ownership_limit: 10_000_000, 256 token_limit: u32::max_value(),257 sponsored_data_size: u32::MAX, 258 sponsored_data_rate_limit: None,259 sponsor_transfer_timeout: 14400,260 owner_can_transfer: true,261 owner_can_destroy: true262 }263 }264}265266#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]267#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]268pub struct ChainLimits {269 pub collection_numbers_limit: u32,270 pub account_token_ownership_limit: u32,271 pub collections_admins_limit: u64,272 pub custom_data_limit: u32,273274 // Timeouts for item types in passed blocks275 pub nft_sponsor_transfer_timeout: u32,276 pub fungible_sponsor_transfer_timeout: u32,277 pub refungible_sponsor_transfer_timeout: u32,278279 // Schema limits280 pub offchain_schema_limit: u32,281 pub variable_on_chain_schema_limit: u32,282 pub const_on_chain_schema_limit: u32,283}284285pub trait WeightInfo {286 fn create_collection() -> Weight;287 fn destroy_collection() -> Weight;288 fn add_to_white_list() -> Weight;289 fn remove_from_white_list() -> Weight;290 fn set_public_access_mode() -> Weight;291 fn set_mint_permission() -> Weight;292 fn change_collection_owner() -> Weight;293 fn add_collection_admin() -> Weight;294 fn remove_collection_admin() -> Weight;295 fn set_collection_sponsor() -> Weight;296 fn confirm_sponsorship() -> Weight;297 fn remove_collection_sponsor() -> Weight;298 fn create_item(s: usize) -> Weight;299 fn burn_item() -> Weight;300 fn transfer() -> Weight;301 fn approve() -> Weight;302 fn transfer_from() -> Weight;303 fn set_offchain_schema() -> Weight;304 fn set_const_on_chain_schema() -> Weight;305 fn set_variable_on_chain_schema() -> Weight;306 fn set_variable_meta_data() -> Weight;307 fn enable_contract_sponsoring() -> Weight;308 fn set_schema_version() -> Weight;309 fn set_chain_limits() -> Weight;310 fn set_contract_sponsoring_rate_limit() -> Weight;311 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;312 fn toggle_contract_white_list() -> Weight;313 fn add_to_contract_white_list() -> Weight;314 fn remove_from_contract_white_list() -> Weight;315 fn set_collection_limits() -> Weight;316}317318#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]319#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]320pub struct CreateNftData {321 pub const_data: Vec<u8>,322 pub variable_data: Vec<u8>,323}324325#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]326#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]327pub struct CreateFungibleData {328 pub value: u128,329}330331#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]332#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]333pub struct CreateReFungibleData {334 pub const_data: Vec<u8>,335 pub variable_data: Vec<u8>,336 pub pieces: u128,337}338339#[derive(Encode, Decode, Debug, Clone, PartialEq)]340#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]341pub enum CreateItemData {342 NFT(CreateNftData),343 Fungible(CreateFungibleData),344 ReFungible(CreateReFungibleData),345}346347impl CreateItemData {348 pub fn len(&self) -> usize {349 let len = match self {350 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),351 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),352 _ => 0353 };354 355 return len;356 }357}358359impl From<CreateNftData> for CreateItemData {360 fn from(item: CreateNftData) -> Self {361 CreateItemData::NFT(item)362 }363}364365impl From<CreateReFungibleData> for CreateItemData {366 fn from(item: CreateReFungibleData) -> Self {367 CreateItemData::ReFungible(item)368 }369}370371impl From<CreateFungibleData> for CreateItemData {372 fn from(item: CreateFungibleData) -> Self {373 CreateItemData::Fungible(item)374 }375}376377378decl_error! {379 /// Error for non-fungible-token module.380 pub enum Error for Module<T: Config> {381 /// Total collections bound exceeded.382 TotalCollectionsLimitExceeded,383 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.384 CollectionDecimalPointLimitExceeded, 385 /// Collection name can not be longer than 63 char.386 CollectionNameLimitExceeded, 387 /// Collection description can not be longer than 255 char.388 CollectionDescriptionLimitExceeded, 389 /// Token prefix can not be longer than 15 char.390 CollectionTokenPrefixLimitExceeded,391 /// This collection does not exist.392 CollectionNotFound,393 /// Item not exists.394 TokenNotFound,395 /// Admin not found396 AdminNotFound,397 /// Arithmetic calculation overflow.398 NumOverflow, 399 /// Account already has admin role.400 AlreadyAdmin, 401 /// You do not own this collection.402 NoPermission,403 /// This address is not set as sponsor, use setCollectionSponsor first.404 ConfirmUnsetSponsorFail,405 /// Collection is not in mint mode.406 PublicMintingNotAllowed,407 /// Sender parameter and item owner must be equal.408 MustBeTokenOwner,409 /// Item balance not enough.410 TokenValueTooLow,411 /// Size of item is too large.412 NftSizeLimitExceeded,413 /// No approve found414 ApproveNotFound,415 /// Requested value more than approved.416 TokenValueNotEnough,417 /// Only approved addresses can call this method.418 ApproveRequired,419 /// Address is not in white list.420 AddresNotInWhiteList,421 /// Number of collection admins bound exceeded.422 CollectionAdminsLimitExceeded,423 /// Owned tokens by a single address bound exceeded.424 AddressOwnershipLimitExceeded,425 /// Length of items properties must be greater than 0.426 EmptyArgument,427 /// const_data exceeded data limit.428 TokenConstDataLimitExceeded,429 /// variable_data exceeded data limit.430 TokenVariableDataLimitExceeded,431 /// Not NFT item data used to mint in NFT collection.432 NotNftDataUsedToMintNftCollectionToken,433 /// Not Fungible item data used to mint in Fungible collection.434 NotFungibleDataUsedToMintFungibleCollectionToken,435 /// Not Re Fungible item data used to mint in Re Fungible collection.436 NotReFungibleDataUsedToMintReFungibleCollectionToken,437 /// Unexpected collection type.438 UnexpectedCollectionType,439 /// Can't store metadata in fungible tokens.440 CantStoreMetadataInFungibleTokens,441 /// Collection token limit exceeded442 CollectionTokenLimitExceeded,443 /// Account token limit exceeded per collection444 AccountTokenLimitExceeded,445 /// Collection limit bounds per collection exceeded446 CollectionLimitBoundsExceeded,447 /// Tried to enable permissions which are only permitted to be disabled448 OwnerPermissionsCantBeReverted,449 /// Schema data size limit bound exceeded450 SchemaDataLimitExceeded,451 /// Maximum refungibility exceeded452 WrongRefungiblePieces,453 /// createRefungible should be called with one owner454 BadCreateRefungibleCall,455 }456}457458pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {459 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;460461 /// Weight information for extrinsics in this pallet.462 type WeightInfo: WeightInfo;463464 type Currency: Currency<Self::AccountId>;465 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;466 type TreasuryAccountId: Get<Self::AccountId>;467}468469#[cfg(feature = "runtime-benchmarks")]470mod benchmarking;471472// #endregion473474// # Used definitions475//476// ## User control levels477//478// chain-controlled - key is uncontrolled by user479// i.e autoincrementing index480// can use non-cryptographic hash481// real - key is controlled by user482// but it is hard to generate enough colliding values, i.e owner of signed txs483// can use non-cryptographic hash484// controlled - key is completly controlled by users485// i.e maps with mutable keys486// should use cryptographic hash487//488// ## User control level downgrade reasons489//490// ?1 - chain-controlled -> controlled491// collections/tokens can be destroyed, resulting in massive holes492// ?2 - chain-controlled -> controlled493// same as ?1, but can be only added, resulting in easier exploitation494// ?3 - real -> controlled495// no confirmation required, so addresses can be easily generated496decl_storage! {497 trait Store for Module<T: Config> as Nft {498499 //#region Private members500 /// Id of next collection501 CreatedCollectionCount: u32;502 /// Used for migrations503 ChainVersion: u64;504 /// Id of last collection token505 /// Collection id (controlled?1)506 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;507 //#endregion508509 //#region Chain limits struct510 pub ChainLimit get(fn chain_limit) config(): ChainLimits;511 //#endregion512513 //#region Bound counters514 /// Amount of collections destroyed, used for total amount tracking with515 /// CreatedCollectionCount516 DestroyedCollectionCount: u32;517 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)518 /// Account id (real)519 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;520 //#endregion521522 //#region Basic collections523 /// Collection info524 /// Collection id (controlled?1)525 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;526 /// List of collection admins527 /// Collection id (controlled?2)528 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;529 /// Whitelisted collection users530 /// Collection id (controlled?2), user id (controlled?3)531 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;532 //#endregion533534 /// How many of collection items user have535 /// Collection id (controlled?2), account id (real)536 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;537538 /// Amount of items which spender can transfer out of owners account (via transferFrom)539 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))540 /// TODO: Off chain worker should remove from this map when token gets removed541 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;542543 //#region Item collections544 /// Collection id (controlled?2), token id (controlled?1)545 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::AccountId>>;546 /// Collection id (controlled?2), owner (controlled?2)547 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;548 /// Collection id (controlled?2), token id (controlled?1)549 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::AccountId>>;550 //#endregion551552 //#region Index list553 /// Collection id (controlled?2), tokens owner (controlled?2)554 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;555 //#endregion556557 //#region Tokens transfer rate limit baskets558 /// (Collection id (controlled?2), who created (real))559 /// TODO: Off chain worker should remove from this map when collection gets removed560 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;561 /// Collection id (controlled?2), token id (controlled?2)562 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;563 /// Collection id (controlled?2), owning user (real)564 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;565 /// Collection id (controlled?2), token id (controlled?2)566 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;567 //#endregion568569 /// Variable metadata sponsoring570 /// Collection id (controlled?2), token id (controlled?2)571 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;572 573 //#region Contract Sponsorship and Ownership574 /// Contract address (real)575 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;576 /// Contract address (real)577 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;578 /// (Contract address(real), caller (real))579 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;580 /// Contract address (real)581 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;582 /// Contract address (real)583 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 584 /// Contract address (real) => Whitelisted user (controlled?3)585 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 586 //#endregion587 }588 add_extra_genesis {589 build(|config: &GenesisConfig<T>| {590 // Modification of storage591 for (_num, _c) in &config.collection_id {592 <Module<T>>::init_collection(_c);593 }594595 for (_num, _c, _i) in &config.nft_item_id {596 <Module<T>>::init_nft_token(*_c, _i);597 }598599 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {600 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);601 }602603 for (_num, _c, _i) in &config.refungible_item_id {604 <Module<T>>::init_refungible_token(*_c, _i);605 }606 })607 }608}609610decl_event!(611 pub enum Event<T>612 where613 AccountId = <T as system::Config>::AccountId,614 {615 /// New collection was created616 /// 617 /// # Arguments618 /// 619 /// * collection_id: Globally unique identifier of newly created collection.620 /// 621 /// * mode: [CollectionMode] converted into u8.622 /// 623 /// * account_id: Collection owner.624 CollectionCreated(CollectionId, u8, AccountId),625626 /// New item was created.627 /// 628 /// # Arguments629 /// 630 /// * collection_id: Id of the collection where item was created.631 /// 632 /// * item_id: Id of an item. Unique within the collection.633 ///634 /// * recipient: Owner of newly created item 635 ItemCreated(CollectionId, TokenId, AccountId),636637 /// Collection item was burned.638 /// 639 /// # Arguments640 /// 641 /// collection_id.642 /// 643 /// item_id: Identifier of burned NFT.644 ItemDestroyed(CollectionId, TokenId),645646 /// Item was transferred647 ///648 /// * collection_id: Id of collection to which item is belong649 ///650 /// * item_id: Id of an item651 ///652 /// * sender: Original owner of item653 ///654 /// * recipient: New owner of item655 ///656 /// * amount: Always 1 for NFT657 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),658659 /// * collection_id660 ///661 /// * item_id662 ///663 /// * sender664 ///665 /// * spender666 ///667 /// * amount668 Approved(CollectionId, TokenId, AccountId, AccountId, u128),669 }670);671672decl_module! {673 pub struct Module<T: Config> for enum Call 674 where 675 origin: T::Origin676 {677 fn deposit_event() = default;678 type Error = Error<T>;679680 fn on_initialize(_now: T::BlockNumber) -> Weight {681 0682 }683684 /// 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.685 /// 686 /// # Permissions687 /// 688 /// * Anyone.689 /// 690 /// # Arguments691 /// 692 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.693 /// 694 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.695 /// 696 /// * token_prefix: UTF-8 string with token prefix.697 /// 698 /// * mode: [CollectionMode] collection type and type dependent data.699 // returns collection ID700 #[weight = <T as Config>::WeightInfo::create_collection()]701 #[transactional]702 pub fn create_collection(origin,703 collection_name: Vec<u16>,704 collection_description: Vec<u16>,705 token_prefix: Vec<u8>,706 mode: CollectionMode) -> DispatchResult {707708 // Anyone can create a collection709 let who = ensure_signed(origin)?;710711 // Take a (non-refundable) deposit of collection creation712 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();713 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(714 &T::TreasuryAccountId::get(),715 T::CollectionCreationPrice::get(),716 ));717 <T as Config>::Currency::settle(718 &who,719 imbalance,720 WithdrawReasons::TRANSFER,721 ExistenceRequirement::KeepAlive,722 ).map_err(|_| Error::<T>::NoPermission)?;723724 let decimal_points = match mode {725 CollectionMode::Fungible(points) => points,726 _ => 0727 };728729 let chain_limit = ChainLimit::get();730731 let created_count = CreatedCollectionCount::get();732 let destroyed_count = DestroyedCollectionCount::get();733734 // bound Total number of collections735 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);736737 // check params738 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);739 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);740 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);741 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);742743 // Generate next collection ID744 let next_id = created_count745 .checked_add(1)746 .ok_or(Error::<T>::NumOverflow)?;747748 CreatedCollectionCount::put(next_id);749750 let limits = CollectionLimits {751 sponsored_data_size: chain_limit.custom_data_limit,752 ..Default::default()753 };754755 // Create new collection756 let new_collection = Collection {757 owner: who.clone(),758 name: collection_name,759 mode: mode.clone(),760 mint_mode: false,761 access: AccessMode::Normal,762 description: collection_description,763 decimal_points: decimal_points,764 token_prefix: token_prefix,765 offchain_schema: Vec::new(),766 schema_version: SchemaVersion::ImageURL,767 sponsorship: SponsorshipState::Disabled,768 variable_on_chain_schema: Vec::new(),769 const_on_chain_schema: Vec::new(),770 limits,771 };772773 // Add new collection to map774 <CollectionById<T>>::insert(next_id, new_collection);775776 // call event777 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who.clone()));778779 Ok(())780 }781782 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.783 /// 784 /// # Permissions785 /// 786 /// * Collection Owner.787 /// 788 /// # Arguments789 /// 790 /// * collection_id: collection to destroy.791 #[weight = <T as Config>::WeightInfo::destroy_collection()]792 #[transactional]793 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {794795 let sender = ensure_signed(origin)?;796 let collection = Self::get_collection(collection_id)?;797 Self::check_owner_permissions(&collection, sender)?;798 if !collection.limits.owner_can_destroy {799 fail!(Error::<T>::NoPermission);800 }801802 <AddressTokens<T>>::remove_prefix(collection_id);803 <Allowances<T>>::remove_prefix(collection_id);804 <Balance<T>>::remove_prefix(collection_id);805 <ItemListIndex>::remove(collection_id);806 <AdminList<T>>::remove(collection_id);807 <CollectionById<T>>::remove(collection_id);808 <WhiteList<T>>::remove_prefix(collection_id);809810 <NftItemList<T>>::remove_prefix(collection_id);811 <FungibleItemList<T>>::remove_prefix(collection_id);812 <ReFungibleItemList<T>>::remove_prefix(collection_id);813814 <NftTransferBasket<T>>::remove_prefix(collection_id);815 <FungibleTransferBasket<T>>::remove_prefix(collection_id);816 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);817818 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);819820 DestroyedCollectionCount::put(DestroyedCollectionCount::get()821 .checked_add(1)822 .ok_or(Error::<T>::NumOverflow)?);823824 Ok(())825 }826827 /// Add an address to white list.828 /// 829 /// # Permissions830 /// 831 /// * Collection Owner832 /// * Collection Admin833 /// 834 /// # Arguments835 /// 836 /// * collection_id.837 /// 838 /// * address.839 #[weight = <T as Config>::WeightInfo::add_to_white_list()]840 #[transactional]841 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{842843 let sender = ensure_signed(origin)?;844 let collection = Self::get_collection(collection_id)?;845846 Self::toggle_white_list_internal(847 &sender,848 &collection,849 &address,850 true,851 )?;852853 Ok(())854 }855856 /// Remove an address from white list.857 /// 858 /// # Permissions859 /// 860 /// * Collection Owner861 /// * Collection Admin862 /// 863 /// # Arguments864 /// 865 /// * collection_id.866 /// 867 /// * address.868 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]869 #[transactional]870 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{871872 let sender = ensure_signed(origin)?;873 let collection = Self::get_collection(collection_id)?;874875 Self::toggle_white_list_internal(876 &sender,877 &collection,878 &address,879 false,880 )?;881882 Ok(())883 }884885 /// Toggle between normal and white list access for the methods with access for `Anyone`.886 /// 887 /// # Permissions888 /// 889 /// * Collection Owner.890 /// 891 /// # Arguments892 /// 893 /// * collection_id.894 /// 895 /// * mode: [AccessMode]896 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]897 #[transactional]898 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult899 {900 let sender = ensure_signed(origin)?;901902 let mut target_collection = Self::get_collection(collection_id)?;903 Self::check_owner_permissions(&target_collection, sender)?;904 target_collection.access = mode;905 Self::save_collection(target_collection);906907 Ok(())908 }909910 /// Allows Anyone to create tokens if:911 /// * White List is enabled, and912 /// * Address is added to white list, and913 /// * This method was called with True parameter914 /// 915 /// # Permissions916 /// * Collection Owner917 ///918 /// # Arguments919 /// 920 /// * collection_id.921 /// 922 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.923 #[weight = <T as Config>::WeightInfo::set_mint_permission()]924 #[transactional]925 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult926 {927 let sender = ensure_signed(origin)?;928929 let mut target_collection = Self::get_collection(collection_id)?;930 Self::check_owner_permissions(&target_collection, sender)?;931 target_collection.mint_mode = mint_permission;932 Self::save_collection(target_collection);933934 Ok(())935 }936937 /// Change the owner of the collection.938 /// 939 /// # Permissions940 /// 941 /// * Collection Owner.942 /// 943 /// # Arguments944 /// 945 /// * collection_id.946 /// 947 /// * new_owner.948 #[weight = <T as Config>::WeightInfo::change_collection_owner()]949 #[transactional]950 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {951952 let sender = ensure_signed(origin)?;953 let mut target_collection = Self::get_collection(collection_id)?;954 Self::check_owner_permissions(&target_collection, sender)?;955 target_collection.owner = new_owner;956 Self::save_collection(target_collection);957958 Ok(())959 }960961 /// Adds an admin of the Collection.962 /// 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. 963 /// 964 /// # Permissions965 /// 966 /// * Collection Owner.967 /// * Collection Admin.968 /// 969 /// # Arguments970 /// 971 /// * collection_id: ID of the Collection to add admin for.972 /// 973 /// * new_admin_id: Address of new admin to add.974 #[weight = <T as Config>::WeightInfo::add_collection_admin()]975 #[transactional]976 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {977978 let sender = ensure_signed(origin)?;979 let collection = Self::get_collection(collection_id)?;980 Self::check_owner_or_admin_permissions(&collection, sender)?;981 let mut admin_arr = <AdminList<T>>::get(collection_id);982983 match admin_arr.binary_search(&new_admin_id) {984 Ok(_) => {},985 Err(idx) => {986 let limits = ChainLimit::get();987 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);988 admin_arr.insert(idx, new_admin_id);989 <AdminList<T>>::insert(collection_id, admin_arr);990 }991 }992 Ok(())993 }994995 /// 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.996 ///997 /// # Permissions998 /// 999 /// * Collection Owner.1000 /// * Collection Admin.1001 /// 1002 /// # Arguments1003 /// 1004 /// * collection_id: ID of the Collection to remove admin for.1005 /// 1006 /// * account_id: Address of admin to remove.1007 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1008 #[transactional]1009 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {10101011 let sender = ensure_signed(origin)?;1012 let collection = Self::get_collection(collection_id)?;1013 Self::check_owner_or_admin_permissions(&collection, sender)?;1014 let mut admin_arr = <AdminList<T>>::get(collection_id);10151016 match admin_arr.binary_search(&account_id) {1017 Ok(idx) => {1018 admin_arr.remove(idx);1019 <AdminList<T>>::insert(collection_id, admin_arr);1020 },1021 Err(_) => {}1022 }1023 Ok(())1024 }10251026 /// # Permissions1027 /// 1028 /// * Collection Owner1029 /// 1030 /// # Arguments1031 /// 1032 /// * collection_id.1033 /// 1034 /// * new_sponsor.1035 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1036 #[transactional]1037 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {10381039 let sender = ensure_signed(origin)?;1040 let mut target_collection = Self::get_collection(collection_id)?;1041 Self::check_owner_permissions(&target_collection, sender)?;10421043 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1044 Self::save_collection(target_collection);10451046 Ok(())1047 }10481049 /// # Permissions1050 /// 1051 /// * Sponsor.1052 /// 1053 /// # Arguments1054 /// 1055 /// * collection_id.1056 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1057 #[transactional]1058 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {10591060 let sender = ensure_signed(origin)?;10611062 let mut target_collection = Self::get_collection(collection_id)?;1063 ensure!(1064 target_collection.sponsorship.pending_sponsor() == Some(&sender),1065 Error::<T>::ConfirmUnsetSponsorFail1066 );10671068 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1069 Self::save_collection(target_collection);10701071 Ok(())1072 }10731074 /// Switch back to pay-per-own-transaction model.1075 ///1076 /// # Permissions1077 ///1078 /// * Collection owner.1079 /// 1080 /// # Arguments1081 /// 1082 /// * collection_id.1083 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1084 #[transactional]1085 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {10861087 let sender = ensure_signed(origin)?;10881089 let mut target_collection = Self::get_collection(collection_id)?;1090 Self::check_owner_permissions(&target_collection, sender)?;10911092 target_collection.sponsorship = SponsorshipState::Disabled;1093 Self::save_collection(target_collection);10941095 Ok(())1096 }10971098 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1099 /// 1100 /// # Permissions1101 /// 1102 /// * Collection Owner.1103 /// * Collection Admin.1104 /// * Anyone if1105 /// * White List is enabled, and1106 /// * Address is added to white list, and1107 /// * MintPermission is enabled (see SetMintPermission method)1108 /// 1109 /// # Arguments1110 /// 1111 /// * collection_id: ID of the collection.1112 /// 1113 /// * owner: Address, initial owner of the NFT.1114 ///1115 /// * data: Token data to store on chain.1116 // #[weight =1117 // (130_000_000 as Weight)1118 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1119 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1120 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11211122 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1123 #[transactional]1124 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1125 let sender = ensure_signed(origin)?;1126 Self::create_item_internal(sender, collection_id, owner, data)1127 }11281129 /// This method creates multiple items in a collection created with CreateCollection method.1130 /// 1131 /// # Permissions1132 /// 1133 /// * Collection Owner.1134 /// * Collection Admin.1135 /// * Anyone if1136 /// * White List is enabled, and1137 /// * Address is added to white list, and1138 /// * MintPermission is enabled (see SetMintPermission method)1139 /// 1140 /// # Arguments1141 /// 1142 /// * collection_id: ID of the collection.1143 /// 1144 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1145 /// 1146 /// * owner: Address, initial owner of the NFT.1147 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1148 .map(|data| { data.len() })1149 .sum())]1150 #[transactional]1151 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11521153 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1154 let sender = ensure_signed(origin)?;11551156 let target_collection = Self::get_collection(collection_id)?;11571158 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;11591160 for data in &items_data {1161 Self::validate_create_item_args(&target_collection, data)?;1162 }1163 for data in &items_data {1164 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;1165 }11661167 Ok(())1168 }11691170 /// Destroys a concrete instance of NFT.1171 /// 1172 /// # Permissions1173 /// 1174 /// * Collection Owner.1175 /// * Collection Admin.1176 /// * Current NFT Owner.1177 /// 1178 /// # Arguments1179 /// 1180 /// * collection_id: ID of the collection.1181 /// 1182 /// * item_id: ID of NFT to burn.1183 #[weight = <T as Config>::WeightInfo::burn_item()]1184 #[transactional]1185 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {11861187 let sender = ensure_signed(origin)?;11881189 // Transfer permissions check1190 let target_collection = Self::get_collection(collection_id)?;1191 ensure!(1192 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||1193 (1194 target_collection.limits.owner_can_transfer &&1195 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())1196 ),1197 Error::<T>::NoPermission1198 );11991200 if target_collection.access == AccessMode::WhiteList {1201 Self::check_white_list(&target_collection, &sender)?;1202 }12031204 match target_collection.mode1205 {1206 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,1207 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,1208 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,1209 _ => ()1210 };12111212 // call event1213 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));12141215 Ok(())1216 }12171218 /// Change ownership of the token.1219 /// 1220 /// # Permissions1221 /// 1222 /// * Collection Owner1223 /// * Collection Admin1224 /// * Current NFT owner1225 ///1226 /// # Arguments1227 /// 1228 /// * recipient: Address of token recipient.1229 /// 1230 /// * collection_id.1231 /// 1232 /// * item_id: ID of the item1233 /// * Non-Fungible Mode: Required.1234 /// * Fungible Mode: Ignored.1235 /// * Re-Fungible Mode: Required.1236 /// 1237 /// * value: Amount to transfer.1238 /// * Non-Fungible Mode: Ignored1239 /// * Fungible Mode: Must specify transferred amount1240 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1241 #[weight = <T as Config>::WeightInfo::transfer()]1242 #[transactional]1243 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1244 let sender = ensure_signed(origin)?;1245 let collection = Self::get_collection(collection_id)?;12461247 Self::transfer_internal(sender, recipient, &collection, item_id, value)1248 }12491250 /// Set, change, or remove approved address to transfer the ownership of the NFT.1251 /// 1252 /// # Permissions1253 /// 1254 /// * Collection Owner1255 /// * Collection Admin1256 /// * Current NFT owner1257 /// 1258 /// # Arguments1259 /// 1260 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1261 /// 1262 /// * collection_id.1263 /// 1264 /// * item_id: ID of the item.1265 #[weight = <T as Config>::WeightInfo::approve()]1266 #[transactional]1267 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {12681269 let sender = ensure_signed(origin)?;1270 let collection = Self::get_collection(collection_id)?;12711272 Self::approve_internal(sender, spender, &collection, item_id, amount)?;12731274 Ok(())1275 }1276 1277 /// 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.1278 /// 1279 /// # Permissions1280 /// * Collection Owner1281 /// * Collection Admin1282 /// * Current NFT owner1283 /// * Address approved by current NFT owner1284 /// 1285 /// # Arguments1286 /// 1287 /// * from: Address that owns token.1288 /// 1289 /// * recipient: Address of token recipient.1290 /// 1291 /// * collection_id.1292 /// 1293 /// * item_id: ID of the item.1294 /// 1295 /// * value: Amount to transfer.1296 #[weight = <T as Config>::WeightInfo::transfer_from()]1297 #[transactional]1298 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12991300 let sender = ensure_signed(origin)?;1301 let collection = Self::get_collection(collection_id)?;13021303 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;13041305 Ok(())1306 }13071308 // #[weight = 0]1309 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {13101311 // // let no_perm_mes = "You do not have permissions to modify this collection";1312 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1313 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1314 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13151316 // // // on_nft_received call13171318 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13191320 // Ok(())1321 // }13221323 /// Set off-chain data schema.1324 /// 1325 /// # Permissions1326 /// 1327 /// * Collection Owner1328 /// * Collection Admin1329 /// 1330 /// # Arguments1331 /// 1332 /// * collection_id.1333 /// 1334 /// * schema: String representing the offchain data schema.1335 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1336 #[transactional]1337 pub fn set_variable_meta_data (1338 origin,1339 collection_id: CollectionId,1340 item_id: TokenId,1341 data: Vec<u8>1342 ) -> DispatchResult {1343 let sender = ensure_signed(origin)?;1344 1345 let collection = Self::get_collection(collection_id)?;13461347 Self::set_variable_meta_data_internal(sender, &collection, item_id, data)?;13481349 Ok(())1350 }1351 1352 /// Set schema standard1353 /// ImageURL1354 /// Unique1355 /// 1356 /// # Permissions1357 /// 1358 /// * Collection Owner1359 /// * Collection Admin1360 /// 1361 /// # Arguments1362 /// 1363 /// * collection_id.1364 /// 1365 /// * schema: SchemaVersion: enum1366 #[weight = <T as Config>::WeightInfo::set_schema_version()]1367 #[transactional]1368 pub fn set_schema_version(1369 origin,1370 collection_id: CollectionId,1371 version: SchemaVersion1372 ) -> DispatchResult {1373 let sender = ensure_signed(origin)?;1374 let mut target_collection = Self::get_collection(collection_id)?;1375 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1376 target_collection.schema_version = version;1377 Self::save_collection(target_collection);13781379 Ok(())1380 }13811382 /// Set off-chain data schema.1383 /// 1384 /// # Permissions1385 /// 1386 /// * Collection Owner1387 /// * Collection Admin1388 /// 1389 /// # Arguments1390 /// 1391 /// * collection_id.1392 /// 1393 /// * schema: String representing the offchain data schema.1394 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1395 #[transactional]1396 pub fn set_offchain_schema(1397 origin,1398 collection_id: CollectionId,1399 schema: Vec<u8>1400 ) -> DispatchResult {1401 let sender = ensure_signed(origin)?;1402 let mut target_collection = Self::get_collection(collection_id)?;1403 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;14041405 // check schema limit1406 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14071408 target_collection.offchain_schema = schema;1409 Self::save_collection(target_collection);14101411 Ok(())1412 }14131414 /// Set const on-chain data schema.1415 /// 1416 /// # Permissions1417 /// 1418 /// * Collection Owner1419 /// * Collection Admin1420 /// 1421 /// # Arguments1422 /// 1423 /// * collection_id.1424 /// 1425 /// * schema: String representing the const on-chain data schema.1426 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1427 #[transactional]1428 pub fn set_const_on_chain_schema (1429 origin,1430 collection_id: CollectionId,1431 schema: Vec<u8>1432 ) -> DispatchResult {1433 let sender = ensure_signed(origin)?;1434 let mut target_collection = Self::get_collection(collection_id)?;1435 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;14361437 // check schema limit1438 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14391440 target_collection.const_on_chain_schema = schema;1441 Self::save_collection(target_collection);14421443 Ok(())1444 }14451446 /// Set variable on-chain data schema.1447 /// 1448 /// # Permissions1449 /// 1450 /// * Collection Owner1451 /// * Collection Admin1452 /// 1453 /// # Arguments1454 /// 1455 /// * collection_id.1456 /// 1457 /// * schema: String representing the variable on-chain data schema.1458 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1459 #[transactional]1460 pub fn set_variable_on_chain_schema (1461 origin,1462 collection_id: CollectionId,1463 schema: Vec<u8>1464 ) -> DispatchResult {1465 let sender = ensure_signed(origin)?;1466 let mut target_collection = Self::get_collection(collection_id)?;1467 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;14681469 // check schema limit1470 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14711472 target_collection.variable_on_chain_schema = schema;1473 Self::save_collection(target_collection);14741475 Ok(())1476 }14771478 // Sudo permissions function1479 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1480 #[transactional]1481 pub fn set_chain_limits(1482 origin,1483 limits: ChainLimits1484 ) -> DispatchResult {14851486 #[cfg(not(feature = "runtime-benchmarks"))]1487 ensure_root(origin)?;14881489 <ChainLimit>::put(limits);1490 Ok(())1491 }14921493 /// Enable smart contract self-sponsoring.1494 /// 1495 /// # Permissions1496 /// 1497 /// * Contract Owner1498 /// 1499 /// # Arguments1500 /// 1501 /// * contract address1502 /// * enable flag1503 /// 1504 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1505 #[transactional]1506 pub fn enable_contract_sponsoring(1507 origin,1508 contract_address: T::AccountId,1509 enable: bool1510 ) -> DispatchResult {15111512 let sender = ensure_signed(origin)?;15131514 #[cfg(feature = "runtime-benchmarks")]1515 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15161517 Self::ensure_contract_owned(sender, &contract_address)?;15181519 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1520 Ok(())1521 }15221523 /// Set the rate limit for contract sponsoring to specified number of blocks.1524 /// 1525 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1526 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1527 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1528 /// from contract endowment if there are at least B blocks between such transactions. 1529 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1530 /// 1531 /// # Permissions1532 /// 1533 /// * Contract Owner1534 /// 1535 /// # Arguments1536 /// 1537 /// -`contract_address`: Address of the contract to sponsor1538 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1539 /// 1540 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1541 #[transactional]1542 pub fn set_contract_sponsoring_rate_limit(1543 origin,1544 contract_address: T::AccountId,1545 rate_limit: T::BlockNumber1546 ) -> DispatchResult {1547 let sender = ensure_signed(origin)?;15481549 #[cfg(feature = "runtime-benchmarks")]1550 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15511552 Self::ensure_contract_owned(sender, &contract_address)?;1553 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1554 Ok(())1555 }15561557 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1558 /// 1559 /// # Permissions1560 /// 1561 /// * Address that deployed smart contract.1562 /// 1563 /// # Arguments1564 /// 1565 /// -`contract_address`: Address of the contract.1566 /// 1567 /// - `enable`: . 1568 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1569 #[transactional]1570 pub fn toggle_contract_white_list(1571 origin,1572 contract_address: T::AccountId,1573 enable: bool1574 ) -> DispatchResult {1575 let sender = ensure_signed(origin)?;15761577 #[cfg(feature = "runtime-benchmarks")]1578 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15791580 Self::ensure_contract_owned(sender, &contract_address)?;1581 if enable {1582 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1583 } else {1584 <ContractWhiteListEnabled<T>>::remove(contract_address);1585 }1586 Ok(())1587 }1588 1589 /// Add an address to smart contract white list.1590 /// 1591 /// # Permissions1592 /// 1593 /// * Address that deployed smart contract.1594 /// 1595 /// # Arguments1596 /// 1597 /// -`contract_address`: Address of the contract.1598 ///1599 /// -`account_address`: Address to add.1600 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1601 #[transactional]1602 pub fn add_to_contract_white_list(1603 origin,1604 contract_address: T::AccountId,1605 account_address: T::AccountId1606 ) -> DispatchResult {1607 let sender = ensure_signed(origin)?;16081609 #[cfg(feature = "runtime-benchmarks")]1610 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1611 1612 Self::ensure_contract_owned(sender, &contract_address)?; 1613 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1614 Ok(())1615 }16161617 /// Remove an address from smart contract white list.1618 /// 1619 /// # Permissions1620 /// 1621 /// * Address that deployed smart contract.1622 /// 1623 /// # Arguments1624 /// 1625 /// -`contract_address`: Address of the contract.1626 ///1627 /// -`account_address`: Address to remove.1628 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1629 #[transactional]1630 pub fn remove_from_contract_white_list(1631 origin,1632 contract_address: T::AccountId,1633 account_address: T::AccountId1634 ) -> DispatchResult {1635 let sender = ensure_signed(origin)?;16361637 #[cfg(feature = "runtime-benchmarks")]1638 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16391640 Self::ensure_contract_owned(sender, &contract_address)?;1641 <ContractWhiteList<T>>::remove(contract_address, account_address);1642 Ok(())1643 }16441645 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1646 #[transactional]1647 pub fn set_collection_limits(1648 origin,1649 collection_id: u32,1650 new_limits: CollectionLimits<T::BlockNumber>,1651 ) -> DispatchResult {1652 let sender = ensure_signed(origin)?;1653 let mut target_collection = Self::get_collection(collection_id)?;1654 Self::check_owner_permissions(&target_collection, sender.clone())?;1655 let old_limits = &target_collection.limits;1656 let chain_limits = ChainLimit::get();16571658 // collection bounds1659 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1660 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1661 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1662 Error::<T>::CollectionLimitBoundsExceeded);16631664 // token_limit check prev1665 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1666 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16671668 ensure!(1669 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1670 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1671 Error::<T>::OwnerPermissionsCantBeReverted,1672 );16731674 target_collection.limits = new_limits;1675 Self::save_collection(target_collection);16761677 Ok(())1678 } 1679 }1680}16811682impl<T: Config> Module<T> {1683 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1684 let target_collection = Self::get_collection(collection_id)?;16851686 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1687 Self::validate_create_item_args(&target_collection, &data)?;1688 Self::create_item_no_validation(&target_collection, owner, data)?;16891690 Ok(())1691 }16921693 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1694 // Limits check1695 Self::is_correct_transfer(target_collection, &recipient)?;16961697 // Transfer permissions check1698 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1699 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1700 Error::<T>::NoPermission);17011702 if target_collection.access == AccessMode::WhiteList {1703 Self::check_white_list(target_collection, &sender)?;1704 Self::check_white_list(target_collection, &recipient)?;1705 }17061707 match target_collection.mode1708 {1709 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1710 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1711 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1712 _ => ()1713 };17141715 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));17161717 Ok(())1718 }17191720 pub fn approve_internal(1721 sender: T::AccountId,1722 spender: T::AccountId,1723 collection: &CollectionHandle<T>,1724 item_id: TokenId,1725 amount: u128,1726 ) -> DispatchResult {1727 Self::token_exists(&collection, item_id)?;17281729 // Transfer permissions check1730 let bypasses_limits = collection.limits.owner_can_transfer &&1731 Self::is_owner_or_admin_permissions(1732 &collection,1733 sender.clone(),1734 );17351736 let allowance_limit = if bypasses_limits {1737 None1738 } else if let Some(amount) = Self::owned_amount(1739 sender.clone(),1740 &collection,1741 item_id,1742 ) {1743 Some(amount)1744 } else {1745 fail!(Error::<T>::NoPermission);1746 };17471748 if collection.access == AccessMode::WhiteList {1749 Self::check_white_list(&collection, &sender)?;1750 Self::check_white_list(&collection, &spender)?;1751 }17521753 let allowance: u128 = amount1754 .checked_add(<Allowances<T>>::get(collection.id, (item_id, &sender, &spender)))1755 .ok_or(Error::<T>::NumOverflow)?;1756 if let Some(limit) = allowance_limit {1757 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1758 }1759 <Allowances<T>>::insert(collection.id, (item_id, &sender, &spender), allowance);17601761 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1762 Ok(())1763 }17641765 pub fn transfer_from_internal(1766 sender: T::AccountId,1767 from: T::AccountId,1768 recipient: T::AccountId,1769 collection: &CollectionHandle<T>,1770 item_id: TokenId,1771 amount: u128,1772 ) -> DispatchResult {1773 // Check approval1774 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, &from, &sender));17751776 // Limits check1777 Self::is_correct_transfer(&collection, &recipient)?;17781779 // Transfer permissions check1780 ensure!(1781 approval >= amount || 1782 (1783 collection.limits.owner_can_transfer &&1784 Self::is_owner_or_admin_permissions(&collection, sender.clone())1785 ),1786 Error::<T>::NoPermission1787 );17881789 if collection.access == AccessMode::WhiteList {1790 Self::check_white_list(&collection, &sender)?;1791 Self::check_white_list(&collection, &recipient)?;1792 }17931794 // Reduce approval by transferred amount or remove if remaining approval drops to 01795 let allowance = approval.saturating_sub(amount);1796 if allowance > 0 {1797 <Allowances<T>>::insert(collection.id, (item_id, &from, &sender), allowance);1798 } else {1799 <Allowances<T>>::remove(collection.id, (item_id, &from, &sender));1800 }18011802 match collection.mode {1803 CollectionMode::NFT => {1804 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1805 }1806 CollectionMode::Fungible(_) => {1807 Self::transfer_fungible(&collection, amount, &from, &recipient)?1808 }1809 CollectionMode::ReFungible => {1810 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1811 }1812 _ => ()1813 };18141815 Ok(())1816 }18171818 pub fn set_variable_meta_data_internal(1819 sender: T::AccountId,1820 collection: &CollectionHandle<T>, 1821 item_id: TokenId,1822 data: Vec<u8>,1823 ) -> DispatchResult {1824 Self::token_exists(&collection, item_id)?;18251826 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18271828 // Modify permissions check1829 ensure!(Self::is_item_owner(sender.clone(), &collection, item_id) ||1830 Self::is_owner_or_admin_permissions(&collection, sender.clone()),1831 Error::<T>::NoPermission);18321833 match collection.mode1834 {1835 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1836 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1837 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1838 _ => fail!(Error::<T>::UnexpectedCollectionType)1839 };18401841 Ok(())1842 }18431844 pub fn create_multiple_items_internal(1845 sender: T::AccountId,1846 collection: &CollectionHandle<T>,1847 owner: T::AccountId,1848 items_data: Vec<CreateItemData>,1849 ) -> DispatchResult {1850 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18511852 for data in &items_data {1853 Self::validate_create_item_args(&collection, data)?;1854 }1855 for data in &items_data {1856 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1857 }18581859 Ok(())1860 }18611862 pub fn toggle_white_list_internal(1863 sender: &T::AccountId,1864 collection: &CollectionHandle<T>,1865 address: &T::AccountId,1866 whitelisted: bool,1867 ) -> DispatchResult {1868 Self::check_owner_or_admin_permissions(&collection, sender.clone())?;18691870 if whitelisted {1871 <WhiteList<T>>::insert(collection.id, address, true);1872 } else {1873 <WhiteList<T>>::remove(collection.id, address);1874 }18751876 Ok(())1877 }18781879 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1880 let collection_id = collection.id;18811882 // check token limit and account token limit1883 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1884 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1885 1886 Ok(())1887 }18881889 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1890 let collection_id = collection.id;18911892 // check token limit and account token limit1893 let total_items: u32 = ItemListIndex::get(collection_id)1894 .checked_add(amount)1895 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1896 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1897 .checked_add(amount)1898 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1899 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1900 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19011902 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1903 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1904 Self::check_white_list(collection, owner)?;1905 Self::check_white_list(collection, sender)?;1906 }19071908 Ok(())1909 }19101911 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1912 match target_collection.mode1913 {1914 CollectionMode::NFT => {1915 if let CreateItemData::NFT(data) = data {1916 // check sizes1917 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1918 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1919 } else {1920 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1921 }1922 },1923 CollectionMode::Fungible(_) => {1924 if let CreateItemData::Fungible(_) = data {1925 } else {1926 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1927 }1928 },1929 CollectionMode::ReFungible => {1930 if let CreateItemData::ReFungible(data) = data {19311932 // check sizes1933 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1934 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19351936 // Check refungibility limits1937 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1938 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1939 } else {1940 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1941 }1942 },1943 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1944 };19451946 Ok(())1947 }19481949 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1950 match data1951 {1952 CreateItemData::NFT(data) => {1953 let item = NftItemType {1954 owner: owner.clone(),1955 const_data: data.const_data,1956 variable_data: data.variable_data1957 };19581959 Self::add_nft_item(collection, item)?;1960 },1961 CreateItemData::Fungible(data) => {1962 Self::add_fungible_item(collection, &owner, data.value)?;1963 },1964 CreateItemData::ReFungible(data) => {1965 let mut owner_list = Vec::new();1966 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});19671968 let item = ReFungibleItemType {1969 owner: owner_list,1970 const_data: data.const_data,1971 variable_data: data.variable_data1972 };19731974 Self::add_refungible_item(collection, item)?;1975 }1976 };19771978 Ok(())1979 }19801981 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1982 let collection_id = collection.id;19831984 // Does new owner already have an account?1985 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;19861987 // Mint 1988 let item = FungibleItemType {1989 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1990 };1991 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);19921993 // Update balance1994 let new_balance = <Balance<T>>::get(collection_id, owner)1995 .checked_add(value)1996 .ok_or(Error::<T>::NumOverflow)?;1997 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);19981999 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2000 Ok(())2001 }20022003 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {2004 let collection_id = collection.id;20052006 let current_index = <ItemListIndex>::get(collection_id)2007 .checked_add(1)2008 .ok_or(Error::<T>::NumOverflow)?;2009 let itemcopy = item.clone();20102011 ensure!(2012 item.owner.len() == 1,2013 Error::<T>::BadCreateRefungibleCall,2014 );2015 let item_owner = item.owner.first().expect("only one owner is defined");20162017 let value = item_owner.fraction;2018 let owner = item_owner.owner.clone();20192020 Self::add_token_index(collection_id, current_index, &owner)?;20212022 <ItemListIndex>::insert(collection_id, current_index);2023 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20242025 // Update balance2026 let new_balance = <Balance<T>>::get(collection_id, &owner)2027 .checked_add(value)2028 .ok_or(Error::<T>::NumOverflow)?;2029 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);20302031 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2032 Ok(())2033 }20342035 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {2036 let collection_id = collection.id;20372038 let current_index = <ItemListIndex>::get(collection_id)2039 .checked_add(1)2040 .ok_or(Error::<T>::NumOverflow)?;20412042 let item_owner = item.owner.clone();2043 Self::add_token_index(collection_id, current_index, &item.owner)?;20442045 <ItemListIndex>::insert(collection_id, current_index);2046 <NftItemList<T>>::insert(collection_id, current_index, item);20472048 // Update balance2049 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2050 .checked_add(1)2051 .ok_or(Error::<T>::NumOverflow)?;2052 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);20532054 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2055 Ok(())2056 }20572058 fn burn_refungible_item(2059 collection: &CollectionHandle<T>,2060 item_id: TokenId,2061 owner: &T::AccountId,2062 ) -> DispatchResult {2063 let collection_id = collection.id;20642065 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2066 .ok_or(Error::<T>::TokenNotFound)?;2067 let rft_balance = token2068 .owner2069 .iter()2070 .find(|&i| i.owner == *owner)2071 .ok_or(Error::<T>::TokenNotFound)?;2072 Self::remove_token_index(collection_id, item_id, owner)?;20732074 // update balance2075 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())2076 .checked_sub(rft_balance.fraction)2077 .ok_or(Error::<T>::NumOverflow)?;2078 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);20792080 // Re-create owners list with sender removed2081 let index = token2082 .owner2083 .iter()2084 .position(|i| i.owner == *owner)2085 .expect("owned item is exists");2086 token.owner.remove(index);2087 let owner_count = token.owner.len();20882089 // Burn the token completely if this was the last (only) owner2090 if owner_count == 0 {2091 <ReFungibleItemList<T>>::remove(collection_id, item_id);2092 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2093 }2094 else {2095 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2096 }20972098 Ok(())2099 }21002101 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2102 let collection_id = collection.id;21032104 let item = <NftItemList<T>>::get(collection_id, item_id)2105 .ok_or(Error::<T>::TokenNotFound)?;2106 Self::remove_token_index(collection_id, item_id, &item.owner)?;21072108 // update balance2109 let new_balance = <Balance<T>>::get(collection_id, &item.owner)2110 .checked_sub(1)2111 .ok_or(Error::<T>::NumOverflow)?;2112 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);2113 <NftItemList<T>>::remove(collection_id, item_id);2114 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21152116 Ok(())2117 }21182119 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2120 let collection_id = collection.id;21212122 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2123 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21242125 // update balance2126 let new_balance = <Balance<T>>::get(collection_id, owner)2127 .checked_sub(value)2128 .ok_or(Error::<T>::NumOverflow)?;2129 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);21302131 if balance.value - value > 0 {2132 balance.value -= value;2133 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2134 }2135 else {2136 <FungibleItemList<T>>::remove(collection_id, owner);2137 }21382139 Ok(())2140 }21412142 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2143 Ok(<CollectionById<T>>::get(collection_id)2144 .map(|collection| CollectionHandle {2145 id: collection_id,2146 collection2147 })2148 .ok_or(Error::<T>::CollectionNotFound)?)2149 }21502151 fn save_collection(collection: CollectionHandle<T>) {2152 <CollectionById<T>>::insert(collection.id, collection.collection);2153 }21542155 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {2156 ensure!(2157 subject == target_collection.owner,2158 Error::<T>::NoPermission2159 );21602161 Ok(())2162 }21632164 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {2165 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2166 }21672168 fn check_owner_or_admin_permissions(2169 collection: &CollectionHandle<T>,2170 subject: T::AccountId,2171 ) -> DispatchResult {2172 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);21732174 Ok(())2175 }21762177 fn owned_amount(2178 subject: T::AccountId,2179 target_collection: &CollectionHandle<T>,2180 item_id: TokenId,2181 ) -> Option<u128> {2182 let collection_id = target_collection.id;21832184 match target_collection.mode {2185 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)2186 .then(|| 1),2187 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)2188 .value),2189 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2190 .owner2191 .iter()2192 .find(|i| i.owner == subject)2193 .map(|i| i.fraction),2194 CollectionMode::Invalid => None,2195 }2196 }21972198 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2199 match target_collection.mode {2200 CollectionMode::Fungible(_) => true,2201 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),2202 }2203 }22042205 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {2206 let collection_id = collection.id;22072208 let mes = Error::<T>::AddresNotInWhiteList;2209 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);22102211 Ok(())2212 }22132214 /// Check if token exists. In case of Fungible, check if there is an entry for 2215 /// the owner in fungible balances double map2216 fn token_exists(2217 target_collection: &CollectionHandle<T>,2218 item_id: TokenId,2219 ) -> DispatchResult {2220 let collection_id = target_collection.id;2221 let exists = match target_collection.mode2222 {2223 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2224 CollectionMode::Fungible(_) => true,2225 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2226 _ => false2227 };22282229 ensure!(exists == true, Error::<T>::TokenNotFound);2230 Ok(())2231 }22322233 fn transfer_fungible(2234 collection: &CollectionHandle<T>,2235 value: u128,2236 owner: &T::AccountId,2237 recipient: &T::AccountId,2238 ) -> DispatchResult {2239 let collection_id = collection.id;22402241 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2242 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);22432244 // Send balance to recipient (updates balanceOf of recipient)2245 Self::add_fungible_item(collection, recipient, value)?;22462247 // update balanceOf of sender2248 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);22492250 // Reduce or remove sender2251 if balance.value == value {2252 <FungibleItemList<T>>::remove(collection_id, owner);2253 }2254 else {2255 balance.value -= value;2256 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2257 }22582259 Ok(())2260 }22612262 fn transfer_refungible(2263 collection: &CollectionHandle<T>,2264 item_id: TokenId,2265 value: u128,2266 owner: T::AccountId,2267 new_owner: T::AccountId,2268 ) -> DispatchResult {2269 let collection_id = collection.id;2270 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2271 .ok_or(Error::<T>::TokenNotFound)?;22722273 let item = full_item2274 .owner2275 .iter()2276 .filter(|i| i.owner == owner)2277 .next()2278 .ok_or(Error::<T>::TokenNotFound)?;2279 let amount = item.fraction;22802281 ensure!(amount >= value, Error::<T>::TokenValueTooLow);22822283 // update balance2284 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2285 .checked_sub(value)2286 .ok_or(Error::<T>::NumOverflow)?;2287 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);22882289 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2290 .checked_add(value)2291 .ok_or(Error::<T>::NumOverflow)?;2292 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);22932294 let old_owner = item.owner.clone();2295 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);22962297 // transfer2298 if amount == value && !new_owner_has_account {2299 // change owner2300 // new owner do not have account2301 let mut new_full_item = full_item.clone();2302 new_full_item2303 .owner2304 .iter_mut()2305 .find(|i| i.owner == owner)2306 .expect("old owner does present in refungible")2307 .owner = new_owner.clone();2308 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);23092310 // update index collection2311 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2312 } else {2313 let mut new_full_item = full_item.clone();2314 new_full_item2315 .owner2316 .iter_mut()2317 .find(|i| i.owner == owner)2318 .expect("old owner does present in refungible")2319 .fraction -= value;23202321 // separate amount2322 if new_owner_has_account {2323 // new owner has account2324 new_full_item2325 .owner2326 .iter_mut()2327 .find(|i| i.owner == new_owner)2328 .expect("new owner has account")2329 .fraction += value;2330 } else {2331 // new owner do not have account2332 new_full_item.owner.push(Ownership {2333 owner: new_owner.clone(),2334 fraction: value,2335 });2336 Self::add_token_index(collection_id, item_id, &new_owner)?;2337 }23382339 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2340 }23412342 Ok(())2343 }23442345 fn transfer_nft(2346 collection: &CollectionHandle<T>,2347 item_id: TokenId,2348 sender: T::AccountId,2349 new_owner: T::AccountId,2350 ) -> DispatchResult {2351 let collection_id = collection.id;2352 let mut item = <NftItemList<T>>::get(collection_id, item_id)2353 .ok_or(Error::<T>::TokenNotFound)?;23542355 ensure!(2356 sender == item.owner,2357 Error::<T>::MustBeTokenOwner2358 );23592360 // update balance2361 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2362 .checked_sub(1)2363 .ok_or(Error::<T>::NumOverflow)?;2364 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);23652366 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2367 .checked_add(1)2368 .ok_or(Error::<T>::NumOverflow)?;2369 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);23702371 // change owner2372 let old_owner = item.owner.clone();2373 item.owner = new_owner.clone();2374 <NftItemList<T>>::insert(collection_id, item_id, item);23752376 // update index collection2377 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;23782379 Ok(())2380 }2381 2382 fn set_re_fungible_variable_data(2383 collection: &CollectionHandle<T>,2384 item_id: TokenId,2385 data: Vec<u8>2386 ) -> DispatchResult {2387 let collection_id = collection.id;2388 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2389 .ok_or(Error::<T>::TokenNotFound)?;23902391 item.variable_data = data;23922393 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);23942395 Ok(())2396 }23972398 fn set_nft_variable_data(2399 collection: &CollectionHandle<T>,2400 item_id: TokenId,2401 data: Vec<u8>2402 ) -> DispatchResult {2403 let collection_id = collection.id;2404 let mut item = <NftItemList<T>>::get(collection_id, item_id)2405 .ok_or(Error::<T>::TokenNotFound)?;2406 2407 item.variable_data = data;24082409 <NftItemList<T>>::insert(collection_id, item_id, item);2410 2411 Ok(())2412 }24132414 #[allow(dead_code)]2415 fn init_collection(item: &Collection<T>) {2416 // check params2417 assert!(2418 item.decimal_points <= MAX_DECIMAL_POINTS,2419 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2420 );2421 assert!(2422 item.name.len() <= 64,2423 "Collection name can not be longer than 63 char"2424 );2425 assert!(2426 item.name.len() <= 256,2427 "Collection description can not be longer than 255 char"2428 );2429 assert!(2430 item.token_prefix.len() <= 16,2431 "Token prefix can not be longer than 15 char"2432 );24332434 // Generate next collection ID2435 let next_id = CreatedCollectionCount::get()2436 .checked_add(1)2437 .unwrap();24382439 CreatedCollectionCount::put(next_id);2440 }24412442 #[allow(dead_code)]2443 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2444 let current_index = <ItemListIndex>::get(collection_id)2445 .checked_add(1)2446 .unwrap();24472448 let item_owner = item.owner.clone();2449 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();24502451 <ItemListIndex>::insert(collection_id, current_index);24522453 // Update balance2454 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2455 .checked_add(1)2456 .unwrap();2457 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2458 }24592460 #[allow(dead_code)]2461 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2462 let current_index = <ItemListIndex>::get(collection_id)2463 .checked_add(1)2464 .unwrap();24652466 Self::add_token_index(collection_id, current_index, owner).unwrap();24672468 <ItemListIndex>::insert(collection_id, current_index);24692470 // Update balance2471 let new_balance = <Balance<T>>::get(collection_id, owner)2472 .checked_add(item.value)2473 .unwrap();2474 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2475 }24762477 #[allow(dead_code)]2478 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2479 let current_index = <ItemListIndex>::get(collection_id)2480 .checked_add(1)2481 .unwrap();24822483 let value = item.owner.first().unwrap().fraction;2484 let owner = item.owner.first().unwrap().owner.clone();24852486 Self::add_token_index(collection_id, current_index, &owner).unwrap();24872488 <ItemListIndex>::insert(collection_id, current_index);24892490 // Update balance2491 let new_balance = <Balance<T>>::get(collection_id, &owner)2492 .checked_add(value)2493 .unwrap();2494 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2495 }24962497 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2498 // add to account limit2499 if <AccountItemCount<T>>::contains_key(owner) {25002501 // bound Owned tokens by a single address2502 let count = <AccountItemCount<T>>::get(owner);2503 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);25042505 <AccountItemCount<T>>::insert(owner.clone(), count2506 .checked_add(1)2507 .ok_or(Error::<T>::NumOverflow)?);2508 }2509 else {2510 <AccountItemCount<T>>::insert(owner.clone(), 1);2511 }25122513 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2514 if list_exists {2515 let mut list = <AddressTokens<T>>::get(collection_id, owner);2516 let item_contains = list.contains(&item_index.clone());25172518 if !item_contains {2519 list.push(item_index.clone());2520 }25212522 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2523 } else {2524 let mut itm = Vec::new();2525 itm.push(item_index.clone());2526 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2527 }25282529 Ok(())2530 }25312532 fn remove_token_index(2533 collection_id: CollectionId,2534 item_index: TokenId,2535 owner: &T::AccountId,2536 ) -> DispatchResult {25372538 // update counter2539 <AccountItemCount<T>>::insert(owner.clone(), 2540 <AccountItemCount<T>>::get(owner)2541 .checked_sub(1)2542 .ok_or(Error::<T>::NumOverflow)?);254325442545 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2546 if list_exists {2547 let mut list = <AddressTokens<T>>::get(collection_id, owner);2548 let item_contains = list.contains(&item_index.clone());25492550 if item_contains {2551 list.retain(|&item| item != item_index);2552 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2553 }2554 }25552556 Ok(())2557 }25582559 fn move_token_index(2560 collection_id: CollectionId,2561 item_index: TokenId,2562 old_owner: &T::AccountId,2563 new_owner: &T::AccountId,2564 ) -> DispatchResult {2565 Self::remove_token_index(collection_id, item_index, old_owner)?;2566 Self::add_token_index(collection_id, item_index, new_owner)?;25672568 Ok(())2569 }2570 2571 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2572 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);25732574 Ok(())2575 }2576}25772578////////////////////////////////////////////////////////////////////////////////////////////////////2579// Economic models2580// #region25812582/// Fee multiplier.2583pub type Multiplier = FixedU128;25842585type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;25862587/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2588/// in the queue.2589#[derive(Encode, Decode, Clone, Eq, PartialEq)]2590pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);25912592impl<T: Config + Send + Sync> sp_std::fmt::Debug 2593 for ChargeTransactionPayment<T>2594{2595 #[cfg(feature = "std")]2596 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2597 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2598 }2599 #[cfg(not(feature = "std"))]2600 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2601 Ok(())2602 }2603}26042605impl<T: Config> ChargeTransactionPayment<T>2606where2607 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2608 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2609 T::AccountId: AsRef<[u8]>,2610 T::AccountId: UncheckedFrom<T::Hash>,2611{2612 fn traditional_fee(2613 len: usize,2614 info: &DispatchInfoOf<T::Call>,2615 tip: BalanceOf<T>,2616 ) -> BalanceOf<T>2617 where2618 T::Call: Dispatchable<Info = DispatchInfo>,2619 {2620 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2621 }26222623 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2624 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2625 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2626 let len_saturation = max_block_length as u64 / (len as u64).max(1);2627 let coefficient: BalanceOf<T> = weight_saturation2628 .min(len_saturation)2629 .saturated_into::<BalanceOf<T>>();2630 final_fee2631 .saturating_mul(coefficient)2632 .saturated_into::<TransactionPriority>()2633 }26342635 fn withdraw_fee(2636 &self,2637 who: &T::AccountId,2638 call: &T::Call,2639 info: &DispatchInfoOf<T::Call>,2640 len: usize,2641 ) -> Result<2642 (2643 BalanceOf<T>,2644 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2645 ),2646 TransactionValidityError,2647 > {2648 let tip = self.0;26492650 let fee = Self::traditional_fee(len, info, tip);26512652 // Only mess with balances if fee is not zero.2653 if fee.is_zero() {2654 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2655 .map(|i| (fee, i));2656 }26572658 // Determine who is paying transaction fee based on ecnomic model2659 // Parse call to extract collection ID and access collection sponsor2660 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2661 Some(Call::create_item(collection_id, _owner, _properties)) => {2662 let collection = <CollectionById<T>>::get(collection_id)?;26632664 // sponsor timeout2665 let block_number = <system::Pallet<T>>::block_number() as T::BlockNumber;26662667 let limit = collection.limits.sponsor_transfer_timeout;2668 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2669 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2670 let limit_time = last_tx_block + limit.into();2671 if block_number <= limit_time {2672 return None;2673 }2674 }2675 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);26762677 // check free create limit2678 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2679 collection.sponsorship.sponsor()2680 .cloned()2681 } else {2682 None2683 }2684 }2685 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2686 let collection = <CollectionById<T>>::get(collection_id)?;2687 2688 let mut sponsor_transfer = false;2689 if collection.sponsorship.confirmed() {26902691 let collection_limits = collection.limits;2692 let collection_mode = collection.mode;2693 2694 // sponsor timeout2695 let block_number = <system::Pallet<T>>::block_number() as T::BlockNumber;2696 sponsor_transfer = match collection_mode {2697 CollectionMode::NFT => {2698 2699 // get correct limit2700 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2701 collection_limits.sponsor_transfer_timeout2702 } else {2703 ChainLimit::get().nft_sponsor_transfer_timeout2704 };2705 2706 let mut sponsored = true;2707 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2708 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2709 let limit_time = last_tx_block + limit.into();2710 if block_number <= limit_time {2711 sponsored = false;2712 }2713 }2714 if sponsored {2715 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2716 }27172718 sponsored2719 }2720 CollectionMode::Fungible(_) => {2721 2722 // get correct limit2723 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2724 collection_limits.sponsor_transfer_timeout2725 } else {2726 ChainLimit::get().fungible_sponsor_transfer_timeout2727 };2728 2729 let block_number = <system::Pallet<T>>::block_number() as T::BlockNumber;2730 let mut sponsored = true;2731 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2732 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2733 let limit_time = last_tx_block + limit.into();2734 if block_number <= limit_time {2735 sponsored = false;2736 }2737 }2738 if sponsored {2739 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2740 }27412742 sponsored2743 }2744 CollectionMode::ReFungible => {2745 2746 // get correct limit2747 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2748 collection_limits.sponsor_transfer_timeout2749 } else {2750 ChainLimit::get().refungible_sponsor_transfer_timeout2751 };2752 2753 let mut sponsored = true;2754 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2755 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2756 let limit_time = last_tx_block + limit.into();2757 if block_number <= limit_time {2758 sponsored = false;2759 }2760 }2761 if sponsored {2762 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2763 }27642765 sponsored2766 }2767 _ => {2768 false2769 },2770 };2771 }27722773 if !sponsor_transfer {2774 None2775 } else {2776 collection.sponsorship.sponsor()2777 .cloned()2778 }2779 }27802781 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2782 let mut sponsor_metadata_changes = false;27832784 let collection = <CollectionById<T>>::get(collection_id)?;27852786 if2787 collection.sponsorship.confirmed() &&2788 // Can't sponsor fungible collection, this tx will be rejected2789 // as invalid2790 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2791 data.len() <= collection.limits.sponsored_data_size as usize2792 {2793 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2794 let block_number = <system::Pallet<T>>::block_number() as T::BlockNumber;27952796 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2797 .map(|last_block| block_number - last_block > rate_limit)2798 .unwrap_or(true) 2799 {2800 sponsor_metadata_changes = true;2801 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2802 }2803 }2804 }28052806 if !sponsor_metadata_changes {2807 None2808 } else {2809 collection.sponsorship.sponsor().cloned()2810 }2811 }28122813 _ => None,2814 })();28152816 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2817 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {28182819 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());28202821 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2822 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2823 2824 if !owned_contract && white_list_enabled {2825 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2826 return Err(InvalidTransaction::Call.into());2827 }2828 }2829 },2830 _ => {},2831 }28322833 // Sponsor smart contracts2834 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {28352836 // On instantiation: set the contract owner2837 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {28382839 let new_contract_address = <pallet_contracts::Pallet<T>>::contract_address(2840 &who,2841 code_hash,2842 salt,2843 );2844 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());28452846 None2847 },28482849 // On instantiation with code: set the contract owner2850 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {28512852 let new_contract_address = <pallet_contracts::Pallet<T>>::contract_address(2853 &who,2854 &T::Hashing::hash(&_code),2855 _salt,2856 );28572858 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());28592860 None2861 }28622863 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2864 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {28652866 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());28672868 let mut sponsor_transfer = false;2869 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2870 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2871 let block_number = <system::Pallet<T>>::block_number() as T::BlockNumber;2872 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2873 let limit_time = last_tx_block + rate_limit;28742875 if block_number >= limit_time {2876 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2877 sponsor_transfer = true;2878 }2879 } else {2880 sponsor_transfer = false;2881 }2882 2883 if sponsor_transfer {2884 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2885 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2886 return Some(called_contract);2887 }2888 }2889 }28902891 None2892 },28932894 _ => None,2895 });28962897 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());28982899 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2900 .map(|i| (fee, i))2901 }2902}290329042905impl<T: Config + Send + Sync> SignedExtension2906 for ChargeTransactionPayment<T>2907where2908 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2909 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2910 T::AccountId: AsRef<[u8]>,2911 T::AccountId: UncheckedFrom<T::Hash>,2912{2913 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2914 type AccountId = T::AccountId;2915 type Call = T::Call;2916 type AdditionalSigned = ();2917 type Pre = (2918 // tip2919 BalanceOf<T>,2920 // who pays fee2921 Self::AccountId,2922 // imbalance resulting from withdrawing the fee2923 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2924 );2925 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2926 Ok(())2927 }29282929 fn validate(2930 &self,2931 who: &Self::AccountId,2932 call: &Self::Call,2933 info: &DispatchInfoOf<Self::Call>,2934 len: usize,2935 ) -> TransactionValidity {2936 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2937 Ok(ValidTransaction {2938 priority: Self::get_priority(len, info, fee),2939 ..Default::default()2940 })2941 }29422943 fn pre_dispatch(2944 self,2945 who: &Self::AccountId,2946 call: &Self::Call,2947 info: &DispatchInfoOf<Self::Call>,2948 len: usize,2949 ) -> Result<Self::Pre, TransactionValidityError> {2950 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2951 Ok((self.0, who.clone(), imbalance))2952 }29532954 fn post_dispatch(2955 pre: Self::Pre,2956 info: &DispatchInfoOf<Self::Call>,2957 post_info: &PostDispatchInfoOf<Self::Call>,2958 len: usize,2959 _result: &DispatchResult,2960 ) -> Result<(), TransactionValidityError> {2961 let (tip, who, imbalance) = pre;2962 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(2963 len as u32,2964 info,2965 post_info,2966 tip,2967 );2968 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2969 Ok(())2970 }2971}29722973// #endregion1//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)?;569570 Self::toggle_white_list_internal(571 &sender,572 &collection,573 &address,574 true,575 )?;576577 Ok(())578 }579580 /// Remove an address from white list.581 /// 582 /// # Permissions583 /// 584 /// * Collection Owner585 /// * Collection Admin586 /// 587 /// # Arguments588 /// 589 /// * collection_id.590 /// 591 /// * address.592 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]593 #[transactional]594 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{595596 let sender = ensure_signed(origin)?;597 let collection = Self::get_collection(collection_id)?;598599 Self::toggle_white_list_internal(600 &sender,601 &collection,602 &address,603 false,604 )?;605606 Ok(())607 }608609 /// Toggle between normal and white list access for the methods with access for `Anyone`.610 /// 611 /// # Permissions612 /// 613 /// * Collection Owner.614 /// 615 /// # Arguments616 /// 617 /// * collection_id.618 /// 619 /// * mode: [AccessMode]620 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]621 #[transactional]622 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult623 {624 let sender = ensure_signed(origin)?;625626 let mut target_collection = Self::get_collection(collection_id)?;627 Self::check_owner_permissions(&target_collection, sender)?;628 target_collection.access = mode;629 Self::save_collection(target_collection);630631 Ok(())632 }633634 /// Allows Anyone to create tokens if:635 /// * White List is enabled, and636 /// * Address is added to white list, and637 /// * This method was called with True parameter638 /// 639 /// # Permissions640 /// * Collection Owner641 ///642 /// # Arguments643 /// 644 /// * collection_id.645 /// 646 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.647 #[weight = <T as Config>::WeightInfo::set_mint_permission()]648 #[transactional]649 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult650 {651 let sender = ensure_signed(origin)?;652653 let mut target_collection = Self::get_collection(collection_id)?;654 Self::check_owner_permissions(&target_collection, sender)?;655 target_collection.mint_mode = mint_permission;656 Self::save_collection(target_collection);657658 Ok(())659 }660661 /// Change the owner of the collection.662 /// 663 /// # Permissions664 /// 665 /// * Collection Owner.666 /// 667 /// # Arguments668 /// 669 /// * collection_id.670 /// 671 /// * new_owner.672 #[weight = <T as Config>::WeightInfo::change_collection_owner()]673 #[transactional]674 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {675676 let sender = ensure_signed(origin)?;677 let mut target_collection = Self::get_collection(collection_id)?;678 Self::check_owner_permissions(&target_collection, sender)?;679 target_collection.owner = new_owner;680 Self::save_collection(target_collection);681682 Ok(())683 }684685 /// Adds an admin of the Collection.686 /// 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. 687 /// 688 /// # Permissions689 /// 690 /// * Collection Owner.691 /// * Collection Admin.692 /// 693 /// # Arguments694 /// 695 /// * collection_id: ID of the Collection to add admin for.696 /// 697 /// * new_admin_id: Address of new admin to add.698 #[weight = <T as Config>::WeightInfo::add_collection_admin()]699 #[transactional]700 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {701702 let sender = ensure_signed(origin)?;703 let collection = Self::get_collection(collection_id)?;704 Self::check_owner_or_admin_permissions(&collection, sender)?;705 let mut admin_arr = <AdminList<T>>::get(collection_id);706707 match admin_arr.binary_search(&new_admin_id) {708 Ok(_) => {},709 Err(idx) => {710 let limits = ChainLimit::get();711 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);712 admin_arr.insert(idx, new_admin_id);713 <AdminList<T>>::insert(collection_id, admin_arr);714 }715 }716 Ok(())717 }718719 /// 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.720 ///721 /// # Permissions722 /// 723 /// * Collection Owner.724 /// * Collection Admin.725 /// 726 /// # Arguments727 /// 728 /// * collection_id: ID of the Collection to remove admin for.729 /// 730 /// * account_id: Address of admin to remove.731 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]732 #[transactional]733 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {734735 let sender = ensure_signed(origin)?;736 let collection = Self::get_collection(collection_id)?;737 Self::check_owner_or_admin_permissions(&collection, sender)?;738 let mut admin_arr = <AdminList<T>>::get(collection_id);739740 match admin_arr.binary_search(&account_id) {741 Ok(idx) => {742 admin_arr.remove(idx);743 <AdminList<T>>::insert(collection_id, admin_arr);744 },745 Err(_) => {}746 }747 Ok(())748 }749750 /// # Permissions751 /// 752 /// * Collection Owner753 /// 754 /// # Arguments755 /// 756 /// * collection_id.757 /// 758 /// * new_sponsor.759 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]760 #[transactional]761 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {762763 let sender = ensure_signed(origin)?;764 let mut target_collection = Self::get_collection(collection_id)?;765 Self::check_owner_permissions(&target_collection, sender)?;766767 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);768 Self::save_collection(target_collection);769770 Ok(())771 }772773 /// # Permissions774 /// 775 /// * Sponsor.776 /// 777 /// # Arguments778 /// 779 /// * collection_id.780 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]781 #[transactional]782 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {783784 let sender = ensure_signed(origin)?;785786 let mut target_collection = Self::get_collection(collection_id)?;787 ensure!(788 target_collection.sponsorship.pending_sponsor() == Some(&sender),789 Error::<T>::ConfirmUnsetSponsorFail790 );791792 target_collection.sponsorship = SponsorshipState::Confirmed(sender);793 Self::save_collection(target_collection);794795 Ok(())796 }797798 /// Switch back to pay-per-own-transaction model.799 ///800 /// # Permissions801 ///802 /// * Collection owner.803 /// 804 /// # Arguments805 /// 806 /// * collection_id.807 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]808 #[transactional]809 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {810811 let sender = ensure_signed(origin)?;812813 let mut target_collection = Self::get_collection(collection_id)?;814 Self::check_owner_permissions(&target_collection, sender)?;815816 target_collection.sponsorship = SponsorshipState::Disabled;817 Self::save_collection(target_collection);818819 Ok(())820 }821822 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.823 /// 824 /// # Permissions825 /// 826 /// * Collection Owner.827 /// * Collection Admin.828 /// * Anyone if829 /// * White List is enabled, and830 /// * Address is added to white list, and831 /// * MintPermission is enabled (see SetMintPermission method)832 /// 833 /// # Arguments834 /// 835 /// * collection_id: ID of the collection.836 /// 837 /// * owner: Address, initial owner of the NFT.838 ///839 /// * data: Token data to store on chain.840 // #[weight =841 // (130_000_000 as Weight)842 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))843 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))844 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]845846 #[weight = <T as Config>::WeightInfo::create_item(data.len())]847 #[transactional]848 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {849 let sender = ensure_signed(origin)?;850 Self::create_item_internal(sender, collection_id, owner, data)851 }852853 /// This method creates multiple items in a collection created with CreateCollection method.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Anyone if860 /// * White List is enabled, and861 /// * Address is added to white list, and862 /// * MintPermission is enabled (see SetMintPermission method)863 /// 864 /// # Arguments865 /// 866 /// * collection_id: ID of the collection.867 /// 868 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].869 /// 870 /// * owner: Address, initial owner of the NFT.871 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()872 .map(|data| { data.len() })873 .sum())]874 #[transactional]875 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {876877 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);878 let sender = ensure_signed(origin)?;879880 let target_collection = Self::get_collection(collection_id)?;881882 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;883884 for data in &items_data {885 Self::validate_create_item_args(&target_collection, data)?;886 }887 for data in &items_data {888 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;889 }890891 Ok(())892 }893894 /// Destroys a concrete instance of NFT.895 /// 896 /// # Permissions897 /// 898 /// * Collection Owner.899 /// * Collection Admin.900 /// * Current NFT Owner.901 /// 902 /// # Arguments903 /// 904 /// * collection_id: ID of the collection.905 /// 906 /// * item_id: ID of NFT to burn.907 #[weight = <T as Config>::WeightInfo::burn_item()]908 #[transactional]909 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {910911 let sender = ensure_signed(origin)?;912913 // Transfer permissions check914 let target_collection = Self::get_collection(collection_id)?;915 ensure!(916 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||917 (918 target_collection.limits.owner_can_transfer &&919 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())920 ),921 Error::<T>::NoPermission922 );923924 if target_collection.access == AccessMode::WhiteList {925 Self::check_white_list(&target_collection, &sender)?;926 }927928 match target_collection.mode929 {930 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,931 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,932 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,933 _ => ()934 };935936 // call event937 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));938939 Ok(())940 }941942 /// Change ownership of the token.943 /// 944 /// # Permissions945 /// 946 /// * Collection Owner947 /// * Collection Admin948 /// * Current NFT owner949 ///950 /// # Arguments951 /// 952 /// * recipient: Address of token recipient.953 /// 954 /// * collection_id.955 /// 956 /// * item_id: ID of the item957 /// * Non-Fungible Mode: Required.958 /// * Fungible Mode: Ignored.959 /// * Re-Fungible Mode: Required.960 /// 961 /// * value: Amount to transfer.962 /// * Non-Fungible Mode: Ignored963 /// * Fungible Mode: Must specify transferred amount964 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)965 #[weight = <T as Config>::WeightInfo::transfer()]966 #[transactional]967 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {968 let sender = ensure_signed(origin)?;969 let collection = Self::get_collection(collection_id)?;970971 Self::transfer_internal(sender, recipient, &collection, item_id, value)972 }973974 /// Set, change, or remove approved address to transfer the ownership of the NFT.975 /// 976 /// # Permissions977 /// 978 /// * Collection Owner979 /// * Collection Admin980 /// * Current NFT owner981 /// 982 /// # Arguments983 /// 984 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).985 /// 986 /// * collection_id.987 /// 988 /// * item_id: ID of the item.989 #[weight = <T as Config>::WeightInfo::approve()]990 #[transactional]991 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {992993 let sender = ensure_signed(origin)?;994 let collection = Self::get_collection(collection_id)?;995996 Self::approve_internal(sender, spender, &collection, item_id, amount)?;997998 Ok(())999 }1000 1001 /// 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.1002 /// 1003 /// # Permissions1004 /// * Collection Owner1005 /// * Collection Admin1006 /// * Current NFT owner1007 /// * Address approved by current NFT owner1008 /// 1009 /// # Arguments1010 /// 1011 /// * from: Address that owns token.1012 /// 1013 /// * recipient: Address of token recipient.1014 /// 1015 /// * collection_id.1016 /// 1017 /// * item_id: ID of the item.1018 /// 1019 /// * value: Amount to transfer.1020 #[weight = <T as Config>::WeightInfo::transfer_from()]1021 #[transactional]1022 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {10231024 let sender = ensure_signed(origin)?;1025 let collection = Self::get_collection(collection_id)?;10261027 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;10281029 Ok(())1030 }10311032 // #[weight = 0]1033 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {10341035 // // let no_perm_mes = "You do not have permissions to modify this collection";1036 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1037 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1038 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10391040 // // // on_nft_received call10411042 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10431044 // Ok(())1045 // }10461047 /// Set off-chain data schema.1048 /// 1049 /// # Permissions1050 /// 1051 /// * Collection Owner1052 /// * Collection Admin1053 /// 1054 /// # Arguments1055 /// 1056 /// * collection_id.1057 /// 1058 /// * schema: String representing the offchain data schema.1059 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1060 #[transactional]1061 pub fn set_variable_meta_data (1062 origin,1063 collection_id: CollectionId,1064 item_id: TokenId,1065 data: Vec<u8>1066 ) -> DispatchResult {1067 let sender = ensure_signed(origin)?;1068 1069 let collection = Self::get_collection(collection_id)?;10701071 Self::set_variable_meta_data_internal(sender, &collection, item_id, data)?;10721073 Ok(())1074 }1075 1076 /// Set schema standard1077 /// ImageURL1078 /// Unique1079 /// 1080 /// # Permissions1081 /// 1082 /// * Collection Owner1083 /// * Collection Admin1084 /// 1085 /// # Arguments1086 /// 1087 /// * collection_id.1088 /// 1089 /// * schema: SchemaVersion: enum1090 #[weight = <T as Config>::WeightInfo::set_schema_version()]1091 #[transactional]1092 pub fn set_schema_version(1093 origin,1094 collection_id: CollectionId,1095 version: SchemaVersion1096 ) -> DispatchResult {1097 let sender = ensure_signed(origin)?;1098 let mut target_collection = Self::get_collection(collection_id)?;1099 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1100 target_collection.schema_version = version;1101 Self::save_collection(target_collection);11021103 Ok(())1104 }11051106 /// Set off-chain data schema.1107 /// 1108 /// # Permissions1109 /// 1110 /// * Collection Owner1111 /// * Collection Admin1112 /// 1113 /// # Arguments1114 /// 1115 /// * collection_id.1116 /// 1117 /// * schema: String representing the offchain data schema.1118 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1119 #[transactional]1120 pub fn set_offchain_schema(1121 origin,1122 collection_id: CollectionId,1123 schema: Vec<u8>1124 ) -> DispatchResult {1125 let sender = ensure_signed(origin)?;1126 let mut target_collection = Self::get_collection(collection_id)?;1127 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11281129 // check schema limit1130 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11311132 target_collection.offchain_schema = schema;1133 Self::save_collection(target_collection);11341135 Ok(())1136 }11371138 /// Set const on-chain data schema.1139 /// 1140 /// # Permissions1141 /// 1142 /// * Collection Owner1143 /// * Collection Admin1144 /// 1145 /// # Arguments1146 /// 1147 /// * collection_id.1148 /// 1149 /// * schema: String representing the const on-chain data schema.1150 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1151 #[transactional]1152 pub fn set_const_on_chain_schema (1153 origin,1154 collection_id: CollectionId,1155 schema: Vec<u8>1156 ) -> DispatchResult {1157 let sender = ensure_signed(origin)?;1158 let mut target_collection = Self::get_collection(collection_id)?;1159 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11601161 // check schema limit1162 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11631164 target_collection.const_on_chain_schema = schema;1165 Self::save_collection(target_collection);11661167 Ok(())1168 }11691170 /// Set variable on-chain data schema.1171 /// 1172 /// # Permissions1173 /// 1174 /// * Collection Owner1175 /// * Collection Admin1176 /// 1177 /// # Arguments1178 /// 1179 /// * collection_id.1180 /// 1181 /// * schema: String representing the variable on-chain data schema.1182 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1183 #[transactional]1184 pub fn set_variable_on_chain_schema (1185 origin,1186 collection_id: CollectionId,1187 schema: Vec<u8>1188 ) -> DispatchResult {1189 let sender = ensure_signed(origin)?;1190 let mut target_collection = Self::get_collection(collection_id)?;1191 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11921193 // check schema limit1194 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");11951196 target_collection.variable_on_chain_schema = schema;1197 Self::save_collection(target_collection);11981199 Ok(())1200 }12011202 // Sudo permissions function1203 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1204 #[transactional]1205 pub fn set_chain_limits(1206 origin,1207 limits: ChainLimits1208 ) -> DispatchResult {12091210 #[cfg(not(feature = "runtime-benchmarks"))]1211 ensure_root(origin)?;12121213 <ChainLimit>::put(limits);1214 Ok(())1215 }12161217 /// Enable smart contract self-sponsoring.1218 /// 1219 /// # Permissions1220 /// 1221 /// * Contract Owner1222 /// 1223 /// # Arguments1224 /// 1225 /// * contract address1226 /// * enable flag1227 /// 1228 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1229 #[transactional]1230 pub fn enable_contract_sponsoring(1231 origin,1232 contract_address: T::AccountId,1233 enable: bool1234 ) -> DispatchResult {12351236 let sender = ensure_signed(origin)?;12371238 #[cfg(feature = "runtime-benchmarks")]1239 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12401241 Self::ensure_contract_owned(sender, &contract_address)?;12421243 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1244 Ok(())1245 }12461247 /// Set the rate limit for contract sponsoring to specified number of blocks.1248 /// 1249 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1250 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1251 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1252 /// from contract endowment if there are at least B blocks between such transactions. 1253 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1254 /// 1255 /// # Permissions1256 /// 1257 /// * Contract Owner1258 /// 1259 /// # Arguments1260 /// 1261 /// -`contract_address`: Address of the contract to sponsor1262 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1263 /// 1264 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1265 #[transactional]1266 pub fn set_contract_sponsoring_rate_limit(1267 origin,1268 contract_address: T::AccountId,1269 rate_limit: T::BlockNumber1270 ) -> DispatchResult {1271 let sender = ensure_signed(origin)?;12721273 #[cfg(feature = "runtime-benchmarks")]1274 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12751276 Self::ensure_contract_owned(sender, &contract_address)?;1277 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1278 Ok(())1279 }12801281 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1282 /// 1283 /// # Permissions1284 /// 1285 /// * Address that deployed smart contract.1286 /// 1287 /// # Arguments1288 /// 1289 /// -`contract_address`: Address of the contract.1290 /// 1291 /// - `enable`: . 1292 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1293 #[transactional]1294 pub fn toggle_contract_white_list(1295 origin,1296 contract_address: T::AccountId,1297 enable: bool1298 ) -> DispatchResult {1299 let sender = ensure_signed(origin)?;13001301 #[cfg(feature = "runtime-benchmarks")]1302 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13031304 Self::ensure_contract_owned(sender, &contract_address)?;1305 if enable {1306 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1307 } else {1308 <ContractWhiteListEnabled<T>>::remove(contract_address);1309 }1310 Ok(())1311 }1312 1313 /// Add an address to smart contract white list.1314 /// 1315 /// # Permissions1316 /// 1317 /// * Address that deployed smart contract.1318 /// 1319 /// # Arguments1320 /// 1321 /// -`contract_address`: Address of the contract.1322 ///1323 /// -`account_address`: Address to add.1324 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1325 #[transactional]1326 pub fn add_to_contract_white_list(1327 origin,1328 contract_address: T::AccountId,1329 account_address: T::AccountId1330 ) -> DispatchResult {1331 let sender = ensure_signed(origin)?;13321333 #[cfg(feature = "runtime-benchmarks")]1334 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1335 1336 Self::ensure_contract_owned(sender, &contract_address)?; 1337 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1338 Ok(())1339 }13401341 /// Remove an address from smart contract white list.1342 /// 1343 /// # Permissions1344 /// 1345 /// * Address that deployed smart contract.1346 /// 1347 /// # Arguments1348 /// 1349 /// -`contract_address`: Address of the contract.1350 ///1351 /// -`account_address`: Address to remove.1352 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1353 #[transactional]1354 pub fn remove_from_contract_white_list(1355 origin,1356 contract_address: T::AccountId,1357 account_address: T::AccountId1358 ) -> DispatchResult {1359 let sender = ensure_signed(origin)?;13601361 #[cfg(feature = "runtime-benchmarks")]1362 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13631364 Self::ensure_contract_owned(sender, &contract_address)?;1365 <ContractWhiteList<T>>::remove(contract_address, account_address);1366 Ok(())1367 }13681369 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1370 #[transactional]1371 pub fn set_collection_limits(1372 origin,1373 collection_id: u32,1374 new_limits: CollectionLimits<T::BlockNumber>,1375 ) -> DispatchResult {1376 let sender = ensure_signed(origin)?;1377 let mut target_collection = Self::get_collection(collection_id)?;1378 Self::check_owner_permissions(&target_collection, sender.clone())?;1379 let old_limits = &target_collection.limits;1380 let chain_limits = ChainLimit::get();13811382 // collection bounds1383 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1384 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1385 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1386 Error::<T>::CollectionLimitBoundsExceeded);13871388 // token_limit check prev1389 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1390 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);13911392 ensure!(1393 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1394 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1395 Error::<T>::OwnerPermissionsCantBeReverted,1396 );13971398 target_collection.limits = new_limits;1399 Self::save_collection(target_collection);14001401 Ok(())1402 } 1403 }1404}14051406impl<T: Config> Module<T> {1407 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1408 let target_collection = Self::get_collection(collection_id)?;14091410 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1411 Self::validate_create_item_args(&target_collection, &data)?;1412 Self::create_item_no_validation(&target_collection, owner, data)?;14131414 Ok(())1415 }14161417 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1418 // Limits check1419 Self::is_correct_transfer(target_collection, &recipient)?;14201421 // Transfer permissions check1422 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1423 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1424 Error::<T>::NoPermission);14251426 if target_collection.access == AccessMode::WhiteList {1427 Self::check_white_list(target_collection, &sender)?;1428 Self::check_white_list(target_collection, &recipient)?;1429 }14301431 match target_collection.mode1432 {1433 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1434 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1435 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1436 _ => ()1437 };14381439 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));14401441 Ok(())1442 }14431444 pub fn approve_internal(1445 sender: T::AccountId,1446 spender: T::AccountId,1447 collection: &CollectionHandle<T>,1448 item_id: TokenId,1449 amount: u128,1450 ) -> DispatchResult {1451 Self::token_exists(&collection, item_id)?;14521453 // Transfer permissions check1454 let bypasses_limits = collection.limits.owner_can_transfer &&1455 Self::is_owner_or_admin_permissions(1456 &collection,1457 sender.clone(),1458 );14591460 let allowance_limit = if bypasses_limits {1461 None1462 } else if let Some(amount) = Self::owned_amount(1463 sender.clone(),1464 &collection,1465 item_id,1466 ) {1467 Some(amount)1468 } else {1469 fail!(Error::<T>::NoPermission);1470 };14711472 if collection.access == AccessMode::WhiteList {1473 Self::check_white_list(&collection, &sender)?;1474 Self::check_white_list(&collection, &spender)?;1475 }14761477 let allowance: u128 = amount1478 .checked_add(<Allowances<T>>::get(collection.id, (item_id, &sender, &spender)))1479 .ok_or(Error::<T>::NumOverflow)?;1480 if let Some(limit) = allowance_limit {1481 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1482 }1483 <Allowances<T>>::insert(collection.id, (item_id, &sender, &spender), allowance);14841485 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1486 Ok(())1487 }14881489 pub fn transfer_from_internal(1490 sender: T::AccountId,1491 from: T::AccountId,1492 recipient: T::AccountId,1493 collection: &CollectionHandle<T>,1494 item_id: TokenId,1495 amount: u128,1496 ) -> DispatchResult {1497 // Check approval1498 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, &from, &sender));14991500 // Limits check1501 Self::is_correct_transfer(&collection, &recipient)?;15021503 // Transfer permissions check1504 ensure!(1505 approval >= amount || 1506 (1507 collection.limits.owner_can_transfer &&1508 Self::is_owner_or_admin_permissions(&collection, sender.clone())1509 ),1510 Error::<T>::NoPermission1511 );15121513 if collection.access == AccessMode::WhiteList {1514 Self::check_white_list(&collection, &sender)?;1515 Self::check_white_list(&collection, &recipient)?;1516 }15171518 // Reduce approval by transferred amount or remove if remaining approval drops to 01519 let allowance = approval.saturating_sub(amount);1520 if allowance > 0 {1521 <Allowances<T>>::insert(collection.id, (item_id, &from, &sender), allowance);1522 } else {1523 <Allowances<T>>::remove(collection.id, (item_id, &from, &sender));1524 }15251526 match collection.mode {1527 CollectionMode::NFT => {1528 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1529 }1530 CollectionMode::Fungible(_) => {1531 Self::transfer_fungible(&collection, amount, &from, &recipient)?1532 }1533 CollectionMode::ReFungible => {1534 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1535 }1536 _ => ()1537 };15381539 Ok(())1540 }15411542 pub fn set_variable_meta_data_internal(1543 sender: T::AccountId,1544 collection: &CollectionHandle<T>, 1545 item_id: TokenId,1546 data: Vec<u8>,1547 ) -> DispatchResult {1548 Self::token_exists(&collection, item_id)?;15491550 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15511552 // Modify permissions check1553 ensure!(Self::is_item_owner(sender.clone(), &collection, item_id) ||1554 Self::is_owner_or_admin_permissions(&collection, sender.clone()),1555 Error::<T>::NoPermission);15561557 match collection.mode1558 {1559 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1560 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1561 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1562 _ => fail!(Error::<T>::UnexpectedCollectionType)1563 };15641565 Ok(())1566 }15671568 pub fn create_multiple_items_internal(1569 sender: T::AccountId,1570 collection: &CollectionHandle<T>,1571 owner: T::AccountId,1572 items_data: Vec<CreateItemData>,1573 ) -> DispatchResult {1574 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;15751576 for data in &items_data {1577 Self::validate_create_item_args(&collection, data)?;1578 }1579 for data in &items_data {1580 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1581 }15821583 Ok(())1584 }15851586 pub fn toggle_white_list_internal(1587 sender: &T::AccountId,1588 collection: &CollectionHandle<T>,1589 address: &T::AccountId,1590 whitelisted: bool,1591 ) -> DispatchResult {1592 Self::check_owner_or_admin_permissions(&collection, sender.clone())?;15931594 if whitelisted {1595 <WhiteList<T>>::insert(collection.id, address, true);1596 } else {1597 <WhiteList<T>>::remove(collection.id, address);1598 }15991600 Ok(())1601 }16021603 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1604 let collection_id = collection.id;16051606 // check token limit and account token limit1607 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1608 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1609 1610 Ok(())1611 }16121613 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1614 let collection_id = collection.id;16151616 // check token limit and account token limit1617 let total_items: u32 = ItemListIndex::get(collection_id)1618 .checked_add(amount)1619 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1620 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1621 .checked_add(amount)1622 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1623 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1624 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);16251626 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1627 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1628 Self::check_white_list(collection, owner)?;1629 Self::check_white_list(collection, sender)?;1630 }16311632 Ok(())1633 }16341635 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1636 match target_collection.mode1637 {1638 CollectionMode::NFT => {1639 if let CreateItemData::NFT(data) = data {1640 // check sizes1641 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1642 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1643 } else {1644 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1645 }1646 },1647 CollectionMode::Fungible(_) => {1648 if let CreateItemData::Fungible(_) = data {1649 } else {1650 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1651 }1652 },1653 CollectionMode::ReFungible => {1654 if let CreateItemData::ReFungible(data) = data {16551656 // check sizes1657 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1658 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16591660 // Check refungibility limits1661 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1662 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1663 } else {1664 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1665 }1666 },1667 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1668 };16691670 Ok(())1671 }16721673 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1674 match data1675 {1676 CreateItemData::NFT(data) => {1677 let item = NftItemType {1678 owner: owner.clone(),1679 const_data: data.const_data,1680 variable_data: data.variable_data1681 };16821683 Self::add_nft_item(collection, item)?;1684 },1685 CreateItemData::Fungible(data) => {1686 Self::add_fungible_item(collection, &owner, data.value)?;1687 },1688 CreateItemData::ReFungible(data) => {1689 let mut owner_list = Vec::new();1690 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16911692 let item = ReFungibleItemType {1693 owner: owner_list,1694 const_data: data.const_data,1695 variable_data: data.variable_data1696 };16971698 Self::add_refungible_item(collection, item)?;1699 }1700 };17011702 Ok(())1703 }17041705 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1706 let collection_id = collection.id;17071708 // Does new owner already have an account?1709 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;17101711 // Mint 1712 let item = FungibleItemType {1713 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1714 };1715 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);17161717 // Update balance1718 let new_balance = <Balance<T>>::get(collection_id, owner)1719 .checked_add(value)1720 .ok_or(Error::<T>::NumOverflow)?;1721 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17221723 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1724 Ok(())1725 }17261727 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1728 let collection_id = collection.id;17291730 let current_index = <ItemListIndex>::get(collection_id)1731 .checked_add(1)1732 .ok_or(Error::<T>::NumOverflow)?;1733 let itemcopy = item.clone();17341735 ensure!(1736 item.owner.len() == 1,1737 Error::<T>::BadCreateRefungibleCall,1738 );1739 let item_owner = item.owner.first().expect("only one owner is defined");17401741 let value = item_owner.fraction;1742 let owner = item_owner.owner.clone();17431744 Self::add_token_index(collection_id, current_index, &owner)?;17451746 <ItemListIndex>::insert(collection_id, current_index);1747 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17481749 // Update balance1750 let new_balance = <Balance<T>>::get(collection_id, &owner)1751 .checked_add(value)1752 .ok_or(Error::<T>::NumOverflow)?;1753 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17541755 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1756 Ok(())1757 }17581759 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {1760 let collection_id = collection.id;17611762 let current_index = <ItemListIndex>::get(collection_id)1763 .checked_add(1)1764 .ok_or(Error::<T>::NumOverflow)?;17651766 let item_owner = item.owner.clone();1767 Self::add_token_index(collection_id, current_index, &item.owner)?;17681769 <ItemListIndex>::insert(collection_id, current_index);1770 <NftItemList<T>>::insert(collection_id, current_index, item);17711772 // Update balance1773 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1774 .checked_add(1)1775 .ok_or(Error::<T>::NumOverflow)?;1776 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17771778 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1779 Ok(())1780 }17811782 fn burn_refungible_item(1783 collection: &CollectionHandle<T>,1784 item_id: TokenId,1785 owner: &T::AccountId,1786 ) -> DispatchResult {1787 let collection_id = collection.id;17881789 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1790 .ok_or(Error::<T>::TokenNotFound)?;1791 let rft_balance = token1792 .owner1793 .iter()1794 .find(|&i| i.owner == *owner)1795 .ok_or(Error::<T>::TokenNotFound)?;1796 Self::remove_token_index(collection_id, item_id, owner)?;17971798 // update balance1799 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1800 .checked_sub(rft_balance.fraction)1801 .ok_or(Error::<T>::NumOverflow)?;1802 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);18031804 // Re-create owners list with sender removed1805 let index = token1806 .owner1807 .iter()1808 .position(|i| i.owner == *owner)1809 .expect("owned item is exists");1810 token.owner.remove(index);1811 let owner_count = token.owner.len();18121813 // Burn the token completely if this was the last (only) owner1814 if owner_count == 0 {1815 <ReFungibleItemList<T>>::remove(collection_id, item_id);1816 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1817 }1818 else {1819 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1820 }18211822 Ok(())1823 }18241825 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1826 let collection_id = collection.id;18271828 let item = <NftItemList<T>>::get(collection_id, item_id)1829 .ok_or(Error::<T>::TokenNotFound)?;1830 Self::remove_token_index(collection_id, item_id, &item.owner)?;18311832 // update balance1833 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1834 .checked_sub(1)1835 .ok_or(Error::<T>::NumOverflow)?;1836 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1837 <NftItemList<T>>::remove(collection_id, item_id);1838 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18391840 Ok(())1841 }18421843 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1844 let collection_id = collection.id;18451846 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1847 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18481849 // update balance1850 let new_balance = <Balance<T>>::get(collection_id, owner)1851 .checked_sub(value)1852 .ok_or(Error::<T>::NumOverflow)?;1853 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18541855 if balance.value - value > 0 {1856 balance.value -= value;1857 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1858 }1859 else {1860 <FungibleItemList<T>>::remove(collection_id, owner);1861 }18621863 Ok(())1864 }18651866 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1867 Ok(<CollectionById<T>>::get(collection_id)1868 .map(|collection| CollectionHandle {1869 id: collection_id,1870 collection1871 })1872 .ok_or(Error::<T>::CollectionNotFound)?)1873 }18741875 fn save_collection(collection: CollectionHandle<T>) {1876 <CollectionById<T>>::insert(collection.id, collection.collection);1877 }18781879 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {1880 ensure!(1881 subject == target_collection.owner,1882 Error::<T>::NoPermission1883 );18841885 Ok(())1886 }18871888 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {1889 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1890 }18911892 fn check_owner_or_admin_permissions(1893 collection: &CollectionHandle<T>,1894 subject: T::AccountId,1895 ) -> DispatchResult {1896 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18971898 Ok(())1899 }19001901 fn owned_amount(1902 subject: T::AccountId,1903 target_collection: &CollectionHandle<T>,1904 item_id: TokenId,1905 ) -> Option<u128> {1906 let collection_id = target_collection.id;19071908 match target_collection.mode {1909 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)1910 .then(|| 1),1911 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)1912 .value),1913 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1914 .owner1915 .iter()1916 .find(|i| i.owner == subject)1917 .map(|i| i.fraction),1918 CollectionMode::Invalid => None,1919 }1920 }19211922 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1923 match target_collection.mode {1924 CollectionMode::Fungible(_) => true,1925 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1926 }1927 }19281929 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {1930 let collection_id = collection.id;19311932 let mes = Error::<T>::AddresNotInWhiteList;1933 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);19341935 Ok(())1936 }19371938 /// Check if token exists. In case of Fungible, check if there is an entry for 1939 /// the owner in fungible balances double map1940 fn token_exists(1941 target_collection: &CollectionHandle<T>,1942 item_id: TokenId,1943 ) -> DispatchResult {1944 let collection_id = target_collection.id;1945 let exists = match target_collection.mode1946 {1947 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1948 CollectionMode::Fungible(_) => true,1949 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1950 _ => false1951 };19521953 ensure!(exists == true, Error::<T>::TokenNotFound);1954 Ok(())1955 }19561957 fn transfer_fungible(1958 collection: &CollectionHandle<T>,1959 value: u128,1960 owner: &T::AccountId,1961 recipient: &T::AccountId,1962 ) -> DispatchResult {1963 let collection_id = collection.id;19641965 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1966 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19671968 // Send balance to recipient (updates balanceOf of recipient)1969 Self::add_fungible_item(collection, recipient, value)?;19701971 // update balanceOf of sender1972 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19731974 // Reduce or remove sender1975 if balance.value == value {1976 <FungibleItemList<T>>::remove(collection_id, owner);1977 }1978 else {1979 balance.value -= value;1980 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1981 }19821983 Ok(())1984 }19851986 fn transfer_refungible(1987 collection: &CollectionHandle<T>,1988 item_id: TokenId,1989 value: u128,1990 owner: T::AccountId,1991 new_owner: T::AccountId,1992 ) -> DispatchResult {1993 let collection_id = collection.id;1994 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1995 .ok_or(Error::<T>::TokenNotFound)?;19961997 let item = full_item1998 .owner1999 .iter()2000 .filter(|i| i.owner == owner)2001 .next()2002 .ok_or(Error::<T>::TokenNotFound)?;2003 let amount = item.fraction;20042005 ensure!(amount >= value, Error::<T>::TokenValueTooLow);20062007 // update balance2008 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2009 .checked_sub(value)2010 .ok_or(Error::<T>::NumOverflow)?;2011 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20122013 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2014 .checked_add(value)2015 .ok_or(Error::<T>::NumOverflow)?;2016 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20172018 let old_owner = item.owner.clone();2019 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20202021 // transfer2022 if amount == value && !new_owner_has_account {2023 // change owner2024 // new owner do not have account2025 let mut new_full_item = full_item.clone();2026 new_full_item2027 .owner2028 .iter_mut()2029 .find(|i| i.owner == owner)2030 .expect("old owner does present in refungible")2031 .owner = new_owner.clone();2032 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20332034 // update index collection2035 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2036 } else {2037 let mut new_full_item = full_item.clone();2038 new_full_item2039 .owner2040 .iter_mut()2041 .find(|i| i.owner == owner)2042 .expect("old owner does present in refungible")2043 .fraction -= value;20442045 // separate amount2046 if new_owner_has_account {2047 // new owner has account2048 new_full_item2049 .owner2050 .iter_mut()2051 .find(|i| i.owner == new_owner)2052 .expect("new owner has account")2053 .fraction += value;2054 } else {2055 // new owner do not have account2056 new_full_item.owner.push(Ownership {2057 owner: new_owner.clone(),2058 fraction: value,2059 });2060 Self::add_token_index(collection_id, item_id, &new_owner)?;2061 }20622063 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2064 }20652066 Ok(())2067 }20682069 fn transfer_nft(2070 collection: &CollectionHandle<T>,2071 item_id: TokenId,2072 sender: T::AccountId,2073 new_owner: T::AccountId,2074 ) -> DispatchResult {2075 let collection_id = collection.id;2076 let mut item = <NftItemList<T>>::get(collection_id, item_id)2077 .ok_or(Error::<T>::TokenNotFound)?;20782079 ensure!(2080 sender == item.owner,2081 Error::<T>::MustBeTokenOwner2082 );20832084 // update balance2085 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2086 .checked_sub(1)2087 .ok_or(Error::<T>::NumOverflow)?;2088 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20892090 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2091 .checked_add(1)2092 .ok_or(Error::<T>::NumOverflow)?;2093 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20942095 // change owner2096 let old_owner = item.owner.clone();2097 item.owner = new_owner.clone();2098 <NftItemList<T>>::insert(collection_id, item_id, item);20992100 // update index collection2101 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21022103 Ok(())2104 }2105 2106 fn set_re_fungible_variable_data(2107 collection: &CollectionHandle<T>,2108 item_id: TokenId,2109 data: Vec<u8>2110 ) -> DispatchResult {2111 let collection_id = collection.id;2112 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2113 .ok_or(Error::<T>::TokenNotFound)?;21142115 item.variable_data = data;21162117 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21182119 Ok(())2120 }21212122 fn set_nft_variable_data(2123 collection: &CollectionHandle<T>,2124 item_id: TokenId,2125 data: Vec<u8>2126 ) -> DispatchResult {2127 let collection_id = collection.id;2128 let mut item = <NftItemList<T>>::get(collection_id, item_id)2129 .ok_or(Error::<T>::TokenNotFound)?;2130 2131 item.variable_data = data;21322133 <NftItemList<T>>::insert(collection_id, item_id, item);2134 2135 Ok(())2136 }21372138 #[allow(dead_code)]2139 fn init_collection(item: &Collection<T>) {2140 // check params2141 assert!(2142 item.decimal_points <= MAX_DECIMAL_POINTS,2143 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2144 );2145 assert!(2146 item.name.len() <= 64,2147 "Collection name can not be longer than 63 char"2148 );2149 assert!(2150 item.name.len() <= 256,2151 "Collection description can not be longer than 255 char"2152 );2153 assert!(2154 item.token_prefix.len() <= 16,2155 "Token prefix can not be longer than 15 char"2156 );21572158 // Generate next collection ID2159 let next_id = CreatedCollectionCount::get()2160 .checked_add(1)2161 .unwrap();21622163 CreatedCollectionCount::put(next_id);2164 }21652166 #[allow(dead_code)]2167 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2168 let current_index = <ItemListIndex>::get(collection_id)2169 .checked_add(1)2170 .unwrap();21712172 let item_owner = item.owner.clone();2173 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21742175 <ItemListIndex>::insert(collection_id, current_index);21762177 // Update balance2178 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2179 .checked_add(1)2180 .unwrap();2181 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2182 }21832184 #[allow(dead_code)]2185 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2186 let current_index = <ItemListIndex>::get(collection_id)2187 .checked_add(1)2188 .unwrap();21892190 Self::add_token_index(collection_id, current_index, owner).unwrap();21912192 <ItemListIndex>::insert(collection_id, current_index);21932194 // Update balance2195 let new_balance = <Balance<T>>::get(collection_id, owner)2196 .checked_add(item.value)2197 .unwrap();2198 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2199 }22002201 #[allow(dead_code)]2202 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2203 let current_index = <ItemListIndex>::get(collection_id)2204 .checked_add(1)2205 .unwrap();22062207 let value = item.owner.first().unwrap().fraction;2208 let owner = item.owner.first().unwrap().owner.clone();22092210 Self::add_token_index(collection_id, current_index, &owner).unwrap();22112212 <ItemListIndex>::insert(collection_id, current_index);22132214 // Update balance2215 let new_balance = <Balance<T>>::get(collection_id, &owner)2216 .checked_add(value)2217 .unwrap();2218 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2219 }22202221 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2222 // add to account limit2223 if <AccountItemCount<T>>::contains_key(owner) {22242225 // bound Owned tokens by a single address2226 let count = <AccountItemCount<T>>::get(owner);2227 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);22282229 <AccountItemCount<T>>::insert(owner.clone(), count2230 .checked_add(1)2231 .ok_or(Error::<T>::NumOverflow)?);2232 }2233 else {2234 <AccountItemCount<T>>::insert(owner.clone(), 1);2235 }22362237 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2238 if list_exists {2239 let mut list = <AddressTokens<T>>::get(collection_id, owner);2240 let item_contains = list.contains(&item_index.clone());22412242 if !item_contains {2243 list.push(item_index.clone());2244 }22452246 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2247 } else {2248 let mut itm = Vec::new();2249 itm.push(item_index.clone());2250 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2251 }22522253 Ok(())2254 }22552256 fn remove_token_index(2257 collection_id: CollectionId,2258 item_index: TokenId,2259 owner: &T::AccountId,2260 ) -> DispatchResult {22612262 // update counter2263 <AccountItemCount<T>>::insert(owner.clone(), 2264 <AccountItemCount<T>>::get(owner)2265 .checked_sub(1)2266 .ok_or(Error::<T>::NumOverflow)?);226722682269 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2270 if list_exists {2271 let mut list = <AddressTokens<T>>::get(collection_id, owner);2272 let item_contains = list.contains(&item_index.clone());22732274 if item_contains {2275 list.retain(|&item| item != item_index);2276 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2277 }2278 }22792280 Ok(())2281 }22822283 fn move_token_index(2284 collection_id: CollectionId,2285 item_index: TokenId,2286 old_owner: &T::AccountId,2287 new_owner: &T::AccountId,2288 ) -> DispatchResult {2289 Self::remove_token_index(collection_id, item_index, old_owner)?;2290 Self::add_token_index(collection_id, item_index, new_owner)?;22912292 Ok(())2293 }2294 2295 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2296 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22972298 Ok(())2299 }2300}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::*;
use crate::Vec;
@@ -80,7 +81,7 @@
pub type NftWeightInfoOf<C> = <C as pallet_nft::Config>::WeightInfo;
-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;
@@ -706,6 +708,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,