Преглед на файлове

runtime: proposals/discussion: add state bloat fix

conectado преди 4 години
родител
ревизия
90fb242cf8

+ 0 - 1
runtime-modules/proposals/codex/src/lib.rs

@@ -887,6 +887,5 @@ impl<T: Trait> ProposalObserver<T> for Module<T> {
         let thread_id = Self::thread_id_by_proposal_id(proposal_id);
 
         proposals_discussion::ThreadById::<T>::remove(thread_id);
-        proposals_discussion::PostThreadIdByPostId::<T>::remove_prefix(thread_id);
     }
 }

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

@@ -11,7 +11,7 @@ use sp_runtime::curve::PiecewiseLinear;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    DispatchResult, Perbill,
+    DispatchResult, ModuleId, Perbill,
 };
 use sp_staking::SessionIndex;
 use staking_handler::{LockComparator, StakingManager};
@@ -311,6 +311,9 @@ parameter_types! {
     pub const ThreadTitleLengthLimit: u32 = 200;
     pub const PostLengthLimit: u32 = 2000;
     pub const MaxWhiteListSize: u32 = 20;
+    pub const PostLifeTime: u64 = 10;
+    pub const PostDeposit: u64 = 100;
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:propo");
 }
 
 pub struct MockProposalsDiscussionWeight;
