Browse Source

Merge pull request #301 from shamil-gadelshin/proposal_execution_fixes

Proposal execution fixes
Bedeho Mender 4 years ago
parent
commit
d0febc080f

+ 1 - 1
runtime-modules/governance/src/council.rs

@@ -136,7 +136,7 @@ decl_module! {
 
         /// Sets the capacity of the the council mint, if it doesn't exist, attempts to
         /// create a new one.
-        fn set_council_mint_capacity(origin, capacity: minting::BalanceOf<T>) {
+        pub fn set_council_mint_capacity(origin, capacity: minting::BalanceOf<T>) {
             ensure_root(origin)?;
 
             if let Some(mint_id) = Self::council_mint() {

+ 6 - 6
runtime-modules/proposals/codex/Cargo.toml

@@ -91,12 +91,6 @@ git = 'https://github.com/paritytech/substrate.git'
 package = 'srml-staking'
 rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
-[dependencies.runtime-io]
-default_features = false
-git = 'https://github.com/paritytech/substrate.git'
-package = 'sr-io'
-rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
-
 [dependencies.stake]
 default_features = false
 package = 'substrate-stake-module'
@@ -173,6 +167,12 @@ git = 'https://github.com/paritytech/substrate.git'
 package = 'sr-staking-primitives'
 rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
 
+[dev-dependencies.runtime-io]
+default_features = false
+git = 'https://github.com/paritytech/substrate.git'
+package = 'sr-io'
+rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'
+
 # don't rename the dependency it is causing some strange compiler error:
 # https://github.com/rust-lang/rust/issues/64450
 [dev-dependencies.srml-staking-reward-curve]

+ 54 - 70
runtime-modules/proposals/codex/src/lib.rs

@@ -34,6 +34,9 @@
 //! - [governance](../substrate_governance_module/index.html)
 //! - [content_working_group](../substrate_content_working_group_module/index.html)
 //!
+//! ### Notes
+//! The module uses [ProposalEncoder](./trait.ProposalEncoder.html) to encode the proposal using
+//! its details. Encoded byte vector is passed to the _proposals engine_ as serialized executable code.
 
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
@@ -42,20 +45,19 @@
 // #![warn(missing_docs)]
 
 mod proposal_types;
+
 #[cfg(test)]
 mod tests;
 
-use codec::Encode;
 use common::origin_validator::ActorOriginValidator;
 use governance::election_params::ElectionParameters;
 use proposal_engine::ProposalParameters;
-use roles::actors::{Role, RoleParameters};
+use roles::actors::RoleParameters;
 use rstd::clone::Clone;
 use rstd::convert::TryInto;
 use rstd::prelude::*;
 use rstd::str::from_utf8;
 use rstd::vec::Vec;
-use runtime_io::blake2_256;
 use sr_primitives::traits::SaturatedConversion;
 use sr_primitives::traits::{One, Zero};
 use sr_primitives::Perbill;
@@ -64,7 +66,7 @@ use srml_support::traits::{Currency, Get};
 use srml_support::{decl_error, decl_module, decl_storage, ensure, print};
 use system::{ensure_root, RawOrigin};
 
-pub use proposal_types::ProposalDetails;
+pub use proposal_types::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
 
 // Percentage of the total token issue as max mint balance value. Shared with spending
 // proposal max balance percentage.
@@ -93,6 +95,9 @@ pub trait Trait:
         MemberId<Self>,
         Self::AccountId,
     >;
+
+    /// Encodes the proposal usint its details
+    type ProposalEncoder: ProposalEncoder<Self>;
 }
 
 /// Balance alias for `stake` module
@@ -341,8 +346,8 @@ decl_module! {
                 Error::TextProposalSizeExceeded);
 
             let proposal_parameters = proposal_types::parameters::text_proposal::<T>();
-            let proposal_code =
-                <Call<T>>::execute_text_proposal(title.clone(), description.clone(), text.clone());
+            let proposal_details = ProposalDetails::<BalanceOfMint<T>, BalanceOfGovernanceCurrency<T>, T::BlockNumber, T::AccountId, MemberId<T>>::Text(text);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -350,9 +355,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::Text(text),
+                proposal_details,
             )?;
         }
 
@@ -370,12 +375,9 @@ decl_module! {
             ensure!(wasm.len() as u32 <= T::RuntimeUpgradeWasmProposalMaxLength::get(),
                 Error::RuntimeProposalSizeExceeded);
 
-            let wasm_hash = blake2_256(&wasm);
-
-            let proposal_code =
-                <Call<T>>::execute_runtime_upgrade_proposal(title.clone(), description.clone(), wasm);
-
             let proposal_parameters = proposal_types::parameters::runtime_upgrade_proposal::<T>();
+            let proposal_details = ProposalDetails::RuntimeUpgrade(wasm);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -383,9 +385,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::RuntimeUpgrade(wasm_hash.to_vec()),
+                proposal_details,
             )?;
         }
 
