Browse Source

Merge branch 'olympia' into olympia_audit_fixes1

# Conflicts:
#	runtime-modules/forum/src/benchmarking.rs
#	runtime-modules/forum/src/lib.rs
#	runtime-modules/forum/src/mock.rs
#	runtime-modules/forum/src/tests.rs
#	runtime-modules/membership/src/tests/mock.rs
Shamil Gadelshin 4 years ago
parent
commit
773935b333
28 changed files with 1343 additions and 558 deletions
  1. 6 5
      node/src/chain_spec/forum_config.rs
  2. 1 1
      node/src/service.rs
  3. 24 20
      runtime-modules/content-directory/src/schema/property.rs
  4. 134 151
      runtime-modules/forum/src/benchmarking.rs
  5. 282 99
      runtime-modules/forum/src/lib.rs
  6. 115 33
      runtime-modules/forum/src/mock.rs
  7. 483 96
      runtime-modules/forum/src/tests.rs
  8. 4 1
      runtime-modules/membership/src/tests/mock.rs
  9. 1 1
      runtime-modules/proposals/codex/src/benchmarking.rs
  10. 10 5
      runtime-modules/proposals/codex/src/tests/mock.rs
  11. 1 1
      runtime-modules/proposals/codex/src/tests/mod.rs
  12. 3 3
      runtime-modules/proposals/engine/src/tests/mod.rs
  13. 1 1
      runtime-modules/proposals/engine/src/types/proposal_statuses.rs
  14. 4 2
      runtime-modules/service-discovery/src/mock.rs
  15. 4 2
      runtime-modules/storage/src/tests/mock.rs
  16. 10 5
      runtime-modules/utility/src/tests/mocks.rs
  17. 3 3
      runtime-modules/working-group/src/benchmarking.rs
  18. 26 1
      runtime-modules/working-group/src/checks.rs
  19. 45 3
      runtime-modules/working-group/src/lib.rs
  20. 15 8
      runtime-modules/working-group/src/tests/fixtures.rs
  21. 6 4
      runtime-modules/working-group/src/tests/hiring_workflow.rs
  22. 4 2
      runtime-modules/working-group/src/tests/mock.rs
  23. 46 10
      runtime-modules/working-group/src/tests/mod.rs
  24. 3 0
      runtime-modules/working-group/src/types.rs
  25. 16 8
      runtime/src/lib.rs
  26. 2 2
      runtime/src/tests/proposals_integration/mod.rs
  27. 7 7
      runtime/src/tests/proposals_integration/working_group_proposals.rs
  28. 87 84
      runtime/src/weights/forum.rs

+ 6 - 5
node/src/chain_spec/forum_config.rs

@@ -2,7 +2,7 @@ use codec::Decode;
 use node_runtime::{
     forum,
     forum::{Category, Post, Thread},
-    AccountId, Balance, ForumConfig, Moment, PostId, Runtime, ThreadId,
+    AccountId, Balance, BlockNumber, ForumConfig, Moment, PostId, Runtime, ThreadId,
 };
 use serde::Deserialize;
 use sp_core::H256;
@@ -12,18 +12,18 @@ type CategoryId = <Runtime as forum::Trait>::CategoryId;
 type ForumUserId = forum::ForumUserId<Runtime>;
 type ModeratorId = forum::ModeratorId<Runtime>;
 type Hash = H256;
