Browse Source

Add create_slash_working_group_leader_stake_proposal() to the codex

Shamil Gadelshin 4 years ago
parent
commit
7e33da59a5

+ 4 - 0
node/src/chain_spec.rs

@@ -351,6 +351,10 @@ pub fn testnet_genesis(
                 .decrease_working_group_leader_stake_proposal_voting_period,
             decrease_working_group_leader_stake_proposal_grace_period: cpcp
                 .decrease_working_group_leader_stake_proposal_grace_period,
+            slash_working_group_leader_stake_proposal_voting_period: cpcp
+                .slash_working_group_leader_stake_proposal_voting_period,
+            slash_working_group_leader_stake_proposal_grace_period: cpcp
+                .slash_working_group_leader_stake_proposal_grace_period,
         }),
     }
 }

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

@@ -31,6 +31,7 @@
 //! - [create_fill_working_group_leader_opening_proposal](./struct.Module.html#method.create_fill_working_group_leader_opening_proposal)
 //! - [create_set_working_group_mint_capacity_proposal](./struct.Module.html#method.create_set_working_group_mint_capacity_proposal)
 //! - [create_decrease_working_group_leader_stake_proposal](./struct.Module.html#method.create_decrease_working_group_leader_stake_proposal)
+//! - [create_slash_working_group_leader_stake_proposal](./struct.Module.html#method.create_slash_working_group_leader_stake_proposal)
 //!
 //! ### Proposal implementations of this module
 //! - execute_text_proposal - prints the proposal to the log
@@ -242,7 +243,13 @@ decl_error! {
         InvalidWorkingGroupMintCapacity,
 
         /// Invalid 'set lead proposal' parameter - proposed lead cannot be a councilor
-        InvalidSetLeadParameterCannotBeCouncilor
+        InvalidSetLeadParameterCannotBeCouncilor,
+
+        /// Invalid 'slash stake proposal' parameter - cannot slash by zero balance.
+        SlashingStakeIsZero,
+
+        /// Invalid 'decrease stake proposal' parameter - cannot decrease by zero balance.
+        DecreasingStakeIsZero,
     }
 }
 
@@ -372,6 +379,14 @@ decl_storage! {
         /// Grace period for the 'decrease working group leader stake' proposal
         pub DecreaseWorkingGroupLeaderStakeProposalGracePeriod get(decrease_working_group_leader_stake_proposal_grace_period)
             config(): T::BlockNumber;
+
+        /// Voting period for the 'slash working group leader stake' proposal
+        pub SlashWorkingGroupLeaderStakeProposalVotingPeriod get(slash_working_group_leader_stake_proposal_voting_period)
+            config(): T::BlockNumber;
+
+        /// Grace period for the 'slash working group leader stake' proposal
+        pub SlashWorkingGroupLeaderStakeProposalGracePeriod get(slash_working_group_leader_stake_proposal_grace_period)
+            config(): T::BlockNumber;
     }
 }
 
