Browse Source

membership pallet: endow members added through screeing

Mokhtar Naamani 4 years ago
parent
commit
ddb833862f

+ 1 - 1
Cargo.lock

@@ -2053,7 +2053,7 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime"
-version = "7.12.0"
+version = "7.13.0"
 dependencies = [
  "frame-benchmarking",
  "frame-executive",

+ 6 - 0
runtime-modules/governance/src/mock.rs

@@ -69,12 +69,18 @@ impl election::Trait for Test {
 
     type CouncilElected = (Council,);
 }
+
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = ();
     type MemberId = u64;
     type SubscriptionId = u32;
     type PaidTermId = u32;
     type ActorId = u32;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 impl minting::Trait for Test {
     type Currency = Balances;

+ 46 - 3
runtime-modules/membership/src/lib.rs

@@ -10,10 +10,10 @@ pub(crate) mod mock;
 mod tests;
 
 use codec::{Codec, Decode, Encode};
-use frame_support::traits::Currency;
+use frame_support::traits::{Currency, Get, LockableCurrency, WithdrawReason};
 use frame_support::{decl_event, decl_module, decl_storage, ensure, Parameter};
 use sp_arithmetic::traits::{BaseArithmetic, One};
-use sp_runtime::traits::{MaybeSerialize, Member};
+use sp_runtime::traits::{MaybeSerialize, Member, Zero};
 use sp_std::borrow::ToOwned;
 use sp_std::vec;
 use sp_std::vec::Vec;
@@ -65,6 +65,10 @@ pub trait Trait: system::Trait + GovernanceCurrency + pallet_timestamp::Trait {
         + MaybeSerialize
         + PartialEq
         + Ord;
+
+    /// The maximum amount of initial funds that may be endowed to new members added by
+    /// screening authority. If set to zero, no initial balance can be given.
+    type ScreenedMemberMaxInitialBalance: Get<BalanceOf<Self>>;
 }
 
 const FIRST_PAID_TERMS_ID: u8 = 1;
@@ -259,6 +263,8 @@ decl_module! {
     pub struct Module<T: Trait> for enum Call where origin: T::Origin {
         fn deposit_event() = default;
 
+        const ScreenedMemberMaxInitialBalance: BalanceOf<T> = T::ScreenedMemberMaxInitialBalance::get();
+
         /// Non-members can buy membership
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn buy_membership(
@@ -406,13 +412,18 @@ decl_module! {
             }
         }
 
+        /// Screened members are awarded a initial locked balance that can only be slashed or used
+        /// for fees, and is not transferable. The screening authority must ensure that the provided
+        /// new_member_account was verified to avoid applying locks arbitrarily to accounts not controlled
+        /// by the member.
         #[weight = 10_000_000] // TODO: adjust weight
         pub fn add_screened_member(
             origin,
             new_member_account: T::AccountId,
             handle: Option<Vec<u8>>,
             avatar_uri: Option<Vec<u8>>,
-            about: Option<Vec<u8>>
+            about: Option<Vec<u8>>,
+            initial_balance: Option<BalanceOf<T>>,
         ) {
             // ensure sender is screening authority
             let sender = ensure_signed(origin)?;
@@ -429,6 +440,38 @@ decl_module! {
 
             let user_info = Self::check_user_registration_info(handle, avatar_uri, about)?;
 
+            if let Some(initial_balance) = initial_balance {
+                ensure!(
+                    T::ScreenedMemberMaxInitialBalance::get() >= initial_balance,
+                    "InitialBalanceExceedsMaxInitialBalance"
+                );
+
+                // Only allow "new" accounts with 0 balance
+                ensure!(
+                    T::Currency::free_balance(&new_member_account).is_zero(),
+                    "OnlyNewAccountsCanBeUsedForScreenedMembers"
+                );
+
+                ensure!(
+                    system::Module::<T>::account_nonce(&new_member_account).is_zero(),
+                    "OnlyNewAccountsCanBeUsedForScreenedMembers"
+                );
+
+                // Check account nonce
+
+                // Set a lock to prevent transfers of the amount that will be endowed
+                T::Currency::set_lock(
+                    *b"faucet00",
+                    &new_member_account,
+                    initial_balance,
+                    WithdrawReason::Transfer.into(),
+                );
+
+                // Endow the new member account with an amount to get started
+                T::Currency::deposit_creating(&new_member_account, initial_balance);
+            };
+
+            // cannot fail because of prior check_user_registration_info
             let member_id = Self::insert_member(
                 &new_member_account,
                 &new_member_account,

+ 5 - 0
runtime-modules/membership/src/mock.rs

@@ -78,12 +78,17 @@ impl GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl Trait for Test {
     type Event = ();
     type MemberId = u64;
     type PaidTermId = u32;
     type SubscriptionId = u32;
     type ActorId = u32;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 pub struct TestExternalitiesBuilder<T: Trait> {

+ 24 - 1
runtime-modules/membership/src/tests.rs

@@ -4,6 +4,7 @@ use super::genesis;
 use super::mock::*;
 
 use frame_support::*;
+// use sp_core::traits;
 
 fn get_membership_by_id(member_id: u64) -> crate::Membership<Test> {
     if <crate::MembershipById<Test>>::contains_key(member_id) {
@@ -253,6 +254,7 @@ fn add_screened_member() {
             <crate::ScreeningAuthority<Test>>::put(&screening_authority);
 
             let next_member_id = Members::members_created();
+            let endownment = ScreenedMemberMaxInitialBalance::get() - 1;
 
             let info = get_alice_info();
             assert_ok!(Members::add_screened_member(
@@ -260,7 +262,8 @@ fn add_screened_member() {
                 ALICE_ACCOUNT_ID,
                 info.handle,
                 info.avatar_uri,
-                info.about
+                info.about,
+                Some(endownment),
             ));
 
             let profile = get_membership_by_id(next_member_id);
@@ -272,6 +275,26 @@ fn add_screened_member() {
                 crate::EntryMethod::Screening(screening_authority),
                 profile.entry
             );
+            assert_eq!(Balances::free_balance(ALICE_ACCOUNT_ID), endownment);
+
+            // Transfer should fail because of balance lock
+            assert_err!(
+                Balances::transfer(Origin::signed(ALICE_ACCOUNT_ID), screening_authority, 1),
+                balances::Error::<Test, _>::LiquidityRestrictions
+            );
+
+            // .. but we should be able to slash
+            assert!(Balances::can_slash(&ALICE_ACCOUNT_ID, 1));
+
+            // Deposit more funds to have a surplus above lock limit
+            let _ = Balances::deposit_creating(&ALICE_ACCOUNT_ID, 10);
+
+            // If free balance above lock limit, transfers should be possible
+            assert_ok!(Balances::transfer(
+                Origin::signed(ALICE_ACCOUNT_ID),
+                screening_authority,
+                1
+            ));
         });
 }
 

+ 5 - 0
runtime-modules/proposals/codex/src/tests/mock.rs

@@ -44,12 +44,17 @@ impl common::currency::GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = ();
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 parameter_types! {

+ 5 - 0
runtime-modules/proposals/discussion/src/tests/mock.rs

@@ -71,12 +71,17 @@ impl common::currency::GovernanceCurrency for Test {
     type Currency = balances::Module<Self>;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = TestEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl crate::Trait for Test {

+ 5 - 0
runtime-modules/proposals/engine/src/tests/mock/mod.rs

@@ -84,12 +84,17 @@ parameter_types! {
     pub const MaxActiveProposalLimit: u32 = 100;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = TestEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl crate::Trait for Test {

+ 5 - 0
runtime-modules/service-discovery/src/mock.rs

@@ -105,12 +105,17 @@ impl stake::Trait for Test {
     type SlashId = u64;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = MetaEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl common::currency::GovernanceCurrency for Test {

+ 5 - 0
runtime-modules/storage/src/tests/mock.rs

@@ -233,12 +233,17 @@ impl data_object_storage_registry::Trait for Test {
     type ContentIdExists = MockContent;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = MetaEvent;
     type MemberId = u64;
     type SubscriptionId = u32;
     type PaidTermId = u32;
     type ActorId = u32;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl stake::Trait for Test {

+ 1 - 0
runtime-modules/working-group/src/tests/fixtures.rs

@@ -506,6 +506,7 @@ pub fn setup_members(count: u8) {
             Some(handle.to_vec()),
             None,
             None,
+            None,
         )
         .unwrap();
     }

+ 5 - 0
runtime-modules/working-group/src/tests/mock.rs

@@ -96,12 +96,17 @@ impl stake::Trait for Test {
     type SlashId = u64;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u64 = 500;
+}
+
 impl membership::Trait for Test {
     type Event = TestEvent;
     type MemberId = u64;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = u64;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl common::currency::GovernanceCurrency for Test {

+ 1 - 1
runtime/Cargo.toml

@@ -4,7 +4,7 @@ edition = '2018'
 name = 'joystream-node-runtime'
 # Follow convention: https://github.com/Joystream/substrate-runtime-joystream/issues/1
 # {Authoring}.{Spec}.{Impl} of the RuntimeVersion
-version = '7.12.0'
+version = '7.13.0'
 
 [dependencies]
 # Third-party dependencies

+ 3 - 0
runtime/src/integration/proposals/council_origin_validator.rs

@@ -103,6 +103,7 @@ mod tests {
                 Some(b"handle".to_vec()),
                 None,
                 None,
+                None,
             )
             .unwrap();
             let member_id = 0; // newly created member_id
@@ -133,6 +134,7 @@ mod tests {
                 Some(b"handle".to_vec()),
                 None,
                 None,
+                None,
             )
             .unwrap();
             let member_id = 0; // newly created member_id
@@ -166,6 +168,7 @@ mod tests {
                 Some(b"handle".to_vec()),
                 None,
                 None,
+                None,
             )
             .unwrap();
             let member_id = 0; // newly created member_id

+ 2 - 0
runtime/src/integration/proposals/membership_origin_validator.rs

@@ -92,6 +92,7 @@ mod tests {
                 Some(b"handle".to_vec()),
                 None,
                 None,
+                None,
             )
             .unwrap();
             let member_id = 0; // newly created member_id
@@ -122,6 +123,7 @@ mod tests {
                 Some(b"handle".to_vec()),
                 None,
                 None,
+                None,
             )
             .unwrap();
             let member_id = 0; // newly created member_id

+ 6 - 1
runtime/src/lib.rs

@@ -71,7 +71,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 7,
-    spec_version: 12,
+    spec_version: 13,
     impl_version: 0,
     apis: crate::runtime_api::EXPORTED_RUNTIME_API_VERSIONS,
     transaction_version: 1,
@@ -473,12 +473,17 @@ impl storage::data_object_storage_registry::Trait for Runtime {
     type ContentIdExists = DataDirectory;
 }
 
+parameter_types! {
+    pub const ScreenedMemberMaxInitialBalance: u128 = 5000;
+}
+
 impl membership::Trait for Runtime {
     type Event = Event;
     type MemberId = MemberId;
     type PaidTermId = u64;
     type SubscriptionId = u64;
     type ActorId = ActorId;
+    type ScreenedMemberMaxInitialBalance = ScreenedMemberMaxInitialBalance;
 }
 
 impl forum::Trait for Runtime {

+ 17 - 7
runtime/src/tests/proposals_integration/mod.rs

@@ -45,6 +45,7 @@ fn setup_members(count: u8) {
             Some(account_id.to_vec()),
             None,
             None,
+            None,
         )
         .unwrap();
     }
@@ -288,19 +289,22 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
             .with_stake(stake_amount)
             .with_proposer(member_id);
 
-        let account_balance = 500000;
+        let account_top_up = 500000;
+        let account_starting_balance =
+            <Runtime as stake::Trait>::Currency::total_balance(&account_id);
+
         let _imbalance =
-            <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, account_balance);
+            <Runtime as stake::Trait>::Currency::deposit_creating(&account_id, account_top_up);
 
         assert_eq!(
             <Runtime as stake::Trait>::Currency::total_balance(&account_id),
-            account_balance
+            account_starting_balance + account_top_up
         );
 
         let proposal_id = dummy_proposal.create_proposal_and_assert(Ok(1)).unwrap();
         assert_eq!(
             <Runtime as stake::Trait>::Currency::total_balance(&account_id),
-            account_balance - stake_amount
+            account_starting_balance + account_top_up - stake_amount
         );
 
         let mut proposal = ProposalsEngine::proposals(proposal_id);
@@ -339,7 +343,7 @@ fn proposal_cancellation_with_slashes_with_balance_checks_succeeds() {
         let cancellation_fee = ProposalCancellationFee::get() as u128;
         assert_eq!(
             <Runtime as stake::Trait>::Currency::total_balance(&account_id),
-            account_balance - cancellation_fee
+            account_starting_balance + account_top_up - cancellation_fee
         );
     });
 }
@@ -496,6 +500,8 @@ where
     }
 }
 
+const NUMBER_OF_MEMBERS_TO_SETUP_IN_CODEX_PROPOSAL_FIXTURE: u8 = 15;
+
 impl<SuccessfulCall> CodexProposalTestFixture<SuccessfulCall>
 where
     SuccessfulCall: Fn() -> DispatchResult,
@@ -504,7 +510,7 @@ where
         let account_id: [u8; 32] = [self.member_id as u8; 32];
 
         if self.setup_environment {
-            setup_members(15);
+            setup_members(NUMBER_OF_MEMBERS_TO_SETUP_IN_CODEX_PROPOSAL_FIXTURE);
             setup_council();
 
             increase_total_balance_issuance_using_account_id(account_id.clone().into(), 500000);
@@ -573,7 +579,10 @@ fn spending_proposal_execution_succeeds() {
         let account_id: [u8; 32] = [member_id; 32];
         let new_balance = <BalanceOf<Runtime>>::from(5555u32);
 
-        let target_account_id: [u8; 32] = [12; 32];
+        // account id outside range of generated member accounts when environement is setup
+        // to ensure it doesn't get any endowed balance
+        let target_account_id: [u8; 32] =
+            [NUMBER_OF_MEMBERS_TO_SETUP_IN_CODEX_PROPOSAL_FIXTURE + 1; 32];
 
         assert!(Council::set_council_mint_capacity(RawOrigin::Root.into(), new_balance).is_ok());
 
@@ -591,6 +600,7 @@ fn spending_proposal_execution_succeeds() {
         .with_member_id(member_id as u64);
 
         let converted_account_id: AccountId32 = target_account_id.clone().into();
+
         assert_eq!(Balances::free_balance(converted_account_id.clone()), 0);
 
         codex_extrinsic_test_fixture.call_extrinsic_and_assert();