Browse Source

runtime: bounty: Add veto_bounty extrinsic.

Shamil Gadelshin 4 years ago
parent
commit
8e12b91c8a

+ 28 - 8
runtime-modules/bounty/src/benchmarking.rs

@@ -9,7 +9,7 @@ use sp_std::boxed::Box;
 use sp_std::vec;
 use sp_std::vec::Vec;
 
-use crate::{Bounties, Bounty, BountyCreationParameters, Call, Event, Module, Trait};
+use crate::{Bounties, BountyCreationParameters, Call, Event, Module, Trait};
 
 fn assert_last_event<T: Trait>(generic_event: <T as Trait>::Event) {
     let events = System::<T>::events();
@@ -24,7 +24,7 @@ const MAX_BYTES: u32 = 50000;
 benchmarks! {
     _{ }
 
-    create_bounty{
+    create_bounty {
         let i in 1 .. MAX_BYTES;
         let metadata = vec![0u8].repeat(i as usize);
 
@@ -36,17 +36,13 @@ benchmarks! {
 
     }: _ (RawOrigin::Root, params.clone(), metadata)
     verify {
-        let bounty = Bounty::<T>{
-            creation_params: params,
-        };
-
         let bounty_id: T::BountyId = 1u32.into();
 
-        assert_eq!(Module::<T>::bounties(bounty_id), bounty);
+        assert!(Bounties::<T>::contains_key(bounty_id));
         assert_last_event::<T>(Event::<T>::BountyCreated(bounty_id).into());
     }
 
-    cancel_bounty{
+    cancel_bounty {
         let params = BountyCreationParameters::<T>{
             work_period: One::one(),
             judging_period: One::one(),
@@ -62,6 +58,23 @@ benchmarks! {
         assert!(!<Bounties<T>>::contains_key(&bounty_id));
         assert_last_event::<T>(Event::<T>::BountyCanceled(bounty_id).into());
     }
+
+    veto_bounty {
+        let params = BountyCreationParameters::<T>{
+            work_period: One::one(),
+            judging_period: One::one(),
+            ..Default::default()
+        };
+
+        Module::<T>::create_bounty(RawOrigin::Root.into(), params, Vec::new()).unwrap();
+
+        let bounty_id: T::BountyId = Module::<T>::bounty_count().into();
+
+    }: _ (RawOrigin::Root, bounty_id)
+    verify {
+        assert!(!<Bounties<T>>::contains_key(&bounty_id));
+        assert_last_event::<T>(Event::<T>::BountyVetoed(bounty_id).into());
+    }
 }
 
 #[cfg(test)]
@@ -83,4 +96,11 @@ mod tests {
             assert_ok!(test_benchmark_cancel_bounty::<Test>());
         });
     }
+
+    #[test]
+    fn veto_bounty() {
+        build_test_externalities().execute_with(|| {
+            assert_ok!(test_benchmark_veto_bounty::<Test>());
+        });
+    }
 }

+ 59 - 2
runtime-modules/bounty/src/lib.rs

@@ -6,6 +6,7 @@
 //! ### Supported extrinsics:
 //! - [create_bounty](./struct.Module.html#method.create_bounty) - creates a bounty
 //! - [cancel_bounty](./struct.Module.html#method.cancel_bounty) - cancels a bounty
+//! - [veto_bounty](./struct.Module.html#method.veto_bounty) - vetoes a bounty
 
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
@@ -23,8 +24,9 @@ mod benchmarking;
 /// pallet_bounty WeightInfo.
 /// Note: This was auto generated through the benchmark CLI using the `--weight-trait` flag
 pub trait WeightInfo {
-    fn create_bounty() -> Weight;
+    fn create_bounty(i: u32) -> Weight;
     fn cancel_bounty() -> Weight;
+    fn veto_bounty() -> Weight;
 }
 
 type WeightInfoBounty<T> = <T as Trait>::WeightInfo;
@@ -36,6 +38,7 @@ use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, P
 use frame_system::ensure_root;
 use sp_arithmetic::traits::Saturating;
 use sp_arithmetic::traits::Zero;
+use sp_runtime::traits::SaturatedConversion;
 use sp_std::vec::Vec;
 
 use common::council::CouncilBudgetManager;
@@ -207,6 +210,9 @@ decl_event! {
 
         /// A bounty was canceled.
         BountyCanceled(BountyId),
+
+        /// A bounty was vetoed.
+        BountyVetoed(BountyId),
     }
 }
 
@@ -245,7 +251,15 @@ decl_module! {
         fn deposit_event() = default;
 
         /// Creates a bounty. Metadata stored in the transaction log but discarded after that.
-        #[weight = WeightInfoBounty::<T>::create_bounty()]
+        /// <weight>
+        ///
+        /// ## Weight
+        /// `O (W)` where:
+        /// - `W` is the _metadata length.
+        /// - DB:
+        ///    - O(1)
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::create_bounty(_metadata.len().saturated_into())]
         pub fn create_bounty(origin, params: BountyCreationParameters<T>, _metadata: Vec<u8>) {
             Self::ensure_create_bounty_parameters_valid(&origin, &params)?;
 
@@ -273,6 +287,13 @@ decl_module! {
         }
 
         /// Cancels a bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
         #[weight = WeightInfoBounty::<T>::cancel_bounty()]
         pub fn cancel_bounty(origin, creator_member_id: Option<MemberId<T>>, bounty_id: T::BountyId) {
             Self::ensure_cancel_bounty_parameters_valid(&origin, creator_member_id, bounty_id)?;
@@ -286,6 +307,40 @@ decl_module! {
             <Bounties<T>>::remove(bounty_id);
             Self::deposit_event(RawEvent::BountyCanceled(bounty_id));
         }
+
+        /// Vetoes a bounty.
+        /// # <weight>
+        ///
+        /// ## weight
+        /// `O (1)`
+        /// - db:
+        ///    - `O(1)` doesn't depend on the state or parameters
+        /// # </weight>
+        #[weight = WeightInfoBounty::<T>::veto_bounty()]
+        pub fn veto_bounty(origin, bounty_id: T::BountyId) {
+            ensure_root(origin)?;
+
+            ensure!(
+                <Bounties<T>>::contains_key(bounty_id),
+                Error::<T>::BountyDoesntExist
+            );
+
+            let bounty = <Bounties<T>>::get(bounty_id);
+
+            ensure!(
+                matches!(bounty.stage, BountyStage::Funding(_)),
+                Error::<T>::InvalidBountyStage,
+            );
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            // TODO: make payments for submitted work. Change stage to vetoed.
+
+            <Bounties<T>>::remove(bounty_id);
+            Self::deposit_event(RawEvent::BountyVetoed(bounty_id));
+        }
     }
 }
 
@@ -385,12 +440,14 @@ impl<T: Trait> Module<T> {
         params: &BountyCreationParameters<T>,
     ) -> DispatchResult {
         if let Some(member_id) = params.creator_member_id {
+            // Slash a balance from the member controller account.
             let account_id = T::MemberOriginValidator::ensure_member_controller_account_origin(
                 origin, member_id,
             )?;
 
             let _ = balances::Module::<T>::slash(&account_id, params.cherry);
         } else {
+            // Remove a balance from the council budget.
             let budget = T::CouncilBudgetManager::get_budget();
             let new_budget = budget.saturating_sub(params.cherry);
 

+ 32 - 0
runtime-modules/bounty/src/tests/fixtures.rs

@@ -177,3 +177,35 @@ impl CancelBountyFixture {
         }
     }
 }
+
+pub struct VetoBountyFixture {
+    origin: RawOrigin<u64>,
+    bounty_id: u64,
+}
+
+impl VetoBountyFixture {
+    pub fn default() -> Self {
+        Self {
+            origin: RawOrigin::Root,
+            bounty_id: 1,
+        }
+    }
+
+    pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
+        Self { origin, ..self }
+    }
+
+    pub fn with_bounty_id(self, bounty_id: u64) -> Self {
+        Self { bounty_id, ..self }
+    }
+
+    pub fn call_and_assert(&self, expected_result: DispatchResult) {
+        let actual_result = Bounty::veto_bounty(self.origin.clone().into(), self.bounty_id.clone());
+
+        assert_eq!(actual_result, expected_result);
+
+        if actual_result.is_ok() {
+            assert!(!<crate::Bounties<Test>>::contains_key(&self.bounty_id));
+        }
+    }
+}

+ 4 - 1
runtime-modules/bounty/src/tests/mocks.rs

@@ -99,12 +99,15 @@ impl common::council::CouncilBudgetManager<u64> for CouncilBudgetManager {
 }
 
 impl crate::WeightInfo for () {
-    fn create_bounty() -> u64 {
+    fn create_bounty(_: u32) -> u64 {
         0
     }
     fn cancel_bounty() -> u64 {
         0
     }
+    fn veto_bounty() -> u64 {
+        0
+    }
 }
 
 impl common::Trait for Test {

+ 65 - 1
runtime-modules/bounty/src/tests/mod.rs

@@ -11,7 +11,7 @@ use crate::{BountyStage, Error, RawEvent};
 use common::council::CouncilBudgetManager;
 use fixtures::{
     increase_total_balance_issuance_using_account_id, run_to_block, CancelBountyFixture,
-    CreateBountyFixture, EventFixture,
+    CreateBountyFixture, EventFixture, VetoBountyFixture,
 };
 use mocks::{build_test_externalities, Test};
 
@@ -243,3 +243,67 @@ fn cancel_bounty_fails_with_invalid_stage() {
             .call_and_assert(Err(Error::<Test>::InvalidBountyStage.into()));
     });
 }
+
+#[test]
+fn veto_bounty_succeeds() {
+    build_test_externalities().execute_with(|| {
+        let starting_block = 1;
+        run_to_block(starting_block);
+
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Ok(()));
+
+        EventFixture::assert_last_crate_event(RawEvent::BountyVetoed(bounty_id));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_bounty_id() {
+    build_test_externalities().execute_with(|| {
+        let invalid_bounty_id = 11u64;
+
+        VetoBountyFixture::default()
+            .with_bounty_id(invalid_bounty_id)
+            .call_and_assert(Err(Error::<Test>::BountyDoesntExist.into()));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_origin() {
+    build_test_externalities().execute_with(|| {
+        let account_id = 1;
+
+        CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .with_origin(RawOrigin::Signed(account_id))
+            .call_and_assert(Err(DispatchError::BadOrigin));
+    });
+}
+
+#[test]
+fn veto_bounty_fails_with_invalid_stage() {
+    build_test_externalities().execute_with(|| {
+        CreateBountyFixture::default().call_and_assert(Ok(()));
+
+        let bounty_id = 1u64;
+
+        <crate::Bounties<Test>>::mutate(&bounty_id, |bounty| {
+            bounty.stage = BountyStage::Canceled;
+        });
+
+        VetoBountyFixture::default()
+            .with_bounty_id(bounty_id)
+            .call_and_assert(Err(Error::<Test>::InvalidBountyStage.into()));
+    });
+}

+ 11 - 6
runtime/src/weights/bounty.rs

@@ -7,14 +7,19 @@ use frame_support::weights::{constants::RocksDbWeight as DbWeight, Weight};
 
 pub struct WeightInfo;
 impl bounty::WeightInfo for WeightInfo {
-    // WARNING! Some components were not used: ["i"]
-    fn create_bounty() -> Weight {
-        (147_346_000 as Weight)
-            .saturating_add(DbWeight::get().reads(1 as Weight))
-            .saturating_add(DbWeight::get().writes(2 as Weight))
+    fn create_bounty(i: u32) -> Weight {
+        (199_304_000 as Weight)
+            .saturating_add((1_000 as Weight).saturating_mul(i as Weight))
+            .saturating_add(DbWeight::get().reads(2 as Weight))
+            .saturating_add(DbWeight::get().writes(3 as Weight))
     }
     fn cancel_bounty() -> Weight {
-        (191_000_000 as Weight)
+        (196_000_000 as Weight)
+            .saturating_add(DbWeight::get().reads(1 as Weight))
+            .saturating_add(DbWeight::get().writes(1 as Weight))
+    }
+    fn veto_bounty() -> Weight {
+        (212_000_000 as Weight)
             .saturating_add(DbWeight::get().reads(1 as Weight))
             .saturating_add(DbWeight::get().writes(1 as Weight))
     }