@@ -730,6 +745,8 @@ decl_module! {
             working_group: WorkingGroup,
         ) {
 
+            ensure!(decreasing_stake != Zero::zero(), Error::DecreasingStakeIsZero);
+
             let proposal_details = ProposalDetails::DecreaseWorkingGroupLeaderStake(
                 worker_id,
                 decreasing_stake,
@@ -749,6 +766,40 @@ decl_module! {
 
             Self::create_proposal(params)?;
         }
+        /// Create 'slash working group leader stake' proposal type.
+        /// This proposal uses `slash_stake()` extrinsic from the `working-group`  module.
+        pub fn create_slash_working_group_leader_stake_proposal(
+            origin,
+            member_id: MemberId<T>,
+            title: Vec<u8>,
+            description: Vec<u8>,
+            stake_balance: Option<BalanceOf<T>>,
+            worker_id: working_group::WorkerId<T>,
+            slashing_stake: BalanceOf<T>,
+            working_group: WorkingGroup,
+        ) {
+
+            ensure!(slashing_stake != Zero::zero(), Error::SlashingStakeIsZero);
+
+            let proposal_details = ProposalDetails::SlashWorkingGroupLeaderStake(
+                worker_id,
+                slashing_stake,
+                working_group
+            );
+
+            let params = CreateProposalParameters{
+                origin,
+                member_id,
+                title,
+                description,
+                stake_balance,
+                proposal_details: proposal_details.clone(),
+                proposal_parameters: proposal_types::parameters::slash_working_group_leader_stake_proposal::<T>(),
+                proposal_code: T::ProposalEncoder::encode_proposal(proposal_details)
+            };
+
+            Self::create_proposal(params)?;
+        }
 
 
 // *************** Extrinsic to execute
@@ -1003,5 +1054,11 @@ impl<T: Trait> Module<T> {
         <DecreaseWorkingGroupLeaderStakeProposalGracePeriod<T>>::put(T::BlockNumber::from(
             p.decrease_working_group_leader_stake_proposal_grace_period,
         ));
+        <SlashWorkingGroupLeaderStakeProposalVotingPeriod<T>>::put(T::BlockNumber::from(
+            p.slash_working_group_leader_stake_proposal_voting_period,
+        ));
+        <SlashWorkingGroupLeaderStakeProposalGracePeriod<T>>::put(T::BlockNumber::from(
+            p.slash_working_group_leader_stake_proposal_grace_period,
+        ));
     } //TODO set defaults for new proposals
 }

+ 11 - 0
runtime-modules/proposals/codex/src/proposal_types/mod.rs

@@ -90,6 +90,9 @@ pub enum ProposalDetails<
 
     /// Balance for the `decrease working group leader stake` proposal
     DecreaseWorkingGroupLeaderStake(WorkerId, StakeBalance, WorkingGroup),
+
+    /// Balance for the `slash working group leader stake` proposal
+    SlashWorkingGroupLeaderStake(WorkerId, StakeBalance, WorkingGroup),
 }
 
 impl<
@@ -266,6 +269,12 @@ pub struct ProposalsConfigParameters {
 
     /// 'Decrease working group leader stake' proposal grace period
     pub decrease_working_group_leader_stake_proposal_grace_period: u32,
+
+    /// 'Slash working group leader stake' proposal voting period
+    pub slash_working_group_leader_stake_proposal_voting_period: u32,
+
+    /// 'Slash working group leader stake' proposal grace period
+    pub slash_working_group_leader_stake_proposal_grace_period: u32,
 }
 
 impl Default for ProposalsConfigParameters {
@@ -295,6 +304,8 @@ impl Default for ProposalsConfigParameters {
             set_working_group_mint_capacity_proposal_grace_period: 0u32,
             decrease_working_group_leader_stake_proposal_voting_period: 43200u32,
             decrease_working_group_leader_stake_proposal_grace_period: 0u32,
+            slash_working_group_leader_stake_proposal_voting_period: 43200u32,
+            slash_working_group_leader_stake_proposal_grace_period: 0u32,
         }
     }
 }

+ 14 - 0
runtime-modules/proposals/codex/src/proposal_types/parameters.rs

@@ -169,3 +169,17 @@ pub(crate) fn decrease_working_group_leader_stake_proposal<T: crate::Trait>(
         required_stake: Some(<BalanceOf<T>>::from(50000u32)),
     }
 }
+
+// Proposal parameters for the 'Slash working group leader stake' proposal
+pub(crate) fn slash_working_group_leader_stake_proposal<T: crate::Trait>(
+) -> ProposalParameters<T::BlockNumber, BalanceOf<T>> {
+    ProposalParameters {
+        voting_period: <Module<T>>::slash_working_group_leader_stake_proposal_voting_period(),
+        grace_period: <Module<T>>::slash_working_group_leader_stake_proposal_grace_period(),
+        approval_quorum_percentage: 60,
+        approval_threshold_percentage: 75,
+        slashing_quorum_percentage: 60,
+        slashing_threshold_percentage: 80,
+        required_stake: Some(<BalanceOf<T>>::from(50000u32)),
+    }
+}

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

