Browse Source

runtime: storage: Add exrinsic.

- add `update_distribution_buckets_for_bag`
- add `DistributionBucketsPerBagValueConstraint` constant
Shamil Gadelshin 3 years ago
parent
commit
08985d97fa

+ 47 - 0
runtime-modules/storage/src/bag_manager.rs

@@ -204,6 +204,17 @@ impl<T: Trait> BagManager<T> {
         )
     }
 
+    // Gets distribution bucket ID set from the bag container.
+    pub(crate) fn get_distribution_bucket_ids(
+        bag_id: &BagId<T>,
+    ) -> BTreeSet<T::DistributionBucketId> {
+        Self::query(
+            bag_id,
+            |bag| bag.distributed_by.clone(),
+            |bag| bag.distributed_by.clone(),
+        )
+    }
+
     // Gets storage bucket ID set from the bag container.
     pub(crate) fn get_storage_bucket_ids(bag_id: &BagId<T>) -> BTreeSet<T::StorageBucketId> {
         Self::query(
@@ -213,6 +224,42 @@ impl<T: Trait> BagManager<T> {
         )
     }
 
+    // Add distribution buckets to a bag.
+    pub(crate) fn add_distribution_buckets(
+        bag_id: &BagId<T>,
+        buckets: BTreeSet<T::DistributionBucketId>,
+    ) {
+        Self::mutate(
+            &bag_id,
+            |bag| {
+                bag.distributed_by.append(&mut buckets.clone());
+            },
+            |bag| {
+                bag.distributed_by.append(&mut buckets.clone());
+            },
+        );
+    }
+
+    // Remove distribution buckets from a bag.
+    pub(crate) fn remove_distribution_buckets(
+        bag_id: &BagId<T>,
+        buckets: BTreeSet<T::DistributionBucketId>,
+    ) {
+        Self::mutate(
+            &bag_id,
+            |bag| {
+                for bucket_id in buckets.iter() {
+                    bag.distributed_by.remove(bucket_id);
+                }
+            },
+            |bag| {
+                for bucket_id in buckets.iter() {
+                    bag.distributed_by.remove(bucket_id);
+                }
+            },
+        );
+    }
+
     // Abstract bag query function. Accepts two closures that should have similar result type.
     fn query<
         Res,

+ 165 - 0
runtime-modules/storage/src/lib.rs

@@ -58,8 +58,12 @@
 //! deletes distribution bucket family.
 //! - [create_distribution_bucket](./struct.Module.html#method.create_distribution_bucket) -
 //! creates distribution bucket.
+//! - [delete_distribution_bucket](./struct.Module.html#method.delete_distribution_bucket) -
+//! deletes distribution bucket.
 //! - [update_distribution_bucket_status](./struct.Module.html#method.update_distribution_bucket_status) -
 //! updates distribution bucket status (accepting new bags).
+//! - [update_distribution_buckets_for_bag](./struct.Module.html#method.update_distribution_buckets_for_bag) -
+//! updates distribution buckets for a bag.
 //!
 //! #### Public methods
 //! Public integration methods are exposed via the [DataObjectStorage](./trait.DataObjectStorage.html)
@@ -124,6 +128,7 @@ use storage_bucket_picker::StorageBucketPicker;
 
 // TODO: constants
 // Max number of pending invitations per distribution bucket.
+// TODO: mut in extrinsics
 
 /// Public interface for the storage module.
 pub trait DataObjectStorage<T: Trait> {
@@ -255,6 +260,9 @@ pub trait Trait: frame_system::Trait + balances::Trait + membership::Trait {
     /// "Storage buckets per bag" value constraint.
     type StorageBucketsPerBagValueConstraint: Get<StorageBucketsPerBagValueConstraint>;
 
+    /// "Distribution buckets per bag" value constraint.
+    type DistributionBucketsPerBagValueConstraint: Get<DistributionBucketsPerBagValueConstraint>;
+
     /// Defines the default dynamic bag creation policy for members.
     type DefaultMemberDynamicBagCreationPolicy: Get<DynamicBagCreationPolicy>;
 
@@ -370,6 +378,9 @@ impl DynamicBagCreationPolicy {
 /// "Storage buckets per bag" value constraint type.
 pub type StorageBucketsPerBagValueConstraint = BoundedValueConstraint<u64>;
 
+/// "Distribution buckets per bag" value constraint type.
+pub type DistributionBucketsPerBagValueConstraint = BoundedValueConstraint<u64>;
+
 /// Local module account handler.
 pub type StorageTreasury<T> = ModuleAccountHandler<T, <T as Trait>::ModuleId>;
 
@@ -824,6 +835,9 @@ decl_storage! {
 
         /// Distribution bucket id counter. Starts at zero.
         pub NextDistributionBucketId get(fn next_distribution_bucket_id): T::DistributionBucketId;
+
+        /// "Distribution buckets per bag" number limit.
+        pub DistributionBucketsPerBagLimit get (fn distribution_buckets_per_bag_limit): u64;
     }
 }
 
@@ -1020,6 +1034,18 @@ decl_event! {
         /// - distribution bucket family ID
         /// - distribution bucket ID
         DistributionBucketDeleted(DistributionBucketFamilyId, DistributionBucketId),
+
+        /// Emits on updating distribution buckets for bag.
+        /// Params
+        /// - bag ID
+        /// - storage buckets to add ID collection
+        /// - storage buckets to remove ID collection
+        DistributionBucketsUpdatedForBag(
+            BagId,
+            DistributionBucketFamilyId,
+            BTreeSet<DistributionBucketId>,
+            BTreeSet<DistributionBucketId>
+        ),
     }
 }
 
@@ -1148,6 +1174,21 @@ decl_error! {
 
         /// Distribution bucket doesn't exist.
         DistributionBucketDoesntExist,
+
+        /// Distribution bucket id collections are empty.
+        DistributionBucketIdCollectionsAreEmpty,
+
+        /// Distribution bucket doesn't accept new bags.
+        DistributionBucketDoesntAcceptNewBags,
+
+        /// Max distribution bucket number per bag limit exceeded.
+        MaxDistributionBucketNumberPerBagLimitExceeded,
+
+        /// Distribution bucket is not bound to a bag.
+        DistributionBucketIsNotBoundToBag,
+
+        /// Distribution bucket is bound to a bag.
+        DistributionBucketIsBoundToBag,
     }
 }
 
@@ -1191,6 +1232,10 @@ decl_module! {
         const MaxDistributionBucketNumberPerFamily: u64 =
             T::MaxDistributionBucketNumberPerFamily::get();
 
+        /// Exports const - "Distribution buckets per bag" value constraint.
+        const DistributionBucketsPerBagValueConstraint: StorageBucketsPerBagValueConstraint =
+            T::DistributionBucketsPerBagValueConstraint::get();
+
         // ===== Storage Lead actions =====
 
         /// Delete storage bucket. Must be empty. Storage operator must be missing.
@@ -1757,6 +1802,8 @@ decl_module! {
 
             // TODO: check for emptiness
 
+            // TODO: check dynamic bag policy
+
             //
             // == MUTATION SAFE ==
             //
@@ -1872,6 +1919,70 @@ decl_module! {
             );
         }
 
+        /// Updates distribution buckets for a bag.
+        #[weight = 10_000_000] // TODO: adjust weight
+        pub fn update_distribution_buckets_for_bag(
+            origin,
+            bag_id: BagId<T>,
+            family_id: T::DistributionBucketFamilyId, //TODO: remove this constraint?
+            add_buckets: BTreeSet<T::DistributionBucketId>,
+            remove_buckets: BTreeSet<T::DistributionBucketId>,
+        ) {
+            T::ensure_distribution_working_group_leader_origin(origin)?;
+
+            Self::validate_update_distribution_buckets_for_bag_params(
+                &bag_id,
+                &family_id,
+                &add_buckets,
+                &remove_buckets,
+            )?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // Update vouchers.
+            if !add_buckets.is_empty() {
+                BagManager::<T>::add_distribution_buckets(&bag_id, add_buckets.clone());
+            }
+            if !remove_buckets.is_empty() {
+                BagManager::<T>::remove_distribution_buckets(&bag_id, remove_buckets.clone());
+            }
+
+            Self::deposit_event(
+                RawEvent::DistributionBucketsUpdatedForBag(
+                    bag_id,
+                    family_id,
+                    add_buckets,
+                    remove_buckets
+                )
+            );
+        }
+
+        //TODO: distribution
+
+        //         /// Updates "Storage buckets per bag" number limit.
+        // #[weight = 10_000_000] // TODO: adjust weight
+        // pub fn update_storage_buckets_per_bag_limit(origin, new_limit: u64) {
+        //     T::ensure_storage_working_group_leader_origin(origin)?;
+        //
+        //     T::StorageBucketsPerBagValueConstraint::get().ensure_valid(
+        //         new_limit,
+        //         Error::<T>::StorageBucketsPerBagLimitTooLow,
+        //         Error::<T>::StorageBucketsPerBagLimitTooHigh,
+        //     )?;
+        //
+        //     //
+        //     // == MUTATION SAFE ==
+        //     //
+        //
+        //     StorageBucketsPerBagLimit::put(new_limit);
+        //
+        //     Self::deposit_event(RawEvent::StorageBucketsPerBagLimitUpdated(new_limit));
+        // }
+
+
+        //TODO: upldate distributing field
 
         // ===== Distribution Operator actions =====
 
@@ -2714,4 +2825,58 @@ impl<T: Trait> Module<T> {
             .cloned()
             .ok_or(Error::<T>::DistributionBucketDoesntExist)
     }
+
+    // Ensures validity of the `update_distribution_buckets_for_bag` extrinsic parameters
+    fn validate_update_distribution_buckets_for_bag_params(
+        bag_id: &BagId<T>,
+        family_id: &T::DistributionBucketFamilyId,
+        add_buckets: &BTreeSet<T::DistributionBucketId>,
+        remove_buckets: &BTreeSet<T::DistributionBucketId>,
+    ) -> DispatchResult {
+        ensure!(
+            !add_buckets.is_empty() || !remove_buckets.is_empty(),
+            Error::<T>::DistributionBucketIdCollectionsAreEmpty
+        );
+
+        BagManager::<T>::ensure_bag_exists(&bag_id)?;
+
+        let family = Self::ensure_distribution_bucket_family_exists(family_id)?;
+
+        let distribution_bucket_ids = BagManager::<T>::get_distribution_bucket_ids(bag_id);
+        let new_bucket_number = distribution_bucket_ids
+            .len()
+            .saturating_add(add_buckets.len())
+            .saturating_sub(remove_buckets.len())
+            .saturated_into::<u64>();
+
+        ensure!(
+            new_bucket_number <= Self::distribution_buckets_per_bag_limit(),
+            Error::<T>::MaxDistributionBucketNumberPerBagLimitExceeded
+        );
+
+        for bucket_id in remove_buckets.iter() {
+            Self::ensure_distribution_bucket_exists(&family, bucket_id)?;
+
+            ensure!(
+                distribution_bucket_ids.contains(&bucket_id),
+                Error::<T>::DistributionBucketIsNotBoundToBag
+            );
+        }
+
+        for bucket_id in add_buckets.iter() {
+            let bucket = Self::ensure_distribution_bucket_exists(&family, bucket_id)?;
+
+            ensure!(
+                bucket.accepting_new_bags,
+                Error::<T>::DistributionBucketDoesntAcceptNewBags
+            );
+
+            ensure!(
+                !distribution_bucket_ids.contains(&bucket_id),
+                Error::<T>::DistributionBucketIsBoundToBag
+            );
+        }
+
+        Ok(())
+    }
 }

+ 58 - 0
runtime-modules/storage/src/tests/fixtures.rs

@@ -1426,3 +1426,61 @@ impl DeleteDistributionBucketFixture {
         assert_eq!(actual_result, expected_result);
     }
 }
