From d09cfbc70c397b7431923d3d6f46431584ca21b1 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 22 Mar 2019 08:56:09 +0200 Subject: [PATCH 01/26] refactor IsActiveMember trait to Members --- src/governance/election.rs | 6 +++--- src/governance/mock.rs | 10 +++------- src/governance/proposals.rs | 16 ++++++---------- src/lib.rs | 4 ++-- src/traits.rs | 4 ++-- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/governance/election.rs b/src/governance/election.rs index e6d0f109e7..0cc47a5c49 100644 --- a/src/governance/election.rs +++ b/src/governance/election.rs @@ -17,14 +17,14 @@ use super::sealed_vote::SealedVote; pub use super::{ GovernanceCurrency, BalanceOf }; use super::council; -use crate::traits::{IsActiveMember}; +use crate::traits::{Members}; pub trait Trait: system::Trait + council::Trait + GovernanceCurrency { type Event: From> + Into<::Event>; type CouncilElected: CouncilElected>, Self::BlockNumber>; - type IsActiveMember: IsActiveMember; + type Members: Members; } #[derive(Clone, Copy, Encode, Decode)] @@ -154,7 +154,7 @@ impl Module { fn can_participate(sender: &T::AccountId) -> bool { - !T::Currency::free_balance(sender).is_zero() && T::IsActiveMember::is_active_member(sender) + !T::Currency::free_balance(sender).is_zero() && T::Members::is_active_member(sender) } // PUBLIC IMMUTABLES diff --git a/src/governance/mock.rs b/src/governance/mock.rs index ce5bb9bd4a..5f9025b0fa 100644 --- a/src/governance/mock.rs +++ b/src/governance/mock.rs @@ -18,12 +18,8 @@ impl_outer_origin! { pub enum Origin for Test {} } -pub struct AnyAccountIsMember {} -impl traits::IsActiveMember for AnyAccountIsMember { - fn is_active_member(who: &T::AccountId) -> bool { - true - } -} +pub struct MockMembers {} +impl traits::Members for MockMembers {} // default implementation // default trait implementation - any account is not a member // impl traits::IsActiveMember for () {} @@ -65,7 +61,7 @@ impl election::Trait for Test { type CouncilElected = (Council,); - type IsActiveMember = AnyAccountIsMember; + type Members = MockMembers; } impl balances::Trait for Test { diff --git a/src/governance/proposals.rs b/src/governance/proposals.rs index 58878dc9e9..1e9482511a 100644 --- a/src/governance/proposals.rs +++ b/src/governance/proposals.rs @@ -8,7 +8,7 @@ use rstd::prelude::*; use super::council; pub use super::{ GovernanceCurrency, BalanceOf }; -use crate::traits::{IsActiveMember}; +use crate::traits::{Members}; const DEFAULT_APPROVAL_QUORUM: u32 = 60; const DEFAULT_MIN_STAKE: u64 = 100; @@ -117,7 +117,7 @@ pub trait Trait: timestamp::Trait + council::Trait + GovernanceCurrency { /// The overarching event type. type Event: From> + Into<::Event>; - type IsActiveMember: IsActiveMember; + type Members: Members; } decl_event!( @@ -349,7 +349,7 @@ impl Module { } fn can_participate(sender: T::AccountId) -> bool { - !T::Currency::free_balance(&sender).is_zero() && T::IsActiveMember::is_active_member(&sender) + !T::Currency::free_balance(&sender).is_zero() && T::Members::is_active_member(&sender) } fn is_councilor(sender: &T::AccountId) -> bool { @@ -615,15 +615,11 @@ mod tests { impl Trait for Test { type Event = (); - type IsActiveMember = AnyAccountIsMember; + type Members = MockMembership; } - pub struct AnyAccountIsMember {} - impl IsActiveMember for AnyAccountIsMember { - fn is_active_member(who: &T::AccountId) -> bool { - true - } - } + pub struct MockMembership {} + impl Members for MockMembership {} // default implementation type System = system::Module; type Balances = balances::Module; diff --git a/src/lib.rs b/src/lib.rs index 93e6ba1afc..3b4ba8f8fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,13 +212,13 @@ impl governance::GovernanceCurrency for Runtime { impl governance::proposals::Trait for Runtime { type Event = Event; - type IsActiveMember = Membership; + type Members = Membership; } impl governance::election::Trait for Runtime { type Event = Event; type CouncilElected = (Council,); - type IsActiveMember = Membership; + type Members = Membership; } impl governance::council::Trait for Runtime { diff --git a/src/traits.rs b/src/traits.rs index 8b24b830da..1aedc627c3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,8 +2,8 @@ use system; -pub trait IsActiveMember { +pub trait Members { fn is_active_member(account_id: &T::AccountId) -> bool { - false + true } } \ No newline at end of file From af5863075a640a33e6c07dcfaafc54dadc81c9ff Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 22 Mar 2019 10:51:35 +0200 Subject: [PATCH 02/26] staked roles --- src/governance/mock.rs | 8 ++- src/governance/proposals.rs | 4 +- src/lib.rs | 12 +++- src/membership/registry.rs | 15 ++++- src/roles/actors.rs | 128 ++++++++++++++++++++++++++++++++++++ src/roles/mod.rs | 6 ++ src/traits.rs | 10 +++ 7 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 src/roles/actors.rs create mode 100644 src/roles/mod.rs diff --git a/src/governance/mock.rs b/src/governance/mock.rs index 5f9025b0fa..6929474c9c 100644 --- a/src/governance/mock.rs +++ b/src/governance/mock.rs @@ -18,8 +18,10 @@ impl_outer_origin! { pub enum Origin for Test {} } -pub struct MockMembers {} -impl traits::Members for MockMembers {} // default implementation +pub struct MockMembership {} +impl traits::Members for MockMembership { + type Id = u32; +} // default trait implementation - any account is not a member // impl traits::IsActiveMember for () {} @@ -61,7 +63,7 @@ impl election::Trait for Test { type CouncilElected = (Council,); - type Members = MockMembers; + type Members = MockMembership; } impl balances::Trait for Test { diff --git a/src/governance/proposals.rs b/src/governance/proposals.rs index 1e9482511a..892d355c52 100644 --- a/src/governance/proposals.rs +++ b/src/governance/proposals.rs @@ -619,7 +619,9 @@ mod tests { } pub struct MockMembership {} - impl Members for MockMembership {} // default implementation + impl Members for MockMembership { + type Id = u32; + } type System = system::Module; type Balances = balances::Module; diff --git a/src/lib.rs b/src/lib.rs index 3b4ba8f8fa..0ea2c94861 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,8 @@ mod membership; use membership::registry; mod traits; mod migration; +mod roles; +use roles::actors; use rstd::prelude::*; #[cfg(feature = "std")] @@ -180,11 +182,11 @@ impl balances::Trait for Runtime { /// The type for recording an account's balance. type Balance = u128; /// What to do if an account's free balance gets zeroed. - type OnFreeBalanceZero = Staking; + type OnFreeBalanceZero = Staking; // + roles /// What to do if a new account is created. type OnNewAccount = Indices; /// Restrict whether an account can transfer funds. We don't place any further restrictions. - type EnsureAccountLiquid = Staking; + type EnsureAccountLiquid = Staking; // Change this to look at both staking and roles::actors /// The uniquitous event type. type Event = Event; } @@ -241,6 +243,11 @@ impl migration::Trait for Runtime { type Event = Event; } +impl actors::Trait for Runtime { + type Event = Event; + type Members = Membership; +} + construct_runtime!( pub enum Runtime with Log(InternalLog: DigestItem) where Block = Block, @@ -263,6 +270,7 @@ construct_runtime!( Memo: memo::{Module, Call, Storage, Event}, Membership: registry::{Module, Call, Storage, Event, Config}, Migration: migration::{Module, Call, Storage, Event}, + Actors: actors::{Module, Call, Storage, Event}, } ); diff --git a/src/membership/registry.rs b/src/membership/registry.rs index a088ce275a..65aa8021c5 100644 --- a/src/membership/registry.rs +++ b/src/membership/registry.rs @@ -173,7 +173,9 @@ impl Module { } } -impl traits::IsActiveMember for Module { +impl traits::Members for Module { + type Id = T::MemberId; + fn is_active_member(who: &T::AccountId) -> bool { match Self::ensure_is_member(who) .and_then(|member_id| Self::ensure_profile(member_id)) @@ -182,6 +184,11 @@ impl traits::IsActiveMember for Module { Err(err) => false } } + + fn lookup_member_id(who: &T::AccountId) -> Result { + let id = Self::ensure_is_member(who)?; + Ok(id) + } } decl_module! { @@ -198,6 +205,8 @@ decl_module! { // ensure key not associated with an existing membership Self::ensure_not_member(&who)?; + // ensure account is not in a bonded role + // ensure paid_terms_id is active let terms = Self::ensure_active_terms_id(paid_terms_id)?; @@ -270,6 +279,8 @@ decl_module! { // ensure key not associated with an existing membership Self::ensure_not_member(&new_member)?; + // ensure account is not in a bonded role + let user_info = Self::check_user_registration_info(user_info)?; // ensure handle is not already registered @@ -292,7 +303,7 @@ impl Module { Ok(()) } - fn ensure_is_member(who: &T::AccountId) -> Result { + pub fn ensure_is_member(who: &T::AccountId) -> Result { let member_id = Self::member_id_by_account_id(who).ok_or("no member id found for accountid")?; Ok(member_id) } diff --git a/src/roles/actors.rs b/src/roles/actors.rs new file mode 100644 index 0000000000..b7bed380c6 --- /dev/null +++ b/src/roles/actors.rs @@ -0,0 +1,128 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use rstd::prelude::*; +use parity_codec::Codec; +use parity_codec_derive::{Encode, Decode}; +use srml_support::{StorageMap, StorageValue, dispatch, decl_module, decl_storage, decl_event, ensure, Parameter}; +use srml_support::traits::{Currency}; +use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug}; +use system::{self, ensure_signed}; +use crate::governance::{GovernanceCurrency, BalanceOf }; +use crate::membership::registry; + +use crate::traits::{Members}; + +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)] +pub enum Role { + Storage, +} + +#[derive(Encode, Decode, Clone)] +pub struct RoleParameters { + min_stake: BalanceOf, + max_actors: u32, + // minimum actors to maintain - if role is unstaking + // and remaining actors would be less that this value - prevent or punish for unstaking + min_actors: u32, + // fixed amount of tokens paid to actors' primary account + reward_per_block: BalanceOf, + // payouts are made at this block interval + reward_period: T::BlockNumber, + unbonding_period: T::BlockNumber, + // "startup" time allowed for roles that need to sync their infrastructure + // with other providers before they are considered in service and punishable for + // not delivering required level of service. + //startup_grace_period: T::BlockNumber, +} + +#[derive(Encode, Decode, Clone)] +pub struct Actor { + member_id: >::Id, + role: Role, + role_key: T::AccountId, + joined_at: T::BlockNumber, + //startup_grace_period_ends_at: T::BlockNumber, +} + +pub trait Trait: system::Trait + GovernanceCurrency { + type Event: From> + Into<::Event>; + + type Members: Members; +} + +decl_storage! { + trait Store for Module as Actors { + /// requirements to enter and maintain status in roles + Parameters get(parameters) : map Role => Option>; + + /// roles members can enter into + AvailableRoles get(available_roles) : Vec = vec![Role::Storage]; + + /// role keys mapped to their actor + Actors get(actors) : map T::AccountId => Option>; + + /// active actors for each role + ActorsByRole get(actors_by_role) : map Role => Vec; + + /// role keys associated with a member id + MemberRoleKeys get(member_role_keys) : map >::Id => Vec; + + /// tokens bonded until given block number + Bondage get(bondage) : map T::AccountId => T::BlockNumber; + } +} + +decl_event! { + pub enum Event where + ::AccountId { + ActorJoined(AccountId, Role), + } +} + +impl Module { + fn role_is_available(role: Role) -> bool { + Self::available_roles().into_iter().any(|r| role == r) + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + fn deposit_event() = default; + + /// Member staking to enter a role + pub fn stake(origin, role: Role, role_key: T::AccountId) { + let sender = ensure_signed(origin)?; + let member_id = T::Members::lookup_member_id(&sender)?; + ensure!(!>::exists(&sender), ""); + + ensure!(Self::role_is_available(role), ""); + + let role_parameters = Self::parameters(role); + ensure!(role_parameters.is_some(), ""); + let role_parameters = role_parameters.unwrap(); + + let actors_in_role = Self::actors_by_role(role); + + // ensure there is an empty slot for the role + ensure!(actors_in_role.len() < role_parameters.max_actors as usize, ""); + + // ensure the role key has enough balance + ensure!(T::Currency::free_balance(&role_key) >= role_parameters.min_stake, ""); + + >::mutate(role, |actors| actors.push(role_key.clone())); + >::mutate(&member_id, |keys| keys.push(role_key.clone())); + >::insert(&role_key, T::BlockNumber::max_value()); + >::insert(&role_key, Actor { + member_id, + role_key: role_key.clone(), + role, + joined_at: >::block_number() + }); + + Self::deposit_event(RawEvent::ActorJoined(role_key, role)); + } + + // pub fn set_role_parameters(role: Role, params: RoleParameters) {} + // pub fn set_available_roles(Vec) {} + } +} \ No newline at end of file diff --git a/src/roles/mod.rs b/src/roles/mod.rs new file mode 100644 index 0000000000..c878c3a215 --- /dev/null +++ b/src/roles/mod.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod actors; + +//mod mock; +//mod tests; \ No newline at end of file diff --git a/src/traits.rs b/src/traits.rs index 1aedc627c3..cefa908ec5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,9 +1,19 @@ #![cfg_attr(not(feature = "std"), no_std)] use system; +use parity_codec::Codec; +use srml_support::{Parameter}; +use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug}; pub trait Members { + type Id : Parameter + Member + SimpleArithmetic + Codec + Default + Copy + + As + As + MaybeSerializeDebug + PartialEq; + fn is_active_member(account_id: &T::AccountId) -> bool { true } + + fn lookup_member_id(account_id: &T::AccountId) -> Result { + Err("member not found") + } } \ No newline at end of file From d46fc636596dab36646f3a2902a47ae900b0edc6 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 22 Mar 2019 12:41:21 +0200 Subject: [PATCH 03/26] update Members and Roles traits --- src/governance/mock.rs | 16 +++++++++++----- src/governance/proposals.rs | 29 +++++++++++++++++++---------- src/lib.rs | 1 + src/membership/mock.rs | 1 + src/membership/registry.rs | 8 ++++++-- src/roles/actors.rs | 8 +++++++- src/traits.rs | 20 ++++++++++++++++++-- 7 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/governance/mock.rs b/src/governance/mock.rs index 6929474c9c..8cd1a604f2 100644 --- a/src/governance/mock.rs +++ b/src/governance/mock.rs @@ -3,7 +3,7 @@ use rstd::prelude::*; pub use super::{election, council, proposals, GovernanceCurrency}; pub use system; -use crate::traits; +use crate::traits::{Members}; pub use primitives::{H256, Blake2Hasher}; pub use runtime_primitives::{ @@ -19,13 +19,19 @@ impl_outer_origin! { } pub struct MockMembership {} -impl traits::Members for MockMembership { +impl Members for MockMembership { type Id = u32; + fn is_active_member(who: &T::AccountId) -> bool { + // all accounts are considered members. + // There is currently no test coverage for non-members. + // Should add some coverage, and update this method to reflect which accounts are or are not members + true + } + fn lookup_member_id(account_id: &T::AccountId) -> Result { + Err("not implemented!") + } } -// default trait implementation - any account is not a member -// impl traits::IsActiveMember for () {} - // For testing the module, we construct most of a mock runtime. This means // first constructing a configuration type (`Test`) which `impl`s each of the // configuration traits of modules we want to use. diff --git a/src/governance/proposals.rs b/src/governance/proposals.rs index 892d355c52..de3a69b93f 100644 --- a/src/governance/proposals.rs +++ b/src/governance/proposals.rs @@ -1,4 +1,4 @@ -use srml_support::{StorageValue, StorageMap, dispatch::Result, decl_module, decl_event, decl_storage, ensure}; +use srml_support::{StorageValue, StorageMap, dispatch, decl_module, decl_event, decl_storage, ensure}; use srml_support::traits::{Currency}; use primitives::{storage::well_known_keys}; use runtime_primitives::traits::{As, Hash, Zero}; @@ -368,7 +368,7 @@ impl Module { Self::current_block() >= proposed_at + Self::voting_period() } - fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> Result { + fn _process_vote(voter: T::AccountId, proposal_id: u32, vote: VoteKind) -> dispatch::Result { let new_vote = (voter.clone(), vote.clone()); if >::exists(proposal_id) { // Append a new vote to other votes on this proposal: @@ -382,7 +382,7 @@ impl Module { Ok(()) } - fn end_block(now: T::BlockNumber) -> Result { + fn end_block(now: T::BlockNumber) -> dispatch::Result { // TODO refactor this method @@ -395,7 +395,7 @@ impl Module { } /// Get the voters for the current proposal. - pub fn tally(/* proposal_id: u32 */) -> Result { + pub fn tally(/* proposal_id: u32 */) -> dispatch::Result { let councilors: u32 = Self::councilors_count(); let quorum: u32 = Self::approval_quorum_seats(); @@ -475,7 +475,7 @@ impl Module { } /// Updates proposal status and removes proposal from active ids. - fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> Result { + fn _update_proposal_status(proposal_id: u32, new_status: ProposalStatus) -> dispatch::Result { let all_active_ids = Self::active_proposal_ids(); let all_len = all_active_ids.len(); let other_active_ids: Vec = all_active_ids @@ -503,7 +503,7 @@ impl Module { } /// Slash a proposal. The staked deposit will be slashed. - fn _slash_proposal(proposal_id: u32) -> Result { + fn _slash_proposal(proposal_id: u32) -> dispatch::Result { let proposal = Self::proposals(proposal_id); // Slash proposer's stake: @@ -513,7 +513,7 @@ impl Module { } /// Reject a proposal. The staked deposit will be returned to a proposer. - fn _reject_proposal(proposal_id: u32) -> Result { + fn _reject_proposal(proposal_id: u32) -> dispatch::Result { let proposal = Self::proposals(proposal_id); let proposer = proposal.proposer; @@ -529,7 +529,7 @@ impl Module { } /// Approve a proposal. The staked deposit will be returned. - fn _approve_proposal(proposal_id: u32) -> Result { + fn _approve_proposal(proposal_id: u32) -> dispatch::Result { let proposal = Self::proposals(proposal_id); let wasm_code = Self::wasm_code_by_hash(proposal.wasm_hash); @@ -621,6 +621,15 @@ mod tests { pub struct MockMembership {} impl Members for MockMembership { type Id = u32; + fn is_active_member(who: &T::AccountId) -> bool { + // all accounts are considered members. + // There is currently no test coverage for non-members. + // Should add some coverage, and update this method to reflect which accounts are or are not members + true + } + fn lookup_member_id(account_id: &T::AccountId) -> Result { + Err("not implemented!") + } } type System = system::Module; @@ -714,7 +723,7 @@ mod tests { b"Proposal Wasm Code".to_vec() } - fn _create_default_proposal() -> Result { + fn _create_default_proposal() -> dispatch::Result { _create_proposal(None, None, None, None, None) } @@ -724,7 +733,7 @@ mod tests { name: Option>, description: Option>, wasm_code: Option> - ) -> Result { + ) -> dispatch::Result { Proposals::create_proposal( Origin::signed(origin.unwrap_or(PROPOSER1)), stake.unwrap_or(min_stake()), diff --git a/src/lib.rs b/src/lib.rs index 0ea2c94861..d042139edb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,6 +237,7 @@ impl membership::registry::Trait for Runtime { type MemberId = u64; type PaidTermId = u64; type SubscriptionId = u64; + type Roles = Actors; } impl migration::Trait for Runtime { diff --git a/src/membership/mock.rs b/src/membership/mock.rs index c85efaf4c6..0d05bb03b2 100644 --- a/src/membership/mock.rs +++ b/src/membership/mock.rs @@ -74,6 +74,7 @@ impl registry::Trait for Test { type MemberId = u32; type PaidTermId = u32; type SubscriptionId = u32; + type Roles = (); } pub struct ExtBuilder { diff --git a/src/membership/registry.rs b/src/membership/registry.rs index 65aa8021c5..af8f1d5a53 100644 --- a/src/membership/registry.rs +++ b/src/membership/registry.rs @@ -9,7 +9,7 @@ use runtime_primitives::traits::{Zero, SimpleArithmetic, As, Member, MaybeSerial use system::{self, ensure_signed}; use crate::governance::{GovernanceCurrency, BalanceOf }; use {timestamp}; -use crate::traits; +use crate::traits::{Members, Roles}; pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait { type Event: From> + Into<::Event>; @@ -22,6 +22,8 @@ pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait { type SubscriptionId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy + As + As + MaybeSerializeDebug + PartialEq; + + type Roles: Roles; } const DEFAULT_FIRST_MEMBER_ID: u64 = 1; @@ -173,7 +175,7 @@ impl Module { } } -impl traits::Members for Module { +impl Members for Module { type Id = T::MemberId; fn is_active_member(who: &T::AccountId) -> bool { @@ -206,6 +208,7 @@ decl_module! { Self::ensure_not_member(&who)?; // ensure account is not in a bonded role + ensure!(!T::Roles::is_role_key(&who), "role key cannot be used for membership"); // ensure paid_terms_id is active let terms = Self::ensure_active_terms_id(paid_terms_id)?; @@ -280,6 +283,7 @@ decl_module! { Self::ensure_not_member(&new_member)?; // ensure account is not in a bonded role + ensure!(!T::Roles::is_role_key(&new_member), "role key cannot be used for membership"); let user_info = Self::check_user_registration_info(user_info)?; diff --git a/src/roles/actors.rs b/src/roles/actors.rs index b7bed380c6..728a695bd1 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -10,7 +10,7 @@ use system::{self, ensure_signed}; use crate::governance::{GovernanceCurrency, BalanceOf }; use crate::membership::registry; -use crate::traits::{Members}; +use crate::traits::{Members, Roles}; #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)] pub enum Role { @@ -85,6 +85,12 @@ impl Module { } } +impl Roles for Module { + fn is_role_key(account_id: &T::AccountId) -> bool { + Self::actors(account_id).is_some() + } +} + decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; diff --git a/src/traits.rs b/src/traits.rs index cefa908ec5..06d59512cd 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,15 +5,31 @@ use parity_codec::Codec; use srml_support::{Parameter}; use runtime_primitives::traits::{SimpleArithmetic, As, Member, MaybeSerializeDebug}; +// Members pub trait Members { type Id : Parameter + Member + SimpleArithmetic + Codec + Default + Copy + As + As + MaybeSerializeDebug + PartialEq; + fn is_active_member(account_id: &T::AccountId) -> bool; + + fn lookup_member_id(account_id: &T::AccountId) -> Result; +} + +impl Members for () { + type Id = u32; fn is_active_member(account_id: &T::AccountId) -> bool { - true + false } - fn lookup_member_id(account_id: &T::AccountId) -> Result { Err("member not found") } +} + +// Roles +pub trait Roles { + fn is_role_key(account_id: &T::AccountId) -> bool; +} + +impl Roles for () { + fn is_role_key(_who: &T::AccountId) -> bool { false } } \ No newline at end of file From 14f7cf5b18b821c55ebd45bf5a38382af65e0ec8 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 22 Mar 2019 13:16:19 +0200 Subject: [PATCH 04/26] staked role accounts illiquid --- src/lib.rs | 4 ++-- src/roles/actors.rs | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d042139edb..8bb3f8e333 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,11 +182,11 @@ impl balances::Trait for Runtime { /// The type for recording an account's balance. type Balance = u128; /// What to do if an account's free balance gets zeroed. - type OnFreeBalanceZero = Staking; // + roles + type OnFreeBalanceZero = Staking; /// What to do if a new account is created. type OnNewAccount = Indices; /// Restrict whether an account can transfer funds. We don't place any further restrictions. - type EnsureAccountLiquid = Staking; // Change this to look at both staking and roles::actors + type EnsureAccountLiquid = (Staking, Actors); /// The uniquitous event type. type Event = Event; } diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 728a695bd1..d0b95d9394 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -4,7 +4,7 @@ use rstd::prelude::*; use parity_codec::Codec; use parity_codec_derive::{Encode, Decode}; use srml_support::{StorageMap, StorageValue, dispatch, decl_module, decl_storage, decl_event, ensure, Parameter}; -use srml_support::traits::{Currency}; +use srml_support::traits::{Currency, EnsureAccountLiquid}; use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug}; use system::{self, ensure_signed}; use crate::governance::{GovernanceCurrency, BalanceOf }; @@ -131,4 +131,14 @@ decl_module! { // pub fn set_role_parameters(role: Role, params: RoleParameters) {} // pub fn set_available_roles(Vec) {} } +} + +impl EnsureAccountLiquid for Module { + fn ensure_account_liquid(who: &T::AccountId) -> dispatch::Result { + if Self::bondage(who) <= >::block_number() { + Ok(()) + } else { + Err("cannot transfer illiquid funds") + } + } } \ No newline at end of file From c1a06772aeef0048eee707b813dbd7c90647b2b3 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 22 Mar 2019 18:58:04 +0200 Subject: [PATCH 05/26] refactored staked roles --- src/membership/registry.rs | 4 +- src/roles/actors.rs | 157 +++++++++++++++++++++++++++++-------- src/traits.rs | 4 +- 3 files changed, 130 insertions(+), 35 deletions(-) diff --git a/src/membership/registry.rs b/src/membership/registry.rs index af8f1d5a53..c20377ee4f 100644 --- a/src/membership/registry.rs +++ b/src/membership/registry.rs @@ -208,7 +208,7 @@ decl_module! { Self::ensure_not_member(&who)?; // ensure account is not in a bonded role - ensure!(!T::Roles::is_role_key(&who), "role key cannot be used for membership"); + ensure!(!T::Roles::is_role_account(&who), "role key cannot be used for membership"); // ensure paid_terms_id is active let terms = Self::ensure_active_terms_id(paid_terms_id)?; @@ -283,7 +283,7 @@ decl_module! { Self::ensure_not_member(&new_member)?; // ensure account is not in a bonded role - ensure!(!T::Roles::is_role_key(&new_member), "role key cannot be used for membership"); + ensure!(!T::Roles::is_role_account(&new_member), "role key cannot be used for membership"); let user_info = Self::check_user_registration_info(user_info)?; diff --git a/src/roles/actors.rs b/src/roles/actors.rs index d0b95d9394..3de8cec77b 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -19,29 +19,46 @@ pub enum Role { #[derive(Encode, Decode, Clone)] pub struct RoleParameters { + // minium balance required to stake to enter a role min_stake: BalanceOf, + + // the maximum number of spots available to fill for a role max_actors: u32, + // minimum actors to maintain - if role is unstaking // and remaining actors would be less that this value - prevent or punish for unstaking min_actors: u32, + // fixed amount of tokens paid to actors' primary account reward_per_block: BalanceOf, + // payouts are made at this block interval reward_period: T::BlockNumber, + + // how long tokens remain locked for after unstaking unbonding_period: T::BlockNumber, + + // minimum amount of time before being able to unstake + bonding_time: T::BlockNumber, + + // minimum period required to be in service. unbonding before this time is highly penalized + min_service_period: T::BlockNumber, + // "startup" time allowed for roles that need to sync their infrastructure // with other providers before they are considered in service and punishable for // not delivering required level of service. - //startup_grace_period: T::BlockNumber, + startup_grace_period: T::BlockNumber, + + // entry_request_fee: BalanceOf, } #[derive(Encode, Decode, Clone)] pub struct Actor { member_id: >::Id, role: Role, - role_key: T::AccountId, + account: T::AccountId, joined_at: T::BlockNumber, - //startup_grace_period_ends_at: T::BlockNumber, + // startup_grace_period_ends_at: T::BlockNumber, } pub trait Trait: system::Trait + GovernanceCurrency { @@ -55,27 +72,35 @@ decl_storage! { /// requirements to enter and maintain status in roles Parameters get(parameters) : map Role => Option>; - /// roles members can enter into - AvailableRoles get(available_roles) : Vec = vec![Role::Storage]; + /// the roles members can enter into + AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; - /// role keys mapped to their actor + /// actor accounts mapped to their actor Actors get(actors) : map T::AccountId => Option>; - /// active actors for each role - ActorsByRole get(actors_by_role) : map Role => Vec; + /// actor accounts associated with a role + AccountsByRole get(accounts_by_role) : map Role => Vec; - /// role keys associated with a member id - MemberRoleKeys get(member_role_keys) : map >::Id => Vec; + /// actor accounts associated with a member id + AccountsByMember get(accounts_by_member) : map >::Id => Vec; - /// tokens bonded until given block number + /// tokens locked until given block number Bondage get(bondage) : map T::AccountId => T::BlockNumber; + + /// First step before enter a role is registering intent with a new account/key. + /// This is done by sending a role_entry_request() from the new account. + /// The member must then send a stake() transaction to approve the request and enter the desired role. + /// This list is cleared every N blocks.. the account making the request will be bonded and must have + /// sufficient balance to cover the minimum stake for the role. + /// Bonding only occurs after successful entry into a role. + RoleEntryRequests get(role_entry_requests) : map T::AccountId => Option<(>::Id, Role)>; } } decl_event! { pub enum Event where ::AccountId { - ActorJoined(AccountId, Role), + Staked(AccountId, Role), } } @@ -83,11 +108,30 @@ impl Module { fn role_is_available(role: Role) -> bool { Self::available_roles().into_iter().any(|r| role == r) } + + fn ensure_actor(role_key: &T::AccountId) -> Result, &'static str> { + Self::actors(role_key).ok_or("not role key") + } + + fn ensure_actor_is_member(role_key: &T::AccountId, member_id: >::Id) + -> Result, &'static str> + { + let actor = Self::ensure_actor(role_key)?; + if actor.member_id == member_id { + Ok(actor) + } else { + Err("actor not owned by member") + } + } + + fn ensure_role_parameters(role: Role) -> Result, &'static str> { + Self::parameters(role).ok_or("no parameters for role") + } } impl Roles for Module { - fn is_role_key(account_id: &T::AccountId) -> bool { - Self::actors(account_id).is_some() + fn is_role_account(account_id: &T::AccountId) -> bool { + >::exists(account_id) || >::exists(account_id) || >::exists(account_id) } } @@ -95,37 +139,88 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - /// Member staking to enter a role - pub fn stake(origin, role: Role, role_key: T::AccountId) { + fn on_finalize(now: T::BlockNumber) { + // clear >::kill() every N blocks + + // payout rewards to actors + + // clear unbonded accounts + } + + pub fn role_entry_request(origin, role: Role, member_id: >::Id) { + let sender = ensure_signed(origin)?; + + ensure!(T::Members::lookup_member_id(&sender).is_err(), "account is a member"); + ensure!(!Self::is_role_account(&sender), "account already used"); + + ensure!(Self::role_is_available(role), "inactive role"); + + let role_parameters = Self::ensure_role_parameters(role)?; + + >::insert(&sender, (member_id, role)); + } + + /// Member activating entry request + pub fn stake(origin, role: Role, actor_account: T::AccountId) { let sender = ensure_signed(origin)?; let member_id = T::Members::lookup_member_id(&sender)?; - ensure!(!>::exists(&sender), ""); - ensure!(Self::role_is_available(role), ""); + ensure!(>::exists(&actor_account), "no role entry request matches"); + if let Some(entry_request) = Self::role_entry_requests(&actor_account) { + ensure!(entry_request.0 == member_id, "role entry mismatch - member id"); + ensure!(entry_request.1 == role, "role entry mismatch - role"); + } - let role_parameters = Self::parameters(role); - ensure!(role_parameters.is_some(), ""); - let role_parameters = role_parameters.unwrap(); + // make sure role is still available + ensure!(Self::role_is_available(role), ""); + let role_parameters = Self::ensure_role_parameters(role)?; - let actors_in_role = Self::actors_by_role(role); + let accounts_in_role = Self::accounts_by_role(role); // ensure there is an empty slot for the role - ensure!(actors_in_role.len() < role_parameters.max_actors as usize, ""); + ensure!(accounts_in_role.len() < role_parameters.max_actors as usize, "role slots full"); - // ensure the role key has enough balance - ensure!(T::Currency::free_balance(&role_key) >= role_parameters.min_stake, ""); + // ensure the actor account has enough balance + ensure!(T::Currency::free_balance(&actor_account) >= role_parameters.min_stake, ""); - >::mutate(role, |actors| actors.push(role_key.clone())); - >::mutate(&member_id, |keys| keys.push(role_key.clone())); - >::insert(&role_key, T::BlockNumber::max_value()); - >::insert(&role_key, Actor { + >::mutate(role, |accounts| accounts.push(actor_account.clone())); + >::mutate(&member_id, |accounts| accounts.push(actor_account.clone())); + >::insert(&actor_account, T::BlockNumber::max_value()); + >::insert(&actor_account, Actor { member_id, - role_key: role_key.clone(), + account: actor_account.clone(), role, joined_at: >::block_number() }); + >::remove(&actor_account); + + Self::deposit_event(RawEvent::Staked(actor_account, role)); + } + + pub fn unstake(origin, actor_account: T::AccountId) { + let sender = ensure_signed(origin)?; + let member_id = T::Members::lookup_member_id(&sender)?; + + let actor = Self::ensure_actor_is_member(&actor_account, member_id)?; + + let role_parameters = Self::ensure_role_parameters(actor.role)?; + + // simple unstaking ...only applying unbonding period + let accounts: Vec = Self::accounts_by_role(actor.role) + .into_iter() + .filter(|account| !(*account == actor.account)) + .collect(); + >::insert(actor.role, accounts); + + let accounts: Vec = Self::accounts_by_member(&member_id) + .into_iter() + .filter(|account| !(*account == actor.account)) + .collect(); + >::insert(&member_id, accounts); + + >::insert(&actor_account, >::block_number() + role_parameters.unbonding_period); - Self::deposit_event(RawEvent::ActorJoined(role_key, role)); + >::remove(&actor_account); } // pub fn set_role_parameters(role: Role, params: RoleParameters) {} diff --git a/src/traits.rs b/src/traits.rs index 06d59512cd..46d3db8671 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -27,9 +27,9 @@ impl Members for () { // Roles pub trait Roles { - fn is_role_key(account_id: &T::AccountId) -> bool; + fn is_role_account(account_id: &T::AccountId) -> bool; } impl Roles for () { - fn is_role_key(_who: &T::AccountId) -> bool { false } + fn is_role_account(_who: &T::AccountId) -> bool { false } } \ No newline at end of file From e16cef9fbf5fa616be4d75e8581b1f2869abe464 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Sun, 24 Mar 2019 00:01:05 +0200 Subject: [PATCH 06/26] admin methods for roles --- src/roles/actors.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 3de8cec77b..3454f62a03 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -17,10 +17,10 @@ pub enum Role { Storage, } -#[derive(Encode, Decode, Clone)] -pub struct RoleParameters { +#[derive(Encode, Decode, Copy, Clone, Debug, Eq, PartialEq)] +pub struct RoleParameters { // minium balance required to stake to enter a role - min_stake: BalanceOf, + min_stake: Balance, // the maximum number of spots available to fill for a role max_actors: u32, @@ -30,24 +30,24 @@ pub struct RoleParameters { min_actors: u32, // fixed amount of tokens paid to actors' primary account - reward_per_block: BalanceOf, + reward_per_block: Balance, // payouts are made at this block interval - reward_period: T::BlockNumber, + reward_period: BlockNumber, // how long tokens remain locked for after unstaking - unbonding_period: T::BlockNumber, + unbonding_period: BlockNumber, // minimum amount of time before being able to unstake - bonding_time: T::BlockNumber, + bonding_time: BlockNumber, // minimum period required to be in service. unbonding before this time is highly penalized - min_service_period: T::BlockNumber, + min_service_period: BlockNumber, // "startup" time allowed for roles that need to sync their infrastructure // with other providers before they are considered in service and punishable for // not delivering required level of service. - startup_grace_period: T::BlockNumber, + startup_grace_period: BlockNumber, // entry_request_fee: BalanceOf, } @@ -70,7 +70,7 @@ pub trait Trait: system::Trait + GovernanceCurrency { decl_storage! { trait Store for Module as Actors { /// requirements to enter and maintain status in roles - Parameters get(parameters) : map Role => Option>; + Parameters get(parameters) : map Role => Option, T::BlockNumber>>; /// the roles members can enter into AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; @@ -124,7 +124,7 @@ impl Module { } } - fn ensure_role_parameters(role: Role) -> Result, &'static str> { + fn ensure_role_parameters(role: Role) -> Result, T::BlockNumber>, &'static str> { Self::parameters(role).ok_or("no parameters for role") } } @@ -223,8 +223,24 @@ decl_module! { >::remove(&actor_account); } - // pub fn set_role_parameters(role: Role, params: RoleParameters) {} - // pub fn set_available_roles(Vec) {} + pub fn set_role_parameters(role: Role, params: RoleParameters, T::BlockNumber>) { + >::insert(role, params); + } + + pub fn set_available_roles(roles: Vec) { + >::put(roles); + } + + pub fn add_to_available_roles(role: Role) { + if !Self::available_roles().into_iter().any(|r| r == role) { + >::mutate(|roles| roles.push(role)); + } + } + + pub fn remove_from_available_roles(role: Role) { + let roles: Vec = Self::available_roles().into_iter().filter(|r| role != *r).collect(); + >::put(roles); + } } } From 389619411ce1f705881a5248b50d7588fcfe084d Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 10:37:05 +0200 Subject: [PATCH 07/26] staked roles: clear entry requests every N blocks --- src/roles/actors.rs | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 3454f62a03..ff9c248c97 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -67,6 +67,9 @@ pub trait Trait: system::Trait + GovernanceCurrency { type Members: Members; } +pub type Request = (T::AccountId, >::Id, Role); +pub type Requests = Vec>; + decl_storage! { trait Store for Module as Actors { /// requirements to enter and maintain status in roles @@ -93,7 +96,7 @@ decl_storage! { /// This list is cleared every N blocks.. the account making the request will be bonded and must have /// sufficient balance to cover the minimum stake for the role. /// Bonding only occurs after successful entry into a role. - RoleEntryRequests get(role_entry_requests) : map T::AccountId => Option<(>::Id, Role)>; + RoleEntryRequests get(role_entry_requests) : Requests; } } @@ -131,7 +134,7 @@ impl Module { impl Roles for Module { fn is_role_account(account_id: &T::AccountId) -> bool { - >::exists(account_id) || >::exists(account_id) || >::exists(account_id) + >::exists(account_id) || >::exists(account_id) } } @@ -139,12 +142,19 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - fn on_finalize(now: T::BlockNumber) { - // clear >::kill() every N blocks + fn on_initialize(now: T::BlockNumber) { + // clear requests + if now % T::BlockNumber::sa(300) == T::BlockNumber::zero() { + >::put(vec![]); + } + } + fn on_finalize(now: T::BlockNumber) { // payout rewards to actors // clear unbonded accounts + + // eject actors not staking the minimum } pub fn role_entry_request(origin, role: Role, member_id: >::Id) { @@ -157,7 +167,7 @@ decl_module! { let role_parameters = Self::ensure_role_parameters(role)?; - >::insert(&sender, (member_id, role)); + >::mutate(|requests| requests.push((sender, member_id, role))); } /// Member activating entry request @@ -165,12 +175,16 @@ decl_module! { let sender = ensure_signed(origin)?; let member_id = T::Members::lookup_member_id(&sender)?; - ensure!(>::exists(&actor_account), "no role entry request matches"); - if let Some(entry_request) = Self::role_entry_requests(&actor_account) { - ensure!(entry_request.0 == member_id, "role entry mismatch - member id"); - ensure!(entry_request.1 == role, "role entry mismatch - role"); + if !Self::role_entry_requests() + .iter() + .any(|request| request.0 == actor_account && request.1 == member_id && request.2 == role) + { + return Err("no role entry request matches"); } + ensure!(T::Members::lookup_member_id(&actor_account).is_err(), "account is a member"); + ensure!(!Self::is_role_account(&actor_account), "account already used"); + // make sure role is still available ensure!(Self::role_is_available(role), ""); let role_parameters = Self::ensure_role_parameters(role)?; @@ -192,7 +206,11 @@ decl_module! { role, joined_at: >::block_number() }); - >::remove(&actor_account); + let requests: Requests = Self::role_entry_requests() + .into_iter() + .filter(|request| request.0 != actor_account) + .collect(); + >::put(requests); Self::deposit_event(RawEvent::Staked(actor_account, role)); } From 8e43956e0b6974165dea0324deee1358c863cee7 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 11:24:37 +0200 Subject: [PATCH 08/26] staked roles: add expiery to entry requests --- src/roles/actors.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index ff9c248c97..69f214a8aa 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -67,9 +67,12 @@ pub trait Trait: system::Trait + GovernanceCurrency { type Members: Members; } -pub type Request = (T::AccountId, >::Id, Role); +pub type Request = (T::AccountId, >::Id, Role, T::BlockNumber); // actor account, memberid, role, expires pub type Requests = Vec>; +const REQUEST_LIFETIME: u64 = 300; +const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; + decl_storage! { trait Store for Module as Actors { /// requirements to enter and maintain status in roles @@ -93,9 +96,10 @@ decl_storage! { /// First step before enter a role is registering intent with a new account/key. /// This is done by sending a role_entry_request() from the new account. /// The member must then send a stake() transaction to approve the request and enter the desired role. - /// This list is cleared every N blocks.. the account making the request will be bonded and must have + /// The account making the request will be bonded and must have /// sufficient balance to cover the minimum stake for the role. /// Bonding only occurs after successful entry into a role. + /// The request expires after REQUEST_LIFETIME blocks RoleEntryRequests get(role_entry_requests) : Requests; } } @@ -143,9 +147,14 @@ decl_module! { fn deposit_event() = default; fn on_initialize(now: T::BlockNumber) { - // clear requests - if now % T::BlockNumber::sa(300) == T::BlockNumber::zero() { - >::put(vec![]); + // clear expired requests + if now % T::BlockNumber::sa(DEFAULT_REQUEST_CLEARING_INTERVAL) == T::BlockNumber::zero() { + let requests: Requests = Self::role_entry_requests() + .into_iter() + .filter(|request| request.3 < now) + .collect(); + + >::put(requests); } } @@ -167,7 +176,10 @@ decl_module! { let role_parameters = Self::ensure_role_parameters(role)?; - >::mutate(|requests| requests.push((sender, member_id, role))); + >::mutate(|requests| { + let expires = >::block_number()+ T::BlockNumber::sa(REQUEST_LIFETIME); + requests.push((sender, member_id, role, expires)); + }); } /// Member activating entry request From 1e35b213b998df4fb613b6f254980a5631859dba Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 12:48:35 +0200 Subject: [PATCH 09/26] staked roles: admin remove actor function --- src/roles/actors.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 69f214a8aa..e8765ca40e 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -54,7 +54,7 @@ pub struct RoleParameters { #[derive(Encode, Decode, Clone)] pub struct Actor { - member_id: >::Id, + member_id: MemberId, role: Role, account: T::AccountId, joined_at: T::BlockNumber, @@ -67,7 +67,8 @@ pub trait Trait: system::Trait + GovernanceCurrency { type Members: Members; } -pub type Request = (T::AccountId, >::Id, Role, T::BlockNumber); // actor account, memberid, role, expires +pub type MemberId = >::Id; +pub type Request = (T::AccountId, MemberId, Role, T::BlockNumber); // actor account, memberid, role, expires pub type Requests = Vec>; const REQUEST_LIFETIME: u64 = 300; @@ -88,7 +89,7 @@ decl_storage! { AccountsByRole get(accounts_by_role) : map Role => Vec; /// actor accounts associated with a member id - AccountsByMember get(accounts_by_member) : map >::Id => Vec; + AccountsByMember get(accounts_by_member) : map MemberId => Vec; /// tokens locked until given block number Bondage get(bondage) : map T::AccountId => T::BlockNumber; @@ -120,7 +121,7 @@ impl Module { Self::actors(role_key).ok_or("not role key") } - fn ensure_actor_is_member(role_key: &T::AccountId, member_id: >::Id) + fn ensure_actor_is_member(role_key: &T::AccountId, member_id: MemberId) -> Result, &'static str> { let actor = Self::ensure_actor(role_key)?; @@ -166,7 +167,7 @@ decl_module! { // eject actors not staking the minimum } - pub fn role_entry_request(origin, role: Role, member_id: >::Id) { + pub fn role_entry_request(origin, role: Role, member_id: MemberId) { let sender = ensure_signed(origin)?; ensure!(T::Members::lookup_member_id(&sender).is_err(), "account is a member"); @@ -235,20 +236,24 @@ decl_module! { let role_parameters = Self::ensure_role_parameters(actor.role)?; + Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period); + } + + fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { // simple unstaking ...only applying unbonding period - let accounts: Vec = Self::accounts_by_role(actor.role) + let accounts: Vec = Self::accounts_by_role(role) .into_iter() - .filter(|account| !(*account == actor.account)) + .filter(|account| !(*account == actor_account)) .collect(); - >::insert(actor.role, accounts); + >::insert(role, accounts); let accounts: Vec = Self::accounts_by_member(&member_id) .into_iter() - .filter(|account| !(*account == actor.account)) + .filter(|account| !(*account == actor_account)) .collect(); >::insert(&member_id, accounts); - >::insert(&actor_account, >::block_number() + role_parameters.unbonding_period); + >::insert(&actor_account, >::block_number() + unbonding_period); >::remove(&actor_account); } @@ -271,6 +276,13 @@ decl_module! { let roles: Vec = Self::available_roles().into_iter().filter(|r| role != *r).collect(); >::put(roles); } + + pub fn remove_actor(actor_account: T::AccountId) { + 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); + } } } From 79ef28c38f49e64ad02d81783025622a604f0085 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 15:50:17 +0200 Subject: [PATCH 10/26] staked roles: factor out deleting actor --- src/roles/actors.rs | 49 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index e8765ca40e..3fd348491e 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -82,8 +82,11 @@ decl_storage! { /// the roles members can enter into AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; + /// All active actors + Actors get(actors) : Vec; + /// actor accounts mapped to their actor - Actors get(actors) : map T::AccountId => Option>; + ActorsByAccountId get(actors_by_account_id) : map T::AccountId => Option>; /// actor accounts associated with a role AccountsByRole get(accounts_by_role) : map Role => Vec; @@ -118,7 +121,7 @@ impl Module { } fn ensure_actor(role_key: &T::AccountId) -> Result, &'static str> { - Self::actors(role_key).ok_or("not role key") + Self::actors_by_account_id(role_key).ok_or("not role key") } fn ensure_actor_is_member(role_key: &T::AccountId, member_id: MemberId) @@ -135,11 +138,33 @@ impl Module { fn ensure_role_parameters(role: Role) -> Result, T::BlockNumber>, &'static str> { Self::parameters(role).ok_or("no parameters for role") } + + fn delete_actor(actor_account: T::AccountId, role: Role, member_id: MemberId) { + let accounts: Vec = Self::accounts_by_role(role) + .into_iter() + .filter(|account| !(*account == actor_account)) + .collect(); + >::insert(role, accounts); + + let accounts: Vec = Self::accounts_by_member(&member_id) + .into_iter() + .filter(|account| !(*account == actor_account)) + .collect(); + >::insert(&member_id, accounts); + + let actors: Vec = Self::actors() + .into_iter() + .filter(|account| !(*account == actor_account)) + .collect(); + >::put(actors); + + >::remove(&actor_account); + } } impl Roles for Module { fn is_role_account(account_id: &T::AccountId) -> bool { - >::exists(account_id) || >::exists(account_id) + >::exists(account_id) || >::exists(account_id) } } @@ -213,12 +238,14 @@ decl_module! { >::mutate(role, |accounts| accounts.push(actor_account.clone())); >::mutate(&member_id, |accounts| accounts.push(actor_account.clone())); >::insert(&actor_account, T::BlockNumber::max_value()); - >::insert(&actor_account, Actor { + >::insert(&actor_account, Actor { member_id, account: actor_account.clone(), role, joined_at: >::block_number() }); + >::mutate(|actors| actors.push(actor_account.clone())); + let requests: Requests = Self::role_entry_requests() .into_iter() .filter(|request| request.0 != actor_account) @@ -241,21 +268,9 @@ decl_module! { fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { // simple unstaking ...only applying unbonding period - let accounts: Vec = Self::accounts_by_role(role) - .into_iter() - .filter(|account| !(*account == actor_account)) - .collect(); - >::insert(role, accounts); - - let accounts: Vec = Self::accounts_by_member(&member_id) - .into_iter() - .filter(|account| !(*account == actor_account)) - .collect(); - >::insert(&member_id, accounts); - >::insert(&actor_account, >::block_number() + unbonding_period); - >::remove(&actor_account); + Self::delete_actor(actor_account, role, member_id); } pub fn set_role_parameters(role: Role, params: RoleParameters, T::BlockNumber>) { From 82643451e484f11757c00fc4074688efb1d89701 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 17:13:56 +0200 Subject: [PATCH 11/26] staked roles: clear unbonded accounts --- src/roles/actors.rs | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 3fd348491e..85ea36136d 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -82,7 +82,7 @@ decl_storage! { /// the roles members can enter into AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; - /// All active actors + /// Actors list Actors get(actors) : Vec; /// actor accounts mapped to their actor @@ -139,7 +139,9 @@ impl Module { Self::parameters(role).ok_or("no parameters for role") } - fn delete_actor(actor_account: T::AccountId, role: Role, member_id: MemberId) { + // Mutating + + fn remove_actor_from_service(actor_account: T::AccountId, role: Role, member_id: MemberId) { let accounts: Vec = Self::accounts_by_role(role) .into_iter() .filter(|account| !(*account == actor_account)) @@ -152,14 +154,15 @@ impl Module { .collect(); >::insert(&member_id, accounts); - let actors: Vec = Self::actors() - .into_iter() - .filter(|account| !(*account == actor_account)) - .collect(); - >::put(actors); - >::remove(&actor_account); } + + fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { + // simple unstaking ...only applying unbonding period + >::insert(&actor_account, >::block_number() + unbonding_period); + + Self::remove_actor_from_service(actor_account, role, member_id); + } } impl Roles for Module { @@ -188,8 +191,27 @@ decl_module! { // payout rewards to actors // clear unbonded accounts + if now % T::BlockNumber::sa(100) == T::BlockNumber::zero() { + let actors: Vec = Self::actors() + .into_iter() + .filter(|account| { + if >::exists(account) { + if Self::bondage(account) > now { + true + } else { + >::remove(account); + false + } + } else { + true + } + }) + .collect(); + >::put(actors); + } // eject actors not staking the minimum + } pub fn role_entry_request(origin, role: Role, member_id: MemberId) { @@ -266,13 +288,6 @@ decl_module! { Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period); } - fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { - // simple unstaking ...only applying unbonding period - >::insert(&actor_account, >::block_number() + unbonding_period); - - Self::delete_actor(actor_account, role, member_id); - } - pub fn set_role_parameters(role: Role, params: RoleParameters, T::BlockNumber>) { >::insert(role, params); } From 85d1f330f0a30a333220171d1030a3d57edae28e Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 25 Mar 2019 23:36:49 +0200 Subject: [PATCH 12/26] remove unecessary use statement --- src/roles/actors.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 85ea36136d..607704176a 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -8,7 +8,6 @@ use srml_support::traits::{Currency, EnsureAccountLiquid}; use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug}; use system::{self, ensure_signed}; use crate::governance::{GovernanceCurrency, BalanceOf }; -use crate::membership::registry; use crate::traits::{Members, Roles}; From a4b502e34267f291b2c8438e558c72af0d80d898 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 26 Mar 2019 01:45:53 +0200 Subject: [PATCH 13/26] staked role payouts --- src/roles/actors.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 607704176a..b126c6b3e6 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -29,7 +29,7 @@ pub struct RoleParameters { min_actors: u32, // fixed amount of tokens paid to actors' primary account - reward_per_block: Balance, + reward: Balance, // payouts are made at this block interval reward_period: BlockNumber, @@ -187,10 +187,24 @@ decl_module! { } fn on_finalize(now: T::BlockNumber) { + // payout rewards to actors + for role in Self::available_roles().iter() { + if let Some(params) = Self::parameters(role) { + if !(now % params.reward_period == T::BlockNumber::zero()) { continue } + let accounts = Self::accounts_by_role(role); + for actor in accounts.into_iter().map(|account| Self::actors_by_account_id(account)) { + if let Some(actor) = actor { + if now > actor.joined_at + params.reward_period { + T::Currency::reward(&actor.account, params.reward); + } + } + } + } + } - // clear unbonded accounts if now % T::BlockNumber::sa(100) == T::BlockNumber::zero() { + // clear unbonded accounts let actors: Vec = Self::actors() .into_iter() .filter(|account| { @@ -210,6 +224,13 @@ decl_module! { } // 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() { + // } } @@ -302,6 +323,7 @@ decl_module! { } pub fn remove_from_available_roles(role: Role) { + // Should we eject actors in the role being removed? let roles: Vec = Self::available_roles().into_iter().filter(|r| role != *r).collect(); >::put(roles); } From 4ef307e4fe75f1aa21dd24bf66b222005a7bb5da Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 26 Mar 2019 10:23:37 +0200 Subject: [PATCH 14/26] MaybeDebug needed on trait --- src/roles/actors.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index b126c6b3e6..b603006a69 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -5,7 +5,7 @@ use parity_codec::Codec; use parity_codec_derive::{Encode, Decode}; use srml_support::{StorageMap, StorageValue, dispatch, decl_module, decl_storage, decl_event, ensure, Parameter}; use srml_support::traits::{Currency, EnsureAccountLiquid}; -use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug}; +use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug, MaybeDebug}; use system::{self, ensure_signed}; use crate::governance::{GovernanceCurrency, BalanceOf }; @@ -16,10 +16,12 @@ pub enum Role { Storage, } -#[derive(Encode, Decode, Copy, Clone, Debug, Eq, PartialEq)] -pub struct RoleParameters { + +#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq)] +pub struct RoleParameters { // minium balance required to stake to enter a role - min_stake: Balance, + min_stake: BalanceOf, // the maximum number of spots available to fill for a role max_actors: u32, @@ -29,24 +31,24 @@ pub struct RoleParameters { min_actors: u32, // fixed amount of tokens paid to actors' primary account - reward: Balance, + reward: BalanceOf, // payouts are made at this block interval - reward_period: BlockNumber, + reward_period: T::BlockNumber, // how long tokens remain locked for after unstaking - unbonding_period: BlockNumber, + unbonding_period: T::BlockNumber, // minimum amount of time before being able to unstake - bonding_time: BlockNumber, + bonding_time: T::BlockNumber, // minimum period required to be in service. unbonding before this time is highly penalized - min_service_period: BlockNumber, + min_service_period: T::BlockNumber, // "startup" time allowed for roles that need to sync their infrastructure // with other providers before they are considered in service and punishable for // not delivering required level of service. - startup_grace_period: BlockNumber, + startup_grace_period: T::BlockNumber, // entry_request_fee: BalanceOf, } @@ -60,7 +62,7 @@ pub struct Actor { // startup_grace_period_ends_at: T::BlockNumber, } -pub trait Trait: system::Trait + GovernanceCurrency { +pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug { type Event: From> + Into<::Event>; type Members: Members; @@ -76,7 +78,7 @@ const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; decl_storage! { trait Store for Module as Actors { /// requirements to enter and maintain status in roles - Parameters get(parameters) : map Role => Option, T::BlockNumber>>; + Parameters get(parameters) : map Role => Option>; /// the roles members can enter into AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; @@ -134,7 +136,7 @@ impl Module { } } - fn ensure_role_parameters(role: Role) -> Result, T::BlockNumber>, &'static str> { + fn ensure_role_parameters(role: Role) -> Result, &'static str> { Self::parameters(role).ok_or("no parameters for role") } @@ -308,7 +310,7 @@ decl_module! { Self::apply_unstake(actor.account, actor.role, actor.member_id, role_parameters.unbonding_period); } - pub fn set_role_parameters(role: Role, params: RoleParameters, T::BlockNumber>) { + pub fn set_role_parameters(role: Role, params: RoleParameters) { >::insert(role, params); } From cbab82b9893e43675ac01505ca1cd98e45b8ecea Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 26 Mar 2019 10:43:52 +0200 Subject: [PATCH 15/26] staked roles: initialize runtime allowing Storage role --- src/migration.rs | 55 ++++++++++++++++++++++++++++++--------------- src/roles/actors.rs | 18 +++++++-------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/migration.rs b/src/migration.rs index d835e0a17d..a360c3a311 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -6,24 +6,9 @@ use rstd::prelude::*; use runtime_io::print; use crate::{VERSION}; use crate::membership::members; - -pub trait Trait: system::Trait + members::Trait { - type Event: From> + Into<::Event>; -} - -decl_storage! { - trait Store for Module as Migration { - /// Records at what runtime spec version the store was initialized. This allows the runtime - /// to know when to run initialize code if it was installed as an update. - pub SpecVersion get(spec_version) build(|_| Some(VERSION.spec_version)) : Option; - } -} - -decl_event! { - pub enum Event where ::BlockNumber { - Migrated(BlockNumber, u32), - } -} +use crate::roles::actors; +use crate::governance::{GovernanceCurrency, BalanceOf }; +use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As}; // When preparing a new major runtime release version bump this value to match it and update // the initialization code in runtime_initialization(). Because of the way substrate runs runtime code @@ -40,6 +25,22 @@ impl Module { >::initialize_storage(); + // Initialize Storage provider role parameters + >::set_role_parameters(actors::Role::Storage, actors::RoleParameters { + min_stake: BalanceOf::::sa(3000), + max_actors: 10, + reward: BalanceOf::::sa(10), + reward_period: T::BlockNumber::sa(600), + unbonding_period: T::BlockNumber::sa(600), + + // not currently used + min_actors: 5, + bonding_time: T::BlockNumber::sa(600), + min_service_period: T::BlockNumber::sa(600), + startup_grace_period: T::BlockNumber::sa(600), + }); + >::set_available_roles(vec![actors::Role::Storage]); + // ... // add initialization of other modules introduced in this runtime // ... @@ -48,6 +49,24 @@ impl Module { } } +pub trait Trait: system::Trait + members::Trait + actors::Trait { + type Event: From> + Into<::Event>; +} + +decl_storage! { + trait Store for Module as Migration { + /// Records at what runtime spec version the store was initialized. This allows the runtime + /// to know when to run initialize code if it was installed as an update. + pub SpecVersion get(spec_version) build(|_| Some(VERSION.spec_version)) : Option; + } +} + +decl_event! { + pub enum Event where ::BlockNumber { + Migrated(BlockNumber, u32), + } +} + decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; diff --git a/src/roles/actors.rs b/src/roles/actors.rs index b603006a69..464f76faa3 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -21,34 +21,34 @@ pub enum Role { #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq)] pub struct RoleParameters { // minium balance required to stake to enter a role - min_stake: BalanceOf, + pub min_stake: BalanceOf, // the maximum number of spots available to fill for a role - max_actors: u32, + pub max_actors: u32, // minimum actors to maintain - if role is unstaking // and remaining actors would be less that this value - prevent or punish for unstaking - min_actors: u32, + pub min_actors: u32, // fixed amount of tokens paid to actors' primary account - reward: BalanceOf, + pub reward: BalanceOf, // payouts are made at this block interval - reward_period: T::BlockNumber, + pub reward_period: T::BlockNumber, // how long tokens remain locked for after unstaking - unbonding_period: T::BlockNumber, + pub unbonding_period: T::BlockNumber, // minimum amount of time before being able to unstake - bonding_time: T::BlockNumber, + pub bonding_time: T::BlockNumber, // minimum period required to be in service. unbonding before this time is highly penalized - min_service_period: T::BlockNumber, + pub min_service_period: T::BlockNumber, // "startup" time allowed for roles that need to sync their infrastructure // with other providers before they are considered in service and punishable for // not delivering required level of service. - startup_grace_period: T::BlockNumber, + pub startup_grace_period: T::BlockNumber, // entry_request_fee: BalanceOf, } From d81f8aa10e484707252a9c0dd1c87ae5d81b12c9 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Wed, 27 Mar 2019 12:26:15 +0200 Subject: [PATCH 16/26] fix spelling on_finalise, on_initialise --- src/roles/actors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 464f76faa3..50e9a29a16 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -176,7 +176,7 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - fn on_initialize(now: T::BlockNumber) { + fn on_initialise(now: T::BlockNumber) { // clear expired requests if now % T::BlockNumber::sa(DEFAULT_REQUEST_CLEARING_INTERVAL) == T::BlockNumber::zero() { let requests: Requests = Self::role_entry_requests() @@ -188,7 +188,7 @@ decl_module! { } } - fn on_finalize(now: T::BlockNumber) { + fn on_finalise(now: T::BlockNumber) { // payout rewards to actors for role in Self::available_roles().iter() { From f31673371f2e9c265ac92b517cc7c4f0ba6d1e08 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Thu, 28 Mar 2019 17:01:12 +0200 Subject: [PATCH 17/26] error message when not enough balance to stake --- src/roles/actors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 50e9a29a16..87a090c1cc 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -277,7 +277,7 @@ decl_module! { ensure!(accounts_in_role.len() < role_parameters.max_actors as usize, "role slots full"); // ensure the actor account has enough balance - ensure!(T::Currency::free_balance(&actor_account) >= role_parameters.min_stake, ""); + ensure!(T::Currency::free_balance(&actor_account) >= role_parameters.min_stake, "not enough balance to stake"); >::mutate(role, |accounts| accounts.push(actor_account.clone())); >::mutate(&member_id, |accounts| accounts.push(actor_account.clone())); From 9c9a49a2f3575b40d860d083969048a326df459b Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Thu, 28 Mar 2019 17:12:07 +0200 Subject: [PATCH 18/26] burn role entry request fee --- src/migration.rs | 1 + src/roles/actors.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/migration.rs b/src/migration.rs index a360c3a311..af060ff432 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -38,6 +38,7 @@ impl Module { bonding_time: T::BlockNumber::sa(600), min_service_period: T::BlockNumber::sa(600), startup_grace_period: T::BlockNumber::sa(600), + entry_request_fee: BalanceOf::::sa(50), }); >::set_available_roles(vec![actors::Role::Storage]); diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 87a090c1cc..9a3c6f355c 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -50,7 +50,8 @@ pub struct RoleParameters { // not delivering required level of service. pub startup_grace_period: T::BlockNumber, - // entry_request_fee: BalanceOf, + // small fee burned to make a request to enter role + pub entry_request_fee: BalanceOf, } #[derive(Encode, Decode, Clone)] @@ -246,6 +247,11 @@ decl_module! { let role_parameters = Self::ensure_role_parameters(role)?; + // pay (burn) entry fee - spam filter + let fee = role_parameters.entry_request_fee; + ensure!(T::Currency::can_slash(&sender, fee), "cannot pay role entry request fee"); + let _ = T::Currency::slash(&sender, fee); + >::mutate(|requests| { let expires = >::block_number()+ T::BlockNumber::sa(REQUEST_LIFETIME); requests.push((sender, member_id, role, expires)); From baffb0b68f686fe04247d1b905613ffe146bff0b Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Fri, 29 Mar 2019 00:43:50 +0200 Subject: [PATCH 19/26] rename role parameters field bonding_time -> bonding_period --- src/migration.rs | 4 ++-- src/roles/actors.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/migration.rs b/src/migration.rs index af060ff432..10eb7413f4 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -32,13 +32,13 @@ impl Module { reward: BalanceOf::::sa(10), reward_period: T::BlockNumber::sa(600), unbonding_period: T::BlockNumber::sa(600), + entry_request_fee: BalanceOf::::sa(50), // not currently used min_actors: 5, - bonding_time: T::BlockNumber::sa(600), + bonding_period: T::BlockNumber::sa(600), min_service_period: T::BlockNumber::sa(600), startup_grace_period: T::BlockNumber::sa(600), - entry_request_fee: BalanceOf::::sa(50), }); >::set_available_roles(vec![actors::Role::Storage]); diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 9a3c6f355c..411caecd90 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -40,7 +40,7 @@ pub struct RoleParameters { pub unbonding_period: T::BlockNumber, // minimum amount of time before being able to unstake - pub bonding_time: T::BlockNumber, + pub bonding_period: T::BlockNumber, // minimum period required to be in service. unbonding before this time is highly penalized pub min_service_period: T::BlockNumber, From fd30145fb5e43c8e9a5dab5694c0ea79a9c6146f Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Sat, 30 Mar 2019 10:28:29 +0200 Subject: [PATCH 20/26] renaming storage values --- src/roles/actors.rs | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 411caecd90..121d9319d7 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -23,25 +23,25 @@ pub struct RoleParameters { // minium balance required to stake to enter a role pub min_stake: BalanceOf, - // the maximum number of spots available to fill for a role - pub max_actors: u32, - // minimum actors to maintain - if role is unstaking // and remaining actors would be less that this value - prevent or punish for unstaking pub min_actors: u32, + // the maximum number of spots available to fill for a role + pub max_actors: u32, + // fixed amount of tokens paid to actors' primary account pub reward: BalanceOf, // payouts are made at this block interval pub reward_period: T::BlockNumber, - // how long tokens remain locked for after unstaking - pub unbonding_period: T::BlockNumber, - // minimum amount of time before being able to unstake pub bonding_period: T::BlockNumber, + // how long tokens remain locked for after unstaking + pub unbonding_period: T::BlockNumber, + // minimum period required to be in service. unbonding before this time is highly penalized pub min_service_period: T::BlockNumber, @@ -82,19 +82,19 @@ decl_storage! { Parameters get(parameters) : map Role => Option>; /// the roles members can enter into - AvailableRoles get(available_roles) : Vec; // = vec![Role::Storage]; + AvailableRoles get(available_roles) : Vec; /// Actors list - Actors get(actors) : Vec; + ActorAccountIds get(actor_account_ids) : Vec; /// actor accounts mapped to their actor - ActorsByAccountId get(actors_by_account_id) : map T::AccountId => Option>; + ActorByAccountId get(actor_by_account_id) : map T::AccountId => Option>; /// actor accounts associated with a role - AccountsByRole get(accounts_by_role) : map Role => Vec; + AccountIdsByRole get(account_ids_by_role) : map Role => Vec; /// actor accounts associated with a member id - AccountsByMember get(accounts_by_member) : map MemberId => Vec; + AccountIdsByMemberId get(account_ids_by_member_id) : map MemberId => Vec; /// tokens locked until given block number Bondage get(bondage) : map T::AccountId => T::BlockNumber; @@ -123,7 +123,7 @@ impl Module { } fn ensure_actor(role_key: &T::AccountId) -> Result, &'static str> { - Self::actors_by_account_id(role_key).ok_or("not role key") + Self::actor_by_account_id(role_key).ok_or("not role key") } fn ensure_actor_is_member(role_key: &T::AccountId, member_id: MemberId) @@ -144,19 +144,19 @@ impl Module { // Mutating fn remove_actor_from_service(actor_account: T::AccountId, role: Role, member_id: MemberId) { - let accounts: Vec = Self::accounts_by_role(role) + let accounts: Vec = Self::account_ids_by_role(role) .into_iter() .filter(|account| !(*account == actor_account)) .collect(); - >::insert(role, accounts); + >::insert(role, accounts); - let accounts: Vec = Self::accounts_by_member(&member_id) + let accounts: Vec = Self::account_ids_by_member_id(&member_id) .into_iter() .filter(|account| !(*account == actor_account)) .collect(); - >::insert(&member_id, accounts); + >::insert(&member_id, accounts); - >::remove(&actor_account); + >::remove(&actor_account); } fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { @@ -169,7 +169,7 @@ impl Module { impl Roles for Module { fn is_role_account(account_id: &T::AccountId) -> bool { - >::exists(account_id) || >::exists(account_id) + >::exists(account_id) || >::exists(account_id) } } @@ -195,8 +195,8 @@ decl_module! { for role in Self::available_roles().iter() { if let Some(params) = Self::parameters(role) { if !(now % params.reward_period == T::BlockNumber::zero()) { continue } - let accounts = Self::accounts_by_role(role); - for actor in accounts.into_iter().map(|account| Self::actors_by_account_id(account)) { + let accounts = Self::account_ids_by_role(role); + 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 { T::Currency::reward(&actor.account, params.reward); @@ -208,7 +208,7 @@ decl_module! { if now % T::BlockNumber::sa(100) == T::BlockNumber::zero() { // clear unbonded accounts - let actors: Vec = Self::actors() + let actor_accounts: Vec = Self::actor_account_ids() .into_iter() .filter(|account| { if >::exists(account) { @@ -223,7 +223,7 @@ decl_module! { } }) .collect(); - >::put(actors); + >::put(actor_accounts); } // eject actors not staking the minimum @@ -277,7 +277,7 @@ decl_module! { ensure!(Self::role_is_available(role), ""); let role_parameters = Self::ensure_role_parameters(role)?; - let accounts_in_role = Self::accounts_by_role(role); + let accounts_in_role = Self::account_ids_by_role(role); // ensure there is an empty slot for the role ensure!(accounts_in_role.len() < role_parameters.max_actors as usize, "role slots full"); @@ -285,16 +285,16 @@ decl_module! { // ensure the actor account has enough balance ensure!(T::Currency::free_balance(&actor_account) >= role_parameters.min_stake, "not enough balance to stake"); - >::mutate(role, |accounts| accounts.push(actor_account.clone())); - >::mutate(&member_id, |accounts| accounts.push(actor_account.clone())); + >::mutate(role, |accounts| accounts.push(actor_account.clone())); + >::mutate(&member_id, |accounts| accounts.push(actor_account.clone())); >::insert(&actor_account, T::BlockNumber::max_value()); - >::insert(&actor_account, Actor { + >::insert(&actor_account, Actor { member_id, account: actor_account.clone(), role, joined_at: >::block_number() }); - >::mutate(|actors| actors.push(actor_account.clone())); + >::mutate(|accounts| accounts.push(actor_account.clone())); let requests: Requests = Self::role_entry_requests() .into_iter() From 9b11ce5668e886c47c59eec4bd34290e209e9ffc Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 1 Apr 2019 22:47:27 +0300 Subject: [PATCH 21/26] adding tests for staked roles --- src/roles/actors.rs | 10 ++-- src/roles/mock.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++ src/roles/mod.rs | 4 +- src/roles/tests.rs | 91 ++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 src/roles/mock.rs create mode 100644 src/roles/tests.rs diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 121d9319d7..3d65d86d82 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -73,8 +73,8 @@ pub type MemberId = >::Id; pub type Request = (T::AccountId, MemberId, Role, T::BlockNumber); // actor account, memberid, role, expires pub type Requests = Vec>; -const REQUEST_LIFETIME: u64 = 300; -const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; +pub const REQUEST_LIFETIME: u64 = 300; +pub const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; decl_storage! { trait Store for Module as Actors { @@ -118,7 +118,7 @@ decl_event! { } impl Module { - fn role_is_available(role: Role) -> bool { + fn is_role_available(role: Role) -> bool { Self::available_roles().into_iter().any(|r| role == r) } @@ -243,7 +243,7 @@ decl_module! { ensure!(T::Members::lookup_member_id(&sender).is_err(), "account is a member"); ensure!(!Self::is_role_account(&sender), "account already used"); - ensure!(Self::role_is_available(role), "inactive role"); + ensure!(Self::is_role_available(role), "inactive role"); let role_parameters = Self::ensure_role_parameters(role)?; @@ -274,7 +274,7 @@ decl_module! { ensure!(!Self::is_role_account(&actor_account), "account already used"); // make sure role is still available - ensure!(Self::role_is_available(role), ""); + ensure!(Self::is_role_available(role), "inactive role"); let role_parameters = Self::ensure_role_parameters(role)?; let accounts_in_role = Self::account_ids_by_role(role); diff --git a/src/roles/mock.rs b/src/roles/mock.rs new file mode 100644 index 0000000000..292e00d6c2 --- /dev/null +++ b/src/roles/mock.rs @@ -0,0 +1,111 @@ +#![cfg(test)] + +use rstd::prelude::*; +pub use crate::governance::{GovernanceCurrency}; +pub use super::actors; +use crate::traits::{Members}; +//pub use crate::membership::members; +pub use system; + +pub use primitives::{H256, Blake2Hasher}; +pub use runtime_primitives::{ + BuildStorage, + traits::{BlakeTwo256, OnFinalise, IdentityLookup}, + testing::{Digest, DigestItem, Header, UintAuthorityId} +}; + +use srml_support::impl_outer_origin; + +impl_outer_origin! { + pub enum Origin for Test {} +} + +// For testing the module, we construct most of a mock runtime. This means +// first constructing a configuration type (`Test`) which `impl`s each of the +// configuration traits of modules we want to use. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; +impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + type Event = (); + type Log = DigestItem; + type Lookup = IdentityLookup; +} +impl timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); +} +impl consensus::Trait for Test { + type SessionKey = UintAuthorityId; + type InherentOfflineReport = (); + type Log = DigestItem; +} + +impl balances::Trait for Test { + type Event = (); + + /// The balance of an account. + type Balance = u32; + + /// A function which is invoked when the free-balance has fallen below the existential deposit and + /// has been reduced to zero. + /// + /// Gives a chance to clean up resources associated with the given account. + type OnFreeBalanceZero = (); + + /// Handler for when a new account is created. + type OnNewAccount = (); + + /// A function that returns true iff a given account can transfer its funds to another account. + type EnsureAccountLiquid = (); +} + +impl GovernanceCurrency for Test { + type Currency = balances::Module; +} + +pub struct MockMembers {} + +impl MockMembers { + pub fn alice_id() -> u32 {1} + pub fn alice_account() -> u64 {1} + pub fn bob_id() -> u32 {2} + pub fn bob_account() -> u64 {2} +} + +impl Members for MockMembers { + type Id = u32; + fn is_active_member(who: &u64) -> bool { + if *who == Self::alice_account() { return true; } + if *who == Self::bob_account() { return true; } + false + } + fn lookup_member_id(who: &u64) -> Result { + if *who == Self::alice_account() { return Ok(Self::alice_id()); } + if *who == Self::bob_account() { return Ok(Self::bob_id()); } + Err("member not found") + } +} + +impl actors::Trait for Test { + type Event = (); + type Members = MockMembers; +} + +pub fn initial_test_ext() -> runtime_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap().0; + + runtime_io::TestExternalities::new(t) +} + +pub type System = system::Module; +pub type Balances = balances::Module; +pub type Actors = actors::Module; + diff --git a/src/roles/mod.rs b/src/roles/mod.rs index c878c3a215..227653d691 100644 --- a/src/roles/mod.rs +++ b/src/roles/mod.rs @@ -2,5 +2,5 @@ pub mod actors; -//mod mock; -//mod tests; \ No newline at end of file +mod mock; +mod tests; \ No newline at end of file diff --git a/src/roles/tests.rs b/src/roles/tests.rs new file mode 100644 index 0000000000..9e0f323227 --- /dev/null +++ b/src/roles/tests.rs @@ -0,0 +1,91 @@ +#![cfg(test)] + +use super::*; +use super::mock::*; + +use parity_codec::Encode; +use runtime_io::with_externalities; +use srml_support::*; + +// fn assert_ok_unwrap(value: Option, err: &'static str) -> T { +// match value { +// None => { assert!(false, err); value.unwrap() }, +// Some(v) => v +// } +// } + +fn init_storage_role() { + let roles: Vec = vec![actors::Role::Storage]; + assert!(Actors::set_available_roles(roles).is_ok(), ""); +} + +fn init_storage_parmeters() -> actors::RoleParameters { + let params = actors::RoleParameters { + // minium balance required to stake to enter a role + min_stake: 100 as u32, + min_actors: 1 as u32, + max_actors: 2 as u32, + reward: 100 as u32, + reward_period: 100 as u64, + bonding_period: 100 as u64, + unbonding_period: 100 as u64, + min_service_period: 100 as u64, + startup_grace_period: 100 as u64, + entry_request_fee: 10 as u32, + }; + assert!(Actors::set_role_parameters(actors::Role::Storage, params.clone()).is_ok(), ""); + params +} + +#[test] +fn adding_roles() { + with_externalities(&mut initial_test_ext(), || { + init_storage_role(); + assert_eq!(Actors::available_roles(), vec![actors::Role::Storage]); + }); +} + +#[test] +fn adding_role_parameters() { + with_externalities(&mut initial_test_ext(), || { + init_storage_role(); + let params = init_storage_parmeters(); + assert_eq!(Actors::parameters(actors::Role::Storage), Some(params)); + }); +} + +#[test] +fn make_entry_request() { + with_externalities(&mut initial_test_ext(), || { + init_storage_role(); + let storageParams = init_storage_parmeters(); + + let actor_account = 5 as u64; + + let starting_block = 1; + System::set_block_number(starting_block); + + let requests = Actors::role_entry_requests(); + assert_eq!(requests.len(), 0); + + assert!(Actors::role_entry_request( + Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_err(), ""); + + let surplus_balance = 100; + Balances::set_free_balance(&actor_account, storageParams.entry_request_fee + surplus_balance); + + assert!(Actors::role_entry_request( + Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_ok(), ""); + + assert_eq!(Balances::free_balance(&actor_account), surplus_balance); + + let requests = Actors::role_entry_requests(); + assert_eq!(requests.len(), 1); + let request = requests[0]; + assert_eq!(request.0, actor_account); + assert_eq!(request.1, MockMembers::alice_id()); + assert_eq!(request.2, actors::Role::Storage); + assert_eq!(request.3, starting_block + actors::REQUEST_LIFETIME); + }); +} + From 18fae5ec3d2760bdef0ae8b12602db2822d22f5a Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Mon, 1 Apr 2019 23:35:13 +0300 Subject: [PATCH 22/26] pay reward to member account --- src/governance/mock.rs | 3 +++ src/governance/proposals.rs | 3 +++ src/membership/members.rs | 11 +++++++++-- src/roles/actors.rs | 5 ++++- src/roles/mock.rs | 5 +++++ src/traits.rs | 5 +++++ 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/governance/mock.rs b/src/governance/mock.rs index 8cd1a604f2..183b5f3546 100644 --- a/src/governance/mock.rs +++ b/src/governance/mock.rs @@ -30,6 +30,9 @@ impl Members for MockMembership { fn lookup_member_id(account_id: &T::AccountId) -> Result { Err("not implemented!") } + fn lookup_account_by_member_id(id: Self::Id) -> Result { + Err("not implemented!") + } } // For testing the module, we construct most of a mock runtime. This means diff --git a/src/governance/proposals.rs b/src/governance/proposals.rs index de3a69b93f..18b80fa53b 100644 --- a/src/governance/proposals.rs +++ b/src/governance/proposals.rs @@ -630,6 +630,9 @@ mod tests { fn lookup_member_id(account_id: &T::AccountId) -> Result { Err("not implemented!") } + fn lookup_account_by_member_id(id: Self::Id) -> Result { + Err("not implemented!") + } } type System = system::Module; diff --git a/src/membership/members.rs b/src/membership/members.rs index a87b3a61cd..9ac1c9a4b0 100644 --- a/src/membership/members.rs +++ b/src/membership/members.rs @@ -188,8 +188,15 @@ impl Members for Module { } fn lookup_member_id(who: &T::AccountId) -> Result { - let id = Self::ensure_is_member(who)?; - Ok(id) + Self::ensure_is_member(who) + } + + fn lookup_account_by_member_id(id: Self::Id) -> Result { + if >::exists(&id) { + Ok(Self::account_id_by_member_id(&id)) + } else { + Err("member id doesn't exist") + } } } diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 3d65d86d82..33cabfb1ab 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -199,7 +199,10 @@ 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 { - T::Currency::reward(&actor.account, params.reward); + // send reward to member account - not the actor account + if let Ok(member_account) = T::Members::lookup_account_by_member_id(actor.member_id) { + T::Currency::reward(&member_account, params.reward); + } } } } diff --git a/src/roles/mock.rs b/src/roles/mock.rs index 292e00d6c2..2b33f3eb3a 100644 --- a/src/roles/mock.rs +++ b/src/roles/mock.rs @@ -92,6 +92,11 @@ impl Members for MockMembers { if *who == Self::bob_account() { return Ok(Self::bob_id()); } Err("member not found") } + fn lookup_account_by_member_id(id: Self::Id) -> Result { + if id == Self::alice_id() { return Ok(Self::alice_account()); } + if id == Self::bob_id() { return Ok(Self::bob_account()); } + Err("account not found") + } } impl actors::Trait for Test { diff --git a/src/traits.rs b/src/traits.rs index b118c3b78d..7c55437424 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -14,6 +14,8 @@ pub trait Members { fn is_active_member(account_id: &T::AccountId) -> bool; fn lookup_member_id(account_id: &T::AccountId) -> Result; + + fn lookup_account_by_member_id(member_id: Self::Id) -> Result; } impl Members for () { @@ -24,6 +26,9 @@ impl Members for () { fn lookup_member_id(account_id: &T::AccountId) -> Result { Err("member not found") } + fn lookup_account_by_member_id(member_id: Self::Id) -> Result { + Err("account not found") + } } // Roles From c7940af80ae1b0b78c6908604eebfdd63bf437a0 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 2 Apr 2019 00:56:14 +0300 Subject: [PATCH 23/26] tests: staked roles --- src/roles/actors.rs | 31 +++++++++------- src/roles/tests.rs | 86 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 33cabfb1ab..8b5bce69c1 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -56,11 +56,10 @@ pub struct RoleParameters { #[derive(Encode, Decode, Clone)] pub struct Actor { - member_id: MemberId, - role: Role, - account: T::AccountId, - joined_at: T::BlockNumber, - // startup_grace_period_ends_at: T::BlockNumber, + pub member_id: MemberId, + pub role: Role, + pub account: T::AccountId, + pub joined_at: T::BlockNumber, } pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug { @@ -79,25 +78,25 @@ pub const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; decl_storage! { trait Store for Module as Actors { /// requirements to enter and maintain status in roles - Parameters get(parameters) : map Role => Option>; + pub Parameters get(parameters) : map Role => Option>; /// the roles members can enter into - AvailableRoles get(available_roles) : Vec; + pub AvailableRoles get(available_roles) : Vec; /// Actors list - ActorAccountIds get(actor_account_ids) : Vec; + pub ActorAccountIds get(actor_account_ids) : Vec; /// actor accounts mapped to their actor - ActorByAccountId get(actor_by_account_id) : map T::AccountId => Option>; + pub ActorByAccountId get(actor_by_account_id) : map T::AccountId => Option>; /// actor accounts associated with a role - AccountIdsByRole get(account_ids_by_role) : map Role => Vec; + pub AccountIdsByRole get(account_ids_by_role) : map Role => Vec; /// actor accounts associated with a member id - AccountIdsByMemberId get(account_ids_by_member_id) : map MemberId => Vec; + pub AccountIdsByMemberId get(account_ids_by_member_id) : map MemberId => Vec; /// tokens locked until given block number - Bondage get(bondage) : map T::AccountId => T::BlockNumber; + pub Bondage get(bondage) : map T::AccountId => T::BlockNumber; /// First step before enter a role is registering intent with a new account/key. /// This is done by sending a role_entry_request() from the new account. @@ -106,7 +105,7 @@ decl_storage! { /// sufficient balance to cover the minimum stake for the role. /// Bonding only occurs after successful entry into a role. /// The request expires after REQUEST_LIFETIME blocks - RoleEntryRequests get(role_entry_requests) : Requests; + pub RoleEntryRequests get(role_entry_requests) : Requests; } } @@ -156,6 +155,12 @@ impl Module { .collect(); >::insert(&member_id, accounts); + let accounts: Vec = Self::actor_account_ids() + .into_iter() + .filter(|account| !(*account == actor_account)) + .collect(); + >::put(accounts); + >::remove(&actor_account); } diff --git a/src/roles/tests.rs b/src/roles/tests.rs index 9e0f323227..e043004f1f 100644 --- a/src/roles/tests.rs +++ b/src/roles/tests.rs @@ -3,17 +3,9 @@ use super::*; use super::mock::*; -use parity_codec::Encode; use runtime_io::with_externalities; use srml_support::*; -// fn assert_ok_unwrap(value: Option, err: &'static str) -> T { -// match value { -// None => { assert!(false, err); value.unwrap() }, -// Some(v) => v -// } -// } - fn init_storage_role() { let roles: Vec = vec![actors::Role::Storage]; assert!(Actors::set_available_roles(roles).is_ok(), ""); @@ -58,7 +50,7 @@ fn adding_role_parameters() { fn make_entry_request() { with_externalities(&mut initial_test_ext(), || { init_storage_role(); - let storageParams = init_storage_parmeters(); + let storage_params = init_storage_parmeters(); let actor_account = 5 as u64; @@ -72,7 +64,7 @@ fn make_entry_request() { Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_err(), ""); let surplus_balance = 100; - Balances::set_free_balance(&actor_account, storageParams.entry_request_fee + surplus_balance); + Balances::set_free_balance(&actor_account, storage_params.entry_request_fee + surplus_balance); assert!(Actors::role_entry_request( Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_ok(), ""); @@ -89,3 +81,77 @@ fn make_entry_request() { }); } +#[test] +fn staking() { + with_externalities(&mut initial_test_ext(), || { + init_storage_role(); + let storage_params = init_storage_parmeters(); + let actor_account = 5; + + let request: actors::Request = (actor_account, MockMembers::alice_id(), actors::Role::Storage, 1000); + + >::put(vec![request]); + + Balances::set_free_balance(&actor_account, storage_params.min_stake); + + assert!(Actors::stake( + Origin::signed(MockMembers::alice_account()), + actors::Role::Storage, + actor_account).is_ok()); + + let ids = Actors::actor_account_ids(); + assert_eq!(ids, vec![actor_account]); + + let actor = Actors::actor_by_account_id(actor_account); + assert!(actor.is_some()); + + let accounts_in_role = Actors::account_ids_by_role(actors::Role::Storage); + assert_eq!(accounts_in_role, vec![actor_account]); + + let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id()); + assert_eq!(account_ids_for_member, vec![actor_account]); + + assert!(>::exists(actor_account)); + }); +} + +#[test] +fn unstaking() { + with_externalities(&mut initial_test_ext(), || { + init_storage_role(); + let storage_params = init_storage_parmeters(); + let actor_account = 5; + + assert!(Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_err()); + + let actor: actors::Actor = actors::Actor { + role: actors::Role::Storage, + member_id: MockMembers::alice_id(), + account: actor_account, + joined_at: 1, + }; + >::put(vec![actor_account]); + >::insert(&actor_account, actor); + >::insert(actors::Role::Storage, vec![actor_account]); + >::insert(MockMembers::alice_id(), vec![actor_account]); + >::insert(&actor_account, 10000); + let current_block = 500; + + System::set_block_number(current_block); + assert!(Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_ok()); + + assert_eq!(Actors::actor_account_ids().len(), 0); + + let actor = Actors::actor_by_account_id(actor_account); + assert!(actor.is_none()); + + let accounts_in_role = Actors::account_ids_by_role(actors::Role::Storage); + assert_eq!(accounts_in_role.len(), 0); + + let account_ids_for_member = Actors::account_ids_by_member_id(MockMembers::alice_id()); + assert_eq!(account_ids_for_member.len(), 0); + + assert!(>::exists(actor_account)); + assert_eq!(Actors::bondage(actor_account), current_block + storage_params.unbonding_period); + }); +} \ No newline at end of file From 6c5deff11640007fbafdc661fa726dbb98eb9e83 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 2 Apr 2019 01:10:03 +0300 Subject: [PATCH 24/26] add EntryRequested and Unstaked events --- src/roles/actors.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 8b5bce69c1..2e8f9e8305 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -112,7 +112,9 @@ decl_storage! { decl_event! { pub enum Event where ::AccountId { + EntryRequested(AccountId, Role), Staked(AccountId, Role), + Unstaked(AccountId, Role), } } @@ -262,8 +264,9 @@ decl_module! { >::mutate(|requests| { let expires = >::block_number()+ T::BlockNumber::sa(REQUEST_LIFETIME); - requests.push((sender, member_id, role, expires)); + requests.push((sender.clone(), member_id, role, expires)); }); + Self::deposit_event(RawEvent::EntryRequested(sender, role)); } /// Member activating entry request @@ -321,7 +324,9 @@ decl_module! { 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.clone(), actor.role, actor.member_id, role_parameters.unbonding_period); + + Self::deposit_event(RawEvent::Unstaked(actor.account, actor.role)); } pub fn set_role_parameters(role: Role, params: RoleParameters) { From 75bcdb51ff4ccb5618c74e699b78f4533851b4dd Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 2 Apr 2019 11:30:32 +0300 Subject: [PATCH 25/26] rustfmt --- src/roles/actors.rs | 50 ++++++++++++++++----------- src/roles/mock.rs | 59 +++++++++++++++++++++----------- src/roles/mod.rs | 2 +- src/roles/tests.rs | 82 ++++++++++++++++++++++++++++++++------------- 4 files changed, 130 insertions(+), 63 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 2e8f9e8305..0c52b093dc 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -1,13 +1,17 @@ #![cfg_attr(not(feature = "std"), no_std)] -use rstd::prelude::*; +use crate::governance::{BalanceOf, GovernanceCurrency}; use parity_codec::Codec; -use parity_codec_derive::{Encode, Decode}; -use srml_support::{StorageMap, StorageValue, dispatch, decl_module, decl_storage, decl_event, ensure, Parameter}; +use parity_codec_derive::{Decode, Encode}; +use rstd::prelude::*; +use runtime_primitives::traits::{ + As, Bounded, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic, Zero, +}; use srml_support::traits::{Currency, EnsureAccountLiquid}; -use runtime_primitives::traits::{Zero, Bounded, SimpleArithmetic, As, Member, MaybeSerializeDebug, MaybeDebug}; +use srml_support::{ + decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap, StorageValue, +}; use system::{self, ensure_signed}; -use crate::governance::{GovernanceCurrency, BalanceOf }; use crate::traits::{Members, Roles}; @@ -16,7 +20,6 @@ pub enum Role { Storage, } - #[cfg_attr(feature = "std", derive(Debug))] #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq)] pub struct RoleParameters { @@ -127,9 +130,10 @@ impl Module { Self::actor_by_account_id(role_key).ok_or("not role key") } - fn ensure_actor_is_member(role_key: &T::AccountId, member_id: MemberId) - -> Result, &'static str> - { + fn ensure_actor_is_member( + role_key: &T::AccountId, + member_id: MemberId, + ) -> Result, &'static str> { let actor = Self::ensure_actor(role_key)?; if actor.member_id == member_id { Ok(actor) @@ -166,9 +170,17 @@ impl Module { >::remove(&actor_account); } - fn apply_unstake(actor_account: T::AccountId, role: Role, member_id: MemberId, unbonding_period: T::BlockNumber) { + fn apply_unstake( + actor_account: T::AccountId, + role: Role, + member_id: MemberId, + unbonding_period: T::BlockNumber, + ) { // simple unstaking ...only applying unbonding period - >::insert(&actor_account, >::block_number() + unbonding_period); + >::insert( + &actor_account, + >::block_number() + unbonding_period, + ); Self::remove_actor_from_service(actor_account, role, member_id); } @@ -359,11 +371,11 @@ decl_module! { } impl EnsureAccountLiquid for Module { - fn ensure_account_liquid(who: &T::AccountId) -> dispatch::Result { - if Self::bondage(who) <= >::block_number() { - Ok(()) - } else { - Err("cannot transfer illiquid funds") - } - } -} \ No newline at end of file + fn ensure_account_liquid(who: &T::AccountId) -> dispatch::Result { + if Self::bondage(who) <= >::block_number() { + Ok(()) + } else { + Err("cannot transfer illiquid funds") + } + } +} diff --git a/src/roles/mock.rs b/src/roles/mock.rs index 2b33f3eb3a..94d75f0f08 100644 --- a/src/roles/mock.rs +++ b/src/roles/mock.rs @@ -1,17 +1,16 @@ #![cfg(test)] -use rstd::prelude::*; -pub use crate::governance::{GovernanceCurrency}; pub use super::actors; -use crate::traits::{Members}; -//pub use crate::membership::members; +pub use crate::governance::GovernanceCurrency; +use crate::traits::Members; +use rstd::prelude::*; pub use system; -pub use primitives::{H256, Blake2Hasher}; +pub use primitives::{Blake2Hasher, H256}; pub use runtime_primitives::{ + testing::{Digest, DigestItem, Header, UintAuthorityId}, + traits::{BlakeTwo256, IdentityLookup, OnFinalise}, BuildStorage, - traits::{BlakeTwo256, OnFinalise, IdentityLookup}, - testing::{Digest, DigestItem, Header, UintAuthorityId} }; use srml_support::impl_outer_origin; @@ -74,27 +73,47 @@ impl GovernanceCurrency for Test { pub struct MockMembers {} impl MockMembers { - pub fn alice_id() -> u32 {1} - pub fn alice_account() -> u64 {1} - pub fn bob_id() -> u32 {2} - pub fn bob_account() -> u64 {2} + pub fn alice_id() -> u32 { + 1 + } + pub fn alice_account() -> u64 { + 1 + } + pub fn bob_id() -> u32 { + 2 + } + pub fn bob_account() -> u64 { + 2 + } } impl Members for MockMembers { type Id = u32; fn is_active_member(who: &u64) -> bool { - if *who == Self::alice_account() { return true; } - if *who == Self::bob_account() { return true; } + if *who == Self::alice_account() { + return true; + } + if *who == Self::bob_account() { + return true; + } false } fn lookup_member_id(who: &u64) -> Result { - if *who == Self::alice_account() { return Ok(Self::alice_id()); } - if *who == Self::bob_account() { return Ok(Self::bob_id()); } + if *who == Self::alice_account() { + return Ok(Self::alice_id()); + } + if *who == Self::bob_account() { + return Ok(Self::bob_id()); + } Err("member not found") } fn lookup_account_by_member_id(id: Self::Id) -> Result { - if id == Self::alice_id() { return Ok(Self::alice_account()); } - if id == Self::bob_id() { return Ok(Self::bob_account()); } + if id == Self::alice_id() { + return Ok(Self::alice_account()); + } + if id == Self::bob_id() { + return Ok(Self::bob_account()); + } Err("account not found") } } @@ -105,7 +124,10 @@ impl actors::Trait for Test { } pub fn initial_test_ext() -> runtime_io::TestExternalities { - let t = system::GenesisConfig::::default().build_storage().unwrap().0; + let t = system::GenesisConfig::::default() + .build_storage() + .unwrap() + .0; runtime_io::TestExternalities::new(t) } @@ -113,4 +135,3 @@ pub fn initial_test_ext() -> runtime_io::TestExternalities { pub type System = system::Module; pub type Balances = balances::Module; pub type Actors = actors::Module; - diff --git a/src/roles/mod.rs b/src/roles/mod.rs index 227653d691..4ddb9c4f5d 100644 --- a/src/roles/mod.rs +++ b/src/roles/mod.rs @@ -3,4 +3,4 @@ pub mod actors; mod mock; -mod tests; \ No newline at end of file +mod tests; diff --git a/src/roles/tests.rs b/src/roles/tests.rs index e043004f1f..8aba121aec 100644 --- a/src/roles/tests.rs +++ b/src/roles/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] -use super::*; use super::mock::*; +use super::*; use runtime_io::with_externalities; use srml_support::*; @@ -14,18 +14,21 @@ fn init_storage_role() { fn init_storage_parmeters() -> actors::RoleParameters { let params = actors::RoleParameters { // minium balance required to stake to enter a role - min_stake: 100 as u32, - min_actors: 1 as u32, - max_actors: 2 as u32, - reward: 100 as u32, - reward_period: 100 as u64, - bonding_period: 100 as u64, - unbonding_period: 100 as u64, - min_service_period: 100 as u64, - startup_grace_period: 100 as u64, - entry_request_fee: 10 as u32, + min_stake: 100 as u32, + min_actors: 1 as u32, + max_actors: 2 as u32, + reward: 100 as u32, + reward_period: 100 as u64, + bonding_period: 100 as u64, + unbonding_period: 100 as u64, + min_service_period: 100 as u64, + startup_grace_period: 100 as u64, + entry_request_fee: 10 as u32, }; - assert!(Actors::set_role_parameters(actors::Role::Storage, params.clone()).is_ok(), ""); + assert!( + Actors::set_role_parameters(actors::Role::Storage, params.clone()).is_ok(), + "" + ); params } @@ -60,14 +63,31 @@ fn make_entry_request() { let requests = Actors::role_entry_requests(); assert_eq!(requests.len(), 0); - assert!(Actors::role_entry_request( - Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_err(), ""); + assert!( + Actors::role_entry_request( + Origin::signed(actor_account), + actors::Role::Storage, + MockMembers::alice_id() + ) + .is_err(), + "" + ); let surplus_balance = 100; - Balances::set_free_balance(&actor_account, storage_params.entry_request_fee + surplus_balance); - - assert!(Actors::role_entry_request( - Origin::signed(actor_account), actors::Role::Storage, MockMembers::alice_id()).is_ok(), ""); + Balances::set_free_balance( + &actor_account, + storage_params.entry_request_fee + surplus_balance, + ); + + assert!( + Actors::role_entry_request( + Origin::signed(actor_account), + actors::Role::Storage, + MockMembers::alice_id() + ) + .is_ok(), + "" + ); assert_eq!(Balances::free_balance(&actor_account), surplus_balance); @@ -88,7 +108,12 @@ fn staking() { let storage_params = init_storage_parmeters(); let actor_account = 5; - let request: actors::Request = (actor_account, MockMembers::alice_id(), actors::Role::Storage, 1000); + let request: actors::Request = ( + actor_account, + MockMembers::alice_id(), + actors::Role::Storage, + 1000, + ); >::put(vec![request]); @@ -97,7 +122,9 @@ fn staking() { assert!(Actors::stake( Origin::signed(MockMembers::alice_account()), actors::Role::Storage, - actor_account).is_ok()); + actor_account + ) + .is_ok()); let ids = Actors::actor_account_ids(); assert_eq!(ids, vec![actor_account]); @@ -122,7 +149,9 @@ fn unstaking() { let storage_params = init_storage_parmeters(); let actor_account = 5; - assert!(Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_err()); + assert!( + Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_err() + ); let actor: actors::Actor = actors::Actor { role: actors::Role::Storage, @@ -138,7 +167,9 @@ fn unstaking() { let current_block = 500; System::set_block_number(current_block); - assert!(Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_ok()); + assert!( + Actors::unstake(Origin::signed(MockMembers::alice_account()), actor_account).is_ok() + ); assert_eq!(Actors::actor_account_ids().len(), 0); @@ -152,6 +183,9 @@ fn unstaking() { assert_eq!(account_ids_for_member.len(), 0); assert!(>::exists(actor_account)); - assert_eq!(Actors::bondage(actor_account), current_block + storage_params.unbonding_period); + assert_eq!( + Actors::bondage(actor_account), + current_block + storage_params.unbonding_period + ); }); -} \ No newline at end of file +} From 22fffb7dde812dce268b4c4287b9ac08468820a1 Mon Sep 17 00:00:00 2001 From: Mokhtar Naamani Date: Tue, 2 Apr 2019 11:50:10 +0300 Subject: [PATCH 26/26] fix warnings in roles module --- src/roles/actors.rs | 14 +++++++------- src/roles/mock.rs | 1 - src/roles/tests.rs | 2 +- src/traits.rs | 6 +++--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/roles/actors.rs b/src/roles/actors.rs index 0c52b093dc..9ad261d184 100644 --- a/src/roles/actors.rs +++ b/src/roles/actors.rs @@ -1,15 +1,14 @@ #![cfg_attr(not(feature = "std"), no_std)] use crate::governance::{BalanceOf, GovernanceCurrency}; -use parity_codec::Codec; use parity_codec_derive::{Decode, Encode}; use rstd::prelude::*; use runtime_primitives::traits::{ - As, Bounded, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic, Zero, + As, Bounded, MaybeDebug, Zero, }; use srml_support::traits::{Currency, EnsureAccountLiquid}; use srml_support::{ - decl_event, decl_module, decl_storage, dispatch, ensure, Parameter, StorageMap, StorageValue, + decl_event, decl_module, decl_storage, dispatch, ensure, StorageMap, StorageValue, }; use system::{self, ensure_signed}; @@ -71,9 +70,10 @@ pub trait Trait: system::Trait + GovernanceCurrency + MaybeDebug { type Members: Members; } -pub type MemberId = >::Id; -pub type Request = (T::AccountId, MemberId, Role, T::BlockNumber); // actor account, memberid, role, expires -pub type Requests = Vec>; +pub type MemberId = <::Members as Members>::Id; +// actor account, memberid, role, expires +pub type Request = (::AccountId, MemberId, Role, ::BlockNumber); +pub type Requests = Vec>; pub const REQUEST_LIFETIME: u64 = 300; pub const DEFAULT_REQUEST_CLEARING_INTERVAL: u64 = 100; @@ -220,7 +220,7 @@ decl_module! { 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) { - T::Currency::reward(&member_account, params.reward); + let _ = T::Currency::reward(&member_account, params.reward); } } } diff --git a/src/roles/mock.rs b/src/roles/mock.rs index 94d75f0f08..4dba02bb9a 100644 --- a/src/roles/mock.rs +++ b/src/roles/mock.rs @@ -3,7 +3,6 @@ pub use super::actors; pub use crate::governance::GovernanceCurrency; use crate::traits::Members; -use rstd::prelude::*; pub use system; pub use primitives::{Blake2Hasher, H256}; diff --git a/src/roles/tests.rs b/src/roles/tests.rs index 8aba121aec..da9d630b82 100644 --- a/src/roles/tests.rs +++ b/src/roles/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] use super::mock::*; -use super::*; +//use super::*; use runtime_io::with_externalities; use srml_support::*; diff --git a/src/traits.rs b/src/traits.rs index 7c55437424..4bea919f79 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -20,13 +20,13 @@ pub trait Members { impl Members for () { type Id = u32; - fn is_active_member(account_id: &T::AccountId) -> bool { + fn is_active_member(_account_id: &T::AccountId) -> bool { false } - fn lookup_member_id(account_id: &T::AccountId) -> Result { + fn lookup_member_id(_account_id: &T::AccountId) -> Result { Err("member not found") } - fn lookup_account_by_member_id(member_id: Self::Id) -> Result { + fn lookup_account_by_member_id(_member_id: Self::Id) -> Result { Err("account not found") } }