Browse Source

runtime: proposals: add emergency cancellation

conectado 4 years ago
parent
commit
a6360e4237

+ 29 - 0
runtime-modules/proposals/codex/src/benchmarking.rs

@@ -313,6 +313,28 @@ benchmarks! {
         );
     }
 
+    create_proposal_emergency_proposal_cancellation {
+        let t in ...;
+        let d in ...;
+
+        let (account_id, member_id, general_proposal_paramters) =
+            create_proposal_parameters::<T>(t, d);
+
+        let proposal_details = ProposalDetails::EmergencyProposalCancellation(0.into());
+    }: create_proposal(
+        RawOrigin::Signed(account_id.clone()),
+        general_proposal_paramters.clone(),
+        proposal_details.clone()
+    )
+    verify {
+        create_proposal_verify::<T>(
+            account_id,
+            member_id,
+            general_proposal_paramters,
+            proposal_details
+        );
+    }
+
     create_proposal_create_working_group_lead_opening {
         let i in 1 .. MAX_BYTES;
         let t in ...;
@@ -1131,4 +1153,11 @@ mod tests {
             assert_ok!(test_benchmark_create_proposal_unlock_blog_post::<Test>());
         });
     }
+
+    #[test]
+    fn test_create_proposal_emergency_proposal_cancellation() {
+        initial_test_ext().execute_with(|| {
+            assert_ok!(test_benchmark_create_proposal_emergency_proposal_cancellation::<Test>());
+        });
+    }
 }

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

@@ -113,6 +113,7 @@ pub trait WeightInfo {
     fn create_proposal_edit_blog_post(t: u32, d: u32, h: u32, b: u32) -> Weight;
     fn create_proposal_lock_blog_post(t: u32) -> Weight;
     fn create_proposal_unlock_blog_post() -> Weight;
+    fn create_proposal_emergency_proposal_cancellation(d: u32) -> Weight;
     fn update_working_group_budget_positive_forum() -> Weight;
     fn update_working_group_budget_negative_forum() -> Weight;
     fn update_working_group_budget_positive_storage() -> Weight;
@@ -133,6 +134,7 @@ pub trait Trait:
     + common::Trait
     + council::Trait
     + staking::Trait
+    + proposals_engine::Trait
 {
     /// Proposal Codex module event type.
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
@@ -264,6 +266,11 @@ pub trait Trait:
         ProposalParameters<Self::BlockNumber, BalanceOf<Self>>,
     >;
 
+    /// `Emergency Proposal Cancellation` proposal parameters
+    type EmergencyProposalCancellationProposalParameters: Get<
+        ProposalParameters<Self::BlockNumber, BalanceOf<Self>>,
+    >;
+
     /// Gets the budget of the given WorkingGroup
     fn get_working_group_budget(working_group: WorkingGroup) -> BalanceOf<Self>;
 
@@ -483,6 +490,9 @@ decl_module! {
         const UnlockBlogPostProposalParameters:
             ProposalParameters<T::BlockNumber, BalanceOf<T>> = T::UnlockBlogPostProposalParameters::get();
 
+        const EmergencyProposalCancellationProposalParameters:
+            ProposalParameters<T::BlockNumber, BalanceOf<T>> = T::EmergencyProposalCancellationProposalParameters::get();
+
 
         /// Create a proposal, the type of proposal depends on the `proposal_details` variant
         ///
@@ -770,6 +780,9 @@ impl<T: Trait> Module<T> {
             ProposalDetails::UnlockBlogPost(..) => {
                 // Note: No checks for this proposal for now
             }
+            ProposalDetails::EmergencyProposalCancellation(..) => {
+                // Note: No checks for this proposal for now
+            }
         }
 
         Ok(())
@@ -834,6 +847,9 @@ impl<T: Trait> Module<T> {
             ProposalDetails::EditBlogPost(..) => T::EditBlogPostProoposalParamters::get(),
             ProposalDetails::LockBlogPost(..) => T::LockBlogPostProposalParameters::get(),
             ProposalDetails::UnlockBlogPost(..) => T::UnlockBlogPostProposalParameters::get(),
+            ProposalDetails::EmergencyProposalCancellation(..) => {
+                T::EmergencyProposalCancellationProposalParameters::get()
+            }
         }
     }
 
@@ -1002,6 +1018,12 @@ impl<T: Trait> Module<T> {
             ProposalDetails::UnlockBlogPost(..) => {
                 WeightInfoCodex::<T>::create_proposal_unlock_blog_post().saturated_into()
             }
+            ProposalDetails::EmergencyProposalCancellation(..) => {
+                WeightInfoCodex::<T>::create_proposal_emergency_proposal_cancellation(
+                    description_length.saturated_into(),
+                )
+                .saturated_into()
+            }
         }
     }
 }

