From 2091d692f93f44afd917b2ac8158a15daadbdca5 Mon Sep 17 00:00:00 2001 From: gabrielle oliveira Date: Fri, 7 Feb 2025 18:49:31 -0300 Subject: [PATCH] docs: improve torus substrate docstrings (#71) Adds docstrings to most storage values and core functions. Closes CHAIN-45. --------- Co-authored-by: Luiz Carvalho --- node/src/chain_spec.rs | 1 - node/src/cli/eth.rs | 7 +- node/src/command.rs | 11 +- node/src/command/benchmarking.rs | 4 +- node/src/rpc.rs | 3 +- node/src/rpc/eth.rs | 17 ++- node/src/service.rs | 11 +- pallets/emission0/api/src/lib.rs | 3 +- pallets/emission0/src/distribute.rs | 6 +- pallets/emission0/src/distribute/math.rs | 37 +++--- pallets/emission0/src/lib.rs | 32 +++-- pallets/emission0/src/migrations.rs | 1 - pallets/governance/src/application.rs | 48 +++++-- pallets/governance/src/config.rs | 11 +- pallets/governance/src/lib.rs | 97 ++++++++++---- pallets/governance/src/migrations.rs | 2 - pallets/governance/src/proposal.rs | 133 +++++++++++++------- pallets/governance/src/roles.rs | 16 ++- pallets/governance/src/voting.rs | 7 +- pallets/governance/src/whitelist.rs | 3 + pallets/governance/tests/application.rs | 6 +- pallets/governance/tests/curator.rs | 3 +- pallets/governance/tests/voting.rs | 10 +- pallets/torus0/src/agent.rs | 50 +++++--- pallets/torus0/src/burn.rs | 18 ++- pallets/torus0/src/fee.rs | 5 +- pallets/torus0/src/lib.rs | 110 +++++++++++----- pallets/torus0/src/migrations.rs | 1 - pallets/torus0/src/stake.rs | 41 +++--- runtime/src/apis.rs | 8 +- runtime/src/configs.rs | 25 ++-- runtime/src/configs/eth.rs | 41 +++--- runtime/src/lib.rs | 40 +++--- runtime/src/precompiles/balance_transfer.rs | 6 +- runtime/src/precompiles/mod.rs | 7 +- test-utils/src/lib.rs | 5 +- xtask/src/build_spec.rs | 38 +++--- xtask/src/run.rs | 3 +- 38 files changed, 567 insertions(+), 300 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 535d9c5..20b1a3e 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -52,7 +52,6 @@ fn testnet_genesis() -> Value { polkadot_sdk_frame::traits::Get, sp_keyring::{Ed25519Keyring, Sr25519Keyring}, }; - use torus_runtime::{ interface::{Balance, MinimumBalance}, BalancesConfig, SudoConfig, diff --git a/node/src/cli/eth.rs b/node/src/cli/eth.rs index 602c4d3..b0ecbb7 100644 --- a/node/src/cli/eth.rs +++ b/node/src/cli/eth.rs @@ -25,7 +25,8 @@ pub fn db_config_dir(config: &Configuration) -> PathBuf { /// Available frontier backend types. #[derive(Debug, Copy, Clone, Default, clap::ValueEnum)] pub enum BackendType { - /// Either RocksDb or ParityDb as per inherited from the global backend settings. + /// Either RocksDb or ParityDb as per inherited from the global backend + /// settings. #[default] KeyValue, /// Sql database with custom log indexing. @@ -50,8 +51,8 @@ pub struct EthConfiguration { #[arg(long, default_value = "1")] pub target_gas_price: u64, - /// Maximum allowed gas limit will be `block.gas_limit * execute_gas_limit_multiplier` - /// when using eth_call/eth_estimateGas. + /// Maximum allowed gas limit will be `block.gas_limit * + /// execute_gas_limit_multiplier` when using eth_call/eth_estimateGas. #[arg(long, default_value = "2")] pub execute_gas_limit_multiplier: u64, /// Size in bytes of the LRU cache for block data. diff --git a/node/src/command.rs b/node/src/command.rs index ed977cd..81a76c6 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -15,17 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - chain_spec, - cli::{Cli, Subcommand}, - service, -}; use benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}; use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; use futures::TryFutureExt; use polkadot_sdk::{sc_cli::SubstrateCli, sc_service::PartialComponents, *}; use torus_runtime::interface::Block; +use crate::{ + chain_spec, + cli::{Cli, Subcommand}, + service, +}; + mod benchmarking; impl SubstrateCli for Cli { diff --git a/node/src/command/benchmarking.rs b/node/src/command/benchmarking.rs index 12f8b50..056708c 100644 --- a/node/src/command/benchmarking.rs +++ b/node/src/command/benchmarking.rs @@ -2,7 +2,7 @@ //! //! Should only be used for benchmarking as it may break in other contexts. -use crate::service::FullClient; +use std::{sync::Arc, time::Duration}; use polkadot_sdk::{ sc_cli::Result, @@ -15,7 +15,7 @@ use polkadot_sdk::{ }; use torus_runtime::interface::{AccountId, Balance, BalancesCall, SystemCall}; -use std::{sync::Arc, time::Duration}; +use crate::service::FullClient; /// Generates extrinsics for the `benchmark overhead` command. /// diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 847e0ae..4a419d2 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -22,6 +22,8 @@ #![warn(missing_docs)] +use std::sync::Arc; + use eth::EthDeps; use futures::channel::mpsc; use jsonrpsee::RpcModule; @@ -38,7 +40,6 @@ use polkadot_sdk::{ sp_runtime::traits::Block as BlockT, substrate_frame_rpc_system::{System, SystemApiServer}, }; -use std::sync::Arc; use torus_runtime::{interface::Hash, opaque::Block}; use crate::service::FullClient; diff --git a/node/src/rpc/eth.rs b/node/src/rpc/eth.rs index 0a4d98d..7f3ca66 100644 --- a/node/src/rpc/eth.rs +++ b/node/src/rpc/eth.rs @@ -1,5 +1,10 @@ use std::{collections::BTreeMap, sync::Arc}; +// Frontier +pub use fc_rpc::EthBlockDataCacheTask; +pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; +use fc_storage::StorageOverride; +use fp_rpc::ConvertTransaction; use jsonrpsee::RpcModule; use polkadot_sdk::{ sc_network::service::traits::NetworkService, @@ -11,11 +16,6 @@ use polkadot_sdk::{ sp_inherents::CreateInherentDataProviders, sp_runtime::traits::Block as BlockT, }; -// Frontier -pub use fc_rpc::EthBlockDataCacheTask; -pub use fc_rpc_core::types::{FeeHistoryCache, FeeHistoryCacheLimit, FilterPool}; -use fc_storage::StorageOverride; -use fp_rpc::ConvertTransaction; use torus_runtime::opaque::Block; use crate::service::{FullBackend, FullClient}; @@ -52,8 +52,8 @@ pub struct EthDeps { pub fee_history_cache: FeeHistoryCache, /// Maximum fee history cache size. pub fee_history_cache_limit: FeeHistoryCacheLimit, - /// Maximum allowed gas limit will be ` block.gas_limit * execute_gas_limit_multiplier` when - /// using eth_call/eth_estimateGas. + /// Maximum allowed gas limit will be ` block.gas_limit * + /// execute_gas_limit_multiplier` when using eth_call/eth_estimateGas. pub execute_gas_limit_multiplier: u64, /// Mandated parent hashes for a given block hash. pub forced_parent_hashes: Option>, @@ -95,9 +95,8 @@ where use fc_rpc::{ pending::AuraConsensusDataProvider, Debug, DebugApiServer, Eth, EthApiServer, EthDevSigner, EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, EthSigner, Net, NetApiServer, - Web3, Web3ApiServer, + TxPool, TxPoolApiServer, Web3, Web3ApiServer, }; - use fc_rpc::{TxPool, TxPoolApiServer}; let EthDeps { client, diff --git a/node/src/service.rs b/node/src/service.rs index 41c9451..331e03d 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -15,6 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::{pin::Pin, sync::Arc, time::Duration}; + use fc_rpc::StorageOverrideHandler; use futures::FutureExt; use polkadot_sdk::{ @@ -28,7 +30,6 @@ use polkadot_sdk::{ sp_runtime::traits::Block as BlockT, *, }; -use std::{pin::Pin, sync::Arc, time::Duration}; use torus_runtime::{apis::RuntimeApi, configs::eth::TransactionConverter, opaque::Block}; use crate::cli::{ @@ -495,9 +496,11 @@ pub async fn new_full, > = Default::default(); diff --git a/pallets/emission0/api/src/lib.rs b/pallets/emission0/api/src/lib.rs index e18b4e8..ab57be9 100644 --- a/pallets/emission0/api/src/lib.rs +++ b/pallets/emission0/api/src/lib.rs @@ -8,6 +8,7 @@ pub struct ConsensusMemberStats { pub trait Emission0Api { /// Fetches stats emitted by the consensus for an agent. - /// Returns `None` if the agent has not taken part in the last consensus run. + /// Returns `None` if the agent has not taken part in the last consensus + /// run. fn consensus_stats(member: &AccountId) -> Option; } diff --git a/pallets/emission0/src/distribute.rs b/pallets/emission0/src/distribute.rs index 9d725dc..c040196 100644 --- a/pallets/emission0/src/distribute.rs +++ b/pallets/emission0/src/distribute.rs @@ -212,7 +212,8 @@ impl ConsensusMemberInput { } } - /// Removes self-weights, ensures the keys are registered to the consensus and normalizes it. + /// Removes self-weights, ensures the keys are registered to the consensus + /// and normalizes it. pub fn prepare_weights( member: ConsensusMember, agent_id: &T::AccountId, @@ -242,7 +243,8 @@ impl ConsensusMemberInput { weights } - /// Normalizes the list of stakers to the agent, and adds the agent itself in case no stake was given. + /// Normalizes the list of stakers to the agent, and adds the agent itself + /// in case no stake was given. pub fn normalized_stakers(&self) -> Vec<(T::AccountId, Perquintill)> { self.stakes .iter() diff --git a/pallets/emission0/src/distribute/math.rs b/pallets/emission0/src/distribute/math.rs index d9cf472..fc0e100 100644 --- a/pallets/emission0/src/distribute/math.rs +++ b/pallets/emission0/src/distribute/math.rs @@ -1,16 +1,15 @@ #![allow(dead_code)] +// This is necessary because functions like `f64::{fract, floor, ceil}` +// are only implemented in `std`. +#[cfg(not(feature = "std"))] +use num_traits::float::FloatCore; use polkadot_sdk::sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, vec, vec::Vec}; use substrate_fixed::{ transcendental::{exp, ln}, types::I96F32, }; -// This is necessary because functions like `f64::{fract, floor, ceil}` -// are only implemented in `std`. -#[cfg(not(feature = "std"))] -use num_traits::float::FloatCore; - // Return true when vector sum is zero. pub fn is_zero(vector: &[I96F32]) -> bool { vector.iter().sum::() == I96F32::from_num(0) @@ -344,9 +343,10 @@ pub fn weighted_median_col_sparse( median } -// Stake-weighted median score finding algorithm, based on a mid pivot binary search. -// Normally a random pivot is used, but to ensure full determinism the mid point is chosen instead. -// Assumes relatively random score order for efficiency, typically less than O(nlogn) complexity. +// Stake-weighted median score finding algorithm, based on a mid pivot binary +// search. Normally a random pivot is used, but to ensure full determinism the +// mid point is chosen instead. Assumes relatively random score order for +// efficiency, typically less than O(nlogn) complexity. // // # Args: // * 'stake': ( &Vec ): @@ -362,12 +362,12 @@ pub fn weighted_median_col_sparse( // - minority_ratio = 1 - majority_ratio // // * 'partition_lo' ( I96F32 ): -// - lower edge of stake for partition, where partition is a segment [lo, hi] inside stake -// integral [0, 1]. +// - lower edge of stake for partition, where partition is a segment [lo, hi] +// inside stake integral [0, 1]. // // * 'partition_hi' ( I96F32 ): -// - higher edge of stake for partition, where partition is a segment [lo, hi] inside stake -// integral [0, 1]. +// - higher edge of stake for partition, where partition is a segment [lo, hi] +// inside stake integral [0, 1]. // // # Returns: // * 'median': ( I96F32 ): @@ -466,7 +466,8 @@ pub fn row_sum_sparse(sparse_matrix: &[Vec<(u16, I96F32)>]) -> Vec { result } -// Return sparse matrix with values above column threshold set to threshold value. +// Return sparse matrix with values above column threshold set to threshold +// value. pub fn col_clip_sparse( sparse_matrix: &[Vec<(u16, I96F32)>], col_threshold: &[I96F32], @@ -706,8 +707,8 @@ pub fn mat_ema_sparse( result } -/// Max-upscale vector and convert to u16 so max_value = u16::MAX. Assumes non-negative normalized -/// input. +/// Max-upscale vector and convert to u16 so max_value = u16::MAX. Assumes +/// non-negative normalized input. pub fn vec_max_upscale_to_u16(vec: &[I96F32]) -> Vec { let u16_max = I96F32::from_num(u16::MAX); let threshold = I96F32::from_num(32768); @@ -797,9 +798,10 @@ pub fn inplace_col_max_upscale_sparse(sparse_matrix: &mut [Vec<(u16, I96F32)>], #[cfg(test)] #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] mod tests { - use super::*; use substrate_fixed::types::{I96F32, U64F64}; + use super::*; + macro_rules! fixed_vec { () => { vec![] @@ -809,7 +811,8 @@ mod tests { }; } - /// Reshape vector to sparse matrix with specified number of input rows, cast f32 to I96F32. + /// Reshape vector to sparse matrix with specified number of input rows, + /// cast f32 to I96F32. fn vec_to_sparse_mat_fixed( vector: &[f32], rows: usize, diff --git a/pallets/emission0/src/lib.rs b/pallets/emission0/src/lib.rs index e74cd53..7b18c7c 100644 --- a/pallets/emission0/src/lib.rs +++ b/pallets/emission0/src/lib.rs @@ -6,12 +6,13 @@ pub mod migrations; pub(crate) use ext::*; pub use pallet::*; use pallet_emission0_api::Emission0Api; -use polkadot_sdk::frame_support::dispatch::DispatchResult; -use polkadot_sdk::frame_support::{pallet_prelude::*, DefaultNoBound}; -use polkadot_sdk::frame_system; -use polkadot_sdk::frame_system::pallet_prelude::OriginFor; -use polkadot_sdk::polkadot_sdk_frame::{self as frame, traits::Currency}; -use polkadot_sdk::sp_runtime::Percent; +use polkadot_sdk::{ + frame_support::{dispatch::DispatchResult, pallet_prelude::*, DefaultNoBound}, + frame_system, + frame_system::pallet_prelude::OriginFor, + polkadot_sdk_frame::{self as frame, traits::Currency}, + sp_runtime::Percent, +}; #[doc(hidden)] pub mod distribute; @@ -36,33 +37,48 @@ pub mod pallet { use super::*; + /// Map of consensus members indexed by their keys. A consensus member is + /// any agent eligible for emissions in the next epoch. This means + /// unregistered agents will also receive emissions. #[pallet::storage] pub type ConsensusMembers = StorageMap<_, Identity, AccountIdOf, ConsensusMember>; + /// Map of agents delegating weight control to other agents. Emissions + /// derived from weight delegation are taxed and the fees go the original + /// weight setter. #[pallet::storage] pub type WeightControlDelegation = StorageMap<_, Identity, T::AccountId, T::AccountId>; + // TODO: remove #[pallet::storage] pub type MinAllowedWeights = StorageValue<_, u16, ValueQuery, T::DefaultMinAllowedWeights>; + /// Maximum number of weights a validator can set. #[pallet::storage] pub type MaxAllowedWeights = StorageValue<_, u16, ValueQuery, T::DefaultMaxAllowedWeights>; + // TODO: cap weights on distribution. + /// Minimum stake a validator needs for each weight it sets. #[pallet::storage] pub type MinStakePerWeight = StorageValue<_, BalanceOf, ValueQuery>; + /// Percentage of issued tokens to be burned every epoch. #[pallet::storage] pub type EmissionRecyclingPercentage = StorageValue<_, Percent, ValueQuery, T::DefaultEmissionRecyclingPercentage>; + /// Ratio between incentives and dividends on distribution. 50% means they + /// are distributed equally. #[pallet::storage] pub type IncentivesRatio = StorageValue<_, Percent, ValueQuery, T::DefaultIncentivesRatio>; + /// Amount of tokens accumulated since the last epoch. This increases on + /// every block. See [`distribute::get_total_emission_per_block`]. #[pallet::storage] pub type PendingEmission = StorageValue<_, BalanceOf, ValueQuery>; @@ -79,7 +95,8 @@ pub mod pallet { #[pallet::constant] type MaxSupply: Get; - /// Emissions per block in NANOs. Not taking into account halving and recycling. + /// Emissions per block in NANOs. Not taking into account halving and + /// recycling. #[pallet::constant] type BlockEmission: Get; @@ -156,7 +173,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - // TODO: configure price #[pallet::call_index(0)] #[pallet::weight((T::WeightInfo::set_weights(), DispatchClass::Normal, Pays::Yes))] pub fn set_weights( diff --git a/pallets/emission0/src/migrations.rs b/pallets/emission0/src/migrations.rs index fb5a4cf..57f390c 100644 --- a/pallets/emission0/src/migrations.rs +++ b/pallets/emission0/src/migrations.rs @@ -9,7 +9,6 @@ use crate::{Config, Pallet}; pub mod v1 { use super::*; - use crate::{EmissionRecyclingPercentage, IncentivesRatio}; pub type Migration = VersionedMigration<0, 1, MigrateToV1, Pallet, W>; diff --git a/pallets/governance/src/application.rs b/pallets/governance/src/application.rs index d46a0e2..27af4c4 100644 --- a/pallets/governance/src/application.rs +++ b/pallets/governance/src/application.rs @@ -1,15 +1,24 @@ -use crate::frame::traits::ExistenceRequirement; -use crate::{whitelist, AccountIdOf, AgentApplications, BalanceOf, Block}; use codec::{Decode, Encode, MaxEncodedLen}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::frame_support::dispatch::DispatchResult; -use polkadot_sdk::frame_support::traits::Currency; -use polkadot_sdk::frame_support::traits::WithdrawReasons; -use polkadot_sdk::frame_support::DebugNoBound; -use polkadot_sdk::sp_runtime::BoundedVec; -use polkadot_sdk::sp_std::vec::Vec; +use polkadot_sdk::{ + frame_election_provider_support::Get, + frame_support::{ + dispatch::DispatchResult, + traits::{Currency, WithdrawReasons}, + DebugNoBound, + }, + sp_runtime::BoundedVec, + sp_std::vec::Vec, +}; use scale_info::TypeInfo; +use crate::{ + frame::traits::ExistenceRequirement, whitelist, AccountIdOf, AgentApplications, BalanceOf, + Block, +}; + +/// Decentralized autonomous organization application, it's used to do agent +/// operations on the network, like creating or removing, and needs to be +/// approved by other peers. #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct AgentApplication { @@ -23,6 +32,7 @@ pub struct AgentApplication { pub status: ApplicationStatus, } +/// Possible operations are adding or removing applications. #[derive(DebugNoBound, Decode, Encode, TypeInfo, MaxEncodedLen, PartialEq, Eq)] pub enum ApplicationAction { Add, @@ -32,7 +42,11 @@ pub enum ApplicationAction { #[derive(DebugNoBound, Decode, Encode, TypeInfo, MaxEncodedLen, PartialEq, Eq)] pub enum ApplicationStatus { Open, - Resolved { accepted: bool }, + /// The application was processed before expiration, can be either accepted + /// or rejected. + Resolved { + accepted: bool, + }, Expired, } @@ -44,6 +58,9 @@ impl AgentApplication { } } +/// Creates a new agent application if the key is not yet whitelisted. It +/// withdraws a fee from the payer account, which is given back if the +/// application is accepted. The fee avoids actors spamming applications. pub fn submit_application( payer: AccountIdOf, agent_key: AccountIdOf, @@ -116,6 +133,8 @@ pub fn submit_application( Ok(()) } +/// Accepts an agent application and executes it if it's still open, fails +/// otherwise. pub fn accept_application(application_id: u32) -> DispatchResult { let application = crate::AgentApplications::::get(application_id) .ok_or(crate::Error::::ApplicationNotFound)?; @@ -145,8 +164,7 @@ pub fn accept_application(application_id: u32) -> DispatchResu } }); - // Pay the application cost back to the applicant - // TODO: should this value be used? + // Give the application fee back to the payer key. let _ = ::Currency::deposit_creating(&application.payer_key, application.cost); @@ -155,6 +173,7 @@ pub fn accept_application(application_id: u32) -> DispatchResu Ok(()) } +/// Rejects an open application. pub fn deny_application(application_id: u32) -> DispatchResult { let application = crate::AgentApplications::::get(application_id) .ok_or(crate::Error::::ApplicationNotFound)?; @@ -176,9 +195,11 @@ pub fn deny_application(application_id: u32) -> DispatchResult Ok(()) } +/// Iterates through all open applications checking if the current block is +/// greater or equal to the former's expiration block. If so, marks the +/// application as Expired. pub(crate) fn resolve_expired_applications(current_block: Block) { for application in crate::AgentApplications::::iter_values() { - // Skip if not expired yet or if not in Open status if current_block < application.expires_at || !matches!(application.status, ApplicationStatus::Open) { @@ -197,6 +218,7 @@ pub(crate) fn resolve_expired_applications(current_block: Bloc } } +/// If any applications for this agent and action are already pending. pub(crate) fn exists_for_agent_key( key: &AccountIdOf, action: &ApplicationAction, diff --git a/pallets/governance/src/config.rs b/pallets/governance/src/config.rs index 7d91f25..76f9cb2 100644 --- a/pallets/governance/src/config.rs +++ b/pallets/governance/src/config.rs @@ -1,6 +1,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::{frame_support::DebugNoBound, sp_runtime::Percent}; +use polkadot_sdk::{ + frame_election_provider_support::Get, frame_support::DebugNoBound, sp_runtime::Percent, +}; use scale_info::TypeInfo; use crate::{BalanceOf, BlockAmount}; @@ -22,11 +23,11 @@ impl Default for GovernanceConfiguration { Self { proposal_cost: T::DefaultProposalCost::get(), proposal_expiration: T::DefaultProposalExpiration::get(), //130_000, - agent_application_cost: T::DefaultAgentApplicationCost::get(), //100_000_000_000_000_000_000, + agent_application_cost: T::DefaultAgentApplicationCost::get(), /* 100_000_000_000_000_000_000, */ agent_application_expiration: T::DefaultAgentApplicationExpiration::get(), //2_000, - proposal_reward_treasury_allocation: T::DefaultProposalRewardTreasuryAllocation::get(), //Percent::from_percent(2), + proposal_reward_treasury_allocation: T::DefaultProposalRewardTreasuryAllocation::get(), /* Percent::from_percent(2), */ max_proposal_reward_treasury_allocation: - T::DefaultMaxProposalRewardTreasuryAllocation::get(), //10_000_000_000_000_000_000_000, + T::DefaultMaxProposalRewardTreasuryAllocation::get(), /* 10_000_000_000_000_000_000_000, */ proposal_reward_interval: T::DefaultProposalRewardInterval::get(), //75_600, } } diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index 97478c4..b6fd50f 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -13,25 +13,30 @@ pub mod whitelist; pub mod benchmarks; pub mod weights; -use crate::application::AgentApplication; -use crate::config::GovernanceConfiguration; -use crate::proposal::Proposal; -use crate::proposal::ProposalId; -use crate::proposal::UnrewardedProposal; pub(crate) use ext::*; use frame::prelude::ensure_root; pub use pallet::*; -use polkadot_sdk::frame_support::{ - dispatch::DispatchResult, - pallet_prelude::{ValueQuery, *}, - sp_runtime::Percent, - traits::Currency, - Identity, PalletId, +use polkadot_sdk::{ + frame_support::{ + dispatch::DispatchResult, + pallet_prelude::{ValueQuery, *}, + sp_runtime::Percent, + traits::Currency, + Identity, PalletId, + }, + frame_system::pallet_prelude::{ensure_signed, BlockNumberFor, OriginFor}, + polkadot_sdk_frame::{ + traits::AccountIdConversion, + {self as frame}, + }, + sp_std::vec::Vec, +}; + +use crate::{ + application::AgentApplication, + config::GovernanceConfiguration, + proposal::{Proposal, ProposalId, UnrewardedProposal}, }; -use polkadot_sdk::frame_system::pallet_prelude::{ensure_signed, BlockNumberFor, OriginFor}; -use polkadot_sdk::polkadot_sdk_frame::traits::AccountIdConversion; -use polkadot_sdk::polkadot_sdk_frame::{self as frame}; -use polkadot_sdk::sp_std::vec::Vec; #[frame::pallet] pub mod pallet { @@ -44,17 +49,22 @@ pub mod pallet { use super::*; + /// Map of past and present proposals indexed by their incrementing ID. #[pallet::storage] pub type Proposals = StorageMap<_, Identity, ProposalId, Proposal>; + /// Queue of proposals to be rewarded after closing. #[pallet::storage] pub type UnrewardedProposals = StorageMap<_, Identity, ProposalId, UnrewardedProposal>; + /// List of keys that are NOT delegating their voting power. By default, all + /// keys delegate their voting power. #[pallet::storage] pub type NotDelegatingVotingPower = StorageValue<_, BoundedBTreeSet, ConstU32<{ u32::MAX }>>, ValueQuery>; + /// Global governance configuration files. #[pallet::storage] pub type GlobalGovernanceConfig = StorageValue<_, GovernanceConfiguration, ValueQuery>; @@ -65,22 +75,32 @@ pub mod pallet { ::PalletId::get().into_account_truncating() } + /// The treasury address to which the treasury emission percentages and + /// other funds go to. A proposal can be created withdrawing the funds to a + /// key. #[pallet::storage] pub type DaoTreasuryAddress = StorageValue<_, AccountIdOf, ValueQuery, DefaultDaoTreasuryAddress>; + /// A map of agent applications, past and present. #[pallet::storage] pub type AgentApplications = StorageMap<_, Identity, u32, AgentApplication>; + /// List of whitelisted keys. Keys listed here are allowed to register + /// agents. #[pallet::storage] pub type Whitelist = StorageMap<_, Identity, AccountIdOf, ()>; + /// List of curator keys, which can accept and reject applications. #[pallet::storage] pub type Curators = StorageMap<_, Identity, AccountIdOf, ()>; + /// List of allocator keys, which are the default validators on the network. #[pallet::storage] pub type Allocators = StorageMap<_, Identity, AccountIdOf, ()>; + /// Fee taken from emission distribution and deposited into + /// [`DaoTreasuryAddress`]. #[pallet::storage] pub type TreasuryEmissionFee = StorageValue<_, Percent, ValueQuery, T::DefaultTreasuryEmissionFee>; @@ -162,6 +182,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Adds a new curator to the list. Only available for the root key. #[pallet::call_index(0)] #[pallet::weight((::WeightInfo::add_curator(), DispatchClass::Normal, Pays::Yes))] pub fn add_curator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -169,6 +190,8 @@ pub mod pallet { roles::manage_role::>(key, true, Error::::AlreadyCurator) } + /// Removes an existing curator from the list. Only available for the + /// root key. #[pallet::call_index(1)] #[pallet::weight((::WeightInfo::remove_curator(), DispatchClass::Normal, Pays::Yes))] pub fn remove_curator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -176,6 +199,7 @@ pub mod pallet { roles::manage_role::>(key, false, Error::::NotAllocator) } + /// Adds a new allocator to the list. Only available for the root key. #[pallet::call_index(2)] #[pallet::weight((::WeightInfo::add_allocator(), DispatchClass::Normal, Pays::Yes))] pub fn add_allocator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -183,6 +207,8 @@ pub mod pallet { roles::manage_role::>(key, true, Error::::AlreadyAllocator) } + /// Removes an existing allocator from the list. Only available for the + /// root key. #[pallet::call_index(3)] #[pallet::weight((::WeightInfo::remove_allocator(), DispatchClass::Normal, Pays::Yes))] pub fn remove_allocator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -190,6 +216,8 @@ pub mod pallet { roles::manage_role::>(key, false, Error::::NotAllocator) } + /// Forcefully adds a new agent to the whitelist. Only available for the + /// root key or curators. #[pallet::call_index(4)] #[pallet::weight((::WeightInfo::add_to_whitelist(), DispatchClass::Normal, Pays::Yes))] pub fn add_to_whitelist(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -197,6 +225,8 @@ pub mod pallet { whitelist::add_to_whitelist::(key) } + /// Forcefully removes an agent from the whitelist. Only available for + /// the root key or curators. #[pallet::call_index(5)] #[pallet::weight((::WeightInfo::remove_from_whitelist(), DispatchClass::Normal, Pays::Yes))] pub fn remove_from_whitelist(origin: OriginFor, key: AccountIdOf) -> DispatchResult { @@ -204,6 +234,8 @@ pub mod pallet { whitelist::remove_from_whitelist::(key) } + /// Accepts an agent application. Only available for the root key or + /// curators. #[pallet::call_index(6)] #[pallet::weight((::WeightInfo::accept_application(), DispatchClass::Normal, Pays::Yes))] pub fn accept_application(origin: OriginFor, application_id: u32) -> DispatchResult { @@ -211,6 +243,8 @@ pub mod pallet { application::accept_application::(application_id) } + /// Denies an agent application. Only available for the root key or + /// curators. #[pallet::call_index(7)] #[pallet::weight((::WeightInfo::deny_application(), DispatchClass::Normal, Pays::Yes))] pub fn deny_application(origin: OriginFor, application_id: u32) -> DispatchResult { @@ -218,6 +252,8 @@ pub mod pallet { application::deny_application::(application_id) } + /// Sets a penalty factor to the given agent emissions. Only available + /// for the root key or curators. #[pallet::call_index(8)] #[pallet::weight((::WeightInfo::penalize_agent(), DispatchClass::Normal, Pays::Yes))] pub fn penalize_agent( @@ -229,6 +265,7 @@ pub mod pallet { roles::penalize_agent::(agent_key, percentage) } + /// Submits a new agent application on behalf of a given key. #[pallet::call_index(9)] #[pallet::weight((::WeightInfo::submit_application(), DispatchClass::Normal, Pays::Yes))] pub fn submit_application( @@ -241,6 +278,7 @@ pub mod pallet { application::submit_application::(payer, agent_key, metadata, removing) } + /// Creates a new global parameters proposal. #[pallet::call_index(10)] #[pallet::weight((::WeightInfo::add_global_params_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn add_global_params_proposal( @@ -252,6 +290,7 @@ pub mod pallet { proposal::add_global_params_proposal::(proposer, data, metadata) } + /// Creates a new custom global proposal. #[pallet::call_index(11)] #[pallet::weight((::WeightInfo::add_global_custom_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn add_global_custom_proposal( @@ -262,6 +301,8 @@ pub mod pallet { proposal::add_global_custom_proposal::(proposer, metadata) } + /// Creates a proposal moving funds from the treasury account to the + /// given key. #[pallet::call_index(12)] #[pallet::weight((::WeightInfo::add_dao_treasury_transfer_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn add_dao_treasury_transfer_proposal( @@ -279,6 +320,7 @@ pub mod pallet { ) } + /// Casts a vote for an open proposal. #[pallet::call_index(13)] #[pallet::weight((::WeightInfo::vote_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn vote_proposal( @@ -290,6 +332,7 @@ pub mod pallet { voting::add_vote::(voter, proposal_id, agree) } + /// Removes a casted vote for an open proposal. #[pallet::call_index(14)] #[pallet::weight((::WeightInfo::remove_vote_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn remove_vote_proposal(origin: OriginFor, proposal_id: u64) -> DispatchResult { @@ -297,6 +340,7 @@ pub mod pallet { voting::remove_vote::(voter, proposal_id) } + /// Enables vote power delegation. #[pallet::call_index(15)] #[pallet::weight((::WeightInfo::enable_vote_delegation(), DispatchClass::Normal, Pays::Yes))] pub fn enable_vote_delegation(origin: OriginFor) -> DispatchResult { @@ -304,6 +348,7 @@ pub mod pallet { voting::enable_delegation::(delegator) } + /// Disables vote power delegation. #[pallet::call_index(16)] #[pallet::weight((::WeightInfo::disable_vote_delegation(), DispatchClass::Normal, Pays::Yes))] pub fn disable_vote_delegation(origin: OriginFor) -> DispatchResult { @@ -311,6 +356,7 @@ pub mod pallet { voting::disable_delegation::(delegator) } + /// Creates a new emission percentage proposal. #[pallet::call_index(17)] #[pallet::weight((::WeightInfo::add_emission_proposal(), DispatchClass::Normal, Pays::Yes))] pub fn add_emission_proposal( @@ -330,6 +376,8 @@ pub mod pallet { ) } + /// Forcefully sets emission percentages. Only available for the root + /// key. #[pallet::call_index(18)] #[pallet::weight((::WeightInfo::add_emission_proposal(), DispatchClass::Normal, Pays::No))] pub fn set_emission_params( @@ -385,9 +433,11 @@ pub mod pallet { InvalidProposalFinalizationParameters, /// Invalid parameters were provided to the voting process. InvalidProposalVotingParameters, - /// Negative proposal cost when setting global or subnet governance configuration. + /// Negative proposal cost when setting global or subnet governance + /// configuration. InvalidProposalCost, - /// Negative expiration when setting global or subnet governance configuration. + /// Negative expiration when setting global or subnet governance + /// configuration. InvalidProposalExpiration, /// Key doesn't have enough tokens to create a proposal. NotEnoughBalanceToPropose, @@ -399,7 +449,8 @@ pub mod pallet { ModuleDelegatingForMaxStakers, /// Proposal with given id doesn't exist. ProposalNotFound, - /// Proposal was either accepted, refused or expired and cannot accept votes. + /// Proposal was either accepted, refused or expired and cannot accept + /// votes. ProposalClosed, /// Proposal data isn't composed by valid UTF-8 characters. InvalidProposalData, @@ -413,10 +464,11 @@ pub mod pallet { NotVoted, /// Key doesn't have enough stake to vote. InsufficientStake, - /// The voter is delegating its voting power to their staked modules. Disable voting power - /// delegation. + /// The voter is delegating its voting power to their staked modules. + /// Disable voting power delegation. VoterIsDelegatingVotingPower, - /// An internal error occurred, probably relating to the size of the bounded sets. + /// An internal error occurred, probably relating to the size of the + /// bounded sets. InternalError, /// The application is not in a pending state. ApplicationNotOpen, @@ -430,7 +482,8 @@ pub mod pallet { ApplicationNotFound, /// The account is already whitelisted and cannot be added again. AlreadyWhitelisted, - /// The account is not whitelisted and cannot be removed from the whitelist. + /// The account is not whitelisted and cannot be removed from the + /// whitelist. NotWhitelisted, /// Failed to convert the given value to a balance. CouldNotConvertToBalance, diff --git a/pallets/governance/src/migrations.rs b/pallets/governance/src/migrations.rs index 30cd290..6ed34af 100644 --- a/pallets/governance/src/migrations.rs +++ b/pallets/governance/src/migrations.rs @@ -11,7 +11,6 @@ pub type Migrations = (v1::Migration, v2::Migration); pub mod v2 { use super::*; - use crate::TreasuryEmissionFee; pub type Migration = VersionedMigration<1, 2, MigrateToV2, Pallet, W>; @@ -29,7 +28,6 @@ pub mod v2 { pub mod v1 { use super::*; - use crate::GlobalGovernanceConfig; pub type Migration = VersionedMigration<0, 1, MigrateToV1, Pallet, W>; diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index 9c9de44..56abbf9 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -1,36 +1,36 @@ -use crate::frame::traits::ExistenceRequirement; -use crate::BoundedBTreeSet; -use crate::BoundedVec; -use crate::DebugNoBound; -use crate::TypeInfo; -use crate::{ - AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, GlobalGovernanceConfig, Proposals, - UnrewardedProposals, -}; -use crate::{GovernanceConfiguration, NotDelegatingVotingPower}; use codec::{Decode, Encode, MaxEncodedLen}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::frame_support::traits::Currency; -use polkadot_sdk::frame_support::traits::WithdrawReasons; -use polkadot_sdk::polkadot_sdk_frame::traits::CheckedAdd; -use polkadot_sdk::sp_runtime::SaturatedConversion; -use polkadot_sdk::sp_std::{collections::btree_set::BTreeSet, vec::Vec}; use polkadot_sdk::{ - frame_support::{dispatch::DispatchResult, ensure, storage::with_storage_layer}, + frame_election_provider_support::Get, + frame_support::{ + dispatch::DispatchResult, + ensure, + storage::with_storage_layer, + traits::{Currency, WithdrawReasons}, + }, + polkadot_sdk_frame::traits::CheckedAdd, sp_core::ConstU32, sp_runtime::{BoundedBTreeMap, DispatchError, Percent}, + sp_std::{collections::btree_set::BTreeSet, vec::Vec}, sp_tracing::error, }; use substrate_fixed::types::I92F36; +use crate::{ + frame::traits::ExistenceRequirement, AccountIdOf, BalanceOf, Block, BoundedBTreeSet, + BoundedVec, DaoTreasuryAddress, DebugNoBound, Error, GlobalGovernanceConfig, + GovernanceConfiguration, NotDelegatingVotingPower, Proposals, TypeInfo, UnrewardedProposals, +}; + pub type ProposalId = u64; +/// A network proposal created by the community. Core part of the DAO. #[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Proposal { pub id: ProposalId, pub proposer: AccountIdOf, pub expiration_block: Block, + /// The actual data and type of the proposal. pub data: ProposalData, pub status: ProposalStatus, pub metadata: BoundedVec>, @@ -45,6 +45,10 @@ impl Proposal { matches!(self.status, ProposalStatus::Open { .. }) } + /// Returns the block in which a proposal should be executed. + /// For emission proposals, that is the creation block + 21600 blocks + /// (roughly 2 days at 1 block every 8 seconds), as for the others, they + /// are only executed on the expiration block. pub fn execution_block(&self) -> Block { match self.data { ProposalData::Emission { .. } => self.creation_block + 21_600, @@ -52,7 +56,7 @@ impl Proposal { } } - /// Marks a proposal as accepted and overrides the storage value. + /// Marks a proposal as accepted and executes it. pub fn accept( mut self, block: Block, @@ -75,7 +79,9 @@ impl Proposal { Ok(()) } + /// Executes the changes. fn execute_proposal(self) -> DispatchResult { + // Proposal fee is given back to the proposer. let _ = ::Currency::deposit_creating(&self.proposer, self.proposal_cost); @@ -137,7 +143,7 @@ impl Proposal { Ok(()) } - /// Marks a proposal as refused and overrides the storage value. + /// Marks a proposal as refused. pub fn refuse( mut self, block: Block, @@ -158,7 +164,7 @@ impl Proposal { Ok(()) } - /// Marks a proposal as expired and overrides the storage value. + /// Marks a proposal as expired. pub fn expire(mut self, block_number: u64) -> DispatchResult { ensure!(self.is_active(), crate::Error::::ProposalIsFinished); ensure!( @@ -178,26 +184,44 @@ impl Proposal { #[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub enum ProposalStatus { + /// The proposal is active and being voted upon. The votes values only hold + /// accounts and not stake per key, because this is subtle to change + /// overtime. The stake values are there to help clients estimate the status + /// of the voting, they are updated every few blocks, but are not used in + /// the final calculation. Open { + /// Accounts who have voted for this proposal to be accepted. votes_for: BoundedBTreeSet, ConstU32<{ u32::MAX }>>, + /// Accounts who have voted against this proposal being accepted. votes_against: BoundedBTreeSet, ConstU32<{ u32::MAX }>>, + /// A roughly estimation of the total stake voting for the proposal. stake_for: BalanceOf, + /// A roughly estimation of the total stake voting against the proposal. stake_against: BalanceOf, }, + /// Proposal was accepted. Accepted { block: Block, + /// Total stake that voted for the proposal. stake_for: BalanceOf, + /// Total stake that voted against the proposal. stake_against: BalanceOf, }, + /// Proposal was refused. Refused { block: Block, + /// Total stake that voted for the proposal. stake_for: BalanceOf, + /// Total stake that voted against the proposal. stake_against: BalanceOf, }, + /// Proposal expired without enough network participation. Expired, } // TODO: add Agent URL max length +/// Update the global parameters configuration, like, max and min name lengths, +/// and other validations. All values are set within default storage values. #[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub struct GlobalParamsData { @@ -270,16 +294,27 @@ impl GlobalParamsData { } } +/// The proposal type and data. #[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub enum ProposalData { + /// Applies changes to global parameters. GlobalParams(GlobalParamsData), + /// A custom proposal with not immediate impact in the chain. Can be used as + /// referendums regarding the future of the chain. GlobalCustom, + /// Changes the emission rates for incentives, recycling and treasury. Emission { + /// The amount of tokens per block to be recycled ("burned"). recycling_percentage: Percent, + /// The amount of tokens sent to the treasury AFTER recycling fee was + /// applied. treasury_percentage: Percent, + /// This changes how incentives and dividends are distributed. 50% means + /// they are distributed equally. incentives_ratio: Percent, }, + /// Transfers funds from the treasury account to the specified account. TransferDaoTreasury { account: AccountIdOf, amount: BalanceOf, @@ -287,6 +322,8 @@ pub enum ProposalData { } impl ProposalData { + /// The percentage of total active stake participating in the proposal for + /// it to be processes (either approved or refused). #[must_use] pub fn required_stake(&self) -> Percent { match self { @@ -305,6 +342,7 @@ pub struct UnrewardedProposal { pub votes_against: BoundedBTreeMap, BalanceOf, ConstU32<{ u32::MAX }>>, } +/// Create global update parameters proposal with metadata. #[allow(clippy::too_many_arguments)] pub fn add_global_params_proposal( proposer: AccountIdOf, @@ -317,6 +355,7 @@ pub fn add_global_params_proposal( add_proposal::(proposer, data, metadata) } +/// Create global custom proposal with metadata. pub fn add_global_custom_proposal( proposer: AccountIdOf, metadata: Vec, @@ -324,6 +363,7 @@ pub fn add_global_custom_proposal( add_proposal(proposer, ProposalData::::GlobalCustom, metadata) } +/// Create a treasury transfer proposal with metadata. pub fn add_dao_treasury_transfer_proposal( proposer: AccountIdOf, value: BalanceOf, @@ -338,6 +378,8 @@ pub fn add_dao_treasury_transfer_proposal( add_proposal::(proposer, data, metadata) } +/// Creates a new emissions proposal. Only valid if `recycling_percentage + +/// treasury_percentage <= u128::MAX`. pub fn add_emission_proposal( proposer: AccountIdOf, recycling_percentage: Percent, @@ -361,6 +403,7 @@ pub fn add_emission_proposal( add_proposal::(proposer, data, metadata) } +/// Creates a new proposal and saves it. Internally used. fn add_proposal( proposer: AccountIdOf, data: ProposalData, @@ -417,15 +460,17 @@ fn add_proposal( Ok(()) } +/// Every 100 blocks, iterates through all pending proposals and executes the +/// ones eligible. pub fn tick_proposals(block_number: Block) { - let not_delegating = NotDelegatingVotingPower::::get().into_inner(); - - let proposals = Proposals::::iter().filter(|(_, p)| p.is_active()); - if block_number % 100 != 0 { return; } + let not_delegating = NotDelegatingVotingPower::::get().into_inner(); + + let proposals = Proposals::::iter().filter(|(_, p)| p.is_active()); + for (id, proposal) in proposals { let res = with_storage_layer(|| tick_proposal(¬_delegating, block_number, proposal)); if let Err(err) = res { @@ -434,19 +479,17 @@ pub fn tick_proposals(block_number: Block) { } } -pub fn get_minimal_stake_to_execute_with_percentage( +/// Returns the minimum amount of active stake needed for a proposal be executed +/// based on the given percentage. +fn get_minimum_stake_to_execute_with_percentage( threshold: Percent, ) -> BalanceOf { let stake = pallet_torus0::TotalStake::::get(); - - stake - .saturated_into::>() - .checked_mul(threshold.deconstruct() as u128) - .unwrap_or_default() - .checked_div(100) - .unwrap_or_default() + threshold.mul_floor(stake) } +/// Sums all stakes for votes in favor and against. The biggest value wins and +/// the proposal is processes and executed. expiration block. fn tick_proposal( not_delegating: &BTreeSet, block_number: u64, @@ -500,7 +543,7 @@ fn tick_proposal( let total_stake = stake_for_sum.saturating_add(stake_against_sum); let minimal_stake_to_execute = - get_minimal_stake_to_execute_with_percentage::(proposal.data.required_stake()); + get_minimum_stake_to_execute_with_percentage::(proposal.data.required_stake()); if total_stake >= minimal_stake_to_execute { create_unrewarded_proposal::(proposal.id, block_number, votes_for, votes_against); @@ -517,6 +560,10 @@ fn tick_proposal( } } +type AccountStakes = BoundedBTreeMap, BalanceOf, ConstU32<{ u32::MAX }>>; + +/// Put the proposal in the reward queue, which will be processed by +/// [tick_proposal_rewards]. fn create_unrewarded_proposal( proposal_id: u64, block_number: Block, @@ -530,11 +577,7 @@ fn create_unrewarded_proposal( .expect("this wont exceed u32::MAX"); } - let mut reward_votes_against: BoundedBTreeMap< - T::AccountId, - BalanceOf, - ConstU32<{ u32::MAX }>, - > = BoundedBTreeMap::new(); + let mut reward_votes_against: AccountStakes = BoundedBTreeMap::new(); for (key, value) in votes_against { reward_votes_against .try_insert(key, value) @@ -551,6 +594,8 @@ fn create_unrewarded_proposal( ); } +/// Calculates the stake for a voter. This function takes into account all +/// accounts delegating voting power to the voter. #[inline] fn calc_stake( not_delegating: &BTreeSet, @@ -571,6 +616,7 @@ fn calc_stake( own_stake.saturating_add(delegated_stake) } +/// Processes the proposal reward queue and distributes rewards for all voters. pub fn tick_proposal_rewards(block_number: u64) { let governance_config = crate::GlobalGovernanceConfig::::get(); let reached_interval = block_number @@ -581,10 +627,10 @@ pub fn tick_proposal_rewards(block_number: u64) { } let mut n: u16 = 0; - let mut account_stakes: BoundedBTreeMap, ConstU32<{ u32::MAX }>> = - BoundedBTreeMap::new(); + let mut account_stakes: AccountStakes = BoundedBTreeMap::new(); let mut total_allocation: I92F36 = I92F36::from_num(0); for (proposal_id, unrewarded_proposal) in UnrewardedProposals::::iter() { + // Just checking if it's in the chain interval if unrewarded_proposal.block < block_number.saturating_sub(governance_config.proposal_reward_interval) { @@ -623,6 +669,7 @@ pub fn tick_proposal_rewards(block_number: u64) { ); } +/// Calculates the total balance to be rewarded for a proposal. pub fn get_reward_allocation( governance_config: &GovernanceConfiguration, n: u16, @@ -661,12 +708,14 @@ pub fn get_reward_allocation( Ok(allocation) } +/// Distributes the proposal rewards in a quadratic formula to all voters. fn distribute_proposal_rewards( - account_stakes: BoundedBTreeMap, ConstU32<{ u32::MAX }>>, + account_stakes: AccountStakes, total_allocation: I92F36, max_proposal_reward_treasury_allocation: BalanceOf, ) { - // This is just a sanity check, making sure we can never allocate more than the max + // This is just a sanity check, making sure we can never allocate more than the + // max if total_allocation > I92F36::from_num(max_proposal_reward_treasury_allocation) { error!("total allocation exceeds max proposal reward treasury allocation"); return; diff --git a/pallets/governance/src/roles.rs b/pallets/governance/src/roles.rs index f17e6fe..5701107 100644 --- a/pallets/governance/src/roles.rs +++ b/pallets/governance/src/roles.rs @@ -1,12 +1,15 @@ -use crate::AccountIdOf; -use crate::{ensure, storage::StorageMap, Config, Error}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::sp_runtime::{DispatchError, Percent}; use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, frame_system::ensure_signed, + frame_election_provider_support::Get, + frame_support::dispatch::DispatchResult, + frame_system::ensure_signed, polkadot_sdk_frame::prelude::OriginFor, + sp_runtime::{DispatchError, Percent}, }; +use crate::{ensure, storage::StorageMap, AccountIdOf, Config, Error}; + +/// Generic function used to manage the Curator and Allocator maps, which behave +/// similarly. pub(super) fn manage_role, ()>>( key: AccountIdOf, is_add: bool, @@ -21,6 +24,7 @@ pub(super) fn manage_role, ()>>( Ok(()) } +/// Sets a penalty ratio for the given agent. pub fn penalize_agent(agent_key: AccountIdOf, percentage: u8) -> DispatchResult { let percentage = Percent::from_parts(percentage); if percentage > T::MaxPenaltyPercentage::get() { @@ -40,6 +44,7 @@ pub fn penalize_agent(agent_key: AccountIdOf, percentage: u8) -> D Ok(()) } +/// Returns error if the origin is not listed as a curator. pub fn ensure_curator(origin: OriginFor) -> DispatchResult { let key: AccountIdOf = ensure_signed(origin)?; if !crate::Curators::::contains_key(key) { @@ -49,6 +54,7 @@ pub fn ensure_curator(origin: OriginFor) -> DispatchResult { Ok(()) } +/// Returns error if the origin is not listed as an allocator. pub fn ensure_allocator(key: &AccountIdOf) -> DispatchResult { if !crate::Allocators::::contains_key(key) { return Err(Error::::NotAllocator.into()); diff --git a/pallets/governance/src/voting.rs b/pallets/governance/src/voting.rs index aef2e11..94f4678 100644 --- a/pallets/governance/src/voting.rs +++ b/pallets/governance/src/voting.rs @@ -1,6 +1,8 @@ -use crate::{proposal::ProposalStatus, AccountIdOf, Error, Event, Proposals}; use polkadot_sdk::frame_support::{dispatch::DispatchResult, ensure}; +use crate::{proposal::ProposalStatus, AccountIdOf, Error, Event, Proposals}; + +/// Casts a vote on behalf of a voter. pub fn add_vote( voter: AccountIdOf, proposal_id: u64, @@ -51,6 +53,7 @@ pub fn add_vote( Ok(()) } +/// Removes the casted vote. pub fn remove_vote(voter: AccountIdOf, proposal_id: u64) -> DispatchResult { let Ok(mut proposal) = Proposals::::try_get(proposal_id) else { return Err(Error::::ProposalNotFound.into()); @@ -76,6 +79,7 @@ pub fn remove_vote(voter: AccountIdOf, proposal_id: u64) -> Ok(()) } +/// Gives voting power delegation to the delegator's staked agents. pub fn enable_delegation(delegator: AccountIdOf) -> DispatchResult { crate::NotDelegatingVotingPower::::mutate(|delegators| { delegators.remove(&delegator); @@ -84,6 +88,7 @@ pub fn enable_delegation(delegator: AccountIdOf) -> Dispatc Ok(()) } +/// Removes voting power delegation to the delegator's staked agents. pub fn disable_delegation(delegator: AccountIdOf) -> DispatchResult { crate::NotDelegatingVotingPower::::mutate(|delegators| { delegators diff --git a/pallets/governance/src/whitelist.rs b/pallets/governance/src/whitelist.rs index fadcc1f..3875fd0 100644 --- a/pallets/governance/src/whitelist.rs +++ b/pallets/governance/src/whitelist.rs @@ -2,6 +2,7 @@ use polkadot_sdk::frame_support::dispatch::DispatchResult; use crate::{application, AccountIdOf}; +/// Adds a key to the DAO whitelist, allowing it to register an agent. pub fn add_to_whitelist(key: AccountIdOf) -> DispatchResult { if is_whitelisted::(&key) { return Err(crate::Error::::AlreadyWhitelisted.into()); @@ -16,6 +17,8 @@ pub fn add_to_whitelist(key: AccountIdOf) -> DispatchResult Ok(()) } +/// Remove a key from the DAO whitelist, disallowing the key to register an +/// agent, or de-registering an existing one. pub fn remove_from_whitelist(key: AccountIdOf) -> DispatchResult { if !is_whitelisted::(&key) { return Err(crate::Error::::NotWhitelisted.into()); diff --git a/pallets/governance/tests/application.rs b/pallets/governance/tests/application.rs index 3f559cb..0c522f0 100644 --- a/pallets/governance/tests/application.rs +++ b/pallets/governance/tests/application.rs @@ -1,6 +1,6 @@ -use pallet_governance::application::ApplicationStatus; -use pallet_governance::AgentApplications; -use pallet_governance::GlobalGovernanceConfig; +use pallet_governance::{ + application::ApplicationStatus, AgentApplications, GlobalGovernanceConfig, +}; use polkadot_sdk::frame_support::assert_err; use test_utils::*; diff --git a/pallets/governance/tests/curator.rs b/pallets/governance/tests/curator.rs index 866f620..b93962e 100644 --- a/pallets/governance/tests/curator.rs +++ b/pallets/governance/tests/curator.rs @@ -1,8 +1,9 @@ -use crate::pallet_governance::Error; use pallet_governance::Curators; use polkadot_sdk::frame_support::assert_err; use test_utils::*; +use crate::pallet_governance::Error; + #[test] fn user_is_added_to_whitelist() { new_test_ext().execute_with(|| { diff --git a/pallets/governance/tests/voting.rs b/pallets/governance/tests/voting.rs index 65951a2..491e563 100644 --- a/pallets/governance/tests/voting.rs +++ b/pallets/governance/tests/voting.rs @@ -3,9 +3,10 @@ use pallet_governance::{ proposal::{GlobalParamsData, ProposalStatus}, DaoTreasuryAddress, Error, GlobalGovernanceConfig, Proposals, }; -use polkadot_sdk::frame_support::traits::Get; -use polkadot_sdk::{frame_support::assert_err, sp_runtime::BoundedBTreeSet}; -use polkadot_sdk::{frame_support::assert_ok, sp_runtime::Percent}; +use polkadot_sdk::{ + frame_support::{assert_err, assert_ok, traits::Get}, + sp_runtime::{BoundedBTreeSet, Percent}, +}; use test_utils::{ add_balance, get_balance, get_origin, new_test_ext, step_block, to_nano, zero_min_burn, AccountId, Test, @@ -519,7 +520,8 @@ fn creates_emission_proposal_with_invalid_params_and_it_fails() { fn rewards_wont_exceed_treasury() { new_test_ext().execute_with(|| { zero_min_burn(); - // Fill the governance address with 1 mil so we are not limited by the max allocation + // Fill the governance address with 1 mil so we are not limited by the max + // allocation let amount = to_nano(1_000_000_000); let key = DaoTreasuryAddress::::get(); add_balance(key, amount); diff --git a/pallets/torus0/src/agent.rs b/pallets/torus0/src/agent.rs index 56673d3..ada9171 100644 --- a/pallets/torus0/src/agent.rs +++ b/pallets/torus0/src/agent.rs @@ -1,32 +1,50 @@ -use crate::AccountIdOf; use codec::{Decode, Encode, MaxEncodedLen}; use pallet_emission0_api::Emission0Api; use pallet_governance_api::GovernanceApi; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons}; -use polkadot_sdk::frame_support::DebugNoBound; -use polkadot_sdk::polkadot_sdk_frame::prelude::BlockNumberFor; -use polkadot_sdk::sp_runtime::DispatchError; use polkadot_sdk::{ - frame_support::{dispatch::DispatchResult, ensure}, - sp_runtime::{BoundedVec, Percent}, + frame_election_provider_support::Get, + frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{Currency, ExistenceRequirement, WithdrawReasons}, + DebugNoBound, + }, + polkadot_sdk_frame::prelude::BlockNumberFor, + sp_runtime::{BoundedVec, DispatchError, Percent}, sp_tracing::{debug, debug_span}, }; -use scale_info::prelude::vec::Vec; -use scale_info::TypeInfo; +use scale_info::{prelude::vec::Vec, TypeInfo}; + +use crate::AccountIdOf; +/// Agents are one of the primitives in the Torus ecosystem which are bounded +/// to modules in off-chain environment. They can receive weights by the +/// allocators. +/// +/// Agent registration needs approval from a curator. Registration applications +/// are submitter at dao.torus.network. #[derive(DebugNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Agent { + /// The key that bounds the agent to the module pub key: AccountIdOf, pub name: BoundedVec, pub url: BoundedVec, pub metadata: BoundedVec, + /// Penalities acts on agent's incentives and dividends of users who set + /// weights on them. pub weight_penalty_factor: Percent, pub registration_block: BlockNumberFor, pub fees: crate::fee::ValidatorFee, } +/// Register an agent to the given key, payed by the payer key. +/// +/// If the network is full, this function will drop enough agents until there's +/// at least one slot (see [`find_agent_to_prune`]). Fails if no agents were +/// eligible for pruning. +/// +/// Registration fee is stored as [`crate::Burn`]. pub fn register( payer: AccountIdOf, agent_key: AccountIdOf, @@ -121,6 +139,8 @@ pub fn register( Ok(()) } +/// Unregister an agent key from the network, erasing all its data and removing +/// stakers. pub fn unregister(agent_key: AccountIdOf) -> DispatchResult { let span = debug_span!("unregister", agent.key = ?agent_key); let _guard = span.enter(); @@ -138,6 +158,7 @@ pub fn unregister(agent_key: AccountIdOf) -> DispatchResult Ok(()) } +/// Updates the metadata of an existing agent. pub fn update( agent_key: AccountIdOf, name: Vec, @@ -273,7 +294,8 @@ pub enum PruningStrategy { /// Finds the agent producing least dividends and incentives to /// the network that is older than the current immunity period. LeastProductive, - /// Like [`PruningStrategy::LeastProductive`] but ignoring the immunity period. + /// Like [`PruningStrategy::LeastProductive`] but ignoring the immunity + /// period. IgnoreImmunity, } @@ -282,9 +304,9 @@ pub enum PruningStrategy { /// When search for least productive agent, agents that are older than the /// immunity period will be ranked based on their emissions in the last /// consensus run (epoch). Dividends are multiplied by the participation -/// factor defined by the network and and summed with incentives. The to-be-pruned -/// agent is the one with the lowest result, if multiple agents are found, the -/// algorithm chooses the oldest one. +/// factor defined by the network and and summed with incentives. The +/// to-be-pruned agent is the one with the lowest result, if multiple agents are +/// found, the algorithm chooses the oldest one. #[doc(hidden)] pub fn find_agent_to_prune(strategy: PruningStrategy) -> Option { let current_block: u64 = >::block_number() diff --git a/pallets/torus0/src/burn.rs b/pallets/torus0/src/burn.rs index fcd045a..38b571b 100644 --- a/pallets/torus0/src/burn.rs +++ b/pallets/torus0/src/burn.rs @@ -1,12 +1,13 @@ -use crate::BalanceOf; use codec::{Decode, Encode, MaxEncodedLen}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::frame_support::DebugNoBound; -use polkadot_sdk::polkadot_sdk_frame::prelude::BlockNumberFor; -use scale_info::prelude::marker::PhantomData; -use scale_info::TypeInfo; +use polkadot_sdk::{ + frame_election_provider_support::Get, frame_support::DebugNoBound, + polkadot_sdk_frame::prelude::BlockNumberFor, +}; +use scale_info::{prelude::marker::PhantomData, TypeInfo}; use substrate_fixed::types::I110F18; +use crate::BalanceOf; + #[derive(Clone, TypeInfo, Decode, Encode, PartialEq, Eq, DebugNoBound, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct BurnConfiguration { @@ -36,6 +37,11 @@ where } } +/// Adjusts registration burn for the current block. +/// +/// The next burn is calculated by analyzing the last N +/// (`target_registrations_interval`) blocks and increases if the target +/// registrations per interval was reached. pub fn adjust_burn(current_block: u64) { let BurnConfiguration { min_burn, diff --git a/pallets/torus0/src/fee.rs b/pallets/torus0/src/fee.rs index 5110864..4d77ed4 100644 --- a/pallets/torus0/src/fee.rs +++ b/pallets/torus0/src/fee.rs @@ -1,8 +1,9 @@ use core::marker::PhantomData; use codec::{Decode, Encode, MaxEncodedLen}; -use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::{frame_support::DebugNoBound, sp_runtime::Percent}; +use polkadot_sdk::{ + frame_election_provider_support::Get, frame_support::DebugNoBound, sp_runtime::Percent, +}; use scale_info::TypeInfo; #[derive(DebugNoBound, Decode, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo)] diff --git a/pallets/torus0/src/lib.rs b/pallets/torus0/src/lib.rs index e0ec002..d8af9d5 100644 --- a/pallets/torus0/src/lib.rs +++ b/pallets/torus0/src/lib.rs @@ -7,24 +7,23 @@ pub mod fee; pub mod migrations; pub mod stake; -use crate::agent::Agent; -use crate::burn::BurnConfiguration; -use crate::fee::ValidatorFeeConstraints; pub(crate) use ext::*; -use frame::arithmetic::Percent; -use frame::prelude::ensure_signed; +use frame::{arithmetic::Percent, prelude::ensure_signed}; pub use pallet::*; -use polkadot_sdk::frame_support::{ - dispatch::DispatchResult, - pallet_prelude::{ValueQuery, *}, - traits::Currency, - Identity, +use polkadot_sdk::{ + frame_support::{ + dispatch::DispatchResult, + pallet_prelude::{ValueQuery, *}, + traits::Currency, + Identity, + }, + frame_system::pallet_prelude::OriginFor, + polkadot_sdk_frame as frame, sp_std, }; -use polkadot_sdk::frame_system::pallet_prelude::OriginFor; -use polkadot_sdk::polkadot_sdk_frame as frame; -use polkadot_sdk::sp_std; use scale_info::prelude::vec::Vec; +use crate::{agent::Agent, burn::BurnConfiguration, fee::ValidatorFeeConstraints}; + #[frame::pallet] pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); @@ -35,78 +34,106 @@ pub mod pallet { use super::*; + /// Max allowed of validators. This is used then calculating emissions, only + /// the top staked agents up to this value will have their weights + /// considered. #[pallet::storage] pub type MaxAllowedValidators = StorageValue<_, u16, ValueQuery, T::DefaultMaxAllowedValidators>; + /// Amount of tokens to burn from a payer key when registering new agents. #[pallet::storage] pub type Burn = StorageValue<_, BalanceOf, ValueQuery>; + /// Number of agent registrations that happened in the last + /// [`BurnConfiguration::target_registrations_interval`] blocks. #[pallet::storage] pub type RegistrationsThisInterval = StorageValue<_, u16, ValueQuery>; + /// Minimum required stake for an agent to be considered a validator. #[pallet::storage] pub type MinValidatorStake = StorageValue<_, BalanceOf, ValueQuery, T::DefaultMinValidatorStake>; + /// Number of blocks in which an agent is immune to pruning after + /// registration. #[pallet::storage] pub type ImmunityPeriod = StorageValue<_, u16, ValueQuery, T::DefaultImmunityPeriod>; + /// Number of blocks between emissions. #[pallet::storage] pub type RewardInterval = StorageValue<_, u16, ValueQuery, T::DefaultRewardInterval>; + /// Known registered network agents indexed by the owner's key. #[pallet::storage] pub type Agents = StorageMap<_, Identity, AccountIdOf, Agent>; + // TODO: remove #[pallet::storage] pub type RegistrationBlock = StorageMap<_, Identity, AccountIdOf, BlockNumberFor>; + /// Maximum number of characters allowed in an agent name. #[pallet::storage] pub type MaxNameLength = StorageValue<_, u16, ValueQuery, T::DefaultMaxNameLength>; + /// Minimum number of characters required in an agent name. #[pallet::storage] - pub type MaxAgentUrlLength = - StorageValue<_, u16, ValueQuery, T::DefaultMaxAgentUrlLength>; + pub type MinNameLength = StorageValue<_, u16, ValueQuery, T::DefaultMinNameLength>; + /// Maximum number of characters allowed in an agent URL. #[pallet::storage] - pub type MinNameLength = StorageValue<_, u16, ValueQuery, T::DefaultMinNameLength>; + pub type MaxAgentUrlLength = + StorageValue<_, u16, ValueQuery, T::DefaultMaxAgentUrlLength>; + /// Maximum number of agents registered at one time. Registering when this + /// number is met means new comers will cause pruning of old agents. #[pallet::storage] pub type MaxAllowedAgents = StorageValue<_, u16, ValueQuery, T::DefaultMaxAllowedAgents>; + /// Number of agent registrations that happened this block. #[pallet::storage] pub type RegistrationsThisBlock = StorageValue<_, u16, ValueQuery>; + /// Maximum amount of agent registrations per block, tracked by + /// [`RegistrationsThisBlock`]. #[pallet::storage] pub type MaxRegistrationsPerBlock = StorageValue<_, u16, ValueQuery, T::DefaultMaxRegistrationsPerBlock>; - // StakeTo + // Map of staked tokens prefixed by the staker, and indexed by the staked agents + // mapping to the amount in tokens. #[pallet::storage] pub type StakingTo = StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, BalanceOf>; - // StakeFrom + // Map of staked tokens prefixed by the staked agent, and indexed by the staker + // keys mapping to the amount in tokens. #[pallet::storage] pub type StakedBy = StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, BalanceOf>; + /// The total amount of stake in the network. #[pallet::storage] pub type TotalStake = StorageValue<_, BalanceOf, ValueQuery>; + /// Minimum amount of stake in tokens a key has to deposit in an agent. #[pallet::storage] pub type MinAllowedStake = StorageValue<_, BalanceOf, ValueQuery, T::DefaultMinAllowedStake>; + /// The weight dividends have when finding agents to prune. 100% meaning it + /// is taking fully into account. #[pallet::storage] pub type DividendsParticipationWeight = StorageValue<_, Percent, ValueQuery, T::DefaultDividendsParticipationWeight>; + /// Constraints defining validation of agent fees. #[pallet::storage] pub type FeeConstraints = StorageValue<_, ValidatorFeeConstraints, ValueQuery>; + /// [`Burn`] configuration values. #[pallet::storage] pub type BurnConfig = StorageValue<_, BurnConfiguration, ValueQuery>; @@ -189,12 +216,14 @@ pub mod pallet { #[pallet::no_default_bounds] type DefaultMaxRegistrationsPerInterval: Get; - /// The storage MaxNameLength should be constrained to be no more than the value of this. - /// This is needed on agent::Agent to set the `name` field BoundedVec max length. + /// The storage MaxNameLength should be constrained to be no more than + /// the value of this. This is needed on agent::Agent to set the + /// `name` field BoundedVec max length. #[pallet::constant] type MaxAgentNameLengthConstraint: Get; - /// This is needed on agent::Agent to set the `url` field BoundedVec max length. + /// This is needed on agent::Agent to set the `url` field BoundedVec max + /// length. #[pallet::constant] type MaxAgentUrlLengthConstraint: Get; @@ -221,6 +250,7 @@ pub mod pallet { #[pallet::call] impl Pallet { + /// Adds stakes from origin to the agent key. #[pallet::call_index(0)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( @@ -236,6 +266,7 @@ pub mod pallet { stake::add_stake::(key, agent_key, amount) } + /// Removes stakes from origin to the agent key. #[pallet::call_index(1)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake( @@ -247,6 +278,7 @@ pub mod pallet { stake::remove_stake::(key, agent_key, amount) } + /// Transfers origin's stakes from an agent to another. #[pallet::call_index(2)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn transfer_stake( @@ -259,6 +291,7 @@ pub mod pallet { stake::transfer_stake::(key, agent_key, new_agent_key, amount) } + /// Registers a new agent on behalf of an arbitrary key. #[pallet::call_index(3)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn register_agent( @@ -272,6 +305,7 @@ pub mod pallet { agent::register::(payer, agent_key, name, url, metadata) } + /// Unregister origin's key agent. #[pallet::call_index(4)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn unregister_agent(origin: OriginFor) -> DispatchResult { @@ -279,6 +313,7 @@ pub mod pallet { agent::unregister::(agent_key) } + /// Updates origin's key agent metadata. #[pallet::call_index(5)] #[pallet::weight((Weight::zero(), DispatchClass::Normal, Pays::Yes))] pub fn update_agent( @@ -304,17 +339,20 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { - /// Event created when stake has been transferred from the coldkey account onto the key - /// staking account + /// Event created when stake has been transferred from the coldkey + /// account onto the key staking account StakeAdded(AccountIdOf, AccountIdOf, BalanceOf), - /// Event created when stake has been removed from the key staking account onto the coldkey - /// account + /// Event created when stake has been removed from the key staking + /// account onto the coldkey account StakeRemoved(AccountIdOf, AccountIdOf, BalanceOf), - /// Event created when a new agent account has been registered to the chain + /// Event created when a new agent account has been registered to the + /// chain AgentRegistered(AccountIdOf), - /// Event created when a agent account has been deregistered from the chain + /// Event created when a agent account has been deregistered from the + /// chain AgentUnregistered(AccountIdOf), - /// Event created when the agent's updated information is added to the network + /// Event created when the agent's updated information is added to the + /// network AgentUpdated(AccountIdOf), } @@ -324,11 +362,14 @@ pub mod pallet { AgentDoesNotExist, /// Insufficient stake to withdraw the requested amount. NotEnoughStakeToWithdraw, - /// Insufficient balance in the cold key account to stake the requested amount. + /// Insufficient balance in the cold key account to stake the requested + /// amount. NotEnoughBalanceToStake, - /// The number of agent registrations in this block exceeds the allowed limit. + /// The number of agent registrations in this block exceeds the allowed + /// limit. TooManyAgentRegistrationsThisBlock, - /// The number of agent registrations in this interval exceeds the allowed limit. + /// The number of agent registrations in this interval exceeds the + /// allowed limit. TooManyAgentRegistrationsThisInterval, /// The agent is already registered in the active set. AgentAlreadyRegistered, @@ -352,8 +393,8 @@ pub mod pallet { NotEnoughStakeToRegister, /// The entity is still registered and cannot be modified. StillRegistered, - /// Attempted to set max allowed agents to a value less than the current number of - /// registered agents. + /// Attempted to set max allowed agents to a value less than the current + /// number of registered agents. MaxAllowedAgents, /// Insufficient balance to transfer. NotEnoughBalanceToTransfer, @@ -389,7 +430,8 @@ pub mod pallet { StepPanicked, /// The stake amount to add or remove is too small. Minimum is 0.5 unit. StakeTooSmall, - /// Key is not present in Whitelist, it needs to be whitelisted by a Curator + /// Key is not present in Whitelist, it needs to be whitelisted by a + /// Curator AgentKeyNotWhitelisted, /// The amount given is 0 InvalidAmount, diff --git a/pallets/torus0/src/migrations.rs b/pallets/torus0/src/migrations.rs index 92fcb77..7758c96 100644 --- a/pallets/torus0/src/migrations.rs +++ b/pallets/torus0/src/migrations.rs @@ -9,7 +9,6 @@ use crate::{Config, Pallet}; pub mod v1 { use super::*; - use crate::{Agent, Agents}; pub type Migration = VersionedMigration<0, 1, MigrateToV1, Pallet, W>; diff --git a/pallets/torus0/src/stake.rs b/pallets/torus0/src/stake.rs index e5d9568..d1aee31 100644 --- a/pallets/torus0/src/stake.rs +++ b/pallets/torus0/src/stake.rs @@ -1,11 +1,17 @@ -use polkadot_sdk::sp_std::{collections::btree_map::BTreeMap, vec::Vec}; -use polkadot_sdk::sp_tracing::error; - -use crate::agent; -use crate::{AccountIdOf, BalanceOf}; -use polkadot_sdk::frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons}; -use polkadot_sdk::frame_support::{dispatch::DispatchResult, ensure}; - +use polkadot_sdk::{ + frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{Currency, ExistenceRequirement, WithdrawReasons}, + }, + sp_std::{collections::btree_map::BTreeMap, vec::Vec}, + sp_tracing::error, +}; + +use crate::{agent, AccountIdOf, BalanceOf}; + +/// Stakes `amount` tokens from `staker` to `staked` by withdrawing the tokens +/// and adding them to the [`crate::StakingTo`] and [`crate::StakedBy`] maps. pub fn add_stake( staker: AccountIdOf, staked: AccountIdOf, @@ -39,9 +45,10 @@ pub fn add_stake( Ok(()) } +/// Withdraws stake from an agent and gives it back to the staker. pub fn remove_stake( - key: AccountIdOf, - agent_key: AccountIdOf, + staker: AccountIdOf, + staked: AccountIdOf, amount: BalanceOf, ) -> DispatchResult { ensure!( @@ -50,32 +57,34 @@ pub fn remove_stake( ); ensure!( - agent::exists::(&agent_key), + agent::exists::(&staked), crate::Error::::AgentDoesNotExist ); ensure!( - crate::StakingTo::::get(&key, &agent_key).unwrap_or(0) >= amount, + crate::StakingTo::::get(&staker, &staked).unwrap_or(0) >= amount, crate::Error::::NotEnoughStakeToWithdraw ); - crate::StakingTo::::mutate(&key, &agent_key, |stake| { + crate::StakingTo::::mutate(&staker, &staked, |stake| { *stake = Some(stake.unwrap_or(0).saturating_sub(amount)) }); - crate::StakedBy::::mutate(&agent_key, &key, |stake| { + crate::StakedBy::::mutate(&staked, &staker, |stake| { *stake = Some(stake.unwrap_or(0).saturating_sub(amount)) }); crate::TotalStake::::mutate(|total_stake| *total_stake = total_stake.saturating_sub(amount)); - let _ = ::Currency::deposit_creating(&key, amount); + let _ = ::Currency::deposit_creating(&staker, amount); crate::Pallet::::deposit_event(crate::Event::::StakeRemoved(key, agent_key, amount)); Ok(()) } +/// Transfers stake from an account to another (see [`remove_stake`], +/// [`add_stake`]). pub fn transfer_stake( staker: AccountIdOf, old_staked: AccountIdOf, @@ -87,6 +96,8 @@ pub fn transfer_stake( Ok(()) } +/// Usually called when de-registering an agent, removes all stakes on a given +/// key. pub(crate) fn clear_key(key: &AccountIdOf) -> DispatchResult { for (staker, staked, amount) in crate::StakingTo::::iter() { if &staker == key || &staked == key { diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index 6ccddd1..9255057 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -1,5 +1,5 @@ -use crate::*; use alloc::vec; + use frame::traits::UniqueSaturatedInto; use pallet_ethereum::{ Call::transact, Transaction as EthereumTransaction, TransactionAction, TransactionData, @@ -10,12 +10,12 @@ use pallet_grandpa::AuthorityId as GrandpaId; use polkadot_sdk::{ polkadot_sdk_frame::runtime::prelude::*, sp_consensus_aura::sr25519::AuthorityId as AuraId, - sp_core::crypto::KeyTypeId, - sp_core::H160, - sp_core::{H256, U256}, + sp_core::{crypto::KeyTypeId, H160, H256, U256}, sp_runtime::{traits::Block as BlockT, Permill}, }; +use crate::*; + impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { diff --git a/runtime/src/configs.rs b/runtime/src/configs.rs index 7a76398..e8801a3 100644 --- a/runtime/src/configs.rs +++ b/runtime/src/configs.rs @@ -1,4 +1,3 @@ -use crate::*; use frame_support::PalletId; use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment}; use polkadot_sdk::{ @@ -17,6 +16,8 @@ use polkadot_sdk::{ }; use sp_runtime::{Perbill, Percent}; +use crate::*; + pub mod eth; /// The runtime has 18 token decimals @@ -69,8 +70,8 @@ impl frame_system::Config for Runtime { /// Contains version information about the runtime /// Used for runtime upgrades and compatibility type Version = Version; - /// Defines the weight (computational and storage) costs of blocks and extrinsics - /// Including base values and limits + /// Defines the weight (computational and storage) costs of blocks and + /// extrinsics Including base values and limits type BlockWeights = BlockWeights; /// Specifies the maximum size of a block in bytes type BlockLength = BlockLength; @@ -93,7 +94,8 @@ impl pallet_balances::Config for Runtime { type AccountStore = System; /// The overarching event type type RuntimeEvent = RuntimeEvent; - /// The type for recording an account's reason for being unable to withdraw funds + /// The type for recording an account's reason for being unable to withdraw + /// funds type RuntimeHoldReason = RuntimeHoldReason; /// The type for recording an account's freezing reason type RuntimeFreezeReason = (); @@ -124,7 +126,8 @@ impl pallet_balances::Config for Runtime { impl pallet_sudo::Config for Runtime { /// The overarching event type that will be emitted by this pallet type RuntimeEvent = RuntimeEvent; - /// The type that represents all calls that can be dispatched in this runtime + /// The type that represents all calls that can be dispatched in this + /// runtime type RuntimeCall = RuntimeCall; /// Weight information for the extrinsics in this pallet type WeightInfo = pallet_sudo::weights::SubstrateWeight; @@ -163,9 +166,11 @@ impl pallet_multisig::Config for Runtime { // --- Timestamp --- impl pallet_timestamp::Config for Runtime { - /// The type used to store timestamps. In this case, it's an unsigned 64-bit integer. + /// The type used to store timestamps. In this case, it's an unsigned 64-bit + /// integer. type Moment = u64; - /// A hook that is called after the timestamp is set. In this case, it's empty (unit type). + /// A hook that is called after the timestamp is set. In this case, it's + /// empty (unit type). type OnTimestampSet = (); /// The minimum period between blocks. Set to 4000 (8000/2) milliseconds. /// This helps prevent timestamp manipulation by validators. @@ -190,7 +195,8 @@ parameter_types! { /// Converts transaction length to fee using a polynomial formula pub struct LengthToFee; -/// Fee adjustment mechanism that slowly adjusts transaction fees based on block fullness +/// Fee adjustment mechanism that slowly adjusts transaction fees based on block +/// fullness pub type SlowAdjustingFeeUpdate = TargetedFeeAdjustment< R, TargetBlockFullness, @@ -202,7 +208,8 @@ pub type SlowAdjustingFeeUpdate = TargetedFeeAdjustment< impl WeightToFeePolynomial for LengthToFee { type Balance = Balance; - /// Returns coefficients for a polynomial that converts transaction length to fee + /// Returns coefficients for a polynomial that converts transaction length + /// to fee fn polynomial() -> WeightToFeeCoefficients { sp_std::vec![ WeightToFeeCoefficient { diff --git a/runtime/src/configs/eth.rs b/runtime/src/configs/eth.rs index ddfa7a3..3517594 100644 --- a/runtime/src/configs/eth.rs +++ b/runtime/src/configs/eth.rs @@ -1,7 +1,3 @@ -use crate::{ - configs::{currency, WEIGHT_REF_TIME_PER_SECOND}, - TransactionPayment, -}; use codec::{Decode, Encode}; use pallet_ethereum::PostLogContent; use pallet_evm::{FeeCalculator, HashedAddressMapping}; @@ -21,6 +17,10 @@ use super::{ precompiles::FrontierPrecompiles, Aura, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp, UncheckedExtrinsic, NORMAL_DISPATCH_RATIO, }; +use crate::{ + configs::{currency, WEIGHT_REF_TIME_PER_SECOND}, + TransactionPayment, +}; impl pallet_evm_chain_id::Config for Runtime {} @@ -42,11 +42,13 @@ impl> FindAuthor for FindAuthorTruncated { /// Current approximation of the gas/s consumption considering /// EVM execution over compiled WASM (on 4.4Ghz CPU). /// Given the 2000ms Weight, from which 75% only are used for transactions, -/// the total EVM execution gas limit is: GAS_PER_SECOND * 2 * 0.75 ~= 60_000_000. +/// the total EVM execution gas limit is: GAS_PER_SECOND * 2 * 0.75 ~= +/// 60_000_000. pub const GAS_PER_SECOND: u64 = 40_000_000; /// Approximate ratio of the amount of Weight per Gas. -/// u64 works for approximations because Weight is a very small unit compared to gas. +/// u64 works for approximations because Weight is a very small unit compared to +/// gas. pub const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; @@ -71,19 +73,22 @@ parameter_types! { pub struct TransactionPaymentAsGasPrice; impl FeeCalculator for TransactionPaymentAsGasPrice { fn min_gas_price() -> (U256, Weight) { - // note: transaction-payment differs from EIP-1559 in that its tip and length fees are not - // scaled by the multiplier, which means its multiplier will be overstated when - // applied to an ethereum transaction - // note: transaction-payment uses both a congestion modifier (next_fee_multiplier, which is - // updated once per block in on_finalize) and a 'WeightToFee' implementation. Our - // runtime implements this as a 'ConstantModifier', so we can get away with a simple + // note: transaction-payment differs from EIP-1559 in that its tip and length + // fees are not scaled by the multiplier, which means its + // multiplier will be overstated when applied to an ethereum + // transaction note: transaction-payment uses both a congestion modifier + // (next_fee_multiplier, which is updated once per block in + // on_finalize) and a 'WeightToFee' implementation. Our runtime + // implements this as a 'ConstantModifier', so we can get away with a simple // multiplication here. - // It is imperative that `saturating_mul_int` be performed as late as possible in the - // expression since it involves fixed point multiplication with a division by a fixed - // divisor. This leads to truncation and subsequent precision loss if performed too early. - // This can lead to min_gas_price being same across blocks even if the multiplier changes. - // There's still some precision loss when the final `gas_price` (used_gas * min_gas_price) - // is computed in frontier, but that's currently unavoidable. + // It is imperative that `saturating_mul_int` be performed as late as possible + // in the expression since it involves fixed point multiplication with a + // division by a fixed divisor. This leads to truncation and subsequent + // precision loss if performed too early. This can lead to min_gas_price + // being same across blocks even if the multiplier changes. + // There's still some precision loss when the final `gas_price` (used_gas * + // min_gas_price) is computed in frontier, but that's currently + // unavoidable. let min_gas_price = TransactionPayment::next_fee_multiplier() .saturating_mul_int((currency::WEIGHT_FEE).saturating_mul(WEIGHT_PER_GAS as u128)); ( diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f2a9f16..8959f41 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -5,12 +5,8 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; use alloc::vec::Vec; -use interface::*; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; -use weights::constants::RocksDbWeight; +use interface::*; use polkadot_sdk::{ frame_executive, frame_support, frame_system, polkadot_sdk_frame::{self as frame, prelude::*, runtime::prelude::*}, @@ -19,6 +15,10 @@ use polkadot_sdk::{ sp_runtime::impl_opaque_keys, *, }; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; +use weights::constants::RocksDbWeight; pub mod apis; #[cfg(feature = "runtime-benchmarks")] @@ -46,7 +46,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { state_version: 1, }; -/// The version information used to identify this runtime when compiled natively. +/// The version information used to identify this runtime when compiled +/// natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { NativeVersion { @@ -101,7 +102,8 @@ pub type RuntimeExecutive = frame_executive::Executive< Migrations, >; -// Composes the runtime by adding all the used pallets and deriving necessary types. +// Composes the runtime by adding all the used pallets and deriving necessary +// types. #[frame_construct_runtime] mod runtime { #[runtime::runtime] @@ -162,25 +164,25 @@ parameter_types! { pub const Version: RuntimeVersion = VERSION; } -/// Some re-exports that the node side code needs to know. Some are useful in this context as well. +/// Some re-exports that the node side code needs to know. Some are useful in +/// this context as well. /// /// Other types should preferably be private. // TODO: this should be standardized in some way, see: // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 pub mod interface { - use crate::RuntimeCall; - - use super::Runtime; use polkadot_sdk::{ frame_system, pallet_balances, sp_core::H160, sp_runtime::{self, generic, traits::BlakeTwo256, MultiSignature}, }; - pub use polkadot_sdk::{ frame_system::Call as SystemCall, pallet_balances::Call as BalancesCall, }; + use super::Runtime; + use crate::RuntimeCall; + pub type BlockNumber = u64; pub type Signature = MultiSignature; @@ -202,18 +204,18 @@ pub mod interface { pub type MinimumBalance = ::ExistentialDeposit; } -// Opaque types. These are used by the CLI to instantiate machinery that don't need to know -// the specifics of the runtime. They can then be made to be agnostic over specific formats -// of data like extrinsics, allowing for them to continue syncing the network through upgrades -// to even the core data structures. +// Opaque types. These are used by the CLI to instantiate machinery that don't +// need to know the specifics of the runtime. They can then be made to be +// agnostic over specific formats of data like extrinsics, allowing for them to +// continue syncing the network through upgrades to even the core data +// structures. pub mod opaque { - use super::*; - use frame::traits::BlakeTwo256; use sp_runtime::generic; - pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + use super::*; + /// Opaque block header type. pub type Header = generic::Header; /// Opaque block type. diff --git a/runtime/src/precompiles/balance_transfer.rs b/runtime/src/precompiles/balance_transfer.rs index 0882e40..e0aca36 100644 --- a/runtime/src/precompiles/balance_transfer.rs +++ b/runtime/src/precompiles/balance_transfer.rs @@ -9,8 +9,10 @@ use polkadot_sdk::{ }; use sp_std::vec; -use crate::precompiles::{bytes_to_account_id, get_method_id, get_slice}; -use crate::{Runtime, RuntimeCall}; +use crate::{ + precompiles::{bytes_to_account_id, get_method_id, get_slice}, + Runtime, RuntimeCall, +}; pub const BALANCE_TRANSFER_INDEX: u64 = 2048; diff --git a/runtime/src/precompiles/mod.rs b/runtime/src/precompiles/mod.rs index b745e2b..a00d3dd 100644 --- a/runtime/src/precompiles/mod.rs +++ b/runtime/src/precompiles/mod.rs @@ -2,14 +2,13 @@ use pallet_evm::{ ExitError, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, }; +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; use polkadot_sdk::{ sp_core::{hashing::keccak_256, H160}, sp_runtime::AccountId32, }; - -use pallet_evm_precompile_modexp::Modexp; -use pallet_evm_precompile_sha3fips::Sha3FIPS256; -use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; mod balance_transfer; use balance_transfer::*; diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index f259ba0..e55d339 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -2,6 +2,9 @@ use std::{cell::RefCell, num::NonZeroU128}; +pub use pallet_emission0; +pub use pallet_governance; +pub use pallet_torus0; use pallet_torus0::MinAllowedStake; use polkadot_sdk::{ frame_support::{ @@ -22,8 +25,6 @@ use polkadot_sdk::{ sp_tracing, }; -pub use {pallet_emission0, pallet_governance, pallet_torus0}; - #[frame_construct_runtime] mod runtime { #[runtime::runtime] diff --git a/xtask/src/build_spec.rs b/xtask/src/build_spec.rs index d4f0aee..981ed51 100644 --- a/xtask/src/build_spec.rs +++ b/xtask/src/build_spec.rs @@ -202,38 +202,42 @@ pub type RefCount = u32; pub struct AccountInfo { /// The number of transactions this account has sent. pub nonce: Nonce, - /// The number of other modules that currently depend on this account's existence. The account - /// cannot be reaped until this is zero. + /// The number of other modules that currently depend on this account's + /// existence. The account cannot be reaped until this is zero. pub consumers: RefCount, - /// The number of other modules that allow this account to exist. The account may not be reaped - /// until this and `sufficients` are both zero. + /// The number of other modules that allow this account to exist. The + /// account may not be reaped until this and `sufficients` are both + /// zero. pub providers: RefCount, - /// The number of modules that allow this account to exist for their own purposes only. The - /// account may not be reaped until this and `providers` are both zero. + /// The number of modules that allow this account to exist for their own + /// purposes only. The account may not be reaped until this and + /// `providers` are both zero. pub sufficients: RefCount, - /// The additional data that belongs to this account. Used to store the balance(s) in a lot of - /// chains. + /// The additional data that belongs to this account. Used to store the + /// balance(s) in a lot of chains. pub data: AccountData, } /// All balance information for an account. #[derive(Debug, parity_scale_codec::Decode, parity_scale_codec::Encode)] pub struct AccountData { - /// Non-reserved part of the balance which the account holder may be able to control. + /// Non-reserved part of the balance which the account holder may be able to + /// control. /// - /// This is the only balance that matters in terms of most operations on tokens. + /// This is the only balance that matters in terms of most operations on + /// tokens. pub free: Balance, /// Balance which is has active holds on it and may not be used at all. /// - /// This is the sum of all individual holds together with any sums still under the (deprecated) - /// reserves API. + /// This is the sum of all individual holds together with any sums still + /// under the (deprecated) reserves API. pub reserved: Balance, - /// The amount that `free + reserved` may not drop below when reducing the balance, except for - /// actions where the account owner cannot reasonably benefit from the balance reduction, such - /// as slashing. + /// The amount that `free + reserved` may not drop below when reducing the + /// balance, except for actions where the account owner cannot + /// reasonably benefit from the balance reduction, such as slashing. pub frozen: Balance, - /// Extra information about this account. The MSB is a flag indicating whether the new ref- - /// counting logic is in place for this account. + /// Extra information about this account. The MSB is a flag indicating + /// whether the new ref- counting logic is in place for this account. pub flags: ExtraFlags, } diff --git a/xtask/src/run.rs b/xtask/src/run.rs index 0363425..48a6f86 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -82,13 +82,14 @@ pub(super) fn run(mut r: flags::Run) { #[allow(dead_code)] pub mod ops { - use super::*; use std::{ ffi::OsStr, io::Write, process::{Command, Stdio}, }; + use super::*; + #[macro_export] macro_rules! torus_node { ($($arg:expr),*) => {{