|
@@ -1,425 +0,0 @@
|
|
|
-// Clippy linter warning
|
|
|
-#![allow(clippy::redundant_closure_call)] // disable it because of the substrate lib design
|
|
|
- // example: pub Parameters get(parameters) build(|config: &GenesisConfig| {..}
|
|
|
-
|
|
|
-use codec::{Decode, Encode};
|
|
|
-use common::currency::{BalanceOf, GovernanceCurrency};
|
|
|
-use rstd::prelude::*;
|
|
|
-use sr_primitives::traits::{Bounded, Zero};
|
|
|
-use srml_support::traits::{
|
|
|
- Currency, LockIdentifier, LockableCurrency, WithdrawReason, WithdrawReasons,
|
|
|
-};
|
|
|
-use srml_support::{decl_event, decl_module, decl_storage, ensure};
|
|
|
-use system::{self, ensure_root, ensure_signed};
|
|
|
-
|
|
|
-#[cfg(feature = "std")]
|
|
|
-use serde::{Deserialize, Serialize};
|
|
|
-
|
|
|
-pub use membership::members::Role;
|
|
|
-
|
|
|
-const STAKING_ID: LockIdentifier = *b"role_stk";
|
|
|
-
|
|
|
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
|
|
-#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)]
|
|
|
-pub struct RoleParameters<Balance, BlockNumber> {
|
|
|
- // minium balance required to stake to enter a role
|
|
|
- pub min_stake: Balance,
|
|
|
-
|
|
|
- // 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: Balance,
|
|
|
-
|
|
|
- // payouts are made at this block interval
|
|
|
- pub reward_period: BlockNumber,
|
|
|
-
|
|
|
- // minimum amount of time before being able to unstake
|
|
|
- pub bonding_period: BlockNumber,
|
|
|
-
|
|
|
- // how long tokens remain locked for after unstaking
|
|
|
- pub unbonding_period: BlockNumber,
|
|
|
-
|
|
|
- // minimum period required to be in service. unbonding before this time is highly penalized
|
|
|
- pub 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.
|
|
|
- pub startup_grace_period: BlockNumber,
|
|
|
-
|
|
|
- // small fee burned to make a request to enter role
|
|
|
- pub entry_request_fee: Balance,
|
|
|
-}
|
|
|
-
|
|
|
-impl<Balance: From<u32>, BlockNumber: From<u32>> Default for RoleParameters<Balance, BlockNumber> {
|
|
|
- fn default() -> Self {
|
|
|
- Self {
|
|
|
- min_stake: Balance::from(3000),
|
|
|
- max_actors: 10,
|
|
|
- reward: Balance::from(10),
|
|
|
- reward_period: BlockNumber::from(600),
|
|
|
- unbonding_period: BlockNumber::from(600),
|
|
|
- entry_request_fee: Balance::from(50),
|
|
|
-
|
|
|
- // not currently used
|
|
|
- min_actors: 1,
|
|
|
- bonding_period: BlockNumber::from(600),
|
|
|
- min_service_period: BlockNumber::from(600),
|
|
|
- startup_grace_period: BlockNumber::from(600),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Encode, Decode, Clone)]
|
|
|
-pub struct Actor<T: Trait> {
|
|
|
- pub member_id: MemberId<T>,
|
|
|
- pub role: Role,
|
|
|
- pub account: T::AccountId,
|
|
|
- pub joined_at: T::BlockNumber,
|
|
|
-}
|
|
|
-
|
|
|
-pub trait ActorRemoved<T: Trait> {
|
|
|
- fn actor_removed(actor: &T::AccountId);
|
|
|
-}
|
|
|
-
|
|
|
-pub trait Trait: system::Trait + GovernanceCurrency + membership::members::Trait {
|
|
|
- type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
|
|
|
-
|
|
|
- type OnActorRemoved: ActorRemoved<Self>;
|
|
|
-}
|
|
|
-
|
|
|
-pub type MemberId<T> = <T as membership::members::Trait>::MemberId;
|
|
|
-// actor account, memberid, role, expires
|
|
|
-pub type Request<T> = (
|
|
|
- <T as system::Trait>::AccountId,
|
|
|
- MemberId<T>,
|
|
|
- Role,
|
|
|
- <T as system::Trait>::BlockNumber,
|
|
|
-);
|
|
|
-pub type Requests<T> = Vec<Request<T>>;
|
|
|
-
|
|
|
-pub const DEFAULT_REQUEST_LIFETIME: u32 = 300;
|
|
|
-pub const REQUEST_CLEARING_INTERVAL: u32 = 100;
|
|
|
-
|
|
|
-decl_storage! {
|
|
|
- trait Store for Module<T: Trait> as Actors {
|
|
|
- /// requirements to enter and maintain status in roles
|
|
|
- pub Parameters get(parameters) build(|config: &GenesisConfig| {
|
|
|
- if config.enable_storage_role {
|
|
|
- let storage_params: RoleParameters<BalanceOf<T>, T::BlockNumber> = Default::default();
|
|
|
- vec![(Role::StorageProvider, storage_params)]
|
|
|
- } else {
|
|
|
- vec![]
|
|
|
- }
|
|
|
- }): map Role => Option<RoleParameters<BalanceOf<T>, T::BlockNumber>>;
|
|
|
-
|
|
|
- /// the roles members can enter into
|
|
|
- pub AvailableRoles get(available_roles) build(|config: &GenesisConfig| {
|
|
|
- if config.enable_storage_role {
|
|
|
- vec![(Role::StorageProvider)]
|
|
|
- } else {
|
|
|
- vec![]
|
|
|
- }
|
|
|
- }): Vec<Role>;
|
|
|
-
|
|
|
- /// Actors list
|
|
|
- pub ActorAccountIds get(actor_account_ids) : Vec<T::AccountId>;
|
|
|
-
|
|
|
- /// actor accounts mapped to their actor
|
|
|
- pub ActorByAccountId get(actor_by_account_id) : map T::AccountId => Option<Actor<T>>;
|
|
|
-
|
|
|
- /// actor accounts associated with a role
|
|
|
- pub AccountIdsByRole get(account_ids_by_role) : map Role => Vec<T::AccountId>;
|
|
|
-
|
|
|
- /// actor accounts associated with a member id
|
|
|
- pub AccountIdsByMemberId get(account_ids_by_member_id) : map MemberId<T> => Vec<T::AccountId>;
|
|
|
-
|
|
|
- /// 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.
|
|
|
- /// 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.
|
|
|
- pub RoleEntryRequests get(role_entry_requests) : Requests<T>;
|
|
|
-
|
|
|
- /// Entry request expires after this number of blocks
|
|
|
- pub RequestLifeTime get(request_life_time) config(request_life_time) : u32 = DEFAULT_REQUEST_LIFETIME;
|
|
|
- }
|
|
|
- add_extra_genesis {
|
|
|
- config(enable_storage_role): bool;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_event! {
|
|
|
- pub enum Event<T> where
|
|
|
- <T as system::Trait>::AccountId {
|
|
|
- EntryRequested(AccountId, Role),
|
|
|
- Staked(AccountId, Role),
|
|
|
- Unstaked(AccountId, Role),
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Trait> Module<T> {
|
|
|
- fn is_role_available(role: Role) -> bool {
|
|
|
- Self::available_roles().into_iter().any(|r| role == r)
|
|
|
- }
|
|
|
-
|
|
|
- fn ensure_actor(role_key: &T::AccountId) -> Result<Actor<T>, &'static str> {
|
|
|
- Self::actor_by_account_id(role_key).ok_or("not role key")
|
|
|
- }
|
|
|
-
|
|
|
- fn ensure_role_parameters(
|
|
|
- role: Role,
|
|
|
- ) -> Result<RoleParameters<BalanceOf<T>, T::BlockNumber>, &'static str> {
|
|
|
- Self::parameters(role).ok_or("no parameters for role")
|
|
|
- }
|
|
|
-
|
|
|
- // Mutating
|
|
|
-
|
|
|
- fn remove_actor_from_service(actor_account: T::AccountId, role: Role, member_id: MemberId<T>) {
|
|
|
- let accounts: Vec<T::AccountId> = Self::account_ids_by_role(role)
|
|
|
- .into_iter()
|
|
|
- .filter(|account| *account != actor_account)
|
|
|
- .collect();
|
|
|
- <AccountIdsByRole<T>>::insert(role, accounts);
|
|
|
-
|
|
|
- let accounts: Vec<T::AccountId> = Self::account_ids_by_member_id(&member_id)
|
|
|
- .into_iter()
|
|
|
- .filter(|account| *account != actor_account)
|
|
|
- .collect();
|
|
|
- <AccountIdsByMemberId<T>>::insert(&member_id, accounts);
|
|
|
-
|
|
|
- let accounts: Vec<T::AccountId> = Self::actor_account_ids()
|
|
|
- .into_iter()
|
|
|
- .filter(|account| *account != actor_account)
|
|
|
- .collect();
|
|
|
- <ActorAccountIds<T>>::put(accounts);
|
|
|
-
|
|
|
- <ActorByAccountId<T>>::remove(&actor_account);
|
|
|
-
|
|
|
- T::OnActorRemoved::actor_removed(&actor_account);
|
|
|
- }
|
|
|
-
|
|
|
- fn apply_unstake(
|
|
|
- actor_account: T::AccountId,
|
|
|
- role: Role,
|
|
|
- member_id: MemberId<T>,
|
|
|
- unbonding_period: T::BlockNumber,
|
|
|
- stake: BalanceOf<T>,
|
|
|
- ) {
|
|
|
- // simple unstaking ...only applying unbonding period
|
|
|
- Self::update_lock(
|
|
|
- &actor_account,
|
|
|
- stake,
|
|
|
- <system::Module<T>>::block_number() + unbonding_period,
|
|
|
- );
|
|
|
-
|
|
|
- Self::remove_actor_from_service(actor_account, role, member_id);
|
|
|
- }
|
|
|
-
|
|
|
- // Locks account and only allows paying for transaction fees. Account cannot
|
|
|
- // transfer or reserve funds.
|
|
|
- fn update_lock(account: &T::AccountId, stake: BalanceOf<T>, until: T::BlockNumber) {
|
|
|
- T::Currency::set_lock(
|
|
|
- STAKING_ID,
|
|
|
- account,
|
|
|
- stake,
|
|
|
- until,
|
|
|
- WithdrawReasons::all() & !(WithdrawReason::TransactionPayment | WithdrawReason::Fee),
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- pub fn is_role_account(account_id: &T::AccountId) -> bool {
|
|
|
- <ActorByAccountId<T>>::exists(account_id)
|
|
|
- }
|
|
|
-
|
|
|
- pub fn account_has_role(account_id: &T::AccountId, role: Role) -> bool {
|
|
|
- Self::actor_by_account_id(account_id).map_or(false, |actor| actor.role == role)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-decl_module! {
|
|
|
- pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
|
|
- fn deposit_event() = default;
|
|
|
-
|
|
|
- fn on_initialize(now: T::BlockNumber) {
|
|
|
- // clear expired requests
|
|
|
- if now % T::BlockNumber::from(REQUEST_CLEARING_INTERVAL) == T::BlockNumber::zero() {
|
|
|
- let requests: Requests<T> = Self::role_entry_requests()
|
|
|
- .into_iter()
|
|
|
- .filter(|request| request.3 > now)
|
|
|
- .collect();
|
|
|
-
|
|
|
- <RoleEntryRequests<T>>::put(requests);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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::account_ids_by_role(role);
|
|
|
- for actor in accounts.into_iter().map(Self::actor_by_account_id) {
|
|
|
- if let Some(actor) = actor {
|
|
|
- if now > actor.joined_at + params.reward_period {
|
|
|
- // reward can top up balance if it is below minimum stake requirement
|
|
|
- // this guarantees overtime that actor always covers the minimum stake and
|
|
|
- // has enough balance to pay for tx fees
|
|
|
- let balance = T::Currency::free_balance(&actor.account);
|
|
|
- if balance < params.min_stake {
|
|
|
- let _ = T::Currency::deposit_into_existing(&actor.account, params.reward);
|
|
|
- } else {
|
|
|
- // otherwise it should go the the member's root account
|
|
|
- if let Some(profile) = <membership::members::Module<T>>::member_profile(&actor.member_id) {
|
|
|
- let _ = T::Currency::deposit_into_existing(&profile.root_account, params.reward);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn role_entry_request(origin, role: Role, member_id: MemberId<T>) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
-
|
|
|
- ensure!(!Self::is_role_account(&sender), "account already used");
|
|
|
-
|
|
|
- ensure!(Self::is_role_available(role), "inactive role");
|
|
|
-
|
|
|
- let role_parameters = Self::ensure_role_parameters(role)?;
|
|
|
-
|
|
|
- <membership::members::Module<T>>::ensure_profile(member_id)?;
|
|
|
-
|
|
|
- // 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);
|
|
|
-
|
|
|
- <RoleEntryRequests<T>>::mutate(|requests| {
|
|
|
- let expires = <system::Module<T>>::block_number()+ T::BlockNumber::from(Self::request_life_time());
|
|
|
- requests.push((sender.clone(), member_id, role, expires));
|
|
|
- });
|
|
|
- Self::deposit_event(RawEvent::EntryRequested(sender, role));
|
|
|
- }
|
|
|
-
|
|
|
- /// Member activating entry request
|
|
|
- pub fn stake(origin, role: Role, actor_account: T::AccountId) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
- ensure!(<membership::members::Module<T>>::is_member_account(&sender), "members only can accept storage entry request");
|
|
|
-
|
|
|
- // get member ids from requests that are controller by origin
|
|
|
- let ids = Self::role_entry_requests()
|
|
|
- .iter()
|
|
|
- .filter(|request| request.0 == actor_account && request.2 == role)
|
|
|
- .map(|request| request.1)
|
|
|
- .filter(|member_id|
|
|
|
- <membership::members::Module<T>>::ensure_profile(*member_id)
|
|
|
- .ok()
|
|
|
- .map_or(false, |profile| profile.root_account == sender || profile.controller_account == sender)
|
|
|
- )
|
|
|
- .collect::<Vec<_>>();
|
|
|
-
|
|
|
- ensure!(!ids.is_empty(), "no role entry request matches");
|
|
|
-
|
|
|
- // take first matching id
|
|
|
- let member_id = ids[0];
|
|
|
-
|
|
|
- ensure!(!Self::is_role_account(&actor_account), "account already used");
|
|
|
-
|
|
|
- // make sure role is still available
|
|
|
- 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);
|
|
|
-
|
|
|
- // ensure there is an empty slot for the role
|
|
|
- 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, "not enough balance to stake");
|
|
|
-
|
|
|
- <AccountIdsByRole<T>>::mutate(role, |accounts| accounts.push(actor_account.clone()));
|
|
|
- <AccountIdsByMemberId<T>>::mutate(&member_id, |accounts| accounts.push(actor_account.clone()));
|
|
|
-
|
|
|
- // Lock minimum stake, but allow spending for transaction fees
|
|
|
- Self::update_lock(&actor_account, role_parameters.min_stake, T::BlockNumber::max_value());
|
|
|
- <ActorByAccountId<T>>::insert(&actor_account, Actor {
|
|
|
- member_id,
|
|
|
- account: actor_account.clone(),
|
|
|
- role,
|
|
|
- joined_at: <system::Module<T>>::block_number()
|
|
|
- });
|
|
|
- <ActorAccountIds<T>>::mutate(|accounts| accounts.push(actor_account.clone()));
|
|
|
-
|
|
|
- let requests: Requests<T> = Self::role_entry_requests()
|
|
|
- .into_iter()
|
|
|
- .filter(|request| request.0 != actor_account)
|
|
|
- .collect();
|
|
|
- <RoleEntryRequests<T>>::put(requests);
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::Staked(actor_account, role));
|
|
|
- }
|
|
|
-
|
|
|
- pub fn unstake(origin, actor_account: T::AccountId) {
|
|
|
- let sender = ensure_signed(origin)?;
|
|
|
- let actor = Self::ensure_actor(&actor_account)?;
|
|
|
-
|
|
|
- let profile = <membership::members::Module<T>>::ensure_profile(actor.member_id)?;
|
|
|
- ensure!(profile.root_account == sender || profile.controller_account == sender, "only member can unstake storage provider");
|
|
|
-
|
|
|
- let role_parameters = Self::ensure_role_parameters(actor.role)?;
|
|
|
-
|
|
|
- Self::apply_unstake(actor.account.clone(), actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
|
|
|
-
|
|
|
- Self::deposit_event(RawEvent::Unstaked(actor.account, actor.role));
|
|
|
- }
|
|
|
-
|
|
|
- pub fn set_role_parameters(origin, role: Role, params: RoleParameters<BalanceOf<T>, T::BlockNumber>) {
|
|
|
- ensure_root(origin)?;
|
|
|
- let new_stake = params.min_stake;
|
|
|
- <Parameters<T>>::insert(role, params);
|
|
|
- // Update locks for all actors in the role. The lock for each account is already until max_value
|
|
|
- // It doesn't affect actors which are unbonding, they should have already been removed from AccountIdsByRole
|
|
|
- let accounts = Self::account_ids_by_role(role);
|
|
|
- for account in accounts.into_iter() {
|
|
|
- Self::update_lock(&account, new_stake, T::BlockNumber::max_value());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn set_available_roles(origin, roles: Vec<Role>) {
|
|
|
- ensure_root(origin)?;
|
|
|
- AvailableRoles::put(roles);
|
|
|
- }
|
|
|
-
|
|
|
- pub fn add_to_available_roles(origin, role: Role) {
|
|
|
- ensure_root(origin)?;
|
|
|
- if !Self::available_roles().into_iter().any(|r| r == role) {
|
|
|
- AvailableRoles::mutate(|roles| roles.push(role));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn remove_from_available_roles(origin, role: Role) {
|
|
|
- ensure_root(origin)?;
|
|
|
- // Should we eject actors in the role being removed?
|
|
|
- let roles: Vec<Role> = Self::available_roles().into_iter().filter(|r| role != *r).collect();
|
|
|
- AvailableRoles::put(roles);
|
|
|
- }
|
|
|
-
|
|
|
- pub fn remove_actor(origin, actor_account: T::AccountId) {
|
|
|
- ensure_root(origin)?;
|
|
|
- ensure!(<ActorByAccountId<T>>::exists(&actor_account), "error trying to remove non actor account");
|
|
|
- let actor = Self::actor_by_account_id(&actor_account).unwrap();
|
|
|
- let role_parameters = Self::ensure_role_parameters(actor.role)?;
|
|
|
- Self::apply_unstake(actor_account, actor.role, actor.member_id, role_parameters.unbonding_period, role_parameters.min_stake);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|