@@ -1200,3 +1200,127 @@ fn create_decrease_working_group_leader_stake_proposal_common_checks_succeed() {
         proposal_fixture.check_all();
     });
 }
+
+#[test]
+fn create_slash_working_group_leader_stake_proposal_common_checks_succeed() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance(500000);
+
+        let proposal_fixture = ProposalTestFixture {
+            insufficient_rights_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::None.into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            empty_stake_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    None,
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            invalid_stake_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(5000u32)),
+                    0,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            successful_call: || {
+                ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                    RawOrigin::Signed(1).into(),
+                    1,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Test>>::from(50000u32)),
+                    10,
+                    10,
+                    WorkingGroup::Storage,
+                )
+            },
+            proposal_parameters:
+                crate::proposal_types::parameters::slash_working_group_leader_stake_proposal::<
+                    Test,
+                >(),
+            proposal_details: ProposalDetails::SlashWorkingGroupLeaderStake(
+                10,
+                10,
+                WorkingGroup::Storage,
+            ),
+        };
+        proposal_fixture.check_all();
+    });
+}
+
+#[test]
+fn slash_stake_with_zero_staking_balance_fails() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let lead_account_id = 20;
+        <governance::council::Module<Test>>::set_council(
+            RawOrigin::Root.into(),
+            vec![lead_account_id],
+        )
+        .unwrap();
+
+        assert_eq!(
+            ProposalCodex::create_slash_working_group_leader_stake_proposal(
+                RawOrigin::Signed(1).into(),
+                1,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                10,
+                0,
+                WorkingGroup::Storage,
+            ),
+            Err(Error::SlashingStakeIsZero)
+        );
+    });
+}
+
+#[test]
+fn decrease_stake_with_zero_staking_balance_fails() {
+    initial_test_ext().execute_with(|| {
+        increase_total_balance_issuance_using_account_id(1, 500000);
+
+        let lead_account_id = 20;
+        <governance::council::Module<Test>>::set_council(
+            RawOrigin::Root.into(),
+            vec![lead_account_id],
+        )
+        .unwrap();
+
+        assert_eq!(
+            ProposalCodex::create_decrease_working_group_leader_stake_proposal(
+                RawOrigin::Signed(1).into(),
+                1,
+                b"title".to_vec(),
+                b"body".to_vec(),
+                Some(<BalanceOf<Test>>::from(50000u32)),
+                10,
+                0,
+                WorkingGroup::Storage,
+            ),
+            Err(Error::DecreasingStakeIsZero)
+        );
+    });
+}

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

@@ -105,6 +105,19 @@ impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
                     ),
                 };
 
+                call.encode()
+            }
+            ProposalDetails::SlashWorkingGroupLeaderStake(
+                worker_id,
+                slashing_stake,
+                working_group,
+            ) => {
+                let call = match working_group {
+                    WorkingGroup::Storage => Call::StorageWorkingGroup(
+                        Wg::create_slash_stake_call(worker_id, slashing_stake),
+                    ),
+                };
+
                 call.encode()
             }
         }
@@ -177,4 +190,12 @@ where
     ) -> working_group::Call<T, I> {
         working_group::Call::<T, I>::decrease_stake(worker_id, decreasing_stake)
     }
+
+    // Generic call constructor for the working group  'slash stake'.
+    fn create_slash_stake_call(
+        worker_id: working_group::WorkerId<T>,
+        slashing_stake: working_group::BalanceOf<T>,
+    ) -> working_group::Call<T, I> {
+        working_group::Call::<T, I>::slash_stake(worker_id, slashing_stake)
+    }
 }

+ 98 - 6
runtime/src/tests/proposals_integration/working_group_proposals.rs

