From db3e2247087aadde535b3929a362be8a78ad605f Mon Sep 17 00:00:00 2001 From: devwckd Date: Mon, 16 Dec 2024 16:46:13 -0300 Subject: [PATCH 1/8] feat: impl governance pallet --- Cargo.lock | 22 ++ Cargo.toml | 3 + pallets/governance/Cargo.toml | 10 +- pallets/governance/src/application.rs | 101 +++++- pallets/governance/src/config.rs | 2 + pallets/governance/src/curator.rs | 56 ++- pallets/governance/src/ext.rs | 39 +- pallets/governance/src/lib.rs | 199 ++++++++-- pallets/governance/src/proposal.rs | 503 +++++++++++++++++++++++++- pallets/governance/src/voting.rs | 91 ++++- pallets/governance/src/whitelist.rs | 34 +- pallets/torus0/src/fee.rs | 8 +- pallets/torus0/src/lib.rs | 2 +- pallets/torus0/src/stake.rs | 19 + 14 files changed, 980 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e2bcf8..c2b02bf 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -8364,6 +8364,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk", "scale-info", + "substrate-fixed", ] [[package]] @@ -16292,6 +16293,17 @@ name = "substrate-build-script-utils" version = "11.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?branch=stable2409#981d6c0fa87a00b72bb3b6211d1e71deed21f0cc" +[[package]] +name = "substrate-fixed" +version = "0.5.9" +source = "git+https://github.com/encointer/substrate-fixed#ddaa922892d1565f02c9c5702f0aacd17da53ce2" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "substrate-typenum", +] + [[package]] name = "substrate-frame-rpc-support" version = "40.0.0" @@ -16369,6 +16381,16 @@ dependencies = [ "trie-db", ] +[[package]] +name = "substrate-typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f0091e93c2c75b233ae39424c52cb8a662c0811fb68add149e20e5d7e8a788" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + [[package]] name = "substrate-wasm-builder" version = "24.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0d2c227..1c17589 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ test-utils.path = "./test-utils" # Substrate codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false } scale-info = { version = "2.11.1", default-features = false } + polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "stable2409", default-features = false } # Frontier @@ -43,6 +44,8 @@ pallet-evm-precompile-modexp = { git = "https://github.com/paritytech/frontier.g pallet-evm-precompile-sha3fips = { git = "https://github.com/paritytech/frontier.git", rev = "b9b1c620c8b418bdeeadc79725f9cfa4703c0333", default-features = false } pallet-evm-precompile-simple = { git = "https://github.com/paritytech/frontier.git", rev = "b9b1c620c8b418bdeeadc79725f9cfa4703c0333", default-features = false } +substrate-fixed = { git = "https://github.com/encointer/substrate-fixed", default-features = false } + # CLI-specific dependencies clap = { version = "4.5.22", features = ["derive"] } serde_json = { version = "1.0", default-features = false } diff --git a/pallets/governance/Cargo.toml b/pallets/governance/Cargo.toml index d868322..238a132 100644 --- a/pallets/governance/Cargo.toml +++ b/pallets/governance/Cargo.toml @@ -8,12 +8,18 @@ edition.workspace = true [features] default = ["std"] -std = ["codec/std", "polkadot-sdk/std", "scale-info/std"] +std = ["codec/std", "polkadot-sdk/std", "scale-info/std", "substrate-fixed/std"] runtime-benchmarks = ["polkadot-sdk/runtime-benchmarks"] try-runtime = ["polkadot-sdk/try-runtime"] [dependencies] codec = { workspace = true, features = ["derive"] } pallet-torus0 = { workspace = true } -polkadot-sdk = { workspace = true, features = ["experimental", "runtime"] } +polkadot-sdk = { workspace = true, features = [ + "experimental", + "runtime", + "pallet-sudo", + "sc-telemetry", +] } scale-info = { workspace = true, features = ["derive"] } +substrate-fixed = { workspace = true } diff --git a/pallets/governance/src/application.rs b/pallets/governance/src/application.rs index 630df6e..025845d 100644 --- a/pallets/governance/src/application.rs +++ b/pallets/governance/src/application.rs @@ -1,43 +1,106 @@ +use crate::{remove_balance, whitelist, AccountIdOf, 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::DebugNoBound; -use polkadot_sdk::polkadot_sdk_frame::prelude::OriginFor; -use polkadot_sdk::sp_core::ConstU32; use polkadot_sdk::sp_runtime::BoundedVec; use polkadot_sdk::sp_std::vec::Vec; use scale_info::TypeInfo; -use crate::{AccountIdOf, BalanceOf, Block}; - #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct AgentApplication { pub id: u32, pub payer_key: AccountIdOf, pub agent_key: AccountIdOf, - pub data: BoundedVec>, + pub data: BoundedVec, pub cost: BalanceOf, pub expires_at: Block, } pub fn submit_application( - _origin: OriginFor, - _agent_key: AccountIdOf, - _data: Vec, + payer: AccountIdOf, + agent_key: AccountIdOf, + data: Vec, ) -> DispatchResult { - todo!() + if whitelist::is_whitelisted::(&agent_key) { + return Err(crate::Error::::AlreadyWhitelisted.into()); + } + + let config = crate::GlobalGovernanceConfig::::get(); + let cost = config.agent_application_cost; + + remove_balance::(&payer, cost)?; + + let data_len: u32 = data + .len() + .try_into() + .map_err(|_| crate::Error::::InvalidApplicationDataLength)?; + + if data_len < T::MinApplicationDataLength::get() + || data_len > T::MaxApplicationDataLength::get() + { + return Err(crate::Error::::InvalidApplicationDataLength)?; + } + + let current_block: u64 = + TryInto::try_into(>::block_number()) + .ok() + .expect("blockchain will not exceed 2^64 blocks; QED."); + + let expires_at = current_block + config.agent_application_expiration; + + let application_id: u32 = crate::AgentApplicationId::::mutate(|id| { + let last_id = *id; + *id = id.saturating_add(1); + last_id + }); + + let application = AgentApplication:: { + id: application_id, + payer_key: payer, + agent_key, + data: BoundedVec::truncate_from(data), + cost, + expires_at, + }; + + crate::AgentApplications::::insert(application_id, application); + crate::Pallet::::deposit_event(crate::Event::::ApplicationCreated(application_id)); + + Ok(()) } -pub fn accept_application( - _origin: OriginFor, - _application_id: u32, -) -> DispatchResult { - todo!() +pub fn accept_application(application_id: u32) -> DispatchResult { + let application = crate::AgentApplications::::get(application_id) + .ok_or(crate::Error::::ApplicationNotFound)?; + + crate::AgentApplications::::remove(application.id); + + whitelist::add_to_whitelist::(application.agent_key.clone())?; + crate::Pallet::::deposit_event(crate::Event::::ApplicationAccepted(application.id)); + crate::Pallet::::deposit_event(crate::Event::::WhitelistAdded(application.agent_key)); + + Ok(()) } -pub fn deny_application( - _origin: OriginFor, - _application_id: u32, -) -> DispatchResult { - todo!() +pub fn deny_application(application_id: u32) -> DispatchResult { + let application = crate::AgentApplications::::get(application_id) + .ok_or(crate::Error::::ApplicationNotFound)?; + + crate::AgentApplications::::remove(application.id); + crate::Pallet::::deposit_event(crate::Event::::ApplicationDenied(application.id)); + + Ok(()) +} + +pub(crate) fn remove_expired_applications(current_block: Block) { + for application in crate::AgentApplications::::iter_values() { + if current_block < application.expires_at { + continue; + } + + crate::AgentApplications::::remove(application.id); + crate::Pallet::::deposit_event(crate::Event::::ApplicationExpired(application.id)); + } } diff --git a/pallets/governance/src/config.rs b/pallets/governance/src/config.rs index 8687fcf..919c264 100644 --- a/pallets/governance/src/config.rs +++ b/pallets/governance/src/config.rs @@ -10,6 +10,7 @@ pub struct GovernanceConfiguration { pub proposal_cost: BalanceOf, pub proposal_expiration: BlockAmount, pub agent_application_cost: BalanceOf, + pub agent_application_expiration: BlockAmount, pub proposal_reward_treasury_allocation: Percent, pub max_proposal_reward_treasury_allocation: BalanceOf, pub proposal_reward_interval: BlockAmount, @@ -21,6 +22,7 @@ impl Default for GovernanceConfiguration { proposal_cost: 10_000_000_000_000, proposal_expiration: 130_000, agent_application_cost: 1_000_000_000_000, + agent_application_expiration: 2_000, proposal_reward_treasury_allocation: Percent::from_percent(2), max_proposal_reward_treasury_allocation: 10_000_000_000_000, proposal_reward_interval: 75_600, diff --git a/pallets/governance/src/curator.rs b/pallets/governance/src/curator.rs index d93e88a..fa31f15 100644 --- a/pallets/governance/src/curator.rs +++ b/pallets/governance/src/curator.rs @@ -1,19 +1,53 @@ +use crate::AccountIdOf; +use polkadot_sdk::frame_election_provider_support::Get; +use polkadot_sdk::sp_runtime::Percent; use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, + frame_support::dispatch::DispatchResult, frame_system::ensure_signed, + polkadot_sdk_frame::prelude::OriginFor, }; -use crate::AccountIdOf; +pub fn add_curator(key: AccountIdOf) -> DispatchResult { + if crate::Curators::::contains_key(&key) { + return Err(crate::Error::::AlreadyCurator.into()); + } -pub fn add_curator( - _origin: OriginFor, - _key: AccountIdOf, -) -> DispatchResult { - todo!() + crate::Curators::::insert(key, ()); + Ok(()) } -pub fn remove_curator( - _origin: OriginFor, - _key: AccountIdOf, +pub fn remove_curator(key: AccountIdOf) -> DispatchResult { + if !crate::Curators::::contains_key(&key) { + return Err(crate::Error::::NotCurator.into()); + } + + crate::Curators::::remove(&key); + Ok(()) +} + +pub fn penalize_agent( + agent_key: AccountIdOf, + percentage: u8, ) -> DispatchResult { - todo!() + if percentage > T::MaxPenaltyPercentage::get() { + return Err(crate::Error::::InvalidPenaltyPercentage.into()); + } + + let Some(mut agent) = pallet_torus0::Agents::::get(&agent_key) else { + return Err(crate::Error::::AgentNotFound.into()); + }; + + agent.weight_factor = Percent::from_percent(100u8.saturating_sub(percentage)); + + pallet_torus0::Agents::::insert(agent_key, agent); + + Ok(()) +} + +pub fn ensure_curator(origin: OriginFor) -> DispatchResult { + let key: AccountIdOf = ensure_signed(origin)?; + if !crate::Curators::::contains_key(key) { + return Err(crate::Error::::NotCurator.into()); + } + + Ok(()) } diff --git a/pallets/governance/src/ext.rs b/pallets/governance/src/ext.rs index 539a4dd..8f20a71 100644 --- a/pallets/governance/src/ext.rs +++ b/pallets/governance/src/ext.rs @@ -1,4 +1,7 @@ -use polkadot_sdk::frame_support::traits::Currency; +use polkadot_sdk::frame_support::{ + dispatch::DispatchResult, + traits::{Currency, ExistenceRequirement, WithdrawReasons}, +}; pub(super) type BalanceOf = <::Currency as Currency< ::AccountId, @@ -9,3 +12,37 @@ pub(super) type AccountIdOf = ::Acco pub(super) type Block = u64; pub(super) type BlockAmount = u64; + +pub(super) fn get_balance(key: &AccountIdOf) -> BalanceOf { + ::Currency::free_balance(key) +} + +pub(super) fn remove_balance( + key: &AccountIdOf, + amount: BalanceOf, +) -> DispatchResult { + let _ = ::Currency::withdraw( + key, + amount, + WithdrawReasons::except(WithdrawReasons::TIP), + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::NotEnoughBalanceToPropose)?; // TODO: change error + Ok(()) +} + +pub(super) fn add_balance(key: &AccountIdOf, amount: BalanceOf) { + let _ = ::Currency::deposit_creating(&key, amount); +} + +pub(super) fn transfer_balance( + from: &AccountIdOf, + to: &AccountIdOf, + amount: BalanceOf, +) -> DispatchResult { + // TODO: change error + ::Currency::transfer(from, to, amount, ExistenceRequirement::KeepAlive) + .map_err(|_| crate::Error::::InternalError)?; // TODO: change error + + Ok(()) +} diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index 30444d5..af7ea48 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -10,13 +10,21 @@ mod whitelist; use crate::application::AgentApplication; use crate::config::GovernanceConfiguration; -use crate::proposal::{Proposal, ProposalId, UnrewardedProposal}; +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::Identity; -use polkadot_sdk::frame_support::{pallet_prelude::*, PalletId}; -use polkadot_sdk::polkadot_sdk_frame::{self as frame, prelude::OriginFor, traits::Currency}; -use polkadot_sdk::sp_runtime::traits::AccountIdConversion; +use polkadot_sdk::frame_support::{ + dispatch::DispatchResult, + pallet_prelude::{ValueQuery, *}, + traits::Currency, + Identity, PalletId, +}; +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(dev_mode)] @@ -50,8 +58,10 @@ pub mod pallet { StorageValue<_, AccountIdOf, ValueQuery, DefaultDaoTreasuryAddress>; #[pallet::storage] - pub type AgentApplications = - StorageMap<_, Identity, BalanceOf, AgentApplication>; + pub type AgentApplications = StorageMap<_, Identity, u32, AgentApplication>; + + #[pallet::storage] + pub type AgentApplicationId = StorageValue<_, u32, ValueQuery>; #[pallet::storage] pub type Whitelist = StorageMap<_, Identity, AccountIdOf, ()>; @@ -59,23 +69,56 @@ pub mod pallet { #[pallet::storage] pub type Curators = StorageMap<_, Identity, AccountIdOf, ()>; - #[pallet::config] - pub trait Config: polkadot_sdk::frame_system::Config { + #[pallet::config(with_default)] + pub trait Config: polkadot_sdk::frame_system::Config + pallet_torus0::Config { #[pallet::constant] type PalletId: Get; + #[pallet::constant] + type MinApplicationDataLength: Get; + + #[pallet::constant] + type MaxApplicationDataLength: Get; + + #[pallet::constant] + type ApplicationExpiration: Get; + + #[pallet::constant] + type MaxPenaltyPercentage: Get; + + #[pallet::no_default_bounds] + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + type Currency: Currency + Send + Sync; } #[pallet::pallet] pub struct Pallet(_); + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(block_number: BlockNumberFor) -> Weight { + let current_block: u64 = block_number + .try_into() + .ok() + .expect("blockchain won't pass 2 ^ 64 blocks"); + + application::remove_expired_applications::(current_block); + proposal::tick_proposals::(current_block); + proposal::tick_proposal_rewards::(current_block); + + Weight::zero() + } + } + #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(0)] pub fn add_curator_extrinsic(origin: OriginFor, key: AccountIdOf) -> DispatchResult { - curator::add_curator::(origin, key) + ensure_root(origin)?; + curator::add_curator::(key) } #[pallet::call_index(1)] @@ -84,7 +127,8 @@ pub mod pallet { origin: OriginFor, key: AccountIdOf, ) -> DispatchResult { - curator::remove_curator::(origin, key) + ensure_root(origin)?; + curator::remove_curator::(key) } #[pallet::call_index(2)] @@ -93,7 +137,8 @@ pub mod pallet { origin: OriginFor, key: AccountIdOf, ) -> DispatchResult { - whitelist::add_to_whitelist::(origin, key) + curator::ensure_curator::(origin)?; + whitelist::add_to_whitelist::(key) } #[pallet::call_index(3)] @@ -102,89 +147,169 @@ pub mod pallet { origin: OriginFor, key: AccountIdOf, ) -> DispatchResult { - whitelist::remove_from_whitelist::(origin, key) + curator::ensure_curator::(origin)?; + whitelist::remove_from_whitelist::(key) } #[pallet::call_index(4)] #[pallet::weight(0)] - pub fn submit_application_extrinsic( + pub fn accept_application_extrinsic( origin: OriginFor, - agent_key: AccountIdOf, - data: Vec, + application_id: u32, ) -> DispatchResult { - application::submit_application::(origin, agent_key, data) + curator::ensure_curator::(origin)?; + application::accept_application::(application_id) } #[pallet::call_index(5)] #[pallet::weight(0)] - pub fn accept_application_extrinsic( + pub fn deny_application_extrinsic( origin: OriginFor, application_id: u32, ) -> DispatchResult { - application::accept_application::(origin, application_id) + curator::ensure_curator::(origin)?; + application::deny_application::(application_id) } #[pallet::call_index(6)] #[pallet::weight(0)] - pub fn deny_application_extrinsic( + pub fn penalize_agent_extrinsic( origin: OriginFor, - application_id: u32, + agent_key: AccountIdOf, + percentage: u8, ) -> DispatchResult { - application::deny_application::(origin, application_id) + curator::ensure_curator::(origin)?; + curator::penalize_agent::(agent_key, percentage) } #[pallet::call_index(7)] #[pallet::weight(0)] - pub fn add_global_custom_proposal_extrinsic( + pub fn submit_application_extrinsic( origin: OriginFor, + agent_key: AccountIdOf, data: Vec, ) -> DispatchResult { - proposal::add_global_custom_proposal::(origin, data) + let payer = ensure_signed(origin)?; + application::submit_application::(payer, agent_key, data) } #[pallet::call_index(8)] #[pallet::weight(0)] + pub fn add_global_params_proposal_extrinsic( + origin: OriginFor, + min_name_length: u16, + max_name_length: u16, + max_allowed_agents: u16, + max_allowed_weights: u16, + min_weight_stake: BalanceOf, + min_weight_control_fee: u8, + min_staking_fee: u8, + data: Vec, + ) -> DispatchResult { + let proposer = ensure_signed(origin)?; + proposal::add_global_params_proposal::( + proposer, + min_name_length, + max_name_length, + max_allowed_agents, + max_allowed_weights, + min_weight_stake, + min_weight_control_fee, + min_staking_fee, + data, + ) + } + + #[pallet::call_index(9)] + #[pallet::weight(0)] + pub fn add_global_custom_proposal_extrinsic( + origin: OriginFor, + data: Vec, + ) -> DispatchResult { + let proposer = ensure_signed(origin)?; + proposal::add_global_custom_proposal::(proposer, data) + } + + #[pallet::call_index(10)] + #[pallet::weight(0)] pub fn add_dao_treasury_transfer_proposal_extrinsic( origin: OriginFor, value: BalanceOf, destination_key: AccountIdOf, data: Vec, ) -> DispatchResult { - proposal::add_dao_treasury_transfer_proposal::(origin, value, destination_key, data) + let proposer = ensure_signed(origin)?; + proposal::add_dao_treasury_transfer_proposal::( + proposer, + value, + destination_key, + data, + ) } - #[pallet::call_index(9)] + #[pallet::call_index(11)] #[pallet::weight(0)] pub fn vote_proposal_extrinsic( origin: OriginFor, proposal_id: u64, agree: bool, ) -> DispatchResult { - voting::add_vote::(origin, proposal_id, agree) + let voter = ensure_signed(origin)?; + voting::add_vote::(voter, proposal_id, agree) } - #[pallet::call_index(10)] + #[pallet::call_index(12)] #[pallet::weight(0)] pub fn remove_vote_proposal_extrinsic( origin: OriginFor, proposal_id: u64, ) -> DispatchResult { - voting::remove_vote::(origin, proposal_id) + let voter = ensure_signed(origin)?; + voting::remove_vote::(voter, proposal_id) } - #[pallet::call_index(11)] + #[pallet::call_index(13)] #[pallet::weight(0)] pub fn enable_vote_delegation_extrinsic(origin: OriginFor) -> DispatchResult { voting::enable_delegation::(origin) } - #[pallet::call_index(12)] + #[pallet::call_index(14)] #[pallet::weight(0)] pub fn disable_vote_delegation_extrinsic(origin: OriginFor) -> DispatchResult { voting::disable_delegation::(origin) } } + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A new proposal has been created. + ProposalCreated(ProposalId), + /// A proposal has been accepted. + ProposalAccepted(ProposalId), + /// A proposal has been refused. + ProposalRefused(ProposalId), + /// A proposal has expired. + ProposalExpired(ProposalId), + /// A vote has been cast on a proposal. + ProposalVoted(u64, T::AccountId, bool), + /// A vote has been unregistered from a proposal. + ProposalVoteUnregistered(u64, T::AccountId), + /// An agent account has been added to the whitelist. + WhitelistAdded(T::AccountId), + /// An agent account has been removed from the whitelist. + WhitelistRemoved(T::AccountId), + /// A new application has been created. + ApplicationCreated(u32), + /// An application has been accepted. + ApplicationAccepted(u32), + /// An application has been denied. + ApplicationDenied(u32), + /// An application has expired. + ApplicationExpired(u32), + } + #[pallet::error] pub enum Error { /// The proposal is already finished. Do not retry. @@ -224,8 +349,6 @@ pub mod pallet { /// The voter is delegating its voting power to their staked modules. Disable voting power /// delegation. VoterIsDelegatingVotingPower, - /// The network vote mode must be authority for changes to be imposed. - VoteModeIsNotAuthority, /// An internal error occurred, probably relating to the size of the bounded sets. InternalError, /// The application data is too small or empty. @@ -250,5 +373,15 @@ pub mod pallet { NotWhitelisted, /// Failed to convert the given value to a balance. CouldNotConvertToBalance, + /// The application data provided does not meet the length requirement + InvalidApplicationDataLength, + /// The penalty percentage provided does not meet the maximum requirement + InvalidAgentPenaltyPercentage, + /// The key is already a curator. + AlreadyCurator, + /// Agent not found + AgentNotFound, + /// Invalid agent penalty percentage + InvalidPenaltyPercentage, } } diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index e0cd59a..3095cdb 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -1,15 +1,23 @@ +use crate::add_balance; +use crate::BoundedBTreeSet; +use crate::BoundedVec; +use crate::DebugNoBound; +use crate::TypeInfo; +use crate::{ + get_balance, transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, Event, + GlobalGovernanceConfig, Proposals, UnrewardedProposals, +}; +use crate::{GovernanceConfiguration, NotDelegatingVotingPower}; use codec::{Decode, Encode, MaxEncodedLen}; +use polkadot_sdk::sp_runtime::SaturatedConversion; +use polkadot_sdk::sp_std::{collections::btree_set::BTreeSet, vec::Vec}; use polkadot_sdk::{ - frame_support::{dispatch::DispatchResult, DebugNoBound}, - polkadot_sdk_frame::prelude::OriginFor, + frame_support::{dispatch::DispatchResult, ensure, storage::with_storage_layer}, + sc_telemetry::log, sp_core::ConstU32, - sp_runtime::{BoundedBTreeMap, BoundedBTreeSet, BoundedVec}, - sp_std::vec::Vec, + sp_runtime::{BoundedBTreeMap, DispatchError, Percent}, }; -use scale_info::TypeInfo; - -use crate::{AccountIdOf, BalanceOf, Block}; - +use substrate_fixed::types::I92F36; pub type ProposalId = u64; #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)] @@ -25,6 +33,108 @@ pub struct Proposal { pub creation_block: Block, } +impl Proposal { + /// Whether the proposal is still active. + #[must_use] + pub fn is_active(&self) -> bool { + matches!(self.status, ProposalStatus::Open { .. }) + } + + /// Marks a proposal as accepted and overrides the storage value. + pub fn accept( + mut self, + block: Block, + stake_for: BalanceOf, + stake_against: BalanceOf, + ) -> DispatchResult { + ensure!(self.is_active(), crate::Error::::ProposalIsFinished); + + self.status = ProposalStatus::Accepted { + block, + stake_for, + stake_against, + }; + + Proposals::::insert(self.id, &self); + crate::Pallet::::deposit_event(crate::Event::ProposalAccepted(self.id)); + + self.execute_proposal()?; + + Ok(()) + } + + fn execute_proposal(self) -> DispatchResult { + add_balance::(&self.proposer, self.proposal_cost); + + match self.data { + ProposalData::GlobalParams { + min_name_length, + max_name_length, + max_allowed_agents, + max_allowed_weights, + min_weight_stake, + min_weight_control_fee, + min_staking_fee, + } => { + pallet_torus0::MinNameLength::::set(min_name_length); + pallet_torus0::MaxNameLength::::set(max_name_length); + pallet_torus0::MaxAllowedAgents::::set(max_allowed_agents); + pallet_torus0::MaxAllowedWeights::::set(max_allowed_weights); + pallet_torus0::MinWeightStake::::set(min_weight_stake); + pallet_torus0::FeeConstraints::::mutate(|constraints| { + constraints.min_weight_control_fee = + Percent::from_percent(min_weight_control_fee); + constraints.min_staking_fee = Percent::from_percent(min_staking_fee); + }); + } + ProposalData::TransferDaoTreasury { account, amount } => { + transfer_balance::(&DaoTreasuryAddress::::get(), &account, amount)?; + } + + _ => {} + } + + Ok(()) + } + + /// Marks a proposal as refused and overrides the storage value. + pub fn refuse( + mut self, + block: Block, + stake_for: BalanceOf, + stake_against: BalanceOf, + ) -> DispatchResult { + ensure!(self.is_active(), crate::Error::::ProposalIsFinished); + + self.status = ProposalStatus::Refused { + block, + stake_for, + stake_against, + }; + + Proposals::::insert(self.id, &self); + crate::Pallet::::deposit_event(crate::Event::ProposalRefused(self.id)); + + Ok(()) + } + + /// Marks a proposal as expired and overrides the storage value. + pub fn expire(mut self, block_number: u64) -> DispatchResult { + ensure!(self.is_active(), crate::Error::::ProposalIsFinished); + ensure!( + block_number >= self.expiration_block, + crate::Error::::InvalidProposalFinalizationParameters + ); + + self.status = ProposalStatus::Expired; + + Proposals::::insert(self.id, &self); + crate::Pallet::::deposit_event(crate::Event::ProposalExpired(self.id)); + + Ok(()) + } +} + #[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub enum ProposalStatus { @@ -50,6 +160,15 @@ pub enum ProposalStatus { #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub enum ProposalData { + GlobalParams { + min_name_length: u16, + max_name_length: u16, + max_allowed_agents: u16, + max_allowed_weights: u16, + min_weight_stake: BalanceOf, + min_weight_control_fee: u8, + min_staking_fee: u8, + }, GlobalCustom, TransferDaoTreasury { account: AccountIdOf, @@ -57,26 +176,374 @@ pub enum ProposalData { }, } +impl ProposalData { + #[must_use] + pub fn required_stake(&self) -> Percent { + match self { + Self::GlobalCustom | Self::TransferDaoTreasury { .. } => Percent::from_parts(50), + Self::GlobalParams { .. } => Percent::from_parts(40), + } + } +} + #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub struct UnrewardedProposal { pub block: Block, - pub votes_for: BoundedBTreeMap, u64, ConstU32<{ u32::MAX }>>, - pub votes_against: BoundedBTreeMap, u64, ConstU32<{ u32::MAX }>>, + pub votes_for: BoundedBTreeMap, BalanceOf, ConstU32<{ u32::MAX }>>, + pub votes_against: BoundedBTreeMap, BalanceOf, ConstU32<{ u32::MAX }>>, +} + +pub fn add_global_params_proposal( + proposer: AccountIdOf, + min_name_length: u16, + max_name_length: u16, + max_allowed_agents: u16, + max_allowed_weights: u16, + min_weight_stake: BalanceOf, + min_weight_control_fee: u8, + min_staking_fee: u8, + metadata: Vec, +) -> DispatchResult { + let data = ProposalData::::GlobalParams { + min_name_length, + max_name_length, + max_allowed_agents, + max_allowed_weights, + min_weight_stake, + min_weight_control_fee, + min_staking_fee, + }; + + add_proposal::(proposer, data, metadata) } pub fn add_global_custom_proposal( - _origin: OriginFor, - _data: Vec, + proposer: AccountIdOf, + metadata: Vec, ) -> DispatchResult { - todo!() + add_proposal(proposer, ProposalData::::GlobalCustom, metadata) } pub fn add_dao_treasury_transfer_proposal( - _origin: OriginFor, - _value: BalanceOf, - _destination_key: AccountIdOf, - _data: Vec, + proposer: AccountIdOf, + value: BalanceOf, + destination_key: AccountIdOf, + metadata: Vec, +) -> DispatchResult { + let data = ProposalData::::TransferDaoTreasury { + account: destination_key, + amount: value, + }; + + add_proposal::(proposer, data, metadata) +} + +fn add_proposal( + proposer: AccountIdOf, + data: ProposalData, + metadata: Vec, +) -> DispatchResult { + let config = GlobalGovernanceConfig::::get(); + + let cost = config.proposal_cost; + + // TODO: charge cost + + let proposal_id: u64 = crate::Proposals::::iter() + .count() + .try_into() + .map_err(|_| crate::Error::::InternalError)?; + + let current_block: u64 = + TryInto::try_into(>::block_number()) + .ok() + .expect("blockchain will not exceed 2^64 blocks; QED."); + + let proposal = Proposal:: { + id: proposal_id, + proposer: proposer, + expiration_block: current_block + config.proposal_expiration, + data, + status: ProposalStatus::Open { + votes_for: BoundedBTreeSet::new(), + votes_against: BoundedBTreeSet::new(), + stake_for: 0, + stake_against: 0, + }, + metadata: BoundedVec::truncate_from(metadata), + proposal_cost: cost, + creation_block: current_block, + }; + + crate::Proposals::::insert(proposal_id, proposal); + + Ok(()) +} + +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; + } + + for (id, proposal) in proposals { + let res = with_storage_layer(|| tick_proposal(¬_delegating, block_number, proposal)); + if let Err(err) = res { + log::error!("failed to tick proposal {id}: {err:?}, skipping..."); + } + } +} + +pub fn get_minimal_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() +} + +fn tick_proposal( + not_delegating: &BTreeSet, + block_number: u64, + mut proposal: Proposal, ) -> DispatchResult { - todo!() + let ProposalStatus::Open { + votes_for, + votes_against, + .. + } = &proposal.status + else { + return Err(Error::::ProposalIsFinished.into()); + }; + + let votes_for: Vec<(AccountIdOf, BalanceOf)> = votes_for + .iter() + .cloned() + .map(|id| { + let stake = calc_stake::(not_delegating, &id); + (id, stake) + }) + .collect(); + let votes_against: Vec<(AccountIdOf, BalanceOf)> = votes_against + .iter() + .cloned() + .map(|id| { + let stake = calc_stake::(not_delegating, &id); + (id, stake) + }) + .collect(); + + let stake_for_sum: BalanceOf = votes_for.iter().map(|(_, stake)| stake).sum(); + let stake_against_sum: BalanceOf = votes_against.iter().map(|(_, stake)| stake).sum(); + + if block_number < proposal.expiration_block { + if let ProposalStatus::Open { + stake_for, + stake_against, + .. + } = &mut proposal.status + { + *stake_for = stake_for_sum; + *stake_against = stake_against_sum; + } + Proposals::::set(proposal.id, Some(proposal)); + return Ok(()); + } + + 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()); + + let mut reward_votes_for = BoundedBTreeMap::new(); + for (key, value) in votes_for { + reward_votes_for + .try_insert(key, value) + .expect("this wont exceed u32::MAX"); + } + + let mut reward_votes_against: BoundedBTreeMap< + T::AccountId, + BalanceOf, + ConstU32<{ u32::MAX }>, + > = BoundedBTreeMap::new(); + for (key, value) in votes_against { + reward_votes_against + .try_insert(key, value) + .expect("this probably wont exceed u32::MAX"); + } + + UnrewardedProposals::::insert( + proposal.id, + UnrewardedProposal:: { + block: block_number, + votes_for: reward_votes_for, + votes_against: reward_votes_against, + }, + ); + + if total_stake >= minimal_stake_to_execute { + if stake_against_sum > stake_for_sum { + proposal.refuse(block_number, stake_for_sum, stake_against_sum) + } else { + proposal.accept(block_number, stake_for_sum, stake_against_sum) + } + } else { + proposal.expire(block_number) + } +} + +#[inline] +fn calc_stake( + not_delegating: &BTreeSet, + voter: &T::AccountId, +) -> BalanceOf { + let own_stake: BalanceOf = if !not_delegating.contains(voter) { + 0 + } else { + pallet_torus0::stake::sum_staking_to::(voter) + }; + + let delegated_stake = pallet_torus0::stake::get_staking_to_vector::(voter) + .into_iter() + .filter(|(staker, _)| !not_delegating.contains(staker)) + .map(|(_, stake)| stake) + .sum(); + + own_stake.saturating_add(delegated_stake) +} + +pub fn tick_proposal_rewards(block_number: u64) { + let governance_config = crate::GlobalGovernanceConfig::::get(); + let reached_interval = block_number + .checked_rem(governance_config.proposal_reward_interval) + .is_some_and(|r| r == 0); + if !reached_interval { + return; + } + + let mut n: u16 = 0; + let mut account_stakes: BoundedBTreeMap, ConstU32<{ u32::MAX }>> = + BoundedBTreeMap::new(); + let mut total_allocation: I92F36 = I92F36::from_num(0); + for (proposal_id, unrewarded_proposal) in UnrewardedProposals::::iter() { + if unrewarded_proposal.block + < block_number.saturating_sub(governance_config.proposal_reward_interval) + { + continue; + } + + for (acc_id, stake) in unrewarded_proposal + .votes_for + .into_iter() + .chain(unrewarded_proposal.votes_against.into_iter()) + { + let curr_stake = *account_stakes.get(&acc_id).unwrap_or(&0u128); + account_stakes + .try_insert(acc_id, curr_stake.saturating_add(stake)) + .expect("infallible"); + } + + match get_reward_allocation::(&governance_config, n) { + Ok(allocation) => { + total_allocation = total_allocation.saturating_add(allocation); + } + Err(err) => { + log::error!("could not get reward allocation for proposal {proposal_id}: {err:?}"); + continue; + } + } + + UnrewardedProposals::::remove(proposal_id); + n = n.saturating_add(1); + } + + distribute_proposal_rewards::( + account_stakes, + total_allocation, + governance_config.max_proposal_reward_treasury_allocation, + ); +} + +fn get_reward_allocation( + governance_config: &GovernanceConfiguration, + n: u16, +) -> Result { + let treasury_address = DaoTreasuryAddress::::get(); + let treasury_balance = get_balance::(&treasury_address); + let treasury_balance = I92F36::from_num(treasury_balance); + + let allocation_percentage = I92F36::from_num( + governance_config + .proposal_reward_treasury_allocation + .deconstruct(), + ); + let max_allocation = + I92F36::from_num(governance_config.max_proposal_reward_treasury_allocation); + + let mut allocation = treasury_balance + .checked_mul(allocation_percentage) + .unwrap_or_default() + .min(max_allocation); + if n > 0 { + let mut base = I92F36::from_num(1.5); + let mut result = I92F36::from_num(1); + let mut remaining = n; + + while remaining > 0 { + if remaining % 2 == 1 { + result = result.checked_mul(base).unwrap_or(result); + } + base = base.checked_mul(base).unwrap_or_default(); + remaining /= 2; + } + + allocation = allocation.checked_div(result).unwrap_or(allocation); + } + Ok(allocation) +} + +fn distribute_proposal_rewards( + account_stakes: BoundedBTreeMap, ConstU32<{ u32::MAX }>>, + 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 + if total_allocation > I92F36::from_num(max_proposal_reward_treasury_allocation) { + log::error!("total allocation exceeds max proposal reward treasury allocation"); + return; + } + + use polkadot_sdk::frame_support::sp_runtime::traits::IntegerSquareRoot; + + let dao_treasury_address = DaoTreasuryAddress::::get(); + let account_sqrt_stakes: Vec<_> = account_stakes + .into_iter() + .map(|(acc_id, stake)| (acc_id, stake.integer_sqrt())) + .collect(); + + let total_stake: BalanceOf = account_sqrt_stakes.iter().map(|(_, stake)| *stake).sum(); + let total_stake = I92F36::from_num(total_stake); + + for (acc_id, stake) in account_sqrt_stakes.into_iter() { + let percentage = I92F36::from_num(stake) + .checked_div(total_stake) + .unwrap_or_default(); + + let reward: BalanceOf = total_allocation + .checked_mul(percentage) + .unwrap_or_default() + .to_num(); + + // Transfer the proposal reward to the accounts from treasury + let _ = transfer_balance::(&dao_treasury_address, &acc_id, reward); + } } diff --git a/pallets/governance/src/voting.rs b/pallets/governance/src/voting.rs index 08cfcc9..4dfe92b 100644 --- a/pallets/governance/src/voting.rs +++ b/pallets/governance/src/voting.rs @@ -1,17 +1,94 @@ +use crate::{ + get_balance, + proposal::{Proposal, ProposalStatus, UnrewardedProposal}, + transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, Event, + GlobalGovernanceConfig, Proposals, UnrewardedProposals, +}; +use crate::{GovernanceConfiguration, NotDelegatingVotingPower}; +use polkadot_sdk::sp_std::{collections::btree_set::BTreeSet, vec::Vec}; +use polkadot_sdk::{frame_support::traits::tokens::Balance, sp_runtime::SaturatedConversion}; use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, + frame_support::{dispatch::DispatchResult, ensure, storage::with_storage_layer}, + polkadot_sdk_frame::prelude::OriginFor, + sc_telemetry::log, + sp_core::ConstU32, + sp_runtime::{BoundedBTreeMap, DispatchError, Percent}, }; +use substrate_fixed::types::I92F36; pub fn add_vote( - _origin: OriginFor, - _proposal_id: u64, - _agree: bool, + voter: AccountIdOf, + proposal_id: u64, + agree: bool, ) -> DispatchResult { - todo!() + let Some(mut proposal) = Proposals::::get(proposal_id) else { + return Err(Error::::ProposalNotFound.into()); + }; + + let crate::proposal::ProposalStatus::Open { + votes_for, + votes_against, + .. + } = &mut proposal.status + else { + return Err(Error::::ProposalClosed.into()); + }; + + ensure!( + !votes_for.contains(&voter) && !votes_against.contains(&voter), + crate::Error::::AlreadyVoted + ); + + let voter_delegated_stake = pallet_torus0::stake::sum_staked_by::(&voter); + let voter_owned_stake = pallet_torus0::stake::sum_staking_to::(&voter); + + ensure!( + voter_delegated_stake > 0 || voter_owned_stake > 0, + crate::Error::::InsufficientStake + ); + + if !crate::NotDelegatingVotingPower::::get().contains(&voter) && voter_delegated_stake == 0 { + return Err(Error::::VoterIsDelegatingVotingPower.into()); + } + + if agree { + votes_for + .try_insert(voter.clone()) + .map_err(|_| Error::::InternalError)?; + } else { + votes_against + .try_insert(voter.clone()) + .map_err(|_| Error::::InternalError)?; + } + + Proposals::::insert(proposal.id, proposal); + crate::Pallet::::deposit_event(Event::::ProposalVoted(proposal_id, voter, agree)); + Ok(()) } -pub fn remove_vote(_origin: OriginFor, _proposal_id: u64) -> DispatchResult { - todo!() +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()); + }; + + let ProposalStatus::Open { + votes_for, + votes_against, + .. + } = &mut proposal.status + else { + return Err(Error::::ProposalClosed.into()); + }; + + let removed = votes_for.remove(&voter) || votes_against.remove(&voter); + + // Check if the voter has actually voted on the proposal + ensure!(removed, crate::Error::::NotVoted); + + // Update the proposal in storage + Proposals::::insert(proposal.id, proposal); + crate::Pallet::::deposit_event(Event::::ProposalVoteUnregistered(proposal_id, voter)); + Ok(()) } pub fn enable_delegation(_origin: OriginFor) -> DispatchResult { diff --git a/pallets/governance/src/whitelist.rs b/pallets/governance/src/whitelist.rs index e48504b..fe0353c 100644 --- a/pallets/governance/src/whitelist.rs +++ b/pallets/governance/src/whitelist.rs @@ -1,19 +1,27 @@ -use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, -}; +use polkadot_sdk::frame_support::dispatch::DispatchResult; use crate::AccountIdOf; -pub fn add_to_whitelist( - _origin: OriginFor, - _key: AccountIdOf, -) -> DispatchResult { - todo!() +pub fn add_to_whitelist(key: AccountIdOf) -> DispatchResult { + if is_whitelisted::(&key) { + return Err(crate::Error::::AlreadyWhitelisted.into()); + } + + crate::Whitelist::::insert(key.clone(), ()); + crate::Pallet::::deposit_event(crate::Event::::WhitelistAdded(key)); + Ok(()) +} + +pub fn remove_from_whitelist(key: AccountIdOf) -> DispatchResult { + if !is_whitelisted::(&key) { + return Err(crate::Error::::NotWhitelisted.into()); + } + + crate::Whitelist::::remove(&key); + crate::Pallet::::deposit_event(crate::Event::::WhitelistRemoved(key)); + Ok(()) } -pub fn remove_from_whitelist( - _origin: OriginFor, - _key: AccountIdOf, -) -> DispatchResult { - todo!() +pub fn is_whitelisted(key: &AccountIdOf) -> bool { + crate::Whitelist::::contains_key(key) } diff --git a/pallets/torus0/src/fee.rs b/pallets/torus0/src/fee.rs index 634633c..7faa800 100644 --- a/pallets/torus0/src/fee.rs +++ b/pallets/torus0/src/fee.rs @@ -8,7 +8,7 @@ use scale_info::TypeInfo; #[derive(DebugNoBound, Decode, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ValidatorFeeConstraints { - pub min_stake_delegation_fee: Percent, + pub min_staking_fee: Percent, pub min_weight_control_fee: Percent, pub _pd: PhantomData, } @@ -16,7 +16,7 @@ pub struct ValidatorFeeConstraints { impl Default for ValidatorFeeConstraints { fn default() -> Self { Self { - min_stake_delegation_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), + min_staking_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), min_weight_control_fee: Percent::from_percent(T::DefaultMinWeightControlFee::get()), _pd: PhantomData, } @@ -26,7 +26,7 @@ impl Default for ValidatorFeeConstraints { #[derive(DebugNoBound, Decode, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ValidatorFee { - pub stake_delegation_fee: Percent, + pub staking_fee: Percent, pub weight_control_fee: Percent, pub _pd: PhantomData, } @@ -34,7 +34,7 @@ pub struct ValidatorFee { impl Default for ValidatorFee { fn default() -> Self { Self { - stake_delegation_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), + staking_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), weight_control_fee: Percent::from_percent(T::DefaultMinWeightControlFee::get()), _pd: PhantomData, } diff --git a/pallets/torus0/src/lib.rs b/pallets/torus0/src/lib.rs index 0a45429..950a9dd 100644 --- a/pallets/torus0/src/lib.rs +++ b/pallets/torus0/src/lib.rs @@ -4,7 +4,7 @@ mod agent; mod balance; mod ext; mod fee; -mod stake; +pub mod stake; use crate::agent::Agent; use crate::fee::ValidatorFee; diff --git a/pallets/torus0/src/stake.rs b/pallets/torus0/src/stake.rs index fb38064..1547285 100644 --- a/pallets/torus0/src/stake.rs +++ b/pallets/torus0/src/stake.rs @@ -1,3 +1,5 @@ +use polkadot_sdk::sp_std::collections::btree_map::BTreeMap; + use polkadot_sdk::{ frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, }; @@ -28,3 +30,20 @@ pub fn transfer_stake( ) -> DispatchResult { todo!() } + +#[inline] +pub fn sum_staking_to(staker: &AccountIdOf) -> BalanceOf { + crate::StakingTo::::iter_prefix_values(staker).sum() +} + +#[inline] +pub fn get_staking_to_vector( + staker: &AccountIdOf, +) -> BTreeMap> { + crate::StakingTo::::iter_prefix(staker).collect() +} + +#[inline] +pub fn sum_staked_by(staked: &AccountIdOf) -> BalanceOf { + crate::StakedBy::::iter_prefix_values(staked).sum() +} From 9c75e5d905af46b2dd7511e676c409a1d98bac37 Mon Sep 17 00:00:00 2001 From: devwckd Date: Mon, 16 Dec 2024 18:23:43 -0300 Subject: [PATCH 2/8] feat: implement on runtime --- pallets/governance/src/application.rs | 2 +- pallets/governance/src/ext.rs | 2 +- pallets/governance/src/lib.rs | 1 + pallets/governance/src/proposal.rs | 5 +++-- pallets/governance/src/voting.rs | 16 ++-------------- runtime/src/configs.rs | 10 ++++++++++ runtime/src/lib.rs | 3 +++ 7 files changed, 21 insertions(+), 18 deletions(-) diff --git a/pallets/governance/src/application.rs b/pallets/governance/src/application.rs index 025845d..ded2631 100644 --- a/pallets/governance/src/application.rs +++ b/pallets/governance/src/application.rs @@ -40,7 +40,7 @@ pub fn submit_application( if data_len < T::MinApplicationDataLength::get() || data_len > T::MaxApplicationDataLength::get() { - return Err(crate::Error::::InvalidApplicationDataLength)?; + return Err(crate::Error::::InvalidApplicationDataLength.into()); } let current_block: u64 = diff --git a/pallets/governance/src/ext.rs b/pallets/governance/src/ext.rs index 8f20a71..2a98b58 100644 --- a/pallets/governance/src/ext.rs +++ b/pallets/governance/src/ext.rs @@ -32,7 +32,7 @@ pub(super) fn remove_balance( } pub(super) fn add_balance(key: &AccountIdOf, amount: BalanceOf) { - let _ = ::Currency::deposit_creating(&key, amount); + let _ = ::Currency::deposit_creating(key, amount); } pub(super) fn transfer_balance( diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index af7ea48..ddd304a 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -29,6 +29,7 @@ use polkadot_sdk::sp_std::vec::Vec; #[frame::pallet(dev_mode)] pub mod pallet { + #![allow(clippy::too_many_arguments)] use super::*; diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index 3095cdb..840fd55 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -4,7 +4,7 @@ use crate::BoundedVec; use crate::DebugNoBound; use crate::TypeInfo; use crate::{ - get_balance, transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, Event, + get_balance, transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, GlobalGovernanceConfig, Proposals, UnrewardedProposals, }; use crate::{GovernanceConfiguration, NotDelegatingVotingPower}; @@ -194,6 +194,7 @@ pub struct UnrewardedProposal { pub votes_against: BoundedBTreeMap, BalanceOf, ConstU32<{ u32::MAX }>>, } +#[allow(clippy::too_many_arguments)] pub fn add_global_params_proposal( proposer: AccountIdOf, min_name_length: u16, @@ -262,7 +263,7 @@ fn add_proposal( let proposal = Proposal:: { id: proposal_id, - proposer: proposer, + proposer, expiration_block: current_block + config.proposal_expiration, data, status: ProposalStatus::Open { diff --git a/pallets/governance/src/voting.rs b/pallets/governance/src/voting.rs index 4dfe92b..a07a28b 100644 --- a/pallets/governance/src/voting.rs +++ b/pallets/governance/src/voting.rs @@ -1,20 +1,8 @@ -use crate::{ - get_balance, - proposal::{Proposal, ProposalStatus, UnrewardedProposal}, - transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, Event, - GlobalGovernanceConfig, Proposals, UnrewardedProposals, -}; -use crate::{GovernanceConfiguration, NotDelegatingVotingPower}; -use polkadot_sdk::sp_std::{collections::btree_set::BTreeSet, vec::Vec}; -use polkadot_sdk::{frame_support::traits::tokens::Balance, sp_runtime::SaturatedConversion}; +use crate::{proposal::ProposalStatus, AccountIdOf, Error, Event, Proposals}; use polkadot_sdk::{ - frame_support::{dispatch::DispatchResult, ensure, storage::with_storage_layer}, + frame_support::{dispatch::DispatchResult, ensure}, polkadot_sdk_frame::prelude::OriginFor, - sc_telemetry::log, - sp_core::ConstU32, - sp_runtime::{BoundedBTreeMap, DispatchError, Percent}, }; -use substrate_fixed::types::I92F36; pub fn add_vote( voter: AccountIdOf, diff --git a/runtime/src/configs.rs b/runtime/src/configs.rs index 59e3265..af804cb 100644 --- a/runtime/src/configs.rs +++ b/runtime/src/configs.rs @@ -333,5 +333,15 @@ parameter_types! { impl pallet_governance::Config for Runtime { type PalletId = GovernancePalletId; + type MinApplicationDataLength = ConstU32<2>; + + type MaxApplicationDataLength = ConstU32<256>; + + type ApplicationExpiration = ConstU64<2000>; + + type MaxPenaltyPercentage = ConstU8<20>; + + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ba8bd73..a06547f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -143,6 +143,9 @@ mod runtime { #[runtime::pallet_index(11)] pub type Torus0 = pallet_torus0::Pallet; + + #[runtime::pallet_index(12)] + pub type Governance = pallet_governance::Pallet; } parameter_types! { From 148566d7b1f09c1c4b00ad7c483205fd229bc199 Mon Sep 17 00:00:00 2001 From: devwckd Date: Mon, 16 Dec 2024 18:31:12 -0300 Subject: [PATCH 3/8] feat: charge proposal cost --- pallets/governance/src/proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index 840fd55..1e4b3e7 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -1,4 +1,5 @@ use crate::add_balance; +use crate::remove_balance; use crate::BoundedBTreeSet; use crate::BoundedVec; use crate::DebugNoBound; @@ -248,8 +249,7 @@ fn add_proposal( let config = GlobalGovernanceConfig::::get(); let cost = config.proposal_cost; - - // TODO: charge cost + remove_balance::(&proposer, cost)?; let proposal_id: u64 = crate::Proposals::::iter() .count() From 89361fe069db3594e029734b089de620b8ab9b7d Mon Sep 17 00:00:00 2001 From: devwckd Date: Tue, 17 Dec 2024 15:23:48 -0300 Subject: [PATCH 4/8] feat: validate proposal params --- pallets/governance/src/application.rs | 13 ++++- pallets/governance/src/ext.rs | 39 +-------------- pallets/governance/src/lib.rs | 10 ++++ pallets/governance/src/proposal.rs | 68 +++++++++++++++++++++++---- 4 files changed, 81 insertions(+), 49 deletions(-) diff --git a/pallets/governance/src/application.rs b/pallets/governance/src/application.rs index ded2631..0d18403 100644 --- a/pallets/governance/src/application.rs +++ b/pallets/governance/src/application.rs @@ -1,7 +1,10 @@ -use crate::{remove_balance, whitelist, AccountIdOf, BalanceOf, Block}; +use crate::frame::traits::ExistenceRequirement; +use crate::{whitelist, AccountIdOf, 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; @@ -30,7 +33,13 @@ pub fn submit_application( let config = crate::GlobalGovernanceConfig::::get(); let cost = config.agent_application_cost; - remove_balance::(&payer, cost)?; + let _ = ::Currency::withdraw( + &payer, + cost, + WithdrawReasons::except(WithdrawReasons::TIP), + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::NotEnoughBalanceToApply)?; let data_len: u32 = data .len() diff --git a/pallets/governance/src/ext.rs b/pallets/governance/src/ext.rs index 2a98b58..539a4dd 100644 --- a/pallets/governance/src/ext.rs +++ b/pallets/governance/src/ext.rs @@ -1,7 +1,4 @@ -use polkadot_sdk::frame_support::{ - dispatch::DispatchResult, - traits::{Currency, ExistenceRequirement, WithdrawReasons}, -}; +use polkadot_sdk::frame_support::traits::Currency; pub(super) type BalanceOf = <::Currency as Currency< ::AccountId, @@ -12,37 +9,3 @@ pub(super) type AccountIdOf = ::Acco pub(super) type Block = u64; pub(super) type BlockAmount = u64; - -pub(super) fn get_balance(key: &AccountIdOf) -> BalanceOf { - ::Currency::free_balance(key) -} - -pub(super) fn remove_balance( - key: &AccountIdOf, - amount: BalanceOf, -) -> DispatchResult { - let _ = ::Currency::withdraw( - key, - amount, - WithdrawReasons::except(WithdrawReasons::TIP), - ExistenceRequirement::KeepAlive, - ) - .map_err(|_| crate::Error::::NotEnoughBalanceToPropose)?; // TODO: change error - Ok(()) -} - -pub(super) fn add_balance(key: &AccountIdOf, amount: BalanceOf) { - let _ = ::Currency::deposit_creating(key, amount); -} - -pub(super) fn transfer_balance( - from: &AccountIdOf, - to: &AccountIdOf, - amount: BalanceOf, -) -> DispatchResult { - // TODO: change error - ::Currency::transfer(from, to, amount, ExistenceRequirement::KeepAlive) - .map_err(|_| crate::Error::::InternalError)?; // TODO: change error - - Ok(()) -} diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index ddd304a..c776754 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -384,5 +384,15 @@ pub mod pallet { AgentNotFound, /// Invalid agent penalty percentage InvalidPenaltyPercentage, + /// Invalid minimum name length in proposal + InvalidMinNameLength, + /// Invalid maximum name length in proposal + InvalidMaxNameLength, + /// Invalid maximum allowed agents in proposal + InvalidMaxAllowedAgents, + /// Invalid maximum allowed weights in proposal + InvalidMaxAllowedWeights, + /// Invalid minimum weight control fee in proposal + InvalidMinWeightControlFee, } } diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index 1e4b3e7..2b67fc0 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -1,15 +1,17 @@ -use crate::add_balance; -use crate::remove_balance; +use crate::frame::traits::ExistenceRequirement; use crate::BoundedBTreeSet; use crate::BoundedVec; use crate::DebugNoBound; use crate::TypeInfo; use crate::{ - get_balance, transfer_balance, AccountIdOf, BalanceOf, Block, DaoTreasuryAddress, Error, - GlobalGovernanceConfig, Proposals, UnrewardedProposals, + 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::sp_runtime::SaturatedConversion; use polkadot_sdk::sp_std::{collections::btree_set::BTreeSet, vec::Vec}; use polkadot_sdk::{ @@ -19,6 +21,7 @@ use polkadot_sdk::{ sp_runtime::{BoundedBTreeMap, DispatchError, Percent}, }; use substrate_fixed::types::I92F36; + pub type ProposalId = u64; #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)] @@ -65,7 +68,8 @@ impl Proposal { } fn execute_proposal(self) -> DispatchResult { - add_balance::(&self.proposer, self.proposal_cost); + let _ = + ::Currency::deposit_creating(&self.proposer, self.proposal_cost); match self.data { ProposalData::GlobalParams { @@ -89,7 +93,13 @@ impl Proposal { }); } ProposalData::TransferDaoTreasury { account, amount } => { - transfer_balance::(&DaoTreasuryAddress::::get(), &account, amount)?; + ::Currency::transfer( + &DaoTreasuryAddress::::get(), + &account, + amount, + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::InternalError)?; } _ => {} @@ -207,6 +217,24 @@ pub fn add_global_params_proposal( min_staking_fee: u8, metadata: Vec, ) -> DispatchResult { + ensure!(min_name_length > 1, crate::Error::::InvalidMinNameLength); + ensure!( + (max_name_length as u32) < T::MaxAgentNameLengthConstraint::get(), + crate::Error::::InvalidMaxNameLength + ); + ensure!( + max_allowed_agents < 2000, + crate::Error::::InvalidMaxAllowedAgents + ); + ensure!( + max_allowed_weights < 2000, + crate::Error::::InvalidMaxAllowedWeights + ); + ensure!( + min_weight_control_fee > 10, + crate::Error::::InvalidMaxAllowedWeights + ); + let data = ProposalData::::GlobalParams { min_name_length, max_name_length, @@ -246,10 +274,25 @@ fn add_proposal( data: ProposalData, metadata: Vec, ) -> DispatchResult { + ensure!( + !metadata.is_empty(), + crate::Error::::ProposalDataTooSmall + ); + ensure!( + metadata.len() <= 256, + crate::Error::::ProposalDataTooLarge + ); + let config = GlobalGovernanceConfig::::get(); let cost = config.proposal_cost; - remove_balance::(&proposer, cost)?; + let _ = ::Currency::withdraw( + &proposer, + cost, + WithdrawReasons::except(WithdrawReasons::TIP), + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::NotEnoughBalanceToPropose)?; let proposal_id: u64 = crate::Proposals::::iter() .count() @@ -479,7 +522,7 @@ fn get_reward_allocation( n: u16, ) -> Result { let treasury_address = DaoTreasuryAddress::::get(); - let treasury_balance = get_balance::(&treasury_address); + let treasury_balance = ::Currency::free_balance(&treasury_address); let treasury_balance = I92F36::from_num(treasury_balance); let allocation_percentage = I92F36::from_num( @@ -545,6 +588,13 @@ fn distribute_proposal_rewards( .to_num(); // Transfer the proposal reward to the accounts from treasury - let _ = transfer_balance::(&dao_treasury_address, &acc_id, reward); + if let Err(err) = ::Currency::transfer( + &dao_treasury_address, + &acc_id, + reward, + ExistenceRequirement::KeepAlive, + ) { + log::error!("could not transfer proposal reward: {err:?}") + } } } From 789798e25d6f9ee54d1d70c98c13e11a0e2c4833 Mon Sep 17 00:00:00 2001 From: devwckd Date: Tue, 17 Dec 2024 15:41:38 -0300 Subject: [PATCH 5/8] refac: remove _extrinsic suffix from calls --- pallets/governance/src/lib.rs | 51 +++++++++++------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index c776754..9d72122 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -117,64 +117,49 @@ pub mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(0)] - pub fn add_curator_extrinsic(origin: OriginFor, key: AccountIdOf) -> DispatchResult { + pub fn add_curator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { ensure_root(origin)?; curator::add_curator::(key) } #[pallet::call_index(1)] #[pallet::weight(0)] - pub fn remove_curator_extrinsic( - origin: OriginFor, - key: AccountIdOf, - ) -> DispatchResult { + pub fn remove_curator(origin: OriginFor, key: AccountIdOf) -> DispatchResult { ensure_root(origin)?; curator::remove_curator::(key) } #[pallet::call_index(2)] #[pallet::weight(0)] - pub fn add_to_whitelist_extrinsic( - origin: OriginFor, - key: AccountIdOf, - ) -> DispatchResult { + pub fn add_to_whitelist(origin: OriginFor, key: AccountIdOf) -> DispatchResult { curator::ensure_curator::(origin)?; whitelist::add_to_whitelist::(key) } #[pallet::call_index(3)] #[pallet::weight(0)] - pub fn remove_from_whitelist_extrinsic( - origin: OriginFor, - key: AccountIdOf, - ) -> DispatchResult { + pub fn remove_from_whitelist(origin: OriginFor, key: AccountIdOf) -> DispatchResult { curator::ensure_curator::(origin)?; whitelist::remove_from_whitelist::(key) } #[pallet::call_index(4)] #[pallet::weight(0)] - pub fn accept_application_extrinsic( - origin: OriginFor, - application_id: u32, - ) -> DispatchResult { + pub fn accept_application(origin: OriginFor, application_id: u32) -> DispatchResult { curator::ensure_curator::(origin)?; application::accept_application::(application_id) } #[pallet::call_index(5)] #[pallet::weight(0)] - pub fn deny_application_extrinsic( - origin: OriginFor, - application_id: u32, - ) -> DispatchResult { + pub fn deny_application(origin: OriginFor, application_id: u32) -> DispatchResult { curator::ensure_curator::(origin)?; application::deny_application::(application_id) } #[pallet::call_index(6)] #[pallet::weight(0)] - pub fn penalize_agent_extrinsic( + pub fn penalize_agent( origin: OriginFor, agent_key: AccountIdOf, percentage: u8, @@ -185,7 +170,7 @@ pub mod pallet { #[pallet::call_index(7)] #[pallet::weight(0)] - pub fn submit_application_extrinsic( + pub fn submit_application( origin: OriginFor, agent_key: AccountIdOf, data: Vec, @@ -196,7 +181,7 @@ pub mod pallet { #[pallet::call_index(8)] #[pallet::weight(0)] - pub fn add_global_params_proposal_extrinsic( + pub fn add_global_params_proposal( origin: OriginFor, min_name_length: u16, max_name_length: u16, @@ -223,17 +208,14 @@ pub mod pallet { #[pallet::call_index(9)] #[pallet::weight(0)] - pub fn add_global_custom_proposal_extrinsic( - origin: OriginFor, - data: Vec, - ) -> DispatchResult { + pub fn add_global_custom_proposal(origin: OriginFor, data: Vec) -> DispatchResult { let proposer = ensure_signed(origin)?; proposal::add_global_custom_proposal::(proposer, data) } #[pallet::call_index(10)] #[pallet::weight(0)] - pub fn add_dao_treasury_transfer_proposal_extrinsic( + pub fn add_dao_treasury_transfer_proposal( origin: OriginFor, value: BalanceOf, destination_key: AccountIdOf, @@ -250,7 +232,7 @@ pub mod pallet { #[pallet::call_index(11)] #[pallet::weight(0)] - pub fn vote_proposal_extrinsic( + pub fn vote_proposal( origin: OriginFor, proposal_id: u64, agree: bool, @@ -261,23 +243,20 @@ pub mod pallet { #[pallet::call_index(12)] #[pallet::weight(0)] - pub fn remove_vote_proposal_extrinsic( - origin: OriginFor, - proposal_id: u64, - ) -> DispatchResult { + pub fn remove_vote_proposal(origin: OriginFor, proposal_id: u64) -> DispatchResult { let voter = ensure_signed(origin)?; voting::remove_vote::(voter, proposal_id) } #[pallet::call_index(13)] #[pallet::weight(0)] - pub fn enable_vote_delegation_extrinsic(origin: OriginFor) -> DispatchResult { + pub fn enable_vote_delegation(origin: OriginFor) -> DispatchResult { voting::enable_delegation::(origin) } #[pallet::call_index(14)] #[pallet::weight(0)] - pub fn disable_vote_delegation_extrinsic(origin: OriginFor) -> DispatchResult { + pub fn disable_vote_delegation(origin: OriginFor) -> DispatchResult { voting::disable_delegation::(origin) } } From 7c83aee8c2758e59e91a17e265a0619709db95da Mon Sep 17 00:00:00 2001 From: devwckd Date: Thu, 19 Dec 2024 14:06:51 -0300 Subject: [PATCH 6/8] refac: use range instead of || --- pallets/governance/src/application.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pallets/governance/src/application.rs b/pallets/governance/src/application.rs index 0d18403..7516fd0 100644 --- a/pallets/governance/src/application.rs +++ b/pallets/governance/src/application.rs @@ -46,9 +46,8 @@ pub fn submit_application( .try_into() .map_err(|_| crate::Error::::InvalidApplicationDataLength)?; - if data_len < T::MinApplicationDataLength::get() - || data_len > T::MaxApplicationDataLength::get() - { + let data_range = T::MinApplicationDataLength::get()..T::MaxApplicationDataLength::get(); + if !data_range.contains(&data_len) { return Err(crate::Error::::InvalidApplicationDataLength.into()); } From 3adcd1b7935255f075e8ac459763ff220bb093d3 Mon Sep 17 00:00:00 2001 From: devwckd Date: Thu, 19 Dec 2024 14:07:34 -0300 Subject: [PATCH 7/8] refac: try_mutate instead of getting and inserting --- pallets/governance/src/curator.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pallets/governance/src/curator.rs b/pallets/governance/src/curator.rs index fa31f15..8329b77 100644 --- a/pallets/governance/src/curator.rs +++ b/pallets/governance/src/curator.rs @@ -1,6 +1,6 @@ use crate::AccountIdOf; use polkadot_sdk::frame_election_provider_support::Get; -use polkadot_sdk::sp_runtime::Percent; +use polkadot_sdk::sp_runtime::{DispatchError, Percent}; use polkadot_sdk::{ frame_support::dispatch::DispatchResult, frame_system::ensure_signed, polkadot_sdk_frame::prelude::OriginFor, @@ -32,13 +32,15 @@ pub fn penalize_agent( return Err(crate::Error::::InvalidPenaltyPercentage.into()); } - let Some(mut agent) = pallet_torus0::Agents::::get(&agent_key) else { - return Err(crate::Error::::AgentNotFound.into()); - }; + pallet_torus0::Agents::::try_mutate(&agent_key, |agent| { + let Some(agent) = agent else { + return Err(crate::Error::::AgentNotFound.into()); + }; - agent.weight_factor = Percent::from_percent(100u8.saturating_sub(percentage)); + agent.weight_factor = Percent::from_percent(100u8.saturating_sub(percentage)); - pallet_torus0::Agents::::insert(agent_key, agent); + Ok::<(), DispatchError>(()) + })?; Ok(()) } From 4a3a14bb83df85cf5a84165ddc2ecfba48355050 Mon Sep 17 00:00:00 2001 From: devwckd Date: Thu, 19 Dec 2024 17:24:56 -0300 Subject: [PATCH 8/8] refac: use data struct on add_global_params_proposal --- pallets/governance/src/lib.rs | 35 ++++------ pallets/governance/src/proposal.rs | 105 +++++++++++++++-------------- 2 files changed, 65 insertions(+), 75 deletions(-) diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index 9d72122..0f735b9 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -31,6 +31,8 @@ use polkadot_sdk::sp_std::vec::Vec; pub mod pallet { #![allow(clippy::too_many_arguments)] + use proposal::GlobalParamsData; + use super::*; #[pallet::storage] @@ -173,44 +175,31 @@ pub mod pallet { pub fn submit_application( origin: OriginFor, agent_key: AccountIdOf, - data: Vec, + metadata: Vec, ) -> DispatchResult { let payer = ensure_signed(origin)?; - application::submit_application::(payer, agent_key, data) + application::submit_application::(payer, agent_key, metadata) } #[pallet::call_index(8)] #[pallet::weight(0)] pub fn add_global_params_proposal( origin: OriginFor, - min_name_length: u16, - max_name_length: u16, - max_allowed_agents: u16, - max_allowed_weights: u16, - min_weight_stake: BalanceOf, - min_weight_control_fee: u8, - min_staking_fee: u8, - data: Vec, + data: GlobalParamsData, + metadata: Vec, ) -> DispatchResult { let proposer = ensure_signed(origin)?; - proposal::add_global_params_proposal::( - proposer, - min_name_length, - max_name_length, - max_allowed_agents, - max_allowed_weights, - min_weight_stake, - min_weight_control_fee, - min_staking_fee, - data, - ) + proposal::add_global_params_proposal::(proposer, data, metadata) } #[pallet::call_index(9)] #[pallet::weight(0)] - pub fn add_global_custom_proposal(origin: OriginFor, data: Vec) -> DispatchResult { + pub fn add_global_custom_proposal( + origin: OriginFor, + metadata: Vec, + ) -> DispatchResult { let proposer = ensure_signed(origin)?; - proposal::add_global_custom_proposal::(proposer, data) + proposal::add_global_custom_proposal::(proposer, metadata) } #[pallet::call_index(10)] diff --git a/pallets/governance/src/proposal.rs b/pallets/governance/src/proposal.rs index 2b67fc0..3139ba5 100644 --- a/pallets/governance/src/proposal.rs +++ b/pallets/governance/src/proposal.rs @@ -72,15 +72,17 @@ impl Proposal { ::Currency::deposit_creating(&self.proposer, self.proposal_cost); match self.data { - ProposalData::GlobalParams { - min_name_length, - max_name_length, - max_allowed_agents, - max_allowed_weights, - min_weight_stake, - min_weight_control_fee, - min_staking_fee, - } => { + ProposalData::GlobalParams(data) => { + let GlobalParamsData { + min_name_length, + max_name_length, + max_allowed_agents, + max_allowed_weights, + min_weight_stake, + min_weight_control_fee, + min_staking_fee, + } = data; + pallet_torus0::MinNameLength::::set(min_name_length); pallet_torus0::MaxNameLength::::set(max_name_length); pallet_torus0::MaxAllowedAgents::::set(max_allowed_agents); @@ -168,18 +170,49 @@ pub enum ProposalStatus { Expired, } +#[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] +#[scale_info(skip_type_params(T))] +pub struct GlobalParamsData { + pub min_name_length: u16, + pub max_name_length: u16, + pub max_allowed_agents: u16, + pub max_allowed_weights: u16, + pub min_weight_stake: BalanceOf, + pub min_weight_control_fee: u8, + pub min_staking_fee: u8, +} + +impl GlobalParamsData { + pub fn validate(&self) -> DispatchResult { + ensure!( + self.min_name_length > 1, + crate::Error::::InvalidMinNameLength + ); + ensure!( + (self.max_name_length as u32) < T::MaxAgentNameLengthConstraint::get(), + crate::Error::::InvalidMaxNameLength + ); + ensure!( + self.max_allowed_agents < 2000, + crate::Error::::InvalidMaxAllowedAgents + ); + ensure!( + self.max_allowed_weights < 2000, + crate::Error::::InvalidMaxAllowedWeights + ); + ensure!( + self.min_weight_control_fee > 10, + crate::Error::::InvalidMaxAllowedWeights + ); + + Ok(()) + } +} + #[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)] #[scale_info(skip_type_params(T))] pub enum ProposalData { - GlobalParams { - min_name_length: u16, - max_name_length: u16, - max_allowed_agents: u16, - max_allowed_weights: u16, - min_weight_stake: BalanceOf, - min_weight_control_fee: u8, - min_staking_fee: u8, - }, + GlobalParams(GlobalParamsData), GlobalCustom, TransferDaoTreasury { account: AccountIdOf, @@ -208,42 +241,10 @@ pub struct UnrewardedProposal { #[allow(clippy::too_many_arguments)] pub fn add_global_params_proposal( proposer: AccountIdOf, - min_name_length: u16, - max_name_length: u16, - max_allowed_agents: u16, - max_allowed_weights: u16, - min_weight_stake: BalanceOf, - min_weight_control_fee: u8, - min_staking_fee: u8, + data: GlobalParamsData, metadata: Vec, ) -> DispatchResult { - ensure!(min_name_length > 1, crate::Error::::InvalidMinNameLength); - ensure!( - (max_name_length as u32) < T::MaxAgentNameLengthConstraint::get(), - crate::Error::::InvalidMaxNameLength - ); - ensure!( - max_allowed_agents < 2000, - crate::Error::::InvalidMaxAllowedAgents - ); - ensure!( - max_allowed_weights < 2000, - crate::Error::::InvalidMaxAllowedWeights - ); - ensure!( - min_weight_control_fee > 10, - crate::Error::::InvalidMaxAllowedWeights - ); - - let data = ProposalData::::GlobalParams { - min_name_length, - max_name_length, - max_allowed_agents, - max_allowed_weights, - min_weight_stake, - min_weight_control_fee, - min_staking_fee, - }; + let data = ProposalData::::GlobalParams(data); add_proposal::(proposer, data, metadata) }