Browse Source

Restore add_opening() method in the bureacracy module

Shamil Gadelshin 4 years ago
parent
commit
b7ccf18e56

+ 40 - 0
runtime-modules/bureaucracy/src/constraints.rs

@@ -0,0 +1,40 @@
+use codec::{Decode, Encode};
+#[cfg(feature = "std")]
+use serde::{Deserialize, Serialize};
+
+/// Length constraint for input validation
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
+pub struct InputValidationLengthConstraint {
+    /// Minimum length
+    pub min: u16,
+
+    /// Difference between minimum length and max length.
+    /// While having max would have been more direct, this
+    /// way makes max < min unrepresentable semantically,
+    /// which is safer.
+    pub max_min_diff: u16,
+}
+
+impl InputValidationLengthConstraint {
+    /// Helper for computing max
+    pub fn max(&self) -> u16 {
+        self.min + self.max_min_diff
+    }
+
+    pub fn ensure_valid(
+        &self,
+        len: usize,
+        too_short_msg: &'static str,
+        too_long_msg: &'static str,
+    ) -> Result<(), &'static str> {
+        let length = len as u16;
+        if length < self.min {
+            Err(too_short_msg)
+        } else if length > self.max() {
+            Err(too_long_msg)
+        } else {
+            Ok(())
+        }
+    }
+}

+ 138 - 8
runtime-modules/bureaucracy/src/lib.rs

@@ -1,28 +1,47 @@
 // Ensure we're `no_std` when compiling for Wasm.
 #![cfg_attr(not(feature = "std"), no_std)]
 
+mod constraints;
 mod types;
 
-use sr_primitives::traits::EnsureOrigin;
-use srml_support::{decl_event, decl_module, decl_storage, dispatch};
-use system::{ensure_root, RawOrigin};
 
-use types::Lead;
+use rstd::collections::btree_set::BTreeSet;
+use rstd::vec::Vec;
+use sr_primitives::traits::One;
+use srml_support::traits::Currency;
+use srml_support::{decl_module, decl_storage, dispatch, ensure};
+use system::{ensure_root, ensure_signed};
+
+use constraints::InputValidationLengthConstraint;
+use membership::role_types::{ActorInRole, Role};
+use types::{CuratorOpening, Lead, LeadRoleState, OpeningPolicyCommitment};
 
 //TODO: convert messages to the decl_error! entries
 pub static MSG_ORIGIN_IS_NOT_LEAD: &str = "Origin is not lead";
 pub static MSG_CURRENT_LEAD_NOT_SET: &str = "Current lead is not set";
 pub static MSG_CURRENT_LEAD_ALREADY_SET: &str = "Current lead is already set";
 pub static MSG_IS_NOT_LEAD_ACCOUNT: &str = "Not a lead account";
+pub static MSG_CHANNEL_DESCRIPTION_TOO_SHORT: &str = "Channel description too short";
+pub static MSG_CHANNEL_DESCRIPTION_TOO_LONG: &str = "Channel description too long";
 
 /// Alias for the _Lead_ type
 pub type LeadOf<T> =
     Lead<<T as membership::members::Trait>::MemberId, <T as system::Trait>::AccountId>;
 
+/// Type for the identifier for an opening for a curator.
+pub type CuratorOpeningId<T> = <T as hiring::Trait>::OpeningId;
+
+/// Type for the identifier for an application as a curator.
+pub type CuratorApplicationId<T> = <T as hiring::Trait>::ApplicationId;
+
+/// Balance type of runtime
+pub type BalanceOf<T> =
+    <<T as stake::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
+
 /// The bureaucracy main _Trait_
