Browse Source

runtime: bounty: Add announce_work_entry extrinsic.

Shamil Gadelshin 4 years ago
parent
commit
99c486691d

+ 4 - 2
runtime-modules/bounty/Cargo.toml

@@ -12,8 +12,9 @@ sp-std = { package = 'sp-std', default-features = false, git = 'https://github.c
 frame-support = { package = 'frame-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 frame-system = { package = 'frame-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 balances = { package = 'pallet-balances', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
-common = { package = 'pallet-common', default-features = false, path = '../common'}
 sp-runtime = { package = 'sp-runtime', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
+common = { package = 'pallet-common', default-features = false, path = '../common'}
+staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../staking-handler'}
 
 # Benchmarking
 frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
@@ -46,6 +47,7 @@ std = [
 	'frame-support/std',
 	'frame-system/std',
 	'balances/std',
-	'common/std',
 	'sp-runtime/std',
+	'common/std',
+	'staking-handler/std',
 ]

+ 130 - 1
runtime-modules/bounty/src/lib.rs

@@ -60,6 +60,7 @@ use sp_std::vec::Vec;
 use common::council::CouncilBudgetManager;
 use common::origin::MemberOriginValidator;
 use common::MemberId;
+use staking_handler::StakingHandler;
 
 use codec::{Decode, Encode};
 #[cfg(feature = "std")]
@@ -85,6 +86,12 @@ pub trait Trait: frame_system::Trait + balances::Trait + common::Trait {
 
     /// Provides an access for the council budget.
     type CouncilBudgetManager: CouncilBudgetManager<BalanceOf<Self>>;
+
+    /// Provides stake logic implementation.
+    type StakingHandler: StakingHandler<Self::AccountId, BalanceOf<Self>, MemberId<Self>>;
+
+    /// Work entry Id type
+    type WorkEntryId: From<u32> + Parameter + Default + Copy;
 }
 
 /// Alias type for the BountyParameters.
@@ -272,12 +279,33 @@ pub struct BountyRecord<Balance, BlockNumber, MemberId> {
     pub milestone: BountyMilestone<BlockNumber>,
 }
 
+/// Alias type for the WorkEntry.
+pub type WorkEntry<T> = WorkEntryRecord<
+    <T as frame_system::Trait>::AccountId,
+    <T as common::Trait>::MemberId,
+    <T as Trait>::BountyId,
+>;
+
+/// Work entry.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
+pub struct WorkEntryRecord<AccountId, MemberId, BountyId> {
+    /// Work entrant member ID.
+    pub member_id: MemberId,
+
+    /// Bounty ID .
+    pub bounty_id: BountyId,
+
+    /// Optional account ID for staking lock.
+    pub staking_account_id: Option<AccountId>,
+}
+
 /// Balance alias for `balances` module.
 pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 
 decl_storage! {
     trait Store for Module<T: Trait> as Bounty {
-        /// Bounty storage
+        /// Bounty storage.
         pub Bounties get(fn bounties) : map hasher(blake2_128_concat) T::BountyId => Bounty<T>;
 
         /// Double map for bounty funding. It stores member funding for bounties.
@@ -287,6 +315,13 @@ decl_storage! {
 
         /// Count of all bounties that have been created.
         pub BountyCount get(fn bounty_count): u32;
+
+        /// Work entry storage.
+        pub WorkEntries get(fn work_entries) : map hasher(blake2_128_concat)
+            T::WorkEntryId => WorkEntry<T>;
+
+        /// Count of all work entries that have been created.
+        pub WorkEntryCount get(fn work_entry_count): u32;
     }
 }
 
@@ -294,9 +329,11 @@ decl_event! {
     pub enum Event<T>
     where
         <T as Trait>::BountyId,
+        <T as Trait>::WorkEntryId,
         Balance = BalanceOf<T>,
         MemberId = MemberId<T>,
         <T as frame_system::Trait>::BlockNumber,
+        <T as frame_system::Trait>::AccountId,
     {
         /// A bounty was created.
         BountyCreated(BountyId, BountyParameters<Balance, BlockNumber, MemberId>),
@@ -321,6 +358,14 @@ decl_event! {
 
         /// A bounty was removed.
         BountyRemoved(BountyId),
+
+        /// Work entry announced.
+        /// Params:
+        /// - bounty ID
+        /// - entrant member ID
+        /// - optional staking account ID
+        /// - created entry ID
+        WorkEntryAnnounced(BountyId, MemberId, Option<AccountId>, WorkEntryId),
     }
 }
 
@@ -359,6 +404,15 @@ decl_error! {
 
         /// Incorrect funding amount.
         ZeroFundingAmount,
+
+        /// There is not enough balance for a stake.
+        InsufficientBalanceForStake,
+
+        /// The conflicting stake discovered. Cannot stake.
+        ConflictingStakes,
+
+        /// Stake cannot be empty with this bounty.
+        EmptyStake,
     }
 }
 
@@ -664,6 +718,52 @@ decl_module! {
                 Self::remove_bounty(&bounty_id);
             }
         }
+
+        /// Withdraw creator funding.
+        #[weight = 10_000_000]
+        pub fn announce_work_entry(
+            origin,
+            member_id: MemberId<T>,
+            staking_account_id: Option<T::AccountId>,
+            bounty_id: T::BountyId,
+        ) {
+            T::MemberOriginValidator::ensure_member_controller_account_origin(origin, member_id)?;
+
+            let bounty = Self::ensure_bounty_exists(&bounty_id)?;
+
+            let current_bounty_stage = Self::get_bounty_stage(&bounty);
+            ensure!(
+                matches!(current_bounty_stage, BountyStage::WorkSubmission),
+                Error::<T>::InvalidBountyStage,
+            );
+
+            Self::validate_entrant_stake(&bounty, staking_account_id.clone())?;
+
+            //
+            // == MUTATION SAFE ==
+            //
+
+            let next_entry_count_value = Self::work_entry_count() + 1;
+            let entry_id = T::WorkEntryId::from(next_entry_count_value);
+
+            let entry = WorkEntry::<T> {
+                bounty_id,
+                member_id,
+                staking_account_id: staking_account_id.clone()
+            };
+
+            <WorkEntries<T>>::insert(entry_id, entry);
+            WorkEntryCount::mutate(|count| {
+                *count = next_entry_count_value
+            });
+
+            Self::deposit_event(RawEvent::WorkEntryAnnounced(
+                bounty_id,
+                member_id,
+                staking_account_id,
+                entry_id,
+            ));
+        }
     }
 }
 
@@ -1085,4 +1185,33 @@ impl<T: Trait> Module<T> {
             previous_milestone
         }
     }
+
+    // Validates stake on announcing the work entry.
+    fn validate_entrant_stake(
+        bounty: &Bounty<T>,
+        staking_account_id: Option<T::AccountId>,
+    ) -> DispatchResult {
+        let staking_balance = bounty.creation_params.entrant_stake;
+
+        if staking_balance != Zero::zero() {
+            if let Some(staking_account_id) = staking_account_id {
+                ensure!(
+                    T::StakingHandler::is_account_free_of_conflicting_stakes(&staking_account_id),
+                    Error::<T>::ConflictingStakes
+                );
+
+                ensure!(
+                    T::StakingHandler::is_enough_balance_for_stake(
+                        &staking_account_id,
+                        staking_balance
+                    ),
+                    Error::<T>::InsufficientBalanceForStake
+                );
+            } else {
+                return Err(Error::<T>::EmptyStake.into());
+            }
+        }
+
+        Ok(())
+    }
 }

