Skip to content

Commit

Permalink
feat(emission0): general emission fees (#26)
Browse files Browse the repository at this point in the history
Implement general emission fees for recycling tokens and treasury
account.
  • Loading branch information
saiintbrisson authored Dec 27, 2024
1 parent 623066c commit 0dd60f5
Show file tree
Hide file tree
Showing 16 changed files with 139 additions and 49 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pallets/emission0/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ substrate-fixed.workspace = true
num-traits.workspace = true

pallet-torus0-api.workspace = true
pallet-governance-api.workspace = true

[dev-dependencies]
test-utils.workspace = true
19 changes: 17 additions & 2 deletions pallets/emission0/src/distribute.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use pallet_governance_api::GovernanceApi;
use pallet_torus0_api::Torus0Api;
use polkadot_sdk::{
frame_support::{
Expand All @@ -8,7 +9,7 @@ use polkadot_sdk::{
},
polkadot_sdk_frame::prelude::BlockNumberFor,
sp_core::Get,
sp_runtime::{traits::Saturating, ArithmeticError, DispatchError, Perquintill},
sp_runtime::{traits::Saturating, ArithmeticError, DispatchError, Percent, Perquintill},
sp_std::{
borrow::Cow,
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
Expand Down Expand Up @@ -72,7 +73,10 @@ pub fn get_total_emission_per_block<T: Config>() -> BalanceOf<T> {

let interval = T::HalvingInterval::get();
let halving_count = total_issuance.saturating_div(interval.get());
T::BlockEmission::get() >> halving_count
let emission = T::BlockEmission::get() >> halving_count;

let not_recycled = Percent::one() - crate::EmissionRecyclingPercentage::<T>::get();
not_recycled.mul_floor(emission)
}

#[doc(hidden)]
Expand Down Expand Up @@ -176,10 +180,14 @@ impl<T: Config> ConsensusMemberInput<T> {
member: ConsensusMember<T>,
min_allowed_stake: u128,
) -> ConsensusMemberInput<T> {
let weight_factor = Percent::one() - <T::Torus>::weight_penalty_factor(&agent_id);

let mut total_stake = 0;
let stakes = <T::Torus>::staked_by(&agent_id)
.into_iter()
.map(|(id, stake)| {
let stake = weight_factor.mul_floor(stake);

total_stake = total_stake.saturating_add(stake);
(id, stake)
})
Expand Down Expand Up @@ -247,6 +255,13 @@ impl<T: Config> ConsensusMemberInput<T> {
fn linear_rewards<T: Config>(
mut emission: <T::Currency as Currency<T::AccountId>>::NegativeImbalance,
) -> <T::Currency as Currency<T::AccountId>>::NegativeImbalance {
let treasury_fee = <T::Governance>::treasury_emission_fee();
if !treasury_fee.is_zero() {
let treasury_fee = treasury_fee.mul_floor(emission.peek());
let treasury_fee = emission.extract(treasury_fee);
T::Currency::resolve_creating(&<T::Governance>::dao_treasury_address(), treasury_fee);
}

let inputs = ConsensusMemberInput::<T>::all_members();

let id_to_idx: BTreeMap<_, _> = inputs
Expand Down
11 changes: 11 additions & 0 deletions pallets/emission0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use polkadot_sdk::frame_system;
use polkadot_sdk::frame_system::pallet_prelude::OriginFor;
use polkadot_sdk::polkadot_sdk_frame::prelude::BlockNumberFor;
use polkadot_sdk::polkadot_sdk_frame::{self as frame, traits::Currency};
use polkadot_sdk::sp_runtime::Percent;

#[doc(hidden)]
pub mod distribute;
Expand All @@ -21,6 +22,7 @@ pub mod pallet {
use core::num::NonZeroU128;

use frame::prelude::BlockNumberFor;
use pallet_governance_api::GovernanceApi;
use pallet_torus0_api::Torus0Api;
use polkadot_sdk::sp_std;

Expand All @@ -45,6 +47,10 @@ pub mod pallet {
#[pallet::storage]
pub type MinStakePerWeight<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;

#[pallet::storage]
pub type EmissionRecyclingPercentage<T: Config> =
StorageValue<_, Percent, ValueQuery, T::DefaultEmissionRecyclingPercentage>;

#[pallet::storage]
pub type PendingEmission<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

Expand All @@ -71,13 +77,18 @@ pub mod pallet {
#[pallet::constant]
type DefaultMaxAllowedWeights: Get<u16>;

#[pallet::constant]
type DefaultEmissionRecyclingPercentage: Get<Percent>;

type Currency: Currency<Self::AccountId, Balance = u128> + Send + Sync;

type Torus: Torus0Api<
Self::AccountId,
<Self::Currency as Currency<Self::AccountId>>::Balance,
<Self::Currency as Currency<Self::AccountId>>::NegativeImbalance,
>;

type Governance: GovernanceApi<Self::AccountId>;
}

#[pallet::pallet]
Expand Down
41 changes: 27 additions & 14 deletions pallets/emission0/tests/distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::array::from_fn;

use pallet_emission0::{
distribute::{get_total_emission_per_block, ConsensusMemberInput},
Config, ConsensusMember, ConsensusMembers, PendingEmission, WeightControlDelegation,
Config, ConsensusMember, ConsensusMembers, EmissionRecyclingPercentage, PendingEmission,
WeightControlDelegation,
};
use polkadot_sdk::{
pallet_balances,
Expand All @@ -12,6 +13,7 @@ use polkadot_sdk::{
use substrate_fixed::{traits::ToFixed, types::I96F32};
use test_utils::{
add_balance, add_stake, get_balance,
pallet_governance::TreasuryEmissionFee,
pallet_torus0::{fee::ValidatorFee, Fee, MaxAllowedValidators, MinAllowedStake, StakedBy},
register_empty_agent, step_block, Test,
};
Expand All @@ -23,20 +25,26 @@ fn total_emission_per_block_does_halving() {
let halving_interval = <<Test as Config>::HalvingInterval as Get<u128>>::get();
let max_supply = <<Test as Config>::MaxSupply as Get<u128>>::get();

let recycling_percentage = EmissionRecyclingPercentage::<Test>::get();
let halving_emission = |halving: u128| {
let block_emission = block_emission >> halving;
block_emission - recycling_percentage.mul_ceil(block_emission)
};

let emissions = get_total_emission_per_block::<Test>();
assert_eq!(emissions, block_emission);
assert_eq!(emissions, halving_emission(0));

pallet_balances::TotalIssuance::<Test>::set(halving_interval - 1);
let emissions = get_total_emission_per_block::<Test>();
assert_eq!(emissions, block_emission);
assert_eq!(emissions, halving_emission(0));

pallet_balances::TotalIssuance::<Test>::set(halving_interval);
let emissions = get_total_emission_per_block::<Test>();
assert_eq!(emissions, block_emission >> 1);
assert_eq!(emissions, halving_emission(1));

pallet_balances::TotalIssuance::<Test>::set(halving_interval * 2);
let emissions = get_total_emission_per_block::<Test>();
assert_eq!(emissions, block_emission >> 2);
assert_eq!(emissions, halving_emission(2));

pallet_balances::TotalIssuance::<Test>::set(max_supply);
let emissions = get_total_emission_per_block::<Test>();
Expand All @@ -47,22 +55,23 @@ fn total_emission_per_block_does_halving() {
#[test]
fn pending_emission_accumulates_and_returns_when_network_is_empty() {
test_utils::new_test_ext().execute_with(|| {
assert_eq!(PendingEmission::<Test>::get(), 0);
EmissionRecyclingPercentage::<Test>::set(Percent::zero());

step_block(1);
assert_eq!(PendingEmission::<Test>::get(), 0);

let emissions = get_total_emission_per_block::<Test>();
assert_eq!(PendingEmission::<Test>::get(), emissions);

step_block(1);
assert_eq!(PendingEmission::<Test>::get(), emissions);

let emissions = get_total_emission_per_block::<Test>();
step_block(1);
assert_eq!(PendingEmission::<Test>::get(), emissions * 2);

step_block(98);
let after_treasury_fee = Percent::one() - TreasuryEmissionFee::<Test>::get();
let emissions = after_treasury_fee.mul_floor(emissions * 100);

let emissions = get_total_emission_per_block::<Test>();
assert_eq!(PendingEmission::<Test>::get(), emissions * 100);
step_block(98);
assert_eq!(PendingEmission::<Test>::get(), emissions);
});
}

Expand Down Expand Up @@ -317,6 +326,9 @@ fn deregister_old_agents_and_registers_new() {
#[test]
fn pays_dividends_and_incentives() {
test_utils::new_test_ext().execute_with(|| {
EmissionRecyclingPercentage::<Test>::set(Percent::zero());
TreasuryEmissionFee::<Test>::set(Percent::zero());

let min_allowed_stake = MinAllowedStake::<Test>::get();

let mut member = ConsensusMember::<Test>::default();
Expand Down Expand Up @@ -361,6 +373,9 @@ fn pays_dividends_and_incentives() {
#[test]
fn pays_dividends_to_stakers() {
test_utils::new_test_ext().execute_with(|| {
EmissionRecyclingPercentage::<Test>::set(Percent::zero());
TreasuryEmissionFee::<Test>::set(Percent::zero());

let min_allowed_stake = 1;
MinAllowedStake::<Test>::set(min_allowed_stake);

Expand Down Expand Up @@ -442,5 +457,3 @@ fn pays_dividends_to_stakers() {
assert_eq!(sum, get_total_emission_per_block::<Test>() * 100);
});
}

// TODO: test staking and weight control delegation
7 changes: 5 additions & 2 deletions pallets/governance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ try-runtime = [

[dependencies]
codec = { workspace = true, features = ["derive"] }
pallet-torus0 = { workspace = true }
pallet-emission0 = { workspace = true }
polkadot-sdk = { workspace = true, features = [
"experimental",
"runtime",
"pallet-sudo",
] }
scale-info = { workspace = true, features = ["derive"] }
substrate-fixed = { workspace = true }

pallet-torus0 = { workspace = true }
pallet-emission0 = { workspace = true }

pallet-governance-api.workspace = true
7 changes: 7 additions & 0 deletions pallets/governance/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@ license = "MIT-0"
authors.workspace = true
edition.workspace = true

[features]
default = ["std"]
std = ["polkadot-sdk/std"]
runtime-benchmarks = ["polkadot-sdk/runtime-benchmarks"]
try-runtime = ["polkadot-sdk/try-runtime"]

[dependencies]
polkadot-sdk = { workspace = true, features = ["sp-runtime"] }
6 changes: 5 additions & 1 deletion pallets/governance/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#![no_std]

use polkadot_sdk::sp_runtime::Percent;

pub trait GovernanceApi<AccountId> {
fn get_dao_treasury_address() -> AccountId;
fn dao_treasury_address() -> AccountId;

fn treasury_emission_fee() -> Percent;

fn is_whitelisted(key: &AccountId) -> bool;
}
2 changes: 1 addition & 1 deletion pallets/governance/src/curator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn penalize_agent<T: crate::Config>(
return Err(crate::Error::<T>::AgentNotFound.into());
};

agent.weight_factor = Percent::from_percent(100u8.saturating_sub(percentage));
agent.weight_penalty_factor = Percent::from_percent(100u8.saturating_sub(percentage));

Ok::<(), DispatchError>(())
})?;
Expand Down
22 changes: 22 additions & 0 deletions pallets/governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub use pallet::*;
use polkadot_sdk::frame_support::{
dispatch::DispatchResult,
pallet_prelude::{ValueQuery, *},
sp_runtime::Percent,
traits::Currency,
Identity, PalletId,
};
Expand Down Expand Up @@ -72,6 +73,10 @@ pub mod pallet {
#[pallet::storage]
pub type Curators<T: Config> = StorageMap<_, Identity, AccountIdOf<T>, ()>;

#[pallet::storage]
pub type TreasuryEmissionFee<T: Config> =
StorageValue<_, Percent, ValueQuery, T::DefaultTreasuryEmissionFee>;

#[pallet::config(with_default)]
pub trait Config:
polkadot_sdk::frame_system::Config + pallet_torus0::Config + pallet_emission0::Config
Expand All @@ -91,6 +96,9 @@ pub mod pallet {
#[pallet::constant]
type MaxPenaltyPercentage: Get<u8>;

#[pallet::constant]
type DefaultTreasuryEmissionFee: Get<Percent>;

#[pallet::no_default_bounds]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
Expand Down Expand Up @@ -368,3 +376,17 @@ pub mod pallet {
InvalidMinWeightControlFee,
}
}

impl<T: Config> pallet_governance_api::GovernanceApi<T::AccountId> for Pallet<T> {
fn dao_treasury_address() -> T::AccountId {
DaoTreasuryAddress::<T>::get()
}

fn treasury_emission_fee() -> Percent {
TreasuryEmissionFee::<T>::get()
}

fn is_whitelisted(key: &T::AccountId) -> bool {
whitelist::is_whitelisted::<T>(key)
}
}
1 change: 1 addition & 0 deletions pallets/torus0/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub trait Torus0Api<AccountId, Balance, NegativeImbalance> {
fn max_validators() -> u16;

fn weight_control_fee(who: &AccountId) -> Percent;
fn weight_penalty_factor(who: &AccountId) -> Percent;
fn staking_fee(who: &AccountId) -> Percent;

fn staked_by(staked: &AccountId) -> alloc::vec::Vec<(AccountId, Balance)>;
Expand Down
7 changes: 4 additions & 3 deletions pallets/torus0/src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::AccountIdOf;
use codec::{Decode, Encode, MaxEncodedLen};
use pallet_governance_api::GovernanceApi;
use polkadot_sdk::frame_election_provider_support::Get;
use polkadot_sdk::sp_runtime::DispatchError;
use polkadot_sdk::{
Expand All @@ -16,7 +17,7 @@ pub struct Agent<T: crate::Config> {
pub name: BoundedVec<u8, T::MaxAgentNameLengthConstraint>,
pub url: BoundedVec<u8, T::MaxAgentUrlLengthConstraint>,
pub metadata: BoundedVec<u8, T::MaxAgentMetadataLengthConstraint>,
pub weight_factor: Percent,
pub weight_penalty_factor: Percent,
}

pub fn register<T: crate::Config>(
Expand Down Expand Up @@ -53,7 +54,7 @@ pub fn register<T: crate::Config>(
);

ensure!(
<T as pallet_governance_api::GovernanceApi<T::AccountId>>::is_whitelisted(&agent_key),
<T::Governance>::is_whitelisted(&agent_key),
crate::Error::<T>::AgentKeyNotWhitelisted
);

Expand All @@ -68,7 +69,7 @@ pub fn register<T: crate::Config>(
name: BoundedVec::truncate_from(name),
url: BoundedVec::truncate_from(url),
metadata: BoundedVec::truncate_from(metadata),
weight_factor: Percent::from_percent(100),
weight_penalty_factor: Percent::from_percent(100),
},
);

Expand Down
Loading

0 comments on commit 0dd60f5

Please sign in to comment.