-pub trait Trait<I: Instance>: system::Trait + membership::members::Trait {
-    /// Bureaucracy event type.
-    type Event: From<Event<Self, I>> + Into<<Self as system::Trait>::Event>;
+pub trait Trait<I: Instance>:
+    system::Trait + recurringrewards::Trait + membership::members::Trait + hiring::Trait
+{
 }
 
 decl_event!(
@@ -43,7 +62,17 @@ decl_event!(
 decl_storage! {
     trait Store for Module<T: Trait<I>, I: Instance> as Bureaucracy {
         /// The current lead.
+
         pub CurrentLead get(current_lead) : Option<LeadOf<T>>;
+
+        /// Next identifier value for new curator opening.
+        pub NextCuratorOpeningId get(next_curator_opening_id): CuratorOpeningId<T>;
+
+        /// Maps identifier to curator opening.
+        pub CuratorOpeningById get(curator_opening_by_id): linked_map CuratorOpeningId<T> => CuratorOpening<T::OpeningId, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>>;
+
+        pub OpeningHumanReadableText get(opening_human_readable_text): InputValidationLengthConstraint;
+
     }
 }
 
@@ -51,6 +80,61 @@ decl_module! {
     pub struct Module<T: Trait<I>, I: Instance> for enum Call where origin: T::Origin {
         /// Default deposit_event() handler
         fn deposit_event() = default;
+            /// Add an opening for a curator role.
+        pub fn add_curator_opening(origin, activate_at: hiring::ActivateOpeningAt<T::BlockNumber>, commitment: OpeningPolicyCommitment<T::BlockNumber, BalanceOf<T>>, human_readable_text: Vec<u8>)  {
+
+            // Ensure lead is set and is origin signer
+            Self::ensure_origin_is_set_lead(origin)?;
+
+            Self::ensure_opening_human_readable_text_is_valid(&human_readable_text)?;
+
+            // Add opening
+            // NB: This call can in principle fail, because the staking policies
+            // may not respect the minimum currency requirement.
+
+            let policy_commitment = commitment.clone();
+
+            // let opening_id = ensure_on_wrapped_error!(
+            //     hiring::Module::<T>::add_opening(
+            //         activate_at,
+            //         commitment.max_review_period_length,
+            //         commitment.application_rationing_policy,
+            //         commitment.application_staking_policy,
+            //         commitment.role_staking_policy,
+            //         human_readable_text,
+            //     ))?;
+
+            let opening_id = hiring::Module::<T>::add_opening(
+                activate_at,
+                commitment.max_review_period_length,
+                commitment.application_rationing_policy,
+                commitment.application_staking_policy,
+                commitment.role_staking_policy,
+                human_readable_text,
+            ).unwrap(); //TODO
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let new_curator_opening_id = NextCuratorOpeningId::<T, I>::get();
+
+            // Create and add curator opening.
+            let new_opening_by_id = CuratorOpening::<CuratorOpeningId<T>, T::BlockNumber, BalanceOf<T>, CuratorApplicationId<T>> {
+                opening_id : opening_id,
+                curator_applications: BTreeSet::new(),
+                policy_commitment: policy_commitment
+            };
+
+            CuratorOpeningById::<T, I>::insert(new_curator_opening_id, new_opening_by_id);
+
+            // Update NextCuratorOpeningId
+            NextCuratorOpeningId::<T, I>::mutate(|id| *id += <CuratorOpeningId<T> as One>::one());
+
+            // Trigger event
+            //Self::deposit_event(RawEvent::CuratorOpeningAdded(new_curator_opening_id));
+    }
+
 
         /// Introduce a lead when one is not currently set.
         pub fn set_lead(origin, member_id: T::MemberId, role_account_id: T::AccountId) -> dispatch::Result {
@@ -72,7 +156,7 @@ decl_module! {
 
             Ok(())
         }
-    }
+}
 }
 
 impl<T: Trait<I>, I: Instance> Module<T, I> {
@@ -108,4 +192,50 @@ where
             _ => Err(RawOrigin::None.into()),
         })
     }
+
+    fn ensure_opening_human_readable_text_is_valid(text: &Vec<u8>) -> dispatch::Result {
+        <OpeningHumanReadableText<I>>::get().ensure_valid(
+            text.len(),
+            MSG_CHANNEL_DESCRIPTION_TOO_SHORT,
+            MSG_CHANNEL_DESCRIPTION_TOO_LONG,
+        )
+    }
+
+    fn ensure_origin_is_set_lead(
+        origin: T::Origin,
+    ) -> Result<
+        (
+            LeadId<T>,
+            Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>,
+        ),
+        &'static str,
+    > {
+        // Ensure lead is actually set
+        let (lead_id, lead) = Self::ensure_lead_is_set()?;
+
+        // Ensure is signed
+        let signer = ensure_signed(origin)?;
+
+        // Ensure signer is lead
+        ensure!(signer == lead.role_account, MSG_ORIGIN_IS_NOT_LEAD);
+
+        Ok((lead_id, lead))
+    }
+
+    pub fn ensure_lead_is_set() -> Result<
+        (
+            LeadId<T>,
+            Lead<T::AccountId, T::RewardRelationshipId, T::BlockNumber>,
+        ),
+        &'static str,
+    > {
+        // Ensure lead id is set
+        let lead_id = Self::ensure_lead_id_set()?;
+
+        // If so, grab actual lead
+        let lead = <LeadById<T, I>>::get(lead_id);
+
+        // and return both
+        Ok((lead_id, lead))
+    }
 }