@@ -403,9 +405,8 @@ decl_module! {
 
             Self::ensure_council_election_parameters_valid(&election_parameters)?;
 
-            let proposal_code =
-                <governance::election::Call<T>>::set_election_parameters(election_parameters.clone());
-
+            let proposal_details = ProposalDetails::SetElectionParameters(election_parameters);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
             let proposal_parameters =
                 proposal_types::parameters::set_election_parameters_proposal::<T>();
 
@@ -415,9 +416,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::SetElectionParameters(election_parameters),
+                proposal_details,
             )?;
         }
 
@@ -440,11 +441,10 @@ decl_module! {
                 Error::InvalidStorageWorkingGroupMintCapacity
             );
 
-            let proposal_code =
-                <content_working_group::Call<T>>::set_mint_capacity(mint_balance.clone());
-
             let proposal_parameters =
                 proposal_types::parameters::set_content_working_group_mint_capacity_proposal::<T>();
+            let proposal_details = ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -452,9 +452,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance),
+                proposal_details,
             )?;
         }
 
@@ -483,13 +483,10 @@ decl_module! {
                 Error::InvalidSpendingProposalBalance
             );
 
-            let proposal_code = <governance::council::Call<T>>::spend_from_council_mint(
-                balance.clone(),
-                destination.clone()
-            );
-
             let proposal_parameters =
                 proposal_types::parameters::spending_proposal::<T>();
+            let proposal_details = ProposalDetails::Spending(balance, destination);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -497,13 +494,12 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::Spending(balance, destination),
+                proposal_details,
             )?;
         }
 
-
         /// Create 'Set lead' proposal type.
         /// This proposal uses `replace_lead()` extrinsic from the `content_working_group`  module.
         pub fn create_set_lead_proposal(
@@ -522,11 +518,10 @@ decl_module! {
                 );
             }
 
-            let proposal_code =
-                <content_working_group::Call<T>>::replace_lead(new_lead.clone());
-
             let proposal_parameters =
                 proposal_types::parameters::set_lead_proposal::<T>();
+            let proposal_details = ProposalDetails::SetLead(new_lead);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -534,9 +529,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::SetLead(new_lead),
+                proposal_details,
             )?;
         }
 
@@ -550,11 +545,10 @@ decl_module! {
             stake_balance: Option<BalanceOf<T>>,
             actor_account: T::AccountId,
         ) {
-            let proposal_code =
-                <roles::actors::Call<T>>::remove_actor(actor_account.clone());
-
             let proposal_parameters =
                 proposal_types::parameters::evict_storage_provider_proposal::<T>();
+            let proposal_details = ProposalDetails::EvictStorageProvider(actor_account);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -562,9 +556,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::EvictStorageProvider(actor_account),
+                proposal_details,
             )?;
         }
 
@@ -588,11 +582,10 @@ decl_module! {
                 Error::InvalidValidatorCount
             );
 
-            let proposal_code =
-                <staking::Call<T>>::set_validator_count(new_validator_count);
-
             let proposal_parameters =
                 proposal_types::parameters::set_validator_count_proposal::<T>();
