Browse Source

Merge pull request #42 from mnaamani/improved-role-staking

Improved role staking
Mokhtar Naamani 5 years ago
parent
commit
5d9448fe7b

+ 1 - 1
Cargo.toml

@@ -2,7 +2,7 @@
 authors = ['Joystream']
 edition = '2018'
 name = 'joystream-node-runtime'
-version = '5.1.0'
+version = '5.2.0'
 
 [features]
 default = ['std']

+ 17 - 19
src/currency.rs

@@ -11,30 +11,28 @@ pub trait GovernanceCurrency: system::Trait + Sized {
 pub type BalanceOf<T> =
     <<T as GovernanceCurrency>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
 
-
 /// A structure that converts the currency type into a lossy u64
 /// And back from u128
 pub struct CurrencyToVoteHandler;
 
 impl Convert<u128, u64> for CurrencyToVoteHandler {
-	fn convert(x: u128) -> u64 {
-		if x >> 96 == 0 {
-			x as u64
-		} else {
-			u64::max_value()
-		}
-	}
+    fn convert(x: u128) -> u64 {
+        if x >> 96 == 0 {
+            x as u64
+        } else {
+            u64::max_value()
+        }
+    }
 }
 
 impl Convert<u128, u128> for CurrencyToVoteHandler {
-	fn convert(x: u128) -> u128 {
-		// if it practically fits in u64
-		if x >> 64 == 0 {
-			x
-		}
-		else {
-			// 0000_0000_FFFF_FFFF_FFFF_FFFF_0000_0000
-			u64::max_value() as u128
-		}
-	}
-}
+    fn convert(x: u128) -> u128 {
+        // if it practically fits in u64
+        if x >> 64 == 0 {
+            x
+        } else {
+            // 0000_0000_FFFF_FFFF_FFFF_FFFF_0000_0000
+            u64::max_value() as u128
+        }
+    }
+}

+ 9 - 12
src/lib.rs

@@ -18,10 +18,7 @@ pub mod currency;
 pub mod governance;
 use governance::{council, election, proposals};
 pub mod storage;
-use storage::{
-    data_directory, data_object_storage_registry, data_object_type_registry,
-    downloads,
-};
+use storage::{data_directory, data_object_storage_registry, data_object_type_registry, downloads};
 mod membership;
 mod memo;
 mod traits;
@@ -41,8 +38,8 @@ use rstd::prelude::*; // needed for Vec
 use runtime_primitives::{
     create_runtime_str, generic,
     traits::{
-        self as runtime_traits, AuthorityIdFor, BlakeTwo256, Block as BlockT,
-        DigestFor, NumberFor, StaticLookup, Verify,
+        self as runtime_traits, AuthorityIdFor, BlakeTwo256, Block as BlockT, DigestFor, NumberFor,
+        StaticLookup, Verify,
     },
     transaction_validity::TransactionValidity,
     AnySignature, ApplyResult,
@@ -99,11 +96,11 @@ pub mod opaque {
     #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
     pub struct UncheckedExtrinsic(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec<u8>);
     #[cfg(feature = "std")]
-	impl std::fmt::Debug for UncheckedExtrinsic {
-		fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
-			write!(fmt, "{}", primitives::hexdisplay::HexDisplay::from(&self.0))
-		}
-	}
+    impl std::fmt::Debug for UncheckedExtrinsic {
+        fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+            write!(fmt, "{}", primitives::hexdisplay::HexDisplay::from(&self.0))
+        }
+    }
     impl runtime_traits::Extrinsic for UncheckedExtrinsic {
         fn is_signed(&self) -> Option<bool> {
             None
@@ -128,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
     spec_name: create_runtime_str!("joystream-node"),
     impl_name: create_runtime_str!("joystream-node"),
     authoring_version: 5,
-    spec_version: 1,
+    spec_version: 2,
     impl_version: 0,
     apis: RUNTIME_API_VERSIONS,
 };

+ 39 - 23
src/roles/actors.rs

@@ -2,7 +2,7 @@ use crate::currency::{BalanceOf, GovernanceCurrency};
 use parity_codec_derive::{Decode, Encode};
 use rstd::prelude::*;
 use runtime_primitives::traits::{As, Bounded, MaybeDebug, Zero};
-use srml_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons};
+use srml_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason};
 use srml_support::{decl_event, decl_module, decl_storage, ensure, StorageMap, StorageValue};
 use system::{self, ensure_signed};
 
