Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Allow to set the max supply for collection #11441

Merged
merged 5 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frame/uniques/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {

with_details(collection_details)?;

if let Ok(max_supply) = CollectionMaxSupply::<T, I>::try_get(&collection) {
ensure!(collection_details.items < max_supply, Error::<T, I>::MaxSupplyReached);
}

let items =
collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?;
collection_details.items = items;
Expand Down
50 changes: 50 additions & 0 deletions frame/uniques/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ pub mod pallet {
OptionQuery,
>;

#[pallet::storage]
/// Keeps track of the number of items a collection might have.
pub(super) type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Expand Down Expand Up @@ -334,6 +339,8 @@ pub mod pallet {
},
/// Ownership acceptance has changed for an account.
OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
/// Max supply has been set for a collection.
CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
}

#[pallet::error]
Expand Down Expand Up @@ -362,6 +369,10 @@ pub mod pallet {
Unaccepted,
/// The item is locked.
Locked,
/// All items have been minted.
MaxSupplyReached,
/// The max supply has already been set.
MaxSupplyAlreadySet,
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
Expand Down Expand Up @@ -1356,5 +1367,44 @@ pub mod pallet {
Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
Ok(())
}

/// Set the maximum amount of items a collection could have.
///
/// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of
/// the `collection`.
///
/// Note: it's a one-time call method.
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved
///
/// - `collection`: The identifier of the collection to change.
/// - `max_supply`: The maximum amount of items a collection could have.
///
/// Emits `CollectionMaxSupplySet` event when successful.
#[pallet::weight(0)]
pub fn set_collection_max_supply(
origin: OriginFor<T>,
collection: T::CollectionId,
max_supply: u32,
) -> DispatchResult {
let maybe_check_owner = T::ForceOrigin::try_origin(origin)
.map(|_| None)
.or_else(|origin| ensure_signed(origin).map(Some))?;

ensure!(
!CollectionMaxSupply::<T, I>::contains_key(&collection),
Error::<T, I>::MaxSupplyAlreadySet
);

let details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}

ensure!(details.items <= max_supply, Error::<T, I>::NoPermission);
jsidorenko marked this conversation as resolved.
Show resolved Hide resolved

CollectionMaxSupply::<T, I>::insert(&collection, max_supply);
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
Ok(())
}
}
}
57 changes: 55 additions & 2 deletions frame/uniques/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

//! Tests for Uniques pallet.

use super::*;
use crate::mock::*;
use crate::{mock::*, Event, *};
use frame_support::{assert_noop, assert_ok, traits::Currency};
use pallet_balances::Error as BalancesError;
use sp_std::prelude::*;
Expand Down Expand Up @@ -71,6 +70,18 @@ fn attributes(collection: u32) -> Vec<(Option<u32>, Vec<u8>, Vec<u8>)> {
s
}

fn events() -> Vec<Event<Test>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep seeing this pattern in every pallet 🙈
Really makes me want to write a generic function and refactor them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a more advanced function to test the events, for example, to check that some event is within the array, but has different params. That would help to understand whether the params are wrong or the event wasn't emitted

Copy link
Member

@shawntabrizi shawntabrizi May 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do it. 👍

let result = System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| if let mock::Event::Uniques(inner) = e { Some(inner) } else { None })
.collect::<Vec<_>>();

System::reset_events();

result
}

#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -633,3 +644,45 @@ fn cancel_approval_works_with_force() {
);
});
}

#[test]
fn max_supply_should_work() {
new_test_ext().execute_with(|| {
let collection_id = 0;
let user_id = 1;
let max_supply = 2;

// validate set_collection_max_supply
assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true));
assert!(!CollectionMaxSupply::<Test>::contains_key(collection_id));

assert_ok!(Uniques::set_collection_max_supply(
Origin::signed(user_id),
collection_id,
max_supply
));
assert_eq!(CollectionMaxSupply::<Test>::get(collection_id).unwrap(), max_supply);

assert!(events().contains(&Event::<Test>::CollectionMaxSupplySet {
ggwpez marked this conversation as resolved.
Show resolved Hide resolved
collection: collection_id,
max_supply,
}));

assert_noop!(
Uniques::set_collection_max_supply(
Origin::signed(user_id),
collection_id,
max_supply + 1
),
Error::<Test>::MaxSupplyAlreadySet
);

// validate we can't mint more to max supply
assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 0, user_id));
assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 1, user_id));
assert_noop!(
Uniques::mint(Origin::signed(user_id), collection_id, 2, user_id),
Error::<Test>::MaxSupplyReached
);
});
}