+            let proposal_details = ProposalDetails::SetValidatorCount(new_validator_count);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -600,9 +593,9 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::SetValidatorCount(new_validator_count),
+                proposal_details,
             )?;
         }
 
@@ -618,13 +611,10 @@ decl_module! {
         ) {
             Self::ensure_storage_role_parameters_valid(&role_parameters)?;
 
-            let proposal_code = <roles::actors::Call<T>>::set_role_parameters(
-                Role::StorageProvider,
-                role_parameters.clone()
-            );
-
             let proposal_parameters =
                 proposal_types::parameters::set_storage_role_parameters_proposal::<T>();
+            let proposal_details =  ProposalDetails::SetStorageRoleParameters(role_parameters);
+            let proposal_code = T::ProposalEncoder::encode_proposal(proposal_details.clone());
 
             Self::create_proposal(
                 origin,
@@ -632,47 +622,41 @@ decl_module! {
                 title,
                 description,
                 stake_balance,
-                proposal_code.encode(),
+                proposal_code,
                 proposal_parameters,
-                ProposalDetails::SetStorageRoleParameters(role_parameters),
+                proposal_details,
             )?;
         }
 
 // *************** Extrinsic to execute
 
         /// Text proposal extrinsic. Should be used as callable object to pass to the `engine` module.
-        fn execute_text_proposal(
+        pub fn execute_text_proposal(
             origin,
-            title: Vec<u8>,
-            _description: Vec<u8>,
-            _text: Vec<u8>,
+            text: Vec<u8>,
         ) {
             ensure_root(origin)?;
             print("Text proposal: ");
-            let title_string_result = from_utf8(title.as_slice());
-            if let Ok(title_string) = title_string_result{
-                print(title_string);
+            let text_string_result = from_utf8(text.as_slice());
+            if let Ok(text_string) = text_string_result{
+                print(text_string);
             }
         }
 
         /// Runtime upgrade proposal extrinsic.
         /// Should be used as callable object to pass to the `engine` module.
-        fn execute_runtime_upgrade_proposal(
+        pub fn execute_runtime_upgrade_proposal(
             origin,
-            title: Vec<u8>,
-            _description: Vec<u8>,
             wasm: Vec<u8>,
         ) {
             let (cloned_origin1, cloned_origin2) =  Self::double_origin(origin);
             ensure_root(cloned_origin1)?;
 
-            print("Runtime upgrade proposal: ");
-            let title_string_result = from_utf8(title.as_slice());
-            if let Ok(title_string) = title_string_result{
-                print(title_string);
-            }
+            print("Runtime upgrade proposal execution started.");
 
             <system::Module<T>>::set_code(cloned_origin2, wasm)?;
+
+            print("Runtime upgrade proposal execution finished.");
         }
     }
 }

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

@@ -8,6 +8,21 @@ use serde::{Deserialize, Serialize};
 use crate::ElectionParameters;
 use roles::actors::RoleParameters;
 
+/// Encodes proposal using its details information.
+pub trait ProposalEncoder<T: crate::Trait> {
+    /// Encodes proposal using its details information.
+    fn encode_proposal(proposal_details: ProposalDetailsOf<T>) -> Vec<u8>;
+}
+
+/// _ProposalDetails_ alias for type simplification
+pub type ProposalDetailsOf<T> = ProposalDetails<
+    crate::BalanceOfMint<T>,
+    crate::BalanceOfGovernanceCurrency<T>,
+    <T as system::Trait>::BlockNumber,
+    <T as system::Trait>::AccountId,
+    crate::MemberId<T>,
+>;
+
 /// Proposal details provide voters the information required for the perceived voting.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Clone, PartialEq, Debug)]
@@ -15,7 +30,7 @@ pub enum ProposalDetails<MintedBalance, CurrencyBalance, BlockNumber, AccountId,
     /// The text of the `text` proposal
     Text(Vec<u8>),
 
-    /// The hash of wasm code for the `runtime upgrade` proposal
+    /// The wasm code for the `runtime upgrade` proposal
     RuntimeUpgrade(Vec<u8>),
 
     /// Election parameters for the `set election parameters` proposal

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

@@ -3,6 +3,7 @@
 // TODO: remove after post-Rome substrate upgrade
 #![allow(array_into_iter)]
 
+use crate::{ProposalDetailsOf, ProposalEncoder};
 pub use primitives::{Blake2Hasher, H256};
 use proposal_engine::VotersParameters;
 use sr_primitives::curve::PiecewiseLinear;
@@ -249,6 +250,13 @@ impl crate::Trait for Test {
     type TextProposalMaxLength = TextProposalMaxLength;
     type RuntimeUpgradeWasmProposalMaxLength = RuntimeUpgradeWasmProposalMaxLength;
     type MembershipOriginValidator = ();
+    type ProposalEncoder = ();
+}
+
+impl ProposalEncoder<Test> for () {
+    fn encode_proposal(_proposal_details: ProposalDetailsOf<Test>) -> Vec<u8> {
+        Vec::new()
+    }
 }
 
 impl system::Trait for Test {

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

@@ -8,7 +8,6 @@ use system::RawOrigin;
 use crate::{BalanceOf, Error, ProposalDetails};
 use proposal_engine::ProposalParameters;
 use roles::actors::RoleParameters;
-use runtime_io::blake2_256;
 use srml_support::dispatch::DispatchResult;
 
 pub use mock::*;
@@ -223,7 +222,7 @@ fn create_runtime_upgrade_common_checks_succeed() {
                 )
             },
             proposal_parameters: crate::proposal_types::parameters::runtime_upgrade_proposal::<Test>(),
-            proposal_details: ProposalDetails::RuntimeUpgrade(blake2_256(b"wasm").to_vec()),
+            proposal_details: ProposalDetails::RuntimeUpgrade(b"wasm".to_vec()),
         };
         proposal_fixture.check_all();
     });

