difftreelog
Merge pull request #579 from UniqueNetwork/doc/app-promotion
in: master
added `doc` to app promotion
16 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5733,7 +5733,7 @@
[[package]]
name = "pallet-evm-contract-helpers"
-version = "0.2.0"
+version = "0.3.0"
dependencies = [
"ethereum",
"evm-coder",
@@ -12316,7 +12316,7 @@
[[package]]
name = "unique-rpc"
-version = "0.1.1"
+version = "0.1.2"
dependencies = [
"app-promotion-rpc",
"fc-db",
node/cli/CHANGELOG.mddiffbeforeafterboth--- a/node/cli/CHANGELOG.md
+++ b/node/cli/CHANGELOG.md
@@ -1,4 +1,10 @@
<!-- bureaucrate goes here -->
+
+## [v0.9.27] 2022-09-08
+
+### Added
+- Support RPC for `AppPromotion` pallet.
+
## [v0.9.27] 2022-08-16
### Other changes
node/rpc/CHANGELOG.mddiffbeforeafterboth--- a/node/rpc/CHANGELOG.md
+++ b/node/rpc/CHANGELOG.md
@@ -1,4 +1,9 @@
<!-- bureaucrate goes here -->
+## [v0.1.2] 2022-09-08
+
+### Added
+- Support RPC for `AppPromotion` pallet.
+
## [v0.1.1] 2022-08-16
### Other changes
node/rpc/Cargo.tomldiffbeforeafterboth--- a/node/rpc/Cargo.toml
+++ b/node/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "unique-rpc"
-version = "0.1.1"
+version = "0.1.2"
authors = ['Unique Network <support@uniquenetwork.io>']
license = 'GPLv3'
edition = "2021"
pallets/app-promotion/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/benchmarking.rs
+++ b/pallets/app-promotion/src/benchmarking.rs
@@ -112,6 +112,7 @@
let share = Perbill::from_rational(1u32, 20);
let _ = <T as Config>::Currency::make_free_balance_be(&caller, Perbill::from_rational(1u32, 2) * BalanceOf::<T>::max_value());
(0..10).map(|_| {
+ // used to change block number
<frame_system::Pallet<T>>::finalize();
PromototionPallet::<T>::stake(RawOrigin::Signed(caller.clone()).into(), share * <T as Config>::Currency::total_balance(&caller))
}).collect::<Result<Vec<_>, _>>()?;
pallets/app-promotion/src/lib.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -14,13 +14,34 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-//! # App promotion
+//! # App Promotion pallet
+//!
+//! The pallet implements the mechanics of staking and sponsoring collections/contracts.
//!
-//! The app promotion pallet is designed to ... .
+//! - [`Config`]
+//! - [`Pallet`]
+//! - [`Error`]
+//! - [`Event`]
+//!
+//! ## Overview
+//! The App Promotion pallet allows fund holders to stake at a certain daily rate of return.
+//! The mechanics implemented in the pallet allow it to act as a sponsor for collections / contracts,
+//! the list of which is set by the pallet administrator.
+//!
//!
//! ## Interface
+//! The pallet provides interfaces for funds, collection/contract operations (see [types] module).
+
//!
//! ### Dispatchable Functions
+//! - [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.
+//! - [`stake`][`Pallet::stake`] - stakes the amount of native tokens.
+//! - [`unstake`][`Pallet::unstake`] - unstakes all stakes.
+//! - [`sponsor_collection`][`Pallet::sponsor_collection`] - sets the pallet to be the sponsor for the collection.
+//! - [`stop_sponsoring_collection`][`Pallet::stop_sponsoring_collection`] - removes the pallet as the sponsor for the collection.
+//! - [`sponsor_contract`][`Pallet::sponsor_contract`] - sets the pallet to be the sponsor for the contract.
+//! - [`stop_sponsoring_contract`][`Pallet::stop_sponsoring_contract`] - removes the pallet as the sponsor for the contract.
+//! - [`payout_stakers`][`Pallet::payout_stakers`] - recalculates interest for the specified number of stakers.
//!
// #![recursion_limit = "1024"]
@@ -95,10 +116,10 @@
/// Type for interacting with conrtacts
type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;
- /// ID for treasury
+ /// `AccountId` for treasury
type TreasuryAccountId: Get<Self::AccountId>;
- /// The app's pallet id, used for deriving its sovereign account ID.
+ /// The app's pallet id, used for deriving its sovereign account address.
#[pallet::constant]
type PalletId: Get<PalletId>;
@@ -138,7 +159,7 @@
/// Staking recalculation was performed
///
/// # Arguments
- /// * AccountId: ID of the staker.
+ /// * AccountId: account of the staker.
/// * Balance : recalculation base
/// * Balance : total income
StakingRecalculation(
@@ -153,21 +174,21 @@
/// Staking was performed
///
/// # Arguments
- /// * AccountId: ID of the staker
+ /// * AccountId: account of the staker
/// * Balance : staking amount
Stake(T::AccountId, BalanceOf<T>),
/// Unstaking was performed
///
/// # Arguments
- /// * AccountId: ID of the staker
+ /// * AccountId: account of the staker
/// * Balance : unstaking amount
Unstake(T::AccountId, BalanceOf<T>),
/// The admin was set
///
/// # Arguments
- /// * AccountId: ID of the admin
+ /// * AccountId: account address of the admin
SetAdmin(T::AccountId),
}
@@ -187,13 +208,20 @@
IncorrectLockedBalanceOperation,
}
+ /// Stores the total staked amount.
#[pallet::storage]
pub type TotalStaked<T: Config> = StorageValue<Value = BalanceOf<T>, QueryKind = ValueQuery>;
+ /// Stores the `admin` account. Some extrinsics can only be executed if they were signed by `admin`.
#[pallet::storage]
pub type Admin<T: Config> = StorageValue<Value = T::AccountId, QueryKind = OptionQuery>;
- /// Amount of tokens staked by account in the blocknumber.
+ /// Stores the amount of tokens staked by account in the blocknumber.
+ ///
+ /// * **Key1** - Staker account.
+ /// * **Key2** - Relay block number when the stake was made.
+ /// * **(Balance, BlockNumber)** - Balance of the stake.
+ /// The number of the relay block in which we must perform the interest recalculation
#[pallet::storage]
pub type Staked<T: Config> = StorageNMap<
Key = (
@@ -203,11 +231,19 @@
Value = (BalanceOf<T>, T::BlockNumber),
QueryKind = ValueQuery,
>;
- /// Amount of stakes for an Account
+
+ /// Stores amount of stakes for an `Account`.
+ ///
+ /// * **Key** - Staker account.
+ /// * **Value** - Amount of stakes.
#[pallet::storage]
pub type StakesPerAccount<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, u8, ValueQuery>;
+ /// Stores amount of stakes for an `Account`.
+ ///
+ /// * **Key** - Staker account.
+ /// * **Value** - Amount of stakes.
#[pallet::storage]
pub type PendingUnstake<T: Config> = StorageMap<
_,
@@ -252,6 +288,15 @@
T::BlockNumber: From<u32> + Into<u32>,
<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum + From<u128>,
{
+ /// Sets an address as the the admin.
+ ///
+ /// # Permissions
+ ///
+ /// * Sudo
+ ///
+ /// # Arguments
+ ///
+ /// * `admin`: account of the new admin.
#[pallet::weight(T::WeightInfo::set_admin_address())]
pub fn set_admin_address(origin: OriginFor<T>, admin: T::CrossAccountId) -> DispatchResult {
ensure_root(origin)?;
@@ -263,6 +308,13 @@
Ok(())
}
+ /// Stakes the amount of native tokens.
+ /// Sets `amount` to the locked state.
+ /// The maximum number of stakes for a staker is 10.
+ ///
+ /// # Arguments
+ ///
+ /// * `amount`: in native tokens.
#[pallet::weight(T::WeightInfo::stake())]
pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
let staker_id = ensure_signed(staker)?;
@@ -280,6 +332,7 @@
let balance =
<<T as Config>::Currency as Currency<T::AccountId>>::free_balance(&staker_id);
+ // checks that we can lock `amount` on the `staker` account.
<<T as Config>::Currency as Currency<T::AccountId>>::ensure_can_withdraw(
&staker_id,
amount,
@@ -293,6 +346,8 @@
let block_number = T::RelayBlockNumberProvider::current_block_number();
+ // Calculation of the number of recalculation periods,
+ // after how much the first interest calculation should be performed for the stake
let recalculate_after_interval: T::BlockNumber =
if block_number % T::RecalculationInterval::get() == 0u32.into() {
1u32.into()
@@ -300,6 +355,8 @@
2u32.into()
};
+ // Сalculation of the number of the relay block
+ // in which it is necessary to accrue remuneration for the stake.
let recalc_block = (block_number / T::RecalculationInterval::get()
+ recalculate_after_interval)
* T::RecalculationInterval::get();
@@ -327,12 +384,20 @@
Ok(())
}
+ /// Unstakes all stakes.
+ /// Moves the sum of all stakes to the `reserved` state.
+ /// After the end of `PendingInterval` this sum becomes completely
+ /// free for further use.
#[pallet::weight(T::WeightInfo::unstake())]
pub fn unstake(staker: OriginFor<T>) -> DispatchResultWithPostInfo {
let staker_id = ensure_signed(staker)?;
+
+ // calculate block number where the sum would be free
let block = <frame_system::Pallet<T>>::block_number() + T::PendingInterval::get();
+
let mut pendings = <PendingUnstake<T>>::get(block);
+ // checks that we can do unreserve stakes in the block
ensure!(!pendings.is_full(), Error::<T>::PendingForBlockOverflow);
let mut total_stakes = 0u64;
@@ -371,6 +436,15 @@
Ok(None.into())
}
+ /// Sets the pallet to be the sponsor for the collection.
+ ///
+ /// # Permissions
+ ///
+ /// * Pallet admin
+ ///
+ /// # Arguments
+ ///
+ /// * `collection_id`: ID of the collection that will be sponsored by `pallet_id`
#[pallet::weight(T::WeightInfo::sponsor_collection())]
pub fn sponsor_collection(
admin: OriginFor<T>,
@@ -384,6 +458,18 @@
T::CollectionHandler::set_sponsor(Self::account_id(), collection_id)
}
+
+ /// Removes the pallet as the sponsor for the collection.
+ /// Returns [`NoPermission`][`Error::NoPermission`]
+ /// if the pallet wasn't the sponsor.
+ ///
+ /// # Permissions
+ ///
+ /// * Pallet admin
+ ///
+ /// # Arguments
+ ///
+ /// * `collection_id`: ID of the collection that is sponsored by `pallet_id`
#[pallet::weight(T::WeightInfo::stop_sponsoring_collection())]
pub fn stop_sponsoring_collection(
admin: OriginFor<T>,
@@ -404,6 +490,15 @@
T::CollectionHandler::remove_collection_sponsor(collection_id)
}
+ /// Sets the pallet to be the sponsor for the contract.
+ ///
+ /// # Permissions
+ ///
+ /// * Pallet admin
+ ///
+ /// # Arguments
+ ///
+ /// * `contract_id`: the contract address that will be sponsored by `pallet_id`
#[pallet::weight(T::WeightInfo::sponsor_contract())]
pub fn sponsor_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
let admin_id = ensure_signed(admin)?;
@@ -419,6 +514,17 @@
)
}
+ /// Removes the pallet as the sponsor for the contract.
+ /// Returns [`NoPermission`][`Error::NoPermission`]
+ /// if the pallet wasn't the sponsor.
+ ///
+ /// # Permissions
+ ///
+ /// * Pallet admin
+ ///
+ /// # Arguments
+ ///
+ /// * `contract_id`: the contract address that is sponsored by `pallet_id`
#[pallet::weight(T::WeightInfo::stop_sponsoring_contract())]
pub fn stop_sponsoring_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
let admin_id = ensure_signed(admin)?;
@@ -437,6 +543,18 @@
T::ContractHandler::remove_contract_sponsor(contract_id)
}
+ /// Recalculates interest for the specified number of stakers.
+ /// If all stakers are not recalculated, the next call of the extrinsic
+ /// will continue the recalculation, from those stakers for whom this
+ /// was not perform in last call.
+ ///
+ /// # Permissions
+ ///
+ /// * Pallet admin
+ ///
+ /// # Arguments
+ ///
+ /// * `stakers_number`: the number of stakers for which recalculation will be performed
#[pallet::weight(T::WeightInfo::payout_stakers(stakers_number.unwrap_or(20) as u32))]
pub fn payout_stakers(admin: OriginFor<T>, stakers_number: Option<u8>) -> DispatchResult {
let admin_id = ensure_signed(admin)?;
@@ -446,63 +564,19 @@
Error::<T>::NoPermission
);
+ // calculate the number of the current recalculation block,
+ // this is necessary in order to understand which stakers we should calculate interest
let current_recalc_block =
Self::get_current_recalc_block(T::RelayBlockNumberProvider::current_block_number());
+
+ // calculate the number of the next recalculation block,
+ // this value is set for the stakers to whom the recalculation will be performed
let next_recalc_block = current_recalc_block + T::RecalculationInterval::get();
let mut storage_iterator = Self::get_next_calculated_key()
.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));
NextCalculatedRecord::<T>::set(None);
-
- // {
- // let mut stakers_number = stakers_number.unwrap_or(20);
- // let mut last_id = admin_id;
- // let mut income_acc = BalanceOf::<T>::default();
- // let mut amount_acc = BalanceOf::<T>::default();
-
- // while let Some((
- // (current_id, staked_block),
- // (amount, next_recalc_block_for_stake),
- // )) = storage_iterator.next()
- // {
- // if last_id != current_id {
- // if income_acc != BalanceOf::<T>::default() {
- // <T::Currency as Currency<T::AccountId>>::transfer(
- // &T::TreasuryAccountId::get(),
- // &last_id,
- // income_acc,
- // ExistenceRequirement::KeepAlive,
- // )
- // .and_then(|_| Self::add_lock_balance(&last_id, income_acc))?;
-
- // Self::deposit_event(Event::StakingRecalculation(
- // last_id, amount, income_acc,
- // ));
- // }
-
- // if stakers_number == 0 {
- // NextCalculatedRecord::<T>::set(Some((current_id, staked_block)));
- // break;
- // }
- // stakers_number -= 1;
- // income_acc = BalanceOf::<T>::default();
- // last_id = current_id;
- // };
- // if current_recalc_block >= next_recalc_block_for_stake {
- // Self::recalculate_and_insert_stake(
- // &last_id,
- // staked_block,
- // next_recalc_block,
- // amount,
- // ((current_recalc_block - next_recalc_block_for_stake)
- // / T::RecalculationInterval::get())
- // .into() + 1,
- // &mut income_acc,
- // );
- // }
- // }
- // }
{
let mut stakers_number = stakers_number.unwrap_or(20);
@@ -510,6 +584,8 @@
let income_acc = RefCell::new(BalanceOf::<T>::default());
let amount_acc = RefCell::new(BalanceOf::<T>::default());
+ // this closure is used to perform some of the actions if we break the loop because we reached the number of stakers for recalculation,
+ // but there were unrecalculated records in the storage.
let flush_stake = || -> DispatchResult {
if let Some(last_id) = &*last_id.borrow() {
if !income_acc.borrow().is_zero() {
@@ -578,10 +654,18 @@
}
impl<T: Config> Pallet<T> {
+ /// The account address of the app promotion pot.
+ ///
+ /// This actually does computation. If you need to keep using it, then make sure you cache the
+ /// value and only call this once.
pub fn account_id() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
+ /// Unlocks the balance that was locked by the pallet.
+ ///
+ /// - `staker`: staker account.
+ /// - `amount`: amount of unlocked funds.
fn unlock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
let locked_balance = Self::get_locked_balance(staker)
.map(|l| l.amount)
@@ -598,6 +682,10 @@
Ok(())
}
+ /// Adds the balance to locked by the pallet.
+ ///
+ /// - `staker`: staker account.
+ /// - `amount`: amount of added locked funds.
fn add_lock_balance(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
Self::get_locked_balance(staker)
.map_or(<BalanceOf<T>>::default(), |l| l.amount)
@@ -606,6 +694,10 @@
.ok_or(ArithmeticError::Overflow.into())
}
+ /// Sets the new state of a balance locked by the pallet.
+ ///
+ /// - `staker`: staker account.
+ /// - `amount`: amount of locked funds.
fn set_lock_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
if amount.is_zero() {
<T::Currency as LockableCurrency<T::AccountId>>::remove_lock(LOCK_IDENTIFIER, &staker);
@@ -619,6 +711,9 @@
}
}
+ /// Returns the balance locked by the pallet for the staker.
+ ///
+ /// - `staker`: staker account.
pub fn get_locked_balance(
staker: impl EncodeLike<T::AccountId>,
) -> Option<BalanceLock<BalanceOf<T>>> {
@@ -627,6 +722,9 @@
.find(|l| l.id == LOCK_IDENTIFIER)
}
+ /// Returns the total staked balance for the staker.
+ ///
+ /// - `staker`: staker account.
pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {
let staked = Staked::<T>::iter_prefix((staker,))
.into_iter()
@@ -640,6 +738,10 @@
}
}
+ /// Returns all relay block numbers when stake was made,
+ /// the amount of the stake.
+ ///
+ /// - `staker`: staker account.
pub fn total_staked_by_id_per_block(
staker: impl EncodeLike<T::AccountId>,
) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {
@@ -655,6 +757,9 @@
}
}
+ /// Returns the total staked balance for the staker.
+ /// If `staker` is `None`, returns the total amount staked.
+ /// - `staker`: staker account.
pub fn cross_id_total_staked(staker: Option<T::CrossAccountId>) -> Option<BalanceOf<T>> {
staker.map_or(Some(<TotalStaked<T>>::get()), |s| {
Self::total_staked_by_id(s.as_sub())
@@ -667,6 +772,10 @@
// .unwrap_or_default()
// }
+ /// Returns all relay block numbers when stake was made,
+ /// the amount of the stake.
+ ///
+ /// - `staker`: staker account.
pub fn cross_id_total_staked_per_block(
staker: T::CrossAccountId,
) -> Vec<(T::BlockNumber, BalanceOf<T>)> {
@@ -703,10 +812,6 @@
fn get_current_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {
(current_relay_block / T::RecalculationInterval::get()) * T::RecalculationInterval::get()
}
-
- // fn get_next_recalc_block(current_relay_block: T::BlockNumber) -> T::BlockNumber {
- // Self::get_current_recalc_block(current_relay_block) + T::RecalculationInterval::get()
- // }
fn get_next_calculated_key() -> Option<Vec<u8>> {
Self::get_next_calculated_record().map(|key| Staked::<T>::hashed_key_for(key))
@@ -717,6 +822,11 @@
where
<<T as Config>::Currency as Currency<T::AccountId>>::Balance: Sum,
{
+ /// Returns the amount reserved by the pending.
+ /// If `staker` is `None`, returns the total pending.
+ ///
+ /// -`staker`: staker account.
+ ///
/// Since user funds are not transferred anywhere by staking, overflow protection is provided
/// at the level of the associated type `Balance` of `Currency` trait. In order to overflow,
/// the staker must have more funds on his account than the maximum set for `Balance` type.
@@ -740,6 +850,10 @@
)
}
+ /// Returns all parachain block numbers when unreserve is expected,
+ /// the amount of the unreserved funds.
+ ///
+ /// - `staker`: staker account.
pub fn cross_id_pending_unstake_per_block(
staker: T::CrossAccountId,
) -> Vec<(T::BlockNumber, BalanceOf<T>)> {
pallets/app-promotion/src/types.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -9,7 +9,10 @@
use sp_std::borrow::ToOwned;
use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig};
+/// This trait was defined because `LockableCurrency`
+/// has no way to know the state of the lock for an account.
pub trait ExtendedLockableCurrency<AccountId: Parameter>: LockableCurrency<AccountId> {
+ /// Returns lock balance for an account. Allows to determine the cause of the lock.
fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
where
KArg: EncodeLike<AccountId>;
@@ -25,18 +28,28 @@
Self::locks(who)
}
}
-
+/// Trait for interacting with collections.
pub trait CollectionHandler {
type CollectionId;
type AccountId;
+ /// Sets sponsor for a collection.
+ ///
+ /// - `sponsor_id`: the account of the sponsor-to-be.
+ /// - `collection_id`: ID of the modified collection.
fn set_sponsor(
sponsor_id: Self::AccountId,
collection_id: Self::CollectionId,
) -> DispatchResult;
+ /// Removes sponsor for a collection.
+ ///
+ /// - `collection_id`: ID of the modified collection.
fn remove_collection_sponsor(collection_id: Self::CollectionId) -> DispatchResult;
+ /// Retuns the current sponsor for a collection if one is set.
+ ///
+ /// - `collection_id`: ID of the collection.
fn sponsor(collection_id: Self::CollectionId)
-> Result<Option<Self::AccountId>, DispatchError>;
}
@@ -66,18 +79,28 @@
.map(|acc| acc.to_owned()))
}
}
-
+/// Trait for interacting with contracts.
pub trait ContractHandler {
type ContractId;
type AccountId;
+ /// Sets sponsor for a contract.
+ ///
+ /// - `sponsor_id`: the account of the sponsor-to-be.
+ /// - `contract_address`: the address of the modified contract.
fn set_sponsor(
sponsor_id: Self::AccountId,
contract_address: Self::ContractId,
) -> DispatchResult;
+ /// Removes sponsor for a contract.
+ ///
+ /// - `contract_address`: the address of the modified contract.
fn remove_contract_sponsor(contract_address: Self::ContractId) -> DispatchResult;
+ /// Retuns the current sponsor for a contract if one is set.
+ ///
+ /// - `contract_address`: the contract address.
fn sponsor(
contract_address: Self::ContractId,
) -> Result<Option<Self::AccountId>, DispatchError>;
pallets/evm-contract-helpers/CHANGELOG.mddiffbeforeafterboth--- a/pallets/evm-contract-helpers/CHANGELOG.md
+++ b/pallets/evm-contract-helpers/CHANGELOG.md
@@ -2,29 +2,35 @@
All notable changes to this project will be documented in this file.
+## [v0.3.0] 2022-09-05
+
+### Added
+
+- Methods `force_set_sponsor` , `force_remove_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
+
## [v0.2.0] - 2022-08-19
### Added
- - Set arbitrary evm address as contract sponsor.
- - Ability to remove current sponsor.
+- Set arbitrary evm address as contract sponsor.
+- Ability to remove current sponsor.
### Removed
- - Remove methods
- + sponsoring_enabled
- + toggle_sponsoring
- ### Changed
+- Remove methods
+ - sponsoring_enabled
+ - toggle_sponsoring
- - Change `toggle_sponsoring` to `self_sponsored_enable`.
+### Changed
+- Change `toggle_sponsoring` to `self_sponsored_enable`.
## [v0.1.2] 2022-08-16
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
\ No newline at end of file
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
pallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-evm-contract-helpers"
-version = "0.2.0"
+version = "0.3.0"
license = "GPLv3"
edition = "2021"
pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -216,9 +216,10 @@
Ok(())
}
- /// TO-DO
- ///
+ /// Force set `sponsor` for `contract`.
///
+ /// Differs from `set_sponsor` in that confirmation
+ /// from the sponsor is not required.
pub fn force_set_sponsor(
contract_address: H160,
sponsor: &T::CrossAccountId,
@@ -269,9 +270,10 @@
Self::force_remove_sponsor(contract_address)
}
- /// TO-DO
- ///
+ /// Force remove `sponsor` for `contract`.
///
+ /// Differs from `remove_sponsor` in that
+ /// it doesn't require consent from the `owner` of the contract.
pub fn force_remove_sponsor(contract_address: H160) -> DispatchResult {
Sponsoring::<T>::remove(contract_address);
pallets/unique/CHANGELOG.mddiffbeforeafterboth--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -4,7 +4,7 @@
<!-- bureaucrate goes here -->
-## [v0.1.4] 2022-09-5
+## [v0.1.4] 2022-09-05
### Added
pallets/unique/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69 clippy::too_many_arguments,70 clippy::unnecessary_mut_passed,71 clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77 decl_module, decl_storage, decl_error, decl_event,78 dispatch::DispatchResult,79 ensure, fail,80 weights::{Weight},81 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82 BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_runtime::{sp_std::prelude::Vec};87use up_data_structs::{88 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,90 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,91 PropertyKeyPermission,92};93use pallet_evm::account::CrossAccountId;94use pallet_common::{95 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,96 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,97};98pub mod eth;99100#[cfg(feature = "runtime-benchmarks")]101pub mod benchmarking;102pub mod weights;103use weights::WeightInfo;104105/// Maximum number of levels of depth in the token nesting tree.106pub const NESTING_BUDGET: u32 = 5;107108decl_error! {109 /// Errors for the common Unique transactions.110 pub enum Error for Module<T: Config> {111 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].112 CollectionDecimalPointLimitExceeded,113 /// This address is not set as sponsor, use setCollectionSponsor first.114 ConfirmUnsetSponsorFail,115 /// Length of items properties must be greater than 0.116 EmptyArgument,117 /// Repertition is only supported by refungible collection.118 RepartitionCalledOnNonRefungibleCollection,119 }120}121122/// Configuration trait of this pallet.123pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {124 /// Overarching event type.125 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;126127 /// Weight information for extrinsics in this pallet.128 type WeightInfo: WeightInfo;129130 /// Weight information for common pallet operations.131 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;132133 /// Weight info information for extra refungible pallet operations.134 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;135}136137decl_event! {138 pub enum Event<T>139 where140 <T as frame_system::Config>::AccountId,141 <T as pallet_evm::account::Config>::CrossAccountId,142 {143 /// Collection sponsor was removed144 ///145 /// # Arguments146 /// * collection_id: ID of the affected collection.147 CollectionSponsorRemoved(CollectionId),148149 /// Collection admin was added150 ///151 /// # Arguments152 /// * collection_id: ID of the affected collection.153 /// * admin: Admin address.154 CollectionAdminAdded(CollectionId, CrossAccountId),155156 /// Collection owned was changed157 ///158 /// # Arguments159 /// * collection_id: ID of the affected collection.160 /// * owner: New owner address.161 CollectionOwnedChanged(CollectionId, AccountId),162163 /// Collection sponsor was set164 ///165 /// # Arguments166 /// * collection_id: ID of the affected collection.167 /// * owner: New sponsor address.168 CollectionSponsorSet(CollectionId, AccountId),169170 /// New sponsor was confirm171 ///172 /// # Arguments173 /// * collection_id: ID of the affected collection.174 /// * sponsor: New sponsor address.175 SponsorshipConfirmed(CollectionId, AccountId),176177 /// Collection admin was removed178 ///179 /// # Arguments180 /// * collection_id: ID of the affected collection.181 /// * admin: Removed admin address.182 CollectionAdminRemoved(CollectionId, CrossAccountId),183184 /// Address was removed from the allow list185 ///186 /// # Arguments187 /// * collection_id: ID of the affected collection.188 /// * user: Address of the removed account.189 AllowListAddressRemoved(CollectionId, CrossAccountId),190191 /// Address was added to the allow list192 ///193 /// # Arguments194 /// * collection_id: ID of the affected collection.195 /// * user: Address of the added account.196 AllowListAddressAdded(CollectionId, CrossAccountId),197198 /// Collection limits were set199 ///200 /// # Arguments201 /// * collection_id: ID of the affected collection.202 CollectionLimitSet(CollectionId),203204 /// Collection permissions were set205 ///206 /// # Arguments207 /// * collection_id: ID of the affected collection.208 CollectionPermissionSet(CollectionId),209 }210}211212type SelfWeightOf<T> = <T as Config>::WeightInfo;213214// # Used definitions215//216// ## User control levels217//218// chain-controlled - key is uncontrolled by user219// i.e autoincrementing index220// can use non-cryptographic hash221// real - key is controlled by user222// but it is hard to generate enough colliding values, i.e owner of signed txs223// can use non-cryptographic hash224// controlled - key is completly controlled by users225// i.e maps with mutable keys226// should use cryptographic hash227//228// ## User control level downgrade reasons229//230// ?1 - chain-controlled -> controlled231// collections/tokens can be destroyed, resulting in massive holes232// ?2 - chain-controlled -> controlled233// same as ?1, but can be only added, resulting in easier exploitation234// ?3 - real -> controlled235// no confirmation required, so addresses can be easily generated236decl_storage! {237 trait Store for Module<T: Config> as Unique {238239 //#region Private members240 /// Used for migrations241 ChainVersion: u64;242 //#endregion243244 //#region Tokens transfer sponosoring rate limit baskets245 /// (Collection id (controlled?2), who created (real))246 /// TODO: Off chain worker should remove from this map when collection gets removed247 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;248 /// Collection id (controlled?2), token id (controlled?2)249 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250 /// Collection id (controlled?2), owning user (real)251 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252 /// Collection id (controlled?2), token id (controlled?2)253 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 //#endregion255256 /// Variable metadata sponsoring257 /// Collection id (controlled?2), token id (controlled?2)258 #[deprecated]259 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;260 /// Last sponsoring of token property setting // todo:doc rephrase this and the following261 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262263 /// Last sponsoring of NFT approval in a collection264 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;265 /// Last sponsoring of fungible tokens approval in a collection266 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;267 /// Last sponsoring of RFT approval in a collection268 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269 }270}271272decl_module! {273 /// Type alias to Pallet, to be used by construct_runtime.274 pub struct Module<T: Config> for enum Call275 where276 origin: T::Origin277 {278 type Error = Error<T>;279280 pub fn deposit_event() = default;281282 fn on_initialize(_now: T::BlockNumber) -> Weight {283 0284 }285286 fn on_runtime_upgrade() -> Weight {287 0288 }289290 /// Create a collection of tokens.291 ///292 /// Each Token may have multiple properties encoded as an array of bytes293 /// of certain length. The initial owner of the collection is set294 /// to the address that signed the transaction and can be changed later.295 ///296 /// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.297 ///298 /// # Permissions299 ///300 /// * Anyone - becomes the owner of the new collection.301 ///302 /// # Arguments303 ///304 /// * `collection_name`: Wide-character string with collection name305 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).306 /// * `collection_description`: Wide-character string with collection description307 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).308 /// * `token_prefix`: Byte string containing the token prefix to mark a collection309 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).310 /// * `mode`: Type of items stored in the collection and type dependent data.311 // returns collection ID312 #[weight = <SelfWeightOf<T>>::create_collection()]313 #[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]314 pub fn create_collection(315 origin,316 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,317 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,318 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,319 mode: CollectionMode320 ) -> DispatchResult {321 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {322 name: collection_name,323 description: collection_description,324 token_prefix,325 mode,326 ..Default::default()327 };328 Self::create_collection_ex(origin, data)329 }330331 /// Create a collection with explicit parameters.332 ///333 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.334 ///335 /// # Permissions336 ///337 /// * Anyone - becomes the owner of the new collection.338 ///339 /// # Arguments340 ///341 /// * `data`: Explicit data of a collection used for its creation.342 #[weight = <SelfWeightOf<T>>::create_collection()]343 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {344 let sender = ensure_signed(origin)?;345346 // =========347348 let _id = T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;349350 Ok(())351 }352353 /// Destroy a collection if no tokens exist within.354 ///355 /// # Permissions356 ///357 /// * Collection owner358 ///359 /// # Arguments360 ///361 /// * `collection_id`: Collection to destroy.362 #[weight = <SelfWeightOf<T>>::destroy_collection()]363 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365 let collection = <CollectionHandle<T>>::try_get(collection_id)?;366 collection.check_is_internal()?;367368 // =========369370 T::CollectionDispatch::destroy(sender, collection)?;371372 // TODO: basket cleanup should be moved elsewhere373 // Maybe runtime dispatch.rs should perform it?374375 let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);376 let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);377 let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);378379 let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);380 let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);381 let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);382383 Ok(())384 }385386 /// Add an address to allow list.387 ///388 /// # Permissions389 ///390 /// * Collection owner391 /// * Collection admin392 ///393 /// # Arguments394 ///395 /// * `collection_id`: ID of the modified collection.396 /// * `address`: ID of the address to be added to the allowlist.397 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]398 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{399400 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);401 let collection = <CollectionHandle<T>>::try_get(collection_id)?;402 collection.check_is_internal()?;403404 <PalletCommon<T>>::toggle_allowlist(405 &collection,406 &sender,407 &address,408 true,409 )?;410411 Self::deposit_event(Event::<T>::AllowListAddressAdded(412 collection_id,413 address414 ));415416 Ok(())417 }418419 /// Remove an address from allow list.420 ///421 /// # Permissions422 ///423 /// * Collection owner424 /// * Collection admin425 ///426 /// # Arguments427 ///428 /// * `collection_id`: ID of the modified collection.429 /// * `address`: ID of the address to be removed from the allowlist.430 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]431 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435 collection.check_is_internal()?;436437 <PalletCommon<T>>::toggle_allowlist(438 &collection,439 &sender,440 &address,441 false,442 )?;443444 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(445 collection_id,446 address447 ));448449 Ok(())450 }451452 /// Change the owner of the collection.453 ///454 /// # Permissions455 ///456 /// * Collection owner457 ///458 /// # Arguments459 ///460 /// * `collection_id`: ID of the modified collection.461 /// * `new_owner`: ID of the account that will become the owner.462 #[weight = <SelfWeightOf<T>>::change_collection_owner()]463 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {464465 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);466467 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;468 target_collection.check_is_internal()?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.owner = new_owner.clone();472 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(473 collection_id,474 new_owner475 ));476477 target_collection.save()478 }479480 /// Add an admin to a collection.481 ///482 /// NFT Collection can be controlled by multiple admin addresses483 /// (some which can also be servers, for example). Admins can issue484 /// and burn NFTs, as well as add and remove other admins,485 /// but cannot change NFT or Collection ownership.486 ///487 /// # Permissions488 ///489 /// * Collection owner490 /// * Collection admin491 ///492 /// # Arguments493 ///494 /// * `collection_id`: ID of the Collection to add an admin for.495 /// * `new_admin`: Address of new admin to add.496 #[weight = <SelfWeightOf<T>>::add_collection_admin()]497 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499 let collection = <CollectionHandle<T>>::try_get(collection_id)?;500 collection.check_is_internal()?;501502 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(503 collection_id,504 new_admin_id.clone()505 ));506507 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)508 }509510 /// Remove admin of a collection.511 ///512 /// An admin address can remove itself. List of admins may become empty,513 /// in which case only Collection Owner will be able to add an Admin.514 ///515 /// # Permissions516 ///517 /// * Collection owner518 /// * Collection admin519 ///520 /// # Arguments521 ///522 /// * `collection_id`: ID of the collection to remove the admin for.523 /// * `account_id`: Address of the admin to remove.524 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]525 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527 let collection = <CollectionHandle<T>>::try_get(collection_id)?;528 collection.check_is_internal()?;529530 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(531 collection_id,532 account_id.clone()533 ));534535 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)536 }537538 /// Set (invite) a new collection sponsor.539 ///540 /// If successful, confirmation from the sponsor-to-be will be pending.541 ///542 /// # Permissions543 ///544 /// * Collection owner545 /// * Collection admin546 ///547 /// # Arguments548 ///549 /// * `collection_id`: ID of the modified collection.550 /// * `new_sponsor`: ID of the account of the sponsor-to-be.551 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]552 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;556 target_collection.check_is_owner_or_admin(&sender)?;557 target_collection.check_is_internal()?;558559 target_collection.set_sponsor(new_sponsor.clone())?;560561 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(562 collection_id,563 new_sponsor564 ));565566 target_collection.save()567 }568569 /// Confirm own sponsorship of a collection, becoming the sponsor.570 ///571 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].572 /// Sponsor can pay the fees of a transaction instead of the sender,573 /// but only within specified limits.574 ///575 /// # Permissions576 ///577 /// * Sponsor-to-be578 ///579 /// # Arguments580 ///581 /// * `collection_id`: ID of the collection with the pending sponsor.582 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]583 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {584 let sender = ensure_signed(origin)?;585586 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;587 target_collection.check_is_internal()?;588 ensure!(589 target_collection.confirm_sponsorship(&sender)?,590 Error::<T>::ConfirmUnsetSponsorFail591 );592593 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(594 collection_id,595 sender596 ));597598 target_collection.save()599 }600601 /// Remove a collection's a sponsor, making everyone pay for their own transactions.602 ///603 /// # Permissions604 ///605 /// * Collection owner606 ///607 /// # Arguments608 ///609 /// * `collection_id`: ID of the collection with the sponsor to remove.610 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]611 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {612 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);613614 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;615 target_collection.check_is_internal()?;616 target_collection.check_is_owner(&sender)?;617618 target_collection.sponsorship = SponsorshipState::Disabled;619620 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(621 collection_id622 ));623 target_collection.save()624 }625626 /// Mint an item within a collection.627 ///628 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].629 ///630 /// # Permissions631 ///632 /// * Collection owner633 /// * Collection admin634 /// * Anyone if635 /// * Allow List is enabled, and636 /// * Address is added to allow list, and637 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])638 ///639 /// # Arguments640 ///641 /// * `collection_id`: ID of the collection to which an item would belong.642 /// * `owner`: Address of the initial owner of the item.643 /// * `data`: Token data describing the item to store on chain.644 #[weight = T::CommonWeightInfo::create_item()]645 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {646 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);647 let budget = budget::Value::new(NESTING_BUDGET);648649 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))650 }651652 /// Create multiple items within a collection.653 ///654 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].655 ///656 /// # Permissions657 ///658 /// * Collection owner659 /// * Collection admin660 /// * Anyone if661 /// * Allow List is enabled, and662 /// * Address is added to the allow list, and663 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])664 ///665 /// # Arguments666 ///667 /// * `collection_id`: ID of the collection to which the tokens would belong.668 /// * `owner`: Address of the initial owner of the tokens.669 /// * `items_data`: Vector of data describing each item to be created.670 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]671 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {672 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);673 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);674 let budget = budget::Value::new(NESTING_BUDGET);675676 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))677 }678679 /// Add or change collection properties.680 ///681 /// # Permissions682 ///683 /// * Collection owner684 /// * Collection admin685 ///686 /// # Arguments687 ///688 /// * `collection_id`: ID of the modified collection.689 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.690 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.691 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]692 pub fn set_collection_properties(693 origin,694 collection_id: CollectionId,695 properties: Vec<Property>696 ) -> DispatchResultWithPostInfo {697 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);698699 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);700701 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))702 }703704 /// Delete specified collection properties.705 ///706 /// # Permissions707 ///708 /// * Collection Owner709 /// * Collection Admin710 ///711 /// # Arguments712 ///713 /// * `collection_id`: ID of the modified collection.714 /// * `property_keys`: Vector of keys of the properties to be deleted.715 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.716 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]717 pub fn delete_collection_properties(718 origin,719 collection_id: CollectionId,720 property_keys: Vec<PropertyKey>,721 ) -> DispatchResultWithPostInfo {722 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);723724 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))727 }728729 /// Add or change token properties according to collection's permissions.730 /// Currently properties only work with NFTs.731 ///732 /// # Permissions733 ///734 /// * Depends on collection's token property permissions and specified property mutability:735 /// * Collection owner736 /// * Collection admin737 /// * Token owner738 ///739 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].740 ///741 /// # Arguments742 ///743 /// * `collection_id: ID of the collection to which the token belongs.744 /// * `token_id`: ID of the modified token.745 /// * `properties`: Vector of key-value pairs stored as the token's metadata.746 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.747 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]748 pub fn set_token_properties(749 origin,750 collection_id: CollectionId,751 token_id: TokenId,752 properties: Vec<Property>753 ) -> DispatchResultWithPostInfo {754 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);755756 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);757 let budget = budget::Value::new(NESTING_BUDGET);758759 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))760 }761762 /// Delete specified token properties. Currently properties only work with NFTs.763 ///764 /// # Permissions765 ///766 /// * Depends on collection's token property permissions and specified property mutability:767 /// * Collection owner768 /// * Collection admin769 /// * Token owner770 ///771 /// # Arguments772 ///773 /// * `collection_id`: ID of the collection to which the token belongs.774 /// * `token_id`: ID of the modified token.775 /// * `property_keys`: Vector of keys of the properties to be deleted.776 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.777 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]778 pub fn delete_token_properties(779 origin,780 collection_id: CollectionId,781 token_id: TokenId,782 property_keys: Vec<PropertyKey>783 ) -> DispatchResultWithPostInfo {784 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);785786 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);787 let budget = budget::Value::new(NESTING_BUDGET);788789 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))790 }791792 /// Add or change token property permissions of a collection.793 ///794 /// Without a permission for a particular key, a property with that key795 /// cannot be created in a token.796 ///797 /// # Permissions798 ///799 /// * Collection owner800 /// * Collection admin801 ///802 /// # Arguments803 ///804 /// * `collection_id`: ID of the modified collection.805 /// * `property_permissions`: Vector of permissions for property keys.806 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.807 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]808 pub fn set_token_property_permissions(809 origin,810 collection_id: CollectionId,811 property_permissions: Vec<PropertyKeyPermission>,812 ) -> DispatchResultWithPostInfo {813 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);814815 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);816817 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))818 }819820 /// Create multiple items within a collection with explicitly specified initial parameters.821 ///822 /// # Permissions823 ///824 /// * Collection owner825 /// * Collection admin826 /// * Anyone if827 /// * Allow List is enabled, and828 /// * Address is added to allow list, and829 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])830 ///831 /// # Arguments832 ///833 /// * `collection_id`: ID of the collection to which the tokens would belong.834 /// * `data`: Explicit item creation data.835 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]836 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {837 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838 let budget = budget::Value::new(NESTING_BUDGET);839840 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))841 }842843 /// Completely allow or disallow transfers for a particular collection.844 ///845 /// # Permissions846 ///847 /// * Collection owner848 ///849 /// # Arguments850 ///851 /// * `collection_id`: ID of the collection.852 /// * `value`: New value of the flag, are transfers allowed?853 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]854 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {855 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;857 target_collection.check_is_internal()?;858 target_collection.check_is_owner(&sender)?;859860 // =========861862 target_collection.limits.transfers_enabled = Some(value);863 target_collection.save()864 }865866 /// Destroy an item.867 ///868 /// # Permissions869 ///870 /// * Collection owner871 /// * Collection admin872 /// * Current item owner873 ///874 /// # Arguments875 ///876 /// * `collection_id`: ID of the collection to which the item belongs.877 /// * `item_id`: ID of item to burn.878 /// * `value`: Number of pieces of the item to destroy.879 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.880 /// * Fungible Mode: The desired number of pieces to burn.881 /// * Re-Fungible Mode: The desired number of pieces to burn.882 #[weight = T::CommonWeightInfo::burn_item()]883 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {884 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);885886 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;887 if value == 1 {888 <NftTransferBasket<T>>::remove(collection_id, item_id);889 <NftApproveBasket<T>>::remove(collection_id, item_id);890 }891 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?892 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());893 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));894 Ok(post_info)895 }896897 /// Destroy a token on behalf of the owner as a non-owner account.898 ///899 /// See also: [`approve`][`Pallet::approve`].900 ///901 /// After this method executes, one approval is removed from the total so that902 /// the approved address will not be able to transfer this item again from this owner.903 ///904 /// # Permissions905 ///906 /// * Collection owner907 /// * Collection admin908 /// * Current token owner909 /// * Address approved by current item owner910 ///911 /// # Arguments912 ///913 /// * `from`: The owner of the burning item.914 /// * `collection_id`: ID of the collection to which the item belongs.915 /// * `item_id`: ID of item to burn.916 /// * `value`: Number of pieces to burn.917 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.918 /// * Fungible Mode: The desired number of pieces to burn.919 /// * Re-Fungible Mode: The desired number of pieces to burn.920 #[weight = T::CommonWeightInfo::burn_from()]921 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {922 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);923 let budget = budget::Value::new(NESTING_BUDGET);924925 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))926 }927928 /// Change ownership of the token.929 ///930 /// # Permissions931 ///932 /// * Collection owner933 /// * Collection admin934 /// * Current token owner935 ///936 /// # Arguments937 ///938 /// * `recipient`: Address of token recipient.939 /// * `collection_id`: ID of the collection the item belongs to.940 /// * `item_id`: ID of the item.941 /// * Non-Fungible Mode: Required.942 /// * Fungible Mode: Ignored.943 /// * Re-Fungible Mode: Required.944 ///945 /// * `value`: Amount to transfer.946 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.947 /// * Fungible Mode: The desired number of pieces to transfer.948 /// * Re-Fungible Mode: The desired number of pieces to transfer.949 #[weight = T::CommonWeightInfo::transfer()]950 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {951 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952 let budget = budget::Value::new(NESTING_BUDGET);953954 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))955 }956957 /// Allow a non-permissioned address to transfer or burn an item.958 ///959 /// # Permissions960 ///961 /// * Collection owner962 /// * Collection admin963 /// * Current item owner964 ///965 /// # Arguments966 ///967 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.968 /// * `collection_id`: ID of the collection the item belongs to.969 /// * `item_id`: ID of the item transactions on which are now approved.970 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).971 /// Set to 0 to revoke the approval.972 #[weight = T::CommonWeightInfo::approve()]973 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {974 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);975976 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))977 }978979 /// Change ownership of an item on behalf of the owner as a non-owner account.980 ///981 /// See the [`approve`][`Pallet::approve`] method for additional information.982 ///983 /// After this method executes, one approval is removed from the total so that984 /// the approved address will not be able to transfer this item again from this owner.985 ///986 /// # Permissions987 ///988 /// * Collection owner989 /// * Collection admin990 /// * Current item owner991 /// * Address approved by current item owner992 ///993 /// # Arguments994 ///995 /// * `from`: Address that currently owns the token.996 /// * `recipient`: Address of the new token-owner-to-be.997 /// * `collection_id`: ID of the collection the item.998 /// * `item_id`: ID of the item to be transferred.999 /// * `value`: Amount to transfer.1000 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1001 /// * Fungible Mode: The desired number of pieces to transfer.1002 /// * Re-Fungible Mode: The desired number of pieces to transfer.1003 #[weight = T::CommonWeightInfo::transfer_from()]1004 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1005 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1006 let budget = budget::Value::new(NESTING_BUDGET);10071008 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1009 }10101011 /// Set specific limits of a collection. Empty, or None fields mean chain default.1012 ///1013 /// # Permissions1014 ///1015 /// * Collection owner1016 /// * Collection admin1017 ///1018 /// # Arguments1019 ///1020 /// * `collection_id`: ID of the modified collection.1021 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1022 /// will not overwrite the old ones.1023 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1024 pub fn set_collection_limits(1025 origin,1026 collection_id: CollectionId,1027 new_limit: CollectionLimits,1028 ) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1031 target_collection.check_is_internal()?;1032 target_collection.check_is_owner_or_admin(&sender)?;1033 let old_limit = &target_collection.limits;10341035 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10361037 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1038 collection_id1039 ));10401041 target_collection.save()1042 }10431044 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1045 ///1046 /// # Permissions1047 ///1048 /// * Collection owner1049 /// * Collection admin1050 ///1051 /// # Arguments1052 ///1053 /// * `collection_id`: ID of the modified collection.1054 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1055 /// will not overwrite the old ones.1056 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1057 pub fn set_collection_permissions(1058 origin,1059 collection_id: CollectionId,1060 new_permission: CollectionPermissions,1061 ) -> DispatchResult {1062 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1063 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1064 target_collection.check_is_internal()?;1065 target_collection.check_is_owner_or_admin(&sender)?;1066 let old_limit = &target_collection.permissions;10671068 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10691070 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1071 collection_id1072 ));10731074 target_collection.save()1075 }10761077 /// Re-partition a refungible token, while owning all of its parts/pieces.1078 ///1079 /// # Permissions1080 ///1081 /// * Token owner (must own every part)1082 ///1083 /// # Arguments1084 ///1085 /// * `collection_id`: ID of the collection the RFT belongs to.1086 /// * `token_id`: ID of the RFT.1087 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1088 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1089 pub fn repartition(1090 origin,1091 collection_id: CollectionId,1092 token_id: TokenId,1093 amount: u128,1094 ) -> DispatchResultWithPostInfo {1095 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1096 dispatch_tx::<T, _>(collection_id, |d| {1097 if let Some(refungible_extensions) = d.refungible_extensions() {1098 refungible_extensions.repartition(&sender, token_id, amount)1099 } else {1100 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1101 }1102 })1103 }1104 }1105}11061107impl<T: Config> Pallet<T> {1108 pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1109 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1110 target_collection.check_is_internal()?;1111 target_collection.set_sponsor(sponsor.clone())?;11121113 Self::deposit_event(Event::<T>::CollectionSponsorSet(1114 collection_id,1115 sponsor.clone(),1116 ));11171118 ensure!(1119 target_collection.confirm_sponsorship(&sponsor)?,1120 Error::<T>::ConfirmUnsetSponsorFail1121 );11221123 Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11241125 target_collection.save()1126 }11271128 pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1129 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1130 target_collection.check_is_internal()?;1131 target_collection.sponsorship = SponsorshipState::Disabled;11321133 Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11341135 target_collection.save()1136 }1137}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69 clippy::too_many_arguments,70 clippy::unnecessary_mut_passed,71 clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77 decl_module, decl_storage, decl_error, decl_event,78 dispatch::DispatchResult,79 ensure, fail,80 weights::{Weight},81 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82 BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_runtime::{sp_std::prelude::Vec};87use up_data_structs::{88 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,90 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,91 PropertyKeyPermission,92};93use pallet_evm::account::CrossAccountId;94use pallet_common::{95 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,96 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,97};98pub mod eth;99100#[cfg(feature = "runtime-benchmarks")]101pub mod benchmarking;102pub mod weights;103use weights::WeightInfo;104105/// Maximum number of levels of depth in the token nesting tree.106pub const NESTING_BUDGET: u32 = 5;107108decl_error! {109 /// Errors for the common Unique transactions.110 pub enum Error for Module<T: Config> {111 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].112 CollectionDecimalPointLimitExceeded,113 /// This address is not set as sponsor, use setCollectionSponsor first.114 ConfirmUnsetSponsorFail,115 /// Length of items properties must be greater than 0.116 EmptyArgument,117 /// Repertition is only supported by refungible collection.118 RepartitionCalledOnNonRefungibleCollection,119 }120}121122/// Configuration trait of this pallet.123pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {124 /// Overarching event type.125 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;126127 /// Weight information for extrinsics in this pallet.128 type WeightInfo: WeightInfo;129130 /// Weight information for common pallet operations.131 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;132133 /// Weight info information for extra refungible pallet operations.134 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;135}136137decl_event! {138 pub enum Event<T>139 where140 <T as frame_system::Config>::AccountId,141 <T as pallet_evm::account::Config>::CrossAccountId,142 {143 /// Collection sponsor was removed144 ///145 /// # Arguments146 /// * collection_id: ID of the affected collection.147 CollectionSponsorRemoved(CollectionId),148149 /// Collection admin was added150 ///151 /// # Arguments152 /// * collection_id: ID of the affected collection.153 /// * admin: Admin address.154 CollectionAdminAdded(CollectionId, CrossAccountId),155156 /// Collection owned was changed157 ///158 /// # Arguments159 /// * collection_id: ID of the affected collection.160 /// * owner: New owner address.161 CollectionOwnedChanged(CollectionId, AccountId),162163 /// Collection sponsor was set164 ///165 /// # Arguments166 /// * collection_id: ID of the affected collection.167 /// * owner: New sponsor address.168 CollectionSponsorSet(CollectionId, AccountId),169170 /// New sponsor was confirm171 ///172 /// # Arguments173 /// * collection_id: ID of the affected collection.174 /// * sponsor: New sponsor address.175 SponsorshipConfirmed(CollectionId, AccountId),176177 /// Collection admin was removed178 ///179 /// # Arguments180 /// * collection_id: ID of the affected collection.181 /// * admin: Removed admin address.182 CollectionAdminRemoved(CollectionId, CrossAccountId),183184 /// Address was removed from the allow list185 ///186 /// # Arguments187 /// * collection_id: ID of the affected collection.188 /// * user: Address of the removed account.189 AllowListAddressRemoved(CollectionId, CrossAccountId),190191 /// Address was added to the allow list192 ///193 /// # Arguments194 /// * collection_id: ID of the affected collection.195 /// * user: Address of the added account.196 AllowListAddressAdded(CollectionId, CrossAccountId),197198 /// Collection limits were set199 ///200 /// # Arguments201 /// * collection_id: ID of the affected collection.202 CollectionLimitSet(CollectionId),203204 /// Collection permissions were set205 ///206 /// # Arguments207 /// * collection_id: ID of the affected collection.208 CollectionPermissionSet(CollectionId),209 }210}211212type SelfWeightOf<T> = <T as Config>::WeightInfo;213214// # Used definitions215//216// ## User control levels217//218// chain-controlled - key is uncontrolled by user219// i.e autoincrementing index220// can use non-cryptographic hash221// real - key is controlled by user222// but it is hard to generate enough colliding values, i.e owner of signed txs223// can use non-cryptographic hash224// controlled - key is completly controlled by users225// i.e maps with mutable keys226// should use cryptographic hash227//228// ## User control level downgrade reasons229//230// ?1 - chain-controlled -> controlled231// collections/tokens can be destroyed, resulting in massive holes232// ?2 - chain-controlled -> controlled233// same as ?1, but can be only added, resulting in easier exploitation234// ?3 - real -> controlled235// no confirmation required, so addresses can be easily generated236decl_storage! {237 trait Store for Module<T: Config> as Unique {238239 //#region Private members240 /// Used for migrations241 ChainVersion: u64;242 //#endregion243244 //#region Tokens transfer sponosoring rate limit baskets245 /// (Collection id (controlled?2), who created (real))246 /// TODO: Off chain worker should remove from this map when collection gets removed247 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;248 /// Collection id (controlled?2), token id (controlled?2)249 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250 /// Collection id (controlled?2), owning user (real)251 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252 /// Collection id (controlled?2), token id (controlled?2)253 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 //#endregion255256 /// Variable metadata sponsoring257 /// Collection id (controlled?2), token id (controlled?2)258 #[deprecated]259 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;260 /// Last sponsoring of token property setting // todo:doc rephrase this and the following261 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262263 /// Last sponsoring of NFT approval in a collection264 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;265 /// Last sponsoring of fungible tokens approval in a collection266 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;267 /// Last sponsoring of RFT approval in a collection268 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269 }270}271272decl_module! {273 /// Type alias to Pallet, to be used by construct_runtime.274 pub struct Module<T: Config> for enum Call275 where276 origin: T::Origin277 {278 type Error = Error<T>;279280 pub fn deposit_event() = default;281282 fn on_initialize(_now: T::BlockNumber) -> Weight {283 0284 }285286 fn on_runtime_upgrade() -> Weight {287 0288 }289290 /// Create a collection of tokens.291 ///292 /// Each Token may have multiple properties encoded as an array of bytes293 /// of certain length. The initial owner of the collection is set294 /// to the address that signed the transaction and can be changed later.295 ///296 /// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.297 ///298 /// # Permissions299 ///300 /// * Anyone - becomes the owner of the new collection.301 ///302 /// # Arguments303 ///304 /// * `collection_name`: Wide-character string with collection name305 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).306 /// * `collection_description`: Wide-character string with collection description307 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).308 /// * `token_prefix`: Byte string containing the token prefix to mark a collection309 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).310 /// * `mode`: Type of items stored in the collection and type dependent data.311 // returns collection ID312 #[weight = <SelfWeightOf<T>>::create_collection()]313 #[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]314 pub fn create_collection(315 origin,316 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,317 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,318 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,319 mode: CollectionMode320 ) -> DispatchResult {321 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {322 name: collection_name,323 description: collection_description,324 token_prefix,325 mode,326 ..Default::default()327 };328 Self::create_collection_ex(origin, data)329 }330331 /// Create a collection with explicit parameters.332 ///333 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.334 ///335 /// # Permissions336 ///337 /// * Anyone - becomes the owner of the new collection.338 ///339 /// # Arguments340 ///341 /// * `data`: Explicit data of a collection used for its creation.342 #[weight = <SelfWeightOf<T>>::create_collection()]343 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {344 let sender = ensure_signed(origin)?;345346 // =========347348 let _id = T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;349350 Ok(())351 }352353 /// Destroy a collection if no tokens exist within.354 ///355 /// # Permissions356 ///357 /// * Collection owner358 ///359 /// # Arguments360 ///361 /// * `collection_id`: Collection to destroy.362 #[weight = <SelfWeightOf<T>>::destroy_collection()]363 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365 let collection = <CollectionHandle<T>>::try_get(collection_id)?;366 collection.check_is_internal()?;367368 // =========369370 T::CollectionDispatch::destroy(sender, collection)?;371372 // TODO: basket cleanup should be moved elsewhere373 // Maybe runtime dispatch.rs should perform it?374375 let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);376 let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);377 let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);378379 let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);380 let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);381 let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);382383 Ok(())384 }385386 /// Add an address to allow list.387 ///388 /// # Permissions389 ///390 /// * Collection owner391 /// * Collection admin392 ///393 /// # Arguments394 ///395 /// * `collection_id`: ID of the modified collection.396 /// * `address`: ID of the address to be added to the allowlist.397 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]398 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{399400 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);401 let collection = <CollectionHandle<T>>::try_get(collection_id)?;402 collection.check_is_internal()?;403404 <PalletCommon<T>>::toggle_allowlist(405 &collection,406 &sender,407 &address,408 true,409 )?;410411 Self::deposit_event(Event::<T>::AllowListAddressAdded(412 collection_id,413 address414 ));415416 Ok(())417 }418419 /// Remove an address from allow list.420 ///421 /// # Permissions422 ///423 /// * Collection owner424 /// * Collection admin425 ///426 /// # Arguments427 ///428 /// * `collection_id`: ID of the modified collection.429 /// * `address`: ID of the address to be removed from the allowlist.430 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]431 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435 collection.check_is_internal()?;436437 <PalletCommon<T>>::toggle_allowlist(438 &collection,439 &sender,440 &address,441 false,442 )?;443444 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(445 collection_id,446 address447 ));448449 Ok(())450 }451452 /// Change the owner of the collection.453 ///454 /// # Permissions455 ///456 /// * Collection owner457 ///458 /// # Arguments459 ///460 /// * `collection_id`: ID of the modified collection.461 /// * `new_owner`: ID of the account that will become the owner.462 #[weight = <SelfWeightOf<T>>::change_collection_owner()]463 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {464465 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);466467 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;468 target_collection.check_is_internal()?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.owner = new_owner.clone();472 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(473 collection_id,474 new_owner475 ));476477 target_collection.save()478 }479480 /// Add an admin to a collection.481 ///482 /// NFT Collection can be controlled by multiple admin addresses483 /// (some which can also be servers, for example). Admins can issue484 /// and burn NFTs, as well as add and remove other admins,485 /// but cannot change NFT or Collection ownership.486 ///487 /// # Permissions488 ///489 /// * Collection owner490 /// * Collection admin491 ///492 /// # Arguments493 ///494 /// * `collection_id`: ID of the Collection to add an admin for.495 /// * `new_admin`: Address of new admin to add.496 #[weight = <SelfWeightOf<T>>::add_collection_admin()]497 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499 let collection = <CollectionHandle<T>>::try_get(collection_id)?;500 collection.check_is_internal()?;501502 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(503 collection_id,504 new_admin_id.clone()505 ));506507 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)508 }509510 /// Remove admin of a collection.511 ///512 /// An admin address can remove itself. List of admins may become empty,513 /// in which case only Collection Owner will be able to add an Admin.514 ///515 /// # Permissions516 ///517 /// * Collection owner518 /// * Collection admin519 ///520 /// # Arguments521 ///522 /// * `collection_id`: ID of the collection to remove the admin for.523 /// * `account_id`: Address of the admin to remove.524 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]525 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527 let collection = <CollectionHandle<T>>::try_get(collection_id)?;528 collection.check_is_internal()?;529530 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(531 collection_id,532 account_id.clone()533 ));534535 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)536 }537538 /// Set (invite) a new collection sponsor.539 ///540 /// If successful, confirmation from the sponsor-to-be will be pending.541 ///542 /// # Permissions543 ///544 /// * Collection owner545 /// * Collection admin546 ///547 /// # Arguments548 ///549 /// * `collection_id`: ID of the modified collection.550 /// * `new_sponsor`: ID of the account of the sponsor-to-be.551 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]552 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;556 target_collection.check_is_owner_or_admin(&sender)?;557 target_collection.check_is_internal()?;558559 target_collection.set_sponsor(new_sponsor.clone())?;560561 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(562 collection_id,563 new_sponsor564 ));565566 target_collection.save()567 }568569 /// Confirm own sponsorship of a collection, becoming the sponsor.570 ///571 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].572 /// Sponsor can pay the fees of a transaction instead of the sender,573 /// but only within specified limits.574 ///575 /// # Permissions576 ///577 /// * Sponsor-to-be578 ///579 /// # Arguments580 ///581 /// * `collection_id`: ID of the collection with the pending sponsor.582 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]583 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {584 let sender = ensure_signed(origin)?;585586 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;587 target_collection.check_is_internal()?;588 ensure!(589 target_collection.confirm_sponsorship(&sender)?,590 Error::<T>::ConfirmUnsetSponsorFail591 );592593 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(594 collection_id,595 sender596 ));597598 target_collection.save()599 }600601 /// Remove a collection's a sponsor, making everyone pay for their own transactions.602 ///603 /// # Permissions604 ///605 /// * Collection owner606 ///607 /// # Arguments608 ///609 /// * `collection_id`: ID of the collection with the sponsor to remove.610 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]611 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {612 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);613614 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;615 target_collection.check_is_internal()?;616 target_collection.check_is_owner(&sender)?;617618 target_collection.sponsorship = SponsorshipState::Disabled;619620 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(621 collection_id622 ));623 target_collection.save()624 }625626 /// Mint an item within a collection.627 ///628 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].629 ///630 /// # Permissions631 ///632 /// * Collection owner633 /// * Collection admin634 /// * Anyone if635 /// * Allow List is enabled, and636 /// * Address is added to allow list, and637 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])638 ///639 /// # Arguments640 ///641 /// * `collection_id`: ID of the collection to which an item would belong.642 /// * `owner`: Address of the initial owner of the item.643 /// * `data`: Token data describing the item to store on chain.644 #[weight = T::CommonWeightInfo::create_item()]645 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {646 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);647 let budget = budget::Value::new(NESTING_BUDGET);648649 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))650 }651652 /// Create multiple items within a collection.653 ///654 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].655 ///656 /// # Permissions657 ///658 /// * Collection owner659 /// * Collection admin660 /// * Anyone if661 /// * Allow List is enabled, and662 /// * Address is added to the allow list, and663 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])664 ///665 /// # Arguments666 ///667 /// * `collection_id`: ID of the collection to which the tokens would belong.668 /// * `owner`: Address of the initial owner of the tokens.669 /// * `items_data`: Vector of data describing each item to be created.670 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]671 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {672 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);673 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);674 let budget = budget::Value::new(NESTING_BUDGET);675676 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))677 }678679 /// Add or change collection properties.680 ///681 /// # Permissions682 ///683 /// * Collection owner684 /// * Collection admin685 ///686 /// # Arguments687 ///688 /// * `collection_id`: ID of the modified collection.689 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.690 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.691 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]692 pub fn set_collection_properties(693 origin,694 collection_id: CollectionId,695 properties: Vec<Property>696 ) -> DispatchResultWithPostInfo {697 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);698699 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);700701 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))702 }703704 /// Delete specified collection properties.705 ///706 /// # Permissions707 ///708 /// * Collection Owner709 /// * Collection Admin710 ///711 /// # Arguments712 ///713 /// * `collection_id`: ID of the modified collection.714 /// * `property_keys`: Vector of keys of the properties to be deleted.715 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.716 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]717 pub fn delete_collection_properties(718 origin,719 collection_id: CollectionId,720 property_keys: Vec<PropertyKey>,721 ) -> DispatchResultWithPostInfo {722 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);723724 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))727 }728729 /// Add or change token properties according to collection's permissions.730 /// Currently properties only work with NFTs.731 ///732 /// # Permissions733 ///734 /// * Depends on collection's token property permissions and specified property mutability:735 /// * Collection owner736 /// * Collection admin737 /// * Token owner738 ///739 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].740 ///741 /// # Arguments742 ///743 /// * `collection_id: ID of the collection to which the token belongs.744 /// * `token_id`: ID of the modified token.745 /// * `properties`: Vector of key-value pairs stored as the token's metadata.746 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.747 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]748 pub fn set_token_properties(749 origin,750 collection_id: CollectionId,751 token_id: TokenId,752 properties: Vec<Property>753 ) -> DispatchResultWithPostInfo {754 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);755756 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);757 let budget = budget::Value::new(NESTING_BUDGET);758759 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))760 }761762 /// Delete specified token properties. Currently properties only work with NFTs.763 ///764 /// # Permissions765 ///766 /// * Depends on collection's token property permissions and specified property mutability:767 /// * Collection owner768 /// * Collection admin769 /// * Token owner770 ///771 /// # Arguments772 ///773 /// * `collection_id`: ID of the collection to which the token belongs.774 /// * `token_id`: ID of the modified token.775 /// * `property_keys`: Vector of keys of the properties to be deleted.776 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.777 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]778 pub fn delete_token_properties(779 origin,780 collection_id: CollectionId,781 token_id: TokenId,782 property_keys: Vec<PropertyKey>783 ) -> DispatchResultWithPostInfo {784 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);785786 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);787 let budget = budget::Value::new(NESTING_BUDGET);788789 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))790 }791792 /// Add or change token property permissions of a collection.793 ///794 /// Without a permission for a particular key, a property with that key795 /// cannot be created in a token.796 ///797 /// # Permissions798 ///799 /// * Collection owner800 /// * Collection admin801 ///802 /// # Arguments803 ///804 /// * `collection_id`: ID of the modified collection.805 /// * `property_permissions`: Vector of permissions for property keys.806 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.807 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]808 pub fn set_token_property_permissions(809 origin,810 collection_id: CollectionId,811 property_permissions: Vec<PropertyKeyPermission>,812 ) -> DispatchResultWithPostInfo {813 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);814815 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);816817 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))818 }819820 /// Create multiple items within a collection with explicitly specified initial parameters.821 ///822 /// # Permissions823 ///824 /// * Collection owner825 /// * Collection admin826 /// * Anyone if827 /// * Allow List is enabled, and828 /// * Address is added to allow list, and829 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])830 ///831 /// # Arguments832 ///833 /// * `collection_id`: ID of the collection to which the tokens would belong.834 /// * `data`: Explicit item creation data.835 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]836 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {837 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838 let budget = budget::Value::new(NESTING_BUDGET);839840 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))841 }842843 /// Completely allow or disallow transfers for a particular collection.844 ///845 /// # Permissions846 ///847 /// * Collection owner848 ///849 /// # Arguments850 ///851 /// * `collection_id`: ID of the collection.852 /// * `value`: New value of the flag, are transfers allowed?853 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]854 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {855 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;857 target_collection.check_is_internal()?;858 target_collection.check_is_owner(&sender)?;859860 // =========861862 target_collection.limits.transfers_enabled = Some(value);863 target_collection.save()864 }865866 /// Destroy an item.867 ///868 /// # Permissions869 ///870 /// * Collection owner871 /// * Collection admin872 /// * Current item owner873 ///874 /// # Arguments875 ///876 /// * `collection_id`: ID of the collection to which the item belongs.877 /// * `item_id`: ID of item to burn.878 /// * `value`: Number of pieces of the item to destroy.879 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.880 /// * Fungible Mode: The desired number of pieces to burn.881 /// * Re-Fungible Mode: The desired number of pieces to burn.882 #[weight = T::CommonWeightInfo::burn_item()]883 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {884 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);885886 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;887 if value == 1 {888 <NftTransferBasket<T>>::remove(collection_id, item_id);889 <NftApproveBasket<T>>::remove(collection_id, item_id);890 }891 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?892 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());893 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));894 Ok(post_info)895 }896897 /// Destroy a token on behalf of the owner as a non-owner account.898 ///899 /// See also: [`approve`][`Pallet::approve`].900 ///901 /// After this method executes, one approval is removed from the total so that902 /// the approved address will not be able to transfer this item again from this owner.903 ///904 /// # Permissions905 ///906 /// * Collection owner907 /// * Collection admin908 /// * Current token owner909 /// * Address approved by current item owner910 ///911 /// # Arguments912 ///913 /// * `from`: The owner of the burning item.914 /// * `collection_id`: ID of the collection to which the item belongs.915 /// * `item_id`: ID of item to burn.916 /// * `value`: Number of pieces to burn.917 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.918 /// * Fungible Mode: The desired number of pieces to burn.919 /// * Re-Fungible Mode: The desired number of pieces to burn.920 #[weight = T::CommonWeightInfo::burn_from()]921 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {922 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);923 let budget = budget::Value::new(NESTING_BUDGET);924925 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))926 }927928 /// Change ownership of the token.929 ///930 /// # Permissions931 ///932 /// * Collection owner933 /// * Collection admin934 /// * Current token owner935 ///936 /// # Arguments937 ///938 /// * `recipient`: Address of token recipient.939 /// * `collection_id`: ID of the collection the item belongs to.940 /// * `item_id`: ID of the item.941 /// * Non-Fungible Mode: Required.942 /// * Fungible Mode: Ignored.943 /// * Re-Fungible Mode: Required.944 ///945 /// * `value`: Amount to transfer.946 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.947 /// * Fungible Mode: The desired number of pieces to transfer.948 /// * Re-Fungible Mode: The desired number of pieces to transfer.949 #[weight = T::CommonWeightInfo::transfer()]950 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {951 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952 let budget = budget::Value::new(NESTING_BUDGET);953954 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))955 }956957 /// Allow a non-permissioned address to transfer or burn an item.958 ///959 /// # Permissions960 ///961 /// * Collection owner962 /// * Collection admin963 /// * Current item owner964 ///965 /// # Arguments966 ///967 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.968 /// * `collection_id`: ID of the collection the item belongs to.969 /// * `item_id`: ID of the item transactions on which are now approved.970 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).971 /// Set to 0 to revoke the approval.972 #[weight = T::CommonWeightInfo::approve()]973 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {974 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);975976 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))977 }978979 /// Change ownership of an item on behalf of the owner as a non-owner account.980 ///981 /// See the [`approve`][`Pallet::approve`] method for additional information.982 ///983 /// After this method executes, one approval is removed from the total so that984 /// the approved address will not be able to transfer this item again from this owner.985 ///986 /// # Permissions987 ///988 /// * Collection owner989 /// * Collection admin990 /// * Current item owner991 /// * Address approved by current item owner992 ///993 /// # Arguments994 ///995 /// * `from`: Address that currently owns the token.996 /// * `recipient`: Address of the new token-owner-to-be.997 /// * `collection_id`: ID of the collection the item.998 /// * `item_id`: ID of the item to be transferred.999 /// * `value`: Amount to transfer.1000 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1001 /// * Fungible Mode: The desired number of pieces to transfer.1002 /// * Re-Fungible Mode: The desired number of pieces to transfer.1003 #[weight = T::CommonWeightInfo::transfer_from()]1004 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1005 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1006 let budget = budget::Value::new(NESTING_BUDGET);10071008 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1009 }10101011 /// Set specific limits of a collection. Empty, or None fields mean chain default.1012 ///1013 /// # Permissions1014 ///1015 /// * Collection owner1016 /// * Collection admin1017 ///1018 /// # Arguments1019 ///1020 /// * `collection_id`: ID of the modified collection.1021 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1022 /// will not overwrite the old ones.1023 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1024 pub fn set_collection_limits(1025 origin,1026 collection_id: CollectionId,1027 new_limit: CollectionLimits,1028 ) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1031 target_collection.check_is_internal()?;1032 target_collection.check_is_owner_or_admin(&sender)?;1033 let old_limit = &target_collection.limits;10341035 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10361037 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1038 collection_id1039 ));10401041 target_collection.save()1042 }10431044 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1045 ///1046 /// # Permissions1047 ///1048 /// * Collection owner1049 /// * Collection admin1050 ///1051 /// # Arguments1052 ///1053 /// * `collection_id`: ID of the modified collection.1054 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1055 /// will not overwrite the old ones.1056 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1057 pub fn set_collection_permissions(1058 origin,1059 collection_id: CollectionId,1060 new_permission: CollectionPermissions,1061 ) -> DispatchResult {1062 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1063 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1064 target_collection.check_is_internal()?;1065 target_collection.check_is_owner_or_admin(&sender)?;1066 let old_limit = &target_collection.permissions;10671068 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10691070 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1071 collection_id1072 ));10731074 target_collection.save()1075 }10761077 /// Re-partition a refungible token, while owning all of its parts/pieces.1078 ///1079 /// # Permissions1080 ///1081 /// * Token owner (must own every part)1082 ///1083 /// # Arguments1084 ///1085 /// * `collection_id`: ID of the collection the RFT belongs to.1086 /// * `token_id`: ID of the RFT.1087 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1088 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1089 pub fn repartition(1090 origin,1091 collection_id: CollectionId,1092 token_id: TokenId,1093 amount: u128,1094 ) -> DispatchResultWithPostInfo {1095 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1096 dispatch_tx::<T, _>(collection_id, |d| {1097 if let Some(refungible_extensions) = d.refungible_extensions() {1098 refungible_extensions.repartition(&sender, token_id, amount)1099 } else {1100 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1101 }1102 })1103 }1104 }1105}11061107impl<T: Config> Pallet<T> {1108 /// Force set `sponsor` for `collection`.1109 ///1110 /// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1111 /// from the `sponsor` is not required.1112 ///1113 /// # Arguments1114 ///1115 /// * `sponsor`: ID of the account of the sponsor-to-be.1116 /// * `collection_id`: ID of the modified collection.1117 pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1118 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1119 target_collection.check_is_internal()?;1120 target_collection.set_sponsor(sponsor.clone())?;11211122 Self::deposit_event(Event::<T>::CollectionSponsorSet(1123 collection_id,1124 sponsor.clone(),1125 ));11261127 ensure!(1128 target_collection.confirm_sponsorship(&sponsor)?,1129 Error::<T>::ConfirmUnsetSponsorFail1130 );11311132 Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11331134 target_collection.save()1135 }11361137 /// Force remove `sponsor` for `collection`.1138 ///1139 /// Differs from `remove_sponsor` in that1140 /// it doesn't require consent from the `owner` of the collection.1141 ///1142 /// # Arguments1143 ///1144 /// * `collection_id`: ID of the modified collection.1145 pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1146 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1147 target_collection.check_is_internal()?;1148 target_collection.sponsorship = SponsorshipState::Disabled;11491150 Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11511152 target_collection.save()1153 }1154}primitives/common/CHANGELOG.mddiffbeforeafterboth--- a/primitives/common/CHANGELOG.md
+++ b/primitives/common/CHANGELOG.md
@@ -1,4 +1,9 @@
<!-- bureaucrate goes here -->
+## [v0.9.27] 2022-09-08
+
+### Added
+- Relay block constants. In particular, it is necessary to add the `AppPromotion` pallet at runtime.
+
## [v0.9.25] 2022-08-16
### Other changes
runtime/opal/CHANGELOG.mddiffbeforeafterboth--- a/runtime/opal/CHANGELOG.md
+++ b/runtime/opal/CHANGELOG.md
@@ -3,16 +3,23 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+
+## [v0.9.27] 2022-09-08
+
+### Added
+
+- `AppPromotion` pallet to runtime.
+
## [v0.9.27] 2022-08-16
### Bugfixes
-- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
+- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
runtime/quartz/CHANGELOG.mddiffbeforeafterboth--- a/runtime/quartz/CHANGELOG.md
+++ b/runtime/quartz/CHANGELOG.md
@@ -3,17 +3,23 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+
+## [v0.9.27] 2022-09-08
+
+### Added
+
+- `AppPromotion` pallet to runtime.
+
## [v0.9.27] 2022-08-16
### Bugfixes
-- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
+- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
-
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
runtime/unique/CHANGELOG.mddiffbeforeafterboth--- a/runtime/unique/CHANGELOG.md
+++ b/runtime/unique/CHANGELOG.md
@@ -3,16 +3,23 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+
+## [v0.9.27] 2022-09-08
+
+### Added
+
+- `AppPromotion` pallet to runtime.
+
## [v0.9.27] 2022-08-16
### Bugfixes
-- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
+- Add missing config keys 74f532ac28dce15c15e7d576c074a58eba658c08
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b