Browse Source

Merge branch 'giza' into giza-staging-update

Mokhtar Naamani 3 years ago
parent
commit
e9493798dc

+ 156 - 86
runtime-modules/content/src/lib.rs

@@ -616,7 +616,6 @@ decl_module! {
             // Ensure given origin is lead
             ensure_lead_auth_success::<T>(&sender)?;
 
-
             // Ensure curator group under provided curator_group_id already exist, retrieve corresponding one
             let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?;
 
@@ -674,7 +673,6 @@ decl_module! {
             Self::deposit_event(RawEvent::CuratorRemoved(curator_group_id, curator_id));
         }
 
-        // TODO: Add Option<reward_account> to ChannelCreationParameters ?
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn create_channel(
             origin,
@@ -700,27 +698,66 @@ decl_module! {
             // ensure collaborator member ids are valid
             Self::validate_collaborator_set(&params.collaborators)?;
 
+            let upload_params = params.assets.as_ref().map(|assets| {
+                Self::construct_upload_parameters(
+                    assets,
+                    &channel_id,
+                    &sender
+                )});
+
+            let channel_bag_id = Self::bag_id_for_channel(&channel_id);
+
+            let deletion_prize = storage::DynamicBagDeletionPrize::<T> {
+                prize: Zero::zero(), // put 0 for Giza release
+                account_id: sender.clone(),
+            };
+
+            if Storage::<T>::ensure_bag_exists(&channel_bag_id).is_err() {
+                if let Some(params) = upload_params.clone() {
+                    Storage::<T>::can_create_dynamic_bag_with_objects_constraints(
+                        &DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(channel_id),
+                        &Some(deletion_prize.clone()),
+                        &params
+                    )?;
+                } else {
+                    Storage::<T>::can_create_dynamic_bag(
+                        &DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(channel_id),
+                        &Some(deletion_prize.clone()),
+                    )?;
+                }
+            }
+
             //
             // == MUTATION SAFE ==
             //
 
-            // upload to storage
-            if let Some(upload_assets) = params.assets.as_ref() {
-                Self::upload_assets_to_storage(
-                    upload_assets,
-                    &channel_id,
-                    &sender,
+            if Storage::<T>::ensure_bag_exists(&channel_bag_id).is_err() {
+                if let Some(params) = upload_params.clone() {
+                    Storage::<T>::create_dynamic_bag_with_objects_constraints(
+                        DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(channel_id),
+                        Some(deletion_prize),
+                        params,
                 )?;
+                // create_dynamic_bag_with_objects with its can* guard ensures that this invocation succeds
+                } else {
+                    Storage::<T>::create_dynamic_bag(
+                        DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(channel_id),
+                        Some(deletion_prize),
+                    )?;
+                }
+            }
+
+             // this will not fail because can_create_dynamic_bag_with_objects_constraints will check also for successful upload conditions
+            if let Some(params) = upload_params.clone() {
+                Storage::<T>::upload_data_objects(params)?;
             }
 
             // Only increment next channel id if adding content was successful
             NextChannelId::<T>::mutate(|id| *id += T::ChannelId::one());
 
-
             // channel creation
             let channel: Channel<T> = ChannelRecord {
                 owner: channel_owner,
-                // a newly create channel has zero videos ??
                 num_videos: 0u64,
                 is_censored: false,
                 reward_account: params.reward_account.clone(),
@@ -767,27 +804,50 @@ decl_module! {
                 channel.collaborators = new_collabs.clone();
             }
 
+            if let Some(upload_assets) = params.assets_to_upload.as_ref() {
+                let params = Self::construct_upload_parameters(
+                    upload_assets,
+                    &channel_id,
+                    &sender
+                );
+
+                Storage::<T>::can_upload_data_objects(&params)?;
+            }
+
+            if !params.assets_to_remove.is_empty() {
+                Storage::<T>::can_delete_data_objects(
+                    &Self::bag_id_for_channel(&channel_id),
+                    &params.assets_to_remove
+                )?;
+            }
+
             //
             // == MUTATION SAFE ==
             //
 
-            // upload assets to storage
             if let Some(upload_assets) = params.assets_to_upload.as_ref() {
-                Self::upload_assets_to_storage(
+                let params = Self::construct_upload_parameters(
                     upload_assets,
                     &channel_id,
-                    &sender,
-                )?;
+                    &sender
+                );
+
+                Storage::<T>::upload_data_objects(params.clone())?;
             }
 
-            // remove eassets from storage
-            Self::remove_assets_from_storage(&params.assets_to_remove, &channel_id, &sender)?;
+            if !params.assets_to_remove.is_empty() {
+                Storage::<T>::delete_data_objects(
+                    sender,
+                    Self::bag_id_for_channel(&channel_id),
+                    params.assets_to_remove.clone()
+                )?;
+            }
 
             // Update the channel
             ChannelById::<T>::insert(channel_id, channel.clone());
 
             Self::deposit_event(RawEvent::ChannelUpdated(actor, channel_id, channel, params));
-}
+        }
 
         // extrinsics for channel deletion
         #[weight = 10_000_000] // TODO: adjust weight
@@ -823,19 +883,36 @@ decl_module! {
                     Error::<T>::InvalidBagSizeSpecified
                 );
 
+                // construct collection of assets to be removed
+                let assets_to_remove = T::DataObjectStorage::get_data_objects_id(&bag_id);
+
+                if !assets_to_remove.is_empty() {
+                    Storage::<T>::can_delete_dynamic_bag_with_objects(
+                        &dyn_bag,
+                    )?;
+
+                    Storage::<T>::can_delete_data_objects(
+                        &bag_id,
+                        &assets_to_remove,
+                    )?;
+                } else {
+                    Storage::<T>::can_delete_dynamic_bag(
+                        &dyn_bag,
+                    )?;
+                }
+
                 //
                 // == MUTATION SAFE ==
                 //
 
-                // construct collection of assets to be removed
-                let assets_to_remove = T::DataObjectStorage::get_data_objects_id(&bag_id);
-
                 // remove specified assets from storage
-                Self::remove_assets_from_storage(
-                    &assets_to_remove,
-                    &channel_id,
-                    &sender,
-                )?;
+                if !assets_to_remove.is_empty() {
+                    Storage::<T>::delete_data_objects(
+                        sender.clone(),
+                        Self::bag_id_for_channel(&channel_id),
+                        assets_to_remove.clone(),
+                    )?;
+                }
 
                 // delete channel dynamic bag
                 Storage::<T>::delete_dynamic_bag(
@@ -882,8 +959,6 @@ decl_module! {
                 channel.is_censored = is_censored
             });
 
-            // TODO: unset the reward account ? so no revenue can be earned for censored channels?
-
             Self::deposit_event(RawEvent::ChannelCensorshipStatusUpdated(actor, channel_id, is_censored, rationale));
         }
 
@@ -1000,13 +1075,14 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
-            // upload to storage
+            // upload to storage: check is performed beforehand in the extrinsics so storage state won't be endangered
             if let Some(upload_assets) = params.assets.as_ref() {
-                Self::upload_assets_to_storage(
+                let params = Self::construct_upload_parameters(
                     upload_assets,
                     &channel_id,
-                    &sender,
-                )?;
+                    &sender
+                );
+                 Storage::<T>::upload_data_objects(params)?;
             }
 
             // create the video struct
@@ -1057,19 +1133,40 @@ decl_module! {
                 &channel,
             )?;
 
+            if let Some(upload_assets) = params.assets_to_upload.as_ref() {
+                let params = Self::construct_upload_parameters(
+                    upload_assets,
+                    &channel_id,
+                    &sender
+                );
+                Storage::<T>::can_upload_data_objects(&params)?;
+            }
+
+            if !params.assets_to_remove.is_empty() {
+                Storage::<T>::can_delete_data_objects(
+                    &Self::bag_id_for_channel(&channel_id),
+                    &params.assets_to_remove,
+                )?;
+            }
+
             //
             // == MUTATION SAFE ==
             //
 
-            // remove specified assets from channel bag in storage
-            Self::remove_assets_from_storage(&params.assets_to_remove, &channel_id, &sender)?;
-
-            // atomically upload to storage and return the # of uploaded assets
             if let Some(upload_assets) = params.assets_to_upload.as_ref() {
-                Self::upload_assets_to_storage(
+                let params = Self::construct_upload_parameters(
                     upload_assets,
                     &channel_id,
-                    &sender,
+                    &sender
+                );
+                Storage::<T>::upload_data_objects(params)?;
+            }
+
+            if !params.assets_to_remove.is_empty() {
+                Storage::<T>::delete_data_objects(
+                    sender,
+                    Self::bag_id_for_channel(&channel_id),
+                    params.assets_to_remove.clone(),
                 )?;
             }
 
@@ -1102,12 +1199,24 @@ decl_module! {
             // ensure video can be removed
             Self::ensure_video_can_be_removed(&video)?;
 
+            if !assets_to_remove.is_empty() {
+                Storage::<T>::can_delete_data_objects(
+                    &Self::bag_id_for_channel(&channel_id),
+                    &assets_to_remove,
+                )?;
+            }
+
             //
             // == MUTATION SAFE ==
             //
 
-            // remove specified assets from channel bag in storage
-            Self::remove_assets_from_storage(&assets_to_remove, &channel_id, &sender)?;
+            if !assets_to_remove.is_empty() {
+                Storage::<T>::delete_data_objects(
+                    sender,
+                    Self::bag_id_for_channel(&channel_id),
+                    assets_to_remove.clone()
+                )?;
+            }
 
             // Remove video
             VideoById::<T>::remove(video_id);
@@ -1494,28 +1603,6 @@ impl<T: Trait> Module<T> {
         Ok(VideoCategoryById::<T>::get(video_category_id))
     }
 
-    fn pick_upload_parameters_from_assets(
-        assets: &StorageAssets<T>,
-        channel_id: &T::ChannelId,
-        prize_source_account: &T::AccountId,
-    ) -> UploadParameters<T> {
-        // dynamic bag for a media object
-        let dyn_bag = DynamicBagIdType::<T::MemberId, T::ChannelId>::Channel(*channel_id);
-        let bag_id = BagIdType::from(dyn_bag.clone());
-
-        if T::DataObjectStorage::ensure_bag_exists(&bag_id).is_err() {
-            // create_dynamic_bag checks automatically satifsfied with None as second parameter
-            Storage::<T>::create_dynamic_bag(dyn_bag, None).unwrap();
-        }
-
-        UploadParametersRecord {
-            bag_id,
-            object_creation_list: assets.object_creation_list.clone(),
-            deletion_prize_source_account_id: prize_source_account.clone(),
-            expected_data_size_fee: assets.expected_data_size_fee,
-        }
-    }
-
     fn actor_to_channel_owner(
         actor: &ContentActor<T::CuratorGroupId, T::CuratorId, T::MemberId>,
     ) -> ActorToChannelOwnerResult<T> {
@@ -1539,35 +1626,18 @@ impl<T: Trait> Module<T> {
         Err(Error::<T>::FeatureNotImplemented.into())
     }
 
-    fn upload_assets_to_storage(
+    // construct parameters to be upload to storage
+    fn construct_upload_parameters(
         assets: &StorageAssets<T>,
         channel_id: &T::ChannelId,
         prize_source_account: &T::AccountId,
-    ) -> DispatchResult {
-        // construct upload params
-        let upload_params =
-            Self::pick_upload_parameters_from_assets(assets, channel_id, prize_source_account);
-
-        // attempt to upload objects att
-        Storage::<T>::upload_data_objects(upload_params.clone())?;
-
-        Ok(())
-    }
-
-    fn remove_assets_from_storage(
-        assets: &BTreeSet<DataObjectId<T>>,
-        channel_id: &T::ChannelId,
-        prize_source_account: &T::AccountId,
-    ) -> DispatchResult {
-        // remove assets if any
-        if !assets.is_empty() {
-            Storage::<T>::delete_data_objects(
-                prize_source_account.clone(),
-                Self::bag_id_for_channel(&channel_id),
-                assets.clone(),
-            )?;
+    ) -> UploadParameters<T> {
+        UploadParameters::<T> {
+            bag_id: Self::bag_id_for_channel(channel_id),
+            object_creation_list: assets.object_creation_list.clone(),
+            deletion_prize_source_account_id: prize_source_account.clone(),
+            expected_data_size_fee: assets.expected_data_size_fee,
         }
-        Ok(())
     }
 
     fn validate_collaborator_set(collaborators: &BTreeSet<T::MemberId>) -> DispatchResult {

+ 66 - 6
runtime-modules/content/src/tests/channels.rs

@@ -12,10 +12,12 @@ fn successful_channel_deletion() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         // create an account with enought balance
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         // 3 assets added at creation
@@ -66,7 +68,7 @@ fn successful_channel_deletion() {
             FIRST_MEMBER_ORIGIN,
             ContentActor::Member(FIRST_MEMBER_ID),
             channel_id,
-            3u64,
+            3u64, // now assets are 0
             Ok(()),
         );
 
@@ -83,11 +85,12 @@ fn successful_channel_deletion() {
             },
             Ok(()),
         );
+
         delete_channel_mock(
             FIRST_MEMBER_ORIGIN,
             ContentActor::Member(FIRST_MEMBER_ID),
             empty_channel_id,
-            43u64, // this param will be discarded if channel has no assets
+            0u64,
             Ok(()),
         );
     })
@@ -99,10 +102,11 @@ fn successful_channel_assets_deletion() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
         // create an account with enought balance
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         // 3 assets
@@ -163,10 +167,12 @@ fn succesful_channel_update() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         // create an account with enought balance
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         // 2 + 1 assets to be uploaded
@@ -254,10 +260,12 @@ fn succesful_channel_creation() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         // create an account with enought balance
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         // 3 assets to be uploaded
@@ -297,6 +305,7 @@ fn succesful_channel_creation() {
 #[test]
 fn lead_cannot_create_channel() {
     with_default_mock_builder(|| {
+        create_initial_storage_buckets();
         assert_err!(
             Content::create_channel(
                 Origin::signed(LEAD_ORIGIN),
@@ -438,6 +447,7 @@ fn invalid_member_cannot_create_channel() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
         // Not a member
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -459,6 +469,7 @@ fn invalid_member_cannot_update_channel() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
             ContentActor::Member(FIRST_MEMBER_ID),
@@ -493,6 +504,8 @@ fn invalid_member_cannot_delete_channel() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
             ContentActor::Member(FIRST_MEMBER_ID),
@@ -523,6 +536,8 @@ fn non_authorized_collaborators_cannot_update_channel() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
+
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -598,6 +613,7 @@ fn authorized_collaborators_can_update_channel() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -749,3 +765,47 @@ fn channel_censoring() {
         ));
     })
 }
+
+#[test]
+fn channel_creation_doesnt_leave_bags_dangling() {
+    with_default_mock_builder(|| {
+        // in order to emit events
+        run_to_block(1);
+
+        create_initial_storage_buckets();
+        // number of assets big enought to make upload_data_objects throw
+        let asset_num = 100_000usize;
+        let mut object_creation_list =
+            Vec::<DataObjectCreationParameters>::with_capacity(asset_num);
+        for _i in 0..asset_num {
+            object_creation_list.push(DataObjectCreationParameters {
+                size: 1_000_000, // size big enought to make upload_data_objects throw
+                ipfs_content_id: b"test".to_vec(),
+            });
+        }
+
+        let assets = StorageAssetsRecord {
+            object_creation_list: object_creation_list,
+            expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+        };
+
+        let channel_id = NextChannelId::<Test>::get();
+        // create channel
+        create_channel_mock(
+            FIRST_MEMBER_ORIGIN,
+            ContentActor::Member(FIRST_MEMBER_ID),
+            ChannelCreationParametersRecord {
+                assets: Some(assets),
+                meta: Some(vec![]),
+                reward_account: None,
+                collaborators: BTreeSet::new(),
+            },
+            Err(storage::Error::<Test>::MaxDataObjectSizeExceeded.into()),
+        );
+
+        // ensure that no bag are left dangling
+        let dyn_bag = DynamicBagIdType::<MemberId, ChannelId>::Channel(channel_id);
+        let bag_id = storage::BagIdType::from(dyn_bag.clone());
+        assert!(<Test as Trait>::DataObjectStorage::ensure_bag_exists(&bag_id).is_err());
+    })
+}

+ 60 - 2
runtime-modules/content/src/tests/mock.rs

@@ -52,7 +52,7 @@ pub const COLLABORATOR_MEMBER_ID: MemberId = 9;
 
 /// Constants
 // initial balancer for an account
-pub const INIT_BALANCE: u32 = 500;
+pub const INITIAL_BALANCE: u32 = 1_000_000;
 
 impl_outer_origin! {
     pub enum Origin for Test {}
@@ -675,7 +675,65 @@ pub fn helper_init_accounts(accounts: Vec<u64>) {
     for acc in accounts.iter() {
         let _ = balances::Module::<Test>::deposit_creating(
             acc,
-            <Test as balances::Trait>::Balance::from(INIT_BALANCE),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
     }
 }
+
+pub fn create_initial_storage_buckets() {
+    // first set limits
+    assert_eq!(
+        Storage::<Test>::update_storage_buckets_voucher_max_limits(
+            Origin::signed(STORAGE_WG_LEADER_ACCOUNT_ID),
+            400,
+            40
+        ),
+        Ok(())
+    );
+
+    // create bucket(s)
+    assert_eq!(
+        Storage::<Test>::create_storage_bucket(
+            Origin::signed(STORAGE_WG_LEADER_ACCOUNT_ID),
+            None,
+            true,
+            100,
+            10,
+        ),
+        Ok(())
+    );
+}
+
+pub fn create_channel_with_bag() {
+    // 3 assets added at creation
+    let assets = StorageAssetsRecord {
+        object_creation_list: vec![
+            DataObjectCreationParameters {
+                size: 3,
+                ipfs_content_id: b"first".to_vec(),
+            },
+            DataObjectCreationParameters {
+                size: 3,
+                ipfs_content_id: b"second".to_vec(),
+            },
+            DataObjectCreationParameters {
+                size: 3,
+                ipfs_content_id: b"third".to_vec(),
+            },
+        ],
+        expected_data_size_fee: storage::DataObjectPerMegabyteFee::<Test>::get(),
+    };
+
+    // create channel
+    create_channel_mock(
+        FIRST_MEMBER_ORIGIN,
+        ContentActor::Member(FIRST_MEMBER_ID),
+        ChannelCreationParametersRecord {
+            assets: Some(assets),
+            meta: None,
+            reward_account: None,
+            collaborators: BTreeSet::new(),
+        },
+        Ok(()),
+    );
+}

+ 17 - 24
runtime-modules/content/src/tests/videos.rs

@@ -28,25 +28,19 @@ fn video_creation_successful() {
     with_default_mock_builder(|| {
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         // depositi initial balance
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         let channel_id = NextChannelId::<Test>::get();
 
-        create_channel_mock(
-            FIRST_MEMBER_ORIGIN,
-            ContentActor::Member(FIRST_MEMBER_ID),
-            ChannelCreationParametersRecord {
-                assets: None,
-                meta: None,
-                reward_account: None,
-                collaborators: BTreeSet::new(),
-            },
-            Ok(()),
-        );
+        create_initial_storage_buckets();
+
+        create_channel_with_bag();
 
         let params = VideoCreationParametersRecord {
             assets: Some(StorageAssetsRecord {
@@ -84,24 +78,15 @@ fn video_update_successful() {
     with_default_mock_builder(|| {
         run_to_block(1);
 
+        create_initial_storage_buckets();
         let _ = balances::Module::<Test>::deposit_creating(
             &FIRST_MEMBER_ORIGIN,
-            <Test as balances::Trait>::Balance::from(100u32),
+            <Test as balances::Trait>::Balance::from(INITIAL_BALANCE),
         );
 
         let channel_id = NextChannelId::<Test>::get();
 
-        create_channel_mock(
-            FIRST_MEMBER_ORIGIN,
-            ContentActor::Member(FIRST_MEMBER_ID),
-            ChannelCreationParametersRecord {
-                assets: None,
-                meta: None,
-                reward_account: None,
-                collaborators: BTreeSet::new(),
-            },
-            Ok(()),
-        );
+        create_channel_with_bag();
 
         // create video with 3 assets
         let params = VideoCreationParametersRecord {
@@ -413,6 +398,8 @@ fn non_authorized_collaborators_cannot_add_video() {
         // Run to block one to see emitted events
         run_to_block(1);
 
+        create_initial_storage_buckets();
+
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
         // create channel
@@ -449,6 +436,7 @@ fn non_authorized_collaborators_cannot_update_video() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -498,6 +486,7 @@ fn non_authorized_collaborators_cannot_delete_video() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -546,6 +535,7 @@ fn authorized_collaborators_can_add_video() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -582,6 +572,8 @@ fn authorized_collaborators_can_update_video() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
+
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,
@@ -633,6 +625,7 @@ fn authorized_collaborators_can_delete_video() {
 
         helper_init_accounts(vec![FIRST_MEMBER_ORIGIN, COLLABORATOR_MEMBER_ORIGIN]);
 
+        create_initial_storage_buckets();
         // create channel
         create_channel_mock(
             FIRST_MEMBER_ORIGIN,

+ 336 - 125
runtime-modules/storage/src/lib.rs

@@ -97,6 +97,10 @@
 //! - delete_dynamic_bag
 //! - can_create_dynamic_bag
 //! - create_dynamic_bag
+//! - can_create_dynamic_bag_with_objects_constraints
+//! - create_dynamic_bag_with_objects_constraints
+//! - can_delete_dynamic_bag_with_objects
+
 //!
 //! ### Pallet constants
 //! - DataObjectDeletionPrize
@@ -118,7 +122,6 @@
 #![allow(clippy::unused_unit)]
 // needed for step iteration over DataObjectId range
 #![feature(step_trait)]
-
 #[cfg(test)]
 mod tests;
 
@@ -200,6 +203,9 @@ pub trait DataObjectStorage<T: Trait> {
     /// Validates `delete_dynamic_bag` parameters and conditions.
     fn can_delete_dynamic_bag(bag_id: &DynamicBagId<T>) -> DispatchResult;
 
+    /// Validates `delete_dynamic_bag` without checking for num objects == 0
+    fn can_delete_dynamic_bag_with_objects(bag_id: &DynamicBagId<T>) -> DispatchResult;
+
     /// Creates dynamic bag. BagId should provide the caller.
     fn create_dynamic_bag(
         bag_id: DynamicBagId<T>,
@@ -212,6 +218,20 @@ pub trait DataObjectStorage<T: Trait> {
         deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
     ) -> DispatchResult;
 
+    /// Same as create_dynamic_bag but with caller provided objects/data
+    fn create_dynamic_bag_with_objects_constraints(
+        bag_id: DynamicBagId<T>,
+        deletion_prize: Option<DynamicBagDeletionPrize<T>>,
+        params: UploadParameters<T>,
+    ) -> DispatchResult;
+
+    /// Same as can_create_dynamic_bag but with caller provided objects/data
+    fn can_create_dynamic_bag_with_objects_constraints(
+        bag_id: &DynamicBagId<T>,
+        deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
+        params: &UploadParameters<T>,
+    ) -> DispatchResult;
+
     /// Checks if a bag does exists and returns it. Static Always exists
     fn ensure_bag_exists(bag_id: &BagId<T>) -> Result<Bag<T>, DispatchError>;
 
@@ -466,6 +486,12 @@ pub type WorkerId<T> = <T as common::MembershipTypes>::ActorId;
 /// Balance alias for `balances` module.
 pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 
+/// Type alias for the storage & distribution bucket ids pair
+pub type BucketPair<T> = (
+    BTreeSet<<T as Trait>::StorageBucketId>,
+    BTreeSet<DistributionBucketId<T>>,
+);
+
 /// The fundamental concept in the system, which represents single static binary object in the
 /// system. The main goal of the system is to retain an index of all such objects, including who
 /// owns them, and information about what actors are currently tasked with storing and distributing
@@ -1440,6 +1466,9 @@ decl_error! {
         /// Max data object size exceeded.
         MaxDataObjectSizeExceeded,
 
+        /// Different Accounts for dynamic bag deletion prize and upload fees
+        AccountsNotCoherent,
+
         /// Invalid transactor account ID for this bucket.
         InvalidTransactorAccount,
     }
@@ -2498,46 +2527,8 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
 
     fn upload_data_objects(params: UploadParameters<T>) -> DispatchResult {
         let bag = Self::ensure_bag_exists(&params.bag_id)?;
-
         let bag_change = Self::validate_upload_data_objects_parameters(&params)?;
-
-        //
-        // == MUTATION SAFE ==
-        //
-
-        let data = Self::create_data_objects(params.object_creation_list.clone());
-
-        <StorageTreasury<T>>::deposit(
-            &params.deletion_prize_source_account_id,
-            bag_change.total_deletion_prize,
-        )?;
-
-        Self::slash_data_size_fee(
-            &params.deletion_prize_source_account_id,
-            bag_change.voucher_update.objects_total_size,
-        );
-
-        // Save next object id.
-        <NextDataObjectId<T>>::put(data.next_data_object_id);
-
-        // Insert new objects.
-        for (data_object_id, data_object) in data.data_objects_map.iter() {
-            DataObjectsById::<T>::insert(&params.bag_id, &data_object_id, data_object);
-        }
-
-        Self::change_storage_bucket_vouchers_for_bag(
-            &params.bag_id,
-            &bag,
-            &bag_change.voucher_update,
-            OperationType::Increase,
-        );
-
-        Self::deposit_event(RawEvent::DataObjectsUploaded(
-            data.data_objects_map.keys().cloned().collect(),
-            params,
-            T::DataObjectDeletionPrize::get(),
-        ));
-
+        Self::upload_data_objects_inner(&params, &bag_change, &bag)?;
         Ok(())
     }
 
@@ -2634,15 +2625,20 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
         Ok(())
     }
 
-    fn can_delete_dynamic_bag(bag_id: &DynamicBagId<T>) -> DispatchResult {
-        Self::validate_delete_dynamic_bag_params(bag_id).map(|_| ())
+    fn can_delete_dynamic_bag(dynamic_bag_id: &DynamicBagId<T>) -> DispatchResult {
+        Self::validate_delete_dynamic_bag_params(dynamic_bag_id, false).map(|_| ())
+    }
+
+    fn can_delete_dynamic_bag_with_objects(dynamic_bag_id: &DynamicBagId<T>) -> DispatchResult {
+        Self::validate_delete_dynamic_bag_params(dynamic_bag_id, true).map(|_| ())
     }
 
     fn delete_dynamic_bag(
         deletion_prize_account_id: T::AccountId,
         dynamic_bag_id: DynamicBagId<T>,
     ) -> DispatchResult {
-        let deletion_prize = Self::validate_delete_dynamic_bag_params(&dynamic_bag_id)?;
+        // make deletion always be performed on an empty bag
+        let deletion_prize = Self::validate_delete_dynamic_bag_params(&dynamic_bag_id, false)?;
 
         let bag_id: BagId<T> = dynamic_bag_id.clone().into();
 
@@ -2668,21 +2664,100 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
         dynamic_bag_id: DynamicBagId<T>,
         deletion_prize: Option<DynamicBagDeletionPrize<T>>,
     ) -> DispatchResult {
-        Self::validate_create_dynamic_bag_params(&dynamic_bag_id, &deletion_prize)?;
+        // validate params and get storage & distribution buckets
+        let bag_change =
+            Self::validate_create_dynamic_bag_params(&dynamic_bag_id, &deletion_prize, &None)?;
+
+        let (storage_bucket_ids, distribution_bucket_ids) =
+            Self::pick_buckets_for_bag(dynamic_bag_id.clone(), &bag_change)?;
+
+        //
+        // == MUTATION SAFE ==
+        //
+
+        Self::create_dynamic_bag_inner(
+            &dynamic_bag_id,
+            &deletion_prize,
+            &storage_bucket_ids,
+            &distribution_bucket_ids,
+        )?;
+        Ok(())
+    }
+
+    fn create_dynamic_bag_with_objects_constraints(
+        dynamic_bag_id: DynamicBagId<T>,
+        deletion_prize: Option<DynamicBagDeletionPrize<T>>,
+        params: UploadParameters<T>,
+    ) -> DispatchResult {
+        let bag_change = Self::validate_create_dynamic_bag_params(
+            &dynamic_bag_id,
+            &deletion_prize,
+            &Some(params),
+        )?;
+
+        let (storage_bucket_ids, distribution_bucket_ids) =
+            Self::pick_buckets_for_bag(dynamic_bag_id.clone(), &bag_change)?;
 
         //
         // == MUTATION SAFE ==
         //
+        Self::create_dynamic_bag_inner(
+            &dynamic_bag_id,
+            &deletion_prize,
+            &storage_bucket_ids,
+            &distribution_bucket_ids,
+        )?;
+        Ok(())
+    }
+
+    fn can_create_dynamic_bag(
+        bag_id: &DynamicBagId<T>,
+        deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
+    ) -> DispatchResult {
+        Self::validate_create_dynamic_bag_params(bag_id, deletion_prize, &None).map(|_| ())
+    }
+
+    fn can_create_dynamic_bag_with_objects_constraints(
+        dynamic_bag_id: &DynamicBagId<T>,
+        deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
+        params: &UploadParameters<T>,
+    ) -> DispatchResult {
+        let bag_change = Self::validate_create_dynamic_bag_params(
+            dynamic_bag_id,
+            deletion_prize,
+            &Some(params.clone()),
+        )?;
+
+        Self::pick_buckets_for_bag(dynamic_bag_id.clone(), &bag_change).map(|_| ())
+    }
+
+    fn ensure_bag_exists(bag_id: &BagId<T>) -> Result<Bag<T>, DispatchError> {
+        Self::ensure_bag_exists(bag_id)
+    }
+
+    fn get_data_objects_id(bag_id: &BagId<T>) -> BTreeSet<T::DataObjectId> {
+        DataObjectsById::<T>::iter_prefix(&bag_id)
+            .map(|x| x.0)
+            .collect()
+    }
+}
+
+impl<T: Trait> Module<T> {
+    // dynamic bag creation logic
+    fn create_dynamic_bag_inner(
+        dynamic_bag_id: &DynamicBagId<T>,
+        deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
+        storage_buckets: &BTreeSet<T::StorageBucketId>,
+        distribution_buckets: &BTreeSet<DistributionBucketId<T>>,
+    ) -> DispatchResult {
+        //
+        // = MUTATION SAFE =
+        //
 
         if let Some(deletion_prize) = deletion_prize.clone() {
             <StorageTreasury<T>>::deposit(&deletion_prize.account_id, deletion_prize.prize)?;
         }
 
-        let bag_type: DynamicBagType = dynamic_bag_id.clone().into();
-
-        let storage_buckets = Self::pick_storage_buckets_for_dynamic_bag(bag_type);
-        let distribution_buckets = Self::pick_distribution_buckets_for_dynamic_bag(bag_type);
-
         let bag = Bag::<T> {
             stored_by: storage_buckets.clone(),
             deletion_prize: deletion_prize.clone().map(|dp| dp.prize),
@@ -2695,34 +2770,60 @@ impl<T: Trait> DataObjectStorage<T> for Module<T> {
         <Bags<T>>::insert(&bag_id, bag);
 
         Self::deposit_event(RawEvent::DynamicBagCreated(
-            dynamic_bag_id,
-            deletion_prize,
-            storage_buckets,
-            distribution_buckets,
+            dynamic_bag_id.clone(),
+            deletion_prize.clone(),
+            storage_buckets.clone(),
+            distribution_buckets.clone(),
         ));
 
         Ok(())
     }
 
-    fn can_create_dynamic_bag(
-        bag_id: &DynamicBagId<T>,
-        deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
+    fn upload_data_objects_inner(
+        params: &UploadParameters<T>,
+        bag_change: &BagUpdate<BalanceOf<T>>,
+        bag: &Bag<T>,
     ) -> DispatchResult {
-        Self::validate_create_dynamic_bag_params(bag_id, deletion_prize)
-    }
+        let data = Self::create_data_objects(params.object_creation_list.clone());
 
-    fn ensure_bag_exists(bag_id: &BagId<T>) -> Result<Bag<T>, DispatchError> {
-        Self::ensure_bag_exists(bag_id)
-    }
+        //
+        // == MUTATION SAFE ==
+        //
 
-    fn get_data_objects_id(bag_id: &BagId<T>) -> BTreeSet<T::DataObjectId> {
-        DataObjectsById::<T>::iter_prefix(&bag_id)
-            .map(|x| x.0)
-            .collect()
+        <StorageTreasury<T>>::deposit(
+            &params.deletion_prize_source_account_id,
+            bag_change.total_deletion_prize,
+        )?;
+
+        Self::slash_data_size_fee(
+            &params.deletion_prize_source_account_id,
+            bag_change.voucher_update.objects_total_size,
+        );
+
+        // Save next object id.
+        <NextDataObjectId<T>>::put(data.next_data_object_id);
+
+        // Insert new objects.
+        for (data_object_id, data_object) in data.data_objects_map.iter() {
+            DataObjectsById::<T>::insert(&params.bag_id, &data_object_id, data_object);
+        }
+
+        Self::change_storage_bucket_vouchers_for_bag(
+            &params.bag_id,
+            &bag,
+            &bag_change.voucher_update,
+            OperationType::Increase,
+        );
+
+        Self::deposit_event(RawEvent::DataObjectsUploaded(
+            data.data_objects_map.keys().cloned().collect(),
+            params.clone(),
+            T::DataObjectDeletionPrize::get(),
+        ));
+
+        Ok(())
     }
-}
 
-impl<T: Trait> Module<T> {
     // Increment distribution family number in the storage.
     fn increment_distribution_family_number() {
         DistributionBucketFamilyNumber::put(Self::distribution_bucket_family_number() + 1);
@@ -2739,43 +2840,95 @@ impl<T: Trait> Module<T> {
     fn validate_create_dynamic_bag_params(
         dynamic_bag_id: &DynamicBagId<T>,
         deletion_prize: &Option<DynamicBagDeletionPrize<T>>,
-    ) -> DispatchResult {
+        upload_params: &Option<UploadParameters<T>>,
+    ) -> Result<Option<BagUpdate<BalanceOf<T>>>, DispatchError> {
         let bag_id: BagId<T> = dynamic_bag_id.clone().into();
-
         ensure!(
             !<Bags<T>>::contains_key(bag_id),
             Error::<T>::DynamicBagExists
         );
 
-        if let Some(deletion_prize) = deletion_prize {
-            ensure!(
-                Balances::<T>::usable_balance(&deletion_prize.account_id) >= deletion_prize.prize,
-                Error::<T>::InsufficientBalance
-            );
-        }
+        // call can upload data explicitly
+        let bag_change = upload_params
+            .as_ref()
+            .map(|params| {
+                // ensure coherent account ids for prize
+                if let Some(deletion_prize) = deletion_prize {
+                    ensure!(
+                        params.deletion_prize_source_account_id == deletion_prize.account_id,
+                        Error::<T>::AccountsNotCoherent,
+                    );
+                }
+                Self::validate_bag_change(params)
+            })
+            .transpose()?;
+
+        // check that fees are sufficient
+        let total_upload_fee = deletion_prize
+            .as_ref()
+            .map_or(Zero::zero(), |del_prize| del_prize.prize)
+            .saturating_add(bag_change.as_ref().map_or(Zero::zero(), |bag_change| {
+                Self::compute_upload_fees(bag_change)
+            }));
+
+        Self::ensure_sufficient_balance_for_upload(
+            deletion_prize
+                .as_ref()
+                .map(|deletion_prize| deletion_prize.account_id.clone()),
+            total_upload_fee,
+        )?;
 
+        Ok(bag_change)
+    }
+
+    fn ensure_sufficient_balance_for_upload(
+        deletion_prize_source_account_id: Option<T::AccountId>,
+        required_balance: BalanceOf<T>,
+    ) -> DispatchResult {
+        let usable_balance = deletion_prize_source_account_id.map_or(Zero::zero(), |account_id| {
+            Balances::<T>::usable_balance(account_id)
+        });
+
+        ensure!(
+            usable_balance >= required_balance,
+            Error::<T>::InsufficientBalance
+        );
         Ok(())
     }
 
     // Validates dynamic bag deletion params and conditions. Returns bag's deletion prize.
     fn validate_delete_dynamic_bag_params(
         dynamic_bag_id: &DynamicBagId<T>,
+        with_objects: bool,
     ) -> Result<Option<BalanceOf<T>>, DispatchError> {
         Self::ensure_dynamic_bag_exists(dynamic_bag_id)?;
 
         let dynamic_bag = Self::dynamic_bag(dynamic_bag_id);
 
-        ensure!(
-            dynamic_bag.objects_number == 0,
-            Error::<T>::CannotDeleteNonEmptyDynamicBag
-        );
-
-        if let Some(deletion_prize) = dynamic_bag.deletion_prize {
+        // deletion prize = bag.deletion_prize + total_objects fees if any
+        let deletion_prize = if !with_objects {
             ensure!(
-                <StorageTreasury<T>>::usable_balance() >= deletion_prize,
-                Error::<T>::InsufficientTreasuryBalance
+                dynamic_bag.objects_number == 0,
+                Error::<T>::CannotDeleteNonEmptyDynamicBag
             );
-        }
+            dynamic_bag.deletion_prize.unwrap_or_else(Zero::zero)
+        } else {
+            let bag_id: BagId<T> = dynamic_bag_id.clone().into();
+            let objects_del_prize = <DataObjectsById<T>>::iter_prefix(bag_id)
+                .fold(BalanceOf::<T>::zero(), |acc, (_, data_object)| {
+                    acc.saturating_add(data_object.deletion_prize)
+                });
+
+            dynamic_bag
+                .deletion_prize
+                .unwrap_or_else(Zero::zero)
+                .saturating_add(objects_del_prize)
+        };
+
+        ensure!(
+            <StorageTreasury<T>>::usable_balance() >= deletion_prize,
+            Error::<T>::InsufficientTreasuryBalance
+        );
 
         Ok(dynamic_bag.deletion_prize)
     }
@@ -3155,6 +3308,37 @@ impl<T: Trait> Module<T> {
         }
     }
 
+    // Validates upload parameters and conditions (like global uploading block).
+    // Returns voucher update parameters for the storage buckets.
+    fn validate_upload_data_objects_parameters(
+        params: &UploadParameters<T>,
+    ) -> Result<BagUpdate<BalanceOf<T>>, DispatchError> {
+        let bag_change = Self::validate_bag_change(params)?;
+        Self::ensure_sufficient_balance_for_upload(
+            Some(params.deletion_prize_source_account_id.clone()),
+            Self::compute_upload_fees(&bag_change),
+        )?;
+        Self::ensure_upload_bag_validity(&params.bag_id, &bag_change.voucher_update)?;
+        Ok(bag_change)
+    }
+
+    // construct bag change after validating the inputs
+    fn validate_bag_change(
+        params: &UploadParameters<T>,
+    ) -> Result<BagUpdate<BalanceOf<T>>, DispatchError> {
+        Self::check_global_uploading_block()?;
+
+        Self::ensure_objects_creation_list_validity(&params.object_creation_list)?;
+
+        let bag_change = Self::construct_bag_change(&params.object_creation_list)?;
+
+        ensure!(
+            params.expected_data_size_fee == Self::data_object_per_mega_byte_fee(),
+            Error::<T>::DataSizeFeeChanged
+        );
+        Ok(bag_change)
+    }
+
     // Validates `delete_data_objects` parameters.
     // Returns voucher update for an affected bag.
     fn validate_delete_data_objects_params(
@@ -3191,39 +3375,33 @@ impl<T: Trait> Module<T> {
         Ok(bag_change)
     }
 
-    // Validates upload parameters and conditions (like global uploading block).
-    // Returns voucher update parameters for the storage buckets.
-    fn validate_upload_data_objects_parameters(
-        params: &UploadParameters<T>,
-    ) -> Result<BagUpdate<BalanceOf<T>>, DispatchError> {
-        // Check global uploading block.
-        ensure!(!Self::uploading_blocked(), Error::<T>::UploadingBlocked);
-
-        // Check object creation list validity.
-        ensure!(
-            !params.object_creation_list.is_empty(),
-            Error::<T>::NoObjectsOnUpload
-        );
+    fn ensure_upload_bag_validity(
+        bag_id: &BagId<T>,
+        voucher_update: &VoucherUpdate,
+    ) -> DispatchResult {
+        let bag = Self::ensure_bag_exists(bag_id)?;
+        // Check buckets.
+        Self::check_bag_for_buckets_overflow(&bag, voucher_update)?;
+        Ok(())
+    }
 
-        // Check data objects' max size.
-        ensure!(
-            params
-                .object_creation_list
-                .iter()
-                .all(|obj| obj.size <= T::MaxDataObjectSize::get()),
-            Error::<T>::MaxDataObjectSizeExceeded
-        );
+    fn compute_upload_fees(bag_change: &BagUpdate<BalanceOf<T>>) -> BalanceOf<T> {
+        let size_fee =
+            Self::calculate_data_storage_fee(bag_change.voucher_update.objects_total_size);
 
-        let bag = Self::ensure_bag_exists(&params.bag_id)?;
+        bag_change.total_deletion_prize.saturating_add(size_fee)
+    }
 
-        // Check data size fee change.
-        ensure!(
-            params.expected_data_size_fee == Self::data_object_per_mega_byte_fee(),
-            Error::<T>::DataSizeFeeChanged
-        );
+    // Check global uploading block.
+    fn check_global_uploading_block() -> DispatchResult {
+        ensure!(!Self::uploading_blocked(), Error::<T>::UploadingBlocked);
+        Ok(())
+    }
 
-        let bag_change = params
-            .object_creation_list
+    fn construct_bag_change(
+        object_creation_list: &[DataObjectCreationParameters],
+    ) -> Result<BagUpdate<BalanceOf<T>>, DispatchError> {
+        let bag_change = object_creation_list
             .iter()
             .try_fold::<_, _, Result<_, DispatchError>>(
                 BagUpdate::default(),
@@ -3250,19 +3428,27 @@ impl<T: Trait> Module<T> {
                 },
             )?;
 
-        let size_fee =
-            Self::calculate_data_storage_fee(bag_change.voucher_update.objects_total_size);
-        let usable_balance =
-            Balances::<T>::usable_balance(&params.deletion_prize_source_account_id);
-
-        // Check account balance to satisfy deletion prize and storage fee.
-        let total_fee = bag_change.total_deletion_prize + size_fee;
-        ensure!(usable_balance >= total_fee, Error::<T>::InsufficientBalance);
+        Ok(bag_change)
+    }
 
-        // Check buckets.
-        Self::check_bag_for_buckets_overflow(&bag, &bag_change.voucher_update)?;
+    // objects creation list validity
+    fn ensure_objects_creation_list_validity(
+        object_creation_list: &[DataObjectCreationParameters],
+    ) -> DispatchResult {
+        // Check object creation list is not empty
+        ensure!(
+            !object_creation_list.is_empty(),
+            Error::<T>::NoObjectsOnUpload
+        );
 
-        Ok(bag_change)
+        // Check data objects' max size.
+        ensure!(
+            object_creation_list
+                .iter()
+                .all(|obj| obj.size <= T::MaxDataObjectSize::get()),
+            Error::<T>::MaxDataObjectSizeExceeded
+        );
+        Ok(())
     }
 
     // Iterates through buckets in the bag. Verifies voucher parameters to fit the new limits:
@@ -3326,11 +3512,36 @@ impl<T: Trait> Module<T> {
         }
     }
 
+    // helper pick buckets for bag
+    fn pick_buckets_for_bag(
+        dynamic_bag_id: DynamicBagId<T>,
+        bag_change: &Option<BagUpdate<BalanceOf<T>>>,
+    ) -> Result<BucketPair<T>, DispatchError> {
+        let bag_type: DynamicBagType = dynamic_bag_id.into();
+
+        let storage_bucket_ids = Self::pick_storage_buckets_for_dynamic_bag(
+            bag_type,
+            bag_change.map(|bag_change| bag_change.voucher_update),
+        );
+
+        let distribution_bucket_ids = Self::pick_distribution_buckets_for_dynamic_bag(bag_type);
+
+        if bag_change.is_some() {
+            ensure!(
+                !storage_bucket_ids.is_empty(),
+                Error::<T>::StorageBucketIdCollectionsAreEmpty
+            );
+        }
+
+        Ok((storage_bucket_ids, distribution_bucket_ids))
+    }
+
     // Selects storage bucket ID sets to assign to the dynamic bag.
     pub(crate) fn pick_storage_buckets_for_dynamic_bag(
         bag_type: DynamicBagType,
+        voucher_update: Option<VoucherUpdate>,
     ) -> BTreeSet<T::StorageBucketId> {
-        StorageBucketPicker::<T>::pick_storage_buckets(bag_type)
+        StorageBucketPicker::<T>::pick_storage_buckets(bag_type, voucher_update)
     }
 
     // Selects distributed bucket ID sets to assign to the dynamic bag.

+ 32 - 7
runtime-modules/storage/src/random_buckets/storage_bucket_picker.rs

@@ -5,7 +5,7 @@ use sp_std::collections::btree_set::BTreeSet;
 use sp_std::marker::PhantomData;
 
 pub(crate) use super::{RandomBucketIdIterator, SequentialBucketIdIterator};
-use crate::{DynamicBagType, Module, Trait};
+use crate::{DynamicBagType, Module, Trait, VoucherUpdate};
 
 // Generates storage bucket IDs to assign to a new dynamic bag.
 pub(crate) struct StorageBucketPicker<T> {
@@ -19,7 +19,10 @@ impl<T: Trait> StorageBucketPicker<T> {
     // The function filters deleted buckets and disabled buckets (accepting_new_bags == false)
     // Total number of possible IDs is limited by the dynamic bag settings.
     // Returns an accumulated bucket ID set or an empty set.
-    pub(crate) fn pick_storage_buckets(bag_type: DynamicBagType) -> BTreeSet<T::StorageBucketId> {
+    pub(crate) fn pick_storage_buckets(
+        bag_type: DynamicBagType,
+        voucher_update: Option<VoucherUpdate>,
+    ) -> BTreeSet<T::StorageBucketId> {
         let creation_policy = Module::<T>::get_dynamic_bag_creation_policy(bag_type);
 
         if creation_policy.no_storage_buckets_required() {
@@ -28,6 +31,7 @@ impl<T: Trait> StorageBucketPicker<T> {
 
         let required_bucket_num = creation_policy.number_of_storage_buckets as usize;
 
+        // TODO: the selection algorithm will change: https://github.com/Joystream/joystream/issues/2904
         // Storage bucket IDs accumulator.
         let bucket_ids_cell = RefCell::new(BTreeSet::new());
         let next_storage_bucket_id = Module::<T>::next_storage_bucket_id();
@@ -35,7 +39,7 @@ impl<T: Trait> StorageBucketPicker<T> {
             .chain(SequentialBucketIdIterator::<T, T::StorageBucketId>::new(
                 next_storage_bucket_id,
             ))
-            .filter(Self::check_storage_bucket_is_valid_for_bag_assigning)
+            .filter(|id| Self::check_storage_bucket_is_valid_for_bag_assigning(id, &voucher_update))
             .filter(|bucket_id| {
                 let bucket_ids = bucket_ids_cell.borrow();
 
@@ -55,11 +59,32 @@ impl<T: Trait> StorageBucketPicker<T> {
     // Verifies storage bucket ID (non-deleted and accepting new bags).
     pub(crate) fn check_storage_bucket_is_valid_for_bag_assigning(
         bucket_id: &T::StorageBucketId,
+        voucher_update: &Option<VoucherUpdate>,
     ) -> bool {
         // Check bucket for existence (return false if not). Check `accepting_new_bags`.
-        Module::<T>::ensure_storage_bucket_exists(bucket_id)
-            .ok()
-            .map(|bucket| bucket.accepting_new_bags)
-            .unwrap_or(false)
+        let bucket = Module::<T>::ensure_storage_bucket_exists(bucket_id).ok();
+
+        // check that bucket is accepting new bags
+        let accepting_bags = bucket
+            .as_ref()
+            .map_or(false, |bucket| bucket.accepting_new_bags);
+
+        // check that buckets has enough room for objects and size
+        let limits_sufficient = bucket.as_ref().map_or(false, |bucket| {
+            voucher_update.map_or(true, |voucher_update| {
+                let num_objects_enough = bucket.voucher.objects_limit
+                    >= bucket
+                        .voucher
+                        .objects_used
+                        .saturating_add(voucher_update.objects_number);
+                let size_enough = bucket.voucher.size_limit
+                    >= bucket
+                        .voucher
+                        .size_used
+                        .saturating_add(voucher_update.objects_total_size);
+                size_enough && num_objects_enough
+            })
+        });
+        accepting_bags && limits_sufficient
     }
 }

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

@@ -805,6 +805,28 @@ impl DeleteDynamicBagFixture {
     }
 }
 
+pub struct CanDeleteDynamicBagWithObjectsFixture {
+    bag_id: DynamicBagId<Test>,
+}
+
+impl CanDeleteDynamicBagWithObjectsFixture {
+    pub fn default() -> Self {
+        Self {
+            bag_id: Default::default(),
+        }
+    }
+
+    pub fn with_bag_id(self, bag_id: DynamicBagId<Test>) -> Self {
+        Self { bag_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Storage::can_delete_dynamic_bag_with_objects(&self.bag_id.clone());
+
+        assert_eq!(actual_result, expected_result);
+    }
+}
+
 pub struct DeleteStorageBucketFixture {
     origin: RawOrigin<u64>,
     storage_bucket_id: u64,
@@ -1128,6 +1150,58 @@ impl CreateDynamicBagFixture {
     }
 }
 
+pub struct CreateDynamicBagWithObjectsFixture {
+    bag_id: DynamicBagId<Test>,
+    deletion_prize: Option<DynamicBagDeletionPrize<Test>>,
+    upload_parameters: UploadParameters<Test>,
+}
+
+impl CreateDynamicBagWithObjectsFixture {
+    pub fn default() -> Self {
+        Self {
+            bag_id: Default::default(),
+            deletion_prize: Default::default(),
+            upload_parameters: Default::default(),
+        }
+    }
+
+    pub fn with_bag_id(self, bag_id: DynamicBagId<Test>) -> Self {
+        Self { bag_id, ..self }
+    }
+
+    pub fn with_deletion_prize(
+        self,
+        deletion_prize: Option<DynamicBagDeletionPrize<Test>>,
+    ) -> Self {
+        Self {
+            deletion_prize: deletion_prize,
+            ..self
+        }
+    }
+
+    pub fn with_objects(self, upload_parameters: UploadParameters<Test>) -> Self {
+        Self {
+            upload_parameters,
+            ..self
+        }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Storage::create_dynamic_bag_with_objects_constraints(
+            self.bag_id.clone(),
+            self.deletion_prize.clone(),
+            self.upload_parameters.clone(),
+        );
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            let bag_id: BagId<Test> = self.bag_id.clone().into();
+            assert!(<crate::Bags<Test>>::contains_key(&bag_id));
+        }
+    }
+}
+
 pub struct UpdateNumberOfStorageBucketsInDynamicBagCreationPolicyFixture {
     origin: RawOrigin<u64>,
     new_storage_buckets_number: u64,

+ 408 - 14
runtime-modules/storage/src/tests/mod.rs

@@ -3052,9 +3052,13 @@ fn set_max_voucher_limits() {
     let new_size_limit = 100;
     let new_objects_limit = 1;
 
+    set_max_voucher_limits_with_params(new_size_limit, new_objects_limit);
+}
+
+fn set_max_voucher_limits_with_params(size_limit: u64, objects_limit: u64) {
     UpdateStorageBucketsVoucherMaxLimitsFixture::default()
-        .with_new_objects_size_limit(new_size_limit)
-        .with_new_objects_number_limit(new_objects_limit)
+        .with_new_objects_size_limit(size_limit)
+        .with_new_objects_number_limit(objects_limit)
         .call_and_assert(Ok(()));
 }
 
@@ -3208,20 +3212,23 @@ fn test_storage_bucket_picking_for_bag_non_random() {
 
         let initial_buckets_number = InitialStorageBucketsNumberForDynamicBag::get();
         // No buckets
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
         assert_eq!(bucket_ids, BTreeSet::new());
 
         // Less then initial buckets number
         let buckets_number = initial_buckets_number - 1;
         let created_buckets = create_storage_buckets(buckets_number);
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         assert_eq!(bucket_ids, created_buckets);
 
         // More then initial buckets number
         let buckets_number = 5;
         create_storage_buckets(buckets_number);
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         assert_eq!(
             bucket_ids,
@@ -3232,7 +3239,8 @@ fn test_storage_bucket_picking_for_bag_non_random() {
         let removed_bucket_id = 1;
         <crate::StorageBucketById<Test>>::remove(removed_bucket_id);
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         let mut expected_ids =
             BTreeSet::from_iter((0u64..(initial_buckets_number + 1)).into_iter());
@@ -3246,7 +3254,8 @@ fn test_storage_bucket_picking_for_bag_non_random() {
             bucket.accepting_new_bags = false;
         });
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         let mut expected_ids =
             BTreeSet::from_iter((0u64..(initial_buckets_number + 2)).into_iter());
@@ -3261,7 +3270,8 @@ fn test_storage_bucket_picking_for_bag_non_random() {
             DynamicBagCreationPolicy::default(),
         );
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
         assert_eq!(bucket_ids, BTreeSet::new());
     });
 }
@@ -3275,20 +3285,23 @@ fn test_storage_bucket_picking_for_bag_with_randomness() {
 
         let initial_buckets_number = InitialStorageBucketsNumberForDynamicBag::get();
         // No buckets
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
         assert_eq!(bucket_ids, BTreeSet::new());
 
         // Less then initial buckets number
         let buckets_number = initial_buckets_number - 1;
         let created_buckets = create_storage_buckets(buckets_number);
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         assert_eq!(bucket_ids, created_buckets);
 
         // More then initial buckets number
         let buckets_number = 5;
         create_storage_buckets(buckets_number);
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         let sequential_random_ids = BTreeSet::from_iter((0u64..initial_buckets_number).into_iter());
 
@@ -3305,7 +3318,8 @@ fn test_storage_bucket_picking_for_bag_with_randomness() {
         let removed_bucket_id = bucket_ids.iter().next().unwrap();
         <crate::StorageBucketById<Test>>::remove(removed_bucket_id);
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
         // Check number of generated IDs
         assert_eq!(initial_buckets_number, bucket_ids.len() as u64);
         // Check that IDs are within possible range.
@@ -3321,7 +3335,8 @@ fn test_storage_bucket_picking_for_bag_with_randomness() {
             bucket.accepting_new_bags = false;
         });
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
 
         let mut expected_ids =
             BTreeSet::from_iter((0u64..(initial_buckets_number + 2)).into_iter());
@@ -3343,7 +3358,8 @@ fn test_storage_bucket_picking_for_bag_with_randomness() {
             DynamicBagCreationPolicy::default(),
         );
 
-        let bucket_ids = Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member);
+        let bucket_ids =
+            Storage::pick_storage_buckets_for_dynamic_bag(DynamicBagType::Member, None);
         assert_eq!(bucket_ids, BTreeSet::new());
     });
 }
@@ -3390,6 +3406,14 @@ fn create_storage_buckets(buckets_number: u64) -> BTreeSet<u64> {
     let objects_limit = 1;
     let size_limit = 100;
 
+    create_storage_buckets_with_limits(buckets_number, size_limit, objects_limit)
+}
+
+fn create_storage_buckets_with_limits(
+    buckets_number: u64,
+    size_limit: u64,
+    objects_limit: u64,
+) -> BTreeSet<u64> {
     let mut bucket_ids = BTreeSet::new();
 
     for _ in 0..buckets_number {
@@ -5042,3 +5066,373 @@ fn set_distribution_bucket_family_metadata_fails_with_invalid_distribution_bucke
             ));
     });
 }
+
+#[test]
+fn create_dynamic_bag_with_objects_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        create_storage_buckets(10);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 10000;
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: create_single_data_object(),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters)
+            .call_and_assert(Ok(()));
+
+        let bag = Storage::dynamic_bag(&dynamic_bag_id);
+
+        // Check that IDs are within possible range.
+        assert!(bag
+            .stored_by
+            .iter()
+            .all(|id| { *id < Storage::next_storage_bucket_id() }));
+
+        let creation_policy =
+            Storage::get_dynamic_bag_creation_policy(dynamic_bag_id.clone().into());
+        assert_eq!(
+            bag.stored_by.len(),
+            creation_policy.number_of_storage_buckets as usize
+        );
+
+        assert_eq!(bag.deletion_prize.unwrap(), deletion_prize_value);
+
+        // post-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance - deletion_prize_value
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            deletion_prize_value
+        );
+
+        EventFixture::assert_last_crate_event(RawEvent::DynamicBagCreated(
+            dynamic_bag_id,
+            deletion_prize,
+            BTreeSet::from_iter(bag.stored_by),
+            BTreeSet::from_iter(bag.distributed_by),
+        ));
+    });
+}
+
+#[test]
+fn create_dynamic_bag_with_objects_fails_with_no_bucket_availables_with_sufficient_objects_limit() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        // set limit size 100 and limit obj number 20
+        set_max_voucher_limits_with_params(100, 20);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        // create 10 buckets each with size limit 10 and num object limit 1
+        create_storage_buckets_with_limits(10, 10, 1);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 10000;
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: create_data_object_candidates(1, 3),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        // this fails because num objects == 3 & bucket.num_objects_limit == 1
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters.clone())
+            .call_and_assert(Err(Error::<Test>::StorageBucketIdCollectionsAreEmpty.into()));
+
+        // set bucket size limits to be large enought and retry
+        let new_objects_number_limit = 10;
+        let new_objects_size_limit = 100;
+        let bucket_id_to_enlarge = 1;
+
+        SetStorageBucketVoucherLimitsFixture::default()
+            .with_origin(RawOrigin::Signed(STORAGE_WG_LEADER_ACCOUNT_ID))
+            .with_storage_bucket_id(bucket_id_to_enlarge)
+            .with_new_objects_number_limit(new_objects_number_limit)
+            .with_new_objects_size_limit(new_objects_size_limit)
+            .call_and_assert(Ok(()));
+
+        // this succeeds now
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters)
+            .call_and_assert(Ok(()));
+    })
+}
+
+#[test]
+fn create_dynamic_bag_with_objects_fails_with_no_bucket_availables_with_sufficient_size_limit() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        // set limit size 100 and limit obj number 20
+        set_max_voucher_limits_with_params(100, 20);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        // create 10 buckets each with size limit 1 and num object limit 10
+        create_storage_buckets_with_limits(10, 1, 10);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 10000;
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        // try uploading with 3 objects each exceeding bucket size limit
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: create_data_object_candidates(1, 3),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters.clone())
+            .call_and_assert(Err(Error::<Test>::StorageBucketIdCollectionsAreEmpty.into()));
+
+        // set bucket size limits to be large enought and retry
+        let new_objects_number_limit = 10;
+        let new_objects_size_limit = 100;
+        let bucket_id_to_enlarge = 1;
+
+        SetStorageBucketVoucherLimitsFixture::default()
+            .with_origin(RawOrigin::Signed(STORAGE_WG_LEADER_ACCOUNT_ID))
+            .with_storage_bucket_id(bucket_id_to_enlarge)
+            .with_new_objects_number_limit(new_objects_number_limit)
+            .with_new_objects_size_limit(new_objects_size_limit)
+            .call_and_assert(Ok(()));
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters)
+            .call_and_assert(Ok(()));
+    })
+}
+
+#[test]
+fn create_dynamic_bag_with_objects_fails_with_unsufficient_balance() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        // set limit size 100 and limit obj number 20
+        set_max_voucher_limits_with_params(100, 20);
+        // create 3 buckets with size limit 10 and objects limit 3
+        create_storage_buckets_with_limits(3, 10, 3);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 100; // just enough for the del prize
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        // try uploading with > 0 objects exceeding balance
+        let data_objects = create_data_object_candidates(1, 3);
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: data_objects.clone(),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters.clone())
+            .call_and_assert(Err(Error::<Test>::InsufficientBalance.into()));
+    })
+}
+
+#[test]
+fn can_delete_dynamic_bags_with_objects_succeeded() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        create_storage_buckets(10);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 10000;
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: create_single_data_object(),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters)
+            .call_and_assert(Ok(()));
+
+        CanDeleteDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .call_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn cannot_delete_dynamic_bags_with_objects_with_unsufficient_treasury_balance() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        let dynamic_bag_id = DynamicBagId::<Test>::Member(DEFAULT_MEMBER_ID);
+
+        create_storage_buckets(10);
+
+        let deletion_prize_value = 100;
+        let deletion_prize_account_id = DEFAULT_MEMBER_ACCOUNT_ID;
+        let initial_balance = 10000;
+        increase_account_balance(&deletion_prize_account_id, initial_balance);
+
+        let deletion_prize = Some(DynamicBagDeletionPrize::<Test> {
+            prize: deletion_prize_value,
+            account_id: deletion_prize_account_id,
+        });
+
+        let upload_parameters = UploadParameters::<Test> {
+            bag_id: BagId::<Test>::from(dynamic_bag_id.clone()),
+            object_creation_list: create_single_data_object(),
+            deletion_prize_source_account_id: DEFAULT_MEMBER_ACCOUNT_ID,
+            expected_data_size_fee: Storage::data_object_per_mega_byte_fee(),
+        };
+
+        // pre-check balances
+        assert_eq!(
+            Balances::usable_balance(&DEFAULT_MEMBER_ACCOUNT_ID),
+            initial_balance
+        );
+        assert_eq!(
+            Balances::usable_balance(&<StorageTreasury<Test>>::module_account_id()),
+            0
+        );
+
+        CreateDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .with_deletion_prize(deletion_prize.clone())
+            .with_objects(upload_parameters)
+            .call_and_assert(Ok(()));
+
+        // Corrupt module balance enough so that it doesn't reach sufficient balance for deletion
+        let _ = Balances::slash(
+            &<StorageTreasury<Test>>::module_account_id(),
+            deletion_prize_value,
+        );
+
+        CanDeleteDynamicBagWithObjectsFixture::default()
+            .with_bag_id(dynamic_bag_id.clone())
+            .call_and_assert(Err(Error::<Test>::InsufficientTreasuryBalance.into()));
+    });
+}