@@ -323,6 +326,9 @@ impl proposals_discussion::Trait for Test {
     type PostId = u64;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = MockProposalsDiscussionWeight;
+    type PostLifeTime = PostLifeTime;
+    type PostDeposit = PostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
 }
 
 impl proposals_discussion::WeightInfo for MockProposalsDiscussionWeight {

+ 2 - 5
runtime-modules/proposals/discussion/Cargo.toml

@@ -11,21 +11,19 @@ sp-std = { package = 'sp-std', default-features = false, git = 'https://github.c
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 common = { package = 'pallet-common', default-features = false, path = '../../common'}
+balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
 # Benchmarking dependencies
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership', optional = true}
 council = { package = 'pallet-council', default-features = false, path = '../../council', optional = true}
 referendum = { package = 'pallet-referendum', default-features = false, path = '../../referendum', optional = true}
 
-
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 pallet-timestamp = { package = 'pallet-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 membership = { package = 'pallet-membership', default-features = false, path = '../../membership'}
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../../staking-handler'}
 council = { package = 'pallet-council', default-features = false, path = '../../council'}
@@ -35,7 +33,6 @@ referendum = { package = 'pallet-referendum', default-features = false, path = '
 default = ['std']
 runtime-benchmarks = [
     'frame-benchmarking',
-    'balances',
 	'membership',
     'referendum',
     'council',

+ 4 - 2
runtime-modules/proposals/discussion/src/benchmarking.rs

@@ -251,7 +251,8 @@ benchmarks! {
 
         let text = vec![0u8; j.try_into().unwrap()];
 
-    }: _ (RawOrigin::Signed(account_id), caller_member_id, thread_id, text.clone())
+        assert!(Balances::<T>::usable_balance(&account_id) >= T::PostDeposit::get());
+    }: _ (RawOrigin::Signed(account_id), caller_member_id, thread_id, text.clone(), true)
     verify {
         let post_id = T::PostId::from(1);
 
@@ -293,7 +294,8 @@ benchmarks! {
             RawOrigin::Signed(account_id.clone()).into(),
             caller_member_id,
             thread_id,
-            vec![0u8]
+            vec![0u8],
+            true
         ).unwrap();
 
         let post_id = T::PostId::from(1);

+ 139 - 10
runtime-modules/proposals/discussion/src/lib.rs

@@ -54,11 +54,14 @@ mod tests;
 mod types;
 
 use frame_support::dispatch::{DispatchError, DispatchResult};
+use frame_support::sp_runtime::ModuleId;
 use frame_support::sp_runtime::SaturatedConversion;
 use frame_support::traits::Get;
+use frame_support::traits::{Currency, ExistenceRequirement};
 use frame_support::{
     decl_error, decl_event, decl_module, decl_storage, ensure, weights::Weight, Parameter,
 };
+use sp_runtime::traits::{AccountIdConversion, Saturating};
 use sp_std::clone::Clone;
 use sp_std::vec::Vec;
 
@@ -69,6 +72,11 @@ use types::{DiscussionPost, DiscussionThread};
 
 pub use types::ThreadMode;
 
+/// Balance alias for `balances` module.
+pub type BalanceOf<T> = <T as balances::Trait>::Balance;
+
+type Balances<T> = balances::Module<T>;
+
 /// Proposals discussion WeightInfo.
 /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
 pub trait WeightInfo {
@@ -98,6 +106,9 @@ decl_event!(
 
         /// Emits on thread mode change.
         ThreadModeChanged(ThreadId, ThreadMode<MemberId>, MemberId),
+
+        /// Emits on post deleted
+        PostDeleted(MemberId, ThreadId, PostId),
     }
 );
 
@@ -108,7 +119,7 @@ pub trait CouncilMembership<AccountId, MemberId> {
 }
 
 /// 'Proposal discussion' substrate module Trait
-pub trait Trait: frame_system::Trait + common::membership::Trait {
+pub trait Trait: frame_system::Trait + balances::Trait + common::membership::Trait {
     /// Discussion event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
@@ -133,6 +144,15 @@ pub trait Trait: frame_system::Trait + common::membership::Trait {
 
     /// Weight information for extrinsics in this pallet.
     type WeightInfo: WeightInfo;
+
+    /// Fee for creating a post
+    type PostDeposit: Get<Self::Balance>;
+
+    /// The proposal_discussion module Id, used to derive the account Id to hold the thread bounty
+    type ModuleId: Get<ModuleId>;
+
+    /// Maximum number of blocks before a post can be erased by anyone
+    type PostLifeTime: Get<Self::BlockNumber>;
 }
 
 decl_error! {
@@ -155,6 +175,12 @@ decl_error! {
 
         /// Max allowed authors list limit exceeded.
         MaxWhiteListSizeExceeded,
+
+        /// Account has insufficient balance to create a post
+        InsufficientBalanceForPost,
+
+        /// Account can't delete post at the moment
+        CannotDeletePost,
     }
 }
 
@@ -171,7 +197,7 @@ decl_storage! {
         /// Map thread id and post id to corresponding post.
         pub PostThreadIdByPostId:
             double_map hasher(blake2_128_concat) T::ThreadId, hasher(blake2_128_concat) T::PostId =>
-                DiscussionPost<MemberId<T>>;
+                DiscussionPost<MemberId<T>, BalanceOf<T>, T::BlockNumber>;
 
         /// Count of all posts that have been created.
         pub PostCount get(fn post_count): u64;
@@ -203,10 +229,11 @@ decl_module! {
         pub fn add_post(
             origin,
             post_author_id: MemberId<T>,
-            thread_id : T::ThreadId,
-            text : Vec<u8>
+            thread_id: T::ThreadId,
+            text: Vec<u8>,
+            editable: bool
         ) {
-            T::AuthorOriginValidator::ensure_member_controller_account_origin(
+            let account_id = T::AuthorOriginValidator::ensure_member_controller_account_origin(
                 origin.clone(),
                 post_author_id,
             )?;
@@ -215,21 +242,84 @@ decl_module! {
 
             Self::ensure_thread_mode(origin, post_author_id, thread_id)?;
 
+            // Ensure account has enough funds
+            if editable {
+                ensure!(
+                    Balances::<T>::usable_balance(&account_id) >= T::PostDeposit::get(),
+                    Error::<T>::InsufficientBalanceForPost,
+                );
+            }
+
             // mutation
 
+            if editable {
+                Self::transfer_to_state_cleanup_treasury_account(
+                    T::PostDeposit::get(),
+                    thread_id,
+                    &account_id,
+                )?;
+            }
+
             let next_post_count_value = Self::post_count() + 1;
             let new_post_id = next_post_count_value;
+            let post_id = T::PostId::from(new_post_id);
 
-            let new_post = DiscussionPost {
-                author_id: post_author_id,
-            };
+            if editable {
+                let new_post = DiscussionPost {
+                    author_id: post_author_id,
+                    cleanup_pay_off: T::PostDeposit::get(),
+                    last_edited: frame_system::Module::<T>::block_number(),
+                };
+
+                <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
+            }
 
-            let post_id = T::PostId::from(new_post_id);
-            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
             PostCount::put(next_post_count_value);
             Self::deposit_event(RawEvent::PostCreated(post_id, post_author_id, thread_id, text));
        }
 
+        #[weight = 1_000_000] // TODO: Adjust weight
+        pub fn delete_post(
+            origin,
+            deleter_id: MemberId<T>,
+            post_id : T::PostId,
+            thread_id: T::ThreadId,
+        ) {
+            let account_id = T::AuthorOriginValidator::ensure_member_controller_account_origin(
+                origin.clone(),
+                deleter_id,
+            )?;
+
+            ensure!(
+                <PostThreadIdByPostId<T>>::contains_key(thread_id, post_id),
+                Error::<T>::PostDoesntExist
+            );
+
+            T::AuthorOriginValidator::ensure_member_controller_account_origin(
+                origin,
+                deleter_id,
+            )?;
+
+            let post = <PostThreadIdByPostId<T>>::get(thread_id, post_id);
+            if !Self::anyone_can_delete_post(thread_id, post_id) {
+                ensure!(
+                    post.author_id == deleter_id,
+                    Error::<T>::CannotDeletePost
+                );
+            }
+
+            // mutation
+
+            Self::pay_off(
+                thread_id,
+                T::PostDeposit::get(),
+                &account_id,
+            )?;
+
+            <PostThreadIdByPostId<T>>::remove(thread_id, post_id);
+            Self::deposit_event(RawEvent::PostDeleted(deleter_id, thread_id, post_id));
+        }
+
         /// Updates a post with author origin check. Update attempts number is limited.
         ///
         /// <weight>
@@ -261,6 +351,9 @@ decl_module! {
 
             // mutation
 
+            let mut new_post = <PostThreadIdByPostId<T>>::get(thread_id, post_id);
+            new_post.last_edited = frame_system::Module::<T>::block_number();
+            <PostThreadIdByPostId<T>>::insert(thread_id, post_id, new_post);
             Self::deposit_event(RawEvent::PostUpdated(post_id, post_author_id, thread_id, text));
        }
 
@@ -367,6 +460,42 @@ impl<T: Trait> Module<T> {
         <frame_system::Module<T>>::block_number()
     }
 
+    fn anyone_can_delete_post(thread_id: T::ThreadId, post_id: T::PostId) -> bool {
+        let thread_exists = <ThreadById<T>>::contains_key(thread_id);
+        let post = <PostThreadIdByPostId<T>>::get(thread_id, post_id);
+        !thread_exists
+            && frame_system::Module::<T>::block_number().saturating_sub(post.last_edited)
+                >= T::PostLifeTime::get()
+    }
+
+    fn pay_off(
+        thread_id: T::ThreadId,
+        amount: BalanceOf<T>,
+        account_id: &T::AccountId,
+    ) -> DispatchResult {
+        let state_cleanup_treasury_account = T::ModuleId::get().into_sub_account(thread_id);
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            &state_cleanup_treasury_account,
+            account_id,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
+    fn transfer_to_state_cleanup_treasury_account(
+        amount: BalanceOf<T>,
+        thread_id: T::ThreadId,
+        account_id: &T::AccountId,
+    ) -> DispatchResult {
+        let state_cleanup_treasury_account = T::ModuleId::get().into_sub_account(thread_id);
+        <Balances<T> as Currency<T::AccountId>>::transfer(
+            account_id,
+            &state_cleanup_treasury_account,
+            amount,
+            ExistenceRequirement::AllowDeath,
+        )
+    }
+
     fn ensure_thread_mode(
         origin: T::Origin,
         thread_author_id: MemberId<T>,

+ 12 - 2
runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -9,7 +9,7 @@ use sp_core::H256;
 use sp_runtime::{
     testing::Header,
     traits::{BlakeTwo256, IdentityLookup},
-    DispatchResult, Perbill,
+    DispatchResult, ModuleId, Perbill,
 };
 
 use crate::CouncilOriginValidator;
@@ -78,6 +78,9 @@ parameter_types! {
     pub const ReferralCutMaximumPercent: u8 = 50;
     pub const StakingCandidateLockId: [u8; 8] = [3; 8];
     pub const CandidateStake: u64 = 100;
+    pub const PostLifeTime: u64 = 10;
+    pub const PostDeposit: u64 = 100;
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:propo");
 }
 
 // Weights info stub
@@ -222,6 +225,9 @@ impl crate::Trait for Test {
     type PostId = u64;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = ();
+    type PostLifeTime = PostLifeTime;
+    type PostDeposit = PostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
 }
 
 impl WeightInfo for () {
@@ -248,7 +254,11 @@ impl MemberOriginValidator<Origin, u64, u64> for () {
         }
 
         if actor_id < 12 {
-            return Ok(actor_id);
+            if let Ok(account_id) = frame_system::ensure_signed(origin) {
+                return Ok(account_id);
+            } else {
+                return Ok(actor_id);
+            }
         }
 
         if actor_id == 12 && frame_system::ensure_signed(origin).unwrap_or_default() == 12 {

+ 227 - 5
runtime-modules/proposals/discussion/src/tests/mod.rs

@@ -20,7 +20,14 @@ impl EventFixture {
             })
             .collect::<Vec<EventRecord<_, _>>>();
 
-        assert_eq!(System::events(), expected_events);
+        let actual_events: Vec<_> = System::events()
+            .into_iter()
+            .filter(|e| match e.event {
+                TestEvent::discussion(..) => true,
+                _ => false,
+            })
+            .collect();
+        assert_eq!(actual_events, expected_events);
     }
 }
 
@@ -49,7 +56,11 @@ fn assert_thread_content(thread_entry: TestThreadEntry, post_entries: Vec<TestPo
     for post_entry in post_entries {
         let actual_post =
             <PostThreadIdByPostId<Test>>::get(thread_entry.thread_id, post_entry.post_id);
-        let expected_post = DiscussionPost { author_id: 1 };
+        let expected_post = DiscussionPost {
+            author_id: 1,
+            cleanup_pay_off: <Test as Trait>::PostDeposit::get(),
+            last_edited: frame_system::Module::<Test>::block_number(),
+        };
 
         assert_eq!(actual_post, expected_post);
     }
@@ -94,6 +105,9 @@ struct PostFixture {
     pub thread_id: u64,
     pub post_id: Option<u64>,
     pub author_id: u64,
+    pub initial_balance: u64,
+    pub account_id: u64,
+    pub editable: bool,
 }
 
 impl PostFixture {
@@ -104,9 +118,16 @@ impl PostFixture {
             thread_id,
             origin: RawOrigin::Signed(1),
             post_id: None,
+            initial_balance: <Test as Trait>::PostDeposit::get(),
+            account_id: 1,
+            editable: true,
         }
     }
 
+    fn with_account_id(self, account_id: u64) -> Self {
+        PostFixture { account_id, ..self }
+    }
+
     fn with_origin(self, origin: RawOrigin<u64>) -> Self {
         PostFixture { origin, ..self }
     }
@@ -115,6 +136,10 @@ impl PostFixture {
         PostFixture { author_id, ..self }
     }
 
+    fn with_editable(self, editable: bool) -> Self {
+        PostFixture { editable, ..self }
+    }
+
     fn change_thread_id(self, thread_id: u64) -> Self {
         PostFixture { thread_id, ..self }
     }
@@ -126,18 +151,69 @@ impl PostFixture {
         }
     }
 
+    fn with_initial_balance(self, initial_balance: u64) -> Self {
+        PostFixture {
+            initial_balance,
+            ..self
+        }
+    }
+
     fn add_post_and_assert(&mut self, result: DispatchResult) -> Option<u64> {
+        balances::Module::<Test>::make_free_balance_be(&self.account_id, self.initial_balance);
+        let initial_balance = balances::Module::<Test>::usable_balance(&self.account_id);
         let add_post_result = Discussions::add_post(
             self.origin.clone().into(),
             self.author_id,
             self.thread_id,
             self.text.clone(),
+            self.editable,
+        );
+
+        assert_eq!(add_post_result, result);
+
+        if result.is_ok() {
+            let post_id = <PostCount>::get();
+            self.post_id = Some(post_id);
+            if self.editable {
+                assert!(<PostThreadIdByPostId<Test>>::contains_key(
+                    self.thread_id,
+                    post_id
+                ));
+                assert_eq!(
+                    balances::Module::<Test>::usable_balance(&self.account_id),
+                    initial_balance - <Test as Trait>::PostDeposit::get()
+                );
+            } else {
+                assert!(!<PostThreadIdByPostId<Test>>::contains_key(
+                    self.thread_id,
+                    post_id
+                ));
+            }
+        }
+
+        self.post_id
+    }
+
+    fn delete_post_and_assert(&mut self, result: DispatchResult) -> Option<u64> {
+        let initial_balance = balances::Module::<Test>::usable_balance(&self.account_id);
+        let add_post_result = Discussions::delete_post(
+            self.origin.clone().into(),
+            self.author_id,
+            self.post_id.unwrap(),
+            self.thread_id,
         );
 
         assert_eq!(add_post_result, result);
 
         if result.is_ok() {
-            self.post_id = Some(<PostCount>::get());
+            assert_eq!(
+                balances::Module::<Test>::usable_balance(&self.account_id),
+                initial_balance + <Test as Trait>::PostDeposit::get()
+            );
+            assert!(!<PostThreadIdByPostId<Test>>::contains_key(
+                self.thread_id,
+                self.post_id.unwrap()
+            ));
         }
 
         self.post_id
@@ -169,7 +245,7 @@ fn create_discussion_call_succeeds() {
 }
 
 #[test]
-fn create_post_call_succeeds() {
+fn create_post_call_succeeds_editable() {
     initial_test_ext().execute_with(|| {
         let discussion_fixture = DiscussionFixture::default();
 
@@ -177,7 +253,150 @@ fn create_post_call_succeeds() {
             .create_discussion_and_assert(Ok(1))
             .unwrap();
 
-        let mut post_fixture = PostFixture::default_for_thread(thread_id);
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn delete_post_call_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture.delete_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_invalid_user() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture
+            .with_origin(RawOrigin::Signed(12))
+            .with_author(13)
+            .with_account_id(13)
+            .delete_post_and_assert(Err(DispatchError::Other("Invalid author")));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_incorrect_user() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Err(Error::<Test>::CannotDeletePost.into()));
+    });
+}
+
+#[test]
+fn delete_post_call_fails_with_any_user_before_post_lifetime() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+
+        let current_block = frame_system::Module::<Test>::block_number();
+
+        run_to_block(current_block + <Test as Trait>::PostLifeTime::get());
+
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Err(Error::<Test>::CannotDeletePost.into()));
+    });
+}
+
+#[test]
+fn delete_post_call_succeds_with_any_user_after_post_lifetime() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id).with_editable(true);
+
+        post_fixture.add_post_and_assert(Ok(()));
+
+        // Erasing manually to prevent circular dependency with proposal codex
+        <ThreadById<Test>>::remove(thread_id);
+
+        let current_block = frame_system::Module::<Test>::block_number();
+
+        run_to_block(current_block + <Test as Trait>::PostLifeTime::get());
+
+        post_fixture
+            .with_origin(RawOrigin::Signed(10))
+            .with_author(10)
+            .with_account_id(10)
+            .delete_post_and_assert(Ok(()));
+    });
+}
+
+#[test]
+fn create_post_call_fails_editable_insufficient_funds() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id)
+            .with_editable(true)
+            .with_initial_balance(<Test as Trait>::PostDeposit::get() - 1);
+
+        post_fixture.add_post_and_assert(Err(Error::<Test>::InsufficientBalanceForPost.into()));
+    });
+}
+
+#[test]
+fn create_post_call_succeeds_non_editable() {
+    initial_test_ext().execute_with(|| {
+        let discussion_fixture = DiscussionFixture::default();
+
+        let thread_id = discussion_fixture
+            .create_discussion_and_assert(Ok(1))
+            .unwrap();
+
+        let mut post_fixture = PostFixture::default_for_thread(thread_id)
+            .with_editable(false)
+            .with_initial_balance(0);
 
         post_fixture.add_post_and_assert(Ok(()));
     });
@@ -264,6 +483,7 @@ fn update_post_call_fails_because_of_the_wrong_author() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(12))
+            .with_account_id(12)
             .with_author(12);
 
         post_fixture.add_post_and_assert(Ok(()));
@@ -489,6 +709,7 @@ fn create_post_call_succeeds_with_closed_mode_by_councilor() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(2))
+            .with_account_id(2)
             .with_author(2);
 
         post_fixture.add_post_and_assert(Ok(()));
@@ -507,6 +728,7 @@ fn create_post_call_succeeds_with_closed_mode_by_white_listed_member() {
 
         let mut post_fixture = PostFixture::default_for_thread(thread_id)
             .with_origin(RawOrigin::Signed(11))
+            .with_account_id(11)
             .with_author(11);
 
         post_fixture.add_post_and_assert(Ok(()));

+ 7 - 1
runtime-modules/proposals/discussion/src/types.rs

@@ -22,9 +22,15 @@ pub struct DiscussionThread<ThreadAuthorId, BlockNumber, MemberId> {
 /// Post for the discussion thread
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
-pub struct DiscussionPost<PostAuthorId> {
+pub struct DiscussionPost<PostAuthorId, Balance, BlockNumber> {
     /// Author of the post.
     pub author_id: PostAuthorId,
+
+    /// Cleanup pay off
+    pub cleanup_pay_off: Balance,
+
+    /// Last time post was created/edited
+    pub last_edited: BlockNumber,
 }
 
 /// Discussion thread permission modes.

+ 7 - 0
runtime/src/lib.rs

@@ -836,6 +836,10 @@ impl Default for Call {
 
 parameter_types! {
     pub const MaxWhiteListSize: u32 = 20;
+    pub const ProposalsPostDeposit: Balance = 2000;
+    // module : proposals_discussion
+    pub const ProposalsDiscussionModuleId: ModuleId = ModuleId(*b"mo:propo");
+    pub const ForumPostLifeTime: BlockNumber = 3600;
 }
 
 macro_rules! call_wg {
@@ -857,6 +861,9 @@ impl proposals_discussion::Trait for Runtime {
     type PostId = PostId;
     type MaxWhiteListSize = MaxWhiteListSize;
     type WeightInfo = weights::proposals_discussion::WeightInfo;
+    type PostDeposit = ProposalsPostDeposit;
+    type ModuleId = ProposalsDiscussionModuleId;
+    type PostLifeTime = ForumPostLifeTime;
 }
 
 impl joystream_utility::Trait for Runtime {