+ 2 - 0
runtime/src/integration/proposals/mod.rs

@@ -3,9 +3,11 @@
 mod council_elected_handler;
 mod council_origin_validator;
 mod membership_origin_validator;
+mod proposal_encoder;
 mod staking_events_handler;
 
 pub use council_elected_handler::CouncilElectedHandler;
 pub use council_origin_validator::CouncilManager;
 pub use membership_origin_validator::{MemberId, MembershipOriginValidator};
+pub use proposal_encoder::ExtrinsicProposalEncoder;
 pub use staking_events_handler::StakingEventsHandler;

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

@@ -0,0 +1,51 @@
+use crate::{Call, Runtime};
+use proposals_codex::{ProposalDetails, ProposalDetailsOf, ProposalEncoder};
+use roles::actors::Role;
+
+use codec::Encode;
+use rstd::vec::Vec;
+
+/// _ProposalEncoder_ implementation. It encodes extrinsics with proposal details parameters
+/// using Runtime Call and parity codec.
+pub struct ExtrinsicProposalEncoder;
+impl ProposalEncoder<Runtime> for ExtrinsicProposalEncoder {
+    fn encode_proposal(proposal_details: ProposalDetailsOf<Runtime>) -> Vec<u8> {
+        match proposal_details {
+            ProposalDetails::Text(text) => {
+                Call::ProposalsCodex(proposals_codex::Call::execute_text_proposal(text)).encode()
+            }
+            ProposalDetails::SetElectionParameters(election_parameters) => Call::CouncilElection(
+                governance::election::Call::set_election_parameters(election_parameters),
+            )
+            .encode(),
+            ProposalDetails::SetContentWorkingGroupMintCapacity(mint_balance) => {
+                Call::ContentWorkingGroup(content_working_group::Call::set_mint_capacity(
+                    mint_balance,
+                ))
+                .encode()
+            }
+            ProposalDetails::Spending(balance, destination) => Call::Council(
+                governance::council::Call::spend_from_council_mint(balance, destination),
+            )
+            .encode(),
+            ProposalDetails::SetLead(new_lead) => {
+                Call::ContentWorkingGroup(content_working_group::Call::replace_lead(new_lead))
+                    .encode()
+            }
+            ProposalDetails::EvictStorageProvider(actor_account) => {
+                Call::Actors(roles::actors::Call::remove_actor(actor_account)).encode()
+            }
+            ProposalDetails::SetValidatorCount(new_validator_count) => {
+                Call::Staking(staking::Call::set_validator_count(new_validator_count)).encode()
+            }
+            ProposalDetails::SetStorageRoleParameters(role_parameters) => Call::Actors(
+                roles::actors::Call::set_role_parameters(Role::StorageProvider, role_parameters),
+            )
+            .encode(),
+            ProposalDetails::RuntimeUpgrade(wasm_code) => Call::ProposalsCodex(
+                proposals_codex::Call::execute_runtime_upgrade_proposal(wasm_code),
+            )
+            .encode(),
+        }
+    }
+}

