123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- //! Proposals integration tests - with stake, membership, governance modules.
- #![cfg(test)]
- #[macro_use]
- mod working_group_proposals;
- use crate::{BlockNumber, ProposalCancellationFee, Runtime};
- use codec::Encode;
- use governance::election_params::ElectionParameters;
- use membership;
- use proposals_engine::{
- ActiveStake, ApprovedProposalStatus, BalanceOf, FinalizationData, Proposal,
- ProposalDecisionStatus, ProposalParameters, ProposalStatus, VoteKind, VotersParameters,
- VotingResults,
- };
- use frame_support::dispatch::{DispatchError, DispatchResult};
- use frame_support::traits::{Currency, OnFinalize, OnInitialize};
- use frame_support::{StorageMap, StorageValue};
- use sp_runtime::AccountId32;
- use system::RawOrigin;
- use super::initial_test_ext;
- use crate::CouncilManager;
- pub type Balances = pallet_balances::Module<Runtime>;
- pub type System = system::Module<Runtime>;
- pub type Membership = membership::Module<Runtime>;
- pub type ProposalsEngine = proposals_engine::Module<Runtime>;
- pub type Council = governance::council::Module<Runtime>;
- pub type Election = governance::election::Module<Runtime>;
- pub type ProposalCodex = proposals_codex::Module<Runtime>;
- fn setup_members(count: u8) {
- let authority_account_id = <Runtime as system::Trait>::AccountId::default();
- Membership::set_screening_authority(RawOrigin::Root.into(), authority_account_id.clone())
- .unwrap();
- for i in 0..count {
- let account_id: [u8; 32] = [i; 32];
- Membership::add_screened_member(
- RawOrigin::Signed(authority_account_id.clone().into()).into(),
- account_id.clone().into(),
- Some(account_id.to_vec()),
- None,
- None,
- )
- .unwrap();
- }
- }
- fn setup_council() {
- let councilor0 = AccountId32::default();
- let councilor1: [u8; 32] = [1; 32];
- let councilor2: [u8; 32] = [2; 32];
- let councilor3: [u8; 32] = [3; 32];
- let councilor4: [u8; 32] = [4; 32];
- let councilor5: [u8; 32] = [5; 32];
- assert!(Council::set_council(
- system::RawOrigin::Root.into(),
- vec![
- councilor0,
- councilor1.into(),
- councilor2.into(),
- councilor3.into(),
- councilor4.into(),
- councilor5.into()
- ]
- )
- .is_ok());
- }
- pub(crate) fn increase_total_balance_issuance_using_account_id(
- account_id: AccountId32,
- balance: u128,
- ) {
- type Balances = pallet_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());
- <Election 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());
- <Election 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,
- current_account_id_seed: u8,
- current_voter_id: u64,
- pub auto_increment_voter_id: bool,
- }
- impl VoteGenerator {
- fn new(proposal_id: u32) -> Self {
- VoteGenerator {
- proposal_id,
- current_voter_id: 0,
- current_account_id_seed: 0,
- current_account_id: AccountId32::default(),
- auto_increment_voter_id: true,
- }
- }
- fn vote_and_assert_ok(&mut self, vote_kind: VoteKind) {
- self.vote_and_assert(vote_kind, Ok(()));
- }
- fn vote_and_assert(&mut self, vote_kind: VoteKind, expected_result: DispatchResult) {
- assert_eq!(self.vote(vote_kind.clone()), expected_result);
- }
- fn vote(&mut self, vote_kind: VoteKind) -> DispatchResult {
- if self.auto_increment_voter_id {
- self.current_account_id_seed += 1;
- self.current_voter_id += 1;
- let account_id: [u8; 32] = [self.current_account_id_seed; 32];
- self.current_account_id = account_id.into();
- }
- ProposalsEngine::vote(
- system::RawOrigin::Signed(self.current_account_id.clone()).into(),
- self.current_voter_id,
- self.proposal_id,
- vote_kind,
- )
- }
- }
- #[derive(Clone)]
- struct DummyProposalFixture {
- parameters: ProposalParameters<u32, u128>,
- account_id: AccountId32,
- proposer_id: u64,
- proposal_code: Vec<u8>,
- title: Vec<u8>,
- description: Vec<u8>,
- stake_balance: Option<BalanceOf<Runtime>>,
- }
- 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(b"text".to_vec());
- DummyProposalFixture {
- parameters: ProposalParameters {
- voting_period: 3,
- approval_quorum_percentage: 60,
- approval_threshold_percentage: 60,
- slashing_quorum_percentage: 60,
- slashing_threshold_percentage: 60,
- grace_period: 0,
- required_stake: None,
- },
- account_id: <Runtime as system::Trait>::AccountId::default(),
- proposer_id: 0,
- proposal_code: dummy_proposal.encode(),
- title,
- description,
- stake_balance: None,
- }
- }
- }
- impl DummyProposalFixture {
- fn with_parameters(self, parameters: ProposalParameters<u32, u128>) -> Self {
- DummyProposalFixture { parameters, ..self }
- }
- fn with_account_id(self, account_id: AccountId32) -> Self {
- DummyProposalFixture { account_id, ..self }
- }
- fn with_voting_period(self, voting_period: u32) -> Self {
- DummyProposalFixture {
- parameters: ProposalParameters {
- voting_period,
- ..self.parameters
- },
- ..self
- }
- }
- fn with_stake(self, stake_balance: BalanceOf<Runtime>) -> Self {
- DummyProposalFixture {
- stake_balance: Some(stake_balance),
- ..self
- }
- }
- fn with_proposer(self, proposer_id: u64) -> Self {
- DummyProposalFixture {
- proposer_id,
- ..self
- }
- }
- fn create_proposal_and_assert(self, result: Result<u32, DispatchError>) -> Option<u32> {
- let proposal_id_result = ProposalsEngine::create_proposal(
- self.account_id,
- self.proposer_id,
- self.parameters,
- self.title,
- self.description,
- self.stake_balance,
- self.proposal_code,
- );
- assert_eq!(proposal_id_result, result);
- proposal_id_result.ok()
- }
- }
- struct CancelProposalFixture {
- origin: RawOrigin<AccountId32>,
- proposal_id: u32,
- proposer_id: u64,
- }
- impl CancelProposalFixture {
- fn new(proposal_id: u32) -> Self {
- let account_id = <Runtime as system::Trait>::AccountId::default();
- CancelProposalFixture {
- proposal_id,
- origin: RawOrigin::Signed(account_id),
- proposer_id: 0,
- }
- }
- fn with_proposer(self, proposer_id: u64) -> Self {
- CancelProposalFixture {
- proposer_id,
- ..self
- }
- }
- fn cancel_and_assert(self, expected_result: DispatchResult) {
- assert_eq!(
- ProposalsEngine::cancel_proposal(
- self.origin.into(),
- self.proposer_id,
- self.proposal_id
- ),
- expected_result
- );
- }
- }
- /// Main purpose of this integration test: check balance of the member on proposal finalization (cancellation)
- /// It tests StakingEventsHandler integration. Also, membership module is tested during the proposal creation (ActorOriginValidator).
- #[test]
- fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
- initial_test_ext().execute_with(|| {
- let account_id = <Runtime as system::Trait>::AccountId::default();
- setup_members(2);
- let member_id = 0; // newly created member_id
- let stake_amount = 20000u128;
- let parameters = ProposalParameters {
- voting_period: 3,
- approval_quorum_percentage: 50,
- approval_threshold_percentage: 60,
- slashing_quorum_percentage: 60,
- slashing_threshold_percentage: 60,
- grace_period: 5,
- required_stake: Some(stake_amount),
- };
- let dummy_proposal = DummyProposalFixture::default()
- .with_parameters(parameters)
- .with_account_id(account_id.clone())
- .with_stake(stake_amount)
- .with_proposer(member_id);
- let account_balance = 500000;
- let _imbalance =
- <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, account_balance);
- assert_eq!(
- <Runtime as stake::Trait>::Currency::total_balance(&account_id),
- account_balance
- );
- let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
- assert_eq!(
- <Runtime as stake::Trait>::Currency::total_balance(&account_id),
- account_balance - stake_amount
- );
- let mut proposal = ProposalsEngine::proposals(proposal_id);
- let mut expected_proposal = Proposal {
- parameters,
- proposer_id: member_id,
- created_at: 0,
- status: ProposalStatus::Active(Some(ActiveStake {
- stake_id: 0,
- source_account_id: account_id.clone(),
- })),
- title: b"title".to_vec(),
- description: b"description".to_vec(),
- voting_results: VotingResults::default(),
- };
- assert_eq!(proposal, expected_proposal);
- let cancel_proposal_fixture =
- CancelProposalFixture::new(proposal_id).with_proposer(member_id);
- cancel_proposal_fixture.cancel_and_assert(Ok(()));
- proposal = ProposalsEngine::proposals(proposal_id);
- expected_proposal.status = ProposalStatus::Finalized(FinalizationData {
- proposal_status: ProposalDecisionStatus::Canceled,
- finalized_at: 0,
- encoded_unstaking_error_due_to_broken_runtime: None,
- stake_data_after_unstaking_error: None,
- });
- assert_eq!(proposal, expected_proposal);
- let cancellation_fee = ProposalCancellationFee::get() as u128;
- assert_eq!(
- <Runtime as stake::Trait>::Currency::total_balance(&account_id),
- account_balance - cancellation_fee
- );
- });
- }
- #[test]
- fn proposal_reset_succeeds() {
- initial_test_ext().execute_with(|| {
- setup_members(4);
- setup_council();
- // create proposal
- let dummy_proposal = DummyProposalFixture::default().with_voting_period(100);
- let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
- // create some votes
- let mut vote_generator = VoteGenerator::new(proposal_id);
- vote_generator.vote_and_assert_ok(VoteKind::Reject);
- vote_generator.vote_and_assert_ok(VoteKind::Abstain);
- vote_generator.vote_and_assert_ok(VoteKind::Slash);
- assert!(<proposals_engine::ActiveProposalIds<Runtime>>::contains_key(proposal_id));
- // check
- let proposal = ProposalsEngine::proposals(proposal_id);
- assert_eq!(
- proposal.voting_results,
- VotingResults {
- abstentions: 1,
- approvals: 0,
- rejections: 1,
- slashes: 1,
- }
- );
- // Ensure council was elected
- assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
- let voted_member_id = 2;
- // Check for votes.
- assert_eq!(
- ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
- VoteKind::Abstain
- );
- // Check proposals CouncilElected hook just trigger the election hook (empty council).
- //<Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
- elect_single_councilor();
- let updated_proposal = ProposalsEngine::proposals(proposal_id);
- assert_eq!(
- updated_proposal.voting_results,
- VotingResults {
- abstentions: 0,
- approvals: 0,
- rejections: 0,
- slashes: 0,
- }
- );
- // No votes could survive cleaning: should be default value.
- assert_eq!(
- ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
- VoteKind::default()
- );
- // Check council CouncilElected hook. It should set current council. And we elected single councilor.
- assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 1);
- });
- }
- fn elect_single_councilor() {
- let res = Election::set_election_parameters(
- RawOrigin::Root.into(),
- ElectionParameters {
- announcing_period: 1,
- voting_period: 1,
- revealing_period: 1,
- council_size: 1,
- candidacy_limit: 10,
- new_term_duration: 2000000,
- min_council_stake: 0,
- min_voting_stake: 0,
- },
- );
- assert_eq!(res, Ok(()));
- let res = Election::force_start_election(RawOrigin::Root.into());
- assert_eq!(res, Ok(()));
- let councilor1: [u8; 32] = [1; 32];
- increase_total_balance_issuance_using_account_id(councilor1.clone().into(), 1200000000);
- let res = Election::apply(RawOrigin::Signed(councilor1.into()).into(), 0);
- assert_eq!(res, Ok(()));
- run_to_block(5);
- }
- struct CodexProposalTestFixture<SuccessfulCall>
- where
- SuccessfulCall: Fn() -> DispatchResult,
- {
- successful_call: SuccessfulCall,
- member_id: u64,
- setup_environment: bool,
- proposal_id: u32,
- run_to_block: u32,
- }
- impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
- where
- SuccessfulCall: Fn() -> DispatchResult,
- {
- fn default_for_call(call: SuccessfulCall) -> Self {
- Self {
- successful_call: call,
- member_id: 1,
- setup_environment: true,
- proposal_id: 1,
- run_to_block: 2,
- }
- }
- fn disable_setup_enviroment(self) -> Self {
- Self {
- setup_environment: false,
- ..self
- }
- }
- fn with_setup_enviroment(self, setup_environment: bool) -> Self {
- Self {
- setup_environment,
- ..self
- }
- }
- fn with_member_id(self, member_id: u64) -> Self {
- Self { member_id, ..self }
- }
- fn with_expected_proposal_id(self, expected_proposal_id: u32) -> Self {
- Self {
- proposal_id: expected_proposal_id,
- ..self
- }
- }
- fn with_run_to_block(self, run_to_block: u32) -> Self {
- Self {
- run_to_block,
- ..self
- }
- }
- }
- impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
- where
- SuccessfulCall: Fn() -> DispatchResult,
- {
- fn call_extrinsic_and_assert(&self) {
- let account_id: [u8; 32] = [self.member_id as u8; 32];
- if self.setup_environment {
- setup_members(15);
- setup_council();
- increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
- }
- assert_eq!((self.successful_call)(), Ok(()));
- let mut vote_generator = VoteGenerator::new(self.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(self.run_to_block);
- let proposal = ProposalsEngine::proposals(self.proposal_id);
- assert_eq!(
- proposal,
- Proposal {
- status: ProposalStatus::approved(
- ApprovedProposalStatus::Executed,
- self.run_to_block - 2
- ),
- 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::default_for_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(25000u32)),
- b"text".to_vec(),
- )
- })
- .with_member_id(member_id as u64);
- codex_extrinsic_test_fixture.call_extrinsic_and_assert();
- });
- }
- #[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::default_for_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(25_000_u32)),
- new_balance,
- target_account_id.clone().into(),
- )
- })
- .with_member_id(member_id as u64);
- let converted_account_id: AccountId32 = target_account_id.clone().into();
- assert_eq!(Balances::free_balance(converted_account_id.clone()), 0);
- codex_extrinsic_test_fixture.call_extrinsic_and_assert();
- assert_eq!(Balances::free_balance(converted_account_id), 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::default_for_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(200_000_u32)),
- election_parameters,
- )
- });
- codex_extrinsic_test_fixture.call_extrinsic_and_assert();
- assert_eq!(Election::announcing_period(), 14400);
- });
- }
- #[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!(<pallet_staking::ValidatorCount>::get(), 0);
- let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_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(100_000_u32)),
- new_validator_count,
- )
- });
- codex_extrinsic_test_fixture.call_extrinsic_and_assert();
- assert_eq!(<pallet_staking::ValidatorCount>::get(), new_validator_count);
- });
- }
|