diff --git a/Cargo.lock b/Cargo.lock index 7babea139..fc00bf621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14588,6 +14588,24 @@ dependencies = [ "zrml-orderbook-v1", ] +[[package]] +name = "zrml-parimutuel" +version = "0.3.11" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "orml-traits", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "zeitgeist-primitives", + "zrml-market-commons", +] + [[package]] name = "zrml-prediction-markets" version = "0.3.11" diff --git a/Cargo.toml b/Cargo.toml index 899a30cb3..207c07649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ default-members = [ "zrml/liquidity-mining", "zrml/market-commons", "zrml/orderbook-v1", + "zrml/parimutuel", "zrml/prediction-markets", "zrml/prediction-markets/runtime-api", "zrml/rikiddo", @@ -33,6 +34,7 @@ members = [ "zrml/market-commons", "zrml/orderbook-v1", "zrml/orderbook-v1/fuzz", + "zrml/parimutuel", "zrml/prediction-markets", "zrml/prediction-markets/fuzz", "zrml/prediction-markets/runtime-api", diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index 094f718c9..598059dc7 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -20,6 +20,18 @@ use crate::types::{CategoryIndex, PoolId, SerdeWrapper}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +/// The `Outcome` enum represents all types of outcomes available in the Zeitgeist +/// system. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, Ord, PartialEq, PartialOrd, TypeInfo, +)] +pub enum Outcome { + CategoricalOutcome(MarketId, CategoryIndex), + ScalarOutcome(MarketId, ScalarPosition), +} + /// The `Asset` enum represents all types of assets available in the Zeitgeist /// system. /// @@ -43,13 +55,12 @@ use scale_info::TypeInfo; TypeInfo, )] pub enum Asset { - CategoricalOutcome(MI, CategoryIndex), - ScalarOutcome(MI, ScalarPosition), - CombinatorialOutcome, + Outcome(Outcome), PoolShare(SerdeWrapper), #[default] Ztg, ForeignAsset(u32), + ParimutuelShare(Outcome), } /// In a scalar market, users can either choose a `Long` position, diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index a1be3cd57..f196c93fb 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -17,12 +17,14 @@ // along with Zeitgeist. If not, see . mod dispute_api; +mod distribute_fees; mod market_commons_pallet_api; mod market_id; mod swaps; mod zeitgeist_multi_reservable_currency; 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; diff --git a/primitives/src/traits/distribute_fees.rs b/primitives/src/traits/distribute_fees.rs new file mode 100644 index 000000000..a6b67ad50 --- /dev/null +++ b/primitives/src/traits/distribute_fees.rs @@ -0,0 +1,43 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +/// Trait for distributing fees collected from trading to external recipients like the treasury. +pub trait DistributeFees { + type Asset; + type AccountId; + type Balance; + type MarketId; + + /// Deduct and distribute the swap fees of the pool from the specified amount and returns the + /// deducted fees. + /// + /// # Arguments + /// + /// - `market_id`: The market on which the fees are taken. + /// - `asset`: The asset the fee is paid in. + /// - `account`: The account which pays the fees. + /// - `amount`: The gross amount from which fees are deducted. + /// + /// Note that this function is infallible. If distribution is impossible or fails midway, it + /// should return the balance of the already successfully deducted fees. + fn distribute( + market_id: Self::MarketId, + asset: Self::Asset, + account: Self::AccountId, + amount: Self::Balance, + ) -> Self::Balance; +} diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 2eb042b1e..d87c15271 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -443,10 +443,9 @@ parameter_type_with_key! { // Explicit match arms are used to ensure new asset types are respected. pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), + Asset::Outcome(zeitgeist_primitives::types::Outcome::CategoricalOutcome(_,_)) => ExistentialDeposit::get(), + Asset::Outcome(zeitgeist_primitives::types::Outcome::ScalarOutcome(_,_)) => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] Asset::ForeignAsset(id) => { let maybe_metadata = < @@ -462,6 +461,8 @@ parameter_type_with_key! { #[cfg(not(feature = "parachain"))] Asset::ForeignAsset(_) => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), + Asset::ParimutuelShare(zeitgeist_primitives::types::Outcome::CategoricalOutcome(_, _)) => 2 * ExistentialDeposit::get(), + Asset::ParimutuelShare(zeitgeist_primitives::types::Outcome::ScalarOutcome(_, _)) => 2 * ExistentialDeposit::get(), } }; } diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index b4a907ec3..48532e172 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -443,10 +443,9 @@ parameter_type_with_key! { // Explicit match arms are used to ensure new asset types are respected. pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), + Asset::Outcome(zeitgeist_primitives::types::Outcome::CategoricalOutcome(_,_)) => ExistentialDeposit::get(), + Asset::Outcome(zeitgeist_primitives::types::Outcome::ScalarOutcome(_,_)) => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] Asset::ForeignAsset(id) => { let maybe_metadata = < @@ -462,6 +461,8 @@ parameter_type_with_key! { #[cfg(not(feature = "parachain"))] Asset::ForeignAsset(_) => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), + Asset::ParimutuelShare(zeitgeist_primitives::types::Outcome::CategoricalOutcome(_,_)) => 2 * ExistentialDeposit::get(), + Asset::ParimutuelShare(zeitgeist_primitives::types::Outcome::ScalarOutcome(_,_)) => 2 * ExistentialDeposit::get(), } }; } diff --git a/zrml/orderbook-v1/src/lib.rs b/zrml/orderbook-v1/src/lib.rs index b416a6817..20d358d8c 100644 --- a/zrml/orderbook-v1/src/lib.rs +++ b/zrml/orderbook-v1/src/lib.rs @@ -40,7 +40,7 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ traits::MarketCommonsPalletApi, - types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, + types::{Asset, Market, MarketStatus, MarketType, Outcome, ScalarPosition, ScoringRule}, }; #[cfg(feature = "runtime-benchmarks")] @@ -441,14 +441,14 @@ mod pallet { MarketType::Categorical(categories) => { let mut assets = Vec::new(); for i in 0..categories { - assets.push(Asset::CategoricalOutcome(market_id, i)); + assets.push(Asset::Outcome(Outcome::CategoricalOutcome(market_id, i))); } assets } MarketType::Scalar(_) => { vec![ - Asset::ScalarOutcome(market_id, ScalarPosition::Long), - Asset::ScalarOutcome(market_id, ScalarPosition::Short), + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Long)), + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Short)), ] } } diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml new file mode 100644 index 000000000..2e90483ab --- /dev/null +++ b/zrml/parimutuel/Cargo.toml @@ -0,0 +1,42 @@ +[dependencies] +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime = { workspace = true } +zeitgeist-primitives = { workspace = true } +zrml-market-commons = { workspace = true } +orml-traits = { workspace = true } + +[dev-dependencies] +pallet-balances = { workspace = true, features = ["default"] } +pallet-timestamp = { workspace = true, features = ["default"] } +sp-io = { workspace = true, features = ["default"] } +zeitgeist-primitives = { workspace = true, features = ["mock", "default"] } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "sp-runtime/std", + "zeitgeist-primitives/std", + "zrml-market-commons/std", +] +try-runtime = [ + "frame-support/try-runtime", +] + +[package] +authors = ["Zeitgeist PM "] +edition = "2021" +name = "zrml-parimutuel" +version = "0.3.11" diff --git a/zrml/parimutuel/README.md b/zrml/parimutuel/README.md new file mode 100644 index 000000000..00120ce43 --- /dev/null +++ b/zrml/parimutuel/README.md @@ -0,0 +1 @@ +# Authorized Module diff --git a/zrml/parimutuel/src/benchmarks.rs b/zrml/parimutuel/src/benchmarks.rs new file mode 100644 index 000000000..e78b1ef2e --- /dev/null +++ b/zrml/parimutuel/src/benchmarks.rs @@ -0,0 +1,56 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +#![allow( + // Auto-generated code is a no man's land + clippy::arithmetic_side_effects +)] +#![cfg(feature = "runtime-benchmarks")] + +use crate::{ + market_mock, AuthorizedOutcomeReports, Call, Config, NegativeImbalanceOf, Pallet as Authorized, + Pallet as Parimutuel, +}; +use frame_benchmarking::v2::*; +use frame_support::{ + dispatch::UnfilteredDispatchable, + traits::{EnsureOrigin, Get, Imbalance}, +}; +use sp_runtime::traits::Saturating; +use zeitgeist_primitives::{ + traits::{DisputeApi, DisputeResolutionApi}, + types::{AuthorityReport, OutcomeReport}, +}; +use zrml_market_commons::MarketCommonsPalletApi; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn buy() {} + + #[benchmark] + fn claim_reward() {} + + impl_benchmark_test_suite!( + Parimutuel, + crate::mock::ExtBuilder::default().build(), + crate::mock::Runtime + ); +} diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs new file mode 100644 index 000000000..9e94977b7 --- /dev/null +++ b/zrml/parimutuel/src/lib.rs @@ -0,0 +1,247 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod benchmarks; +mod mock; +mod tests; +pub mod weights; + +pub use pallet::*; + +#[frame_support::pallet] +mod pallet { + use crate::weights::WeightInfoZeitgeist; + use core::marker::PhantomData; + use frame_support::{ + ensure, + traits::{Get, IsType, StorageVersion}, + PalletId, + }; + use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use orml_traits::MultiCurrency; + use sp_runtime::{traits::AccountIdConversion, DispatchResult}; + use zeitgeist_primitives::{ + traits::DistributeFees, + types::{Asset, Market, MarketStatus, MarketType, Outcome, ScalarPosition, ScoringRule}, + }; + use zrml_market_commons::MarketCommonsPalletApi; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type BalanceOf = + <::AssetManager as MultiCurrency>>::Balance; + pub(crate) type MarketIdOf = + <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; + pub(crate) type MarketOf = Market< + AccountIdOf, + BalanceOf, + ::BlockNumber, + MomentOf, + Asset>, + >; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(5000)] + #[frame_support::transactional] + pub fn buy( + origin: OriginFor, + asset: Asset>, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(amount >= T::MinBetSize::get(), Error::::AmountTooSmall); + + let outcome = match asset { + Asset::ParimutuelShare(outcome) => outcome, + _ => return Err(Error::::NotParimutuelOutcome.into()), + }; + let market_id = match outcome { + Outcome::CategoricalOutcome(market_id, _) => market_id, + Outcome::ScalarOutcome(market_id, _) => market_id, + }; + let market = T::MarketCommons::market(&market_id)?; + ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); + ensure!(market.scoring_rule == ScoringRule::Parimutuel, Error::::InvalidScoringRule); + + let market_assets = Self::outcome_assets(market_id, &market); + ensure!(market_assets.binary_search(&asset).is_ok(), Error::::InvalidOutcomeAsset); + + let pot_account = Self::pot_account(market_id); + T::AssetManager::transfer(market.base_asset, &who, &pot_account, amount)?; + + T::AssetManager::deposit(asset, &who, amount)?; + + Self::deposit_event(Event::OutcomeBought { market_id, asset }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(5000)] + #[frame_support::transactional] + pub fn claim_rewards(origin: OriginFor, market_id: MarketIdOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let market = T::MarketCommons::market(&market_id)?; + + Self::deposit_event(Event::RewardsClaimed { market_id }); + + Ok(()) + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type ExternalFees: DistributeFees< + Asset = Asset>, + AccountId = AccountIdOf, + Balance = BalanceOf, + MarketId = MarketIdOf, + >; + + /// Event + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type MarketCommons: MarketCommonsPalletApi< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + Balance = BalanceOf, + >; + + type AssetManager: MultiCurrency>>; + + /// The minimum amount each bet must be. Must be larger than the existential deposit of parimutuel shares. + #[pallet::constant] + type MinBetSize: Get>; + + /// Identifier of this pallet + #[pallet::constant] + type PalletId: Get; + + /// Weights generated by benchmarks + type WeightInfo: WeightInfoZeitgeist; + } + + #[pallet::error] + pub enum Error { + OutcomeMismatch, + MarketIsNotActive, + AmountTooSmall, + NotParimutuelOutcome, + InvalidOutcomeAsset, + InvalidScoringRule, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event + where + T: Config, + { + OutcomeBought { market_id: MarketIdOf, asset: Asset> }, + RewardsClaimed { market_id: MarketIdOf }, + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + impl Pallet + where + T: Config, + { + #[inline] + fn pot_account(market_id: MarketIdOf) -> AccountIdOf { + T::PalletId::get().into_sub_account_truncating(market_id) + } + + pub fn outcome_assets( + market_id: MarketIdOf, + market: &MarketOf, + ) -> Vec>> { + match market.market_type { + MarketType::Categorical(categories) => { + let mut assets = Vec::new(); + for i in 0..categories { + assets.push(Asset::ParimutuelShare(Outcome::CategoricalOutcome( + market_id, i, + ))); + } + assets + } + MarketType::Scalar(_) => { + vec![ + Asset::ParimutuelShare(Outcome::ScalarOutcome( + market_id, + ScalarPosition::Long, + )), + Asset::ParimutuelShare(Outcome::ScalarOutcome( + market_id, + ScalarPosition::Short, + )), + ] + } + } + } + } +} + +#[cfg(any(feature = "runtime-benchmarks", test))] +pub(crate) fn market_mock() -> MarketOf +where + T: crate::Config, +{ + use frame_support::traits::Get; + use sp_runtime::{traits::AccountIdConversion, Perbill}; + use zeitgeist_primitives::types::{ + Asset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, ScoringRule, + }; + + zeitgeist_primitives::types::Market { + base_asset: Asset::Ztg, + creation: MarketCreation::Permissionless, + creator_fee: Perbill::zero(), + creator: T::PalletId::get().into_account_truncating(), + market_type: MarketType::Scalar(0..=100), + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + metadata: Default::default(), + oracle: T::PalletId::get().into_account_truncating(), + period: MarketPeriod::Block(Default::default()), + deadlines: Deadlines { + grace_period: 1_u32.into(), + oracle_duration: 1_u32.into(), + dispute_duration: 1_u32.into(), + }, + report: None, + resolved_outcome: None, + scoring_rule: ScoringRule::CPMM, + status: MarketStatus::Disputed, + bonds: MarketBonds::default(), + } +} diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs new file mode 100644 index 000000000..2f6b8f4b0 --- /dev/null +++ b/zrml/parimutuel/src/mock.rs @@ -0,0 +1,139 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +#![cfg(test)] + +extern crate alloc; + +use crate::{self as zrml_authorized}; +use alloc::{vec, vec::Vec}; +use frame_support::{ + construct_runtime, ord_parameter_types, + pallet_prelude::{DispatchError, Weight}, + traits::Everything, +}; +use frame_system::EnsureSignedBy; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use zeitgeist_primitives::{ + constants::mock::{ + AuthorizedPalletId, BlockHashCount, CorrectionPeriod, MaxReserves, MinimumPeriod, + PmPalletId, BASE, + }, + traits::DisputeResolutionApi, + types::{ + AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketId, + Moment, UncheckedExtrinsicTest, + }, +}; + +pub const ALICE: AccountIdTest = 0; +pub const BOB: AccountIdTest = 1; +pub const CHARLIE: AccountIdTest = 2; + +construct_runtime!( + pub enum Runtime + where + Block = BlockTest, + NodeBlock = BlockTest, + UncheckedExtrinsic = UncheckedExtrinsicTest, + { + Parimutuel: zrml_parimutuel::{Event, Pallet, Storage}, + Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + MarketCommons: zrml_market_commons::{Pallet, Storage}, + System: frame_system::{Call, Config, Event, Pallet, Storage}, + Timestamp: pallet_timestamp::{Pallet}, + } +); + +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountIdTest; + type BaseCallFilter = Everything; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockNumber = BlockNumber; + type BlockWeights = (); + type RuntimeCall = RuntimeCall; + type DbWeight = (); + type RuntimeEvent = (); + type Hash = Hash; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = Index; + type Lookup = IdentityLookup; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type RuntimeOrigin = RuntimeOrigin; + type PalletInfo = PalletInfo; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); + type OnSetCode = (); +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = (); + type ExistentialDeposit = (); + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +impl zrml_market_commons::Config for Runtime { + type Currency = Balances; + type MarketId = MarketId; + type PredictionMarketsPalletId = PmPalletId; + type Timestamp = Timestamp; +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + +pub struct ExtBuilder { + balances: Vec<(AccountIdTest, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![(ALICE, 1_000 * BASE), (BOB, 1_000 * BASE), (CHARLIE, 1_000 * BASE)] } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: self.balances } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/zrml/parimutuel/src/tests.rs b/zrml/parimutuel/src/tests.rs new file mode 100644 index 000000000..2423ac34a --- /dev/null +++ b/zrml/parimutuel/src/tests.rs @@ -0,0 +1,46 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +#![cfg(test)] + +use crate::{ + market_mock, + mock::{Authorized, AuthorizedDisputeResolutionUser, ExtBuilder, Runtime, RuntimeOrigin, BOB}, + mock_storage::pallet as mock_storage, + AuthorizedOutcomeReports, Error, +}; +use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; +use zeitgeist_primitives::{ + traits::DisputeApi, + types::{AuthorityReport, MarketDisputeMechanism, MarketStatus, OutcomeReport}, +}; +use zrml_market_commons::Markets; + +#[test] +fn buy_shares() { + ExtBuilder::default().build().execute_with(|| { + Markets::::insert(0, market_mock::()); + }); +} + +#[test] +fn claim_rewards() { + ExtBuilder::default().build().execute_with(|| { + Markets::::insert(0, market_mock::()); + }); +} diff --git a/zrml/parimutuel/src/weights.rs b/zrml/parimutuel/src/weights.rs new file mode 100644 index 000000000..ac51b43d3 --- /dev/null +++ b/zrml/parimutuel/src/weights.rs @@ -0,0 +1,67 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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 Zeitgeist. If not, see . + +//! Autogenerated weights for zrml_authorized +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-03, STEPS: `10`, REPEAT: 1000, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=10 +// --repeat=1000 +// --pallet=zrml_authorized +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./misc/weight_template.hbs +// --output=./zrml/authorized/src/weights.rs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{traits::Get, weights::Weight}; + +/// Trait containing the required functions for weight retrival within +/// zrml_authorized (automatically generated) +pub trait WeightInfoZeitgeist { + fn buy() -> Weight; + fn claim_rewards() -> Weight; +} + +/// Weight functions for zrml_authorized (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + fn buy() -> Weight { + Weight::from_ref_time(50_720_068) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + + fn claim_rewards() -> Weight { + Weight::from_ref_time(39_720_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 968607532..6533e0e19 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -48,6 +48,7 @@ mod pallet { transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use zeitgeist_primitives::types::Outcome; #[cfg(feature = "parachain")] use {orml_traits::asset_registry::Inspect, zeitgeist_primitives::types::CustomMetadata}; @@ -119,6 +120,7 @@ mod pallet { debug_assert!(false, "{}", warning); return Ok(()); } + // TODO if you use the Balance of Currency, you might not need saturated conversions here!!! T::Currency::unreserve_named( &Self::reserve_id(), @@ -538,7 +540,7 @@ mod pallet { ); match m.scoring_rule { - ScoringRule::CPMM | ScoringRule::Orderbook => { + ScoringRule::CPMM | ScoringRule::Orderbook | ScoringRule::Parimutuel => { m.status = MarketStatus::Active; } ScoringRule::RikiddoSigmoidFeeMarketEma => { @@ -1127,7 +1129,8 @@ mod pallet { let winning_assets = match resolved_outcome { OutcomeReport::Categorical(category_index) => { - let winning_currency_id = Asset::CategoricalOutcome(market_id, category_index); + let winning_currency_id = + Asset::Outcome(Outcome::CategoricalOutcome(market_id, category_index)); let winning_balance = T::AssetManager::free_balance(winning_currency_id, &sender); @@ -1144,8 +1147,10 @@ mod pallet { vec![(winning_currency_id, winning_balance, winning_balance)] } OutcomeReport::Scalar(value) => { - let long_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Long); - let short_currency_id = Asset::ScalarOutcome(market_id, ScalarPosition::Short); + let long_currency_id = + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Long)); + let short_currency_id = + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Short)); let long_balance = T::AssetManager::free_balance(long_currency_id, &sender); let short_balance = T::AssetManager::free_balance(short_currency_id, &sender); @@ -2082,14 +2087,14 @@ mod pallet { MarketType::Categorical(categories) => { let mut assets = Vec::new(); for i in 0..categories { - assets.push(Asset::CategoricalOutcome(market_id, i)); + assets.push(Asset::Outcome(Outcome::CategoricalOutcome(market_id, i))); } assets } MarketType::Scalar(_) => { vec![ - Asset::ScalarOutcome(market_id, ScalarPosition::Long), - Asset::ScalarOutcome(market_id, ScalarPosition::Short), + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Long)), + Asset::Outcome(Outcome::ScalarOutcome(market_id, ScalarPosition::Short)), ] } } @@ -3078,7 +3083,9 @@ mod pallet { } let status: MarketStatus = match creation { MarketCreation::Permissionless => match scoring_rule { - ScoringRule::CPMM | ScoringRule::Orderbook => MarketStatus::Active, + ScoringRule::CPMM | ScoringRule::Orderbook | ScoringRule::Parimutuel => { + MarketStatus::Active + } ScoringRule::RikiddoSigmoidFeeMarketEma => MarketStatus::CollectingSubsidy, }, MarketCreation::Advised => MarketStatus::Proposed, diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 4852b1874..19dfa2f43 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -93,8 +93,8 @@ mod pallet { constants::{BASE, CENT}, traits::{MarketCommonsPalletApi, Swaps, ZeitgeistAssetManager}, types::{ - Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, - ScoringRule, SerdeWrapper, + Asset, MarketType, Outcome, OutcomeReport, Pool, PoolId, PoolStatus, + ResultWithWeightInfo, ScoringRule, SerdeWrapper, }, }; use zrml_liquidity_mining::LiquidityMiningPalletApi; @@ -1755,7 +1755,7 @@ mod pallet { Err(Error::::WinningAssetNotFound.into()); if let OutcomeReport::Categorical(winning_asset_idx) = outcome_report { pool.assets.retain(|el| { - if let Asset::CategoricalOutcome(_, idx) = *el { + if let Asset::Outcome(Outcome::CategoricalOutcome(_, idx)) = *el { if idx == *winning_asset_idx { winning_asset = Ok(*el); return true; @@ -1942,7 +1942,7 @@ mod pallet { let pool_amount = >::zero(); (pool_status, total_subsidy, total_weight, weights, pool_amount) } - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } }; @@ -2503,7 +2503,7 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; cost_before.checked_sub(&cost_after).ok_or(ArithmeticError::Overflow)? } - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } }; @@ -2555,7 +2555,9 @@ mod pallet { ScoringRule::RikiddoSigmoidFeeMarketEma => Ok( T::WeightInfo::swap_exact_amount_in_rikiddo(pool.assets.len().saturated_into()), ), - ScoringRule::Orderbook => Err(Error::::InvalidScoringRule.into()), + ScoringRule::Orderbook | ScoringRule::Parimutuel => { + Err(Error::::InvalidScoringRule.into()) + } } } @@ -2663,7 +2665,7 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; cost_after.checked_sub(&cost_before).ok_or(ArithmeticError::Overflow)? } - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } }; @@ -2727,7 +2729,9 @@ mod pallet { pool.assets.len().saturated_into(), )) } - ScoringRule::Orderbook => Err(Error::::InvalidScoringRule.into()), + ScoringRule::Orderbook | ScoringRule::Parimutuel => { + Err(Error::::InvalidScoringRule.into()) + } } } } diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index a34e2e719..36ea5660a 100644 --- a/zrml/swaps/src/utils.rs +++ b/zrml/swaps/src/utils.rs @@ -216,7 +216,7 @@ where return Err(Error::::UnsupportedTrade.into()); } } - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } } @@ -233,7 +233,7 @@ where spot_price_before.saturating_sub(spot_price_after) < 20u8.into(), Error::::MathApproximation ), - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } } @@ -256,7 +256,7 @@ where let volume = if p.asset_in == base_asset { asset_amount_in } else { asset_amount_out }; T::RikiddoSigmoidFeeMarketEma::update_volume(p.pool_id, volume)?; } - ScoringRule::Orderbook => { + ScoringRule::Orderbook | ScoringRule::Parimutuel => { return Err(Error::::InvalidScoringRule.into()); } }