Browse Source

runtime: bounty: Add oracle and contract types.

Shamil Gadelshin 4 years ago
parent
commit
30630c25eb

+ 1 - 0
Cargo.lock

@@ -3741,6 +3741,7 @@ dependencies = [
  "frame-support",
  "frame-system",
  "pallet-balances",
+ "pallet-common",
  "parity-scale-codec",
  "serde",
  "sp-arithmetic",

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

@@ -12,6 +12,7 @@ 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'}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -29,4 +30,5 @@ std = [
 	'frame-support/std',
 	'frame-system/std',
 	'balances/std',
+	'common/std',
 ]

+ 129 - 33
runtime-modules/bounty/src/lib.rs

@@ -4,61 +4,129 @@
 #[cfg(test)]
 pub(crate) mod tests;
 
-use frame_support::{decl_event, decl_module, decl_storage, Parameter};
-use frame_system::ensure_signed;
-use sp_arithmetic::traits::Zero;
+use frame_support::{decl_error, decl_event, decl_module, decl_storage, Parameter};
+use frame_system::ensure_root;
 use sp_std::vec::Vec;
 
+use common::origin::MemberOriginValidator;
+use common::MemberId;
+
 use codec::{Decode, Encode};
+use frame_support::dispatch::DispatchResult;
 #[cfg(feature = "std")]
 use serde::{Deserialize, Serialize};
 