+
+pub struct UpdateDistributionBucketForBagsFixture {
+    origin: RawOrigin<u64>,
+    bag_id: BagId<Test>,
+    family_id: u64,
+    add_bucket_ids: BTreeSet<u64>,
+    remove_bucket_ids: BTreeSet<u64>,
+}
+
+impl UpdateDistributionBucketForBagsFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Signed(DEFAULT_ACCOUNT_ID),
+            bag_id: Default::default(),
+            family_id: Default::default(),
+            add_bucket_ids: Default::default(),
+            remove_bucket_ids: Default::default(),
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_add_bucket_ids(self, add_bucket_ids: BTreeSet<u64>) -> Self {
+        Self {
+            add_bucket_ids,
+            ..self
+        }
+    }
+
+    pub fn with_remove_bucket_ids(self, remove_bucket_ids: BTreeSet<u64>) -> Self {
+        Self {
+            remove_bucket_ids,
+            ..self
+        }
+    }
+
+    pub fn with_bag_id(self, bag_id: BagId<Test>) -> Self {
+        Self { bag_id, ..self }
+    }
+
+    pub fn with_family_id(self, family_id: u64) -> Self {
+        Self { family_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Storage::update_distribution_buckets_for_bag(
+            self.origin.clone().into(),
+            self.bag_id.clone(),
+            self.family_id,
+            self.add_bucket_ids.clone(),
+            self.remove_bucket_ids.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+    }
+}

+ 3 - 0
runtime-modules/storage/src/tests/mocks.rs

@@ -69,6 +69,8 @@ parameter_types! {
     pub const DefaultChannelDynamicBagCreationPolicy: DynamicBagCreationPolicy = DynamicBagCreationPolicy{
         number_of_storage_buckets: 4
     };
+    pub const DistributionBucketsPerBagValueConstraint: crate::DistributionBucketsPerBagValueConstraint =
+        crate::StorageBucketsPerBagValueConstraint {min: 3, max_min_diff: 7};
 }
 
 pub const STORAGE_WG_LEADER_ACCOUNT_ID: u64 = 100001;
@@ -98,6 +100,7 @@ impl crate::Trait for Test {
     type MaxRandomIterationNumber = MaxRandomIterationNumber;
     type MaxDistributionBucketFamilyNumber = MaxDistributionBucketFamilyNumber;
     type MaxDistributionBucketNumberPerFamily = MaxDistributionBucketNumberPerFamily;
+    type DistributionBucketsPerBagValueConstraint = DistributionBucketsPerBagValueConstraint;
 
     fn ensure_storage_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
         let account_id = ensure_signed(origin)?;

+ 164 - 1
runtime-modules/storage/src/tests/mod.rs

@@ -5,7 +5,7 @@ pub(crate) mod mocks;
 
 use frame_support::dispatch::DispatchError;
 use frame_support::traits::Currency;
-use frame_support::StorageMap;
+use frame_support::{StorageMap, StorageValue};
 use frame_system::RawOrigin;
 use sp_runtime::SaturatedConversion;
 use sp_std::collections::btree_set::BTreeSet;
@@ -3782,3 +3782,166 @@ fn delete_distribution_bucket_fails_with_non_existing_distribution_bucket_family
             ));
     });
 }
