Skip to content

Commit

Permalink
Implement Parimutuel markets (#1138)
Browse files Browse the repository at this point in the history
* wip

* remove market commons currency

* update mocks, fix clippy

* use unambiguous balance type

* Update zrml/prediction-markets/src/lib.rs

* Update zrml/prediction-markets/src/lib.rs

* Update zrml/prediction-markets/src/lib.rs

* fix benchmarks

* fix copyrights

* wip

* restructure outcome asset type

* wip

* wip

* Implement categorical and scalar claims

* outsource into functions

* add refund_pot extrinsic

* prepare tests

* fmt

* fix after merge

* fix mock of neo-swaps

* apply review suggestions

* delete parimutuel

* add tests

* impl bench, add comments

* integrate Parimutuel into runtime

* add parimutuel weights

* remove scalar for parimutuels

* revert outcome type changes in favour of dev speed

* revert outcome type in parimutuels

* add resolution mechanism

* modify copyrights

* taplo fmt

* remove not existent Outcome type

* avoid storage migration

* decrease parimutuel existential deposits

* impl resolution_mechanism

* remove MaxCategories from parimutuel

* Update zrml/parimutuel/Cargo.toml

Co-authored-by: Malte Kliemann <[email protected]>

* rename benchmark module

* remove MaxCategories from parimutuel config

* remove unused block run methods

* move market mock to utils file

* use default market type categorical

* remove unnecessary debug assert

* implement is_redeemable

* update config comments

* add inconsistent state error

* update comment

* update collateral to be base asset

* remove unused scalar market storage item

* use do function style

* order config trait

* change copyright

* update copyrights

* impl market assets contains fn

* improve comments

* reduce indentation

* correct error

* fmt

* delete review jerk comment

* use bmul_floor and bdiv_floor

* add not categorical error

* remove trailing commas

* use test_case for parimutuel

* rename refund_pot to claim_refunds

* Update zrml/parimutuel/README.md

Co-authored-by: Malte Kliemann <[email protected]>

* use test cases for market status

* add test cases for invalid scoring rule

* improve test readibility

* improve test cases

* add docs

* add invalid scoring rule tests

* add redeem shares test

* extend to non-trivial winner rewarding

* add copyright

* revert unsupported currency

* Update zrml/prediction-markets/src/tests.rs

Co-authored-by: Harald Heckmann <[email protected]>

* remove import

* use log

* fmt

* Update zrml/prediction-markets/src/tests.rs

Co-authored-by: Harald Heckmann <[email protected]>

* improve event readability

* rename impl_distribute to do_distribute

* Update zrml/parimutuel/src/lib.rs

Co-authored-by: Harald Heckmann <[email protected]>

* use logs

* use log for optimized builds

* separate tests

* outsource shared code

* outsource get_winning_asset

* add log

* use withdraw instead of slash

* use withdraw instead of slash

* Update runtime/battery-station/src/parameters.rs

Co-authored-by: Harald Heckmann <[email protected]>

* Update runtime/zeitgeist/src/parameters.rs

Co-authored-by: Harald Heckmann <[email protected]>

* use creator parameter in mock

---------

Co-authored-by: Malte Kliemann <[email protected]>
Co-authored-by: Harald Heckmann <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2023
1 parent 4e25695 commit 52a3063
Show file tree
Hide file tree
Showing 42 changed files with 2,218 additions and 107 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ default-members = [
"zrml/market-commons",
"zrml/neo-swaps",
"zrml/orderbook-v1",
"zrml/parimutuel",
"zrml/prediction-markets",
"zrml/prediction-markets/runtime-api",
"zrml/rikiddo",
Expand All @@ -35,6 +36,7 @@ members = [
"zrml/neo-swaps",
"zrml/orderbook-v1",
"zrml/orderbook-v1/fuzz",
"zrml/parimutuel",
"zrml/prediction-markets",
"zrml/prediction-markets/fuzz",
"zrml/prediction-markets/runtime-api",
Expand Down Expand Up @@ -233,6 +235,7 @@ zrml-liquidity-mining = { path = "zrml/liquidity-mining", default-features = fal
zrml-market-commons = { path = "zrml/market-commons", default-features = false }
zrml-neo-swaps = { path = "zrml/neo-swaps", default-features = false }
zrml-orderbook-v1 = { path = "zrml/orderbook-v1", default-features = false }
zrml-parimutuel = { path = "zrml/parimutuel", default-features = false }
zrml-prediction-markets = { path = "zrml/prediction-markets", default-features = false }
zrml-prediction-markets-runtime-api = { path = "zrml/prediction-markets/runtime-api", default-features = false }
zrml-rikiddo = { path = "zrml/rikiddo", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ _anything_.
- [orderbook-v1](./zrml/orderbook-v1) - A naive orderbook implementation that's
only part of Zeitgeist's PoC. Will be replaced by a v2 orderbook that uses
0x-style hybrid on-chain and off-chain trading.
- [parimutuel](./zrml/parimutuel) - A straightforward parimutuel market maker
for categorical markets.
- [prediction-markets](./zrml/prediction-markets) - The core implementation of
the prediction market logic for creating and resolving markets.
- [simple-disputes](./zrml-simple-disputes) - Simple disputes selects the last
Expand Down
21 changes: 21 additions & 0 deletions docs/changelog_for_devs.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ APIs/RPC interface.
## v0.4.2

[#1148]: https://github.com/zeitgeistpm/zeitgeist/pull/1148
[#1138]: https://github.com/zeitgeistpm/zeitgeist/pull/1138

### Added

- Implement parimutuel market ([#1138]) maker to allow markets without liquidity
provision. The new pallet has the following dispatchables:

- `buy`: Buy outcome tokens.
- `claim_rewards`: Claim the winner outcome tokens.
- `claim_refunds`: Claim the refunds in case there was no winner.

The new pallet has the following events:

- `OutcomeBought { market_id, buyer, asset, amount_minus_fees, fees }`:
Informant bought a position.
- `RewardsClaimed { market_id, asset, balance, actual_payoff, sender }`:
Informant claimed rewards.
- `RefundsClaimed { market_id, asset, refunded_balance, sender }`: Informant
claimed refunds.

For details, please refer to the `README.md` and the in-file documentation.

### Changed

Expand Down
3 changes: 2 additions & 1 deletion primitives/src/asset.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 Forecasting Technologies LTD.
// Copyright 2022-2023 Forecasting Technologies LTD.
// Copyright 2021-2022 Zeitgeist PM LLC.
//
// This file is part of Zeitgeist.
Expand Down Expand Up @@ -50,6 +50,7 @@ pub enum Asset<MI: MaxEncodedLen> {
#[default]
Ztg,
ForeignAsset(u32),
ParimutuelShare(MI, CategoryIndex),
}

/// In a scalar market, users can either choose a `Long` position,
Expand Down
3 changes: 3 additions & 0 deletions primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ pub const SWAPS_PALLET_ID: PalletId = PalletId(*b"zge/swap");
// Orderbook
pub const ORDERBOOK_PALLET_ID: PalletId = PalletId(*b"zge/ordb");

// Parimutuel
pub const PARIMUTUEL_PALLET_ID: PalletId = PalletId(*b"zge/prmt");

// Treasury
/// Pallet identifier, used to derive treasury account
pub const TREASURY_PALLET_ID: PalletId = PalletId(*b"zge/tsry");
6 changes: 6 additions & 0 deletions primitives/src/constants/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ parameter_types! {
pub const OrderbookPalletId: PalletId = PalletId(*b"zge/ordb");
}

// Parimutuel parameters
parameter_types! {
pub const ParimutuelPalletId: PalletId = PalletId(*b"zge/prmt");
pub const MinBetSize: Balance = BASE;
}

// Shared within tests
// Balance
parameter_types! {
Expand Down
21 changes: 21 additions & 0 deletions primitives/src/market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ pub struct Market<AI, BA, BN, M, A> {
pub bonds: MarketBonds<AI, BA>,
}

impl<AI, BA, BN, M, A> Market<AI, BA, BN, M, A> {
pub fn resolution_mechanism(&self) -> ResolutionMechanism {
match self.scoring_rule {
ScoringRule::CPMM
| ScoringRule::Lmsr
| ScoringRule::Orderbook
| ScoringRule::RikiddoSigmoidFeeMarketEma => ResolutionMechanism::RedeemTokens,
ScoringRule::Parimutuel => ResolutionMechanism::Noop,
}
}

pub fn is_redeemable(&self) -> bool {
matches!(self.resolution_mechanism(), ResolutionMechanism::RedeemTokens)
}
}

/// Tracks the status of a bond.
#[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct Bond<AI, BA> {
Expand Down Expand Up @@ -303,6 +319,11 @@ pub struct AuthorityReport<BlockNumber> {
pub outcome: OutcomeReport,
}

pub enum ResolutionMechanism {
RedeemTokens,
Noop,
}

/// Contains a market id and the market period.
///
/// * `BN`: Block Number
Expand Down
24 changes: 24 additions & 0 deletions primitives/src/math/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,24 @@ pub fn bmul(a: u128, b: u128) -> Result<u128, DispatchError> {
c1.check_div_rslt(&BASE)
}

pub fn bmul_floor(a: u128, b: u128) -> Result<u128, DispatchError> {
// checked_mul already rounds down
let c0 = a.check_mul_rslt(&b)?;
c0.check_div_rslt(&BASE)
}

pub fn bdiv(a: u128, b: u128) -> Result<u128, DispatchError> {
let c0 = a.check_mul_rslt(&BASE)?;
let c1 = c0.check_add_rslt(&b.check_div_rslt(&2)?)?;
c1.check_div_rslt(&b)
}

pub fn bdiv_floor(a: u128, b: u128) -> Result<u128, DispatchError> {
let c0 = a.check_mul_rslt(&BASE)?;
// checked_div already rounds down
c0.check_div_rslt(&b)
}

pub fn bpowi(a: u128, n: u128) -> Result<u128, DispatchError> {
let mut z = if n % 2 != 0 { a } else { BASE };

Expand Down Expand Up @@ -365,6 +377,18 @@ mod tests {
};
}

#[test]
fn bmul_rounding_behaviours() {
assert_eq!(bmul(3u128, 33_333_333_333u128).unwrap(), 10u128);
assert_eq!(bmul_floor(3u128, 33_333_333_333u128).unwrap(), 9u128);
}

#[test]
fn bdiv_rounding_behaviors() {
assert_eq!(bdiv(14u128, 3u128).unwrap(), 46_666_666_667u128);
assert_eq!(bdiv_floor(14u128, 3u128).unwrap(), 46_666_666_666u128);
}

#[test]
fn bdiv_has_minimum_set_of_correct_values() {
create_tests!(
Expand Down
1 change: 1 addition & 0 deletions primitives/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ pub enum ScoringRule {
RikiddoSigmoidFeeMarketEma,
Lmsr,
Orderbook,
Parimutuel,
}
2 changes: 2 additions & 0 deletions primitives/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
mod complete_set_operations_api;
mod deploy_pool_api;
mod dispute_api;
mod distribute_fees;
mod market_commons_pallet_api;
mod market_id;
mod swaps;
Expand All @@ -27,6 +28,7 @@ mod zeitgeist_multi_reservable_currency;
pub use complete_set_operations_api::CompleteSetOperationsApi;
pub use deploy_pool_api::DeployPoolApi;
pub use dispute_api::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi};
pub use distribute_fees::DistributeFees;
pub use market_commons_pallet_api::MarketCommonsPalletApi;
pub use market_id::MarketId;
pub use swaps::Swaps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait DistributeFees {
fn distribute(
market_id: Self::MarketId,
asset: Self::Asset,
account: Self::AccountId,
account: &Self::AccountId,
amount: Self::Balance,
) -> Self::Balance;
}
4 changes: 4 additions & 0 deletions runtime/battery-station/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ zrml-liquidity-mining = { workspace = true }
zrml-market-commons = { workspace = true }
zrml-neo-swaps = { workspace = true }
zrml-orderbook-v1 = { workspace = true }
zrml-parimutuel = { workspace = true }
zrml-prediction-markets = { workspace = true }
zrml-rikiddo = { workspace = true }
zrml-simple-disputes = { workspace = true }
Expand Down Expand Up @@ -211,6 +212,7 @@ runtime-benchmarks = [
"zrml-court/runtime-benchmarks",
"zrml-liquidity-mining/runtime-benchmarks",
"zrml-neo-swaps/runtime-benchmarks",
"zrml-parimutuel/runtime-benchmarks",
"zrml-prediction-markets/runtime-benchmarks",
"zrml-simple-disputes/runtime-benchmarks",
"zrml-global-disputes/runtime-benchmarks",
Expand Down Expand Up @@ -325,6 +327,7 @@ std = [
"zrml-liquidity-mining/std",
"zrml-market-commons/std",
"zrml-neo-swaps/std",
"zrml-parimutuel/std",
"zrml-prediction-markets/std",
"zrml-rikiddo/std",
"zrml-simple-disputes/std",
Expand Down Expand Up @@ -380,6 +383,7 @@ try-runtime = [
"zrml-liquidity-mining/try-runtime",
"zrml-market-commons/try-runtime",
"zrml-neo-swaps/try-runtime",
"zrml-parimutuel/try-runtime",
"zrml-prediction-markets/try-runtime",
"zrml-rikiddo/try-runtime",
"zrml-simple-disputes/try-runtime",
Expand Down
5 changes: 1 addition & 4 deletions runtime/battery-station/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ use frame_support::{
use frame_system::{EnsureRoot, EnsureWithSuccess};
use orml_currencies::Call::transfer;
use pallet_collective::{EnsureProportionAtLeast, PrimeDefaultVote};
use sp_runtime::{
traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256},
DispatchError,
};
use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256};
#[cfg(feature = "std")]
use sp_version::NativeVersion;
use substrate_fixed::{types::extra::U33, FixedI128, FixedU128};
Expand Down
5 changes: 5 additions & 0 deletions runtime/battery-station/src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ parameter_types! {
// Orderbook parameters
pub const OrderbookPalletId: PalletId = ORDERBOOK_PALLET_ID;

// Parimutuel parameters
pub const MinBetSize: Balance = 100 * ExistentialDeposit::get();
pub const ParimutuelPalletId: PalletId = PARIMUTUEL_PALLET_ID;

// System
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 73;
Expand Down Expand Up @@ -466,6 +470,7 @@ parameter_type_with_key! {
#[cfg(not(feature = "parachain"))]
Asset::ForeignAsset(_) => ExistentialDeposit::get(),
Asset::Ztg => ExistentialDeposit::get(),
Asset::ParimutuelShare(_,_) => ExistentialDeposit::get(),
}
};
}
Expand Down
47 changes: 47 additions & 0 deletions runtime/common/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,53 @@ macro_rules! impl_foreign_fees {
};
}

#[macro_export]
macro_rules! impl_market_creator_fees {
() => {
pub struct MarketCreatorFee;

/// Uses the `creator_fee` field defined by the specified market to deduct a fee for the market's
/// creator. Calling `distribute` is noop if the market doesn't exist or the transfer fails for any
/// reason.
impl DistributeFees for MarketCreatorFee {
type Asset = Asset<MarketId>;
type AccountId = AccountId;
type Balance = Balance;
type MarketId = MarketId;

fn distribute(
market_id: Self::MarketId,
asset: Self::Asset,
account: &Self::AccountId,
amount: Self::Balance,
) -> Self::Balance {
Self::do_distribute(market_id, asset, account, amount)
.unwrap_or_else(|_| 0u8.saturated_into())
}
}

impl MarketCreatorFee {
fn do_distribute(
market_id: MarketId,
asset: Asset<MarketId>,
account: &AccountId,
amount: Balance,
) -> Result<Balance, DispatchError> {
let market = MarketCommons::market(&market_id)?; // Should never fail
let fee_amount = market.creator_fee.mul_floor(amount);
// Might fail if the transaction is too small
<AssetManager as MultiCurrency<_>>::transfer(
asset,
account,
&market.creator,
fee_amount,
)?;
Ok(fee_amount)
}
}
};
}

#[macro_export]
macro_rules! fee_tests {
() => {
Expand Down
Loading

0 comments on commit 52a3063

Please sign in to comment.