-pub trait Trait: frame_system::Trait + balances::Trait {
+/// Main pallet-bounty trait.
+pub trait Trait: frame_system::Trait + balances::Trait + common::Trait {
+    /// Events
     type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
 
     /// Bounty Id type
     type BountyId: From<u32> + Parameter + Default + Copy;
+
+    /// Validates member ID and origin combination.
+    type MemberOriginValidator: MemberOriginValidator<Self::Origin, MemberId<Self>, Self::AccountId>;
+}
+
+/// Alias type for the BountyParameters.
+pub type BountyCreationParameters<T> = BountyParameters<
+    BalanceOf<T>,
+    <T as frame_system::Trait>::BlockNumber,
+    <T as common::Trait>::MemberId,
+>;
+
+/// Defines who will be the oracle of the work submissions.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Copy, Debug)]
+pub enum OracleType<MemberId> {
+    /// Specific member will be the oracle.
+    Member(MemberId),
+
+    /// Council will become an oracle.
+    Council,
+}
+
+impl<MemberId> Default for OracleType<MemberId> {
+    fn default() -> Self {
+        OracleType::Council
+    }
 }
 
-/*
-Metadata: A standardised structure document describing user facing information, for example a title, amount requested, deliverable, discovery metadata, link to forum etc. Is not stored in storage, chain only sees raw extrinsic payload blob, like rationales before.
-Oracle: Origin that will select winner(s), is either a given member or the council.
-Cherry: An mount of funding, possibly 0, provided by the creator which will be split among all other contributors should the min funding bound not be reached. If reached, cherry is returned to the creator. When council is creating bounty, this comes out of their budget, when a member does it, it comes from an account.
-Screened Entrants: The set of members who are allowed to submit their work, if not set, then it is open. Main use case for this is to model dominant assurance contract where member sets contribution cherry and him/herself sa only elidable worker.
-Minimum Amount: The minimum total quantity of funds, possibly 0, required for the bounty to become available for people to work on.
-Max Amount: Maximumm funding accepted, if this limit is reached, funding automatically is over.
-Entrant Stake: Amount of stake required, possibly 0, to enter bounty as entrant.
-Funding Period Length (Optional): Number of blocks from creation until funding is no longer possible. If not provided, then funding is called perpetual, and it only ends when minimum amount is reached.
-Work Period Length: Number of blocks from end of funding period until people can no longer submit bounty submissions.
-Judging Period Length: Number of block from end of work period until oracl can no longer decide winners.
-*/
+/// Defines who can submit the work.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
+pub enum AssuranceContractType<MemberId> {
+    /// Anyone can submit the work.
+    Open,
+
+    /// Only specific members can submit the work.
+    Closed(Vec<MemberId>),
+}
+
+impl<MemberId> Default for AssuranceContractType<MemberId> {
+    fn default() -> Self {
+        AssuranceContractType::Open
+    }
+}
 
+/// Defines parameters for the bounty creation.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct BountyCreationParameters<AccountId, Balance, BlockNumber> {
-    pub metadata: Vec<u8>,
+pub struct BountyParameters<Balance, BlockNumber, MemberId> {
+    /// Origin that will select winner(s), is either a given member or a council.
+    pub oracle: OracleType<MemberId>,
 
-    pub oracle: AccountId,
+    /// Contract type defines who can submit the work.
+    pub contract_type: AssuranceContractType<MemberId>,
 
-    pub cherry: Balance,
+    /// Bounty creator member ID, should be None if created by a council.
+    pub creator_member_id: Option<MemberId>,
 
-    pub screened_entrants: Vec<u8>,
+    /// An mount of funding, possibly 0, provided by the creator which will be split among all other
+    /// contributors should the min funding bound not be reached. If reached, cherry is returned to
+    /// the creator. When council is creating bounty, this comes out of their budget, when a member
+    /// does it, it comes from an account.
+    pub cherry: Balance,
 
+    /// The minimum total quantity of funds, possibly 0, required for the bounty to become
+    /// available for people to work on.
     pub min_amount: Balance,
 
+    /// Maximumm funding accepted, if this limit is reached, funding automatically is over.
     pub max_amount: Balance,
 
+    /// Amount of stake required, possibly 0, to enter bounty as entrant.
+    pub entrant_stake: Balance,
+
+    /// Number of blocks from creation until funding is no longer possible. If not provided, then
+    /// funding is called perpetual, and it only ends when minimum amount is reached.
     pub funding_period: Option<BlockNumber>,
 
+    /// Number of blocks from end of funding period until people can no longer submit
+    /// bounty submissions.
     pub work_period: BlockNumber,
 
+    /// Number of block from end of work period until oracle can no longer decide winners.
     pub judging_period: BlockNumber,
+
+    /// A standardised structure document describing user facing information,
+    /// for example a title, amount requested, deliverable, discovery metadata, link to forum etc.
+    /// Is not stored in storage, chain only sees raw extrinsic payload blob, like rationales before.
+    pub metadata: Vec<u8>,
 }
 
+/// Alias type for the Bounty.
+pub type Bounty<T> = BountyRecord<
+    BalanceOf<T>,
+    <T as frame_system::Trait>::BlockNumber,
+    <T as common::Trait>::MemberId,
+>;
+
+/// Crowdfunded bounty record.
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 #[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)]
-pub struct Bounty<Balance> {
-    pub cherry: Balance,
+pub struct BountyRecord<Balance, BlockNumber, MemberId> {
+    pub creation_params: BountyParameters<Balance, BlockNumber, MemberId>,
 }
 
 /// Balance alias for `balances` module.
@@ -67,7 +135,7 @@ pub type BalanceOf<T> = <T as balances::Trait>::Balance;
 decl_storage! {
     trait Store for Module<T: Trait> as Bounty {
         /// Bounty storage
-        pub Bounties get(fn bounties) : map hasher(blake2_128_concat) T::BountyId => Bounty<BalanceOf<T>>;
+        pub Bounties get(fn bounties) : map hasher(blake2_128_concat) T::BountyId => Bounty<T>;
 
         /// Count of all bounties that have been created.
         pub BountyCount get(fn bounty_count): u32;
@@ -79,34 +147,62 @@ decl_event! {
     where
         <T as Trait>::BountyId,
     {
+        /// A bounty was created.
         BountyCreated(BountyId),
     }
 }
 
+decl_error! {
+    /// Bounty pallet predefined errors
+    pub enum Error for Module<T: Trait> {
+        /// Incorrect origin provided.
+        BadOrigin,
+    }
+}
+
 decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event() = default;
 
         #[weight = 10_000_000] // TODO: adjust weight
-        fn create_bounty(origin, params: BountyCreationParameters<T::AccountId, BalanceOf<T>, T::BlockNumber>) {
-            ensure_signed(origin)?;
+        fn create_bounty(origin, params: BountyCreationParameters<T>) {
+            Self::ensure_create_bounty_parameters_valid(&origin, &params)?;
+
+            //
+            // == MUTATION SAFE ==
+            //
 
             let next_bounty_count_value = Self::bounty_count() + 1;
             let bounty_id = T::BountyId::from(next_bounty_count_value);
 
-            let bounty = Bounty {
-                cherry: Zero::zero()
+            let bounty = Bounty::<T> {
+                creation_params: params,
             };
 
-            //
-            // == MUTATION SAFE ==
-            //
-
             <Bounties<T>>::insert(bounty_id, bounty);
             BountyCount::mutate(|count| {
-                *count = *count + 1
+                *count += 1
             });
             Self::deposit_event(RawEvent::BountyCreated(bounty_id));
         }
     }
 }
+
+impl<T: Trait> Module<T> {
+    fn ensure_create_bounty_parameters_valid(
+        origin: &T::Origin,
+        params: &BountyCreationParameters<T>,
+    ) -> DispatchResult {
+        // Validate origin.
+        if let Some(member_id) = params.creator_member_id {
+            T::MemberOriginValidator::ensure_member_controller_account_origin(
+                origin.clone(),
+                member_id,
+            )?;
+        } else {
+            ensure_root(origin.clone())?;
+        }
+
+        Ok(())
+    }
+}

+ 14 - 3
runtime-modules/bounty/src/tests/fixtures.rs

@@ -40,27 +40,38 @@ impl EventFixture {
 pub struct CreateBountyFixture {
     origin: RawOrigin<u64>,
     metadata: Vec<u8>,
+    creator_member_id: Option<u64>,
 }
 
 impl CreateBountyFixture {
     pub fn default() -> Self {
         Self {
-            origin: RawOrigin::Signed(1),
+            origin: RawOrigin::Root,
             metadata: Vec::new(),
+            creator_member_id: None,
         }
     }
+
     pub fn with_origin(self, origin: RawOrigin<u64>) -> Self {
         Self { origin, ..self }
     }
 
+    pub fn with_creator_member_id(self, member_id: u64) -> Self {
+        Self {
+            creator_member_id: Some(member_id),
+            ..self
+        }
+    }
+
     pub fn with_metadata(self, metadata: Vec<u8>) -> Self {
         Self { metadata, ..self }
     }
 
     pub fn call_and_assert(&self, expected_result: DispatchResult) {
-        let params = BountyCreationParameters {
+        let params = BountyCreationParameters::<Test> {
             metadata: self.metadata.clone(),
-            ..BountyCreationParameters::default()
+            creator_member_id: self.creator_member_id.clone(),
+            ..Default::default()
         };
 
         let next_bounty_count_value = Bounty::bounty_count() + 1;

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

@@ -1,6 +1,6 @@
 #![cfg(test)]
 
-use crate::{Module, Trait};
+use frame_support::dispatch::DispatchError;
 use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
 use sp_core::H256;
 use sp_runtime::{
@@ -9,6 +9,8 @@ use sp_runtime::{
     Perbill,
 };
 
+use crate::{Module, Trait};
+
 impl_outer_origin! {
     pub enum Origin for Test {}
 }
@@ -66,6 +68,27 @@ impl frame_system::Trait for Test {
 impl Trait for Test {
     type Event = TestEvent;
     type BountyId = u64;
+    type MemberOriginValidator = ();
+}
+
+impl common::Trait for Test {
+    type MemberId = u64;
+    type ActorId = u64;
+}
+
+impl common::origin::MemberOriginValidator<Origin, u64, u64> for () {
+    fn ensure_member_controller_account_origin(
+        origin: Origin,
+        _account_id: u64,
+    ) -> Result<u64, DispatchError> {
+        let signed_account_id = frame_system::ensure_signed(origin)?;
+
+        Ok(signed_account_id)
+    }
+
+    fn is_member_controller_account(_member_id: &u64, _account_id: &u64) -> bool {
+        true
+    }
 }
 
 parameter_types! {

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

@@ -30,7 +30,15 @@ fn create_bounty_succeeds() {
 #[test]
 fn create_bounty_fails_with_invalid_origin() {
     build_test_externalities().execute_with(|| {
-        let create_bounty_fixture = CreateBountyFixture::default().with_origin(RawOrigin::None);
+        // For a council bounty.
+        let create_bounty_fixture =
+            CreateBountyFixture::default().with_origin(RawOrigin::Signed(1));
+        create_bounty_fixture.call_and_assert(Err(DispatchError::BadOrigin));
+
+        // For a member bounty.
+        let create_bounty_fixture = CreateBountyFixture::default()
+            .with_origin(RawOrigin::Root)
+            .with_creator_member_id(1);
         create_bounty_fixture.call_and_assert(Err(DispatchError::BadOrigin));
     });
 }