diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index 135476d7cb137..ee58dce4f15ed 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_referenda; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - assert_ok, derive_impl, ord_parameter_types, parameter_types, + assert_ok, derive_impl, ord_parameter_types, parameter_types, storage_alias, traits::{ ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, SortedMembers, @@ -29,6 +29,7 @@ use frame_support::{ weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, Hash}, BuildStorage, DispatchResult, Perbill, @@ -118,12 +119,44 @@ impl SortedMembers for OneToFive { fn add(_m: &u64) {} } +#[storage_alias] +pub type TestRandomnessBlockNumberOffset, I: 'static> = + StorageValue, BlockNumberFor>; + +pub struct TestRandomness; + +impl TestRandomness { + pub fn place_candle() { + TestRandomnessBlockNumberOffset::::set(Some(System::block_number())); + } + + fn hash_256_like_for(n: BlockNumberFor) -> [u8; 32] { + let mut encoded = n.encode(); + while encoded.len() < 32 { + encoded.push(0); + } + + encoded.try_into().expect("added bytes until 32") + } +} + +impl frame_support::traits::Randomness> for TestRandomness { + fn random(_: &[u8]) -> (H256, BlockNumberFor) { + let block_number = TestRandomnessBlockNumberOffset::::get() + .unwrap_or(System::block_number().saturating_less_one()); + + let block_hash: H256 = Self::hash_256_like_for(block_number).into(); + + (block_hash, System::block_number()) + } +} + pub struct TestTracksInfo; impl TracksInfo for TestTracksInfo { type Id = u8; type RuntimeOrigin = ::PalletsOrigin; fn tracks() -> &'static [(Self::Id, TrackInfo)] { - static DATA: [(u8, TrackInfo); 2] = [ + static DATA: [(u8, TrackInfo); 3] = [ ( 0u8, TrackInfo { @@ -168,6 +201,28 @@ impl TracksInfo for TestTracksInfo { }, }, ), + ( + 2u8, + TrackInfo { + name: "signed by 100", + max_deciding: 1, + decision_deposit: 1, + prepare_period: 1, + decision_period: 14, + confirm_period: 7, + min_enactment_period: 2, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(50), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + ), ]; &DATA[..] } @@ -176,6 +231,7 @@ impl TracksInfo for TestTracksInfo { match system_origin { frame_system::RawOrigin::Root => Ok(0), frame_system::RawOrigin::None => Ok(1), + frame_system::RawOrigin::Signed(100) => Ok(2), _ => Err(()), } } else { @@ -190,6 +246,7 @@ impl Config for Test { type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type Scheduler = Scheduler; + type Randomness = TestRandomness; type Currency = pallet_balances::Pallet; type SubmitOrigin = frame_system::EnsureSigned; type CancelOrigin = EnsureSignedBy; @@ -215,7 +272,7 @@ impl Default for ExtBuilder { impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; + let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100), (100, 100)]; pallet_balances::GenesisConfig:: { balances } .assimilate_storage(&mut t) .unwrap(); @@ -293,6 +350,11 @@ pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf { ::bound(c).unwrap() } +pub fn transfer_balance_proposal_bounded(value: u64) -> BoundedCallOf { + let c = RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { dest: 42, value }); + ::bound(c).unwrap() +} + #[allow(dead_code)] pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { Referenda::submit( @@ -358,6 +420,10 @@ pub fn deciding_and_failing_since(i: ReferendumIndex) -> u64 { deciding: Some(DecidingStatus { since, confirming: None, .. }), .. }) => since, + ReferendumInfo::Finalizing(ReferendumStatus { + deciding: Some(DecidingStatus { since, confirming: None, .. }), + .. + }) => since, _ => panic!("Not deciding"), } } @@ -422,13 +488,47 @@ pub enum RefState { } impl RefState { + /// Create default referendum: + /// + /// - **Track**: _Root_ (`0`) + /// - **Call**: `Balances::force_set_balance(42, 1)` pub fn create(self) -> ReferendumIndex { + Self::create_with_params( + self, + frame_support::dispatch::RawOrigin::Root.into(), + set_balance_proposal_bounded(1), + ) + } + + /// Create referendum with long duration: + /// + /// - **Track**: _Signed by 100_ (`2`) + /// - **Call**: `Balances::transfer_keep_alive(42, 10)` + pub fn create_long(self) -> ReferendumIndex { + Self::create_with_params( + self, + frame_support::dispatch::RawOrigin::Signed(100).into(), + transfer_balance_proposal_bounded(10), + ) + } + + pub fn create_with_params( + self, + origin: PalletsOriginOf, + call: BoundedCallOf, + ) -> ReferendumIndex { assert_ok!(Referenda::submit( RuntimeOrigin::signed(1), - Box::new(frame_support::dispatch::RawOrigin::Root.into()), - set_balance_proposal_bounded(1), + Box::new(origin.clone()), + call, DispatchTime::At(10), )); + + let track_id = TestTracksInfo::track_for(&origin) + .expect("track should exist as the ref was successfully submitted; qed"); + let tracks = TestTracksInfo::tracks(); + let (_, track) = &tracks[track_id as usize]; + assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(2), 0)); if matches!(self, RefState::Confirming { immediate: true }) { set_tally(0, 100, 0); @@ -442,7 +542,7 @@ impl RefState { run_to(System::block_number() + 1); } if matches!(self, RefState::Confirming { .. }) { - assert_eq!(confirming_until(index), System::block_number() + 2); + assert_eq!(confirming_until(index), System::block_number() + track.confirm_period); } if matches!(self, RefState::Passing) { set_tally(0, 100, 99); diff --git a/substrate/frame/referenda/src/tests.rs b/substrate/frame/referenda/src/tests.rs index 8f51136de0bfd..66ad2eae2d64c 100644 --- a/substrate/frame/referenda/src/tests.rs +++ b/substrate/frame/referenda/src/tests.rs @@ -30,7 +30,7 @@ fn params_should_work() { ExtBuilder::default().build_and_execute(|| { assert_eq!(ReferendumCount::::get(), 0); assert_eq!(Balances::free_balance(42), 0); - assert_eq!(Balances::total_issuance(), 600); + assert_eq!(Balances::total_issuance(), 700); }); } @@ -57,14 +57,14 @@ fn basic_happy_path_works() { set_tally(0, 100, 0); run_to(7); assert_eq!(confirming_until(0), 9); - run_to(9); + run_to(10); // #8: Should be confirmed & ended. - assert_eq!(approved_since(0), 9); + assert_eq!(approved_since(0), 10); assert_ok!(Referenda::refund_decision_deposit(RuntimeOrigin::signed(2), 0)); - run_to(12); + run_to(13); // #9: Should not yet be enacted. assert_eq!(Balances::free_balance(&42), 0); - run_to(13); + run_to(14); // #10: Proposal should be executed. assert_eq!(Balances::free_balance(&42), 1); }); @@ -89,8 +89,8 @@ fn confirm_then_reconfirm_with_elapsed_trigger_works() { set_tally(r, 100, 99); run_to(8); assert_eq!(deciding_and_failing_since(r), 5); - run_to(11); - assert_eq!(approved_since(r), 11); + run_to(12); + assert_eq!(approved_since(r), 12); }); } @@ -103,8 +103,8 @@ fn instaconfirm_then_reconfirm_with_elapsed_trigger_works() { set_tally(r, 100, 99); run_to(7); assert_eq!(deciding_and_failing_since(r), 5); - run_to(11); - assert_eq!(approved_since(r), 11); + run_to(12); + assert_eq!(approved_since(r), 12); }); } @@ -121,8 +121,8 @@ fn instaconfirm_then_reconfirm_with_voting_trigger_works() { set_tally(r, 100, 0); run_to(9); assert_eq!(confirming_until(r), 11); - run_to(11); - assert_eq!(approved_since(r), 11); + run_to(12); + assert_eq!(approved_since(r), 12); }); } @@ -132,21 +132,22 @@ fn voting_should_extend_for_late_confirmation() { let r = Passing.create(); run_to(10); assert_eq!(confirming_until(r), 11); - run_to(11); - assert_eq!(approved_since(r), 11); + run_to(12); + assert_eq!(approved_since(r), 12); }); } #[test] -fn should_instafail_during_extension_confirmation() { +fn should_fails_during_extension_confirmation_if_on_candle() { ExtBuilder::default().build_and_execute(|| { let r = Passing.create(); run_to(10); assert_eq!(confirming_until(r), 11); - // Should insta-fail since it's now past the normal voting time. + TestRandomness::place_candle(); + // Should record failure. I'll fail anyways set_tally(r, 100, 101); - run_to(11); - assert_eq!(rejected_since(r), 11); + run_to(12); + assert_eq!(rejected_since(r), 12); }); } @@ -165,6 +166,41 @@ fn confirming_then_fail_works() { }); } +// TODO: Add a new test to decide using candle method +// Note: use a deterministic (i.e. manually set the seed) +// method to be able to accurately determine which is +// the "winner" block +#[test] +fn confirming_decided_by_candle() { + ExtBuilder::default().build_and_execute(|| { + let r = Passing.create_long(); + // Normally ends at 5 + 4 (voting period) = 9. + assert_eq!(deciding_and_failing_since(r), 2); + run_to(16); + assert_eq!(confirming_until(r), 23); + run_to(19); + TestRandomness::place_candle(); + run_to(20); + set_tally(r, 100, 101); + run_to(24); + assert_eq!(approved_since(r), 24); + }); + + ExtBuilder::default().build_and_execute(|| { + let r = Passing.create_long(); + // Normally ends at 5 + 4 (voting period) = 9. + assert_eq!(deciding_and_failing_since(r), 2); + run_to(16); + assert_eq!(confirming_until(r), 23); + run_to(20); + set_tally(r, 100, 101); + run_to(21); + TestRandomness::place_candle(); + run_to(24); + assert_eq!(rejected_since(r), 24); + }); +} + #[test] fn queueing_works() { ExtBuilder::default().build_and_execute(|| { @@ -231,35 +267,34 @@ fn queueing_works() { println!("{:?}", Vec::<_>::from(TrackQueue::::get(0))); // Let confirmation period end. - run_to(9); + run_to(10); // #4 should have been confirmed. - assert_eq!(approved_since(4), 9); + assert_eq!(approved_since(4), 10); // On to the next block to select the new referendum - run_to(10); + run_to(11); // #1 (the one with the most approvals) should now be being decided. - assert_eq!(deciding_since(1), 10); + assert_eq!(deciding_since(1), 11); // Let it end unsuccessfully. - run_to(14); - assert_eq!(rejected_since(1), 14); + run_to(15); + assert_eq!(rejected_since(1), 15); // Service queue. - run_to(15); + run_to(16); // #2 should now be being decided. It will (barely) pass. - assert_eq!(deciding_and_failing_since(2), 15); + assert_eq!(deciding_and_failing_since(2), 16); // #2 moves into confirming at the last moment with a 50% approval. - run_to(19); - assert_eq!(confirming_until(2), 21); + run_to(20); + assert_eq!(confirming_until(2), 22); // #2 gets approved. - run_to(21); - assert_eq!(approved_since(2), 21); + run_to(23); + assert_eq!(approved_since(2), 23); // The final one has since timed out. - run_to(22); assert_eq!(timed_out_since(3), 22); }); }