Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic BurnParameters for Kusama Treasury #511

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Kusama Treasury: remove funding to the Kappa Sigma Mu Society and disable burn ([polkadot-fellows/runtimes#507](https://github.com/polkadot-fellows/runtimes/pull/507))
- Kusama Treasury: allow burn parameters to be set via OpenGov ([polkadot-fellows/runtimes#511](https://github.com/polkadot-fellows/runtimes/pull/511))
- Remove Snowbridge create agent and channel extrinsics. ([polkadot-fellows/runtimes#506](https://github.com/polkadot-fellows/runtimes/pull/506))

#### From [#490](https://github.com/polkadot-fellows/runtimes/pull/490)
Expand Down
61 changes: 57 additions & 4 deletions relay/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extern crate alloc;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
dynamic_params::{dynamic_pallet_params, dynamic_params},
traits::EnsureOriginWithArg,
traits::{EnsureOrigin, EnsureOriginWithArg},
weights::constants::{WEIGHT_PROOF_SIZE_PER_KB, WEIGHT_REF_TIME_PER_MICROS},
};
use kusama_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD;
Expand Down Expand Up @@ -641,6 +641,15 @@ impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
type Score = sp_npos_elections::VoteWeight;
}

#[derive(Default, MaxEncodedLen, Encode, Decode, TypeInfo, Clone, Eq, PartialEq, Debug)]
pub struct BurnDestinationAccount(pub Option<AccountId>);

impl BurnDestinationAccount {
pub fn is_set(&self) -> bool {
self.0.is_some()
}
}

/// Dynamic params that can be adjusted at runtime.
#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
pub mod dynamic_params {
Expand Down Expand Up @@ -678,6 +687,17 @@ pub mod dynamic_params {
#[codec(index = 4)]
pub static UseAuctionSlots: bool = true;
}

/// Parameters used by `pallet-treasury` to handle the burn process.
#[dynamic_pallet_params]
#[codec(index = 1)]
pub mod treasury {
#[codec(index = 0)]
pub static BurnPortion: Permill = Permill::from_percent(0);

#[codec(index = 1)]
pub static BurnDestination: BurnDestinationAccount = Default::default();
pandres95 marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(feature = "runtime-benchmarks")]
Expand All @@ -703,6 +723,8 @@ impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for DynamicParamet

match key {
Inflation(_) => frame_system::ensure_root(origin.clone()),
Treasury(_) =>
EitherOf::<EnsureRoot<AccountId>, GeneralAdmin>::ensure_origin(origin.clone()),
}
.map_err(|_| origin)
}
Expand Down Expand Up @@ -828,7 +850,6 @@ parameter_types! {
pub const ProposalBondMinimum: Balance = 2000 * CENTS;
pub const ProposalBondMaximum: Balance = GRAND;
pub const SpendPeriod: BlockNumber = 6 * DAYS;
pub const Burn: Permill = Permill::zero();
pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
pub const PayoutSpendPeriod: BlockNumber = 30 * DAYS;
// The asset's interior location for the paying account. This is the Treasury
Expand All @@ -845,14 +866,46 @@ parameter_types! {
pub const MaxPeerInHeartbeats: u32 = 10_000;
}

use frame_support::traits::{Currency, OnUnbalanced};

pub type BalancesNegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
pub struct TreasuryBurnHandler;

impl OnUnbalanced<BalancesNegativeImbalance> for TreasuryBurnHandler {
fn on_nonzero_unbalanced(amount: BalancesNegativeImbalance) {
let destination = dynamic_params::treasury::BurnDestination::get();

if let BurnDestinationAccount(Some(account)) = destination {
// Must resolve into existing but better to be safe.
Balances::resolve_creating(&account, amount);
} else {
// If no account to destinate the funds to, just drop the
// imbalance.
<() as OnUnbalanced<_>>::on_nonzero_unbalanced(amount)
}
}
}

impl Get<Permill> for TreasuryBurnHandler {
fn get() -> Permill {
let destination = dynamic_params::treasury::BurnDestination::get();

if destination.is_set() {
dynamic_params::treasury::BurnPortion::get()
} else {
Permill::zero()
}
}
}

impl pallet_treasury::Config for Runtime {
type PalletId = TreasuryPalletId;
type Currency = Balances;
type RejectOrigin = EitherOfDiverse<EnsureRoot<AccountId>, Treasurer>;
type RuntimeEvent = RuntimeEvent;
type SpendPeriod = SpendPeriod;
type Burn = Burn;
type BurnDestination = ();
type Burn = TreasuryBurnHandler;
type BurnDestination = TreasuryBurnHandler;
type MaxApprovals = MaxApprovals;
type WeightInfo = weights::pallet_treasury::WeightInfo<Runtime>;
type SpendFunds = Bounties;
Expand Down
166 changes: 166 additions & 0 deletions relay/kusama/tests/treasury_burn_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

//! TreasuryBurnHandler helper structure tests.
//!
//! Note: These tests emulate the effects of burning some amount on `pallet_treasury` via
//! [`OnUnbalanced`], not the behaviour itself.

use frame_support::{
parameter_types,
traits::{
tokens::fungible::{Inspect, Mutate},
Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReasons,
},
};
use kusama_runtime_constants::currency::UNITS;
use polkadot_primitives::{AccountId, Balance};
use sp_arithmetic::Permill;
use staging_kusama_runtime::{
dynamic_params::treasury::{self, BurnDestination, BurnPortion},
Balances, BurnDestinationAccount, Parameters, RuntimeOrigin, RuntimeParameters, Treasury,
TreasuryBurnHandler,
};

parameter_types! {
TreasuryAccount: AccountId = Treasury::account_id();
}

const BURN_DESTINATION_ACCOUNT: AccountId = AccountId::new([1u8; 32]);

const TREASURY_AMOUNT: Balance = 10 * UNITS;
const SURPLUS: Balance = UNITS;

fn test(pre: impl FnOnce(), test: impl FnOnce(Balance)) {
sp_io::TestExternalities::default().execute_with(|| {
pre();

Balances::set_balance(&TreasuryAccount::get(), TREASURY_AMOUNT);

let amount_to_handle = TreasuryBurnHandler::get() * SURPLUS;
let burn = <Balances as Currency<_>>::withdraw(
&TreasuryAccount::get(),
SURPLUS,
WithdrawReasons::RESERVE,
ExistenceRequirement::KeepAlive,
)
.expect("withdrawing of `burn` is within balance limits; qed");

// Withdrawn surplus to burn it.
assert_eq!(Balances::balance(&TreasuryAccount::get()), TREASURY_AMOUNT - SURPLUS);

let (credit, burn) = burn.split(amount_to_handle);

// Burn amount that's not to handle.
<() as OnUnbalanced<_>>::on_unbalanced(burn);

assert_eq!(Balances::total_issuance(), TREASURY_AMOUNT - (SURPLUS - amount_to_handle));

// Let's handle the `credit`
TreasuryBurnHandler::on_unbalanced(credit);

test(amount_to_handle);

// Only the amount to handle was transferred to the burn destination account
// let burn_destination_account = BurnDestination::get();
let burn_destination_account = BURN_DESTINATION_ACCOUNT;
let burn_destination_account_balance =
<Balances as Inspect<_>>::total_balance(&burn_destination_account);

assert_eq!(burn_destination_account_balance, amount_to_handle);
})
}

#[test]
fn on_burn_parameters_not_set_does_not_handle_burn() {
test(
|| {},
|amount_to_handle| {
// Amount to burn should be zero by default
assert_eq!(amount_to_handle, 0);
},
)
}

#[test]
fn on_burn_portion_not_set_does_not_handle_burn() {
test(
|| {
Parameters::set_parameter(
RuntimeOrigin::root(),
RuntimeParameters::Treasury(treasury::Parameters::BurnDestination(
BurnDestination,
Some(BurnDestinationAccount(Some(BURN_DESTINATION_ACCOUNT))),
)),
)
.expect("parameters are set accordingly; qed");
},
|amount_to_handle| {
// Amount to burn should be zero by default
assert_eq!(amount_to_handle, 0);
},
)
}

#[test]
fn on_burn_destination_not_set_does_not_handle_burn() {
let one_percent = Permill::from_percent(1);
test(
|| {
Parameters::set_parameter(
RuntimeOrigin::root(),
RuntimeParameters::Treasury(treasury::Parameters::BurnPortion(
BurnPortion,
Some(one_percent),
)),
)
.expect("parameters are set accordingly; qed");
},
|amount_to_handle| {
// Amount to burn should be zero by default
assert_eq!(amount_to_handle, 0);
},
)
}

#[test]
fn on_burn_parameters_set_works() {
let one_percent = Permill::from_percent(1);
test(
|| {
Parameters::set_parameter(
RuntimeOrigin::root(),
RuntimeParameters::Treasury(treasury::Parameters::BurnDestination(
BurnDestination,
Some(BurnDestinationAccount(Some(BURN_DESTINATION_ACCOUNT))),
)),
)
.expect("parameters are set accordingly; qed");
Parameters::set_parameter(
RuntimeOrigin::root(),
RuntimeParameters::Treasury(treasury::Parameters::BurnPortion(
BurnPortion,
Some(one_percent),
)),
)
.expect("parameters are set accordingly; qed");
},
|amount_to_handle| {
// Amount to burn should be zero by default
assert_eq!(amount_to_handle, one_percent * SURPLUS);
},
)
}
Loading