Skip to content

Commit

Permalink
Move certain slashed funds and fees/tips to treasury (#814)
Browse files Browse the repository at this point in the history
* Add `Slash` config value

* Use `CurrencyOf<T>` to call `slash`

* Add `Slash::on_unbalanced` calls where appropriate

* Add test for `reject_market` slashes

* Add test for dispute slashing

* Add `Slash` parameter to runtime

* Simplify treasury mock parameters

* Add `DealWithFees`

* Add test for `DealWithFees`

* Add missing copyright notice

* Fix test

* Fix test formatting

* Move 20% of transaction fees to the treasury

* Parametrize burn & treasury percentage

* Change burn parameters according to discussion

* Change treasury burn to 10% for build-up period

* Fix formatting

* Apply suggestions from code review

Co-authored-by: Chralt <chralt.developer@gmail.com>

* Apply suggestions from code review

Co-authored-by: Chralt <chralt.developer@gmail.com>

* Add `is_zero` checks

* Apply suggestions from code review

Co-authored-by: Chralt <chralt.developer@gmail.com>

* Fix formatting

Co-authored-by: Chralt <chralt.developer@gmail.com>
  • Loading branch information
maltekliemann and Chralt98 committed Oct 6, 2022
1 parent cadca83 commit d058a9e
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 65 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions primitives/src/constants/mock.rs
Expand Up @@ -78,6 +78,12 @@ parameter_types! {
pub const MaxReserves: u32 = 50;
}

// Treasury
parameter_types! {
pub const MaxApprovals: u32 = 1;
pub const TreasuryPalletId: PalletId = PalletId(*b"zge/tsry");
}

// ORML
parameter_types! {
// ORML
Expand Down
4 changes: 3 additions & 1 deletion runtime/battery-station/src/parameters.rs
Expand Up @@ -42,6 +42,8 @@ use zeitgeist_primitives::{constants::*, types::*};
pub(crate) const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
pub(crate) const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2;
pub(crate) const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
pub(crate) const FEES_AND_TIPS_TREASURY_PERCENTAGE: u32 = 100;
pub(crate) const FEES_AND_TIPS_BURN_PERCENTAGE: u32 = 0;

parameter_types! {
// Authorized
Expand Down Expand Up @@ -288,7 +290,7 @@ parameter_types! {

// Treasury
/// Percentage of spare funds (if any) that are burnt per spend period.
pub const Burn: Permill = Permill::from_percent(50);
pub const Burn: Permill = Permill::from_percent(10);
/// The maximum number of approvals that can wait in the spending queue.
pub const MaxApprovals: u32 = 100;
/// Fraction of a proposal's value that should be bonded in order to place the proposal.
Expand Down
67 changes: 66 additions & 1 deletion runtime/common/src/lib.rs
@@ -1,4 +1,5 @@
// Copyright 2021-2022 Zeitgeist PM LLC.
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
//
// This file is part of Zeitgeist.
//
Expand All @@ -14,6 +15,25 @@
//
// You should have received a copy of the GNU General Public License
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (C) 2020-2022 Acala Foundation.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program 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.
//
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]
#![recursion_limit = "256"]
Expand All @@ -25,6 +45,7 @@ pub mod weights;
macro_rules! decl_common_types {
{} => {
use sp_runtime::generic;
use frame_support::traits::{Currency, Imbalance, OnUnbalanced};

pub type Block = generic::Block<Header, UncheckedExtrinsic>;

Expand Down Expand Up @@ -161,6 +182,25 @@ macro_rules! decl_common_types {
}
}

pub struct DealWithFees;

type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
impl OnUnbalanced<NegativeImbalance> for DealWithFees
{
fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
if let Some(mut fees) = fees_then_tips.next() {
if let Some(tips) = fees_then_tips.next() {
tips.merge_into(&mut fees);
}
let mut split = fees.ration(
FEES_AND_TIPS_TREASURY_PERCENTAGE,
FEES_AND_TIPS_BURN_PERCENTAGE,
);
Treasury::on_unbalanced(split.0);
}
}
}

pub mod opaque {
//! Opaque types. These are used by the CLI to instantiate machinery that don't need to know
//! the specifics of the runtime. They can then be made to be agnostic over specific formats
Expand Down Expand Up @@ -750,7 +790,8 @@ macro_rules! impl_config_traits {
impl pallet_transaction_payment::Config for Runtime {
type FeeMultiplierUpdate = SlowAdjustingFeeUpdate<Runtime>;
type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
type OnChargeTransaction =
pallet_transaction_payment::CurrencyAdapter<Balances, DealWithFees>;
type OperationalFeeMultiplier = OperationalFeeMultiplier;
type WeightToFee = IdentityFee<Balance>;
}
Expand Down Expand Up @@ -886,6 +927,7 @@ macro_rules! impl_config_traits {
type ResolveOrigin = EnsureRoot<AccountId>;
type AssetManager = AssetManager;
type SimpleDisputes = SimpleDisputes;
type Slash = Treasury;
type Swaps = Swaps;
type ValidityBond = ValidityBond;
type WeightInfo = zrml_prediction_markets::weights::WeightInfo<Runtime>;
Expand Down Expand Up @@ -1684,6 +1726,29 @@ macro_rules! create_common_tests {
})
}
}

mod deal_with_fees {
use crate::*;

#[test]
fn treasury_receives_correct_amount_of_fees_and_tips() {
let mut t: sp_io::TestExternalities =
frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap().into();
t.execute_with(|| {
let fee_balance = 3 * ExistentialDeposit::get();
let fee_imbalance = Balances::issue(fee_balance);
let tip_balance = 7 * ExistentialDeposit::get();
let tip_imbalance = Balances::issue(tip_balance);
assert_eq!(Balances::free_balance(Treasury::account_id()), 0);
DealWithFees::on_unbalanceds(vec![fee_imbalance, tip_imbalance].into_iter());
assert_eq!(
Balances::free_balance(Treasury::account_id()),
fee_balance + tip_balance,
);
});
}
}
}

}
}
4 changes: 3 additions & 1 deletion runtime/zeitgeist/src/parameters.rs
Expand Up @@ -42,6 +42,8 @@ use zeitgeist_primitives::{constants::*, types::*};
pub(crate) const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
pub(crate) const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2;
pub(crate) const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
pub(crate) const FEES_AND_TIPS_TREASURY_PERCENTAGE: u32 = 100;
pub(crate) const FEES_AND_TIPS_BURN_PERCENTAGE: u32 = 0;