+ 8 - 0
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -262,6 +262,10 @@ impl proposals_engine::WeightInfo for MockProposalsEngineWeight {
     fn cancel_active_and_pending_proposals(_: u32) -> u64 {
         0
     }
+
+    fn emergency_proposal_cancellation() -> Weight {
+        0
+    }
 }
 
 impl Default for crate::Call<Test> {
@@ -595,6 +599,7 @@ impl crate::Trait for Test {
     type EditBlogPostProoposalParamters = DefaultProposalParameters;
     type LockBlogPostProposalParameters = DefaultProposalParameters;
     type UnlockBlogPostProposalParameters = DefaultProposalParameters;
+    type EmergencyProposalCancellationProposalParameters = DefaultProposalParameters;
 
     fn get_working_group_budget(working_group: WorkingGroup) -> BalanceOf<Test> {
         call_wg!(working_group<Test>, get_budget)
@@ -872,6 +877,9 @@ impl crate::WeightInfo for () {
     fn create_proposal_unlock_blog_post() -> Weight {
         0
     }
+    fn create_proposal_emergency_proposal_cancellation(_: u32) -> Weight {
+        0
+    }
     fn update_working_group_budget_positive_forum() -> Weight {
         0
     }

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

@@ -674,6 +674,60 @@ fn create_set_max_validator_count_proposal_common_checks_succeed() {
     });
 }
 
+#[test]
+fn create_emergency_proposal_cancellation_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let general_proposal_parameters_no_staking = GeneralProposalParameters::<Test> {
+            member_id: 1,
+            title: b"title".to_vec(),
+            description: b"body".to_vec(),
+            staking_account_id: None,
+            exact_execution_block: None,
+        };
+
+        let general_proposal_parameters = GeneralProposalParameters::<Test> {
+            member_id: 1,
+            title: b"title".to_vec(),
+            description: b"body".to_vec(),
+            staking_account_id: Some(1),
+            exact_execution_block: None,
+        };
+
+        let proposal_details = ProposalDetails::EmergencyProposalCancellation(0);
+
+        let proposal_fixture = ProposalTestFixture {
+            general_proposal_parameters: general_proposal_parameters.clone(),
+            proposal_details: proposal_details.clone(),
+            insufficient_rights_call: || {
+                ProposalCodex::create_proposal(
+                    RawOrigin::None.into(),
+                    general_proposal_parameters_no_staking.clone(),
+                    proposal_details.clone(),
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_proposal(
+                    RawOrigin::Signed(1).into(),
+                    general_proposal_parameters_no_staking.clone(),
+                    proposal_details.clone(),
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_proposal(
+                    RawOrigin::Signed(1).into(),
+                    general_proposal_parameters.clone(),
+                    proposal_details.clone(),
+                )
+            },
+            proposal_parameters:
+                <Test as crate::Trait>::EmergencyProposalCancellationProposalParameters::get(),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
 #[test]
 fn create_create_blog_post_proposal_common_checks_succeed() {
     initial_test_ext().execute_with(|| {

+ 7 - 3
runtime-modules/proposals/codex/src/types.rs

@@ -24,6 +24,7 @@ pub type ProposalDetailsOf<T> = ProposalDetails<
     working_group::WorkerId<T>,
     working_group::OpeningId,
     blog::PostId,
+    <T as proposals_engine::Trait>::ProposalId,
 >;
 
 /// Kind of Balance for `Update Working Group Budget`.
@@ -39,7 +40,7 @@ pub enum BalanceKind {
 /// Proposal details provide voters the information required for the perceived voting.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Clone, PartialEq, Debug, Eq)]
-pub enum ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, PostId> {
+pub enum ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, PostId, ProposalId> {
     /// The signal of the `Signal` proposal
     Signal(Vec<u8>),
 
@@ -116,10 +117,13 @@ pub enum ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, P
 
     /// `Unlock Blog Post` proposal
     UnlockBlogPost(PostId),
+
+    /// `Veto Proposal` proposal
+    EmergencyProposalCancellation(ProposalId),
 }
 
-impl<Balance, BlockNumber, AccountId, WorkerId, OpeningId, ProposalId> Default
-    for ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, ProposalId>
+impl<Balance, BlockNumber, AccountId, WorkerId, OpeningId, PostId, ProposalId> Default
+    for ProposalDetails<Balance, BlockNumber, AccountId, WorkerId, OpeningId, PostId, ProposalId>
 {
     fn default() -> Self {
         ProposalDetails::Signal(b"invalid proposal details".to_vec())

+ 36 - 0
runtime-modules/proposals/engine/src/benchmarking.rs

@@ -431,6 +431,35 @@ benchmarks! {
         );
     }
 
+    emergency_proposal_cancellation {
+        let (account_ids, proposals) = create_multiple_finalized_proposals::<T>(1, 0, VoteKind::Approve,
+            1, 5);
+
+        assert_eq!(account_ids.len(), 1);
+        let proposal_id = *proposals.last().unwrap();
+        let account_id = account_ids.last().unwrap().clone();
+
+        assert_eq!(ProposalsEngine::<T>::active_proposal_count(), 1);
+
+        run_to_block::<T>(System::<T>::block_number() + One::one());
+
+        let _ = Balances::<T>::make_free_balance_be(&account_id, T::Balance::max_value());
+    }: _ (RawOrigin::Root, proposal_id)
+    verify {
+        assert!(!Proposals::<T>::contains_key(proposal_id), "Proposal still in storage");
+
+        assert!(
+            !DispatchableCallCode::<T>::contains_key(proposal_id),
+            "Proposal code still in storage"
+        );
+
+        assert_eq!(ProposalsEngine::<T>::active_proposal_count(), 0, "Proposal still active");
+
+        assert_last_event::<T>(
+            RawEvent::ProposalDecisionMade(proposal_id, ProposalDecision::Canceled).into()
+        );
+    }
+
     veto_proposal {
         let (account_id, _, proposal_id) = create_proposal::<T>(0, 1, 0, 0);
     }: _ (RawOrigin::Root, proposal_id)
@@ -822,4 +851,11 @@ mod tests {
             assert_ok!(test_benchmark_cancel_active_and_pending_proposals::<Test>());
         });
     }
+
+    #[test]
+    fn test_emergency_cancellation_proposal() {
+        initial_test_ext().execute_with(|| {
+            assert_ok!(test_benchmark_emergency_proposal_cancellation::<Test>());
+        });
+    }
 }

+ 24 - 0
runtime-modules/proposals/engine/src/lib.rs

@@ -167,6 +167,7 @@ pub trait WeightInfo {
     fn on_initialize_rejected(i: u32) -> Weight;
     fn on_initialize_slashed(i: u32) -> Weight;
     fn cancel_active_and_pending_proposals(i: u32) -> Weight;
+    fn emergency_proposal_cancellation() -> Weight;
 }
 
 type WeightInfoEngine<T> = <T as Trait>::WeightInfo;
@@ -312,6 +313,9 @@ decl_error! {
         /// Proposal is finalized already
         ProposalFinalized,
 
+        /// Proposal is not pending execution
+        ProposalNotPendingExecution,
+
         /// The proposal have been already voted on
         AlreadyVoted,
 
@@ -540,6 +544,26 @@ decl_module! {
             Self::finalize_proposal(proposal_id, proposal, ProposalDecision::Vetoed);
         }
 
+        /// Cancel a proposal within grace period. Must be root.
+        #[weight = WeightInfoEngine::<T>::emergency_proposal_cancellation()]
+        pub fn emergency_proposal_cancellation(origin, proposal_id: T::ProposalId) {
+            ensure_root(origin)?;
+
+            ensure!(<Proposals<T>>::contains_key(proposal_id), Error::<T>::ProposalNotFound);
+            let proposal = Self::proposals(proposal_id);
+
+            ensure!(
+                proposal.status.is_pending_execution_proposal(),
+                Error::<T>::ProposalNotPendingExecution
+            );
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            Self::finalize_proposal(proposal_id, proposal, ProposalDecision::Canceled);
+        }
+
     }
 }
 

+ 4 - 0
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -347,6 +347,10 @@ impl crate::WeightInfo for () {
     fn cancel_active_and_pending_proposals(_: u32) -> u64 {
         0
     }
+
+    fn emergency_proposal_cancellation() -> Weight {
+        0
+    }
 }
 
 impl ProposalObserver<Test> for () {

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

@@ -231,6 +231,27 @@ impl VetoProposalFixture {
     }
 }
 
+struct EmergencyProposalCancellationFixture {
+    origin: RawOrigin<u64>,
+    proposal_id: u32,
+}
+
+impl EmergencyProposalCancellationFixture {
+    fn new(proposal_id: u32) -> Self {
+        EmergencyProposalCancellationFixture {
+            proposal_id,
+            origin: RawOrigin::Root,
+        }
+    }
+
+    fn cancel_and_assert(self, expected_result: DispatchResult) {
+        assert_eq!(
+            ProposalsEngine::emergency_proposal_cancellation(self.origin.into(), self.proposal_id,),
+            expected_result
+        );
+    }
+}
+
 struct VoteGenerator {
     proposal_id: u32,
     current_account_id: u64,
@@ -765,6 +786,90 @@ fn veto_proposal_fails_with_insufficient_rights() {
     });
 }
 
+#[test]
+fn emergency_proposal_cancellation_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let starting_block = 1;
+        run_to_block_and_finalize(starting_block);
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 0);
+
+        let parameters_fixture = ProposalParametersFixture::default().with_grace_period(10);
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 1);
+
+        let mut vote_generator = VoteGenerator::new(proposal_id);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+
+        run_to_block_and_finalize(6);
+
+        let cancel_proposal = EmergencyProposalCancellationFixture::new(proposal_id);
+        cancel_proposal.cancel_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::ProposalDecisionMade(
+            proposal_id,
+            ProposalDecision::Canceled,
+        ));
+
+        assert!(!<crate::Proposals<Test>>::contains_key(proposal_id));
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 0);
+    });
+}
+
+#[test]
+fn emergency_proposal_cancellation_fails_with_not_existing_proposal() {
+    initial_test_ext().execute_with(|| {
+        let emergency_proposal = EmergencyProposalCancellationFixture::new(2);
+        emergency_proposal.cancel_and_assert(Err(Error::<Test>::ProposalNotFound.into()));
+    });
+}
+
+#[test]
+fn emergency_proposal_cancellation_fails_before_grace_period() {
+    initial_test_ext().execute_with(|| {
+        let starting_block = 1;
+        run_to_block_and_finalize(starting_block);
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 0);
+
+        let parameters_fixture = ProposalParametersFixture::default();
+        let dummy_proposal =
+            DummyProposalFixture::default().with_parameters(parameters_fixture.params());
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 1);
+
+        let cancel_proposal = EmergencyProposalCancellationFixture::new(proposal_id);
+        cancel_proposal.cancel_and_assert(Err(Error::<Test>::ProposalNotPendingExecution.into()));
+
+        assert!(<crate::Proposals<Test>>::contains_key(proposal_id));
+        // internal active proposal counter check
+        assert_eq!(<ActiveProposalCount>::get(), 1);
+    });
+}
+
+#[test]
+fn emergency_proposal_fails_with_insufficient_rights() {
+    initial_test_ext().execute_with(|| {
+        let dummy_proposal = DummyProposalFixture::default();
+        let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
+
+        let veto_proposal = VetoProposalFixture::new(proposal_id).with_origin(RawOrigin::Signed(2));
+        veto_proposal.veto_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
 #[test]
 fn create_proposal_event_emitted() {
     initial_test_ext().execute_with(|| {

+ 3 - 0
runtime/src/integration/proposals/proposal_encoder.rs

@@ -135,6 +135,9 @@ impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
             ProposalDetails::UnlockBlogPost(post_id) => {
                 Call::Blog(blog::Call::unlock_post(post_id))
             }
+            ProposalDetails::EmergencyProposalCancellation(proposal_id) => Call::ProposalsEngine(
+                proposals_engine::Call::emergency_proposal_cancellation(proposal_id),
+            ),
         };
 
         call.encode()

+ 2 - 0
runtime/src/lib.rs

@@ -865,6 +865,8 @@ impl proposals_codex::Trait for Runtime {
     type EditBlogPostProoposalParamters = EditBlogPostProoposalParamters;
     type LockBlogPostProposalParameters = LockBlogPostProposalParameters;
     type UnlockBlogPostProposalParameters = UnlockBlogPostProposalParameters;
+    type EmergencyProposalCancellationProposalParameters =
+        EmergencyProposalCancellationProposalParameters;
     type WeightInfo = weights::proposals_codex::WeightInfo;
     fn get_working_group_budget(working_group: WorkingGroup) -> Balance {
         call_wg!(working_group, get_budget)

+ 16 - 0
runtime/src/proposals_configuration/defaults.rs

@@ -347,3 +347,19 @@ pub(crate) fn unlock_blog_post_proposal() -> ProposalParameters<BlockNumber, Bal
         constitutionality: 1,
     }
 }
+
+// TODO: decide on paramaters
+// Proposal parameters for the 'Emergency Proposal Cancellation' proposal
+pub(crate) fn emergency_proposal_cancellation_proposal() -> ProposalParameters<BlockNumber, Balance>
+{
+    ProposalParameters {
+        voting_period: 10000,
+        grace_period: 0,
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(50_000),
+        constitutionality: 1,
+    }
+}

+ 7 - 1
runtime/src/proposals_configuration/mod.rs

@@ -61,6 +61,8 @@ parameter_types! {
         ALL_PROPOSALS_PARAMETERS.lock_blog_post_proposal;
     pub UnlockBlogPostProposalParameters: ProposalParameters<BlockNumber, Balance> =
         ALL_PROPOSALS_PARAMETERS.unlock_blog_post_proposal;
+    pub EmergencyProposalCancellationProposalParameters: ProposalParameters<BlockNumber, Balance> =
+        ALL_PROPOSALS_PARAMETERS.emergency_proposal_cancellation_proposal;
 }
 
 ///////////
@@ -90,6 +92,7 @@ struct AllProposalsParameters {
     pub edit_blog_post_proposal: ProposalParameters<BlockNumber, Balance>,
     pub lock_blog_post_proposal: ProposalParameters<BlockNumber, Balance>,
     pub unlock_blog_post_proposal: ProposalParameters<BlockNumber, Balance>,
+    pub emergency_proposal_cancellation_proposal: ProposalParameters<BlockNumber, Balance>,
 }
 
 // to initialize parameters only once.
@@ -189,7 +192,8 @@ fn convert_json_object_to_proposal_parameters(
         init_proposal_parameter_object!(params, jo.clone(), create_blog_post_proposal);
         init_proposal_parameter_object!(params, jo.clone(), edit_blog_post_proposal);
         init_proposal_parameter_object!(params, jo.clone(), lock_blog_post_proposal);
-        init_proposal_parameter_object!(params, jo, unlock_blog_post_proposal);
+        init_proposal_parameter_object!(params, jo.clone(), unlock_blog_post_proposal);
+        init_proposal_parameter_object!(params, jo, emergency_proposal_cancellation_proposal);
     }
 
     params
@@ -324,5 +328,7 @@ fn default_parameters() -> AllProposalsParameters {
         edit_blog_post_proposal: defaults::edit_blog_post_proposal(),
         lock_blog_post_proposal: defaults::lock_blog_post_proposal(),
         unlock_blog_post_proposal: defaults::unlock_blog_post_proposal(),
+        emergency_proposal_cancellation_proposal:
+            defaults::emergency_proposal_cancellation_proposal(),
     }
 }

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

@@ -494,10 +494,10 @@ where
                 let lead_account_id = [self.lead_id as u8; 32].into();
                 set_membership_leader(lead_account_id, self.lead_id);
             }
-
-            increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
         }
 
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 1_500_000);
+
         assert_eq!((self.successful_call)(), Ok(()));
 
         // Council size is 3
@@ -727,6 +727,66 @@ fn unlock_blog_post_proposal_execution_succeeds() {
     });
 }
 