+ 2 - 1
runtime/src/lib.rs

@@ -59,7 +59,7 @@ pub use srml_support::{
 pub use staking::StakerStatus;
 pub use timestamp::Call as TimestampCall;
 
-use integration::proposals::{CouncilManager, MembershipOriginValidator};
+use integration::proposals::{CouncilManager, ExtrinsicProposalEncoder, MembershipOriginValidator};
 
 /// An index to a block.
 pub type BlockNumber = u32;
@@ -866,6 +866,7 @@ impl proposals_codex::Trait for Runtime {
     type MembershipOriginValidator = MembershipOriginValidator<Self>;
     type TextProposalMaxLength = TextProposalMaxLength;
     type RuntimeUpgradeWasmProposalMaxLength = RuntimeUpgradeWasmProposalMaxLength;
+    type ProposalEncoder = ExtrinsicProposalEncoder;
 }
 
 construct_runtime!(

+ 372 - 10
runtime/src/test/proposals_integration.rs

@@ -2,18 +2,22 @@
 
 #![cfg(test)]
 
-use crate::{ProposalCancellationFee, Runtime};
+use crate::{BlockNumber, ElectionParameters, ProposalCancellationFee, Runtime};
 use codec::Encode;
 use governance::election::CouncilElected;
 use membership::members;
+use membership::role_types::Role;
 use proposals_engine::{
-    ActiveStake, BalanceOf, Error, FinalizationData, Proposal, ProposalDecisionStatus,
-    ProposalParameters, ProposalStatus, VoteKind, VotersParameters, VotingResults,
+    ActiveStake, ApprovedProposalStatus, BalanceOf, Error, FinalizationData, Proposal,
+    ProposalDecisionStatus, ProposalParameters, ProposalStatus, VoteKind, VotersParameters,
+    VotingResults,
 };
-use sr_primitives::traits::DispatchResult;
+use roles::actors::RoleParameters;
+
+use sr_primitives::traits::{DispatchResult, OnFinalize, OnInitialize};
 use sr_primitives::AccountId32;
 use srml_support::traits::Currency;
-use srml_support::StorageLinkedMap;
+use srml_support::{StorageLinkedMap, StorageMap, StorageValue};
 use system::RawOrigin;
 
 use crate::CouncilManager;
@@ -26,9 +30,14 @@ fn initial_test_ext() -> runtime_io::TestExternalities {
     t.into()
 }
 
+type Balances = balances::Module<Runtime>;
+type System = system::Module<Runtime>;
 type Membership = membership::members::Module<Runtime>;
 type ProposalsEngine = proposals_engine::Module<Runtime>;
 type Council = governance::council::Module<Runtime>;
+type Election = governance::election::Module<Runtime>;
+type ProposalCodex = proposals_codex::Module<Runtime>;
+type Mint = minting::Module<Runtime>;
 
 fn setup_members(count: u8) {
     let authority_account_id = <Runtime as system::Trait>::AccountId::default();
@@ -71,6 +80,30 @@ fn setup_council() {
     .is_ok());
 }
 
+pub(crate) fn increase_total_balance_issuance_using_account_id(
+    account_id: AccountId32,
+    balance: u128,
+) {
+    type Balances = balances::Module<Runtime>;
+    let initial_balance = Balances::total_issuance();
+    {
+        let _ = <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, balance);
+    }
+    assert_eq!(Balances::total_issuance(), initial_balance + balance);
+}
+
+// Recommendation from Parity on testing on_finalize
+// https://substrate.dev/docs/en/next/development/module/tests
+fn run_to_block(n: BlockNumber) {
+    while System::block_number() < n {
+        <System as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        <ProposalsEngine as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
+        System::set_block_number(System::block_number() + 1);
+        <System as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+        <ProposalsEngine as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
+    }
+}
+
 struct VoteGenerator {
     proposal_id: u32,
     current_account_id: AccountId32,
@@ -129,11 +162,8 @@ impl Default for DummyProposalFixture {
     fn default() -> Self {
         let title = b"title".to_vec();
         let description = b"description".to_vec();
-        let dummy_proposal = proposals_codex::Call::<Runtime>::execute_text_proposal(
-            title.clone(),
-            description.clone(),
-            b"text".to_vec(),
-        );
+        let dummy_proposal =
+            proposals_codex::Call::<Runtime>::execute_text_proposal(b"text".to_vec());
 
         DummyProposalFixture {
             parameters: ProposalParameters {
@@ -365,3 +395,335 @@ fn proposal_reset_succeeds() {
         assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 0);
     });
 }
+
+struct CodexProposalTestFixture<SuccessfulCall>
+where
+    SuccessfulCall: Fn() -> DispatchResult<proposals_codex::Error>,
+{
+    successful_call: SuccessfulCall,
+    member_id: u64,
+}
+
+impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
+where
+    SuccessfulCall: Fn() -> DispatchResult<proposals_codex::Error>,
+{
+    fn call_extrinsic_and_assert(&self) {
+        setup_members(15);
+        setup_council();
+
+        let account_id: [u8; 32] = [self.member_id as u8; 32];
+        increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
+
+        assert_eq!((self.successful_call)(), Ok(()));
+
+        let proposal_id = 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);
+        vote_generator.vote_and_assert_ok(VoteKind::Approve);
+
+        run_to_block(2);
+
+        let proposal = ProposalsEngine::proposals(proposal_id);
+
+        assert_eq!(
+            proposal,
+            Proposal {
+                status: ProposalStatus::approved(ApprovedProposalStatus::Executed, 1),
+                title: b"title".to_vec(),
+                description: b"body".to_vec(),
+                voting_results: VotingResults {
+                    abstentions: 0,
+                    approvals: 5,
+                    rejections: 0,
+                    slashes: 0,
+                },
+                ..proposal
+            }
+        );
+    }
+}
+
+#[test]
+fn text_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 10;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_text_proposal(
+                    RawOrigin::Signed(account_id.into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    b"text".to_vec(),
+                )
+            },
+        };
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+    });
+}
+
+#[test]
+fn set_lead_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 10;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_set_lead_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    Some((member_id as u64, account_id.into())),
+                )
+            },
+        };
+
+        assert!(content_working_group::Module::<Runtime>::ensure_lead_is_set().is_err());
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert!(content_working_group::Module::<Runtime>::ensure_lead_is_set().is_ok());
+    });
+}
+
+#[test]
+fn spending_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 10;
+        let account_id: [u8; 32] = [member_id; 32];
+        let new_balance = <BalanceOf<Runtime>>::from(5555u32);
+
+        let target_account_id: [u8; 32] = [12; 32];
+
+        assert!(Council::set_council_mint_capacity(RawOrigin::Root.into(), new_balance).is_ok());
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_spending_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    new_balance,
+                    target_account_id.clone().into(),
+                )
+            },
+        };
+
+        assert_eq!(
+            Balances::free_balance::<AccountId32>(target_account_id.clone().into()),
+            0
+        );
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(
+            Balances::free_balance::<AccountId32>(target_account_id.into()),
+            new_balance
+        );
+    });
+}
+
+#[test]
+fn set_content_working_group_mint_capacity_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+        let new_balance = <BalanceOf<Runtime>>::from(55u32);
+
+        let mint_id =
+            Mint::add_mint(0, None).expect("Failed to create a mint for the content working group");
+        <content_working_group::Mint<Runtime>>::put(mint_id);
+
+        assert_eq!(Mint::get_mint_capacity(mint_id), Ok(0));
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_set_content_working_group_mint_capacity_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    new_balance,
+                )
+            },
+        };
+
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(Mint::get_mint_capacity(mint_id), Ok(new_balance));
+    });
+}
+
+#[test]
+fn set_election_parameters_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let election_parameters = ElectionParameters {
+            announcing_period: 14400,
+            voting_period: 14400,
+            revealing_period: 14400,
+            council_size: 4,
+            candidacy_limit: 25,
+            new_term_duration: 14400,
+            min_council_stake: 1,
+            min_voting_stake: 1,
+        };
+        assert_eq!(Election::announcing_period(), 0);
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_set_election_parameters_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(3750u32)),
+                    election_parameters,
+                )
+            },
+        };
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(Election::announcing_period(), 14400);
+    });
+}
+
+#[test]
+fn evict_storage_provider_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let target_member_id = 3;
+        let target_account: [u8; 32] = [target_member_id; 32];
+        let target_account_id: AccountId32 = target_account.into();
+
+        <roles::actors::Parameters<Runtime>>::insert(
+            Role::StorageProvider,
+            roles::actors::RoleParameters::default(),
+        );
+
+        <roles::actors::AccountIdsByRole<Runtime>>::insert(
+            Role::StorageProvider,
+            vec![target_account_id.clone()],
+        );
+
+        <roles::actors::ActorByAccountId<Runtime>>::insert(
+            target_account_id.clone(),
+            roles::actors::Actor {
+                member_id: target_member_id as u64,
+                role: Role::StorageProvider,
+                account: target_account_id,
+                joined_at: 1,
+            },
+        );
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_evict_storage_provider_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(500u32)),
+                    target_account.into(),
+                )
+            },
+        };
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(
+            <roles::actors::AccountIdsByRole<Runtime>>::get(Role::StorageProvider),
+            Vec::new()
+        );
+    });
+}
+
+#[test]
+fn set_storage_role_parameters_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        <roles::actors::Parameters<Runtime>>::insert(
+            Role::StorageProvider,
+            RoleParameters::default(),
+        );
+
+        let target_role_parameters = RoleParameters {
+            startup_grace_period: 700,
+            ..RoleParameters::default()
+        };
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_set_storage_role_parameters_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    target_role_parameters.clone(),
+                )
+            },
+        };
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(
+            <roles::actors::Parameters<Runtime>>::get(Role::StorageProvider),
+            Some(target_role_parameters)
+        );
+    });
+}
+
+#[test]
+fn set_validator_count_proposal_execution_succeeds() {
+    initial_test_ext().execute_with(|| {
+        let member_id = 1;
+        let account_id: [u8; 32] = [member_id; 32];
+
+        let new_validator_count = 8;
+        assert_eq!(<staking::ValidatorCount>::get(), 0);
+
+        let codex_extrinsic_test_fixture = CodexProposalTestFixture {
+            member_id: member_id as u64,
+            successful_call: || {
+                ProposalCodex::create_set_validator_count_proposal(
+                    RawOrigin::Signed(account_id.clone().into()).into(),
+                    member_id as u64,
+                    b"title".to_vec(),
+                    b"body".to_vec(),
+                    Some(<BalanceOf<Runtime>>::from(1250u32)),
+                    new_validator_count,
+                )
+            },
+        };
+        codex_extrinsic_test_fixture.call_extrinsic_and_assert();
+
+        assert_eq!(<staking::ValidatorCount>::get(), new_validator_count);
+    });
+}