+ 5 - 0
runtime/src/constants.rs

@@ -111,6 +111,7 @@ parameter_types! {
     pub const ForumGroupLockId: LockIdentifier = [8; 8];
     pub const MembershipWorkingGroupLockId: LockIdentifier = [9; 8];
     pub const InvitedMemberLockId: LockIdentifier = [10; 8];
+    pub const BountyLockId: LockIdentifier = [11; 8];
 }
 
 // Staking lock ID used by nomination and validation in the staking pallet.
@@ -175,6 +176,10 @@ lazy_static! {
             InvitedMemberLockId::get(),
             VotingLockId::get(),
         ].to_vec()),
+        // Bounty
+        (BountyLockId::get(), [
+            VotingLockId::get(),
+        ].to_vec()),
     ]
     .iter()
     .fold(BTreeSet::new(), |mut acc, item| {

+ 2 - 0
runtime/src/lib.rs

@@ -885,6 +885,8 @@ impl bounty::Trait for Runtime {
     type MemberOriginValidator = Members;
     type WeightInfo = weights::bounty::WeightInfo;
     type CouncilBudgetManager = Council;
+    type StakingHandler = staking_handler::StakingManager<Self, BountyLockId>;
+    type WorkEntryId = u64;
 }
 
 /// Forum identifier for category