+
+#[test]
+fn update_distribution_buckets_for_bags_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        set_default_distribution_buckets_per_bag_limit();
+
+        let static_bag_id = StaticBagId::Council;
+        let bag_id = BagId::<Test>::StaticBag(static_bag_id.clone());
+
+        let family_id = CreateDistributionBucketFamilyFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let bucket_id = CreateDistributionBucketFixture::default()
+            .with_family_id(family_id)
+            .with_accept_new_bags(true)
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let add_buckets = BTreeSet::from_iter(vec![bucket_id]);
+
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .with_bag_id(bag_id.clone())
+            .with_family_id(family_id)
+            .with_add_bucket_ids(add_buckets.clone())
+            .call_and_assert(Ok(()));
+
+        let bag = Storage::static_bag(static_bag_id);
+        assert_eq!(bag.distributed_by, add_buckets);
+
+        EventFixture::assert_last_crate_event(RawEvent::DistributionBucketsUpdatedForBag(
+            bag_id,
+            family_id,
+            add_buckets,
+            BTreeSet::new(),
+        ));
+    });
+}
+
+#[test]
+fn update_distribution_buckets_for_bags_fails_with_non_existing_dynamic_bag() {
+    build_test_externalities().execute_with(|| {
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+        let bag_id = BagId::<Test>::DynamicBag(dynamic_bag_id.clone());
+
+        let family_id = CreateDistributionBucketFamilyFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let bucket_id = CreateDistributionBucketFixture::default()
+            .with_family_id(family_id)
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let add_buckets = BTreeSet::from_iter(vec![bucket_id]);
+
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .with_family_id(family_id)
+            .with_bag_id(bag_id.clone())
+            .with_add_bucket_ids(add_buckets.clone())
+            .call_and_assert(Err(Error::<Test>::DynamicBagDoesntExist.into()));
+    });
+}
+
+#[test]
+fn update_distribution_buckets_for_bags_fails_with_non_accepting_new_bags_bucket() {
+    build_test_externalities().execute_with(|| {
+        set_default_distribution_buckets_per_bag_limit();
+
+        let static_bag_id = StaticBagId::Council;
+        let bag_id = BagId::<Test>::StaticBag(static_bag_id.clone());
+
+        let family_id = CreateDistributionBucketFamilyFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let bucket_id = CreateDistributionBucketFixture::default()
+            .with_family_id(family_id)
+            .with_accept_new_bags(false)
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        let add_buckets = BTreeSet::from_iter(vec![bucket_id]);
+
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .with_family_id(family_id)
+            .with_bag_id(bag_id.clone())
+            .with_add_bucket_ids(add_buckets.clone())
+            .call_and_assert(Err(
+                Error::<Test>::DistributionBucketDoesntAcceptNewBags.into()
+            ));
+    });
+}
+
+#[test]
+fn update_distribution_buckets_for_bags_fails_with_non_leader_origin() {
+    build_test_externalities().execute_with(|| {
+        let non_leader_id = 1;
+
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(non_leader_id))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn update_distribution_buckets_for_bags_fails_with_empty_params() {
+    build_test_externalities().execute_with(|| {
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Err(
+                Error::<Test>::DistributionBucketIdCollectionsAreEmpty.into()
+            ));
+    });
+}
+
+#[test]
+fn update_distribution_buckets_for_bags_fails_with_non_existing_distribution_buckets() {
+    build_test_externalities().execute_with(|| {
+        set_default_distribution_buckets_per_bag_limit();
+
+        let invalid_bucket_id = 11000;
+        let buckets = BTreeSet::from_iter(vec![invalid_bucket_id]);
+        let bag_id = BagId::<Test>::StaticBag(StaticBagId::Council);
+
+        let family_id = CreateDistributionBucketFamilyFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .call_and_assert(Ok(()))
+            .unwrap();
+
+        // Invalid added bucket ID.
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .with_bag_id(bag_id.clone())
+            .with_family_id(family_id)
+            .with_add_bucket_ids(buckets.clone())
+            .call_and_assert(Err(Error::<Test>::DistributionBucketDoesntExist.into()));
+
+        // Invalid removed bucket ID.
+        UpdateDistributionBucketForBagsFixture::default()
+            .with_origin(RawOrigin::Signed(DISTRIBUTION_WG_LEADER_ACCOUNT_ID))
+            .with_bag_id(bag_id.clone())
+            .with_family_id(family_id)
+            .with_remove_bucket_ids(buckets.clone())
+            .call_and_assert(Err(Error::<Test>::DistributionBucketDoesntExist.into()));
+    });
+}
+
+fn set_default_distribution_buckets_per_bag_limit() {
+    crate::DistributionBucketsPerBagLimit::put(5);
+}

+ 3 - 0
runtime/src/lib.rs

@@ -669,6 +669,8 @@ parameter_types! {
     pub const DefaultChannelDynamicBagCreationPolicy: DynamicBagCreationPolicy = DynamicBagCreationPolicy{
         number_of_storage_buckets: 4
     }; //TODO: adjust value
+    pub const DistributionBucketsPerBagValueConstraint: storage::DistributionBucketsPerBagValueConstraint =
+        storage::DistributionBucketsPerBagValueConstraint {min: 3, max_min_diff: 7}; //TODO: adjust value
 }
 
 impl storage::Trait for Runtime {
@@ -691,6 +693,7 @@ impl storage::Trait for Runtime {
     type MaxRandomIterationNumber = MaxRandomIterationNumber;
     type MaxDistributionBucketFamilyNumber = MaxDistributionBucketFamilyNumber;
     type MaxDistributionBucketNumberPerFamily = MaxDistributionBucketNumberPerFamily;
+    type DistributionBucketsPerBagValueConstraint = DistributionBucketsPerBagValueConstraint;
 
     fn ensure_storage_working_group_leader_origin(origin: Self::Origin) -> DispatchResult {
         StorageWorkingGroup::ensure_origin_is_active_leader(origin)