Skip to content

Commit

Permalink
feat: add emission proposal
Browse files Browse the repository at this point in the history
  • Loading branch information
devwckd committed Jan 3, 2025
1 parent 428070d commit 5946ebe
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 15 deletions.
9 changes: 9 additions & 0 deletions pallets/governance/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,13 @@ benchmarks! {
disable_vote_delegation {
let module_key: T::AccountId = account("ModuleKey", 0, 2);
}: _(RawOrigin::Signed(module_key))

add_emission_proposal {
let module_key: T::AccountId = account("ModuleKey", 0, 2);

let config = crate::GlobalGovernanceConfig::<T>::get();
let cost = config.proposal_cost;
let _ = <T as crate::Config>::Currency::deposit_creating(&module_key, cost);

}: _(RawOrigin::Signed(module_key.clone()), Percent::from_parts(40), Percent::from_parts(40), data)
}
19 changes: 19 additions & 0 deletions pallets/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,23 @@ pub mod pallet {
let delegator = ensure_signed(origin)?;
voting::disable_delegation::<T>(delegator)
}

#[pallet::call_index(17)]
#[pallet::weight(0)]

Check failure on line 310 in pallets/governance/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this let-binding has unit value

error: this let-binding has unit value --> pallets/governance/src/lib.rs:310:26 | 310 | #[pallet::weight(0)] | ^ help: omit the `let` binding: `0;` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value = note: `-D clippy::let-unit-value` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]`

Check failure on line 310 in pallets/governance/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: It is deprecated to use hard-coded constant as call weight. Please instead benchmark all calls or put the pallet into `dev` mode. For more info see: <https://github.com/paritytech/substrate/pull/13798>

error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: It is deprecated to use hard-coded constant as call weight. Please instead benchmark all calls or put the pallet into `dev` mode. For more info see: <https://github.com/paritytech/substrate/pull/13798> --> pallets/governance/src/lib.rs:310:26 | 310 | #[pallet::weight(0)] | ^ | = note: `-D deprecated` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(deprecated)]`
pub fn add_emission_proposal(
origin: OriginFor<T>,
recycling_percentage: Percent,
treasury_percentage: Percent,
data: Vec<u8>,
) -> DispatchResult {
let proposer = ensure_signed(origin)?;
proposal::add_emission_proposal::<T>(
proposer,
recycling_percentage,
treasury_percentage,
data,
)
}
}

#[pallet::event]
Expand Down Expand Up @@ -425,6 +442,8 @@ pub mod pallet {
InvalidMinWeightControlFee,
/// Invalid minimum staking fee in proposal
InvalidMinStakingFee,
/// Invalid params given to Emission proposal
InvalidEmissionProposalData,
}
}