+ 115 - 0
runtime-modules/bureaucracy/src/types.rs

@@ -1,7 +1,122 @@
 use codec::{Decode, Encode};
+use rstd::collections::btree_set::BTreeSet;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
+/// Terms for slashings applied to a given role
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
+pub struct SlashableTerms {
+    /// Maximum number of slashes.
+    pub max_count: u16,
+
+    /// Maximum percentage points of remaining stake which may be slashed in a single slash.
+    pub max_percent_pts_per_time: u16,
+}
+
+/// Terms for what slashing can be applied in some context
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
+pub enum SlashingTerms {
+    Unslashable,
+    Slashable(SlashableTerms),
+}
+
+/// Must be default constructible because it indirectly is a value in a storage map.
+/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
+impl Default for SlashingTerms {
+    fn default() -> Self {
+        Self::Unslashable
+    }
+}
+
+/// A commitment to the set of policy variables relevant to an opening.
+/// An applicant can observe this commitment and be secure that the terms
+/// of the application process cannot be changed ex-post.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, Default, PartialEq, Eq)]
+pub struct OpeningPolicyCommitment<BlockNumber, Balance> {
+    /// Rationing to be used
+    pub application_rationing_policy: Option<hiring::ApplicationRationingPolicy>,
+
+    /// Maximum length of review period of applications
+    pub max_review_period_length: BlockNumber,
+
+    /// Staking policy for application
+    pub application_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
+
+    /// Staking policy for role itself
+    pub role_staking_policy: Option<hiring::StakingPolicy<Balance, BlockNumber>>,
+
+    // Slashing terms during application
+    // pub application_slashing_terms: SlashingTerms,
+
+    // Slashing terms during role, NOT application itself!
+    pub role_slashing_terms: SlashingTerms,
+
+    /// When filling an opening: Unstaking period for application stake of successful applicants
+    pub fill_opening_successful_applicant_application_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When filling an opening:
+    pub fill_opening_failed_applicant_application_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When filling an opening:
+    pub fill_opening_failed_applicant_role_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When terminating a curator:
+    pub terminate_curator_application_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When terminating a curator:
+    pub terminate_curator_role_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When a curator exists: ..
+    pub exit_curator_role_application_stake_unstaking_period: Option<BlockNumber>,
+
+    /// When a curator exists: ..
+    pub exit_curator_role_stake_unstaking_period: Option<BlockNumber>,
+}
+
+/// An opening for a curator role.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+pub struct CuratorOpening<OpeningId, BlockNumber, Balance, CuratorApplicationId: core::cmp::Ord> {
+    /// Identifer for underlying opening in the hiring module.
+    pub opening_id: OpeningId,
+
+    /// Set of identifiers for all curator applications ever added
+    pub curator_applications: BTreeSet<CuratorApplicationId>,
+
+    /// Commitment to policies in opening.
+    pub policy_commitment: OpeningPolicyCommitment<BlockNumber, Balance>,
+}
+
+/// The exit stage of a lead involvement in the working group.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+pub struct ExitedLeadRole<BlockNumber> {
+    /// When exit was initiated.
+    pub initiated_at_block_number: BlockNumber,
+}
+
+/// The stage of the involvement of a lead in the working group.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+pub enum LeadRoleState<BlockNumber> {
+    /// Currently active.
+    Active,
+
+    /// No longer active, for some reason
+    Exited(ExitedLeadRole<BlockNumber>),
+}
+
+/// Must be default constructible because it indirectly is a value in a storage map.
+/// ***SHOULD NEVER ACTUALLY GET CALLED, IS REQUIRED TO DUE BAD STORAGE MODEL IN SUBSTRATE***
+impl<BlockNumber> Default for LeadRoleState<BlockNumber> {
+    fn default() -> Self {
+        LeadRoleState::Active
+    }
+}
+
 /// Working group lead: curator lead
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]