-type PostOf = Post<ForumUserId, Hash>;
+type PostOf = Post<ForumUserId, ThreadId, H256, Balance, BlockNumber>;
 
 type ThreadOf = (
     CategoryId,
     ThreadId,
-    Thread<ForumUserId, CategoryId, Moment, Hash, PostId, PostOf, Balance>,
+    Thread<ForumUserId, CategoryId, Moment, Hash, Balance>,
 );
 
 #[derive(Decode)]
 struct ForumData {
-    categories: Vec<(CategoryId, Category<CategoryId, ThreadId, H256>)>,
-    posts: Vec<(ThreadId, PostId, Post<ForumUserId, H256>)>,
+    categories: Vec<(CategoryId, Category<CategoryId, ThreadId, Hash>)>,
+    posts: Vec<(ThreadId, PostId, PostOf)>,
     threads: Vec<ThreadOf>,
     category_by_moderator: Vec<(CategoryId, ModeratorId, ())>,
     data_migration_done: bool,
@@ -139,6 +139,7 @@ fn create(_forum_sudo: AccountId, forum_data: EncodedForumData) -> ForumConfig {
     ForumConfig {
         category_by_id: forum_data.categories,
         thread_by_id: forum_data.threads,
+        post_by_id: forum_data.posts,
         category_by_moderator: forum_data.category_by_moderator,
         next_category_id,
         next_thread_id,

+ 1 - 1
node/src/service.rs

@@ -286,7 +286,7 @@ pub fn new_full_base(
     }
 
     // Spawn authority discovery module.
-    if matches!(role, Role::Authority{..} | Role::Sentry {..}) {
+    if matches!(role, Role::Authority { .. } | Role::Sentry { .. }) {
         let (sentries, authority_discovery_role) = match role {
             sc_service::config::Role::Authority { ref sentry_nodes } => (
                 sentry_nodes.clone(),

+ 24 - 20
runtime-modules/content-directory/src/schema/property.rs

@@ -471,31 +471,35 @@ impl<ClassId: Default + BaseArithmetic + Clone + Copy> Property<ClassId> {
             (
                 InputPropertyValue::Single(single_property_value),
                 PropertyType::Single(ref single_property_type),
-            ) => matches!((single_property_value, single_property_type.deref()),
+            ) => matches!(
+                (single_property_value, single_property_type.deref()),
                 (InputValue::Bool(_), Type::Bool)
-                | (InputValue::Uint16(_), Type::Uint16)
-                | (InputValue::Uint32(_), Type::Uint32)
-                | (InputValue::Uint64(_), Type::Uint64)
-                | (InputValue::Int16(_), Type::Int16)
-                | (InputValue::Int32(_), Type::Int32)
-                | (InputValue::Int64(_), Type::Int64)
-                | (InputValue::Text(_), Type::Text(_))
-                | (InputValue::TextToHash(_), Type::Hash(_))
-                | (InputValue::Reference(_), Type::Reference(_, _))),
+                    | (InputValue::Uint16(_), Type::Uint16)
+                    | (InputValue::Uint32(_), Type::Uint32)
+                    | (InputValue::Uint64(_), Type::Uint64)
+                    | (InputValue::Int16(_), Type::Int16)
+                    | (InputValue::Int32(_), Type::Int32)
+                    | (InputValue::Int64(_), Type::Int64)
+                    | (InputValue::Text(_), Type::Text(_))
+                    | (InputValue::TextToHash(_), Type::Hash(_))
+                    | (InputValue::Reference(_), Type::Reference(_, _))
+            ),
             (
                 InputPropertyValue::Vector(vec_value),
                 PropertyType::Vector(ref vec_property_type),
-            ) => matches!((vec_value, vec_property_type.get_vec_type()),
+            ) => matches!(
+                (vec_value, vec_property_type.get_vec_type()),
                 (VecInputValue::Bool(_), Type::Bool)
-                | (VecInputValue::Uint16(_), Type::Uint16)
-                | (VecInputValue::Uint32(_), Type::Uint32)
-                | (VecInputValue::Uint64(_), Type::Uint64)
-                | (VecInputValue::Int16(_), Type::Int16)
-                | (VecInputValue::Int32(_), Type::Int32)
-                | (VecInputValue::Int64(_), Type::Int64)
-                | (VecInputValue::Text(_), Type::Text(_))
-                | (VecInputValue::TextToHash(_), Type::Hash(_))
-                | (VecInputValue::Reference(_), Type::Reference(_, _))),
+                    | (VecInputValue::Uint16(_), Type::Uint16)
+                    | (VecInputValue::Uint32(_), Type::Uint32)
+                    | (VecInputValue::Uint64(_), Type::Uint64)
+                    | (VecInputValue::Int16(_), Type::Int16)
+                    | (VecInputValue::Int32(_), Type::Int32)
+                    | (VecInputValue::Int64(_), Type::Int64)
+                    | (VecInputValue::Text(_), Type::Text(_))
+                    | (VecInputValue::TextToHash(_), Type::Hash(_))
+                    | (VecInputValue::Reference(_), Type::Reference(_, _))
+            ),
             _ => false,
         }
     }

+ 134 - 151
runtime-modules/forum/src/benchmarking.rs

@@ -2,7 +2,7 @@
 use super::*;
 use balances::Module as Balances;
 use core::convert::TryInto;
-use frame_benchmarking::{account, benchmarks, Zero};
+use frame_benchmarking::{account, benchmarks};
 use frame_support::storage::StorageMap;
 use frame_support::traits::Currency;
 use frame_system::Module as System;
@@ -49,6 +49,8 @@ pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 
 const SEED: u32 = 0;
 const MAX_BYTES: u32 = 16384;
+const MAX_POSTS: u32 = 500;
+const MAX_THREADS: u32 = 500;
 
 fn get_byte(num: u32, byte_number: u8) -> u8 {
     ((num & (0xff << (8 * byte_number))) >> 8 * byte_number) as u8
@@ -214,7 +216,7 @@ fn add_opening_helper<T: Trait + working_group::Trait<ForumWorkingGroupInstance>
         *job_opening_type,
         StakePolicy {
             stake_amount:
-                <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumStakeForOpening::get(
+                <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumApplicationStake::get(
                 ),
             leaving_unstaking_period: <T as
                 working_group::Trait<ForumWorkingGroupInstance>>::MinUnstakingPeriodLimit::get() + One::one(),
@@ -247,7 +249,7 @@ fn apply_on_opening_helper<T: Trait + working_group::Trait<ForumWorkingGroupInst
             reward_account_id: applicant_account_id.clone(),
             description: vec![],
             stake_parameters: StakeParameters {
-                stake: <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumStakeForOpening::get(),
+                stake: <T as working_group::Trait<ForumWorkingGroupInstance>>::MinimumApplicationStake::get(),
                 staking_account_id: applicant_account_id.clone()
             },
         },
@@ -317,6 +319,7 @@ fn add_thread_post<T: Trait>(
         category_id,
         thread_id,
         text,
+        true,
     )
     .unwrap();
     Module::<T>::next_post_id() - T::PostId::one()
@@ -781,10 +784,11 @@ benchmarks! {
         let new_post = Post {
             text_hash: T::calculate_hash(&text),
             author_id: forum_user_id.saturated_into(),
+            thread_id: next_thread_id,
+            last_edited: System::<T>::block_number(),
+            cleanup_pay_off: T::PostDeposit::get(),
         };
 
-        let mut posts = BTreeMap::new();
-        posts.insert(next_post_id, new_post.clone());
         // Ensure new thread created successfully
         let new_thread = Thread {
             category_id,
@@ -792,8 +796,8 @@ benchmarks! {
             author_id: forum_user_id.saturated_into(),
             archived: false,
             poll: poll.clone(),
-            cleanup_pay_off: T::ThreadDeposit::get() + T::PostDeposit::get(),
-            posts
+            cleanup_pay_off: T::ThreadDeposit::get(),
+            number_of_posts: 1,
         };
 
         assert_eq!(Module::<T>::thread_by_id(category_id, next_thread_id), new_thread);
@@ -801,10 +805,10 @@ benchmarks! {
 
 
         assert_eq!(
-            *Module::<T>::thread_by_id(category_id, next_thread_id)
-                .posts.get(&next_post_id).unwrap(),
+            Module::<T>::post_by_id(next_thread_id, next_post_id),
             new_post
         );
+
         assert_eq!(Module::<T>::next_post_id(), next_post_id + T::PostId::one());
 
         assert_last_event::<T>(
@@ -931,8 +935,9 @@ benchmarks! {
         );
     }
 
-    delete_thread_lead {
+    delete_thread {
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
+        let hide = false;
 
         let forum_user_id = 0;
         let caller_id =
@@ -968,25 +973,22 @@ benchmarks! {
         }
 
         let mut category = Module::<T>::category_by_id(category_id);
-        let max_posts_in_thread =
-            <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get();
-
-        for _ in 0..max_posts_in_thread - 1{
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
-        }
 
         let initial_balance = Balances::<T>::usable_balance(&caller_id);
-    }: delete_thread(RawOrigin::Signed(caller_id.clone()), PrivilegedActor::Lead, category_id, thread_id)
+    }: _(
+        RawOrigin::Signed(caller_id.clone()),
+        forum_user_id.saturated_into(),
+        category_id,
+        thread_id,
+        hide
+    )
     verify {
 
         // Ensure that balance is paid off
         assert_eq!(
             Balances::<T>::usable_balance(&caller_id),
             initial_balance +
-            T::ThreadDeposit::get() +
-            BalanceOf::<T>::from(
-                max_posts_in_thread.try_into().unwrap()
-            ) * T::PostDeposit::get()
+            T::ThreadDeposit::get()
         );
 
         // Ensure category num_direct_threads updated successfully.
@@ -1000,90 +1002,9 @@ benchmarks! {
         assert_last_event::<T>(
             RawEvent::ThreadDeleted(
                 thread_id,
-                PrivilegedActor::Lead,
-                category_id
-            ).into()
-        );
-    }
-
-    delete_thread_moderator {
-        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
-
-        let forum_user_id = 0;
-        let caller_id =
-            insert_a_leader::<T>(forum_user_id);
-
-        // Generate categories tree
-        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
-
-        // Create thread
-        let expiration_diff = 10.into();
-        let poll = Some(
-            generate_poll::<T>(expiration_diff, (<<<T as Trait>::MapLimits as StorageLimits>::MaxPollAlternativesNumber>::get() - 1) as u32)
-        );
-        let text = vec![1u8].repeat(MAX_BYTES as usize);
-
-        let thread_id = create_new_thread::<T>(
-            caller_id.clone(), forum_user_id.saturated_into(), category_id,
-            text.clone(), text.clone(), poll
-        );
-
-        let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
-
-        // Add poll voting.
-        for idx in 1..(T::MaxWorkerNumberLimit::get() - 1) {
-            let member_id = idx.into();
-            let member_account_id = insert_a_worker::<T>(caller_id.clone(), member_id);
-
-            Module::<T>::vote_on_poll(
-                RawOrigin::Signed(member_account_id.clone()).into(),
-                member_id.saturated_into(),
+                forum_user_id.saturated_into(),
                 category_id,
-                thread_id,
-                idx
-            ).unwrap();
-        }
-
-        // Set up category membership of moderator.
-        Module::<T>::update_category_membership_of_moderator(
-            RawOrigin::Signed(caller_id.clone()).into(), moderator_id, category_id, true
-        ).unwrap();
-
-        let mut category = Module::<T>::category_by_id(category_id);
-
-        let max_posts_in_thread =
-            <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get();
-
-        for _ in 0..max_posts_in_thread - 1 {
-            add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
-        }
-
-        let initial_balance = Balances::<T>::usable_balance(&caller_id);
-
-    }: delete_thread(RawOrigin::Signed(caller_id.clone()), PrivilegedActor::Moderator(moderator_id), category_id, thread_id)
-    verify {
-        // Ensure that balance is paid off
-        assert_eq!(
-            Balances::<T>::usable_balance(&caller_id),
-            initial_balance +
-            T::ThreadDeposit::get() +
-            BalanceOf::<T>::from(max_posts_in_thread.try_into().unwrap()) *
-            T::PostDeposit::get()
-        );
-
-        // Ensure category num_direct_threads updated successfully.
-        category.num_direct_threads-=1;
-        assert_eq!(Module::<T>::category_by_id(category_id), category);
-
-        // Ensure thread was successfully deleted
-        assert!(!<ThreadById<T>>::contains_key(category_id, thread_id));
-        assert_eq!(<PollVotes<T>>::iter_prefix_values(&thread_id).count(), 0);
-
-        assert_last_event::<T>(
-            RawEvent::ThreadDeleted(
-                thread_id,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
+                hide
             ).into()
         );
     }
@@ -1289,8 +1210,6 @@ benchmarks! {
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() as u32 - 1;
-
         let k in 0 .. MAX_BYTES;
 
         // Generate categories tree
@@ -1310,9 +1229,6 @@ benchmarks! {
 
         let mut category = Module::<T>::category_by_id(category_id);
 
-        for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
-        }
         let rationale = vec![0u8].repeat(k as usize);
 
     }: moderate_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id, rationale.clone())
@@ -1321,7 +1237,7 @@ benchmarks! {
         let thread_account_id = T::ModuleId::get().into_sub_account(thread_id);
         assert_eq!(
            Balances::<T>::free_balance(&thread_account_id),
-           BalanceOf::<T>::zero()
+           T::PostDeposit::get()
         );
 
         // Ensure category num_direct_threads updated successfully.
@@ -1349,7 +1265,6 @@ benchmarks! {
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() as u32 - 1;
 
         let k in 0 .. MAX_BYTES;
 
@@ -1377,9 +1292,6 @@ benchmarks! {
 
         let mut category = Module::<T>::category_by_id(category_id);
 
-        for _ in 0..j {
-            add_thread_post::<T>(caller_id.clone(), (lead_id as u64).saturated_into(), category_id, thread_id, text.clone());
-        }
         let rationale = vec![0u8].repeat(k as usize);
 
     }: moderate_thread(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id, rationale.clone())
@@ -1388,7 +1300,7 @@ benchmarks! {
         let thread_account_id = T::ModuleId::get().into_sub_account(thread_id);
         assert_eq!(
            Balances::<T>::free_balance(&thread_account_id),
-           BalanceOf::<T>::zero()
+           T::PostDeposit::get()
         );
 
 
@@ -1419,9 +1331,6 @@ benchmarks! {
 
         let j in 0 .. MAX_BYTES;
 
-        let k in 0 ..
-            (<<T as Trait>::MapLimits as StorageLimits>::MaxPostsInThread::get() - 2) as u32;
-
         let text = vec![0u8].repeat(j as usize);
 
         // Generate categories tree
@@ -1433,21 +1342,11 @@ benchmarks! {
             vec![0u8].repeat(MAX_BYTES as usize), vec![0u8].repeat(MAX_BYTES as usize), None
         );
 
-        for _ in 0 .. k {
-            add_thread_post::<T>(
-                caller_id.clone(),
-                forum_user_id.saturated_into(),
-                category_id,
-                thread_id,
-                vec![0u8],
-            );
-        }
-
-        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
+        let thread = Module::<T>::thread_by_id(category_id, thread_id);
         let post_id = Module::<T>::next_post_id();
 
         let initial_balance = Balances::<T>::usable_balance(&caller_id);
-    }: _ (RawOrigin::Signed(caller_id.clone()), forum_user_id.saturated_into(), category_id, thread_id, text.clone())
+    }: _ (RawOrigin::Signed(caller_id.clone()), forum_user_id.saturated_into(), category_id, thread_id, text.clone(), true)
     verify {
         assert_eq!(
             Balances::<T>::usable_balance(&caller_id),
@@ -1458,12 +1357,12 @@ benchmarks! {
         let new_post = Post {
             text_hash: T::calculate_hash(&text),
             author_id: forum_user_id.saturated_into(),
+            thread_id: thread_id,
+            last_edited: System::<T>::block_number(),
+            cleanup_pay_off: T::PostDeposit::get(),
         };
 
-        thread.posts.insert(post_id, new_post);
-        thread.cleanup_pay_off += T::PostDeposit::get();
-
-        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
+        assert_eq!(Module::<T>::post_by_id(thread_id, post_id), new_post);
 
         assert_eq!(Module::<T>::next_post_id(), post_id + T::PostId::one());
 
@@ -1473,7 +1372,8 @@ benchmarks! {
                 forum_user_id.saturated_into(),
                 category_id,
                 thread_id,
-                text
+                text,
+                true,
             ).into()
         );
     }
@@ -1544,8 +1444,7 @@ benchmarks! {
 
         let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
 
-        let mut post = Module::<T>::thread_by_id(category_id, thread_id)
-            .posts.get(&post_id).unwrap().clone();
+        let mut post = Module::<T>::post_by_id(thread_id, post_id);
 
         let new_text = vec![0u8].repeat(j as usize);
 
@@ -1554,9 +1453,10 @@ benchmarks! {
 
         // Ensure post text updated successfully.
         post.text_hash = T::calculate_hash(&new_text);
+        post.last_edited = System::<T>::block_number();
 
         assert_eq!(
-            *Module::<T>::thread_by_id(category_id, thread_id).posts.get(&post_id).unwrap(),
+            Module::<T>::post_by_id(thread_id, post_id),
             post
         );
 
@@ -1603,9 +1503,11 @@ benchmarks! {
 
     }: moderate_post(RawOrigin::Signed(caller_id), PrivilegedActor::Lead, category_id, thread_id, post_id, rationale.clone())
     verify {
-        thread.posts.remove(&post_id);
+        thread.number_of_posts -= 1;
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
 
+        assert!(!<PostById<T>>::contains_key(thread_id, post_id));
+
         assert_last_event::<T>(
             RawEvent::PostModerated(
                 post_id,
@@ -1655,9 +1557,11 @@ benchmarks! {
 
     }: moderate_post(RawOrigin::Signed(caller_id), PrivilegedActor::Moderator(moderator_id), category_id, thread_id, post_id, rationale.clone())
     verify {
-        thread.posts.remove(&post_id);
+        thread.number_of_posts -= 1;
         assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
 
+        assert!(!<PostById<T>>::contains_key(thread_id, post_id));
+
         assert_last_event::<T>(
             RawEvent::PostModerated(
                 post_id,
@@ -1669,6 +1573,85 @@ benchmarks! {
         );
     }
 
+    delete_posts {
+        let forum_user_id = 0;
+        let caller_id =
+            insert_a_leader::<T>(forum_user_id);
+
+        let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
+
+        let j in 0 .. MAX_BYTES;
+
+        let k in 1 .. MAX_POSTS;
+
+        // Generate categories tree
+        let (category_id, _) = generate_categories_tree::<T>(caller_id.clone(), i, None);
+
+        // Create thread
+        let expiration_diff = 10.into();
+        let poll = Some(
+            generate_poll::<T>(expiration_diff, (<<<T as Trait>::MapLimits as StorageLimits>::MaxPollAlternativesNumber>::get() - 1) as u32)
+        );
+        let text = vec![1u8].repeat(MAX_BYTES as usize);
+
+        let thread_id = create_new_thread::<T>(
+            caller_id.clone(), forum_user_id.saturated_into(), category_id,
+            text.clone(), text.clone(), poll
+        );
+        let hide = false;
+        let mut posts = Vec::new();
+        for _ in 0 .. k {
+            posts.push((
+                    category_id,
+                    thread_id,
+                    add_thread_post::<T>(
+                        caller_id.clone(),
+                        forum_user_id.saturated_into(),
+                        category_id,
+                        thread_id,
+                        vec![0u8],
+                    ),
+                    hide
+                )
+            );
+        }
+
+        let post_id = add_thread_post::<T>(caller_id.clone(), forum_user_id.saturated_into(), category_id, thread_id, text.clone());
+
+        let mut thread = Module::<T>::thread_by_id(category_id, thread_id);
+
+        let moderator_id = ModeratorId::<T>::from(forum_user_id.try_into().unwrap());
+
+        // Set up category membership of moderator.
+        Module::<T>::update_category_membership_of_moderator(
+            RawOrigin::Signed(caller_id.clone()).into(), moderator_id, category_id, true
+        ).unwrap();
+
+        let rationale = vec![0u8].repeat(j as usize);
+
+    }: _(
+        RawOrigin::Signed(caller_id),
+        forum_user_id.saturated_into(),
+        posts.clone(),
+        rationale.clone()
+    )
+    verify {
+        thread.number_of_posts -= k as u64;
+        assert_eq!(Module::<T>::thread_by_id(category_id, thread_id), thread);
+
+        for post in posts.clone() {
+            assert!(!<PostById<T>>::contains_key(post.1, post.2));
+        }
+
+        assert_last_event::<T>(
+            RawEvent::PostDeleted(
+                rationale,
+                forum_user_id.saturated_into(),
+                posts,
+            ).into()
+        );
+    }
+
     set_stickied_threads_lead {
         let forum_user_id = 0;
         let caller_id =
@@ -1676,7 +1659,7 @@ benchmarks! {
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxThreadsInCategory>::get() as u32;
+        let j in 0 .. MAX_THREADS;
 
         // Generate categories tree
         let (category_id, parent_category_id) = generate_categories_tree::<T>(caller_id.clone(), i, None);
@@ -1719,7 +1702,7 @@ benchmarks! {
 
         let i in 1 .. (T::MaxCategoryDepth::get() + 1) as u32;
 
-        let j in 0 .. <<<T as Trait>::MapLimits as StorageLimits>::MaxThreadsInCategory>::get() as u32;
+        let j in 0 .. MAX_THREADS;
 
         // Generate categories tree
         let (category_id, parent_category_id) = generate_categories_tree::<T>(caller_id.clone(), i, None);
@@ -1851,16 +1834,9 @@ mod tests {
     }
 
     #[test]
-    fn test_delete_thread_lead() {
+    fn test_delete_thread() {
         with_test_externalities(|| {
-            assert_ok!(test_benchmark_delete_thread_lead::<Runtime>());
-        });
-    }
-
-    #[test]
-    fn test_delete_thread_moderator() {
-        with_test_externalities(|| {
-            assert_ok!(test_benchmark_delete_thread_moderator::<Runtime>());
+            assert_ok!(test_benchmark_delete_thread::<Runtime>());
         });
     }
 
@@ -1947,4 +1923,11 @@ mod tests {
             assert_ok!(test_benchmark_set_stickied_threads_lead::<Runtime>());
         });
     }
+
+    #[test]
+    fn test_delete_posts() {
+        with_test_externalities(|| {
+            assert_ok!(test_benchmark_delete_posts::<Runtime>());
+        });
+    }
 }

+ 282 - 99
runtime-modules/forum/src/lib.rs

@@ -15,9 +15,8 @@ use frame_support::{
 use frame_system::ensure_signed;
 use sp_arithmetic::traits::{BaseArithmetic, One};
 pub use sp_io::storage::clear_prefix;
-use sp_runtime::traits::{AccountIdConversion, MaybeSerialize, Member, Saturating};
+use sp_runtime::traits::{AccountIdConversion, MaybeSerialize, Member};
 use sp_runtime::{ModuleId, SaturatedConversion};
-use sp_std::collections::btree_map::BTreeMap;
 use sp_std::collections::btree_set::BTreeSet;
 use sp_std::fmt::Debug;
 use sp_std::prelude::*;
@@ -30,6 +29,9 @@ mod tests;
 
 mod benchmarking;
 
+/// Type for keeping track of number of posts in a thread
+pub type NumberOfPosts = u64;
+
 /// Moderator ID alias for the actor of the system.
 pub type ModeratorId<T> = common::ActorId<T>;
 
@@ -46,8 +48,6 @@ pub type ThreadOf<T> = Thread<
     <T as Trait>::CategoryId,
     <T as pallet_timestamp::Trait>::Moment,
     <T as frame_system::Trait>::Hash,
-    <T as Trait>::PostId,
-    Post<ForumUserId<T>, <T as frame_system::Trait>::Hash>,
     BalanceOf<T>,
 >;
 
@@ -63,22 +63,22 @@ pub trait WeightInfo {
     fn update_category_archival_status_moderator(i: u32) -> Weight;
     fn delete_category_lead(i: u32) -> Weight;
     fn delete_category_moderator(i: u32) -> Weight;
-    fn create_thread(i: u32, j: u32, k: u32, z: u32) -> Weight;
+    fn create_thread(j: u32, k: u32, i: u32) -> Weight;
     fn edit_thread_title(i: u32, j: u32) -> Weight;
     fn update_thread_archival_status_lead(i: u32) -> Weight;
     fn update_thread_archival_status_moderator(i: u32) -> Weight;
-    fn delete_thread_lead(i: u32) -> Weight;
-    fn delete_thread_moderator(i: u32) -> Weight;
+    fn delete_thread(i: u32) -> Weight;
     fn move_thread_to_category_lead(i: u32) -> Weight;
     fn move_thread_to_category_moderator(i: u32) -> Weight;
     fn vote_on_poll(i: u32, j: u32) -> Weight;
-    fn moderate_thread_lead(i: u32, j: u32, k: u32) -> Weight;
+    fn moderate_thread_lead(i: u32, k: u32) -> Weight;
     fn moderate_thread_moderator(i: u32, j: u32, k: u32) -> Weight;
     fn add_post(i: u32, j: u32) -> Weight;
     fn react_post(i: u32) -> Weight;
     fn edit_post_text(i: u32, j: u32) -> Weight;
     fn moderate_post_lead(i: u32, j: u32) -> Weight;
     fn moderate_post_moderator(i: u32, j: u32) -> Weight;
+    fn delete_posts(i: u32, j: u32, k: u32) -> Weight;
     fn set_stickied_threads_lead(i: u32, j: u32) -> Weight;
     fn set_stickied_threads_moderator(i: u32, j: u32) -> Weight;
 }
@@ -142,6 +142,9 @@ pub trait Trait:
     /// Maximum depth for nested categories
     type MaxCategoryDepth: Get<u64>;
 
+    /// Maximum number of blocks before a post can be erased by anyone
+    type PostLifeTime: Get<Self::BlockNumber>;
+
     /// Type defining the limits for different Storage items in the forum pallet
     type MapLimits: StorageLimits;
 
@@ -171,12 +174,6 @@ pub trait StorageLimits {
     /// Maximum direct subcategories in a category
     type MaxSubcategories: Get<u64>;
 
-    /// Maximum direct threads in a category
-    type MaxThreadsInCategory: Get<u64>;
-
-    /// Maximum posts in a thread
-    type MaxPostsInThread: Get<u64>;
-
     /// Maximum moderator count for a single category
     type MaxModeratorsForCategory: Get<u64>;
 
@@ -215,18 +212,27 @@ pub struct Poll<Timestamp, Hash> {
 /// Represents a thread post
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Post<ForumUserId, Hash> {
+pub struct Post<ForumUserId, ThreadId, Hash, Balance, BlockNumber> {
+    /// Id of thread to which this post corresponds.
+    pub thread_id: ThreadId,
+
     /// Hash of current text
     pub text_hash: Hash,
 
     /// Author of post.
     pub author_id: ForumUserId,
+
+    /// Cleanup pay off
+    pub cleanup_pay_off: Balance,
+
+    /// When it was created or last edited
+    pub last_edited: BlockNumber,
 }
 
 /// Represents a thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Debug, Eq)]
-pub struct Thread<ForumUserId, CategoryId, Moment, Hash, PostId: sp_std::cmp::Ord, Post, Balance> {
+pub struct Thread<ForumUserId, CategoryId, Moment, Hash, Balance> {
     /// Title hash
     pub title_hash: Hash,
 
@@ -242,11 +248,11 @@ pub struct Thread<ForumUserId, CategoryId, Moment, Hash, PostId: sp_std::cmp::Or
     /// poll description.
     pub poll: Option<Poll<Moment, Hash>>,
 
-    /// Post in thread
-    pub posts: BTreeMap<PostId, Post>,
-
     /// Pay off by deleting
     pub cleanup_pay_off: Balance,
+
+    /// Number of posts in the thread
+    pub number_of_posts: NumberOfPosts,
 }
 
 /// Represents a category
@@ -450,6 +456,17 @@ decl_storage! {
         PollVotes get(fn poll_votes_by_thread_id_by_forum_user_id): double_map
             hasher(blake2_128_concat) T::ThreadId,
             hasher(blake2_128_concat) ForumUserId<T> => bool;
+
+        /// Map post identifier to corresponding post.
+        pub PostById get(fn post_by_id) config(): double_map hasher(blake2_128_concat) T::ThreadId,
+            hasher(blake2_128_concat) T::PostId =>
+                                                Post<
+                                                    ForumUserId<T>,
+                                                    T::ThreadId,
+                                                    T::Hash,
+                                                    BalanceOf<T>,
+                                                    T::BlockNumber
+                                                >;
     }
 }
 
@@ -489,17 +506,20 @@ decl_event!(
         ThreadTitleUpdated(ThreadId, ForumUserId, CategoryId, Vec<u8>),
 
         /// A thread was deleted.
-        ThreadDeleted(ThreadId, PrivilegedActor, CategoryId),
+        ThreadDeleted(ThreadId, ForumUserId, CategoryId, bool),
 
         /// A thread was moved to new category
         ThreadMoved(ThreadId, CategoryId, PrivilegedActor, CategoryId),
 
         /// Post with given id was created.
-        PostAdded(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>),
+        PostAdded(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>, bool),
 
         /// Post with givne id was moderated.
         PostModerated(PostId, Vec<u8>, PrivilegedActor, CategoryId, ThreadId),
 
+        /// Post with givne id was deleted.
+        PostDeleted(Vec<u8>, ForumUserId, Vec<(CategoryId, ThreadId, PostId, bool)>),
+
         /// Post with given id had its text updated.
         /// The second argument reflects the number of total edits when the text update occurs.
         PostTextUpdated(PostId, ForumUserId, CategoryId, ThreadId, Vec<u8>),
@@ -737,12 +757,9 @@ decl_module! {
         ///    - O(W)
         /// # </weight>
         #[weight = WeightInfoForum::<T>::create_thread(
-            T::MaxCategoryDepth::get() as u32,
             title.len().saturated_into(),
             text.len().saturated_into(),
-            poll.as_ref()
-                .map(|poll| poll.poll_alternatives.len().saturated_into())
-                .unwrap_or_default(),
+            T::MaxCategoryDepth::get() as u32,
         )]
         fn create_thread(
             origin,
@@ -790,8 +807,8 @@ decl_module! {
                 author_id: forum_user_id,
                 archived: false,
                 poll: poll.clone(),
-                posts: BTreeMap::new(),
                 cleanup_pay_off: T::ThreadDeposit::get(),
+                number_of_posts: 0,
             };
 
             // Store thread
@@ -804,7 +821,8 @@ decl_module! {
                 new_thread_id,
                 category_id,
                 &text,
-                forum_user_id
+                forum_user_id,
+                true,
             );
 
             // Update next thread id
@@ -929,18 +947,25 @@ decl_module! {
         /// - DB:
         ///    - O(W)
         /// # </weight>
-        #[weight = WeightInfoForum::<T>::delete_thread_lead(
-            T::MaxCategoryDepth::get() as u32,
-        ).max(WeightInfoForum::<T>::delete_thread_moderator(
-            T::MaxCategoryDepth::get() as u32,
-        ))]
-        fn delete_thread(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId) -> DispatchResult {
+        #[weight = WeightInfoForum::<T>::delete_thread(T::MaxCategoryDepth::get() as u32)]
+        fn delete_thread(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            hide: bool,
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
             let account_id = ensure_signed(origin)?;
 
-            let thread = Self::ensure_can_moderate_thread(&account_id, &actor, &category_id, &thread_id)?;
+            let thread = Self::ensure_can_delete_thread(
+                &account_id,
+                &forum_user_id,
+                &category_id,
+                &thread_id
+            )?;
 
             //
             // == MUTATION SAFE ==
@@ -955,8 +980,9 @@ decl_module! {
             // Store the event
             Self::deposit_event(RawEvent::ThreadDeleted(
                     thread_id,
-                    actor,
-                    category_id
+                    forum_user_id,
+                    category_id,
+                    hide,
                 ));
 
             Ok(())
@@ -1091,12 +1117,11 @@ decl_module! {
         /// # </weight>
         #[weight = WeightInfoForum::<T>::moderate_thread_lead(
             T::MaxCategoryDepth::get() as u32,
-            <T::MapLimits as StorageLimits>::MaxPostsInThread::get() as u32,
             rationale.len().saturated_into(),
         ).max(
             WeightInfoForum::<T>::moderate_thread_moderator(
                 T::MaxCategoryDepth::get() as u32,
-                <T::MapLimits as StorageLimits>::MaxPostsInThread::get() as u32,
+                0,
                 rationale.len().saturated_into(),
             )
         )]
@@ -1141,43 +1166,55 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             text.len().saturated_into(),
         )]
-        fn add_post(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
+        fn add_post(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            text: Vec<u8>,
+            editable: bool,
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
             let account_id = ensure_signed(origin)?;
 
             // Make sure thread exists and is mutable
-            let (_, thread) = Self::ensure_can_add_post(&account_id, &forum_user_id, &category_id, &thread_id)?;
+            let _ = Self::ensure_can_add_post(&account_id, &forum_user_id, &category_id, &thread_id)?;
 
-            // Ensure map limits are not reached
-            Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxPostsInThread>(
-                thread.posts.len().saturated_into(),
-            )?;
+            if editable {
+                ensure!(
+                    Self::ensure_enough_balance(T::PostDeposit::get(), &account_id),
+                    Error::<T>::InsufficientBalanceForPost
+                );
+            }
 
             //
             // == MUTATION SAFE ==
             //
 
-            // Shouldn't fail since we checked in `ensure_can_add_post` that the account
-            // has enough balance.
-            Self::transfer_to_state_cleanup_treasury_account(
-                T::PostDeposit::get(),
-                thread_id,
-                &account_id
-            )?;
+            if editable {
+                // Shouldn't fail since we checked in `ensure_can_add_post` that the account
+                // has enough balance.
+                Self::transfer_to_state_cleanup_treasury_account(
+                    T::PostDeposit::get(),
+                    thread_id,
+                    &account_id
+                )?;
+            }
 
             // Add new post
-            let (post_id, _) = Self::add_new_post(
+            let post_id = Self::add_new_post(
                     thread_id,
                     category_id,
                     text.as_slice(),
-                    forum_user_id
+                    forum_user_id,
+                    editable,
                 );
 
             // Generate event
             Self::deposit_event(
-                RawEvent::PostAdded(post_id, forum_user_id, category_id, thread_id, text)
+                RawEvent::PostAdded(post_id, forum_user_id, category_id, thread_id, text, editable)
             );
 
             Ok(())
@@ -1205,8 +1242,8 @@ decl_module! {
             // Check that account is forum member
             Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
 
-            // Make sure there exists a mutable post with post id `post_id`
-            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
+            // Make sure the thread exists and is mutable
+            Self::ensure_thread_is_mutable(&category_id, &thread_id)?;
 
             //
             // == MUTATION SAFE ==
@@ -1234,7 +1271,14 @@ decl_module! {
             T::MaxCategoryDepth::get() as u32,
             new_text.len().saturated_into(),
         )]
-        fn edit_post_text(origin, forum_user_id: ForumUserId<T>, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
+        fn edit_post_text(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            category_id: T::CategoryId,
+            thread_id: T::ThreadId,
+            post_id: T::PostId,
+            new_text: Vec<u8>
+        ) -> DispatchResult {
             // Ensure data migration is done
             Self::ensure_data_migration_done()?;
 
@@ -1256,10 +1300,9 @@ decl_module! {
             // Update post text
             let text_hash = T::calculate_hash(&new_text);
             post.text_hash = text_hash;
+            post.last_edited = frame_system::Module::<T>::block_number();
 
-            <ThreadById<T>>::mutate(category_id, thread_id,
-                |thread| thread.posts.insert(post_id, post)
-            );
+            <PostById<T>>::insert(thread_id, post_id, post);
 
             // Generate event
             Self::deposit_event(RawEvent::PostTextUpdated(
@@ -1297,13 +1340,21 @@ decl_module! {
 
             let account_id = ensure_signed(origin)?;
 
-            // Ensure actor is allowed to moderate post
-            Self::ensure_can_moderate_post(account_id, &actor, &category_id, &thread_id, &post_id)?;
+            // Ensure actor is allowed to moderate post and post is editable
+            let post = Self::ensure_can_moderate_post(
+                account_id,
+                &actor,
+                &category_id,
+                &thread_id,
+                &post_id
+            )?;
 
             //
             // == MUTATION SAFE ==
             //
 
+            Self::slash_thread_account(thread_id, post.cleanup_pay_off);
+
             Self::delete_post_inner(category_id, thread_id, post_id);
 
             // Generate event
@@ -1314,6 +1365,70 @@ decl_module! {
             Ok(())
         }
 
+        /// Delete post from storage.
+        /// You need to provide a vector of posts to delete in the form
+        /// (T::CategoryId, T::ThreadId, T::PostId, bool)
+        /// where the last bool is whether you want to hide it apart from deleting it
+        ///
+        /// ## Weight
+        /// `O (W + V + P)` where:
+        /// - `W` is the category depth,
+        /// - `V` is the length of the rationale
+        /// - `P` is the number of posts to delete
+        /// - DB:
+        ///    - O(W + P)
+        /// # </weight>
+        #[weight = WeightInfoForum::<T>::delete_posts(
+            T::MaxCategoryDepth::get() as u32,
+            rationale.len().saturated_into(),
+            posts.len().saturated_into(),
+        )]
+        fn delete_posts(
+            origin,
+            forum_user_id: ForumUserId<T>,
+            posts: Vec<(T::CategoryId, T::ThreadId, T::PostId, bool)>,
+            rationale: Vec<u8>,
+        ) -> DispatchResult {
+
+            // Ensure data migration is done
+            Self::ensure_data_migration_done()?;
+
+            let account_id = ensure_signed(origin)?;
+
+            let mut deleting_posts = Vec::new();
+            for (category_id, thread_id, post_id, hide) in &posts {
+                // Ensure actor is allowed to moderate post and post is editable
+                let post = Self::ensure_can_delete_post(
+                    &account_id,
+                    &forum_user_id,
+                    &category_id,
+                    &thread_id,
+                    &post_id,
+                    *hide,
+                )?;
+
+                deleting_posts.push((category_id, thread_id, post_id, post));
+            }
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            for (category_id, thread_id, post_id, post) in deleting_posts {
+                // Pay off to thread deleter
+                Self::pay_off(*thread_id, post.cleanup_pay_off, &account_id)?;
+
+                Self::delete_post_inner(*category_id, *thread_id, *post_id);
+            }
+
+            // Generate event
+            Self::deposit_event(
+                RawEvent::PostDeleted(rationale, forum_user_id, posts)
+            );
+
+            Ok(())
+        }
+
         /// Set stickied threads for category
         ///
         /// <weight>
@@ -1398,27 +1513,33 @@ impl<T: Trait> Module<T> {
         category_id: T::CategoryId,
         text: &[u8],
         author_id: ForumUserId<T>,
-    ) -> (T::PostId, Post<ForumUserId<T>, T::Hash>) {
+        editable: bool,
+    ) -> T::PostId {
         // Make and add initial post
         let new_post_id = <NextPostId<T>>::get();
 
-        // Build a post
-        let new_post = Post {
-            text_hash: T::calculate_hash(text),
-            author_id,
-        };
+        // Update next post id
+        <NextPostId<T>>::mutate(|n| *n += One::one());
 
-        let mut thread = <ThreadById<T>>::get(category_id, thread_id);
-        thread.posts.insert(new_post_id, new_post.clone());
+        if editable {
+            // Build a post
+            let new_post = Post {
+                text_hash: T::calculate_hash(text),
+                thread_id,
+                author_id,
+                cleanup_pay_off: T::PostDeposit::get(),
+                last_edited: frame_system::Module::<T>::block_number(),
+            };
 
-        thread.cleanup_pay_off = thread.cleanup_pay_off.saturating_add(T::PostDeposit::get());
+            <PostById<T>>::insert(thread_id, new_post_id, new_post);
+        }
 
-        <ThreadById<T>>::insert(category_id, thread_id, thread);
+        let mut thread = <ThreadById<T>>::get(category_id, thread_id);
+        thread.number_of_posts = thread.number_of_posts.saturating_add(1);
 
-        // Update next post id
-        <NextPostId<T>>::mutate(|n| *n += One::one());
+        <ThreadById<T>>::mutate(category_id, thread_id, |value| *value = thread);
 
-        (new_post_id, new_post)
+        new_post_id
     }
 
     fn delete_thread_inner(category_id: T::CategoryId, thread_id: T::ThreadId) {
@@ -1433,10 +1554,14 @@ impl<T: Trait> Module<T> {
     }
 
     fn delete_post_inner(category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId) {
-        // Decrease thread's post counter
-        <ThreadById<T>>::mutate(category_id, thread_id, |thread| {
-            thread.posts.remove(&post_id);
-        });
+        if <ThreadById<T>>::contains_key(category_id, thread_id) {
+            let mut thread = <ThreadById<T>>::get(category_id, thread_id);
+            thread.number_of_posts = thread.number_of_posts.saturating_sub(1);
+
+            <ThreadById<T>>::mutate(category_id, thread_id, |value| *value = thread);
+        }
+
+        <PostById<T>>::remove(thread_id, post_id);
     }
 
     // Ensure poll is valid
@@ -1469,8 +1594,9 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
-        // Make sure post exists
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
+        // If the post is stored then it's mutable
         let post = Self::ensure_post_exists(category_id, thread_id, post_id)?;
 
         // and make sure thread is mutable
@@ -1479,22 +1605,22 @@ impl<T: Trait> Module<T> {
         Ok(post)
     }
 
+    // TODO: change this name, since it's no longer descriptive
     fn ensure_post_exists(
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
         if !<ThreadById<T>>::contains_key(category_id, thread_id) {
             return Err(Error::<T>::PostDoesNotExist);
         }
 
-        let thread = <ThreadById<T>>::get(category_id, thread_id);
+        if !<PostById<T>>::contains_key(thread_id, post_id) {
+            return Err(Error::<T>::PostDoesNotExist);
+        }
 
-        thread
-            .posts
-            .get(post_id)
-            .cloned()
-            .ok_or(Error::<T>::PostDoesNotExist)
+        Ok(<PostById<T>>::get(thread_id, post_id))
     }
 
     fn ensure_can_moderate_post(
@@ -1503,16 +1629,62 @@ impl<T: Trait> Module<T> {
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
         post_id: &T::PostId,
-    ) -> Result<Post<ForumUserId<T>, T::Hash>, Error<T>> {
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
         // Ensure the moderator can moderate the category
         Self::ensure_can_moderate_category(&account_id, &actor, &category_id)?;
 
         // Make sure post exists and is mutable
-        let post = Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
+        let post = if Self::thread_exists(category_id, thread_id) {
+            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?
+        } else {
+            <PostById<T>>::get(thread_id, post_id)
+        };
 
         Ok(post)
     }
 
+    fn ensure_can_delete_post(
+        account_id: &T::AccountId,
+        forum_user_id: &ForumUserId<T>,
+        category_id: &T::CategoryId,
+        thread_id: &T::ThreadId,
+        post_id: &T::PostId,
+        hide: bool,
+    ) -> Result<Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>, Error<T>>
+    {
+        let post = if Self::thread_exists(category_id, thread_id) {
+            Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?
+        } else {
+            <PostById<T>>::get(thread_id, post_id)
+        };
+
+        // Check that account is forum member
+        Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
+
+        // Signer does not match creator of post with identifier postId
+        ensure!(
+            post.author_id == *forum_user_id
+                || Self::anyone_can_delete_post(&post, &thread_id, &category_id) && !hide,
+            Error::<T>::AccountDoesNotMatchPostAuthor
+        );
+
+        Ok(post)
+    }
+
+    fn anyone_can_delete_post(
+        post: &Post<ForumUserId<T>, T::ThreadId, T::Hash, BalanceOf<T>, T::BlockNumber>,
+        thread_id: &T::ThreadId,
+        category_id: &T::CategoryId,
+    ) -> bool {
+        frame_system::Module::<T>::block_number() >= T::PostLifeTime::get() + post.last_edited
+            && !Self::thread_exists(&category_id, &thread_id)
+    }
+
+    fn thread_exists(category_id: &T::CategoryId, thread_id: &T::ThreadId) -> bool {
+        <ThreadById<T>>::contains_key(category_id, thread_id)
+    }
+
     fn ensure_thread_is_mutable(
         category_id: &T::CategoryId,
         thread_id: &T::ThreadId,
@@ -1650,6 +1822,25 @@ impl<T: Trait> Module<T> {
         Ok(thread)
     }
 
+    // Ensure actor can manipulate thread.
+    fn ensure_can_delete_thread(
+        account_id: &T::AccountId,
+        forum_user_id: &ForumUserId<T>,
+        category_id: &T::CategoryId,
+        thread_id: &T::ThreadId,
+    ) -> Result<ThreadOf<T>, Error<T>> {
+        // Ensure thread exists and is mutable
+        let (_, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
+
+        // Check that account is forum member
+        Self::ensure_is_forum_user(&account_id, &forum_user_id)?;
+
+        // Ensure forum user is author of the thread
+        Self::ensure_is_thread_author(&thread, &forum_user_id)?;
+
+        Ok(thread)
+    }
+
     fn ensure_can_move_thread(
         account_id: T::AccountId,
         actor: &PrivilegedActor<T>,
@@ -1927,10 +2118,6 @@ impl<T: Trait> Module<T> {
 
         let category = Self::ensure_category_is_mutable(category_id)?;
 
-        Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxThreadsInCategory>(
-            category.num_direct_threads as u64,
-        )?;
-
         // The balance for creation of thread is the base cost plus the cost of a single post
         let minimum_balance = T::ThreadDeposit::get() + T::PostDeposit::get();
         ensure!(
@@ -1954,11 +2141,6 @@ impl<T: Trait> Module<T> {
         // Check that account is forum member
         Self::ensure_is_forum_user(account_id, &forum_user_id)?;
 
-        ensure!(
-            Self::ensure_enough_balance(T::PostDeposit::get(), &account_id),
-            Error::<T>::InsufficientBalanceForPost
-        );
-
         let (category, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
 
         Ok((category, thread))
@@ -1973,6 +2155,7 @@ impl<T: Trait> Module<T> {
         // Ensure actor can moderate the category
         let category = Self::ensure_can_moderate_category(&account_id, &actor, &category_id)?;
 
+        // Ensure all thread id valid and is under the category
         // Helps to prevent thread ID duplicates.
         let mut unique_stickied_ids = BTreeSet::<T::ThreadId>::new();
 

+ 115 - 33
runtime-modules/forum/src/mock.rs

@@ -13,7 +13,7 @@ use staking_handler::LockComparator;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_runtime::{
     testing::Header,
-    traits::{BlakeTwo256, Hash, IdentityLookup, Zero},
+    traits::{BlakeTwo256, Hash, IdentityLookup},
     DispatchError, Perbill,
 };
 
@@ -104,8 +104,9 @@ parameter_types! {
     pub const LockId: [u8; 8] = [9; 8];
     pub const InviteMemberLockId: [u8; 8] = [9; 8];
     pub const StakingCandidateLockId: [u8; 8] = [10; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 // The forum working group instance alias.
@@ -120,7 +121,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = Weights;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl LockComparator<<Runtime as balances::Trait>::Balance> for Runtime {
@@ -315,9 +317,8 @@ impl membership::Trait for Runtime {
 parameter_types! {
     pub const ReferralCutMaximumPercent: u8 = 50;
     pub const MaxCategoryDepth: u64 = 20;
+    pub const PostLifeTime: u64 = 100;
     pub const MaxSubcategories: u64 = 20;
-    pub const MaxThreadsInCategory: u64 = 20;
-    pub const MaxPostsInThread: u64 = 20;
     pub const MaxModeratorsForCategory: u64 = 3;
     pub const MaxCategories: u64 = 40;
     pub const MaxPollAlternativesNumber: u64 = 20;
@@ -330,8 +331,6 @@ pub struct MapLimits;
 
 impl StorageLimits for MapLimits {
     type MaxSubcategories = MaxSubcategories;
-    type MaxThreadsInCategory = MaxThreadsInCategory;
-    type MaxPostsInThread = MaxPostsInThread;
     type MaxModeratorsForCategory = MaxModeratorsForCategory;
     type MaxCategories = MaxCategories;
     type MaxPollAlternativesNumber = MaxPollAlternativesNumber;
@@ -344,6 +343,7 @@ impl Trait for Runtime {
     type PostId = u64;
     type PostReactionId = u64;
     type MaxCategoryDepth = MaxCategoryDepth;
+    type PostLifeTime = PostLifeTime;
 
     type MapLimits = MapLimits;
     type WorkingGroup = ();
@@ -441,7 +441,7 @@ impl WeightInfo for () {
     fn delete_category_moderator(_: u32) -> Weight {
         0
     }
-    fn create_thread(_: u32, _: u32, _: u32, _: u32) -> Weight {
+    fn create_thread(_: u32, _: u32, _: u32) -> Weight {
         0
     }
     fn edit_thread_title(_: u32, _: u32) -> Weight {
@@ -453,10 +453,7 @@ impl WeightInfo for () {
     fn update_thread_archival_status_moderator(_: u32) -> Weight {
         0
     }
-    fn delete_thread_lead(_: u32) -> Weight {
-        0
-    }
-    fn delete_thread_moderator(_: u32) -> Weight {
+    fn delete_thread(_: u32) -> Weight {
         0
     }
     fn move_thread_to_category_lead(_: u32) -> Weight {
@@ -468,7 +465,7 @@ impl WeightInfo for () {
     fn vote_on_poll(_: u32, _: u32) -> Weight {
         0
     }
-    fn moderate_thread_lead(_: u32, _: u32, _: u32) -> Weight {
+    fn moderate_thread_lead(_: u32, _: u32) -> Weight {
         0
     }
     fn moderate_thread_moderator(_: u32, _: u32, _: u32) -> Weight {
@@ -495,6 +492,9 @@ impl WeightInfo for () {
     fn set_stickied_threads_moderator(_: u32, _: u32) -> Weight {
         0
     }
+    fn delete_posts(_: u32, _: u32, _: u32) -> Weight {
+        0
+    }
 }
 
 #[derive(Clone)]
@@ -737,12 +737,13 @@ pub fn edit_thread_title_mock(
 pub fn delete_thread_mock(
     origin: OriginType,
     account_id: <Runtime as frame_system::Trait>::AccountId,
-    moderator_id: ModeratorId<Runtime>,
+    forum_user_id: ForumUserId<Runtime>,
     category_id: <Runtime as Trait>::CategoryId,
-    thread_id: <Runtime as Trait>::PostId,
+    thread_id: <Runtime as Trait>::ThreadId,
     result: DispatchResult,
 ) {
     let initial_balance = balances::Module::<Runtime>::free_balance(&account_id);
+    let hide = false;
 
     let num_direct_threads = match <CategoryById<Runtime>>::contains_key(category_id) {
         true => <CategoryById<Runtime>>::get(category_id).num_direct_threads,
@@ -752,9 +753,10 @@ pub fn delete_thread_mock(
     assert_eq!(
         TestForumModule::delete_thread(
             mock_origin(origin.clone()),
-            PrivilegedActor::Moderator(moderator_id),
+            forum_user_id,
             category_id,
             thread_id,
+            hide,
         ),
         result
     );
@@ -768,8 +770,9 @@ pub fn delete_thread_mock(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::ThreadDeleted(
                 thread_id,
-                PrivilegedActor::Moderator(moderator_id),
-                category_id
+                forum_user_id,
+                category_id,
+                hide,
             ))
         );
 
@@ -785,6 +788,59 @@ pub fn delete_thread_mock(
     }
 }
 
+pub fn delete_post_mock(
+    origin: OriginType,
+    account_id: <Runtime as frame_system::Trait>::AccountId,
+    forum_user_id: ForumUserId<Runtime>,
+    category_id: <Runtime as Trait>::CategoryId,
+    thread_id: <Runtime as Trait>::ThreadId,
+    post_id: <Runtime as Trait>::PostId,
+    result: DispatchResult,
+    hide: bool,
+) {
+    let initial_balance = balances::Module::<Runtime>::free_balance(&account_id);
+    let number_of_posts = <ThreadById<Runtime>>::get(category_id, thread_id).number_of_posts;
+    let deleted_posts = vec![(category_id, thread_id, post_id, hide)];
+
+    assert_eq!(
+        TestForumModule::delete_posts(
+            mock_origin(origin.clone()),
+            forum_user_id,
+            deleted_posts.clone(),
+            vec![0u8]
+        ),
+        result
+    );
+
+    if result.is_ok() {
+        assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
+        if <ThreadById<Runtime>>::contains_key(category_id, thread_id) {
+            assert_eq!(
+                <ThreadById<Runtime>>::get(category_id, thread_id).number_of_posts,
+                number_of_posts - 1,
+            );
+        }
+        assert_eq!(
+            System::events().last().unwrap().event,
+            TestEvent::forum_mod(RawEvent::PostDeleted(
+                vec![0u8],
+                forum_user_id,
+                deleted_posts.clone()
+            ))
+        );
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&account_id),
+            initial_balance + <Runtime as Trait>::PostDeposit::get()
+        );
+    } else {
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&account_id),
+            initial_balance
+        );
+    }
+}
+
 pub fn move_thread_mock(
     origin: OriginType,
     moderator_id: ModeratorId<Runtime>,
@@ -858,6 +914,7 @@ pub fn create_post_mock(
     category_id: <Runtime as Trait>::CategoryId,
     thread_id: <Runtime as Trait>::ThreadId,
     text: Vec<u8>,
+    editable: bool,
     result: DispatchResult,
 ) -> <Runtime as Trait>::PostId {
     let post_id = TestForumModule::next_post_id();
@@ -868,10 +925,12 @@ pub fn create_post_mock(
             forum_user_id,
             category_id,
             thread_id,
-            text.clone()
+            text.clone(),
+            editable
         ),
         result
     );
+
     if result.is_ok() {
         assert_eq!(TestForumModule::next_post_id(), post_id + 1);
         assert_eq!(
@@ -881,14 +940,26 @@ pub fn create_post_mock(
                 forum_user_id,
                 category_id,
                 thread_id,
-                text
+                text,
+                editable
             ))
         );
 
-        assert_eq!(
-            balances::Module::<Runtime>::free_balance(&account_id),
-            initial_balance - <Runtime as Trait>::PostDeposit::get()
-        );
+        if editable {
+            assert_eq!(
+                balances::Module::<Runtime>::free_balance(&account_id),
+                initial_balance - <Runtime as Trait>::PostDeposit::get()
+            );
+
+            assert!(<PostById<Runtime>>::contains_key(thread_id, post_id));
+        } else {
+            assert_eq!(
+                balances::Module::<Runtime>::free_balance(&account_id),
+                initial_balance
+            );
+
+            assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
+        }
     } else {
         assert_eq!(
             balances::Module::<Runtime>::free_balance(&account_id),
@@ -918,12 +989,10 @@ pub fn edit_post_text_mock(
         ),
         result
     );
+
     if result.is_ok() {
-        let thread = TestForumModule::thread_by_id(category_id, thread_id);
-        assert_eq!(
-            thread.posts.get(&post_id).unwrap().text_hash,
-            Runtime::calculate_hash(new_text.as_slice()),
-        );
+        let post = TestForumModule::post_by_id(thread_id, post_id);
+        assert_eq!(post.text_hash, Runtime::calculate_hash(new_text.as_slice()),);
         assert_eq!(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::PostTextUpdated(
@@ -1099,9 +1168,11 @@ pub fn moderate_thread_mock(
             ))
         );
 
+        // If we moderate a thread with no extra post, only the initial post deposit
+        // should remain
         assert_eq!(
             balances::Module::<Runtime>::free_balance(&thread_account_id),
-            BalanceOf::<Runtime>::zero()
+            <Runtime as Trait>::PostDeposit::get()
         );
     }
     thread_id
@@ -1116,6 +1187,7 @@ pub fn moderate_post_mock(
     rationale: Vec<u8>,
     result: DispatchResult,
 ) -> <Runtime as Trait>::PostId {
+    let initial_balance = balances::Module::<Runtime>::free_balance(moderator_id);
     assert_eq!(
         TestForumModule::moderate_post(
             mock_origin(origin),
@@ -1128,8 +1200,7 @@ pub fn moderate_post_mock(
         result
     );
     if result.is_ok() {
-        let thread = <ThreadById<Runtime>>::get(category_id, thread_id);
-        assert!(!thread.posts.contains_key(&post_id));
+        assert!(!<PostById<Runtime>>::contains_key(thread_id, post_id));
         assert_eq!(
             System::events().last().unwrap().event,
             TestEvent::forum_mod(RawEvent::PostModerated(
@@ -1140,7 +1211,17 @@ pub fn moderate_post_mock(
                 thread_id
             ))
         );
-    }
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&moderator_id),
+            initial_balance
+        );
+    } else {
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&moderator_id),
+            initial_balance
+        );
+    };
 
     post_id
 }
@@ -1226,6 +1307,7 @@ pub fn create_genesis_config(data_migration_done: bool) -> GenesisConfig<Runtime
         next_category_id: 1,
         category_counter: 0,
         thread_by_id: vec![],
+        post_by_id: vec![],
         next_thread_id: 1,
         next_post_id: 1,
 

+ 483 - 96
runtime-modules/forum/src/tests.rs

@@ -356,6 +356,7 @@ fn update_category_archival_status_lock_works() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Ok(()),
         );
 
@@ -387,6 +388,7 @@ fn update_category_archival_status_lock_works() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Err(Error::<Runtime>::AncestorCategoryImmutable.into()),
         );
 
@@ -1017,6 +1019,7 @@ fn update_thread_archival_status_lock_works() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Ok(()),
         );
 
@@ -1037,6 +1040,7 @@ fn update_thread_archival_status_lock_works() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Err(Error::<Runtime>::ThreadImmutable.into()),
         );
 
@@ -1064,24 +1068,17 @@ fn update_thread_archival_status_lock_works() {
 }
 
 #[test]
-// test if moderator can delete thread
+// test if thread creator can delete thread
 fn delete_thread() {
-    let moderators = [
-        FORUM_MODERATOR_ORIGIN_ID,
-        FORUM_MODERATOR_2_ORIGIN_ID,
-        NOT_FORUM_MODERATOR_ORIGIN_ID,
-    ];
-    let origins = [
-        FORUM_MODERATOR_ORIGIN,
-        FORUM_MODERATOR_2_ORIGIN,
-        NOT_FORUM_MODERATOR_ORIGIN,
-    ];
-
     let forum_lead = FORUM_LEAD_ORIGIN_ID;
     let origin = OriginType::Signed(forum_lead);
     let initial_balance = 10_000_000;
     with_test_externalities(|| {
         balances::Module::<Runtime>::make_free_balance_be(&forum_lead, initial_balance);
+        balances::Module::<Runtime>::make_free_balance_be(
+            &NOT_FORUM_LEAD_ORIGIN_ID,
+            initial_balance,
+        );
 
         let mut current_balance = initial_balance;
 
@@ -1099,9 +1096,9 @@ fn delete_thread() {
         );
 
         let thread_id = create_thread_mock(
-            origin.clone(),
-            forum_lead,
-            forum_lead,
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
             category_id,
             good_thread_title(),
             good_thread_text(),
@@ -1113,17 +1110,18 @@ fn delete_thread() {
         current_balance -= <Runtime as Trait>::PostDeposit::get();
 
         assert_eq!(
-            balances::Module::<Runtime>::free_balance(&forum_lead),
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
             current_balance
         );
 
         let _ = create_post_mock(
-            origin.clone(),
-            forum_lead,
-            forum_lead,
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Ok(()),
         );
 
@@ -1139,7 +1137,7 @@ fn delete_thread() {
         current_balance -= <Runtime as Trait>::PostDeposit::get();
 
         assert_eq!(
-            balances::Module::<Runtime>::free_balance(&forum_lead),
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
             current_balance
         );
 
@@ -1147,8 +1145,8 @@ fn delete_thread() {
         assert!(<PollVotes<Runtime>>::contains_key(thread_id, forum_lead));
 
         update_category_membership_of_moderator_mock(
-            origin.clone(),
-            moderators[0],
+            FORUM_MODERATOR_ORIGIN.clone(),
+            FORUM_MODERATOR_ORIGIN_ID,
             category_id,
             true,
             Ok(()),
@@ -1162,29 +1160,59 @@ fn delete_thread() {
 
         // regular user will fail to delete the thread
         delete_thread_mock(
-            origins[2].clone(),
-            moderators[2],
-            moderators[2],
+            NOT_FORUM_MODERATOR_ORIGIN.clone(),
+            NOT_FORUM_MODERATOR_ORIGIN_ID,
+            NOT_FORUM_MODERATOR_ORIGIN_ID,
             category_id,
             thread_id,
-            Err(Error::<Runtime>::ModeratorIdNotMatchAccount.into()),
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
         );
 
         // moderator not associated with thread will fail to delete it
         delete_thread_mock(
-            origins[1].clone(),
-            moderators[1],
-            moderators[1],
+            FORUM_MODERATOR_2_ORIGIN.clone(),
+            FORUM_MODERATOR_2_ORIGIN_ID,
+            FORUM_MODERATOR_2_ORIGIN_ID,
             category_id,
             thread_id,
-            Err(Error::<Runtime>::ModeratorCantUpdateCategory.into()),
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
         );
 
-        // moderator will delete thread
+        // moderator will not delete thread
         delete_thread_mock(
-            origins[0].clone(),
-            moderators[0],
-            moderators[0],
+            FORUM_MODERATOR_ORIGIN.clone(),
+            FORUM_MODERATOR_ORIGIN_ID,
+            FORUM_MODERATOR_ORIGIN_ID,
+            category_id,
+            thread_id,
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
+        );
+
+        // forum lead will not delete thread
+        delete_thread_mock(
+            origin.clone(),
+            forum_lead,
+            forum_lead,
+            category_id,
+            thread_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchThreadAuthor.into()),
+        );
+
+        // another user will not delete thread
+        delete_thread_mock(
+            NOT_FORUM_LEAD_2_ORIGIN.clone(),
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchThreadAuthor.into()),
+        );
+
+        // thread creator will delete thread
+        delete_thread_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
             category_id,
             thread_id,
             Ok(()),
@@ -1192,6 +1220,12 @@ fn delete_thread() {
 
         // check poll voting data was deleted
         assert!(!<PollVotes<Runtime>>::contains_key(thread_id, forum_lead));
+        current_balance += <Runtime as Trait>::ThreadDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
 
         // check category's thread count was decreased
         assert_eq!(
@@ -1650,6 +1684,7 @@ fn add_post_origin() {
                 category_id,
                 thread_id,
                 good_post_text(),
+                true,
                 results[index],
             );
         });
@@ -1657,7 +1692,7 @@ fn add_post_origin() {
 }
 
 #[test]
-// test if post can be created by user without enough balance
+// test that we can't add editable posst without enough balance but we can add a non-editable one.
 fn add_post_balance() {
     let forum_lead = FORUM_LEAD_ORIGIN_ID;
     let origin = OriginType::Signed(forum_lead);
@@ -1700,6 +1735,8 @@ fn add_post_balance() {
             &forum_lead,
             <Runtime as Trait>::PostDeposit::get() - 1,
         );
+
+        // Can't create an editable post without enough balance
         create_post_mock(
             FORUM_LEAD_ORIGIN,
             forum_lead,
@@ -1707,8 +1744,21 @@ fn add_post_balance() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Err(Error::<Runtime>::InsufficientBalanceForPost.into()),
         );
+
+        // No balance requirements for non-editable posts
+        create_post_mock(
+            FORUM_LEAD_ORIGIN,
+            forum_lead,
+            forum_lead,
+            category_id,
+            thread_id,
+            good_post_text(),
+            false,
+            Ok(()),
+        );
     });
 }
 
@@ -1760,6 +1810,7 @@ fn edit_post_text() {
             category_id,
             thread_id,
             good_post_text(),
+            true,
             Ok(()),
         );
 
@@ -1787,6 +1838,64 @@ fn edit_post_text() {
     });
 }
 
+#[test]
+// can't edit non-editable post
+fn edit_non_editable_post_text() {
+    let forum_lead = FORUM_LEAD_ORIGIN_ID;
+    let origin = OriginType::Signed(forum_lead);
+
+    let initial_balance = 10_000_000;
+    with_test_externalities(|| {
+        balances::Module::<Runtime>::make_free_balance_be(&forum_lead, initial_balance);
+        balances::Module::<Runtime>::make_free_balance_be(
+            &NOT_FORUM_LEAD_ORIGIN_ID,
+            initial_balance,
+        );
+
+        // prepare category and thread
+        let category_id = create_category_mock(
+            origin.clone(),
+            None,
+            good_category_title(),
+            good_category_description(),
+            Ok(()),
+        );
+        let thread_id = create_thread_mock(
+            origin.clone(),
+            forum_lead,
+            forum_lead,
+            category_id,
+            good_thread_title(),
+            good_thread_text(),
+            None,
+            Ok(()),
+        );
+
+        // create post by author
+        let post_id = create_post_mock(
+            origin.clone(),
+            forum_lead,
+            forum_lead,
+            category_id,
+            thread_id,
+            good_post_text(),
+            false,
+            Ok(()),
+        );
+
+        // check non-author is forbidden from editing text
+        edit_post_text_mock(
+            origin.clone(),
+            forum_lead,
+            category_id,
+            thread_id,
+            post_id,
+            good_post_new_text(),
+            Err(Error::<Runtime>::PostDoesNotExist.into()),
+        );
+    });
+}
+
 /*
  ** react_post
  */
@@ -1827,6 +1936,7 @@ fn react_post() {
                 category_id,
                 thread_id,
                 good_post_text(),
+                true,
                 Ok(()),
             );
             react_post_mock(
@@ -1894,6 +2004,7 @@ fn moderate_post_origin() {
                 category_id,
                 thread_id,
                 good_post_text(),
+                true,
                 Ok(()),
             );
             moderate_post_mock(
@@ -1909,6 +2020,307 @@ fn moderate_post_origin() {
     }
 }
 
+/*
+ * Delete post
+*/
+
+#[test]
+// Test that post creator can delete it
+fn delete_post_creator() {
+    let forum_lead = FORUM_LEAD_ORIGIN_ID;
+    let origin = OriginType::Signed(forum_lead);
+    let initial_balance = 10_000_000;
+    with_test_externalities(|| {
+        balances::Module::<Runtime>::make_free_balance_be(&forum_lead, initial_balance);
+        balances::Module::<Runtime>::make_free_balance_be(
+            &NOT_FORUM_LEAD_ORIGIN_ID,
+            initial_balance,
+        );
+
+        let mut current_balance = initial_balance;
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&forum_lead),
+            current_balance
+        );
+
+        let category_id = create_category_mock(
+            origin.clone(),
+            None,
+            good_category_title(),
+            good_category_description(),
+            Ok(()),
+        );
+
+        let thread_id = create_thread_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            good_thread_title(),
+            good_thread_text(),
+            None,
+            Ok(()),
+        );
+
+        current_balance -= <Runtime as Trait>::ThreadDeposit::get();
+        current_balance -= <Runtime as Trait>::PostDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        let post_id = create_post_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            thread_id,
+            good_post_text(),
+            true,
+            Ok(()),
+        );
+
+        current_balance -= <Runtime as Trait>::PostDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        update_category_membership_of_moderator_mock(
+            FORUM_MODERATOR_ORIGIN.clone(),
+            FORUM_MODERATOR_ORIGIN_ID,
+            category_id,
+            true,
+            Ok(()),
+        );
+
+        // regular user will fail to delete the thread
+        delete_post_mock(
+            NOT_FORUM_MODERATOR_ORIGIN.clone(),
+            NOT_FORUM_MODERATOR_ORIGIN_ID,
+            NOT_FORUM_MODERATOR_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
+            false,
+        );
+
+        // moderator not associated with thread will fail to delete it
+        delete_post_mock(
+            FORUM_MODERATOR_2_ORIGIN.clone(),
+            FORUM_MODERATOR_2_ORIGIN_ID,
+            FORUM_MODERATOR_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
+            false,
+        );
+
+        // moderator will not delete thread
+        delete_post_mock(
+            FORUM_MODERATOR_ORIGIN.clone(),
+            FORUM_MODERATOR_ORIGIN_ID,
+            FORUM_MODERATOR_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::ForumUserIdNotMatchAccount.into()),
+            false,
+        );
+
+        // forum lead will not delete thread
+        delete_post_mock(
+            origin.clone(),
+            forum_lead,
+            forum_lead,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchPostAuthor.into()),
+            false,
+        );
+
+        // another user will not delete thread
+        delete_post_mock(
+            NOT_FORUM_LEAD_2_ORIGIN.clone(),
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchPostAuthor.into()),
+            false,
+        );
+
+        // post creator will delete thread
+        delete_post_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Ok(()),
+            true,
+        );
+
+        current_balance += <Runtime as Trait>::PostDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+    });
+}
+
+#[test]
+// Test that not creator of a post can delete it after N blocks
+fn delete_post_not_creator() {
+    let forum_lead = FORUM_LEAD_ORIGIN_ID;
+    let origin = OriginType::Signed(forum_lead);
+    let initial_balance = 10_000_000;
+    with_test_externalities(|| {
+        balances::Module::<Runtime>::make_free_balance_be(&forum_lead, initial_balance);
+        balances::Module::<Runtime>::make_free_balance_be(
+            &NOT_FORUM_LEAD_ORIGIN_ID,
+            initial_balance,
+        );
+
+        let mut current_balance = initial_balance;
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&forum_lead),
+            current_balance
+        );
+
+        let category_id = create_category_mock(
+            origin.clone(),
+            None,
+            good_category_title(),
+            good_category_description(),
+            Ok(()),
+        );
+
+        let thread_id = create_thread_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            good_thread_title(),
+            good_thread_text(),
+            None,
+            Ok(()),
+        );
+
+        current_balance -= <Runtime as Trait>::ThreadDeposit::get();
+        current_balance -= <Runtime as Trait>::PostDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        let post_id = create_post_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            thread_id,
+            good_post_text(),
+            true,
+            Ok(()),
+        );
+
+        current_balance -= <Runtime as Trait>::PostDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        update_category_membership_of_moderator_mock(
+            FORUM_MODERATOR_ORIGIN.clone(),
+            FORUM_MODERATOR_ORIGIN_ID,
+            category_id,
+            true,
+            Ok(()),
+        );
+
+        delete_thread_mock(
+            NOT_FORUM_LEAD_ORIGIN.clone(),
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            NOT_FORUM_LEAD_ORIGIN_ID,
+            category_id,
+            thread_id,
+            Ok(()),
+        );
+
+        // post creator will not delete thread now
+        delete_post_mock(
+            NOT_FORUM_LEAD_2_ORIGIN.clone(),
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchPostAuthor.into()),
+            false,
+        );
+
+        current_balance += <Runtime as Trait>::ThreadDeposit::get();
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        let current_block = System::block_number();
+        run_to_block(current_block + <Runtime as Trait>::PostLifeTime::get());
+
+        let not_creator_balance =
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_2_ORIGIN_ID);
+
+        // not post creator wil not be able to delete hiding the post
+        delete_post_mock(
+            NOT_FORUM_LEAD_2_ORIGIN.clone(),
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Err(Error::<Runtime>::AccountDoesNotMatchPostAuthor.into()),
+            true,
+        );
+
+        // not post creator will delete thread now
+        delete_post_mock(
+            NOT_FORUM_LEAD_2_ORIGIN.clone(),
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            NOT_FORUM_LEAD_2_ORIGIN_ID,
+            category_id,
+            thread_id,
+            post_id,
+            Ok(()),
+            false,
+        );
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_ORIGIN_ID),
+            current_balance
+        );
+
+        assert_eq!(
+            balances::Module::<Runtime>::free_balance(&NOT_FORUM_LEAD_2_ORIGIN_ID),
+            not_creator_balance + <Runtime as Trait>::PostDeposit::get()
+        );
+    });
+}
+
 #[test]
 fn set_stickied_threads_ok() {
     let forum_lead = FORUM_LEAD_ORIGIN_ID;
@@ -1942,7 +2354,40 @@ fn set_stickied_threads_ok() {
             None,
             Ok(()),
         );
-        set_stickied_threads_mock(origin, moderator_id, category_id, vec![thread_id], Ok(()));
+        set_stickied_threads_mock(
+            origin.clone(),
+            moderator_id,
+            category_id,
+            vec![thread_id],
+            Ok(()),
+        );
+
+        let thread_id_deleted = create_thread_mock(
+            origin.clone(),
+            forum_lead,
+            forum_lead,
+            category_id,
+            good_thread_title(),
+            good_thread_text(),
+            None,
+            Ok(()),
+        );
+        moderate_thread_mock(
+            origin.clone(),
+            moderator_id,
+            category_id,
+            thread_id_deleted,
+            good_moderation_rationale(),
+            Ok(()),
+        );
+        // Cannot set a deleted thread as sticky.
+        set_stickied_threads_mock(
+            origin,
+            moderator_id,
+            category_id,
+            vec![thread_id, thread_id_deleted],
+            Err(Error::<Runtime>::ThreadDoesNotExist.into()),
+        );
     });
 }
 
@@ -2112,6 +2557,7 @@ fn test_migration_not_done() {
                 category_id,
                 thread_id,
                 good_post_text(),
+                true,
             ),
             Error::<Runtime>::DataMigrationNotDone,
         );
@@ -2174,65 +2620,6 @@ fn storage_limit_checks() {
                 },
             );
         }
-
-        // test max threads in category
-        let max = <<<Runtime as Trait>::MapLimits as StorageLimits>::MaxThreadsInCategory>::get();
-        for i in 0..max {
-            create_thread_mock(
-                origin.clone(),
-                forum_lead,
-                forum_lead,
-                category_id,
-                good_thread_title(),
-                good_thread_text(),
-                None,
-                match i {
-                    _ if i == max => Err(Error::<Runtime>::MapSizeLimit.into()),
-                    _ => Ok(()),
-                },
-            );
-        }
-    });
-
-    // test MaxPostsInThread
-    with_test_externalities(|| {
-        balances::Module::<Runtime>::make_free_balance_be(&forum_lead, initial_balance);
-
-        let category_id = create_category_mock(
-            origin.clone(),
-            None,
-            good_category_title(),
-            good_category_description(),
-            Ok(()),
-        );
-        let thread_id = create_thread_mock(
-            origin.clone(),
-            forum_lead,
-            forum_lead,
-            category_id,
-            good_thread_title(),
-            good_thread_text(),
-            None,
-            Ok(()),
-        );
-
-        // test max posts in thread
-        let max = <<<Runtime as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get();
-        // starting from 1 because create_thread_mock creates one post by itself
-        for i in 1..max {
-            create_post_mock(
-                origin.clone(),
-                forum_lead,
-                forum_lead,
-                category_id,
-                thread_id,
-                good_post_text(),
-                match i {
-                    _ if i == max => Err(Error::<Runtime>::MapSizeLimit.into()),
-                    _ => Ok(()),
-                },
-            );
-        }
     });
 
     // test MaxModeratorsForCategory
@@ -2266,7 +2653,7 @@ fn storage_limit_checks() {
     // test MaxCategories
     with_test_externalities(|| {
         let max: usize =
-            <<<Runtime as Trait>::MapLimits as StorageLimits>::MaxPostsInThread>::get() as usize;
+            <<<Runtime as Trait>::MapLimits as StorageLimits>::MaxCategories>::get() as usize;
         for i in 0..max {
             create_category_mock(
                 origin.clone(),

+ 4 - 1
runtime-modules/membership/src/tests/mock.rs

@@ -111,6 +111,8 @@ parameter_types! {
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const ReferralCutMaximumPercent: u8 = 50;
     pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -122,7 +124,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = Weights;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl LockComparator<u64> for Test {

+ 1 - 1
runtime-modules/proposals/codex/src/benchmarking.rs

@@ -326,7 +326,7 @@ benchmarks! {
                 stake_policy: working_group::StakePolicy {
                     stake_amount:
                         <T as working_group::Trait<working_group::Instance1>>
-                            ::MinimumStakeForOpening::get(),
+                            ::MinimumApplicationStake::get(),
                     leaving_unstaking_period: Zero::zero(),
                 },
                 reward_per_block: None,

+ 10 - 5
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -362,7 +362,8 @@ parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 100;
     pub const LockId1: [u8; 8] = [1; 8];
     pub const LockId2: [u8; 8] = [2; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -375,7 +376,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -456,7 +458,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ForumWorkingGroupInstance> for Test {
@@ -468,7 +471,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -480,7 +484,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 pallet_staking_reward_curve::build! {

+ 1 - 1
runtime-modules/proposals/codex/src/tests/mod.rs

@@ -716,7 +716,7 @@ fn run_create_add_working_group_leader_opening_proposal_common_checks_succeed(gr
         let add_opening_parameters = CreateOpeningParameters {
             description: b"some text".to_vec(),
             stake_policy: StakePolicy {
-                stake_amount: <Test as working_group::Trait<working_group::Instance1>>::MinimumStakeForOpening::get() as
+                stake_amount: <Test as working_group::Trait<working_group::Instance1>>::MinimumApplicationStake::get() as
                     u64,
                 leaving_unstaking_period: 0 as u64,
             },

+ 3 - 3
runtime-modules/proposals/engine/src/tests/mod.rs

@@ -747,7 +747,7 @@ fn veto_proposal_succeeds_during_voting_period() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::Active{..}
+            ProposalStatus::Active { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);
@@ -791,7 +791,7 @@ fn veto_proposal_succeeds_during_grace_period() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::PendingExecution{..}
+            ProposalStatus::PendingExecution { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);
@@ -834,7 +834,7 @@ fn veto_proposal_succeeds_during_pending_constitutionality() {
 
         assert!(matches!(
             <Proposals<Test>>::get(proposal_id).status,
-            ProposalStatus::PendingConstitutionality{..}
+            ProposalStatus::PendingConstitutionality { .. }
         ));
 
         let veto_proposal = VetoProposalFixture::new(proposal_id);

+ 1 - 1
runtime-modules/proposals/engine/src/types/proposal_statuses.rs

@@ -52,7 +52,7 @@ impl<BlockNumber: Clone> ProposalStatus<BlockNumber> {
 
     /// Determines whether a proposal in pending execution status.
     pub fn is_pending_execution_proposal(&self) -> bool {
-        matches!(self.clone(), ProposalStatus::PendingExecution{..})
+        matches!(self.clone(), ProposalStatus::PendingExecution { .. })
     }
 
     /// Determines whether a proposal in pending contitutionality status.

+ 4 - 2
runtime-modules/service-discovery/src/mock.rs

@@ -219,8 +219,9 @@ parameter_types! {
     pub const LockId1: [u8; 8] = [1; 8];
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -233,7 +234,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {

+ 4 - 2
runtime-modules/storage/src/tests/mock.rs

@@ -159,8 +159,9 @@ parameter_types! {
     pub const DefaultInitialInvitationBalance: u64 = 100;
     pub const InvitedMemberLockId: [u8; 8] = [2; 8];
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
     pub const CandidateStake: u64 = 100;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u64 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -173,7 +174,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {

+ 10 - 5
runtime-modules/utility/src/tests/mocks.rs

@@ -341,7 +341,8 @@ parameter_types! {
     pub const MaxWorkerNumberLimit: u32 = 100;
     pub const LockId1: [u8; 8] = [1; 8];
     pub const LockId2: [u8; 8] = [2; 8];
-    pub const MinimumStakeForOpening: u32 = 50;
+    pub const MinimumApplicationStake: u32 = 50;
+    pub const LeaderOpeningStake: u32 = 20;
 }
 
 pub struct WorkingGroupWeightInfo;
@@ -354,7 +355,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::WeightInfo for WorkingGroupWeightInfo {
@@ -435,7 +437,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ForumWorkingGroupInstance> for Test {
@@ -447,7 +450,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
@@ -459,7 +463,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Test {
     type MinUnstakingPeriodLimit = ();
     type RewardPeriod = ();
     type WeightInfo = WorkingGroupWeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 parameter_types! {

+ 3 - 3
runtime-modules/working-group/src/benchmarking.rs

@@ -35,7 +35,7 @@ fn add_opening_helper<T: Trait<I>, I: Instance>(
     job_opening_type: &OpeningType,
 ) -> OpeningId {
     let staking_policy = StakePolicy {
-        stake_amount: T::MinimumStakeForOpening::get(),
+        stake_amount: T::MinimumApplicationStake::get(),
         leaving_unstaking_period: T::MinUnstakingPeriodLimit::get() + One::one(),
     };
 
@@ -66,7 +66,7 @@ fn apply_on_opening_helper<T: Trait<I>, I: Instance>(
 ) -> ApplicationId {
     let stake_parameters = StakeParameters {
         // Due to mock implementation of StakingHandler we can't go over 1000
-        stake: T::MinimumStakeForOpening::get(),
+        stake: T::MinimumApplicationStake::get(),
         staking_account_id: applicant_id.clone(),
     };
 
@@ -524,7 +524,7 @@ benchmarks_instance! {
             description: vec![0u8; i.try_into().unwrap()],
             stake_parameters:
                 StakeParameters {
-                    stake: T::MinimumStakeForOpening::get(),
+                    stake: T::MinimumApplicationStake::get(),
                     staking_account_id: lead_account_id.clone(),
                 }
         };

+ 26 - 1
runtime-modules/working-group/src/checks.rs

@@ -12,6 +12,7 @@ use sp_arithmetic::traits::Zero;
 use sp_std::collections::btree_set::BTreeSet;
 use sp_std::marker::PhantomData;
 use sp_std::vec::Vec;
+use staking_handler::StakingHandler;
 
 use crate::types::{ApplicationInfo, StakeParameters};
 
@@ -32,6 +33,30 @@ pub(crate) fn ensure_origin_for_opening_type<T: Trait<I>, I: Instance>(
     }
 }
 
+pub(crate) fn ensure_stake_for_opening_type<T: Trait<I>, I: Instance>(
+    origin: T::Origin,
+    opening_type: OpeningType,
+) -> DispatchResult {
+    // Lead needs stake to generate opening
+    if opening_type == OpeningType::Regular {
+        // We check here that the origin is active leader
+        // just to make this future proof for any change in
+        // `ensure_origin_for_opening_type`
+        ensure_origin_is_active_leader::<T, I>(origin)?;
+        let lead = crate::Module::<T, I>::worker_by_id(ensure_lead_is_set::<T, I>()?);
+
+        ensure!(
+            T::StakingHandler::is_enough_balance_for_stake(
+                &lead.staking_account_id,
+                T::LeaderOpeningStake::get()
+            ),
+            Error::<T, I>::InsufficientBalanceToCoverStake
+        );
+    }
+
+    Ok(())
+}
+
 // Check opening: returns the opening by id if it is exists.
 pub(crate) fn ensure_opening_exists<T: Trait<I>, I: Instance>(
     opening_id: OpeningId,
@@ -191,7 +216,7 @@ pub(crate) fn ensure_valid_stake_policy<T: Trait<I>, I: Instance>(
     stake_policy: &StakePolicy<T::BlockNumber, BalanceOf<T>>,
 ) -> Result<(), DispatchError> {
     ensure!(
-        stake_policy.stake_amount >= T::MinimumStakeForOpening::get(),
+        stake_policy.stake_amount >= T::MinimumApplicationStake::get(),
         Error::<T, I>::BelowMinimumStakes
     );
 

+ 45 - 3
runtime-modules/working-group/src/lib.rs

@@ -118,8 +118,11 @@ pub trait Trait<I: Instance = DefaultInstance>:
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
 
-    /// Minimum stake required for an opening
-    type MinimumStakeForOpening: Get<Self::Balance>;
+    /// Minimum stake required for applying into an opening
+    type MinimumApplicationStake: Get<Self::Balance>;
+
+    /// Stake needed to create an opening
+    type LeaderOpeningStake: Get<Self::Balance>;
 }
 
 decl_event!(
@@ -363,16 +366,31 @@ decl_module! {
             stake_policy: StakePolicy<T::BlockNumber, BalanceOf<T>>,
             reward_per_block: Option<BalanceOf<T>>
         ){
-            checks::ensure_origin_for_opening_type::<T, I>(origin, opening_type)?;
+            checks::ensure_origin_for_opening_type::<T, I>(origin.clone(), opening_type)?;
 
             checks::ensure_valid_stake_policy::<T, I>(&stake_policy)?;
 
             checks::ensure_valid_reward_per_block::<T, I>(&reward_per_block)?;
 
+            checks::ensure_stake_for_opening_type::<T, I>(origin, opening_type)?;
+
             //
             // == MUTATION SAFE ==
             //
 
+            let mut creation_stake = BalanceOf::<T>::zero();
+            if opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                creation_stake = T::LeaderOpeningStake::get();
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    creation_stake.saturating_add(current_stake)
+                )?;
+            }
+
             let hashed_description = T::Hashing::hash(&description);
 
             // Create and add worker opening.
@@ -382,6 +400,7 @@ decl_module! {
                 description_hash: hashed_description.as_ref().to_vec(),
                 stake_policy: stake_policy.clone(),
                 reward_per_block,
+                creation_stake,
             };
 
             let new_opening_id = NextOpeningId::<I>::get();
@@ -542,6 +561,17 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
+            if opening.opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    current_stake.saturating_sub(opening.creation_stake)
+                )?;
+            }
+
             // Process successful applications
             let application_id_to_worker_id = Self::fulfill_successful_applications(
                 &opening,
@@ -861,6 +891,18 @@ decl_module! {
             // == MUTATION SAFE ==
             //
 
+            // Remove opening stake
+            if opening.opening_type == OpeningType::Regular {
+                // Lead must be set for ensure_origin_for_openig_type in the
+                // case of regular.
+                let lead = Self::worker_by_id(checks::ensure_lead_is_set::<T, I>()?);
+                let current_stake = T::StakingHandler::current_stake(&lead.staking_account_id);
+                T::StakingHandler::set_stake(
+                    &lead.staking_account_id,
+                    current_stake.saturating_sub(opening.creation_stake)
+                )?;
+            }
+
             // Remove the opening.
             <OpeningById::<T, I>>::remove(opening_id);
 

+ 15 - 8
runtime-modules/working-group/src/tests/fixtures.rs

@@ -63,7 +63,7 @@ impl Default for AddOpeningFixture {
             opening_type: OpeningType::Regular,
             starting_block: 0,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
@@ -94,6 +94,11 @@ impl AddOpeningFixture {
                 opening_type: self.opening_type,
                 stake_policy: self.stake_policy.clone(),
                 reward_per_block: self.reward_per_block.clone(),
+                creation_stake: if self.opening_type == OpeningType::Regular {
+                    <Test as Trait>::LeaderOpeningStake::get()
+                } else {
+                    0
+                },
             };
 
             assert_eq!(actual_opening, expected_opening);
@@ -204,10 +209,10 @@ impl ApplyOnOpeningFixture {
             reward_account_id: 2,
             description: b"human_text".to_vec(),
             stake_parameters: StakeParameters {
-                stake: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake: <Test as Trait>::MinimumApplicationStake::get(),
                 staking_account_id: 2,
             },
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get(),
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get(),
         }
     }
 
@@ -293,7 +298,7 @@ impl FillOpeningFixture {
             reward_account_id: 2,
             staking_account_id: 2,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
@@ -403,12 +408,14 @@ impl Default for HireLeadFixture {
         Self {
             setup_environment: true,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
             lead_id: 1,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get() + 1,
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get()
+                + <Test as Trait>::LeaderOpeningStake::get()
+                + 1,
         }
     }
 }
@@ -477,11 +484,11 @@ impl Default for HireRegularWorkerFixture {
         Self {
             setup_environment: true,
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get(),
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get(),
         }
     }
 }

+ 6 - 4
runtime-modules/working-group/src/tests/hiring_workflow.rs

@@ -33,13 +33,13 @@ impl Default for HiringWorkflow {
             opening_type: OpeningType::Regular,
             expected_result: Ok(()),
             stake_policy: StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get() + 1,
             },
             reward_per_block: None,
             applications: Vec::new(),
             setup_environment: true,
-            initial_balance: <Test as Trait>::MinimumStakeForOpening::get() + 1,
+            initial_balance: <Test as Trait>::MinimumApplicationStake::get() + 1,
         }
     }
 }
@@ -129,7 +129,9 @@ impl HiringWorkflow {
         } else {
             balances::Module::<Test>::make_free_balance_be(
                 &1,
-                <Test as Trait>::MinimumStakeForOpening::get() + 1,
+                <Test as Trait>::MinimumApplicationStake::get()
+                    + <Test as Trait>::LeaderOpeningStake::get()
+                    + 1,
             );
             //         setup_members(6);
         }
@@ -137,7 +139,7 @@ impl HiringWorkflow {
 
     pub fn execute(&self) -> Option<u64> {
         if self.setup_environment {
-            self.setup_environment()
+            self.setup_environment();
         }
 
         let result = self.fill_worker_position();

+ 4 - 2
runtime-modules/working-group/src/tests/mock.rs

@@ -185,8 +185,9 @@ parameter_types! {
     pub const RewardPeriod: u32 = 2;
     pub const MaxWorkerNumberLimit: u32 = 3;
     pub const MinUnstakingPeriodLimit: u64 = 3;
-    pub const MinimumStakeForOpening: u64 = 50;
+    pub const MinimumApplicationStake: u64 = 50;
     pub const LockId: [u8; 8] = [1; 8];
+    pub const LeaderOpeningStake: u64 = 20;
 }
 
 impl Trait for Test {
@@ -198,7 +199,8 @@ impl Trait for Test {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = RewardPeriod;
     type WeightInfo = ();
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl common::StakingAccountValidator<Test> for () {

+ 46 - 10
runtime-modules/working-group/src/tests/mod.rs

@@ -35,12 +35,13 @@ fn add_opening_succeeded() {
         HireLeadFixture::default().hire_lead();
 
         let starting_block = 1;
+
         run_to_block(starting_block);
 
         let add_opening_fixture = AddOpeningFixture::default()
             .with_starting_block(starting_block)
             .with_stake_policy(StakePolicy {
-                stake_amount: <Test as Trait>::MinimumStakeForOpening::get(),
+                stake_amount: <Test as Trait>::MinimumApplicationStake::get(),
                 leaving_unstaking_period: 100,
             })
             .with_reward_per_block(Some(10));
@@ -83,7 +84,7 @@ fn add_opening_fails_with_less_than_minimum_stake() {
         ));
 
         let add_opening_fixture = AddOpeningFixture::default().with_stake_policy(StakePolicy {
-            stake_amount: <Test as Trait>::MinimumStakeForOpening::get() - 1,
+            stake_amount: <Test as Trait>::MinimumApplicationStake::get() - 1,
             leaving_unstaking_period: <Test as Trait>::MinUnstakingPeriodLimit::get(),
         });
 
@@ -106,6 +107,21 @@ fn add_opening_fails_with_zero_reward() {
     });
 }
 
+#[test]
+fn add_opening_fails_with_insufficient_balance() {
+    build_test_externalities().execute_with(|| {
+        HireLeadFixture::default()
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get() + 1)
+            .hire_lead();
+
+        let add_opening_fixture = AddOpeningFixture::default();
+
+        add_opening_fixture.call_and_assert(Err(
+            Error::<Test, DefaultInstance>::InsufficientBalanceToCoverStake.into(),
+        ));
+    });
+}
+
 #[test]
 fn add_opening_fails_with_incorrect_unstaking_period() {
     build_test_externalities().execute_with(|| {
@@ -152,7 +168,7 @@ fn apply_on_opening_succeeded() {
         let opening_id = add_opening_fixture.call().unwrap();
 
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
-            .with_initial_balance(<Test as Trait>::MinimumStakeForOpening::get());
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get());
 
         let application_id = apply_on_opening_fixture.call_and_assert(Ok(()));
 
@@ -239,8 +255,15 @@ fn fill_opening_succeeded() {
                 .with_reward_per_block(Some(reward_per_block))
                 .with_created_at(starting_block);
 
+        let initial_balance = Balances::usable_balance(&1);
+
         let worker_id = fill_opening_fixture.call_and_assert(Ok(()));
 
+        assert_eq!(
+            Balances::usable_balance(&1),
+            initial_balance + <Test as Trait>::LeaderOpeningStake::get()
+        );
+
         let mut result_map = BTreeMap::new();
         result_map.insert(application_id, worker_id);
 
@@ -328,7 +351,13 @@ fn fill_opening_fails_with_bad_origin() {
 #[test]
 fn fill_opening_fails_with_application_for_other_opening() {
     build_test_externalities().execute_with(|| {
-        HireLeadFixture::default().hire_lead();
+        HireLeadFixture::default()
+            .with_initial_balance(
+                <Test as Trait>::MinimumApplicationStake::get()
+                    + 3 * <Test as Trait>::LeaderOpeningStake::get()
+                    + 1,
+            )
+            .hire_lead();
 
         let add_opening_fixture = AddOpeningFixture::default();
 
@@ -595,7 +624,7 @@ fn leave_worker_role_succeeds_with_paying_missed_reward() {
 
         assert_eq!(
             Balances::usable_balance(&account_id),
-            block_number * reward_per_block + <Test as Trait>::MinimumStakeForOpening::get()
+            block_number * reward_per_block + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
@@ -626,7 +655,7 @@ fn leave_worker_role_succeeds_with_partial_payment_of_missed_reward() {
 
         assert_eq!(
             Balances::usable_balance(&account_id),
-            budget + <Test as Trait>::MinimumStakeForOpening::get()
+            budget + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
@@ -768,7 +797,7 @@ fn terminate_worker_role_succeeds_with_paying_missed_reward() {
 
         assert_eq!(
             Balances::usable_balance(&account_id),
-            block_number * reward_per_block + <Test as Trait>::MinimumStakeForOpening::get()
+            block_number * reward_per_block + <Test as Trait>::MinimumApplicationStake::get()
         );
     });
 }
@@ -921,8 +950,8 @@ fn apply_on_opening_locks_the_stake() {
         HireLeadFixture::default().hire_lead();
 
         let account_id = 2;
-        let total_balance = <Test as Trait>::MinimumStakeForOpening::get() + 100;
-        let stake = <Test as Trait>::MinimumStakeForOpening::get();
+        let total_balance = <Test as Trait>::MinimumApplicationStake::get() + 100;
+        let stake = <Test as Trait>::MinimumApplicationStake::get();
 
         let stake_parameters = StakeParameters {
             stake,
@@ -1684,7 +1713,7 @@ fn withdraw_worker_application_fails_with_invalid_application_author() {
         let opening_id = add_opening_fixture.call_and_assert(Ok(()));
 
         let apply_on_opening_fixture = ApplyOnOpeningFixture::default_for_opening_id(opening_id)
-            .with_initial_balance(<Test as Trait>::MinimumStakeForOpening::get() + 1);
+            .with_initial_balance(<Test as Trait>::MinimumApplicationStake::get() + 1);
         let application_id = apply_on_opening_fixture.call_and_assert(Ok(()));
 
         let invalid_author_account_id = 55;
@@ -1713,9 +1742,16 @@ fn cancel_opening_succeeds() {
         let add_opening_fixture = AddOpeningFixture::default().with_starting_block(starting_block);
         let opening_id = add_opening_fixture.call_and_assert(Ok(()));
 
+        let initial_balance = Balances::usable_balance(&1);
+
         let cancel_opening_fixture = CancelOpeningFixture::default_for_opening_id(opening_id);
         cancel_opening_fixture.call_and_assert(Ok(()));
 
+        assert_eq!(
+            Balances::usable_balance(&1),
+            initial_balance + <Test as Trait>::LeaderOpeningStake::get()
+        );
+
         EventFixture::assert_last_crate_event(RawEvent::OpeningCanceled(opening_id));
     });
 }

+ 3 - 0
runtime-modules/working-group/src/types.rs

@@ -72,6 +72,9 @@ pub struct Opening<BlockNumber: Ord, Balance> {
 
     /// Reward per block for the job opening.
     pub reward_per_block: Option<Balance>,
+
+    /// Stake used to create the opening.
+    pub creation_stake: Balance,
 }
 
 /// Defines type of the opening: regular working group fellow or group leader.

+ 16 - 8
runtime/src/lib.rs

@@ -659,14 +659,13 @@ parameter_types! {
     pub const ThreadDeposit: u64 = 30;
     pub const PostDeposit: u64 = 10;
     pub const ForumModuleId: ModuleId = ModuleId(*b"mo:forum"); // module : forum
+    pub const PostLifeTime: BlockNumber = 3600;
 }
 
 pub struct MapLimits;
 
 impl forum::StorageLimits for MapLimits {
     type MaxSubcategories = MaxSubcategories;
-    type MaxThreadsInCategory = MaxThreadsInCategory;
-    type MaxPostsInThread = MaxPostsInThread;
     type MaxModeratorsForCategory = MaxModeratorsForCategory;
     type MaxCategories = MaxCategories;
     type MaxPollAlternativesNumber = MaxPollAlternativesNumber;
@@ -693,6 +692,7 @@ impl forum::Trait for Runtime {
 
     type WorkingGroup = ForumWorkingGroup;
     type MemberOriginValidator = Members;
+    type PostLifeTime = PostLifeTime;
 }
 
 impl LockComparator<<Runtime as pallet_balances::Trait>::Balance> for Runtime {
@@ -710,10 +710,14 @@ parameter_types! {
     pub const StorageWorkingGroupRewardPeriod: u32 = 14400 + 20;
     pub const ContentWorkingGroupRewardPeriod: u32 = 14400 + 30;
     pub const MembershipRewardPeriod: u32 = 14400 + 40;
+    // This should be more costly than `apply_on_opening` fee with the current configuration
+    // the base cost of `apply_on_opening` in tokens is 193. And has a very slight slope
+    // with the lenght with the length of rationale, with 2000 stake we are probably safe.
+    pub const MinimumApplicationStake: Balance = 2000;
     // This should be more costly than `add_opening` fee with the current configuration
-    // the base cost of `add_opening` in tokens is 193. And has a very slight slope
+    // the base cost of `add_opening` in tokens is 81. And has a very slight slope
     // with the lenght with the length of rationale, with 2000 stake we are probably safe.
-    pub const MinimumStakeForOpening: Balance = 2000;
+    pub const LeaderOpeningStake: Balance = 2000;
 }
 
 // Staking managers type aliases.
@@ -751,7 +755,8 @@ impl working_group::Trait<ForumWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ForumWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
@@ -763,7 +768,8 @@ impl working_group::Trait<StorageWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = StorageWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
@@ -775,7 +781,8 @@ impl working_group::Trait<ContentDirectoryWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = ContentWorkingGroupRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
@@ -787,7 +794,8 @@ impl working_group::Trait<MembershipWorkingGroupInstance> for Runtime {
     type MinUnstakingPeriodLimit = MinUnstakingPeriodLimit;
     type RewardPeriod = MembershipRewardPeriod;
     type WeightInfo = weights::working_group::WeightInfo;
-    type MinimumStakeForOpening = MinimumStakeForOpening;
+    type MinimumApplicationStake = MinimumApplicationStake;
+    type LeaderOpeningStake = LeaderOpeningStake;
 }
 
 impl service_discovery::Trait for Runtime {

+ 2 - 2
runtime/src/tests/proposals_integration/mod.rs

@@ -470,7 +470,7 @@ fn set_membership_leader(lead_account_id: AccountId32, lead_id: u64) {
         working_group::OpeningType::Leader,
         StakePolicy {
             stake_amount:
-                <Runtime as working_group::Trait<MembershipWorkingGroupInstance>>::MinimumStakeForOpening::get(
+                <Runtime as working_group::Trait<MembershipWorkingGroupInstance>>::MinimumApplicationStake::get(
                 ) as u128,
             leaving_unstaking_period: 1000000,
         },
@@ -485,7 +485,7 @@ fn set_membership_leader(lead_account_id: AccountId32, lead_id: u64) {
         reward_account_id: lead_account_id.clone(),
         description: vec![0u8],
         stake_parameters: StakeParameters {
-            stake: <Runtime as working_group::Trait<MembershipWorkingGroupInstance>>::MinimumStakeForOpening::get() as
+            stake: <Runtime as working_group::Trait<MembershipWorkingGroupInstance>>::MinimumApplicationStake::get() as
                 u128,
             staking_account_id: lead_account_id.clone(),
         },

+ 7 - 7
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -409,7 +409,7 @@ fn run_create_add_working_group_leader_opening_proposal_execution_succeeds<
                 StakePolicy {
                     stake_amount: <Runtime as working_group::Trait<
                         MembershipWorkingGroupInstance,
-                    >>::MinimumStakeForOpening::get() as u128,
+                    >>::MinimumApplicationStake::get() as u128,
                     leaving_unstaking_period: 1_000_000,
                 },
                 1,
@@ -481,7 +481,7 @@ fn run_create_fill_working_group_leader_opening_proposal_execution_succeeds<
                 StakePolicy {
                     stake_amount: <Runtime as working_group::Trait<
                         MembershipWorkingGroupInstance,
-                    >>::MinimumStakeForOpening::get() as u128,
+                    >>::MinimumApplicationStake::get() as u128,
                     leaving_unstaking_period: 1_000_000,
                 },
                 1,
@@ -503,7 +503,7 @@ fn run_create_fill_working_group_leader_opening_proposal_execution_succeeds<
                                 T::Balance::from(
                                     <Runtime as working_group::Trait<
                                         MembershipWorkingGroupInstance,
-                                    >>::MinimumStakeForOpening::get(
+                                    >>::MinimumApplicationStake::get(
                                     )
                                     .try_into()
                                     .unwrap(),
@@ -931,7 +931,7 @@ fn run_create_syphon_working_group_mint_capacity_proposal_execution_succeeds<
                 StakePolicy {
                     stake_amount: <Runtime as working_group::Trait<
                         MembershipWorkingGroupInstance,
-                    >>::MinimumStakeForOpening::get() as u128,
+                    >>::MinimumApplicationStake::get() as u128,
                     leaving_unstaking_period: 1_000_000,
                 },
                 1,
@@ -942,7 +942,7 @@ fn run_create_syphon_working_group_mint_capacity_proposal_execution_succeeds<
             StakeParameters {
                 stake: <Runtime as working_group::Trait<
                     MembershipWorkingGroupInstance,
-                >>::MinimumStakeForOpening::get()
+                >>::MinimumApplicationStake::get()
                 .into(),
                 staking_account_id: account_id.into(),
             }
@@ -1073,7 +1073,7 @@ fn run_create_set_group_leader_reward_proposal_execution_succeeds<
             StakeParameters {
                 stake: <Runtime as working_group::Trait<
                     MembershipWorkingGroupInstance,
-                >>::MinimumStakeForOpening::get()
+                >>::MinimumApplicationStake::get()
                 .into(),
                 staking_account_id: account_id.into(),
             }
@@ -1086,7 +1086,7 @@ fn run_create_set_group_leader_reward_proposal_execution_succeeds<
                 StakePolicy {
                     stake_amount: <Runtime as working_group::Trait<
                         MembershipWorkingGroupInstance,
-                    >>::MinimumStakeForOpening::get() as u128,
+                    >>::MinimumApplicationStake::get() as u128,
                     leaving_unstaking_period: 1_000_000,
                 },
                 1,

+ 87 - 84
runtime/src/weights/forum.rs

@@ -9,188 +9,191 @@ pub struct WeightInfo;
 impl forum::WeightInfo for WeightInfo {
     fn create_category(i: u32, j: u32, k: u32) -> Weight {
         (0 as Weight)
-            .saturating_add((536_866_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((206_000 as Weight).saturating_mul(j as Weight))
-            .saturating_add((58_000 as Weight).saturating_mul(k as Weight))
+            .saturating_add((214_529_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((98_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add((134_000 as Weight).saturating_mul(k as Weight))
             .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(3 as Weight))
     }
     fn update_category_membership_of_moderator_new() -> Weight {
-        (769_077_000 as Weight)
+        (297_135_000 as Weight)
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().writes(2 as Weight))
     }
     fn update_category_membership_of_moderator_old() -> Weight {
-        (844_050_000 as Weight)
+        (315_520_000 as Weight)
             .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().writes(2 as Weight))
     }
     fn update_category_archival_status_lead(i: u32) -> Weight {
-        (579_518_000 as Weight)
-            .saturating_add((148_741_000 as Weight).saturating_mul(i as Weight))
+        (177_097_000 as Weight)
+            .saturating_add((57_774_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn update_category_archival_status_moderator(i: u32) -> Weight {
-        (695_186_000 as Weight)
-            .saturating_add((90_362_000 as Weight).saturating_mul(i as Weight))
+        (195_602_000 as Weight)
+            .saturating_add((57_023_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn delete_category_lead(i: u32) -> Weight {
-        (475_589_000 as Weight)
-            .saturating_add((137_382_000 as Weight).saturating_mul(i as Weight))
+        (142_929_000 as Weight)
+            .saturating_add((68_133_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(2 as Weight))
     }
     fn delete_category_moderator(i: u32) -> Weight {
-        (491_714_000 as Weight)
-            .saturating_add((143_917_000 as Weight).saturating_mul(i as Weight))
+        (184_678_000 as Weight)
+            .saturating_add((63_220_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(3 as Weight))
     }
-    fn create_thread(i: u32, j: u32, k: u32, z: u32) -> Weight {
-        (657_005_000 as Weight)
-            .saturating_add((95_246_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((78_000 as Weight).saturating_mul(j as Weight))
-            .saturating_add((80_000 as Weight).saturating_mul(k as Weight))
-            .saturating_add((9_492_000 as Weight).saturating_mul(z as Weight))
+    // WARNING! Some components were not used: ["z"]
+    fn create_thread(j: u32, k: u32, i: u32) -> Weight {
+        (1_763_250_000 as Weight)
+            .saturating_add((122_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add((171_000 as Weight).saturating_mul(k as Weight))
             .saturating_add(DbWeight::get().reads(7 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(5 as Weight))
+            .saturating_add(DbWeight::get().writes(7 as Weight))
     }
     fn edit_thread_title(i: u32, j: u32) -> Weight {
-        (379_562_000 as Weight)
-            .saturating_add((210_957_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((83_000 as Weight).saturating_mul(j as Weight))
+        (161_572_000 as Weight)
+            .saturating_add((72_014_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((127_000 as Weight).saturating_mul(j as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn update_thread_archival_status_lead(i: u32) -> Weight {
-        (532_961_000 as Weight)
-            .saturating_add((233_514_000 as Weight).saturating_mul(i as Weight))
+        (212_838_000 as Weight)
+            .saturating_add((116_257_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn update_thread_archival_status_moderator(i: u32) -> Weight {
-        (688_504_000 as Weight)
-            .saturating_add((197_194_000 as Weight).saturating_mul(i as Weight))
+        (237_385_000 as Weight)
+            .saturating_add((114_127_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
-    fn delete_thread_lead(i: u32) -> Weight {
-        (1_030_000_000 as Weight)
-            .saturating_add((148_165_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add(DbWeight::get().reads(4 as Weight))
-            .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(22 as Weight))
-    }
-    fn delete_thread_moderator(i: u32) -> Weight {
-        (1_260_738_000 as Weight)
-            .saturating_add((122_231_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add(DbWeight::get().reads(4 as Weight))
+    fn delete_thread(i: u32) -> Weight {
+        (592_486_000 as Weight)
+            .saturating_add((57_770_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(22 as Weight))
+            .saturating_add(DbWeight::get().writes(4 as Weight))
     }
     fn move_thread_to_category_lead(i: u32) -> Weight {
-        (977_047_000 as Weight)
-            .saturating_add((205_129_000 as Weight).saturating_mul(i as Weight))
+        (365_000_000 as Weight)
+            .saturating_add((100_941_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(4 as Weight))
     }
     fn move_thread_to_category_moderator(i: u32) -> Weight {
-        (931_744_000 as Weight)
-            .saturating_add((206_882_000 as Weight).saturating_mul(i as Weight))
+        (400_012_000 as Weight)
+            .saturating_add((101_980_000 as Weight).saturating_mul(i as Weight))
             .saturating_add(DbWeight::get().reads(6 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(4 as Weight))
     }
     fn vote_on_poll(i: u32, j: u32) -> Weight {
-        (933_414_000 as Weight)
-            .saturating_add((169_101_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((33_456_000 as Weight).saturating_mul(j as Weight))
+        (311_689_000 as Weight)
+            .saturating_add((57_977_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((18_453_000 as Weight).saturating_mul(j as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
-    fn moderate_thread_lead(i: u32, j: u32, k: u32) -> Weight {
-        (1_339_536_000 as Weight)
-            .saturating_add((157_127_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((5_014_000 as Weight).saturating_mul(j as Weight))
-            .saturating_add((339_000 as Weight).saturating_mul(k as Weight))
-            .saturating_add(DbWeight::get().reads(4 as Weight))
+    // WARNING! Some components were not used: ["j"]
+    fn moderate_thread_lead(i: u32, k: u32) -> Weight {
+        (533_448_000 as Weight)
+            .saturating_add((57_859_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((129_000 as Weight).saturating_mul(k as Weight))
+            .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(3 as Weight))
-            .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(j as Weight)))
     }
-    fn moderate_thread_moderator(k: u32, i: u32, j: u32) -> Weight {
-        (6_048_919_000 as Weight)
-            .saturating_add((270_000 as Weight).saturating_mul(k as Weight))
-            .saturating_add(DbWeight::get().reads(4 as Weight))
+    fn moderate_thread_moderator(i: u32, j: u32, k: u32) -> Weight {
+        (343_909_000 as Weight)
+            .saturating_add((63_466_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((6_334_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add((131_000 as Weight).saturating_mul(k as Weight))
+            .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(3 as Weight))
-            .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(j as Weight)))
     }
     fn add_post(i: u32, j: u32) -> Weight {
-        (544_287_000 as Weight)
-            .saturating_add((84_358_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((83_000 as Weight).saturating_mul(j as Weight))
-            .saturating_add(DbWeight::get().reads(5 as Weight))
+        (584_267_000 as Weight)
+            .saturating_add((54_076_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((128_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add(DbWeight::get().reads(6 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(3 as Weight))
+            .saturating_add(DbWeight::get().writes(5 as Weight))
     }
     fn react_post(i: u32) -> Weight {
-        (624_679_000 as Weight)
-            .saturating_add((90_554_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add(DbWeight::get().reads(4 as Weight))
+        (299_480_000 as Weight)
+            .saturating_add((57_223_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
     }
     fn edit_post_text(i: u32, j: u32) -> Weight {
-        (623_715_000 as Weight)
-            .saturating_add((110_140_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((83_000 as Weight).saturating_mul(j as Weight))
+        (387_494_000 as Weight)
+            .saturating_add((70_340_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((126_000 as Weight).saturating_mul(j as Weight))
             .saturating_add(DbWeight::get().reads(4 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn moderate_post_lead(i: u32, j: u32) -> Weight {
-        (1_265_038_000 as Weight)
-            .saturating_add((179_219_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((240_000 as Weight).saturating_mul(j as Weight))
-            .saturating_add(DbWeight::get().reads(5 as Weight))
+        (1_094_423_000 as Weight)
+            .saturating_add((103_781_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((121_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add(DbWeight::get().reads(6 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(2 as Weight))
+            .saturating_add(DbWeight::get().writes(3 as Weight))
     }
     fn moderate_post_moderator(i: u32, j: u32) -> Weight {
-        (1_156_880_000 as Weight)
-            .saturating_add((193_169_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((243_000 as Weight).saturating_mul(j as Weight))
+        (863_056_000 as Weight)
+            .saturating_add((134_282_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((126_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add(DbWeight::get().reads(6 as Weight))
+            .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
+            .saturating_add(DbWeight::get().writes(3 as Weight))
+    }
+    fn delete_posts(i: u32, j: u32, k: u32) -> Weight {
+        (0 as Weight)
+            .saturating_add((1_007_340_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((113_000 as Weight).saturating_mul(j as Weight))
+            .saturating_add((1_418_940_000 as Weight).saturating_mul(k as Weight))
             .saturating_add(DbWeight::get().reads(5 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
-            .saturating_add(DbWeight::get().writes(2 as Weight))
+            .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(k as Weight)))
+            .saturating_add(DbWeight::get().writes(3 as Weight))
+            .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight)))
     }
     fn set_stickied_threads_lead(i: u32, j: u32) -> Weight {
-        (647_830_000 as Weight)
-            .saturating_add((53_426_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((356_269_000 as Weight).saturating_mul(j as Weight))
+        (219_049_000 as Weight)
+            .saturating_add((59_817_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((204_099_000 as Weight).saturating_mul(j as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(j as Weight)))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }
     fn set_stickied_threads_moderator(i: u32, j: u32) -> Weight {
-        (479_755_000 as Weight)
-            .saturating_add((78_709_000 as Weight).saturating_mul(i as Weight))
-            .saturating_add((354_352_000 as Weight).saturating_mul(j as Weight))
+        (281_825_000 as Weight)
+            .saturating_add((47_851_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add((203_487_000 as Weight).saturating_mul(j as Weight))
             .saturating_add(DbWeight::get().reads(3 as Weight))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight)))
             .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(j as Weight)))