Expand Down
76 changes: 62 additions & 14 deletions pallets/governance/src/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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::{
Expand All @@ -24,7 +25,7 @@ use substrate_fixed::types::I92F36;

pub type ProposalId = u64;

#[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)]
#[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct Proposal<T: crate::Config> {
pub id: ProposalId,
Expand All @@ -44,6 +45,13 @@ impl<T: crate::Config> Proposal<T> {
matches!(self.status, ProposalStatus::Open { .. })
}

pub fn execution_block(&self) -> Block {
match self.data {
ProposalData::Emission { .. } => self.creation_block + 21_600,
_ => self.expiration_block,
}
}

/// Marks a proposal as accepted and overrides the storage value.
pub fn accept(
mut self,
Expand Down Expand Up @@ -250,11 +258,15 @@ impl<T: crate::Config> GlobalParamsData<T> {
}
}

#[derive(DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)]
#[derive(Clone, DebugNoBound, TypeInfo, Decode, Encode, MaxEncodedLen, PartialEq, Eq)]
#[scale_info(skip_type_params(T))]
pub enum ProposalData<T: crate::Config> {
GlobalParams(GlobalParamsData<T>),
GlobalCustom,
Emission {
recycling_percentage: Percent,
treasury_percentage: Percent,
},
TransferDaoTreasury {
account: AccountIdOf<T>,
amount: BalanceOf<T>,
Expand All @@ -265,6 +277,7 @@ impl<T: crate::Config> ProposalData<T> {
#[must_use]
pub fn required_stake(&self) -> Percent {
match self {
Self::Emission { .. } => Percent::from_parts(10),
Self::GlobalCustom | Self::TransferDaoTreasury { .. } => Percent::from_parts(50),
Self::GlobalParams { .. } => Percent::from_parts(40),
}
Expand Down Expand Up @@ -312,6 +325,27 @@ pub fn add_dao_treasury_transfer_proposal<T: crate::Config>(
add_proposal::<T>(proposer, data, metadata)
}

pub fn add_emission_proposal<T: crate::Config>(
proposer: AccountIdOf<T>,
recycling_percentage: Percent,
treasury_percentage: Percent,
metadata: Vec<u8>,
) -> DispatchResult {
ensure!(
recycling_percentage
.checked_add(&treasury_percentage)
.is_some(),
crate::Error::<T>::InvalidEmissionProposalData
);

let data = ProposalData::<T>::Emission {
recycling_percentage,
treasury_percentage,
};

add_proposal::<T>(proposer, data, metadata)
}

fn add_proposal<T: crate::Config>(
proposer: AccountIdOf<T>,
data: ProposalData<T>,
Expand Down Expand Up @@ -442,14 +476,38 @@ fn tick_proposal<T: crate::Config>(
*stake_for = stake_for_sum;
*stake_against = stake_against_sum;
}
Proposals::<T>::set(proposal.id, Some(proposal));
Proposals::<T>::set(proposal.id, Some(proposal.clone()));
}

if block_number < proposal.execution_block() {
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::<T>(proposal.data.required_stake());

if total_stake >= minimal_stake_to_execute {
create_unrewarded_proposal::<T>(proposal.id, block_number, votes_for, votes_against);
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 if block_number >= proposal.expiration_block {
create_unrewarded_proposal::<T>(proposal.id, block_number, votes_for, votes_against);
proposal.expire(block_number)
} else {
Ok(())
}
}

fn create_unrewarded_proposal<T: crate::Config>(
proposal_id: u64,
block_number: Block,
votes_for: Vec<(AccountIdOf<T>, BalanceOf<T>)>,
votes_against: Vec<(AccountIdOf<T>, BalanceOf<T>)>,
) {
let mut reward_votes_for = BoundedBTreeMap::new();
for (key, value) in votes_for {
reward_votes_for
Expand All @@ -469,23 +527,13 @@ fn tick_proposal<T: crate::Config>(
}

UnrewardedProposals::<T>::insert(
proposal.id,
proposal_id,
UnrewardedProposal::<T> {
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]
Expand Down
160 changes: 159 additions & 1 deletion pallets/governance/tests/voting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use pallet_governance::{
proposal::{GlobalParamsData, ProposalStatus},
DaoTreasuryAddress, Error, GlobalGovernanceConfig, Proposals,
};
use polkadot_sdk::frame_support::assert_err;
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 test_utils::{
add_balance, get_balance, get_origin, new_test_ext, step_block, to_nano, zero_min_burn,
Expand Down Expand Up @@ -349,6 +350,163 @@ fn creates_treasury_transfer_proposal_and_transfers() {
});
}

#[test]
fn creates_emission_proposal_and_it_runs_after_2_days() {
new_test_ext().execute_with(|| {
zero_min_burn();

let default_proposal_expiration: u64 =
<Test as pallet_governance::Config>::DefaultProposalExpiration::get();

config(1, default_proposal_expiration);

let origin = get_origin(0);
add_balance(0, to_nano(2));
register(0, 0, 0, to_nano(1));
pallet_torus0::TotalStake::<Test>::set(to_nano(10));

assert_ok!(pallet_governance::Pallet::<Test>::add_emission_proposal(
origin.clone(),
Percent::from_parts(20),
Percent::from_parts(20),
vec![b'0'; 64],
));

vote(0, 0, true);

step_block(21_600);

assert_eq!(
Proposals::<Test>::get(0).unwrap().status,
ProposalStatus::Accepted {
block: 21_600,
stake_for: to_nano(1),
stake_against: 0
}
);
});
}

#[test]
fn creates_emission_proposal_and_it_runs_before_expiration() {
new_test_ext().execute_with(|| {
zero_min_burn();

let default_proposal_expiration: u64 =
<Test as pallet_governance::Config>::DefaultProposalExpiration::get();

let min_stake: u128 = <Test as pallet_torus0::Config>::DefaultMinAllowedStake::get();

config(1, default_proposal_expiration);

let origin = get_origin(0);
add_balance(0, to_nano(2));
register(0, 0, 0, to_nano(1) - min_stake);
pallet_torus0::TotalStake::<Test>::set(to_nano(10));

assert_ok!(pallet_governance::Pallet::<Test>::add_emission_proposal(
origin.clone(),
Percent::from_parts(20),
Percent::from_parts(20),
vec![b'0'; 64],
));

vote(0, 0, true);

step_block(21_600);

let mut votes_for = BoundedBTreeSet::new();
votes_for.try_insert(0).unwrap();

assert_eq!(
Proposals::<Test>::get(0).unwrap().status,
ProposalStatus::Open {
votes_for,
votes_against: BoundedBTreeSet::new(),
stake_for: to_nano(1) - min_stake,
stake_against: 0
}
);

stake(0, 0, min_stake);
pallet_torus0::TotalStake::<Test>::set(to_nano(10));

step_block(100);

assert_eq!(
Proposals::<Test>::get(0).unwrap().status,
ProposalStatus::Accepted {
block: 21_700,
stake_for: to_nano(1),
stake_against: 0
}
);
});
}

#[test]
fn creates_emission_proposal_and_it_expires() {
new_test_ext().execute_with(|| {
zero_min_burn();

let default_proposal_expiration: u64 =
<Test as pallet_governance::Config>::DefaultProposalExpiration::get();

let min_stake: u128 = <Test as pallet_torus0::Config>::DefaultMinAllowedStake::get();

config(1, default_proposal_expiration);

let origin = get_origin(0);
add_balance(0, to_nano(2));
register(0, 0, 0, to_nano(1) - min_stake);
pallet_torus0::TotalStake::<Test>::set(to_nano(10));

assert_ok!(pallet_governance::Pallet::<Test>::add_emission_proposal(
origin.clone(),
Percent::from_parts(20),
Percent::from_parts(20),
vec![b'0'; 64],
));

vote(0, 0, true);

step_block(default_proposal_expiration);

assert_eq!(
Proposals::<Test>::get(0).unwrap().status,
ProposalStatus::Expired
);
});
}

#[test]
fn creates_emission_proposal_with_invalid_params_and_it_fails() {
new_test_ext().execute_with(|| {
zero_min_burn();

let default_proposal_expiration: u64 =
<Test as pallet_governance::Config>::DefaultProposalExpiration::get();

let min_stake: u128 = <Test as pallet_torus0::Config>::DefaultMinAllowedStake::get();

config(1, default_proposal_expiration);

let origin = get_origin(0);
add_balance(0, to_nano(2));
register(0, 0, 0, to_nano(1) - min_stake);

assert_err!(
pallet_governance::Pallet::<Test>::add_emission_proposal(
origin.clone(),
Percent::from_parts(51),
Percent::from_parts(50),
vec![b'0'; 64],
),
Error::<Test>::InvalidEmissionProposalData
);
});
}

#[test]
fn rewards_wont_exceed_treasury() {
new_test_ext().execute_with(|| {
Expand Down

0 comments on commit 5946ebe

Please sign in to comment.