Skip to content

Commit

Permalink
Implement market creator incentives (#1057)
Browse files Browse the repository at this point in the history
* Integrate creator_fee in config and market & pool creation

* Make creator_fee decideable by market creator

Also replaces CreatorFee config element by MaxCreatorFee config element. I am against this change but I got outvoted by stakeholders, reasoning can be found at https://hackmd.io/@lZVVinJVS6WI4IpRgmbsLA/H17iRm6Kn

* Finalize adjustable creator fee changes

* Plan helper function

* Partially implement market creator fees

* Implement market fees for swap_exact_amount_in

* Add tests for swap_exact_amount_in fees (WIP)

* Finalize creator fee for swap_exact_amount_in

* Fix bugs

* Finalize fee handling logic

* Use BASET_ASSET constant in tests

* Use const for pool and market id

* Add swap_in tests

* Complete swap_in with creator fee tests

* Use const for default weights

* Use imported Error instead of absolute path

* Implement limit tests when using creator fee

* Implement max price tests when using creator fee

* Complete swap_out with creator fee tests

* Fix no-base<->no-base swap out creato fee test

* Implement limit tests with swap_out when using creator fee

* Implement max price tests for swap_out when using creator fee

* Implement test to verify swap out amount is correct

* Add test that verifies fee boundaries during pool creation

* Add creator_fee migration

* Adjust all tests to new create_market call

* Format

* Update changelog

* Update changelog

* Fix invalid test

* Add fee limit test in prediction markets crate

* Fix swaps benchmarks (invalid market id)

* Update license headers

* Satisfy clippy

* Repair fuzz tests

* Fix swaps fuzz tests (invalid market id)

* Add market parameter test for create_cpmm_market_and_deploy_assets

* Adjust swap benchmarks to use worst-case execution

* Make handle_creator_fees infallible

* Add ED tests for creator fees

* Update copyright
  • Loading branch information
sea212 authored Sep 11, 2023
1 parent 8654030 commit 851c803
Show file tree
Hide file tree
Showing 32 changed files with 2,085 additions and 654 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

21 changes: 21 additions & 0 deletions docs/changelog_for_devs.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ All things about Global Disputes Fix ⚠️ :

### Added

- Add market creator incentives.
- The following dispatchable calls within the prediction markets pallet now
expect a market creator fee denoted as type `Perbill` after the `base_asset`
parameter. The fee is bounded by the pallet's `Config` parameter
`MaxCreatorFee`:
- `create_market`
- `create_cpmm_market_and_deploy_assets`
- The market type now holds an additional field `creator_fee` using the type
`Perbill` after the `creation` field.
- The swaps pallet's `Config` parameter `MaxSwapFee` now is a boundary for the
sum of all fees, currently the liqudity provider fee and the market creator
fee. It is checked during the execution of the public function
`create_pool`.
- Fees are always transferred from the trader's account to the market
creator's account either before or after the trade. The base asset is always
preferred to pay fees. If the trade does not include the base asset, the
pallet will try to convert the outcome asset to the base asset by executing
a swap.
- A new event `MarketCreatorFeesPaid` is emitted by the swaps pallet after
successful payment of fees to the market creator. It contains the fields
`\[payer, payee, amount, asset\]`.
- ⚠️ Add court production implementation ([#976]). Dispatchable calls are:
- `join_court` - Join the court with a stake to become a juror in order to get
the stake-weighted chance to be selected for decision making.
Expand Down
1 change: 1 addition & 0 deletions primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ orml-traits = { workspace = true }
parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] }
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"], optional = true }
sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }

Expand Down
2 changes: 2 additions & 0 deletions primitives/src/constants/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::{
};
use frame_support::{parameter_types, traits::LockIdentifier, PalletId};
use orml_traits::parameter_type_with_key;
use sp_arithmetic::Perbill;