@@ -212,19 +212,29 @@ impl<T: Trait> Module<T> {
         role: Role,
         member_id: MemberId<T>,
         unbonding_period: T::BlockNumber,
+        stake: BalanceOf<T>,
     ) {
         // simple unstaking ...only applying unbonding period
-        let value = T::Currency::free_balance(&actor_account);
-        T::Currency::set_lock(
-            STAKING_ID,
+        Self::update_lock(
             &actor_account,
-            value,
+            stake,
             <system::Module<T>>::block_number() + unbonding_period,
-            WithdrawReasons::all(),
         );
 
         Self::remove_actor_from_service(actor_account, role, member_id);
     }
+
+    // Locks account and only allows paying for transaction fees. Account cannot
+    // transfer or reserve funds.
+    fn update_lock(account: &T::AccountId, stake: BalanceOf<T>, until: T::BlockNumber) {
+        T::Currency::set_lock(
+            STAKING_ID,
+            account,
+            stake,
+            until,
+            WithdrawReasons::all() & !(WithdrawReason::TransactionPayment | WithdrawReason::Fee),
+        );
+    }
 }
 
 impl<T: Trait> Roles<T> for Module<T> {
@@ -277,25 +287,23 @@ decl_module! {
                     for actor in accounts.into_iter().map(|account| Self::actor_by_account_id(account)) {
                         if let Some(actor) = actor {
                             if now > actor.joined_at + params.reward_period {
-                                // send reward to member account - not the actor account
-                                if let Ok(member_account) = T::Members::lookup_account_by_member_id(actor.member_id) {
-                                    let _ = T::Currency::deposit_into_existing(&member_account, params.reward);
+                                // reward can top up balance if it is below minimum stake requirement
+                                // this guarantees overtime that actor always covers the minimum stake and
+                                // has enough balance to pay for tx fees
+                                let balance = T::Currency::free_balance(&actor.account);
+                                if balance < params.min_stake {
+                                    let _ = T::Currency::deposit_into_existing(&actor.account, params.reward);
+                                } else {
+                                    // otherwise it should go the the member account
+                                    if let Ok(member_account) = T::Members::lookup_account_by_member_id(actor.member_id) {
+                                        let _ = T::Currency::deposit_into_existing(&member_account, params.reward);
+                                    }
                                 }
                             }
                         }
                     }
                 }
             }
-
-            // eject actors not staking the minimum
-            // iterating over available roles, so if a role has been removed at some point
-            // and an actor hasn't unstaked .. this will not apply to them.. which doesn't really matter
-            // because they are no longer incentivised to stay in the role anyway
-            // TODO: this needs a bit more preparation. The right time to check for each actor is different, as they enter
-            // role at different times.
-            // for role in Self::available_roles().iter() {
-            // }
-
         }
 
         pub fn role_entry_request(origin, role: Role, member_id: MemberId<T>) {
@@ -349,8 +357,9 @@ decl_module! {
 
             <AccountIdsByRole<T>>::mutate(role, |accounts| accounts.push(actor_account.clone()));
             <AccountIdsByMemberId<T>>::mutate(&member_id, |accounts| accounts.push(actor_account.clone()));
-            let value = T::Currency::free_balance(&actor_account);
-            T::Currency::set_lock(STAKING_ID, &actor_account, value, T::BlockNumber::max_value(), WithdrawReasons::all());
+
+            // Lock minimum stake, but allow spending for transaction fees
+            Self::update_lock(&actor_account, role_parameters.min_stake, T::BlockNumber::max_value());
             <ActorByAccountId<T>>::insert(&actor_account, Actor {
                 member_id,
                 account: actor_account.clone(),
@@ -376,13 +385,20 @@ decl_module! {
 
             let role_parameters = Self::ensure_role_parameters(actor.role)?;
 
-            Self::apply_unstake(actor.account.clone(), actor.role, actor.member_id, role_parameters.unbonding_period);
+            Self::apply_unstake(actor.account.clone(), actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
 
             Self::deposit_event(RawEvent::Unstaked(actor.account, actor.role));
         }
 
         pub fn set_role_parameters(role: Role, params: RoleParameters<T>) {
+            let new_stake = params.min_stake.clone();
             <Parameters<T>>::insert(role, params);
+            // Update locks for all actors in the role. The lock for each account is already until max_value
+            // It doesn't affect actors which are unbonding, they should have already been removed from AccountIdsByRole
+            let accounts = Self::account_ids_by_role(role);
+            for account in accounts.into_iter() {
+                Self::update_lock(&account, new_stake, T::BlockNumber::max_value());
+            }
         }
 
         pub fn set_available_roles(roles: Vec<Role>) {
@@ -405,7 +421,7 @@ decl_module! {
             let member_id = T::Members::lookup_member_id(&actor_account)?;
             let actor = Self::ensure_actor_is_member(&actor_account, member_id)?;
             let role_parameters = Self::ensure_role_parameters(actor.role)?;
-            Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period);
+            Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
         }
     }
 }

+ 15 - 7
src/storage/data_directory.rs

@@ -5,7 +5,7 @@ use parity_codec::Codec;
 use parity_codec_derive::{Decode, Encode};
 use rstd::prelude::*;
 use runtime_primitives::traits::{
-    As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic, MaybeDisplay
+    As, MaybeDebug, MaybeDisplay, MaybeSerializeDebug, Member, SimpleArithmetic,
 };
 use srml_support::{
     decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap, StorageValue,
@@ -17,8 +17,16 @@ pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
 
     type ContentId: Parameter + Member + MaybeSerializeDebug + MaybeDisplay + Copy + Ord + Default;
 
-    type SchemaId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
-        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+    type SchemaId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + MaybeSerializeDebug
+        + PartialEq;
 
     type Members: Members<Self>;
     type Roles: Roles<Self>;
@@ -61,7 +69,6 @@ pub struct DataObject<T: Trait> {
     pub size: u64,
     pub liaison: T::AccountId,
     pub liaison_judgement: LiaisonJudgement,
-
     // TODO signing_key: public key supplied by the uploader,
     // they sigh the content with this key
 
@@ -302,7 +309,6 @@ impl<T: Trait> ContentIdExists<T> for Module<T> {
 }
 
 impl<T: Trait> Module<T> {
-
     fn current_block_and_time() -> BlockAndTime<T> {
         BlockAndTime {
             block: <system::Module<T>>::block_number(),
@@ -373,7 +379,8 @@ mod tests {
             assert!(res.is_err());
 
             // However, with the liaison as origin it should.
-            let res = TestDataDirectory::accept_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
+            let res =
+                TestDataDirectory::accept_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
             assert!(res.is_ok());
         });
     }
@@ -401,7 +408,8 @@ mod tests {
             assert!(res.is_err());
 
             // However, with the liaison as origin it should.
-            let res = TestDataDirectory::reject_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
+            let res =
+                TestDataDirectory::reject_content(Origin::signed(TEST_MOCK_LIAISON), content_id);
             assert!(res.is_ok());
         });
     }

+ 12 - 5
src/storage/data_object_storage_registry.rs

@@ -14,8 +14,16 @@ pub trait Trait: timestamp::Trait + system::Trait + DDTrait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
     // TODO deprecated
-    type DataObjectStorageRelationshipId: Parameter + Member + SimpleArithmetic + Codec 
-        + Default + Copy + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+    type DataObjectStorageRelationshipId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + MaybeSerializeDebug
+        + PartialEq;
 
     type Members: Members<Self>;
     type Roles: Roles<Self>;
@@ -43,7 +51,7 @@ pub struct DataObjectStorageRelationship<T: Trait> {
 
 decl_storage! {
     trait Store for Module<T: Trait> as DataObjectStorageRegistry {
-        
+
         // TODO deprecated
         // Start at this value
         pub FirstRelationshipId get(first_relationship_id) config(first_relationship_id): T::DataObjectStorageRelationshipId = T::DataObjectStorageRelationshipId::sa(DEFAULT_FIRST_RELATIONSHIP_ID);
@@ -84,7 +92,7 @@ decl_event! {
         // TODO deprecated
         DataObjectStorageRelationshipAdded(DataObjectStorageRelationshipId, ContentId, AccountId),
         DataObjectStorageRelationshipReadyUpdated(DataObjectStorageRelationshipId, bool),
-        
+
         // NEW & COOL
         StorageProviderAddedContent(AccountId, ContentId),
         StorageProviderRemovedContent(AccountId, ContentId),
@@ -92,7 +100,6 @@ decl_event! {
 }
 
 impl<T: Trait> ContentHasStorage<T> for Module<T> {
-
     // TODO deprecated
     fn has_storage_provider(which: &T::ContentId) -> bool {
         let dosr_list = Self::relationships_by_content_id(which);

+ 18 - 11
src/storage/data_object_type_registry.rs

@@ -8,8 +8,16 @@ use srml_support::{decl_event, decl_module, decl_storage, Parameter, StorageMap,
 pub trait Trait: system::Trait + MaybeDebug {
     type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
 
-    type DataObjectTypeId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
-        + As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
+    type DataObjectTypeId: Parameter
+        + Member
+        + SimpleArithmetic
+        + Codec
+        + Default
+        + Copy
+        + As<usize>
+        + As<u64>
+        + MaybeSerializeDebug
+        + PartialEq;
 }
 
 static MSG_DO_TYPE_NOT_FOUND: &str = "Data Object Type with the given ID not found.";
@@ -199,10 +207,7 @@ mod tests {
                 description: "bar".as_bytes().to_vec(),
                 active: false,
             };
-            let res = TestDataObjectTypeRegistry::update_data_object_type(
-                dot_id + 1,
-                updated1,
-            );
+            let res = TestDataObjectTypeRegistry::update_data_object_type(dot_id + 1, updated1);
             assert!(res.is_err());
 
             // Finally with an existing ID, it should work.
@@ -210,8 +215,7 @@ mod tests {
                 description: "bar".as_bytes().to_vec(),
                 active: false,
             };
-            let res =
-                TestDataObjectTypeRegistry::update_data_object_type(dot_id, updated3);
+            let res = TestDataObjectTypeRegistry::update_data_object_type(dot_id, updated3);
             assert!(res.is_ok());
             assert_eq!(
                 *System::events().last().unwrap(),
@@ -248,7 +252,8 @@ mod tests {
             );
 
             // Retrieve, and ensure it's not active.
-            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data =
+                TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(!data.unwrap().active);
 
@@ -270,7 +275,8 @@ mod tests {
             );
 
             // Ensure that the item is actually activated.
-            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data =
+                TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(data.unwrap().active);
 
@@ -279,7 +285,8 @@ mod tests {
                 TEST_FIRST_DATA_OBJECT_TYPE_ID,
             );
             assert!(res.is_ok());
-            let data = TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
+            let data =
+                TestDataObjectTypeRegistry::data_object_types(TEST_FIRST_DATA_OBJECT_TYPE_ID);
             assert!(data.is_some());
             assert!(!data.unwrap().active);
         });

+ 1 - 3
src/storage/mock.rs

@@ -1,8 +1,6 @@
 #![cfg(test)]
 
-pub use super::{
-    data_directory, data_object_storage_registry, data_object_type_registry,
-};
+pub use super::{data_directory, data_object_storage_registry, data_object_type_registry};
 use crate::currency::GovernanceCurrency;
 use crate::roles::actors;
 use crate::traits;

+ 3 - 3
wasm/Cargo.lock

@@ -756,7 +756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "joystream-node-runtime"
-version = "5.1.0"
+version = "5.2.0"
 dependencies = [
  "parity-codec 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parity-codec-derive 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -789,9 +789,9 @@ dependencies = [
 
 [[package]]
 name = "joystream-node-runtime-wasm"
-version = "1.0.1"
+version = "5.2.0"
 dependencies = [
- "joystream-node-runtime 5.1.0",
+ "joystream-node-runtime 5.2.0",
 ]
 
 [[package]]

+ 1 - 1
wasm/Cargo.toml

@@ -20,4 +20,4 @@ path = '..'
 authors = ['Joystream']
 edition = '2018'
 name = 'joystream-node-runtime-wasm'
-version = '1.0.1'
+version = '5.2.0'