parameter_types! {
// Authorized
Expand Down Expand Up @@ -288,7 +290,7 @@ parameter_types! {

// Treasury
/// Percentage of spare funds (if any) that are burnt per spend period.
pub const Burn: Permill = Permill::from_percent(50);
pub const Burn: Permill = Permill::from_percent(10);
/// The maximum number of approvals that can wait in the spending queue.
pub const MaxApprovals: u32 = 100;
/// Fraction of a proposal's value that should be bonded in order to place the proposal.
Expand Down
4 changes: 3 additions & 1 deletion zrml/prediction-markets/Cargo.toml
Expand Up @@ -21,6 +21,7 @@ orml-tokens = { branch = "2022-06-v0.9.19", git = "https://github.com/zeitgeistp
pallet-balances = { branch = "moonbeam-polkadot-v0.9.19", git = "https://github.com/purestake/substrate", optional = true }
pallet-randomness-collective-flip = { branch = "moonbeam-polkadot-v0.9.19", git = "https://github.com/purestake/substrate", optional = true }
pallet-timestamp = { branch = "moonbeam-polkadot-v0.9.19", default-features = false, git = "https://github.com/purestake/substrate", optional = true }
pallet-treasury = { branch = "moonbeam-polkadot-v0.9.19", git = "https://github.com/purestake/substrate", optional = true }
sp-api = { branch = "moonbeam-polkadot-v0.9.19", git = "https://github.com/purestake/substrate", optional = true }
sp-io = { branch = "moonbeam-polkadot-v0.9.19", git = "https://github.com/purestake/substrate", optional = true }
substrate-fixed = { optional = true, git = "https://github.com/encointer/substrate-fixed" }
Expand All @@ -40,14 +41,15 @@ mock = [
"orml-tokens",
"pallet-balances",
"pallet-randomness-collective-flip",
"pallet-timestamp/std",
"pallet-treasury",
"sp-api",
"sp-io",
"substrate-fixed",
"zeitgeist-primitives/mock",
"zrml-prediction-markets-runtime-api",
"zrml-rikiddo",
"zrml-swaps",
"pallet-timestamp/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
Expand Down
65 changes: 29 additions & 36 deletions zrml/prediction-markets/src/lib.rs
Expand Up @@ -39,7 +39,10 @@ mod pallet {
ensure, log,
pallet_prelude::{ConstU32, StorageMap, StorageValue, ValueQuery},
storage::{with_transaction, TransactionOutcome},
traits::{EnsureOrigin, Get, Hooks, IsType, StorageVersion},
traits::{
Currency, EnsureOrigin, Get, Hooks, Imbalance, IsType, NamedReservableCurrency,
OnUnbalanced, StorageVersion,
},
transactional,
weights::Pays,
Blake2_128Concat, BoundedVec, PalletId, Twox64Concat,
Expand Down Expand Up @@ -69,6 +72,10 @@ mod pallet {
pub(crate) type BalanceOf<T> = <<T as Config>::AssetManager as MultiCurrency<
<T as frame_system::Config>::AccountId,
>>::Balance;
pub(crate) type CurrencyOf<T> =
<<T as Config>::MarketCommons as MarketCommonsPalletApi>::Currency;
pub(crate) type NegativeImbalanceOf<T> =
<CurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;
pub(crate) type TimeFrame = u64;
pub(crate) type MarketIdOf<T> =
<<T as Config>::MarketCommons as MarketCommonsPalletApi>::MarketId;
Expand Down Expand Up @@ -1216,6 +1223,9 @@ mod pallet {
Origin = Self::Origin,
>;

/// Handler for slashed funds.
type Slash: OnUnbalanced<NegativeImbalanceOf<Self>>;

/// Swaps pallet API
type Swaps: Swaps<Self::AccountId, Balance = BalanceOf<Self>, MarketId = MarketIdOf<Self>>;

Expand Down Expand Up @@ -1671,12 +1681,12 @@ mod pallet {
T::AdvisoryBondSlashPercentage::get().mul_floor(T::AdvisoryBond::get());
let advisory_bond_unreserve_amount =
T::AdvisoryBond::get().saturating_sub(advisory_bond_slash_amount);
T::AssetManager::slash_reserved_named(
let (imbalance, _) = CurrencyOf::<T>::slash_reserved_named(
&Self::reserve_id(),
Asset::Ztg,
creator,
advisory_bond_slash_amount,
advisory_bond_slash_amount.saturated_into::<u128>().saturated_into(),
);
T::Slash::on_unbalanced(imbalance);
T::AssetManager::unreserve_named(
&Self::reserve_id(),
Asset::Ztg,
Expand Down Expand Up @@ -1935,10 +1945,9 @@ mod pallet {

let mut correct_reporters: Vec<T::AccountId> = Vec::new();

let mut overall_imbalance = BalanceOf::<T>::zero();

// If the oracle reported right, return the OracleBond, otherwise slash it to
// pay the correct reporters.
let mut overall_imbalance = NegativeImbalanceOf::<T>::zero();
if report.outcome == resolved_outcome {
T::AssetManager::unreserve_named(
&Self::reserve_id(),
Expand All @@ -1947,16 +1956,12 @@ mod pallet {
T::OracleBond::get(),
);
} else {
let excess = T::AssetManager::slash_reserved_named(
let (imbalance, _) = CurrencyOf::<T>::slash_reserved_named(
&Self::reserve_id(),
Asset::Ztg,
&market.creator,
T::OracleBond::get(),
T::OracleBond::get().saturated_into::<u128>().saturated_into(),
);

// negative_imbalance is the actual slash value (excess should be zero)
let negative_imbalance = T::OracleBond::get().saturating_sub(excess);
overall_imbalance = overall_imbalance.saturating_add(negative_imbalance);
overall_imbalance.subsume(imbalance);
}

for (i, dispute) in disputes.iter().enumerate() {
Expand All @@ -1971,42 +1976,30 @@ mod pallet {

correct_reporters.push(dispute.by.clone());
} else {
let excess = T::AssetManager::slash_reserved_named(
let (imbalance, _) = CurrencyOf::<T>::slash_reserved_named(
&Self::reserve_id(),
Asset::Ztg,
&dispute.by,
actual_bond,
actual_bond.saturated_into::<u128>().saturated_into(),
);

// negative_imbalance is the actual slash value (excess should be zero)
let negative_imbalance = actual_bond.saturating_sub(excess);
overall_imbalance =
overall_imbalance.saturating_add(negative_imbalance);
overall_imbalance.subsume(imbalance);
}
}

// Fold all the imbalances into one and reward the correct reporters. The
// number of correct reporters might be zero if the market defaults to the
// report after abandoned dispute. In that case, the rewards remain slashed.
if let Some(reward_per_each) =
overall_imbalance.checked_div(&correct_reporters.len().saturated_into())
if let Some(reward_per_each) = overall_imbalance
.peek()
.checked_div(&correct_reporters.len().saturated_into())
{
for correct_reporter in &correct_reporters {
// *Should* always be equal to `reward_per_each`
let reward = overall_imbalance.min(reward_per_each);
overall_imbalance = overall_imbalance.saturating_sub(reward);

if let Err(err) =
T::AssetManager::deposit(Asset::Ztg, correct_reporter, reward)
{
log::warn!(
"[PredictionMarkets] Cannot deposit to the correct reporter. \
error: {:?}",
err
);
}
let (actual_reward, leftover) =
overall_imbalance.split(reward_per_each);
overall_imbalance = leftover;
CurrencyOf::<T>::resolve_creating(correct_reporter, actual_reward);
}
}
T::Slash::on_unbalanced(overall_imbalance);

resolved_outcome
}
Expand Down
7 changes: 5 additions & 2 deletions zrml/prediction-markets/src/migrations.rs
Expand Up @@ -15,9 +15,11 @@
// You should have received a copy of the GNU General Public License
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>.

#[cfg(feature = "try-runtime")]
use crate::Disputes;
use crate::{
CacheSize, Config, Disputes, MarketIdOf, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock,
MomentOf, Pallet,
CacheSize, Config, MarketIdOf, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, MomentOf,
Pallet,
};
use frame_support::{
dispatch::Weight,
Expand All @@ -36,6 +38,7 @@ use zeitgeist_primitives::types::{
Deadlines, Market, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus,
MarketType, OutcomeReport, Report, ScoringRule,
};
#[cfg(feature = "try-runtime")]
use zrml_market_commons::MarketCommonsPalletApi;

const PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION_FOR_MIGRATE_MARKET_IDS_STORAGE: u16 = 4;
Expand Down

0 comments on commit d058a9e

Please sign in to comment.