// Authorized
parameter_types! {
Expand Down Expand Up @@ -75,6 +76,7 @@ parameter_types! {
pub const DisputeBond: Balance = 5 * BASE;
pub const DisputeFactor: Balance = 2 * BASE;
pub const MaxCategories: u16 = 10;
pub const MaxCreatorFee: Perbill = Perbill::from_percent(1);
pub const MaxDisputeDuration: BlockNumber = 50;
pub const MaxDisputes: u16 = 6;
pub const MaxEditReasonLen: u32 = 1024;
Expand Down
11 changes: 6 additions & 5 deletions primitives/src/market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ use alloc::vec::Vec;
use core::ops::{Range, RangeInclusive};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::per_things::Perbill;
use sp_runtime::RuntimeDebug;

/// Types
///
/// * `AI`: Account id
/// * `BA`: Balance type for bonds
/// * `BA`: Balance type
/// * `BN`: Block number
/// * `M`: Moment (time moment)
/// * `A`: Asset
Expand All @@ -38,8 +39,8 @@ pub struct Market<AI, BA, BN, M, A> {
pub creator: AI,
/// Creation type.
pub creation: MarketCreation,
/// The fee the creator gets from each winning share.
pub creator_fee: u8,
/// A fee that is charged each trade and given to the market creator.
pub creator_fee: Perbill,
/// Oracle that reports the outcome of this market.
pub oracle: AI,
/// Metadata for the market, usually a content address of IPFS
Expand Down Expand Up @@ -150,7 +151,7 @@ where
AI::max_encoded_len()
.saturating_add(A::max_encoded_len())
.saturating_add(MarketCreation::max_encoded_len())
.saturating_add(u8::max_encoded_len())
.saturating_add(Perbill::max_encoded_len())
.saturating_add(AI::max_encoded_len())
// We assume that at max. a 512 bit hash function is used
.saturating_add(u8::max_encoded_len().saturating_mul(68))
Expand Down Expand Up @@ -378,7 +379,7 @@ mod tests {
base_asset: Asset::Ztg,
creator: 1,
creation: MarketCreation::Permissionless,
creator_fee: 2,
creator_fee: Default::default(),
oracle: 3,
metadata: vec![4u8; 5],
market_type, // : MarketType::Categorical(6),
Expand Down
7 changes: 7 additions & 0 deletions primitives/src/traits/swaps.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Copyright 2023 Forecasting Technologies LTD.
// Copyright 2021-2022 Zeitgeist PM LLC.
//
// This file is part of Zeitgeist.
Expand Down Expand Up @@ -152,6 +153,8 @@ pub trait Swaps<AccountId> {
/// * `asset_out`: Asset leaving the pool.
/// * `min_asset_amount_out`: Minimum asset amount that can leave the pool.
/// * `max_price`: Market price must be equal or less than the provided value.
/// * `handle_fees`: Whether additional fees are handled or not (sets LP fee to 0)
#[allow(clippy::too_many_arguments)]
fn swap_exact_amount_in(
who: AccountId,
pool_id: PoolId,
Expand All @@ -160,6 +163,7 @@ pub trait Swaps<AccountId> {
asset_out: Asset<Self::MarketId>,
min_asset_amount_out: Option<Self::Balance>,
max_price: Option<Self::Balance>,
handle_fees: bool,
) -> Result<Weight, DispatchError>;

/// Swap - Exact amount out
Expand All @@ -175,6 +179,8 @@ pub trait Swaps<AccountId> {
/// * `asset_out`: Asset leaving the pool.
/// * `asset_amount_out`: Amount that will be transferred from the pool to the provider.
/// * `max_price`: Market price must be equal or less than the provided value.
/// * `handle_fees`: Whether additional fees are handled or not (sets LP fee to 0)
#[allow(clippy::too_many_arguments)]
fn swap_exact_amount_out(
who: AccountId,
pool_id: PoolId,
Expand All @@ -183,5 +189,6 @@ pub trait Swaps<AccountId> {
asset_out: Asset<Self::MarketId>,
asset_amount_out: Self::Balance,
max_price: Option<Self::Balance>,
handle_fees: bool,
) -> Result<Weight, DispatchError>;
}
2 changes: 2 additions & 0 deletions runtime/battery-station/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ parameter_types! {
pub const DisputeBond: Balance = 25 * BASE;
/// Maximum Categories a prediciton market can have (excluding base asset).
pub const MaxCategories: u16 = MAX_CATEGORIES;
/// Max creator fee, bounds the fraction per trade volume that is moved to the market creator.
pub const MaxCreatorFee: Perbill = Perbill::from_percent(1);
/// Maximum block period for a dispute.
pub const MaxDisputeDuration: BlockNumber = MAX_DISPUTE_DURATION;
/// Maximum number of disputes.
Expand Down
9 changes: 5 additions & 4 deletions runtime/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,17 @@ macro_rules! decl_common_types {
orml_asset_registry::Migration<Runtime>,
orml_unknown_tokens::Migration<Runtime>,
pallet_xcm::migration::v1::MigrateToV1<Runtime>,
// IMPORTANT that AddDisputeBond comes before MoveDataToSimpleDisputes!!!
zrml_prediction_markets::migrations::AddDisputeBond<Runtime>,
// IMPORTANT that AddDisputeBondAndConvertCreatorFee comes before MoveDataToSimpleDisputes!!!
zrml_prediction_markets::migrations::AddDisputeBondAndConvertCreatorFee<Runtime>,
zrml_prediction_markets::migrations::MoveDataToSimpleDisputes<Runtime>,
zrml_global_disputes::migrations::ModifyGlobalDisputesStructures<Runtime>,
);

#[cfg(not(feature = "parachain"))]
type Migrations = (
pallet_grandpa::migrations::CleanupSetIdSessionMap<Runtime>,
// IMPORTANT that AddDisputeBond comes before MoveDataToSimpleDisputes!!!
zrml_prediction_markets::migrations::AddDisputeBond<Runtime>,
// IMPORTANT that AddDisputeBondAndConvertCreatorFee comes before MoveDataToSimpleDisputes!!!
zrml_prediction_markets::migrations::AddDisputeBondAndConvertCreatorFee<Runtime>,
zrml_prediction_markets::migrations::MoveDataToSimpleDisputes<Runtime>,
zrml_global_disputes::migrations::ModifyGlobalDisputesStructures<Runtime>,
);
Expand Down Expand Up @@ -1126,6 +1126,7 @@ macro_rules! impl_config_traits {
type LiquidityMining = NoopLiquidityMining;
// type LiquidityMining = LiquidityMining;
type MaxCategories = MaxCategories;
type MaxCreatorFee = MaxCreatorFee;
type MaxDisputes = MaxDisputes;
type MaxMarketLifetime = MaxMarketLifetime;
type MinDisputeDuration = MinDisputeDuration;
Expand Down
2 changes: 2 additions & 0 deletions runtime/zeitgeist/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ parameter_types! {
pub const DisputeBond: Balance = 2_000 * BASE;
/// Maximum Categories a prediciton market can have (excluding base asset).
pub const MaxCategories: u16 = MAX_CATEGORIES;
/// Max creator fee, bounds the fraction per trade volume that is moved to the market creator.
pub const MaxCreatorFee: Perbill = Perbill::from_percent(1);
/// Maximum block period for a dispute.
pub const MaxDisputeDuration: BlockNumber = MAX_DISPUTE_DURATION;
/// Maximum number of disputes.
Expand Down
2 changes: 1 addition & 1 deletion zrml/authorized/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ where
zeitgeist_primitives::types::Market {
base_asset: Asset::Ztg,
creation: zeitgeist_primitives::types::MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: sp_runtime::Perbill::zero(),
creator: T::PalletId::get().into_account_truncating(),
market_type: zeitgeist_primitives::types::MarketType::Scalar(0..=100),
dispute_mechanism: zeitgeist_primitives::types::MarketDisputeMechanism::Authorized,
Expand Down
2 changes: 1 addition & 1 deletion zrml/court/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ where
Market {
base_asset: Asset::Ztg,
creation: MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: sp_runtime::Perbill::zero(),
creator: account("creator", 0, 0),
market_type: MarketType::Scalar(0..=100),
dispute_mechanism: MarketDisputeMechanism::Court,
Expand Down
2 changes: 1 addition & 1 deletion zrml/court/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const ORACLE_REPORT: OutcomeReport = OutcomeReport::Scalar(u128::MAX);
const DEFAULT_MARKET: MarketOf<Runtime> = Market {
base_asset: Asset::Ztg,
creation: MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: sp_runtime::Perbill::zero(),
creator: 0,
market_type: MarketType::Scalar(0..=100),
dispute_mechanism: MarketDisputeMechanism::Court,
Expand Down
2 changes: 1 addition & 1 deletion zrml/global-disputes/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ where
zeitgeist_primitives::types::Market {
base_asset: zeitgeist_primitives::types::Asset::Ztg,
creation: zeitgeist_primitives::types::MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: sp_runtime::Perbill::zero(),
creator: T::GlobalDisputesPalletId::get().into_account_truncating(),
market_type: zeitgeist_primitives::types::MarketType::Scalar(0..=u128::MAX),
dispute_mechanism: zeitgeist_primitives::types::MarketDisputeMechanism::SimpleDisputes,
Expand Down
2 changes: 1 addition & 1 deletion zrml/liquidity-mining/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ fn create_default_market(market_id: u128, period: Range<u64>) {
Market {
base_asset: Asset::Ztg,
creation: MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: sp_runtime::Perbill::zero(),
creator: 0,
market_type: MarketType::Categorical(0),
dispute_mechanism: MarketDisputeMechanism::SimpleDisputes,
Expand Down
4 changes: 2 additions & 2 deletions zrml/market-commons/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
MarketCounter, Markets,
};
use frame_support::{assert_err, assert_noop, assert_ok};
use sp_runtime::DispatchError;
use sp_runtime::{DispatchError, Perbill};
use zeitgeist_primitives::{
traits::MarketCommonsPalletApi,
types::{
Expand All @@ -36,7 +36,7 @@ use zeitgeist_primitives::{
const MARKET_DUMMY: Market<AccountIdTest, Balance, BlockNumber, Moment, Asset<MarketId>> = Market {
base_asset: Asset::Ztg,
creation: MarketCreation::Permissionless,
creator_fee: 0,
creator_fee: Perbill::zero(),
creator: 0,
market_type: MarketType::Scalar(0..=100),
dispute_mechanism: MarketDisputeMechanism::Authorized,
Expand Down
1 change: 1 addition & 0 deletions zrml/prediction-markets/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test = false
arbitrary = { workspace = true, features = ["derive"] }
frame-support = { workspace = true, features = ["default"] }
libfuzzer-sys = { workspace = true }
sp-arithmetic = { workspace = true, features = ["default"] }
zeitgeist-primitives = { workspace = true, features = ["arbitrary", "mock", "default"] }
zrml-prediction-markets = { workspace = true, features = ["mock", "default"] }
zrml-simple-disputes = { workspace = true, features = ["default"] }
Expand Down
15 changes: 12 additions & 3 deletions zrml/prediction-markets/fuzz/pm_full_workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ use arbitrary::Arbitrary;
use core::ops::{Range, RangeInclusive};
use frame_support::traits::Hooks;
use libfuzzer_sys::fuzz_target;
use zeitgeist_primitives::types::{
Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketType, MultiHash,
OutcomeReport, ScoringRule,
use sp_arithmetic::Perbill;
use zeitgeist_primitives::{
constants::mock::MaxCreatorFee,
types::{
Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketType,
MultiHash, OutcomeReport, ScoringRule,
},
};
use zrml_prediction_markets::mock::{ExtBuilder, PredictionMarkets, RuntimeOrigin, System};

Expand All @@ -39,9 +43,13 @@ fuzz_target!(|data: Data| {
oracle_duration: 1_u32.into(),
dispute_duration: 3_u32.into(),
};
let max_parts_per_bill = MaxCreatorFee::get().deconstruct();
let bounded_parts = data.create_scalar_market_fee % max_parts_per_bill as u128;
let fee = Perbill::from_parts(bounded_parts.try_into().unwrap());
let _ = PredictionMarkets::create_market(
RuntimeOrigin::signed(data.create_scalar_market_origin.into()),
Asset::Ztg,
fee,
data.create_scalar_market_oracle.into(),
MarketPeriod::Block(data.create_scalar_market_period),
deadlines,
Expand Down Expand Up @@ -95,6 +103,7 @@ fuzz_target!(|data: Data| {
struct Data {
create_scalar_market_origin: u8,
create_scalar_market_oracle: u8,
create_scalar_market_fee: u128,
create_scalar_market_period: Range<u64>,
create_scalar_market_metadata: MultiHash,
create_scalar_market_creation: u8,
Expand Down
9 changes: 8 additions & 1 deletion zrml/prediction-markets/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ use frame_support::{
};
use frame_system::RawOrigin;
use orml_traits::MultiCurrency;
use sp_runtime::traits::{One, SaturatedConversion, Saturating, Zero};
use sp_runtime::{
traits::{One, SaturatedConversion, Saturating, Zero},
Perbill,
};
use zeitgeist_primitives::{
constants::mock::{MaxSwapFee, MinWeight, BASE, MILLISECS_PER_BLOCK},
traits::{DisputeApi, Swaps},
Expand Down Expand Up @@ -89,11 +92,13 @@ fn create_market_common<T: Config + pallet_timestamp::Config>(
pallet_timestamp::Pallet::<T>::set_timestamp(0u32.into());
let range_start: MomentOf<T> = 100_000u64.saturated_into();
let range_end: MomentOf<T> = 1_000_000u64.saturated_into();
let creator_fee: Perbill = Perbill::zero();
let period = period.unwrap_or(MarketPeriod::Timestamp(range_start..range_end));
let (caller, oracle, deadlines, metadata, creation) =
create_market_common_parameters::<T>(permission)?;
Call::<T>::create_market {
base_asset: Asset::Ztg,
creator_fee,
oracle,
period,
deadlines,
Expand Down Expand Up @@ -625,6 +630,7 @@ benchmarks! {
}: _(
RawOrigin::Signed(caller),
Asset::Ztg,
Perbill::zero(),
oracle,
period,
deadlines,
Expand All @@ -648,6 +654,7 @@ benchmarks! {
create_market_common_parameters::<T>(MarketCreation::Advised)?;
Call::<T>::create_market {
base_asset: Asset::Ztg,
creator_fee: Perbill::zero(),
oracle: oracle.clone(),
period: period.clone(),
deadlines,
Expand Down
Loading

0 comments on commit 851c803

Please sign in to comment.