+#[test]
+fn emergency_proposal_cancellation_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 10;
+        let account_id: [u8; 32] = [member_id; 32];
+        let council_budget = 5_000_000;
+        assert!(Council::set_budget(RawOrigin::Root.into(), council_budget).is_ok());
+
+        let proposal_id = ProposalsEngine::proposal_count() + 1;
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
+                member_id: member_id.into(),
+                title: b"title".to_vec(),
+                description: b"body".to_vec(),
+                staking_account_id: Some(account_id.into()),
+                exact_execution_block: None,
+            };
+
+            ProposalCodex::create_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                general_proposal_parameters,
+                ProposalDetails::AmendConstitution(vec![0u8]),
+            )
+        })
+        .with_member_id(member_id as u64);
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert!(proposals_engine::Proposals::<Runtime>::contains_key(1));
+
+        let member_id = 14;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+            let general_proposal_parameters = GeneralProposalParameters::<Runtime> {
+                member_id: member_id.into(),
+                title: b"title".to_vec(),
+                description: b"body".to_vec(),
+                staking_account_id: Some(account_id.into()),
+                exact_execution_block: None,
+            };
+
+            ProposalCodex::create_proposal(
+                RawOrigin::Signed(account_id.clone().into()).into(),
+                general_proposal_parameters,
+                ProposalDetails::EmergencyProposalCancellation(proposal_id),
+            )
+        })
+        .with_member_id(member_id as u64)
+        .with_expected_proposal_id(2)
+        .with_setup_enviroment(false);
+
+        assert!(proposals_engine::Proposals::<Runtime>::contains_key(1));
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert!(!proposals_engine::Proposals::<Runtime>::contains_key(1));
+    });
+}
+
 #[test]
 fn set_validator_count_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {

+ 6 - 0
runtime/src/weights/proposals_codex.rs

@@ -173,6 +173,12 @@ impl proposals_codex::WeightInfo for WeightInfo {
             .saturating_add(DbWeight::get().reads(6 as Weight))
             .saturating_add(DbWeight::get().writes(10 as Weight))
     }
+    fn create_proposal_emergency_proposal_cancellation(d: u32) -> Weight {
+        (1_209_486_000 as Weight)
+            .saturating_add((222_000 as Weight).saturating_mul(d as Weight))
+            .saturating_add(DbWeight::get().reads(6 as Weight))
+            .saturating_add(DbWeight::get().writes(9 as Weight))
+    }
     // WARNING! Some components were not used: ["t", "d"]
     fn create_proposal_unlock_blog_post() -> Weight {
         (670_647_000 as Weight)

+ 5 - 0
runtime/src/weights/proposals_engine.rs

@@ -71,4 +71,9 @@ impl proposals_engine::WeightInfo for WeightInfo {
             .saturating_add(DbWeight::get().writes(2 as Weight))
             .saturating_add(DbWeight::get().writes((9 as Weight).saturating_mul(i as Weight)))
     }
+    fn emergency_proposal_cancellation() -> Weight {
+        (390_477_000 as Weight)
+            .saturating_add(DbWeight::get().reads(4 as Weight))
+            .saturating_add(DbWeight::get().writes(8 as Weight))
+    }
 }