@@ -100,6 +100,14 @@ fn fill_opening(
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
 
+fn get_stake_balance(stake: stake::Stake<BlockNumber, Balance, u64>) -> Balance {
+    if let stake::StakingStatus::Staked(stake) = stake.staking_status {
+        return stake.staked_amount;
+    }
+
+    panic!("Not staked.");
+}
+
 fn decrease_stake(
     member_id: u8,
     account_id: [u8; 32],
@@ -125,6 +133,26 @@ fn decrease_stake(
     codex_extrinsic_test_fixture.call_extrinsic_and_assert();
 }
 
+fn slash_stake(member_id: u8, account_id: [u8; 32], leader_worker_id: u64, stake_amount: Balance) {
+    let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
+        ProposalCodex::create_slash_working_group_leader_stake_proposal(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            b"title".to_vec(),
+            b"body".to_vec(),
+            Some(<BalanceOf<Runtime>>::from(50_000_u32)),
+            leader_worker_id,
+            stake_amount,
+            WorkingGroup::Storage,
+        )
+    })
+    .disable_setup_enviroment()
+    .with_expected_proposal_id(4)
+    .with_run_to_block(5);
+
+    codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+}
+
 #[test]
 fn create_add_working_group_leader_opening_proposal_execution_succeeds() {
     initial_test_ext().execute_with(|| {
@@ -283,7 +311,7 @@ fn create_decrease_group_leader_leader_stake_proposal_execution_succeeds() {
 
         fill_opening(member_id, account_id, opening_id, expected_application_id);
 
-        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap().worker_id;
+        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
 
         let stake_id = 1;
         let old_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
@@ -310,10 +338,74 @@ fn create_decrease_group_leader_leader_stake_proposal_execution_succeeds() {
     });
 }
 
-fn get_stake_balance(stake: stake::Stake<BlockNumber, Balance, u64>) -> Balance {
-    if let stake::StakingStatus::Staked(stake) = stake.staking_status {
-        return stake.staked_amount;
-    }
+#[test]
+fn create_slash_group_leader_leader_stake_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+        let stake_amount = 100;
 
-    panic!("Not staked.");
+        let opening_policy_commitment = OpeningPolicyCommitment {
+            role_staking_policy: Some(hiring::StakingPolicy {
+                amount: 100,
+                amount_mode: hiring::StakingAmountLimitMode::AtLeast,
+                crowded_out_unstaking_period_length: None,
+                review_period_expired_unstaking_period_length: None,
+            }),
+            ..OpeningPolicyCommitment::default()
+        };
+
+        let opening_id = add_opening(
+            member_id,
+            account_id.clone(),
+            ActivateOpeningAt::CurrentBlock,
+            Some(opening_policy_commitment),
+        );
+
+        let apply_result = StorageWorkingGroup::apply_on_opening(
+            RawOrigin::Signed(account_id.clone().into()).into(),
+            member_id as u64,
+            opening_id,
+            account_id.clone().into(),
+            Some(stake_amount),
+            None,
+            Vec::new(),
+        );
+
+        assert_eq!(apply_result, Ok(()));
+
+        let expected_application_id = 0;
+
+        begin_review_applications(member_id, account_id, opening_id);
+
+        let lead = StorageWorkingGroup::current_lead();
+        assert!(lead.is_none());
+
+        fill_opening(member_id, account_id, opening_id, expected_application_id);
+
+        let leader_worker_id = StorageWorkingGroup::current_lead().unwrap();
+
+        let stake_id = 1;
+        let old_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let old_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(get_stake_balance(old_stake), stake_amount);
+
+        let slashing_stake_amount = 30;
+        slash_stake(
+            member_id,
+            account_id,
+            leader_worker_id,
+            slashing_stake_amount,
+        );
+
+        let new_balance = Balances::free_balance::<&AccountId32>(&account_id.into());
+        let new_stake = <stake::Module<Runtime>>::stakes(stake_id);
+
+        assert_eq!(
+            get_stake_balance(new_stake),
+            stake_amount - slashing_stake_amount
+        );
+        assert_eq!(new_balance, old_balance);
+    });
 }