mod.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. //! Proposals integration tests - with stake, membership, governance modules.
  2. #![cfg(test)]
  3. #[macro_use]
  4. mod working_group_proposals;
  5. use crate::{BlockNumber, ProposalCancellationFee, Runtime};
  6. use codec::Encode;
  7. use governance::election_params::ElectionParameters;
  8. use membership;
  9. use proposals_engine::{
  10. ActiveStake, ApprovedProposalStatus, BalanceOf, FinalizationData, Proposal,
  11. ProposalDecisionStatus, ProposalParameters, ProposalStatus, VoteKind, VotersParameters,
  12. VotingResults,
  13. };
  14. use frame_support::dispatch::{DispatchError, DispatchResult};
  15. use frame_support::traits::{Currency, OnFinalize, OnInitialize};
  16. use frame_support::{StorageMap, StorageValue};
  17. use sp_runtime::AccountId32;
  18. use system::RawOrigin;
  19. use super::initial_test_ext;
  20. use crate::CouncilManager;
  21. pub type Balances = pallet_balances::Module<Runtime>;
  22. pub type System = system::Module<Runtime>;
  23. pub type Membership = membership::Module<Runtime>;
  24. pub type ProposalsEngine = proposals_engine::Module<Runtime>;
  25. pub type Council = governance::council::Module<Runtime>;
  26. pub type Election = governance::election::Module<Runtime>;
  27. pub type ProposalCodex = proposals_codex::Module<Runtime>;
  28. fn setup_members(count: u8) {
  29. let authority_account_id = <Runtime as system::Trait>::AccountId::default();
  30. Membership::set_screening_authority(RawOrigin::Root.into(), authority_account_id.clone())
  31. .unwrap();
  32. for i in 0..count {
  33. let account_id: [u8; 32] = [i; 32];
  34. Membership::add_screened_member(
  35. RawOrigin::Signed(authority_account_id.clone().into()).into(),
  36. account_id.clone().into(),
  37. Some(account_id.to_vec()),
  38. None,
  39. None,
  40. )
  41. .unwrap();
  42. }
  43. }
  44. fn setup_council() {
  45. let councilor0 = AccountId32::default();
  46. let councilor1: [u8; 32] = [1; 32];
  47. let councilor2: [u8; 32] = [2; 32];
  48. let councilor3: [u8; 32] = [3; 32];
  49. let councilor4: [u8; 32] = [4; 32];
  50. let councilor5: [u8; 32] = [5; 32];
  51. assert!(Council::set_council(
  52. system::RawOrigin::Root.into(),
  53. vec![
  54. councilor0,
  55. councilor1.into(),
  56. councilor2.into(),
  57. councilor3.into(),
  58. councilor4.into(),
  59. councilor5.into()
  60. ]
  61. )
  62. .is_ok());
  63. }
  64. pub(crate) fn increase_total_balance_issuance_using_account_id(
  65. account_id: AccountId32,
  66. balance: u128,
  67. ) {
  68. type Balances = pallet_balances::Module<Runtime>;
  69. let initial_balance = Balances::total_issuance();
  70. {
  71. let _ = <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, balance);
  72. }
  73. assert_eq!(Balances::total_issuance(), initial_balance + balance);
  74. }
  75. // Recommendation from Parity on testing on_finalize
  76. // https://substrate.dev/docs/en/next/development/module/tests
  77. fn run_to_block(n: BlockNumber) {
  78. while System::block_number() < n {
  79. <System as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
  80. <Election as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
  81. <ProposalsEngine as OnFinalize<BlockNumber>>::on_finalize(System::block_number());
  82. System::set_block_number(System::block_number() + 1);
  83. <System as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
  84. <Election as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
  85. <ProposalsEngine as OnInitialize<BlockNumber>>::on_initialize(System::block_number());
  86. }
  87. }
  88. struct VoteGenerator {
  89. proposal_id: u32,
  90. current_account_id: AccountId32,
  91. current_account_id_seed: u8,
  92. current_voter_id: u64,
  93. pub auto_increment_voter_id: bool,
  94. }
  95. impl VoteGenerator {
  96. fn new(proposal_id: u32) -> Self {
  97. VoteGenerator {
  98. proposal_id,
  99. current_voter_id: 0,
  100. current_account_id_seed: 0,
  101. current_account_id: AccountId32::default(),
  102. auto_increment_voter_id: true,
  103. }
  104. }
  105. fn vote_and_assert_ok(&mut self, vote_kind: VoteKind) {
  106. self.vote_and_assert(vote_kind, Ok(()));
  107. }
  108. fn vote_and_assert(&mut self, vote_kind: VoteKind, expected_result: DispatchResult) {
  109. assert_eq!(self.vote(vote_kind.clone()), expected_result);
  110. }
  111. fn vote(&mut self, vote_kind: VoteKind) -> DispatchResult {
  112. if self.auto_increment_voter_id {
  113. self.current_account_id_seed += 1;
  114. self.current_voter_id += 1;
  115. let account_id: [u8; 32] = [self.current_account_id_seed; 32];
  116. self.current_account_id = account_id.into();
  117. }
  118. ProposalsEngine::vote(
  119. system::RawOrigin::Signed(self.current_account_id.clone()).into(),
  120. self.current_voter_id,
  121. self.proposal_id,
  122. vote_kind,
  123. )
  124. }
  125. }
  126. #[derive(Clone)]
  127. struct DummyProposalFixture {
  128. parameters: ProposalParameters<u32, u128>,
  129. account_id: AccountId32,
  130. proposer_id: u64,
  131. proposal_code: Vec<u8>,
  132. title: Vec<u8>,
  133. description: Vec<u8>,
  134. stake_balance: Option<BalanceOf<Runtime>>,
  135. }
  136. impl Default for DummyProposalFixture {
  137. fn default() -> Self {
  138. let title = b"title".to_vec();
  139. let description = b"description".to_vec();
  140. let dummy_proposal =
  141. proposals_codex::Call::<Runtime>::execute_text_proposal(b"text".to_vec());
  142. DummyProposalFixture {
  143. parameters: ProposalParameters {
  144. voting_period: 3,
  145. approval_quorum_percentage: 60,
  146. approval_threshold_percentage: 60,
  147. slashing_quorum_percentage: 60,
  148. slashing_threshold_percentage: 60,
  149. grace_period: 0,
  150. required_stake: None,
  151. },
  152. account_id: <Runtime as system::Trait>::AccountId::default(),
  153. proposer_id: 0,
  154. proposal_code: dummy_proposal.encode(),
  155. title,
  156. description,
  157. stake_balance: None,
  158. }
  159. }
  160. }
  161. impl DummyProposalFixture {
  162. fn with_parameters(self, parameters: ProposalParameters<u32, u128>) -> Self {
  163. DummyProposalFixture { parameters, ..self }
  164. }
  165. fn with_account_id(self, account_id: AccountId32) -> Self {
  166. DummyProposalFixture { account_id, ..self }
  167. }
  168. fn with_voting_period(self, voting_period: u32) -> Self {
  169. DummyProposalFixture {
  170. parameters: ProposalParameters {
  171. voting_period,
  172. ..self.parameters
  173. },
  174. ..self
  175. }
  176. }
  177. fn with_stake(self, stake_balance: BalanceOf<Runtime>) -> Self {
  178. DummyProposalFixture {
  179. stake_balance: Some(stake_balance),
  180. ..self
  181. }
  182. }
  183. fn with_proposer(self, proposer_id: u64) -> Self {
  184. DummyProposalFixture {
  185. proposer_id,
  186. ..self
  187. }
  188. }
  189. fn create_proposal_and_assert(self, result: Result<u32, DispatchError>) -> Option<u32> {
  190. let proposal_id_result = ProposalsEngine::create_proposal(
  191. self.account_id,
  192. self.proposer_id,
  193. self.parameters,
  194. self.title,
  195. self.description,
  196. self.stake_balance,
  197. self.proposal_code,
  198. );
  199. assert_eq!(proposal_id_result, result);
  200. proposal_id_result.ok()
  201. }
  202. }
  203. struct CancelProposalFixture {
  204. origin: RawOrigin<AccountId32>,
  205. proposal_id: u32,
  206. proposer_id: u64,
  207. }
  208. impl CancelProposalFixture {
  209. fn new(proposal_id: u32) -> Self {
  210. let account_id = <Runtime as system::Trait>::AccountId::default();
  211. CancelProposalFixture {
  212. proposal_id,
  213. origin: RawOrigin::Signed(account_id),
  214. proposer_id: 0,
  215. }
  216. }
  217. fn with_proposer(self, proposer_id: u64) -> Self {
  218. CancelProposalFixture {
  219. proposer_id,
  220. ..self
  221. }
  222. }
  223. fn cancel_and_assert(self, expected_result: DispatchResult) {
  224. assert_eq!(
  225. ProposalsEngine::cancel_proposal(
  226. self.origin.into(),
  227. self.proposer_id,
  228. self.proposal_id
  229. ),
  230. expected_result
  231. );
  232. }
  233. }
  234. /// Main purpose of this integration test: check balance of the member on proposal finalization (cancellation)
  235. /// It tests StakingEventsHandler integration. Also, membership module is tested during the proposal creation (ActorOriginValidator).
  236. #[test]
  237. fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
  238. initial_test_ext().execute_with(|| {
  239. let account_id = <Runtime as system::Trait>::AccountId::default();
  240. setup_members(2);
  241. let member_id = 0; // newly created member_id
  242. let stake_amount = 20000u128;
  243. let parameters = ProposalParameters {
  244. voting_period: 3,
  245. approval_quorum_percentage: 50,
  246. approval_threshold_percentage: 60,
  247. slashing_quorum_percentage: 60,
  248. slashing_threshold_percentage: 60,
  249. grace_period: 5,
  250. required_stake: Some(stake_amount),
  251. };
  252. let dummy_proposal = DummyProposalFixture::default()
  253. .with_parameters(parameters)
  254. .with_account_id(account_id.clone())
  255. .with_stake(stake_amount)
  256. .with_proposer(member_id);
  257. let account_balance = 500000;
  258. let _imbalance =
  259. <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, account_balance);
  260. assert_eq!(
  261. <Runtime as stake::Trait>::Currency::total_balance(&account_id),
  262. account_balance
  263. );
  264. let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
  265. assert_eq!(
  266. <Runtime as stake::Trait>::Currency::total_balance(&account_id),
  267. account_balance - stake_amount
  268. );
  269. let mut proposal = ProposalsEngine::proposals(proposal_id);
  270. let mut expected_proposal = Proposal {
  271. parameters,
  272. proposer_id: member_id,
  273. created_at: 0,
  274. status: ProposalStatus::Active(Some(ActiveStake {
  275. stake_id: 0,
  276. source_account_id: account_id.clone(),
  277. })),
  278. title: b"title".to_vec(),
  279. description: b"description".to_vec(),
  280. voting_results: VotingResults::default(),
  281. };
  282. assert_eq!(proposal, expected_proposal);
  283. let cancel_proposal_fixture =
  284. CancelProposalFixture::new(proposal_id).with_proposer(member_id);
  285. cancel_proposal_fixture.cancel_and_assert(Ok(()));
  286. proposal = ProposalsEngine::proposals(proposal_id);
  287. expected_proposal.status = ProposalStatus::Finalized(FinalizationData {
  288. proposal_status: ProposalDecisionStatus::Canceled,
  289. finalized_at: 0,
  290. encoded_unstaking_error_due_to_broken_runtime: None,
  291. stake_data_after_unstaking_error: None,
  292. });
  293. assert_eq!(proposal, expected_proposal);
  294. let cancellation_fee = ProposalCancellationFee::get() as u128;
  295. assert_eq!(
  296. <Runtime as stake::Trait>::Currency::total_balance(&account_id),
  297. account_balance - cancellation_fee
  298. );
  299. });
  300. }
  301. #[test]
  302. fn proposal_reset_succeeds() {
  303. initial_test_ext().execute_with(|| {
  304. setup_members(4);
  305. setup_council();
  306. // create proposal
  307. let dummy_proposal = DummyProposalFixture::default().with_voting_period(100);
  308. let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
  309. // create some votes
  310. let mut vote_generator = VoteGenerator::new(proposal_id);
  311. vote_generator.vote_and_assert_ok(VoteKind::Reject);
  312. vote_generator.vote_and_assert_ok(VoteKind::Abstain);
  313. vote_generator.vote_and_assert_ok(VoteKind::Slash);
  314. assert!(<proposals_engine::ActiveProposalIds<Runtime>>::contains_key(proposal_id));
  315. // check
  316. let proposal = ProposalsEngine::proposals(proposal_id);
  317. assert_eq!(
  318. proposal.voting_results,
  319. VotingResults {
  320. abstentions: 1,
  321. approvals: 0,
  322. rejections: 1,
  323. slashes: 1,
  324. }
  325. );
  326. // Ensure council was elected
  327. assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 6);
  328. let voted_member_id = 2;
  329. // Check for votes.
  330. assert_eq!(
  331. ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
  332. VoteKind::Abstain
  333. );
  334. // Check proposals CouncilElected hook just trigger the election hook (empty council).
  335. //<Runtime as governance::election::Trait>::CouncilElected::council_elected(Vec::new(), 10);
  336. elect_single_councilor();
  337. let updated_proposal = ProposalsEngine::proposals(proposal_id);
  338. assert_eq!(
  339. updated_proposal.voting_results,
  340. VotingResults {
  341. abstentions: 0,
  342. approvals: 0,
  343. rejections: 0,
  344. slashes: 0,
  345. }
  346. );
  347. // No votes could survive cleaning: should be default value.
  348. assert_eq!(
  349. ProposalsEngine::vote_by_proposal_by_voter(proposal_id, voted_member_id),
  350. VoteKind::default()
  351. );
  352. // Check council CouncilElected hook. It should set current council. And we elected single councilor.
  353. assert_eq!(CouncilManager::<Runtime>::total_voters_count(), 1);
  354. });
  355. }
  356. fn elect_single_councilor() {
  357. let res = Election::set_election_parameters(
  358. RawOrigin::Root.into(),
  359. ElectionParameters {
  360. announcing_period: 1,
  361. voting_period: 1,
  362. revealing_period: 1,
  363. council_size: 1,
  364. candidacy_limit: 10,
  365. new_term_duration: 2000000,
  366. min_council_stake: 0,
  367. min_voting_stake: 0,
  368. },
  369. );
  370. assert_eq!(res, Ok(()));
  371. let res = Election::force_start_election(RawOrigin::Root.into());
  372. assert_eq!(res, Ok(()));
  373. let councilor1: [u8; 32] = [1; 32];
  374. increase_total_balance_issuance_using_account_id(councilor1.clone().into(), 1200000000);
  375. let res = Election::apply(RawOrigin::Signed(councilor1.into()).into(), 0);
  376. assert_eq!(res, Ok(()));
  377. run_to_block(5);
  378. }
  379. struct CodexProposalTestFixture<SuccessfulCall>
  380. where
  381. SuccessfulCall: Fn() -> DispatchResult,
  382. {
  383. successful_call: SuccessfulCall,
  384. member_id: u64,
  385. setup_environment: bool,
  386. proposal_id: u32,
  387. run_to_block: u32,
  388. }
  389. impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
  390. where
  391. SuccessfulCall: Fn() -> DispatchResult,
  392. {
  393. fn default_for_call(call: SuccessfulCall) -> Self {
  394. Self {
  395. successful_call: call,
  396. member_id: 1,
  397. setup_environment: true,
  398. proposal_id: 1,
  399. run_to_block: 2,
  400. }
  401. }
  402. fn disable_setup_enviroment(self) -> Self {
  403. Self {
  404. setup_environment: false,
  405. ..self
  406. }
  407. }
  408. fn with_setup_enviroment(self, setup_environment: bool) -> Self {
  409. Self {
  410. setup_environment,
  411. ..self
  412. }
  413. }
  414. fn with_member_id(self, member_id: u64) -> Self {
  415. Self { member_id, ..self }
  416. }
  417. fn with_expected_proposal_id(self, expected_proposal_id: u32) -> Self {
  418. Self {
  419. proposal_id: expected_proposal_id,
  420. ..self
  421. }
  422. }
  423. fn with_run_to_block(self, run_to_block: u32) -> Self {
  424. Self {
  425. run_to_block,
  426. ..self
  427. }
  428. }
  429. }
  430. impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
  431. where
  432. SuccessfulCall: Fn() -> DispatchResult,
  433. {
  434. fn call_extrinsic_and_assert(&self) {
  435. let account_id: [u8; 32] = [self.member_id as u8; 32];
  436. if self.setup_environment {
  437. setup_members(15);
  438. setup_council();
  439. increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
  440. }
  441. assert_eq!((self.successful_call)(), Ok(()));
  442. let mut vote_generator = VoteGenerator::new(self.proposal_id);
  443. vote_generator.vote_and_assert_ok(VoteKind::Approve);
  444. vote_generator.vote_and_assert_ok(VoteKind::Approve);
  445. vote_generator.vote_and_assert_ok(VoteKind::Approve);
  446. vote_generator.vote_and_assert_ok(VoteKind::Approve);
  447. vote_generator.vote_and_assert_ok(VoteKind::Approve);
  448. run_to_block(self.run_to_block);
  449. let proposal = ProposalsEngine::proposals(self.proposal_id);
  450. assert_eq!(
  451. proposal,
  452. Proposal {
  453. status: ProposalStatus::approved(
  454. ApprovedProposalStatus::Executed,
  455. self.run_to_block - 2
  456. ),
  457. title: b"title".to_vec(),
  458. description: b"body".to_vec(),
  459. voting_results: VotingResults {
  460. abstentions: 0,
  461. approvals: 5,
  462. rejections: 0,
  463. slashes: 0,
  464. },
  465. ..proposal
  466. }
  467. );
  468. }
  469. }
  470. #[test]
  471. fn text_proposal_execution_succeeds() {
  472. initial_test_ext().execute_with(|| {
  473. let member_id = 10;
  474. let account_id: [u8; 32] = [member_id; 32];
  475. let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
  476. ProposalCodex::create_text_proposal(
  477. RawOrigin::Signed(account_id.into()).into(),
  478. member_id as u64,
  479. b"title".to_vec(),
  480. b"body".to_vec(),
  481. Some(<BalanceOf<Runtime>>::from(25000u32)),
  482. b"text".to_vec(),
  483. )
  484. })
  485. .with_member_id(member_id as u64);
  486. codex_extrinsic_test_fixture.call_extrinsic_and_assert();
  487. });
  488. }
  489. #[test]
  490. fn spending_proposal_execution_succeeds() {
  491. initial_test_ext().execute_with(|| {
  492. let member_id = 10;
  493. let account_id: [u8; 32] = [member_id; 32];
  494. let new_balance = <BalanceOf<Runtime>>::from(5555u32);
  495. let target_account_id: [u8; 32] = [12; 32];
  496. assert!(Council::set_council_mint_capacity(RawOrigin::Root.into(), new_balance).is_ok());
  497. let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
  498. ProposalCodex::create_spending_proposal(
  499. RawOrigin::Signed(account_id.clone().into()).into(),
  500. member_id as u64,
  501. b"title".to_vec(),
  502. b"body".to_vec(),
  503. Some(<BalanceOf<Runtime>>::from(25_000_u32)),
  504. new_balance,
  505. target_account_id.clone().into(),
  506. )
  507. })
  508. .with_member_id(member_id as u64);
  509. let converted_account_id: AccountId32 = target_account_id.clone().into();
  510. assert_eq!(Balances::free_balance(converted_account_id.clone()), 0);
  511. codex_extrinsic_test_fixture.call_extrinsic_and_assert();
  512. assert_eq!(Balances::free_balance(converted_account_id), new_balance);
  513. });
  514. }
  515. #[test]
  516. fn set_election_parameters_proposal_execution_succeeds() {
  517. initial_test_ext().execute_with(|| {
  518. let member_id = 1;
  519. let account_id: [u8; 32] = [member_id; 32];
  520. let election_parameters = ElectionParameters {
  521. announcing_period: 14400,
  522. voting_period: 14400,
  523. revealing_period: 14400,
  524. council_size: 4,
  525. candidacy_limit: 25,
  526. new_term_duration: 14400,
  527. min_council_stake: 1,
  528. min_voting_stake: 1,
  529. };
  530. assert_eq!(Election::announcing_period(), 0);
  531. let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
  532. ProposalCodex::create_set_election_parameters_proposal(
  533. RawOrigin::Signed(account_id.clone().into()).into(),
  534. member_id as u64,
  535. b"title".to_vec(),
  536. b"body".to_vec(),
  537. Some(<BalanceOf<Runtime>>::from(200_000_u32)),
  538. election_parameters,
  539. )
  540. });
  541. codex_extrinsic_test_fixture.call_extrinsic_and_assert();
  542. assert_eq!(Election::announcing_period(), 14400);
  543. });
  544. }
  545. #[test]
  546. fn set_validator_count_proposal_execution_succeeds() {
  547. initial_test_ext().execute_with(|| {
  548. let member_id = 1;
  549. let account_id: [u8; 32] = [member_id; 32];
  550. let new_validator_count = 8;
  551. assert_eq!(<pallet_staking::ValidatorCount>::get(), 0);
  552. let codex_extrinsic_test_fixture = CodexProposalTestFixture::default_for_call(|| {
  553. ProposalCodex::create_set_validator_count_proposal(
  554. RawOrigin::Signed(account_id.clone().into()).into(),
  555. member_id as u64,
  556. b"title".to_vec(),
  557. b"body".to_vec(),
  558. Some(<BalanceOf<Runtime>>::from(100_000_u32)),
  559. new_validator_count,
  560. )
  561. });
  562. codex_extrinsic_test_fixture.call_extrinsic_and_assert();
  563. assert_eq!(<pallet_staking::ValidatorCount>::get(), new_validator_count);
  564. });
  565. }