diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 81e8310e8..4d52a85ec 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,10 +24,11 @@ jobs: - name: Install build tools run: ./scripts/init.sh - - - uses: actions-rs/install@v0.1 - with: - crate: taplo-cli + +# TODO(#1125): Use actions-rs/install for Taplo once stable +# - uses: actions-rs/install@v0.1 +# with: +# crate: taplo-cli - name: Cache Dependencies uses: Swatinem/rust-cache@v1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cbd2dd58..b4a97f540 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,22 +52,9 @@ mark the PR with the following labels according to ## Style Guide -- Use `rustfmt` to format your contributions. -- Avoid panickers like `unwrap()` even if there's proof that they are - infallible. -- Dispatches that don't use `#[transactional]` macro **must** contain a comment - including `MARK(non-transactional): ...` followed by a short explanation why - the dispatch doesn't require `#[transactional]`. -- Functions are written in snake case, i.e. `my_function`, anything else is - declared in CamelCase (starting with a capital first letter). -- Indentations consist of spaces, unless the language used requires tabs. -- Anything that is publicly visible must be documented. This encompasses but is - not limited to whole crates (top level documentation), public types and - functions, dispatachble functions (functions that can be called by - transactions), the `Error` and `Event` enum as well as the `Config` trait. -- Any newly added or modified functionality must be subject to at least one test - case. A full code coverage is the targeted goal. +- Refer to the [style guide]. [rules]: #Rules [docs/changelog_for_devs.md]: docs/changelog_for_devs.md +[style guide]: docs/STYLE_GUIDE.md [zeitgeistpm]: https://github.com/zeitgeistpm diff --git a/Cargo.lock b/Cargo.lock index f8b1d8d68..0fdc7e3d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -617,6 +617,7 @@ dependencies = [ "zrml-global-disputes", "zrml-liquidity-mining", "zrml-market-commons", + "zrml-orderbook-v1", "zrml-prediction-markets", "zrml-rikiddo", "zrml-simple-disputes", @@ -14430,6 +14431,7 @@ dependencies = [ "zrml-global-disputes", "zrml-liquidity-mining", "zrml-market-commons", + "zrml-orderbook-v1", "zrml-prediction-markets", "zrml-rikiddo", "zrml-simple-disputes", @@ -14560,14 +14562,18 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "orml-currencies", "orml-tokens", "orml-traits", "pallet-balances", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-io", "sp-runtime", + "test-case", "zeitgeist-primitives", + "zrml-market-commons", "zrml-orderbook-v1", ] diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index 1dc3d15f8..84e5126ea 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -1,5 +1,10 @@ # Style guide +As a basis, the +[Substrate Style Guide](https://docs.substrate.io/build/troubleshoot-your-code/) +should be taken into account. In addition to that, the following sections +further elaborate the style guide used in this repository. + ## Comments - Comments **must** be wrapped at 100 chars per line. diff --git a/misc/frame_weight_template.hbs b/misc/frame_weight_template.hbs index 373da00e6..d2524d4e8 100644 --- a/misc/frame_weight_template.hbs +++ b/misc/frame_weight_template.hbs @@ -25,6 +25,9 @@ impl {{pallet}}::weights::WeightInfo for WeightInfo {{#each benchmark.comments as |comment|}} /// {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} @@ -34,27 +37,26 @@ impl {{pallet}}::weights::WeightInfo for WeightInfo // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - {{#if (ne benchmark.base_calculated_proof_size "0")}} Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) - {{else}} - Weight::from_ref_time({{underscore benchmark.base_weight}}) - {{/if}} {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}_u64)) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) {{/each}} {{#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}_u64)) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} {{#each benchmark.component_calculated_proof_size as |cp|}} - .saturating_add(Weight::from_proof_size({{cp.slope}}).saturating_mul({{cp.name}}.into())) - {{/each}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } diff --git a/misc/orml_weight_template.hbs b/misc/orml_weight_template.hbs index eec55f462..2da62c06e 100644 --- a/misc/orml_weight_template.hbs +++ b/misc/orml_weight_template.hbs @@ -25,6 +25,9 @@ impl {{pallet}}::WeightInfo for WeightInfo { {{#each benchmark.comments as |comment|}} /// {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} @@ -34,27 +37,26 @@ impl {{pallet}}::WeightInfo for WeightInfo { // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - {{#if (ne benchmark.base_calculated_proof_size "0")}} Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) - {{else}} - Weight::from_ref_time({{underscore benchmark.base_weight}}) - {{/if}} {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}_u64)) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) {{/each}} {{#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}_u64)) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} {{#each benchmark.component_calculated_proof_size as |cp|}} - .saturating_add(Weight::from_proof_size({{cp.slope}}).saturating_mul({{cp.name}}.into())) - {{/each}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } diff --git a/misc/weight_template.hbs b/misc/weight_template.hbs index 30c93b830..875819dda 100644 --- a/misc/weight_template.hbs +++ b/misc/weight_template.hbs @@ -37,6 +37,9 @@ impl WeightInfoZeitgeist for WeightInfo { {{#each benchmark.comments as |comment|}} /// {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} @@ -46,27 +49,26 @@ impl WeightInfoZeitgeist for WeightInfo { // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - {{#if (ne benchmark.base_calculated_proof_size "0")}} Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) - {{else}} - Weight::from_ref_time({{underscore benchmark.base_weight}}) - {{/if}} {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}_u64)) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) {{/if}} {{#each benchmark.component_reads as |cr|}} .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) {{/each}} {{#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}_u64)) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) {{/if}} + {{#each benchmark.component_writes as |cw|}} + .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) + {{/each}} {{#each benchmark.component_calculated_proof_size as |cp|}} - .saturating_add(Weight::from_proof_size({{cp.slope}}).saturating_mul({{cp.name}}.into())) - {{/each}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 6e0b6d3a3..d5e32ef71 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -113,6 +113,9 @@ pub const MAX_ASSETS: u16 = MAX_CATEGORIES + 1; /// Pallet identifier, mainly used for named balance reserves. pub const SWAPS_PALLET_ID: PalletId = PalletId(*b"zge/swap"); +// Orderbook +pub const ORDERBOOK_PALLET_ID: PalletId = PalletId(*b"zge/ordb"); + // Treasury /// Pallet identifier, used to derive treasury account pub const TREASURY_PALLET_ID: PalletId = PalletId(*b"zge/tsry"); diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 2bfae8ecc..5f1ec243c 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -120,6 +120,11 @@ parameter_types! { pub const SwapsPalletId: PalletId = PalletId(*b"zge/swap"); } +// Orderbook parameters +parameter_types! { + pub const OrderbookPalletId: PalletId = PalletId(*b"zge/ordb"); +} + // Shared within tests // Balance parameter_types! { diff --git a/primitives/src/market.rs b/primitives/src/market.rs index c590c0d54..edd51c71e 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -61,7 +61,7 @@ pub struct Market { /// The resolved outcome. pub resolved_outcome: Option, /// See [`MarketDisputeMechanism`]. - pub dispute_mechanism: MarketDisputeMechanism, + pub dispute_mechanism: Option, /// The bonds reserved for this market. pub bonds: MarketBonds, } @@ -162,7 +162,7 @@ where .saturating_add(MarketStatus::max_encoded_len()) .saturating_add(>>::max_encoded_len()) .saturating_add(>::max_encoded_len()) - .saturating_add(::max_encoded_len()) + .saturating_add(>::max_encoded_len()) .saturating_add(>::max_encoded_len()) } } @@ -393,7 +393,7 @@ mod tests { status: MarketStatus::Active, report: None, resolved_outcome: None, - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), bonds: MarketBonds::default(), }; assert_eq!(market.matches_outcome_report(&outcome_report), expected); diff --git a/primitives/src/pool.rs b/primitives/src/pool.rs index bafa0e4af..62ca56d80 100644 --- a/primitives/src/pool.rs +++ b/primitives/src/pool.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -83,4 +84,5 @@ where pub enum ScoringRule { CPMM, RikiddoSigmoidFeeMarketEma, + Orderbook, } diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 03329040f..4d21ad84e 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -113,6 +113,7 @@ zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-liquidity-mining = { workspace = true } zrml-market-commons = { workspace = true } +zrml-orderbook-v1 = { workspace = true } zrml-prediction-markets = { workspace = true } zrml-rikiddo = { workspace = true } zrml-simple-disputes = { workspace = true } @@ -214,6 +215,7 @@ runtime-benchmarks = [ "zrml-global-disputes/runtime-benchmarks", "zrml-styx/runtime-benchmarks", "zrml-swaps/runtime-benchmarks", + "zrml-orderbook-v1/runtime-benchmarks", ] std = [ "frame-executive/std", @@ -328,6 +330,7 @@ std = [ "zrml-styx/std", "zrml-swaps-runtime-api/std", "zrml-swaps/std", + "zrml-orderbook-v1/std", ] try-runtime = [ "frame-executive/try-runtime", @@ -380,6 +383,7 @@ try-runtime = [ "zrml-global-disputes/try-runtime", "zrml-styx/try-runtime", "zrml-swaps/try-runtime", + "zrml-orderbook-v1/try-runtime", # Parachain "pallet-author-inherent?/try-runtime", diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index 682bbbf21..d39d2250d 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -128,16 +128,16 @@ impl Contains for ContractsCallfilter { dispute { .. } => true, // Only allow CPMM markets using Authorized or SimpleDisputes dispute mechanism create_market { - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), scoring_rule: ScoringRule::CPMM, .. } => true, create_cpmm_market_and_deploy_assets { - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), .. } => true, edit_market { - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), scoring_rule: ScoringRule::CPMM, .. } => true, @@ -180,18 +180,18 @@ impl Contains for IsCallable { scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. } => false, create_market { - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), .. } => false, edit_market { scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. } => false, create_cpmm_market_and_deploy_assets { - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), .. } => false, edit_market { - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), .. } => false, _ => true, diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index adf67ddaa..2eb042b1e 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -305,6 +305,9 @@ parameter_types! { /// Pallet identifier, mainly used for named balance reserves. pub const SwapsPalletId: PalletId = SWAPS_PALLET_ID; + // Orderbook parameters + pub const OrderbookPalletId: PalletId = ORDERBOOK_PALLET_ID; + // System pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 73; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index bc33c2c17..f311977a1 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -45,35 +45,23 @@ pub mod weights; #[macro_export] macro_rules! decl_common_types { - {} => { - use sp_runtime::generic; + () => { + use frame_support::traits::{ + Currency, Imbalance, NeverEnsureOrigin, OnRuntimeUpgrade, OnUnbalanced, + }; #[cfg(feature = "try-runtime")] - use frame_try_runtime::{UpgradeCheckSelect, TryStateSelect}; - use frame_support::traits::{Currency, Imbalance, OnRuntimeUpgrade, OnUnbalanced, NeverEnsureOrigin}; + use frame_try_runtime::{TryStateSelect, UpgradeCheckSelect}; + use sp_runtime::generic; pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; #[cfg(feature = "parachain")] - type Migrations = ( - orml_asset_registry::Migration, - orml_unknown_tokens::Migration, - pallet_xcm::migration::v1::MigrateToV1, - // IMPORTANT that AddDisputeBondAndConvertCreatorFee comes before MoveDataToSimpleDisputes!!! - zrml_prediction_markets::migrations::AddDisputeBondAndConvertCreatorFee, - zrml_prediction_markets::migrations::MoveDataToSimpleDisputes, - zrml_global_disputes::migrations::ModifyGlobalDisputesStructures, - ); + type Migrations = (zrml_prediction_markets::migrations::MigrateMarkets,); #[cfg(not(feature = "parachain"))] - type Migrations = ( - pallet_grandpa::migrations::CleanupSetIdSessionMap, - // IMPORTANT that AddDisputeBondAndConvertCreatorFee comes before MoveDataToSimpleDisputes!!! - zrml_prediction_markets::migrations::AddDisputeBondAndConvertCreatorFee, - zrml_prediction_markets::migrations::MoveDataToSimpleDisputes, - zrml_global_disputes::migrations::ModifyGlobalDisputesStructures, - ); + type Migrations = (zrml_prediction_markets::migrations::MigrateMarkets,); pub type Executive = frame_executive::Executive< Runtime, @@ -99,7 +87,8 @@ macro_rules! decl_common_types { pallet_asset_tx_payment::ChargeAssetTxPayment, ); pub type SignedPayload = generic::SignedPayload; - pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; // Governance type AdvisoryCommitteeInstance = pallet_collective::Instance1; @@ -111,20 +100,28 @@ macro_rules! decl_common_types { // Council vote proportions // At least 50% - type EnsureRootOrHalfCouncil = - EitherOfDiverse, EnsureProportionAtLeast>; + type EnsureRootOrHalfCouncil = EitherOfDiverse< + EnsureRoot, + EnsureProportionAtLeast, + >; // At least 66% - type EnsureRootOrTwoThirdsCouncil = - EitherOfDiverse, EnsureProportionAtLeast>; + type EnsureRootOrTwoThirdsCouncil = EitherOfDiverse< + EnsureRoot, + EnsureProportionAtLeast, + >; // At least 75% - type EnsureRootOrThreeFourthsCouncil = - EitherOfDiverse, EnsureProportionAtLeast>; + type EnsureRootOrThreeFourthsCouncil = EitherOfDiverse< + EnsureRoot, + EnsureProportionAtLeast, + >; // At least 100% - type EnsureRootOrAllCouncil = - EitherOfDiverse, EnsureProportionAtLeast>; + type EnsureRootOrAllCouncil = EitherOfDiverse< + EnsureRoot, + EnsureProportionAtLeast, + >; // Technical committee vote proportions // At least 50% @@ -196,6 +193,7 @@ macro_rules! decl_common_types { CourtPalletId::get(), GlobalDisputesPalletId::get(), LiquidityMiningPalletId::get(), + OrderbookPalletId::get(), PmPalletId::get(), SimpleDisputesPalletId::get(), SwapsPalletId::get(), @@ -248,7 +246,7 @@ macro_rules! decl_common_types { } } } - } + }; } // Construct runtime @@ -313,6 +311,7 @@ macro_rules! create_runtime { PredictionMarkets: zrml_prediction_markets::{Call, Event, Pallet, Storage} = 57, Styx: zrml_styx::{Call, Event, Pallet, Storage} = 58, GlobalDisputes: zrml_global_disputes::{Call, Event, Pallet, Storage} = 59, + Orderbook: zrml_orderbook_v1::{Call, Event, Pallet, Storage} = 60, $($additional_pallets)* } @@ -858,6 +857,9 @@ macro_rules! impl_config_traits { c, RuntimeCall::Swaps(zrml_swaps::Call::swap_exact_amount_in { .. }) | RuntimeCall::Swaps(zrml_swaps::Call::swap_exact_amount_out { .. }) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::place_order { .. }) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::fill_order { .. }) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::remove_order { .. }) ), ProxyType::HandleAssets => matches!( c, @@ -877,6 +879,9 @@ macro_rules! impl_config_traits { | RuntimeCall::PredictionMarkets( zrml_prediction_markets::Call::deploy_swap_pool_and_additional_liquidity { .. } ) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::place_order { .. }) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::fill_order { .. }) + | RuntimeCall::Orderbook(zrml_orderbook_v1::Call::remove_order { .. }) ), } } @@ -1231,6 +1236,14 @@ macro_rules! impl_config_traits { type Currency = Balances; type WeightInfo = zrml_styx::weights::WeightInfo; } + + impl zrml_orderbook_v1::Config for Runtime { + type AssetManager = AssetManager; + type RuntimeEvent = RuntimeEvent; + type MarketCommons = MarketCommons; + type PalletId = OrderbookPalletId; + type WeightInfo = zrml_orderbook_v1::weights::WeightInfo; + } } } @@ -1339,6 +1352,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, zrml_court, Court); list_benchmark!(list, extra, zrml_simple_disputes, SimpleDisputes); list_benchmark!(list, extra, zrml_global_disputes, GlobalDisputes); + list_benchmark!(list, extra, zrml_orderbook_v1, Orderbook); #[cfg(not(feature = "parachain"))] list_benchmark!(list, extra, zrml_prediction_markets, PredictionMarkets); list_benchmark!(list, extra, zrml_liquidity_mining, LiquidityMining); @@ -1440,6 +1454,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, zrml_court, Court); add_benchmark!(params, batches, zrml_simple_disputes, SimpleDisputes); add_benchmark!(params, batches, zrml_global_disputes, GlobalDisputes); + add_benchmark!(params, batches, zrml_orderbook_v1, Orderbook); #[cfg(not(feature = "parachain"))] add_benchmark!(params, batches, zrml_prediction_markets, PredictionMarkets); add_benchmark!(params, batches, zrml_liquidity_mining, LiquidityMining); diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 2de9244e0..83514b05d 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -111,6 +111,7 @@ zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } zrml-liquidity-mining = { workspace = true } zrml-market-commons = { workspace = true } +zrml-orderbook-v1 = { workspace = true } zrml-prediction-markets = { workspace = true } zrml-rikiddo = { workspace = true } zrml-simple-disputes = { workspace = true } @@ -211,6 +212,7 @@ runtime-benchmarks = [ "zrml-global-disputes/runtime-benchmarks", "zrml-styx/runtime-benchmarks", "zrml-swaps/runtime-benchmarks", + "zrml-orderbook-v1/runtime-benchmarks", ] std = [ "frame-executive/std", @@ -317,6 +319,7 @@ std = [ "zrml-swaps-runtime-api/std", "zrml-styx/std", "zrml-swaps/std", + "zrml-orderbook-v1/std", ] try-runtime = [ "frame-executive/try-runtime", @@ -369,6 +372,7 @@ try-runtime = [ "zrml-global-disputes/try-runtime", "zrml-styx/try-runtime", "zrml-swaps/try-runtime", + "zrml-orderbook-v1/try-runtime", # Parachain "pallet-author-inherent?/try-runtime", diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index ee9ab02d3..ce7ab6c93 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -174,10 +174,10 @@ impl Contains for IsCallable { create_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, edit_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, // Disable Court & SimpleDisputes dispute resolution mechanism - create_market { dispute_mechanism: Court | SimpleDisputes, .. } => false, - edit_market { dispute_mechanism: Court | SimpleDisputes, .. } => false, + create_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, + edit_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, create_cpmm_market_and_deploy_assets { - dispute_mechanism: Court | SimpleDisputes, + dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, _ => true, diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 2406637e2..b4a907ec3 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -305,6 +305,9 @@ parameter_types! { /// Pallet identifier, mainly used for named balance reserves. DO NOT CHANGE. pub const SwapsPalletId: PalletId = SWAPS_PALLET_ID; + // Orderbook parameters + pub const OrderbookPalletId: PalletId = ORDERBOOK_PALLET_ID; + // System pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 73; diff --git a/scripts/tests/format.sh b/scripts/tests/format.sh index 8cb4d2704..4de815fc1 100755 --- a/scripts/tests/format.sh +++ b/scripts/tests/format.sh @@ -66,7 +66,7 @@ taplo_fmt() { has_taplo=$(which taplo) if [ $? -eq 1 ]; then echo "Installing taplo ..." - cargo install taplo-cli + cargo install taplo-cli --git https://github.com/tamasfe/taplo --rev 848722f2c604de68535e5a3e0bb2a2c1d3c7dc74 fi # install rustfmt if it isn't already has_rustfmt=$(which rustfmt) diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index b5b13b02e..b1721d4cc 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -45,7 +45,7 @@ mod pallet { PalletId, Twox64Concat, }; use frame_system::pallet_prelude::OriginFor; - use sp_runtime::{traits::Saturating, DispatchError}; + use sp_runtime::{traits::Saturating, DispatchError, DispatchResult}; use zeitgeist_primitives::{ traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ @@ -95,10 +95,7 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Disputed, Error::::MarketIsNotDisputed); ensure!(market.matches_outcome_report(&outcome), Error::::OutcomeMismatch); - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(&market)?; let now = frame_system::Pallet::::block_number(); @@ -192,6 +189,15 @@ mod pallet { fn get_auto_resolve(market_id: &MarketIdOf) -> Option { AuthorizedOutcomeReports::::get(market_id).map(|report| report.resolve_at) } + + #[inline] + fn ensure_dispute_mechanism(market: &MarketOf) -> DispatchResult { + ensure!( + market.dispute_mechanism == Some(MarketDisputeMechanism::Authorized), + Error::::MarketDoesNotHaveDisputeMechanismAuthorized + ); + Ok(()) + } } impl DisputeMaxWeightApi for Pallet @@ -243,10 +249,7 @@ mod pallet { _: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; let res = ResultWithWeightInfo { result: (), weight: T::WeightInfo::on_dispute_weight() }; @@ -258,10 +261,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; let report = AuthorizedOutcomeReports::::take(market_id); let res = ResultWithWeightInfo { @@ -278,10 +278,7 @@ mod pallet { _: &OutcomeReport, overall_imbalance: NegativeImbalanceOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; // all funds to treasury let res = ResultWithWeightInfo { result: overall_imbalance, @@ -300,7 +297,7 @@ mod pallet { weight: T::WeightInfo::get_auto_resolve_weight(), }; - if market.dispute_mechanism != MarketDisputeMechanism::Authorized { + if market.dispute_mechanism != Some(MarketDisputeMechanism::Authorized) { return res; } @@ -313,10 +310,7 @@ mod pallet { _: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; let res = ResultWithWeightInfo { result: false, weight: T::WeightInfo::has_failed_weight() }; @@ -331,10 +325,7 @@ mod pallet { ResultWithWeightInfo>>, DispatchError, > { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; let res = ResultWithWeightInfo { result: Vec::new(), @@ -348,10 +339,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Authorized, - Error::::MarketDoesNotHaveDisputeMechanismAuthorized - ); + Self::ensure_dispute_mechanism(market)?; AuthorizedOutcomeReports::::remove(market_id); @@ -376,20 +364,23 @@ where T: crate::Config, { use frame_support::traits::Get; - use sp_runtime::traits::AccountIdConversion; - use zeitgeist_primitives::types::{Asset, MarketBonds, ScoringRule}; + 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: zeitgeist_primitives::types::MarketCreation::Permissionless, - creator_fee: sp_runtime::Perbill::zero(), + creation: MarketCreation::Permissionless, + creator_fee: 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, + market_type: MarketType::Scalar(0..=100), + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), metadata: Default::default(), oracle: T::PalletId::get().into_account_truncating(), - period: zeitgeist_primitives::types::MarketPeriod::Block(Default::default()), - deadlines: zeitgeist_primitives::types::Deadlines { + period: MarketPeriod::Block(Default::default()), + deadlines: Deadlines { grace_period: 1_u32.into(), oracle_duration: 1_u32.into(), dispute_duration: 1_u32.into(), @@ -397,7 +388,7 @@ where report: None, resolved_outcome: None, scoring_rule: ScoringRule::CPMM, - status: zeitgeist_primitives::types::MarketStatus::Disputed, + status: MarketStatus::Disputed, bonds: MarketBonds::default(), } } diff --git a/zrml/authorized/src/tests.rs b/zrml/authorized/src/tests.rs index 018db50eb..81192a638 100644 --- a/zrml/authorized/src/tests.rs +++ b/zrml/authorized/src/tests.rs @@ -101,7 +101,7 @@ fn authorize_market_outcome_fails_if_market_does_not_exist() { fn authorize_market_outcome_fails_on_non_authorized_market() { ExtBuilder::default().build().execute_with(|| { let mut market = market_mock::(); - market.dispute_mechanism = MarketDisputeMechanism::Court; + market.dispute_mechanism = Some(MarketDisputeMechanism::Court); Markets::::insert(0, market); assert_noop!( Authorized::authorize_market_outcome( diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index fdc20aae2..97729349a 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -59,7 +59,7 @@ where creator_fee: sp_runtime::Perbill::zero(), creator: account("creator", 0, 0), market_type: MarketType::Scalar(0..=100), - dispute_mechanism: MarketDisputeMechanism::Court, + dispute_mechanism: Some(MarketDisputeMechanism::Court), metadata: vec![], oracle: account("oracle", 0, 0), period: MarketPeriod::Block( diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 0a19a1576..5ea958bc6 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -1774,10 +1774,7 @@ mod pallet { if let Some(market_id) = >::get(court_id) { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Disputed, Error::::MarketIsNotDisputed); - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(&market)?; } ensure!( @@ -1806,6 +1803,15 @@ mod pallet { T::TreasuryPalletId::get().into_account_truncating() } + #[inline] + fn ensure_dispute_mechanism(market: &MarketOf) -> DispatchResult { + ensure!( + market.dispute_mechanism == Some(MarketDisputeMechanism::Court), + Error::::MarketDoesNotHaveCourtMechanism + ); + Ok(()) + } + /// The court has a specific vote item type. /// We ensure that the vote item matches the predefined vote item type. pub(crate) fn check_vote_item( @@ -2112,10 +2118,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; let court_id = >::get(); let next_court_id = @@ -2160,10 +2163,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; let court_id = >::get(market_id) .ok_or(Error::::MarketIdToCourtIdNotFound)?; @@ -2192,10 +2192,7 @@ mod pallet { resolved_outcome: &OutcomeReport, mut overall_imbalance: NegativeImbalanceOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; let court_id = >::get(market_id) .ok_or(Error::::MarketIdToCourtIdNotFound)?; @@ -2231,7 +2228,7 @@ mod pallet { let mut res = ResultWithWeightInfo { result: None, weight: T::WeightInfo::get_auto_resolve() }; - if market.dispute_mechanism != MarketDisputeMechanism::Court { + if market.dispute_mechanism != Some(MarketDisputeMechanism::Court) { return res; } @@ -2246,10 +2243,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; let mut has_failed = false; let now = >::block_number(); @@ -2314,10 +2308,7 @@ mod pallet { ResultWithWeightInfo>>, DispatchError, > { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; // oracle outcome is added by pm-pallet let mut gd_outcomes: Vec> = @@ -2376,10 +2367,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::Court, - Error::::MarketDoesNotHaveCourtMechanism - ); + Self::ensure_dispute_mechanism(market)?; let court_id = >::get(market_id) .ok_or(Error::::MarketIdToCourtIdNotFound)?; diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 72fd7a378..880649471 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -67,7 +67,7 @@ const DEFAULT_MARKET: MarketOf = Market { creator_fee: sp_runtime::Perbill::zero(), creator: 0, market_type: MarketType::Scalar(0..=100), - dispute_mechanism: MarketDisputeMechanism::Court, + dispute_mechanism: Some(MarketDisputeMechanism::Court), metadata: vec![], oracle: 0, period: MarketPeriod::Block(0..100), @@ -1692,7 +1692,7 @@ fn check_appealable_market_fails_if_dispute_mechanism_wrong() { let market_id = >::get(court_id).unwrap(); MarketCommons::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + market.dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); Ok(()) }) .unwrap(); @@ -2402,7 +2402,7 @@ fn reassign_court_stakes_rewards_treasury_if_no_winner() { fn on_dispute_denies_non_court_markets() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + market.dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); assert_noop!( Court::on_dispute(&0, &market), Error::::MarketDoesNotHaveCourtMechanism @@ -2439,7 +2439,7 @@ fn on_resolution_fails_if_court_not_found() { fn on_resolution_denies_non_court_markets() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + market.dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); assert_noop!( Court::on_resolution(&0, &market), Error::::MarketDoesNotHaveCourtMechanism @@ -2451,7 +2451,7 @@ fn on_resolution_denies_non_court_markets() { fn exchange_fails_if_non_court_markets() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + market.dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); assert_noop!( Court::exchange(&0, &market, &ORACLE_REPORT, NegativeImbalance::::zero()), Error::::MarketDoesNotHaveCourtMechanism @@ -2562,7 +2562,7 @@ fn on_global_dispute_removes_draws() { fn on_global_dispute_fails_if_wrong_dispute_mechanism() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + market.dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); assert_noop!( Court::on_global_dispute(&0, &market), Error::::MarketDoesNotHaveCourtMechanism diff --git a/zrml/global-disputes/src/migrations.rs b/zrml/global-disputes/src/migrations.rs index 597b46789..3bc5ddb7c 100644 --- a/zrml/global-disputes/src/migrations.rs +++ b/zrml/global-disputes/src/migrations.rs @@ -15,369 +15,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -extern crate alloc; - -use crate::{types::*, Config, Pallet as GDPallet, *}; -#[cfg(feature = "try-runtime")] -use alloc::vec::Vec; -use frame_support::{ - dispatch::Weight, - log, - pallet_prelude::PhantomData, - traits::{Get, OnRuntimeUpgrade, StorageVersion}, -}; -use sp_runtime::traits::Saturating; - -#[cfg(feature = "try-runtime")] -use alloc::collections::BTreeMap; -#[cfg(feature = "try-runtime")] -use parity_scale_codec::{Decode, Encode}; -#[cfg(feature = "try-runtime")] -use scale_info::prelude::format; - -const GD_REQUIRED_STORAGE_VERSION: u16 = 0; -const GD_NEXT_STORAGE_VERSION: u16 = 1; - -pub struct ModifyGlobalDisputesStructures(PhantomData); - -impl OnRuntimeUpgrade - for ModifyGlobalDisputesStructures -{ - fn on_runtime_upgrade() -> Weight - where - T: Config, - { - let mut total_weight = T::DbWeight::get().reads(1); - let gd_version = StorageVersion::get::>(); - if gd_version != GD_REQUIRED_STORAGE_VERSION { - log::info!( - "ModifyGlobalDisputesStructures: global disputes version is {:?}, require {:?};", - gd_version, - GD_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("ModifyGlobalDisputesStructures: Starting..."); - - for (market_id, winner_info) in crate::Winners::::drain() { - total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); - - let owners = winner_info.outcome_info.owners; - let owners_len = owners.len(); - let possession = match owners_len { - 1usize => Possession::Paid { - owner: owners - .get(0) - .expect("Owners len is 1, but could not get this owner.") - .clone(), - fee: T::VotingOutcomeFee::get(), - }, - _ => Possession::Shared { owners }, - }; - - let outcome_info = - OutcomeInfo { outcome_sum: winner_info.outcome_info.outcome_sum, possession }; - let gd_info = GlobalDisputeInfo { - winner_outcome: winner_info.outcome, - outcome_info, - status: GdStatus::Finished, - }; - crate::GlobalDisputesInfo::::insert(market_id, gd_info); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - } - - let mut translated = 0u64; - Outcomes::::translate::, OwnerInfoOf>, _>( - |_key1, _key2, old_value| { - translated.saturating_inc(); - - let owners = old_value.owners; - let owners_len = owners.len(); - let possession = match owners_len { - 1usize => Possession::Paid { - owner: owners - .get(0) - .expect("Owners len is 1, but could not get this owner.") - .clone(), - fee: T::VotingOutcomeFee::get(), - }, - _ => Possession::Shared { owners }, - }; - - let new_value = OutcomeInfo { outcome_sum: old_value.outcome_sum, possession }; - - Some(new_value) - }, - ); - log::info!("ModifyGlobalDisputesStructures: Upgraded {} outcomes.", translated); - total_weight = total_weight - .saturating_add(T::DbWeight::get().reads_writes(translated + 1, translated + 1)); - - StorageVersion::new(GD_NEXT_STORAGE_VERSION).put::>(); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("ModifyGlobalDisputesStructures: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - let old_winners = crate::Winners::::iter() - .collect::, OldWinnerInfo, OwnerInfoOf>>>(); - Ok(old_winners.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - let mut markets_count = 0_u32; - let old_winners: BTreeMap, OldWinnerInfo, OwnerInfoOf>> = - Decode::decode(&mut &previous_state[..]) - .expect("Failed to decode state: Invalid state"); - for (market_id, gd_info) in crate::GlobalDisputesInfo::::iter() { - let GlobalDisputeInfo { winner_outcome, outcome_info, status } = gd_info; - - let winner_info: &OldWinnerInfo, OwnerInfoOf> = old_winners - .get(&market_id) - .expect(&format!("Market {:?} not found", market_id)[..]); - - assert_eq!(winner_outcome, winner_info.outcome); - assert_eq!(status, GdStatus::Finished); - - let owners = winner_info.outcome_info.owners.clone(); - let owners_len = owners.len(); - - let possession = match owners_len { - 1usize => Possession::Paid { - owner: owners - .get(0) - .expect("Owners len is 1, but could not get this owner.") - .clone(), - fee: T::VotingOutcomeFee::get(), - }, - _ => Possession::Shared { owners }, - }; - - let outcome_info_expected = - OutcomeInfo { outcome_sum: winner_info.outcome_info.outcome_sum, possession }; - assert_eq!(outcome_info, outcome_info_expected); - - markets_count += 1_u32; - } - let old_markets_count = old_winners.len() as u32; - assert_eq!(markets_count, old_markets_count); - - // empty Winners storage map - assert!(crate::Winners::::iter().next().is_none()); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mock::{ExtBuilder, Runtime, ALICE, BOB}; - use frame_support::{ - migration::{get_storage_value, put_storage_value}, - BoundedVec, - }; - use sp_runtime::traits::SaturatedConversion; - use zeitgeist_primitives::{ - constants::mock::VotingOutcomeFee, - types::{MarketId, OutcomeReport}, - }; - - const GLOBAL_DISPUTES: &[u8] = b"GlobalDisputes"; - const GD_OUTCOMES: &[u8] = b"Outcomes"; - - type OldOutcomeInfoOf = OldOutcomeInfo, OwnerInfoOf>; - - #[test] - fn on_runtime_upgrade_increments_the_storage_versions() { - ExtBuilder::default().build().execute_with(|| { - set_up_chain(); - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - let gd_version = StorageVersion::get::>(); - assert_eq!(gd_version, GD_NEXT_STORAGE_VERSION); - }); - } - - #[test] - fn on_runtime_sets_new_global_disputes_storage_paid() { - ExtBuilder::default().build().execute_with(|| { - set_up_chain(); - - let market_id = 0u128; - - let outcome_sum = 42u128.saturated_into::>(); - let owners = BoundedVec::try_from(vec![ALICE]).unwrap(); - - let outcome_info = OldOutcomeInfo { outcome_sum, owners }; - let outcome = OutcomeReport::Categorical(42u16); - let winner_info = - OldWinnerInfo { outcome: outcome.clone(), outcome_info, is_finished: true }; - - crate::Winners::::insert(market_id, winner_info); - - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - - let possession = Possession::Paid { owner: ALICE, fee: VotingOutcomeFee::get() }; - - let new_outcome_info = OutcomeInfo { outcome_sum, possession }; - - let expected = GlobalDisputeInfo { - winner_outcome: outcome, - outcome_info: new_outcome_info, - status: GdStatus::Finished, - }; - - let actual = crate::GlobalDisputesInfo::::get(market_id).unwrap(); - assert_eq!(expected, actual); - - assert!(crate::Winners::::iter().next().is_none()); - }); - } - - #[test] - fn on_runtime_sets_new_global_disputes_storage_shared() { - ExtBuilder::default().build().execute_with(|| { - set_up_chain(); - - let market_id = 0u128; - - let outcome_sum = 42u128.saturated_into::>(); - let owners = BoundedVec::try_from(vec![ALICE, BOB]).unwrap(); - - let outcome_info = OldOutcomeInfo { outcome_sum, owners: owners.clone() }; - let outcome = OutcomeReport::Categorical(42u16); - let winner_info = - OldWinnerInfo { outcome: outcome.clone(), outcome_info, is_finished: true }; - - crate::Winners::::insert(market_id, winner_info); - - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - - let possession = Possession::Shared { owners }; - - let new_outcome_info = OutcomeInfo { outcome_sum, possession }; - - let expected = GlobalDisputeInfo { - winner_outcome: outcome, - outcome_info: new_outcome_info, - status: GdStatus::Finished, - }; - - let actual = crate::GlobalDisputesInfo::::get(market_id).unwrap(); - assert_eq!(expected, actual); - - assert!(crate::Winners::::iter().next().is_none()); - }); - } - - #[test] - fn on_runtime_sets_new_outcomes_storage_value_shared() { - ExtBuilder::default().build().execute_with(|| { - set_up_chain(); - - let outcome = OutcomeReport::Categorical(0u16); - let hash = - crate::Outcomes::::hashed_key_for::(0, outcome); - - let outcome_sum = 42u128.saturated_into::>(); - let owners = BoundedVec::try_from(vec![ALICE, BOB]).unwrap(); - - let outcome_info = OldOutcomeInfo { outcome_sum, owners: owners.clone() }; - - put_storage_value::>( - GLOBAL_DISPUTES, - GD_OUTCOMES, - &hash, - outcome_info, - ); - - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - - let possession = Possession::Shared { owners }; - let expected = OutcomeInfo { outcome_sum, possession }; - - let actual = frame_support::migration::get_storage_value::>( - GLOBAL_DISPUTES, - GD_OUTCOMES, - &hash, - ) - .unwrap(); - assert_eq!(expected, actual); - }); - } - - #[test] - fn on_runtime_sets_new_outcomes_storage_value_paid() { - ExtBuilder::default().build().execute_with(|| { - set_up_chain(); - - let outcome = OutcomeReport::Categorical(0u16); - let hash = - crate::Outcomes::::hashed_key_for::(0, outcome); - - let outcome_sum = 42u128.saturated_into::>(); - let owners = BoundedVec::try_from(vec![ALICE]).unwrap(); - - let outcome_info = OldOutcomeInfo { outcome_sum, owners }; - - put_storage_value::>( - GLOBAL_DISPUTES, - GD_OUTCOMES, - &hash, - outcome_info, - ); - - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - - let possession = Possession::Paid { owner: ALICE, fee: VotingOutcomeFee::get() }; - let expected = OutcomeInfo { outcome_sum, possession }; - - let actual = frame_support::migration::get_storage_value::>( - GLOBAL_DISPUTES, - GD_OUTCOMES, - &hash, - ) - .unwrap(); - assert_eq!(expected, actual); - }); - } - - #[test] - fn on_runtime_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - // storage migration already executed (storage version is incremented already) - StorageVersion::new(GD_NEXT_STORAGE_VERSION).put::>(); - - let outcome = OutcomeReport::Categorical(0u16); - let hash = - crate::Outcomes::::hashed_key_for::(0, outcome); - - let outcome_info = OldOutcomeInfo { - outcome_sum: 0u128.saturated_into::>(), - owners: BoundedVec::try_from(vec![ALICE]).unwrap(), - }; - - put_storage_value::>( - GLOBAL_DISPUTES, - GD_OUTCOMES, - &hash, - outcome_info, - ); - - ModifyGlobalDisputesStructures::::on_runtime_upgrade(); - - // no changes should have been made, because the storage version was already incremented - assert!( - get_storage_value::>(GLOBAL_DISPUTES, GD_OUTCOMES, &hash) - .is_none() - ); - }); - } - - fn set_up_chain() { - StorageVersion::new(GD_REQUIRED_STORAGE_VERSION).put::>(); - } -} diff --git a/zrml/global-disputes/src/utils.rs b/zrml/global-disputes/src/utils.rs index 866bb06e5..bd87378bf 100644 --- a/zrml/global-disputes/src/utils.rs +++ b/zrml/global-disputes/src/utils.rs @@ -45,7 +45,9 @@ where 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, + dispute_mechanism: Some( + zeitgeist_primitives::types::MarketDisputeMechanism::SimpleDisputes, + ), metadata: Default::default(), oracle: T::GlobalDisputesPalletId::get().into_account_truncating(), period: zeitgeist_primitives::types::MarketPeriod::Block(Default::default()), diff --git a/zrml/liquidity-mining/src/tests.rs b/zrml/liquidity-mining/src/tests.rs index 380ed39dc..88c0f7c98 100644 --- a/zrml/liquidity-mining/src/tests.rs +++ b/zrml/liquidity-mining/src/tests.rs @@ -208,7 +208,7 @@ fn create_default_market(market_id: u128, period: Range) { creator_fee: sp_runtime::Perbill::zero(), creator: 0, market_type: MarketType::Categorical(0), - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), metadata: vec![], oracle: 0, period: MarketPeriod::Block(period), diff --git a/zrml/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index a65462392..025091e19 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -39,7 +39,7 @@ const MARKET_DUMMY: Market Asset { diff --git a/zrml/orderbook-v1/src/benchmarks.rs b/zrml/orderbook-v1/src/benchmarks.rs index e60585e88..8f51b5d23 100644 --- a/zrml/orderbook-v1/src/benchmarks.rs +++ b/zrml/orderbook-v1/src/benchmarks.rs @@ -24,10 +24,11 @@ #![allow(clippy::type_complexity)] use super::*; +use crate::utils::market_mock; #[cfg(test)] use crate::Pallet as OrderBook; use frame_benchmarking::{account, benchmarks, whitelisted_caller}; -use frame_support::{dispatch::UnfilteredDispatchable, traits::Currency}; +use frame_support::dispatch::UnfilteredDispatchable; use frame_system::RawOrigin; use orml_traits::MultiCurrency; use sp_runtime::SaturatedConversion; @@ -37,71 +38,80 @@ use zeitgeist_primitives::{constants::BASE, types::Asset}; fn generate_funded_account(seed: Option) -> Result { let acc = if let Some(s) = seed { account("AssetHolder", 0, s) } else { whitelisted_caller() }; - let asset = Asset::CategoricalOutcome::(0u32.into(), 0); - T::Shares::deposit(asset, &acc, BASE.saturating_mul(1_000).saturated_into())?; - let _ = T::Currency::deposit_creating(&acc, BASE.saturating_mul(1_000).saturated_into()); + let outcome_asset = Asset::CategoricalOutcome::>(0u32.into(), 0); + T::AssetManager::deposit(outcome_asset, &acc, BASE.saturating_mul(1_000).saturated_into())?; + let _ = T::AssetManager::deposit(Asset::Ztg, &acc, BASE.saturating_mul(1_000).saturated_into()); Ok(acc) } // Creates an account and gives it asset and currency. `seed` specifies the account seed, // None will return a whitelisted account -// Returns `account`, `asset`, `amount` and `price` +// Returns `account`, `asset`, `outcome_asset_amount` and `base_asset_amount` fn order_common_parameters( seed: Option, -) -> Result<(T::AccountId, Asset, BalanceOf, BalanceOf), &'static str> { +) -> Result< + (T::AccountId, Asset>, BalanceOf, BalanceOf, MarketIdOf), + &'static str, +> { let acc = generate_funded_account::(seed)?; - let asset = Asset::CategoricalOutcome::(0u32.into(), 0); - let amt: BalanceOf = BASE.saturated_into(); - let prc: BalanceOf = 1u32.into(); - Ok((acc, asset, amt, prc)) + let outcome_asset = Asset::CategoricalOutcome::>(0u32.into(), 0); + let outcome_asset_amount: BalanceOf = BASE.saturated_into(); + let base_asset_amount: BalanceOf = 100u32.into(); + let market_id: MarketIdOf = 0u32.into(); + let market = market_mock::(); + T::MarketCommons::push_market(market.clone()).unwrap(); + Ok((acc, outcome_asset, outcome_asset_amount, base_asset_amount, market_id)) } // Creates an order of type `order_type`. `seed` specifies the account seed, // None will return a whitelisted account -// Returns `account`, `asset` and `order_hash` -fn create_order( +// Returns `account`, `asset`, `order_id` +fn place_order( order_type: OrderSide, seed: Option, -) -> Result<(T::AccountId, Asset, T::Hash), &'static str> { - let (acc, asset, amount, price) = order_common_parameters::(seed)?; - let _ = Call::::make_order { asset, side: order_type.clone(), amount, price } - .dispatch_bypass_filter(RawOrigin::Signed(acc.clone()).into())?; +) -> Result<(T::AccountId, MarketIdOf, OrderId), &'static str> { + let (acc, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = + order_common_parameters::(seed)?; - if order_type == OrderSide::Bid { - let hash = Pallet::::bids(asset).last().copied().ok_or("No bids found")?; - Ok((acc, asset, hash)) - } else { - let hash = Pallet::::asks(asset).last().copied().ok_or("No asks found")?; - Ok((acc, asset, hash)) + let order_id = >::get(); + let _ = Call::::place_order { + market_id, + outcome_asset, + side: order_type.clone(), + outcome_asset_amount, + base_asset_amount, } + .dispatch_bypass_filter(RawOrigin::Signed(acc.clone()).into())?; + + Ok((acc, market_id, order_id)) } benchmarks! { - cancel_order_ask { - let (caller, asset, order_hash) = create_order::(OrderSide::Ask, None)?; - }: cancel_order(RawOrigin::Signed(caller), asset, order_hash) + remove_order_ask { + let (caller, _, order_id) = place_order::(OrderSide::Ask, None)?; + }: remove_order(RawOrigin::Signed(caller), order_id) - cancel_order_bid { - let (caller, asset, order_hash) = create_order::(OrderSide::Bid, None)?; - }: cancel_order(RawOrigin::Signed(caller), asset, order_hash) + remove_order_bid { + let (caller, _, order_id) = place_order::(OrderSide::Bid, None)?; + }: remove_order(RawOrigin::Signed(caller), order_id) fill_order_ask { let caller = generate_funded_account::(None)?; - let (_, _, order_hash) = create_order::(OrderSide::Ask, Some(0))?; - }: fill_order(RawOrigin::Signed(caller), order_hash) + let (_, _, order_id) = place_order::(OrderSide::Ask, Some(0))?; + }: fill_order(RawOrigin::Signed(caller), order_id, None) fill_order_bid { let caller = generate_funded_account::(None)?; - let (_, _, order_hash) = create_order::(OrderSide::Bid, Some(0))?; - }: fill_order(RawOrigin::Signed(caller), order_hash) + let (_, _, order_id) = place_order::(OrderSide::Bid, Some(0))?; + }: fill_order(RawOrigin::Signed(caller), order_id, None) - make_order_ask { - let (caller, asset, amt, prc) = order_common_parameters::(None)?; - }: make_order(RawOrigin::Signed(caller), asset, OrderSide::Ask, amt, prc) + place_order_ask { + let (caller, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = order_common_parameters::(None)?; + }: place_order(RawOrigin::Signed(caller), market_id, outcome_asset, OrderSide::Ask, outcome_asset_amount, base_asset_amount) - make_order_bid { - let (caller, asset, amt, prc) = order_common_parameters::(None)?; - }: make_order(RawOrigin::Signed(caller), asset, OrderSide::Bid, amt, prc) + place_order_bid { + let (caller, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = order_common_parameters::(None)?; + }: place_order(RawOrigin::Signed(caller), market_id, outcome_asset, OrderSide::Bid, outcome_asset_amount, base_asset_amount) impl_benchmark_test_suite!( OrderBook, diff --git a/zrml/orderbook-v1/src/lib.rs b/zrml/orderbook-v1/src/lib.rs index ba80157c7..946896e37 100644 --- a/zrml/orderbook-v1/src/lib.rs +++ b/zrml/orderbook-v1/src/lib.rs @@ -21,373 +21,437 @@ extern crate alloc; +use crate::{types::*, weights::*}; +use alloc::{vec, vec::Vec}; +use core::marker::PhantomData; +use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, + pallet_prelude::{OptionQuery, StorageMap, StorageValue, ValueQuery}, + traits::{Currency, IsType, StorageVersion}, + transactional, PalletId, Twox64Concat, +}; +use frame_system::{ensure_signed, pallet_prelude::OriginFor}; +use orml_traits::{BalanceStatus, MultiCurrency, NamedMultiReservableCurrency}; pub use pallet::*; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; use sp_runtime::{ - traits::{CheckedMul, CheckedSub}, - ArithmeticError, DispatchError, RuntimeDebug, + traits::{CheckedSub, Get, Zero}, + ArithmeticError, Perquintill, SaturatedConversion, Saturating, +}; +use zeitgeist_primitives::{ + traits::MarketCommonsPalletApi, + types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; -use zeitgeist_primitives::types::Asset; #[cfg(feature = "runtime-benchmarks")] mod benchmarks; pub mod mock; #[cfg(test)] mod tests; +pub mod types; +mod utils; pub mod weights; #[frame_support::pallet] mod pallet { - use crate::{weights::*, Order, OrderSide}; - use core::{cmp, marker::PhantomData}; - use frame_support::{ - dispatch::DispatchResultWithPostInfo, - ensure, - pallet_prelude::{ConstU32, StorageMap, StorageValue, ValueQuery}, - traits::{ - Currency, ExistenceRequirement, Hooks, IsType, ReservableCurrency, StorageVersion, - WithdrawReasons, - }, - transactional, Blake2_128Concat, BoundedVec, Identity, - }; - use frame_system::{ensure_signed, pallet_prelude::OriginFor}; - use orml_traits::{MultiCurrency, MultiReservableCurrency}; - use parity_scale_codec::Encode; - use sp_runtime::{ - traits::{Hash, Zero}, - ArithmeticError, DispatchError, - }; - use zeitgeist_primitives::{traits::MarketId, types::Asset}; + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Shares of outcome assets and native currency + type AssetManager: NamedMultiReservableCurrency< + Self::AccountId, + CurrencyId = Asset>, + ReserveIdentifier = [u8; 8], + >; + + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type MarketCommons: MarketCommonsPalletApi; + + #[pallet::constant] + type PalletId: Get; + + type WeightInfo: WeightInfoZeitgeist; + } /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); - pub(crate) type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; + pub(crate) type BalanceOf = <::AssetManager as MultiCurrency< + ::AccountId, + >>::Balance; + pub(crate) type MarketIdOf = + <::MarketCommons as MarketCommonsPalletApi>::MarketId; + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type OrderOf = Order, BalanceOf, MarketIdOf>; + pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; + pub(crate) type MarketCommonsBalanceOf = + <<::MarketCommons as MarketCommonsPalletApi>::Currency as Currency< + AccountIdOf, + >>::Balance; + pub(crate) type MarketOf = Market< + AccountIdOf, + MarketCommonsBalanceOf, + ::BlockNumber, + MomentOf, + Asset>, + >; + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::storage] + pub type NextOrderId = StorageValue<_, OrderId, ValueQuery>; + + #[pallet::storage] + pub type Orders = StorageMap<_, Twox64Concat, OrderId, OrderOf, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(fn deposit_event)] + pub enum Event + where + T: Config, + { + OrderFilled { + order_id: OrderId, + maker: AccountIdOf, + taker: AccountIdOf, + filled: BalanceOf, + unfilled_outcome_asset_amount: BalanceOf, + unfilled_base_asset_amount: BalanceOf, + }, + OrderPlaced { + order_id: OrderId, + order: OrderOf, + }, + OrderRemoved { + order_id: OrderId, + maker: T::AccountId, + }, + } + + #[pallet::error] + pub enum Error { + /// The sender is not the order creator. + NotOrderCreator, + /// The order does not exist. + OrderDoesNotExist, + /// The market is not active. + MarketIsNotActive, + /// The scoring rule is not orderbook. + InvalidScoringRule, + /// The specified amount parameter is too high for the order. + AmountTooHighForOrder, + /// The specified amount parameter is zero. + AmountIsZero, + /// The specified outcome asset is not part of the market. + InvalidOutcomeAsset, + /// The maker partial fill leads to a too low quotient for the next order execution. + MakerPartialFillTooLow, + } #[pallet::call] impl Pallet { + /// Removes an order. + /// + /// # Weight + /// + /// Complexity: `O(1)` #[pallet::call_index(0)] #[pallet::weight( - T::WeightInfo::cancel_order_ask().max(T::WeightInfo::cancel_order_bid()) + T::WeightInfo::remove_order_ask().max(T::WeightInfo::remove_order_bid()) )] #[transactional] - pub fn cancel_order( - origin: OriginFor, - asset: Asset, - order_hash: T::Hash, - ) -> DispatchResultWithPostInfo { + pub fn remove_order(origin: OriginFor, order_id: OrderId) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; - let mut bid = true; - - if let Some(order_data) = Self::order_data(order_hash) { - let maker = &order_data.maker; - ensure!(sender == *maker, Error::::NotOrderCreator); - - match order_data.side { - OrderSide::Bid => { - let cost = order_data.cost()?; - T::Currency::unreserve(maker, cost); - let mut bids = Self::bids(asset); - remove_item::(&mut bids, order_hash); - >::insert(asset, bids); - } - OrderSide::Ask => { - T::Shares::unreserve(order_data.asset, maker, order_data.total); - let mut asks = Self::asks(asset); - remove_item::(&mut asks, order_hash); - >::insert(asset, asks); - bid = false; - } - } - >::remove(order_hash); - } else { - return Err(Error::::OrderDoesNotExist.into()); + let order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; + + let maker = &order_data.maker; + ensure!(sender == *maker, Error::::NotOrderCreator); + + match order_data.side { + OrderSide::Bid => { + T::AssetManager::unreserve_named( + &Self::reserve_id(), + order_data.base_asset, + maker, + order_data.base_asset_amount, + ); + } + OrderSide::Ask => { + T::AssetManager::unreserve_named( + &Self::reserve_id(), + order_data.outcome_asset, + maker, + order_data.outcome_asset_amount, + ); + } } - if bid { - Ok(Some(T::WeightInfo::cancel_order_bid()).into()) - } else { - Ok(Some(T::WeightInfo::cancel_order_ask()).into()) + >::remove(order_id); + + Self::deposit_event(Event::OrderRemoved { order_id, maker: maker.clone() }); + + match order_data.side { + OrderSide::Bid => Ok(Some(T::WeightInfo::remove_order_bid()).into()), + OrderSide::Ask => Ok(Some(T::WeightInfo::remove_order_ask()).into()), } } + /// Fill an existing order entirely (`maker_partial_fill` = None) + /// or partially (`maker_partial_fill` = Some(partial_amount)). + /// + /// NOTE: The `maker_partial_fill` is the partial amount of what the maker wants to fill. + /// + /// # Weight + /// + /// Complexity: `O(1)` #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::fill_order_ask().max(T::WeightInfo::fill_order_bid()) )] #[transactional] - pub fn fill_order(origin: OriginFor, order_hash: T::Hash) -> DispatchResultWithPostInfo { - let sender = ensure_signed(origin)?; - let mut bid = true; - - if let Some(order_data) = Self::order_data(order_hash) { - ensure!(order_data.taker.is_none(), Error::::OrderAlreadyTaken); + pub fn fill_order( + origin: OriginFor, + order_id: OrderId, + maker_partial_fill: Option>, + ) -> DispatchResultWithPostInfo { + let taker = ensure_signed(origin)?; - let cost = order_data.cost()?; + let mut order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; + let market = T::MarketCommons::market(&order_data.market_id)?; + ensure!(market.scoring_rule == ScoringRule::Orderbook, Error::::InvalidScoringRule); + ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); + let base_asset = market.base_asset; - let maker = order_data.maker; + let makers_requested_total = match order_data.side { + OrderSide::Bid => order_data.outcome_asset_amount, + OrderSide::Ask => order_data.base_asset_amount, + }; + let maker_fill = maker_partial_fill.unwrap_or(makers_requested_total); + ensure!(!maker_fill.is_zero(), Error::::AmountIsZero); + ensure!(maker_fill <= makers_requested_total, Error::::AmountTooHighForOrder); - match order_data.side { - OrderSide::Bid => { - T::Shares::ensure_can_withdraw( - order_data.asset, - &sender, - order_data.total, - )?; + let maker = order_data.maker.clone(); - T::Currency::unreserve(&maker, cost); - T::Currency::transfer( - &maker, - &sender, - cost, - ExistenceRequirement::AllowDeath, - )?; + // the reserve of the maker should always be enough to repatriate successfully, e.g. taker gets a little bit less + // it should always ensure that the maker's request (maker_fill) is fully filled + match order_data.side { + OrderSide::Bid => { + T::AssetManager::ensure_can_withdraw( + order_data.outcome_asset, + &taker, + maker_fill, + )?; + + // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. + // This ensures that the reserve of the maker is always enough to repatriate successfully! + let ratio = Perquintill::from_rational( + maker_fill.saturated_into::(), + order_data.outcome_asset_amount.saturated_into::(), + ); + let taker_fill = ratio + .mul_floor(order_data.base_asset_amount.saturated_into::()) + .saturated_into::>(); + + T::AssetManager::repatriate_reserved_named( + &Self::reserve_id(), + base_asset, + &maker, + &taker, + taker_fill, + BalanceStatus::Free, + )?; + + T::AssetManager::transfer( + order_data.outcome_asset, + &taker, + &maker, + maker_fill, + )?; + + order_data.base_asset_amount = order_data + .base_asset_amount + .checked_sub(&taker_fill) + .ok_or(ArithmeticError::Underflow)?; + order_data.outcome_asset_amount = order_data + .outcome_asset_amount + .checked_sub(&maker_fill) + .ok_or(ArithmeticError::Underflow)?; + // this ensures that partial fills, which fill nearly the whole order, are not executed + // this protects the last fill happening without a division by zero for `Perquintill::from_rational` + let is_ratio_quotient_valid = order_data.outcome_asset_amount.is_zero() + || order_data.outcome_asset_amount.saturated_into::() >= 100u128; + ensure!(is_ratio_quotient_valid, Error::::MakerPartialFillTooLow); + } + OrderSide::Ask => { + T::AssetManager::ensure_can_withdraw(base_asset, &taker, maker_fill)?; - T::Shares::transfer(order_data.asset, &sender, &maker, order_data.total)?; - } - OrderSide::Ask => { - T::Currency::ensure_can_withdraw( - &sender, - cost, - WithdrawReasons::all(), - Zero::zero(), - )?; - - T::Shares::unreserve(order_data.asset, &maker, order_data.total); - T::Shares::transfer(order_data.asset, &maker, &sender, order_data.total)?; - - T::Currency::transfer( - &sender, - &maker, - cost, - ExistenceRequirement::AllowDeath, - )?; - bid = false; - } + // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. + // This ensures that the reserve of the maker is always enough to repatriate successfully! + let ratio = Perquintill::from_rational( + maker_fill.saturated_into::(), + order_data.base_asset_amount.saturated_into::(), + ); + let taker_fill = ratio + .mul_floor(order_data.outcome_asset_amount.saturated_into::()) + .saturated_into::>(); + + T::AssetManager::repatriate_reserved_named( + &Self::reserve_id(), + order_data.outcome_asset, + &maker, + &taker, + taker_fill, + BalanceStatus::Free, + )?; + + T::AssetManager::transfer(base_asset, &taker, &maker, maker_fill)?; + + order_data.outcome_asset_amount = order_data + .outcome_asset_amount + .checked_sub(&taker_fill) + .ok_or(ArithmeticError::Underflow)?; + order_data.base_asset_amount = order_data + .base_asset_amount + .checked_sub(&maker_fill) + .ok_or(ArithmeticError::Underflow)?; + // this ensures that partial fills, which fill nearly the whole order, are not executed + // this protects the last fill happening without a division by zero for `Perquintill::from_rational` + let is_ratio_quotient_valid = order_data.base_asset_amount.is_zero() + || order_data.base_asset_amount.saturated_into::() >= 100u128; + ensure!(is_ratio_quotient_valid, Error::::MakerPartialFillTooLow); } + }; + + let unfilled_outcome_asset_amount = order_data.outcome_asset_amount; + let unfilled_base_asset_amount = order_data.base_asset_amount; + let total_unfilled = + unfilled_outcome_asset_amount.saturating_add(unfilled_base_asset_amount); - Self::deposit_event(Event::OrderFilled(sender, order_hash)); + if total_unfilled.is_zero() { + >::remove(order_id); } else { - return Err(Error::::OrderDoesNotExist.into()); + >::insert(order_id, order_data.clone()); } - if bid { - Ok(Some(T::WeightInfo::fill_order_bid()).into()) - } else { - Ok(Some(T::WeightInfo::fill_order_ask()).into()) + Self::deposit_event(Event::OrderFilled { + order_id, + maker, + taker: taker.clone(), + filled: maker_fill, + unfilled_outcome_asset_amount, + unfilled_base_asset_amount, + }); + + match order_data.side { + OrderSide::Bid => Ok(Some(T::WeightInfo::fill_order_bid()).into()), + OrderSide::Ask => Ok(Some(T::WeightInfo::fill_order_ask()).into()), } } + /// Place a new order. + /// + /// # Weight + /// + /// Complexity: `O(1)` #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::make_order_ask().max(T::WeightInfo::make_order_bid()) + T::WeightInfo::place_order_ask().max(T::WeightInfo::place_order_bid()) )] #[transactional] - pub fn make_order( + pub fn place_order( origin: OriginFor, - asset: Asset, + #[pallet::compact] market_id: MarketIdOf, + outcome_asset: Asset>, side: OrderSide, - #[pallet::compact] amount: BalanceOf, - #[pallet::compact] price: BalanceOf, + #[pallet::compact] outcome_asset_amount: BalanceOf, + #[pallet::compact] base_asset_amount: BalanceOf, ) -> DispatchResultWithPostInfo { - let sender = ensure_signed(origin)?; + let who = ensure_signed(origin)?; - // Only store nonce in memory for now. - let nonce = >::get(); - let hash = Self::order_hash(&sender, asset, nonce); - let mut bid = true; + let market = T::MarketCommons::market(&market_id)?; + ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); + ensure!(market.scoring_rule == ScoringRule::Orderbook, Error::::InvalidScoringRule); + let market_assets = Self::outcome_assets(market_id, &market); + ensure!( + market_assets.binary_search(&outcome_asset).is_ok(), + Error::::InvalidOutcomeAsset + ); + let base_asset = market.base_asset; + + let order_id = >::get(); + let next_order_id = order_id.checked_add(1).ok_or(ArithmeticError::Overflow)?; - // Love the smell of fresh orders in the morning. let order = Order { + market_id, side: side.clone(), - maker: sender.clone(), - taker: None, - asset, - total: amount, - price, - filled: Zero::zero(), + maker: who.clone(), + outcome_asset, + base_asset, + outcome_asset_amount, + base_asset_amount, }; - let cost = order.cost()?; - match side { OrderSide::Bid => { - ensure!( - T::Currency::can_reserve(&sender, cost), - Error::::InsufficientBalance, - ); - - >::try_mutate(asset, |b: &mut BoundedVec| { - b.try_push(hash).map_err(|_| >::StorageOverflow) - })?; - - T::Currency::reserve(&sender, cost)?; + T::AssetManager::reserve_named( + &Self::reserve_id(), + base_asset, + &who, + base_asset_amount, + )?; } OrderSide::Ask => { - ensure!( - T::Shares::can_reserve(asset, &sender, amount), - Error::::InsufficientBalance, - ); - - >::try_mutate(asset, |a| { - a.try_push(hash).map_err(|_| >::StorageOverflow) - })?; - - T::Shares::reserve(asset, &sender, amount)?; - bid = false; + T::AssetManager::reserve_named( + &Self::reserve_id(), + outcome_asset, + &who, + outcome_asset_amount, + )?; } } - >::insert(hash, Some(order.clone())); - >::try_mutate(|n| { - *n = n.checked_add(1).ok_or(ArithmeticError::Overflow)?; - Ok::<_, DispatchError>(()) - })?; - Self::deposit_event(Event::OrderMade(sender, hash, order)); + >::insert(order_id, order.clone()); + >::put(next_order_id); + Self::deposit_event(Event::OrderPlaced { order_id, order }); - if bid { - Ok(Some(T::WeightInfo::make_order_bid()).into()) - } else { - Ok(Some(T::WeightInfo::make_order_ask()).into()) + match side { + OrderSide::Bid => Ok(Some(T::WeightInfo::place_order_bid()).into()), + OrderSide::Ask => Ok(Some(T::WeightInfo::place_order_ask()).into()), } } } - #[pallet::config] - pub trait Config: frame_system::Config { - type Currency: ReservableCurrency; - - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - type MarketId: MarketId; - - type Shares: MultiReservableCurrency< - Self::AccountId, - Balance = BalanceOf, - CurrencyId = Asset, - >; - - type WeightInfo: WeightInfoZeitgeist; - } - - #[pallet::error] - pub enum Error { - /// Insufficient balance. - InsufficientBalance, - NotOrderCreator, - /// The order was already taken. - OrderAlreadyTaken, - /// The order does not exist. - OrderDoesNotExist, - /// It was tried to append an item to storage beyond the boundaries. - StorageOverflow, - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event - where - T: Config, - { - /// [taker, order_hash] - OrderFilled(::AccountId, ::Hash), - /// [maker, order_hash, order_data] - OrderMade( - ::AccountId, - ::Hash, - Order, T::MarketId>, - ), - } - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); - - #[pallet::storage] - #[pallet::getter(fn asks)] - pub type Asks = StorageMap< - _, - Blake2_128Concat, - Asset, - BoundedVec>, - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn bids)] - pub type Bids = StorageMap< - _, - Blake2_128Concat, - Asset, - BoundedVec>, - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn nonce)] - pub type Nonce = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn order_data)] - pub type OrderData = StorageMap< - _, - Identity, - T::Hash, - Option, T::MarketId>>, - ValueQuery, - >; - impl Pallet { - pub fn order_hash( - creator: &T::AccountId, - asset: Asset, - nonce: u64, - ) -> T::Hash { - (&creator, asset, nonce).using_encoded(T::Hashing::hash) + /// The reserve ID of the orderbook pallet. + #[inline] + pub fn reserve_id() -> [u8; 8] { + T::PalletId::get().0 } - } - - fn remove_item(items: &mut BoundedVec, item: I) { - let pos = items.iter().position(|&i| i == item).unwrap(); - items.swap_remove(pos); - } -} -#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub enum OrderSide { - Bid, - Ask, -} - -#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Order { - side: OrderSide, - maker: AccountId, - taker: Option, - asset: Asset, - total: Balance, - price: Balance, - filled: Balance, -} - -impl Order -where - Balance: CheckedSub + CheckedMul, - MarketId: MaxEncodedLen, -{ - pub fn cost(&self) -> Result { - match self.total.checked_sub(&self.filled) { - Some(subtotal) => match subtotal.checked_mul(&self.price) { - Some(cost) => Ok(cost), - _ => Err(DispatchError::Arithmetic(ArithmeticError::Overflow)), - }, - _ => Err(DispatchError::Arithmetic(ArithmeticError::Overflow)), + 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::CategoricalOutcome(market_id, i)); + } + assets + } + MarketType::Scalar(_) => { + vec![ + Asset::ScalarOutcome(market_id, ScalarPosition::Long), + Asset::ScalarOutcome(market_id, ScalarPosition::Short), + ] + } + } } } } diff --git a/zrml/orderbook-v1/src/mock.rs b/zrml/orderbook-v1/src/mock.rs index d7570fc83..4b42d12c7 100644 --- a/zrml/orderbook-v1/src/mock.rs +++ b/zrml/orderbook-v1/src/mock.rs @@ -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. @@ -26,11 +26,12 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposit, ExistentialDeposits, MaxLocks, MaxReserves, BASE, + BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, + MaxReserves, MinimumPeriod, OrderbookPalletId, PmPalletId, BASE, }, types::{ - AccountIdTest, Amount, Balance, BlockNumber, BlockTest, CurrencyId, Hash, Index, MarketId, - UncheckedExtrinsicTest, + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, + Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -45,17 +46,20 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsicTest, { Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + MarketCommons: zrml_market_commons::{Pallet, Storage}, Orderbook: orderbook_v1::{Call, Event, Pallet}, System: frame_system::{Call, Config, Event, Pallet, Storage}, - Tokens: orml_tokens::{Config, Pallet, Storage}, + Tokens: orml_tokens::{Config, Event, Pallet, Storage}, + AssetManager: orml_currencies::{Call, Pallet, Storage}, + Timestamp: pallet_timestamp::{Pallet}, } ); impl crate::Config for Runtime { - type Currency = Balances; - type RuntimeEvent = (); - type MarketId = MarketId; - type Shares = Tokens; + type AssetManager = AssetManager; + type RuntimeEvent = RuntimeEvent; + type MarketCommons = MarketCommons; + type PalletId = OrderbookPalletId; type WeightInfo = orderbook_v1::weights::WeightInfo; } @@ -69,7 +73,7 @@ impl frame_system::Config for Runtime { type BlockWeights = (); type RuntimeCall = RuntimeCall; type DbWeight = (); - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type Hash = Hash; type Hashing = BlakeTwo256; type Header = Header; @@ -86,12 +90,19 @@ impl frame_system::Config for Runtime { type OnSetCode = (); } +impl orml_currencies::Config for Runtime { + type GetNativeCurrencyId = GetNativeCurrencyId; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type WeightInfo = (); +} + impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; type CurrencyId = CurrencyId; type DustRemovalWhitelist = Everything; - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; type MaxLocks = (); type MaxReserves = MaxReserves; @@ -104,7 +115,7 @@ impl pallet_balances::Config for Runtime { type AccountStore = System; type Balance = Balance; type DustRemoval = (); - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type MaxLocks = MaxLocks; type MaxReserves = MaxReserves; @@ -112,6 +123,20 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + +impl zrml_market_commons::Config for Runtime { + type Currency = Balances; + type MarketId = MarketId; + type PredictionMarketsPalletId = PmPalletId; + type Timestamp = Timestamp; +} + pub struct ExtBuilder { balances: Vec<(AccountIdTest, Balance)>, } @@ -129,6 +154,10 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); - t.into() + let mut t: sp_io::TestExternalities = t.into(); + + t.execute_with(|| System::set_block_number(1)); + + t } } diff --git a/zrml/orderbook-v1/src/tests.rs b/zrml/orderbook-v1/src/tests.rs index 4bbbd31af..3c5ae098d 100644 --- a/zrml/orderbook-v1/src/tests.rs +++ b/zrml/orderbook-v1/src/tests.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,30 +16,110 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{mock::*, Error, OrderSide}; +use crate::{mock::*, utils::market_mock, Error, Event, Order, OrderSide, Orders}; use frame_support::{ assert_noop, assert_ok, traits::{Currency, ReservableCurrency}, }; use orml_traits::{MultiCurrency, MultiReservableCurrency}; +use test_case::test_case; use zeitgeist_primitives::{ constants::BASE, - types::{AccountIdTest, Asset}, + types::{AccountIdTest, Asset, ScoringRule}, }; +use zrml_market_commons::{MarketCommonsPalletApi, Markets}; + +#[test_case(ScoringRule::CPMM; "CPMM")] +#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "Rikiddo")] +fn place_order_fails_with_wrong_scoring_rule(scoring_rule: ScoringRule) { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.scoring_rule = scoring_rule; + Ok(()) + })); + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + Asset::CategoricalOutcome(0, 2), + OrderSide::Bid, + 100, + 250, + ), + Error::::InvalidScoringRule + ); + }); +} + +#[test_case(ScoringRule::CPMM; "CPMM")] +#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "Rikiddo")] +fn fill_order_fails_with_wrong_scoring_rule(scoring_rule: ScoringRule) { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + + let order_id = 0u128; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + Asset::CategoricalOutcome(0, 2), + OrderSide::Bid, + 10, + 250, + )); + + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.scoring_rule = scoring_rule; + Ok(()) + })); + + assert_noop!( + Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None), + Error::::InvalidScoringRule + ); + }); +} #[test] -fn it_makes_orders() { +fn it_fails_order_does_not_exist() { ExtBuilder::default().build().execute_with(|| { + let order_id = 0u128; + assert_noop!( + Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None), + Error::::OrderDoesNotExist, + ); + + assert_noop!( + Orderbook::remove_order(RuntimeOrigin::signed(ALICE), order_id), + Error::::OrderDoesNotExist, + ); + }); +} + +#[test] +fn it_places_orders() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + // Give some shares for Bob. - assert_ok!(Tokens::deposit(Asset::CategoricalOutcome(0, 1), &BOB, 100)); + assert_ok!(AssetManager::deposit(Asset::CategoricalOutcome(0, 1), &BOB, 10)); // Make an order from Alice to buy shares. - assert_ok!(Orderbook::make_order( + assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), + market_id, Asset::CategoricalOutcome(0, 2), OrderSide::Bid, - 25, 10, + 250, )); let reserved_funds = @@ -46,8 +127,9 @@ fn it_makes_orders() { assert_eq!(reserved_funds, 250); // Make an order from Bob to sell shares. - assert_ok!(Orderbook::make_order( + assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), + market_id, Asset::CategoricalOutcome(0, 1), OrderSide::Ask, 10, @@ -60,55 +142,332 @@ fn it_makes_orders() { } #[test] -fn it_takes_orders() { +fn it_fills_ask_orders_fully() { ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + + let outcome_asset = Asset::CategoricalOutcome(0, 1); // Give some shares for Bob. - assert_ok!(Tokens::deposit(Asset::CategoricalOutcome(0, 1), &BOB, 100)); + assert_ok!(Tokens::deposit(outcome_asset, &BOB, 100)); // Make an order from Bob to sell shares. - assert_ok!(Orderbook::make_order( + assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), - Asset::CategoricalOutcome(0, 1), + market_id, + outcome_asset, OrderSide::Ask, 10, - 5, + 50, )); - let order_hash = Orderbook::order_hash(&BOB, Asset::CategoricalOutcome(0, 1), 0); - assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_hash)); + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 10); + + let order_id = 0u128; + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None)); + + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 0); + + System::assert_last_event( + Event::::OrderFilled { + order_id, + maker: BOB, + taker: ALICE, + filled: 50, + unfilled_outcome_asset_amount: 0, + unfilled_base_asset_amount: 0, + } + .into(), + ); let alice_bal = >::free_balance(&ALICE); - let alice_shares = Tokens::free_balance(Asset::CategoricalOutcome(0, 1), &ALICE); + let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); assert_eq!(alice_bal, BASE - 50); assert_eq!(alice_shares, 10); let bob_bal = >::free_balance(&BOB); - let bob_shares = Tokens::free_balance(Asset::CategoricalOutcome(0, 1), &BOB); + let bob_shares = Tokens::free_balance(outcome_asset, &BOB); assert_eq!(bob_bal, BASE + 50); assert_eq!(bob_shares, 90); }); } #[test] -fn it_cancels_orders() { +fn it_fills_bid_orders_fully() { ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + + let outcome_asset = Asset::CategoricalOutcome(0, 1); + + // Make an order from Bob to sell shares. + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(BOB), + market_id, + outcome_asset, + OrderSide::Bid, + 10, + 50, + )); + + let reserved_bob = Balances::reserved_balance(BOB); + assert_eq!(reserved_bob, 50); + + let order_id = 0u128; + assert_ok!(Tokens::deposit(outcome_asset, &ALICE, 10)); + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None)); + + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 0); + + System::assert_last_event( + Event::::OrderFilled { + order_id, + maker: BOB, + taker: ALICE, + filled: 10, + unfilled_outcome_asset_amount: 0, + unfilled_base_asset_amount: 0, + } + .into(), + ); + + let alice_bal = >::free_balance(&ALICE); + let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); + assert_eq!(alice_bal, BASE + 50); + assert_eq!(alice_shares, 0); + + let bob_bal = >::free_balance(&BOB); + let bob_shares = Tokens::free_balance(outcome_asset, &BOB); + assert_eq!(bob_bal, BASE - 50); + assert_eq!(bob_shares, 10); + }); +} + +#[test] +fn it_fills_bid_orders_partially() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + + let outcome_asset = Asset::CategoricalOutcome(0, 1); + + // Make an order from Bob to buy outcome tokens. + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(BOB), + market_id, + outcome_asset, + OrderSide::Bid, + 1000, + 5000, + )); + + let reserved_bob = Balances::reserved_balance(BOB); + assert_eq!(reserved_bob, 5000); + + let order_id = 0u128; + assert_ok!(Tokens::deposit(outcome_asset, &ALICE, 1000)); + + // instead of selling 1000 shares, Alice sells 700 shares + let portion = Some(700); + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, portion,)); + + let order = >::get(order_id).unwrap(); + assert_eq!( + order, + Order { + market_id, + side: OrderSide::Bid, + maker: BOB, + outcome_asset, + base_asset: Asset::Ztg, + // from 1000 to 300 changed (partially filled) + outcome_asset_amount: 300, + base_asset_amount: 1500, + } + ); + + let reserved_bob = Balances::reserved_balance(BOB); + // 5000 - (700 shares * 500 price) = 1500 + assert_eq!(reserved_bob, 1500); + + System::assert_last_event( + Event::::OrderFilled { + order_id, + maker: BOB, + taker: ALICE, + filled: 700, + unfilled_outcome_asset_amount: 300, + unfilled_base_asset_amount: 1500, + } + .into(), + ); + + let alice_bal = >::free_balance(&ALICE); + let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); + assert_eq!(alice_bal, BASE + 3500); + assert_eq!(alice_shares, 300); + + let bob_bal = >::free_balance(&BOB); + let bob_shares = Tokens::free_balance(outcome_asset, &BOB); + // 3500 of base_asset lost, 1500 of base_asset reserved + assert_eq!(bob_bal, BASE - 5000); + assert_eq!(bob_shares, 700); + + let reserved_bob = Balances::reserved_balance(BOB); + assert_eq!(reserved_bob, 1500); + }); +} + +#[test] +fn it_fills_ask_orders_partially() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let outcome_asset = Asset::CategoricalOutcome(0, 1); + + assert_ok!(Tokens::deposit(outcome_asset, &BOB, 2000)); + + // Make an order from Bob to sell outcome tokens. + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(BOB), + market_id, + outcome_asset, + OrderSide::Ask, + 1000, + 5000, + )); + + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 1000); + + let order_id = 0u128; + assert_ok!(Tokens::deposit(market.base_asset, &ALICE, 5000)); + + // instead of buying 5000 of the base asset, Alice buys 700 shares + let portion = Some(700); + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, portion,)); + + let order = >::get(order_id).unwrap(); + assert_eq!( + order, + Order { + market_id, + side: OrderSide::Ask, + maker: BOB, + outcome_asset, + base_asset: Asset::Ztg, + // from 1000 to 860 changed (partially filled) + outcome_asset_amount: 860, + // from 5000 to 4300 changed (partially filled) + base_asset_amount: 4300, + } + ); + + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 860); + + System::assert_last_event( + Event::::OrderFilled { + order_id, + maker: BOB, + taker: ALICE, + filled: 700, + unfilled_outcome_asset_amount: 860, + unfilled_base_asset_amount: 4300, + } + .into(), + ); + + let alice_bal = >::free_balance(&ALICE); + let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); + assert_eq!(alice_bal, BASE - 700); + assert_eq!(alice_shares, 140); + + let bob_bal = >::free_balance(&BOB); + let bob_shares = Tokens::free_balance(outcome_asset, &BOB); + assert_eq!(bob_bal, BASE + 700); + // ask order was adjusted from 1000 to 860, and bob had 2000 shares at start + assert_eq!(bob_shares, 1000); + + let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + assert_eq!(reserved_bob, 860); + }); +} + +#[test] +fn it_removes_orders() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market); + // Make an order from Alice to buy shares. let share_id = Asset::CategoricalOutcome(0, 2); - assert_ok!(Orderbook::make_order( + assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), + market_id, share_id, OrderSide::Bid, 25, 10 )); - let order_hash = Orderbook::order_hash(&ALICE, share_id, 0); + let order_id = 0u128; + System::assert_last_event( + Event::::OrderPlaced { + order_id: 0, + order: Order { + market_id, + side: OrderSide::Bid, + maker: ALICE, + outcome_asset: share_id, + base_asset: Asset::Ztg, + outcome_asset_amount: 25, + base_asset_amount: 10, + }, + } + .into(), + ); + + let order = >::get(order_id).unwrap(); + assert_eq!( + order, + Order { + market_id, + side: OrderSide::Bid, + maker: ALICE, + outcome_asset: share_id, + base_asset: Asset::Ztg, + outcome_asset_amount: 25, + base_asset_amount: 10, + } + ); assert_noop!( - Orderbook::cancel_order(RuntimeOrigin::signed(BOB), share_id, order_hash), + Orderbook::remove_order(RuntimeOrigin::signed(BOB), order_id), Error::::NotOrderCreator, ); - assert_ok!(Orderbook::cancel_order(RuntimeOrigin::signed(ALICE), share_id, order_hash)); + let reserved_funds = + >::reserved_balance(&ALICE); + assert_eq!(reserved_funds, 10); + + assert_ok!(Orderbook::remove_order(RuntimeOrigin::signed(ALICE), order_id)); + + let reserved_funds = + >::reserved_balance(&ALICE); + assert_eq!(reserved_funds, 0); + + assert!(>::get(order_id).is_none()); + + System::assert_last_event(Event::::OrderRemoved { order_id, maker: ALICE }.into()); }); } diff --git a/zrml/orderbook-v1/src/types.rs b/zrml/orderbook-v1/src/types.rs new file mode 100644 index 000000000..b0e1d2b73 --- /dev/null +++ b/zrml/orderbook-v1/src/types.rs @@ -0,0 +1,40 @@ +// 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 . + +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; +use zeitgeist_primitives::types::Asset; + +pub type OrderId = u128; + +#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub enum OrderSide { + Bid, + Ask, +} + +#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Order { + pub market_id: MarketId, + pub side: OrderSide, + pub maker: AccountId, + pub outcome_asset: Asset, + pub base_asset: Asset, + pub outcome_asset_amount: Balance, + pub base_asset_amount: Balance, +} diff --git a/zrml/orderbook-v1/src/utils.rs b/zrml/orderbook-v1/src/utils.rs new file mode 100644 index 000000000..bac412f2a --- /dev/null +++ b/zrml/orderbook-v1/src/utils.rs @@ -0,0 +1,66 @@ +// 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 . + +#![cfg(any(feature = "runtime-benchmarks", test))] + +use crate::*; +use frame_support::traits::Currency; +use sp_runtime::traits::AccountIdConversion; +use zeitgeist_primitives::{ + traits::MarketCommonsPalletApi, + types::{ + Asset, Deadlines, Market, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, ScoringRule, + }, +}; + +type CurrencyOf = <::MarketCommons as MarketCommonsPalletApi>::Currency; +type BalanceOf = as Currency<::AccountId>>::Balance; +type MarketOf = Market< + ::AccountId, + BalanceOf, + ::BlockNumber, + MomentOf, + Asset>, +>; + +pub(crate) fn market_mock() -> MarketOf +where + T: crate::Config, +{ + Market { + base_asset: Asset::Ztg, + creation: MarketCreation::Permissionless, + creator_fee: sp_runtime::Perbill::zero(), + creator: T::PalletId::get().into_account_truncating(), + market_type: MarketType::Categorical(64u16), + 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::Orderbook, + status: MarketStatus::Active, + bonds: Default::default(), + } +} diff --git a/zrml/orderbook-v1/src/weights.rs b/zrml/orderbook-v1/src/weights.rs index 4d02a09ba..9b173638b 100644 --- a/zrml/orderbook-v1/src/weights.rs +++ b/zrml/orderbook-v1/src/weights.rs @@ -1,5 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. -// Copyright 2021-2022 Zeitgeist PM LLC. +// Copyright 2023 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,38 +14,31 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - //! Autogenerated weights for zrml_orderbook_v1 //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 -//! DATE: 2021-08-20, STEPS: `[0, ]`, REPEAT: 30000, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2023-09-14`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `chralt`, CPU: `` +//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// target/release/zeitgeist +// ./target/release/zeitgeist // benchmark -// --chain -// dev -// --execution -// wasm -// --extrinsic -// * -// --output -// ./zrml/orderbook-v1/src/weights.rs -// --pallet -// zrml-orderbook-v1 -// --repeat -// 30000 -// --steps -// 0 -// --template -// ./misc/weight_template.hbs -// --wasm-execution -// compiled +// pallet +// --chain=dev +// --steps=10 +// --repeat=1000 +// --pallet=zrml_orderbook_v1 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./zrml/orderbook-v1/src/weights.rs +// --template=./misc/weight_template.hbs #![allow(unused_parens)] #![allow(unused_imports)] -#![allow(clippy::unnecessary_cast)] use core::marker::PhantomData; use frame_support::{traits::Get, weights::Weight}; @@ -54,45 +46,117 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_orderbook_v1 (automatically generated) pub trait WeightInfoZeitgeist { - fn cancel_order_ask() -> Weight; - fn cancel_order_bid() -> Weight; + fn remove_order_ask() -> Weight; + fn remove_order_bid() -> Weight; fn fill_order_ask() -> Weight; fn fill_order_bid() -> Weight; - fn make_order_ask() -> Weight; - fn make_order_bid() -> Weight; + fn place_order_ask() -> Weight; + fn place_order_bid() -> Weight; } /// Weight functions for zrml_orderbook_v1 (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { - fn cancel_order_ask() -> Weight { - Weight::from_ref_time(53_301_000) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + /// Storage: Orderbook Orders (r:1 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + /// Storage: Tokens Reserves (r:1 w:1) + /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:1 w:1) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + fn remove_order_ask() -> Weight { + // Proof Size summary in bytes: + // Measured: `635` + // Estimated: `8999` + // Minimum execution time: 28_000 nanoseconds. + Weight::from_parts(31_000_000, 8999) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - fn cancel_order_bid() -> Weight { - Weight::from_ref_time(49_023_000) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + /// Storage: Orderbook Orders (r:1 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + fn remove_order_bid() -> Weight { + // Proof Size summary in bytes: + // Measured: `316` + // Estimated: `6374` + // Minimum execution time: 27_000 nanoseconds. + Weight::from_parts(29_000_000, 6374) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: Orderbook Orders (r:1 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) + /// Storage: Tokens Reserves (r:1 w:1) + /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:2 w:2) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn fill_order_ask() -> Weight { - Weight::from_ref_time(119_376_000) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `1304` + // Estimated: `17220` + // Minimum execution time: 70_000 nanoseconds. + Weight::from_parts(73_000_000, 17220) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } + /// Storage: Orderbook Orders (r:1 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:2 w:2) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn fill_order_bid() -> Weight { - Weight::from_ref_time(119_917_000) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `1243` + // Estimated: `17193` + // Minimum execution time: 62_000 nanoseconds. + Weight::from_parts(64_000_000, 17193) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - fn make_order_ask() -> Weight { - Weight::from_ref_time(80_092_000) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) + /// Storage: Orderbook NextOrderId (r:1 w:1) + /// Proof: Orderbook NextOrderId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Tokens Reserves (r:1 w:1) + /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:1 w:1) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: Orderbook Orders (r:0 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + fn place_order_ask() -> Weight { + // Proof Size summary in bytes: + // Measured: `588` + // Estimated: `9876` + // Minimum execution time: 35_000 nanoseconds. + Weight::from_parts(38_000_000, 9876) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - fn make_order_bid() -> Weight { - Weight::from_ref_time(62_027_000) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) + /// Storage: Orderbook NextOrderId (r:1 w:1) + /// Proof: Orderbook NextOrderId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + /// Storage: Orderbook Orders (r:0 w:1) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(175), added: 2650, mode: MaxEncodedLen) + fn place_order_bid() -> Weight { + // Proof Size summary in bytes: + // Measured: `330` + // Estimated: `7251` + // Minimum execution time: 31_000 nanoseconds. + Weight::from_parts(32_000_000, 7251) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } } diff --git a/zrml/prediction-markets/fuzz/pm_full_workflow.rs b/zrml/prediction-markets/fuzz/pm_full_workflow.rs index 7bb2df0e7..38316d3a4 100644 --- a/zrml/prediction-markets/fuzz/pm_full_workflow.rs +++ b/zrml/prediction-markets/fuzz/pm_full_workflow.rs @@ -130,11 +130,12 @@ fn market_creation(seed: u8) -> MarketCreation { } #[inline] -fn market_dispute_mechanism(seed: u8) -> MarketDisputeMechanism { - match seed % 3 { - 0 => MarketDisputeMechanism::Authorized, - 1 => MarketDisputeMechanism::Court, - _ => MarketDisputeMechanism::SimpleDisputes, +fn market_dispute_mechanism(seed: u8) -> Option { + match seed % 4 { + 0 => Some(MarketDisputeMechanism::Authorized), + 1 => Some(MarketDisputeMechanism::Court), + 2 => Some(MarketDisputeMechanism::SimpleDisputes), + _ => None, } } diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index dea996ae6..777bff022 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -61,12 +61,8 @@ const LIQUIDITY: u128 = 100 * BASE; // Get default values for market creation. Also spawns an account with maximum // amount of native currency -fn create_market_common_parameters( - permission: MarketCreation, -) -> Result< - (T::AccountId, T::AccountId, Deadlines, MultiHash, MarketCreation), - &'static str, -> { +fn create_market_common_parameters() +-> Result<(T::AccountId, T::AccountId, Deadlines, MultiHash), &'static str> { let caller: T::AccountId = whitelisted_caller(); T::AssetManager::deposit(Asset::Ztg, &caller, (100u128 * LIQUIDITY).saturated_into()).unwrap(); let oracle = caller.clone(); @@ -78,13 +74,12 @@ fn create_market_common_parameters( let mut metadata = [0u8; 50]; metadata[0] = 0x15; metadata[1] = 0x30; - let creation = permission; - Ok((caller, oracle, deadlines, MultiHash::Sha3_384(metadata), creation)) + Ok((caller, oracle, deadlines, MultiHash::Sha3_384(metadata))) } // Create a market based on common parameters fn create_market_common( - permission: MarketCreation, + creation: MarketCreation, options: MarketType, scoring_rule: ScoringRule, period: Option>>, @@ -94,8 +89,7 @@ fn create_market_common( let range_end: MomentOf = 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::(permission)?; + let (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; Call::::create_market { base_asset: Asset::Ztg, creator_fee, @@ -105,7 +99,7 @@ fn create_market_common( metadata, creation, market_type: options, - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), scoring_rule, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; @@ -248,7 +242,7 @@ benchmarks! { )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -441,7 +435,7 @@ benchmarks! { OutcomeReport::Categorical(0u16), )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -478,7 +472,7 @@ benchmarks! { )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -531,7 +525,7 @@ benchmarks! { )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -615,8 +609,7 @@ benchmarks! { create_market { let m in 0..63; - let (caller, oracle, deadlines, metadata, creation) = - create_market_common_parameters::(MarketCreation::Permissionless)?; + let (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; let range_end = T::MaxSubsidyPeriod::get(); let period = MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..range_end); @@ -635,9 +628,9 @@ benchmarks! { period, deadlines, metadata, - creation, + MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ) @@ -645,13 +638,13 @@ benchmarks! { let m in 0..63; let market_type = MarketType::Categorical(T::MaxCategories::get()); - let dispute_mechanism = MarketDisputeMechanism::SimpleDisputes; + let dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); let scoring_rule = ScoringRule::CPMM; let range_start: MomentOf = 100_000u64.saturated_into(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let period = MarketPeriod::Timestamp(range_start..range_end); - let (caller, oracle, deadlines, metadata, creation) = - create_market_common_parameters::(MarketCreation::Advised)?; + let (caller, oracle, deadlines, metadata) = + create_market_common_parameters::()?; Call::::create_market { base_asset: Asset::Ztg, creator_fee: Perbill::zero(), @@ -659,7 +652,7 @@ benchmarks! { period: period.clone(), deadlines, metadata: metadata.clone(), - creation, + creation: MarketCreation::Advised, market_type: market_type.clone(), dispute_mechanism: dispute_mechanism.clone(), scoring_rule, @@ -806,7 +799,7 @@ benchmarks! { )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Court; + market.dispute_mechanism = Some(MarketDisputeMechanism::Court); Ok(()) })?; @@ -880,7 +873,7 @@ benchmarks! { )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -908,7 +901,7 @@ benchmarks! { OutcomeReport::Categorical(1u16), )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; let market = >::market(&market_id)?; @@ -927,7 +920,7 @@ benchmarks! { OutcomeReport::Categorical(1u16) )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -970,7 +963,7 @@ benchmarks! { OutcomeReport::Scalar(u128::MAX), )?; >::mutate_market(&market_id, |market| { - market.dispute_mechanism = MarketDisputeMechanism::Authorized; + market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; let market = >::market(&market_id)?; @@ -1061,7 +1054,7 @@ benchmarks! { let call = Call::::reject_market { market_id, reject_reason }; }: { call.dispatch_bypass_filter(reject_origin)? } - report { + report_market_with_dispute_mechanism { let m in 0..63; // ensure range.start is now to get the heaviest path @@ -1104,7 +1097,41 @@ benchmarks! { ids.try_push(i.into()) }).unwrap(); } - }: _(RawOrigin::Signed(caller), market_id, outcome) + let call = Call::::report { market_id, outcome }; + }: { + call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; + } + + report_trusted_market { + pallet_timestamp::Pallet::::set_timestamp(0u32.into()); + let start: MomentOf = >::now(); + let end: MomentOf = 1_000_000u64.saturated_into(); + let (caller, oracle, _, metadata) = create_market_common_parameters::()?; + Call::::create_market { + base_asset: Asset::Ztg, + creator_fee: Perbill::zero(), + oracle: caller.clone(), + period: MarketPeriod::Timestamp(start..end), + deadlines: Deadlines:: { + grace_period: 0u8.into(), + oracle_duration: T::MinOracleDuration::get(), + dispute_duration: 0u8.into(), + }, + metadata, + creation: MarketCreation::Permissionless, + market_type: MarketType::Categorical(3), + dispute_mechanism: None, + scoring_rule: ScoringRule::CPMM, + } + .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; + let market_id = >::latest_market_id()?; + let close_origin = T::CloseOrigin::try_successful_origin().unwrap(); + Pallet::::admin_move_market_to_closed(close_origin, market_id)?; + let outcome = OutcomeReport::Categorical(0); + let call = Call::::report { market_id, outcome }; + }: { + call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; + } sell_complete_set { let a in (T::MinCategories::get().into())..T::MaxCategories::get().into(); diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 68742a4e1..8e0fbf90c 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -75,7 +75,7 @@ mod pallet { use zrml_market_commons::MarketCommonsPalletApi; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); pub(crate) type BalanceOf = <::AssetManager as MultiCurrency< ::AccountId, @@ -94,6 +94,8 @@ mod pallet { MomentOf, Asset>, >; + pub(crate) type ReportOf = + Report<::AccountId, ::BlockNumber>; pub type CacheSize = ConstU32<64>; pub type EditReason = BoundedVec::MaxEditReasonLen>; pub type RejectReason = BoundedVec::MaxRejectReasonLen>; @@ -330,7 +332,10 @@ mod pallet { T::DestroyOrigin::ensure_origin(origin)?; let market = >::market(&market_id)?; - ensure!(market.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); + ensure!( + matches!(market.scoring_rule, ScoringRule::CPMM | ScoringRule::Orderbook), + Error::::InvalidScoringRule + ); let market_status = market.status; let market_account = >::market_account(market_id); @@ -364,8 +369,11 @@ mod pallet { let open_ids_len = Self::clear_auto_open(&market_id)?; let close_ids_len = Self::clear_auto_close(&market_id)?; + // Note: This is noop if the market is trusted. let (ids_len, _) = Self::clear_auto_resolve(&market_id)?; - Self::clear_dispute_mechanism(&market_id)?; + if market.dispute_mechanism.is_some() { + Self::clear_dispute_mechanism(&market_id)?; + } >::remove_market(&market_id)?; Self::deposit_event(Event::MarketDestroyed(market_id)); @@ -527,7 +535,7 @@ mod pallet { ); match m.scoring_rule { - ScoringRule::CPMM => { + ScoringRule::CPMM | ScoringRule::Orderbook => { m.status = MarketStatus::Active; } ScoringRule::RikiddoSigmoidFeeMarketEma => { @@ -641,7 +649,9 @@ mod pallet { let market = >::market(&market_id)?; ensure!(market.status == MarketStatus::Reported, Error::::InvalidMarketStatus); - let weight = match market.dispute_mechanism { + let dispute_mechanism = + market.dispute_mechanism.as_ref().ok_or(Error::::NoDisputeMechanism)?; + let weight = match dispute_mechanism { MarketDisputeMechanism::Authorized => { T::Authorized::on_dispute(&market_id, &market)?; T::WeightInfo::dispute_authorized() @@ -721,7 +731,7 @@ mod pallet { deadlines: Deadlines, metadata: MultiHash, market_type: MarketType, - dispute_mechanism: MarketDisputeMechanism, + dispute_mechanism: Option, #[pallet::compact] swap_fee: BalanceOf, #[pallet::compact] amount: BalanceOf, weights: Vec, @@ -778,7 +788,7 @@ mod pallet { metadata: MultiHash, creation: MarketCreation, market_type: MarketType, - dispute_mechanism: MarketDisputeMechanism, + dispute_mechanism: Option, scoring_rule: ScoringRule, ) -> DispatchResultWithPostInfo { // TODO(#787): Handle Rikiddo benchmarks! @@ -867,7 +877,7 @@ mod pallet { deadlines: Deadlines, metadata: MultiHash, market_type: MarketType, - dispute_mechanism: MarketDisputeMechanism, + dispute_mechanism: Option, scoring_rule: ScoringRule, ) -> DispatchResultWithPostInfo { // TODO(#787): Handle Rikiddo benchmarks! @@ -1276,7 +1286,10 @@ mod pallet { /// Complexity: `O(n)`, where `n` is the number of market ids, /// which reported at the same time as the specified market. #[pallet::call_index(14)] - #[pallet::weight(T::WeightInfo::report(CacheSize::get()))] + #[pallet::weight( + T::WeightInfo::report_market_with_dispute_mechanism(CacheSize::get()) + .max(T::WeightInfo::report_trusted_market()) + )] #[transactional] pub fn report( origin: OriginFor, @@ -1284,100 +1297,30 @@ mod pallet { outcome: OutcomeReport, ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin.clone())?; - let current_block = >::block_number(); let market_report = Report { at: current_block, by: sender.clone(), outcome }; - - >::mutate_market(&market_id, |market| { - ensure!(market.report.is_none(), Error::::MarketAlreadyReported); - Self::ensure_market_is_closed(market)?; - ensure!( - market.matches_outcome_report(&market_report.outcome), - Error::::OutcomeMismatch - ); - - let mut should_check_origin = false; - //NOTE: Saturating operation in following block may saturate to u32::MAX value - // but that will be the case after thousands of years time. So it is fine. - match market.period { - MarketPeriod::Block(ref range) => { - let grace_period_end = - range.end.saturating_add(market.deadlines.grace_period); - ensure!( - grace_period_end <= current_block, - Error::::NotAllowedToReportYet - ); - let oracle_duration_end = - grace_period_end.saturating_add(market.deadlines.oracle_duration); - if current_block <= oracle_duration_end { - should_check_origin = true; - } - } - MarketPeriod::Timestamp(ref range) => { - let grace_period_in_moments: MomentOf = - market.deadlines.grace_period.saturated_into::().into(); - let grace_period_in_ms = - grace_period_in_moments.saturating_mul(MILLISECS_PER_BLOCK.into()); - let grace_period_end = range.end.saturating_add(grace_period_in_ms); - let now = >::now(); - ensure!(grace_period_end <= now, Error::::NotAllowedToReportYet); - let oracle_duration_in_moments: MomentOf = - market.deadlines.oracle_duration.saturated_into::().into(); - let oracle_duration_in_ms = - oracle_duration_in_moments.saturating_mul(MILLISECS_PER_BLOCK.into()); - let oracle_duration_end = - grace_period_end.saturating_add(oracle_duration_in_ms); - if now <= oracle_duration_end { - should_check_origin = true; - } - } - } - - let sender_is_oracle = sender == market.oracle; - let origin_has_permission = T::ResolveOrigin::ensure_origin(origin).is_ok(); - let sender_is_outsider = !sender_is_oracle && !origin_has_permission; - - if should_check_origin { - ensure!( - sender_is_oracle || origin_has_permission, - Error::::ReporterNotOracle - ); - } else if sender_is_outsider { - let outsider_bond = T::OutsiderBond::get(); - - market.bonds.outsider = Some(Bond::new(sender.clone(), outsider_bond)); - - T::AssetManager::reserve_named( - &Self::reserve_id(), - Asset::Ztg, - &sender, - outsider_bond, - )?; - } - - market.report = Some(market_report.clone()); - market.status = MarketStatus::Reported; - - Ok(()) - })?; - let market = >::market(&market_id)?; - let block_after_dispute_duration = - current_block.saturating_add(market.deadlines.dispute_duration); - let ids_len = MarketIdsPerReportBlock::::try_mutate( - block_after_dispute_duration, - |ids| -> Result { - ids.try_push(market_id).map_err(|_| >::StorageOverflow)?; - Ok(ids.len() as u32) - }, - )?; - + ensure!(market.report.is_none(), Error::::MarketAlreadyReported); + Self::ensure_market_is_closed(&market)?; + ensure!( + market.matches_outcome_report(&market_report.outcome), + Error::::OutcomeMismatch + ); + let weight = if market.dispute_mechanism.is_some() { + Self::report_market_with_dispute_mechanism( + origin, + market_id, + market_report.clone(), + )? + } else { + Self::report_and_resolve_market(origin, market_id, market_report.clone())? + }; Self::deposit_event(Event::MarketReported( market_id, MarketStatus::Reported, market_report, )); - Ok(Some(T::WeightInfo::report(ids_len)).into()) + Ok(weight) } /// Sells a complete set of outcomes shares for a market. @@ -1401,7 +1344,10 @@ mod pallet { ensure!(amount != BalanceOf::::zero(), Error::::ZeroAmount); let market = >::market(&market_id)?; - ensure!(market.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); + ensure!( + matches!(market.scoring_rule, ScoringRule::CPMM | ScoringRule::Orderbook), + Error::::InvalidScoringRule + ); Self::ensure_market_is_active(&market)?; let market_account = >::market_account(market_id); @@ -1454,13 +1400,15 @@ mod pallet { ensure_signed(origin)?; let market = >::market(&market_id)?; + let dispute_mechanism = + market.dispute_mechanism.as_ref().ok_or(Error::::NoDisputeMechanism)?; ensure!( matches!(market.status, MarketStatus::Disputed | MarketStatus::Reported), Error::::InvalidMarketStatus ); ensure!( - matches!(market.dispute_mechanism, MarketDisputeMechanism::Court), + matches!(dispute_mechanism, MarketDisputeMechanism::Court), Error::::InvalidDisputeMechanism ); @@ -1471,7 +1419,7 @@ mod pallet { let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; - let res_0 = match market.dispute_mechanism { + let res_0 = match dispute_mechanism { MarketDisputeMechanism::Authorized => { T::Authorized::has_failed(&market_id, &market)? } @@ -1483,7 +1431,7 @@ mod pallet { let has_failed = res_0.result; ensure!(has_failed, Error::::MarketDisputeMechanismNotFailed); - let res_1 = match market.dispute_mechanism { + let res_1 = match dispute_mechanism { MarketDisputeMechanism::Authorized => { T::Authorized::on_global_dispute(&market_id, &market)? } @@ -1629,6 +1577,7 @@ mod pallet { type MinCategories: Get; /// A upper bound for the fee that is charged each trade and given to the market creator. + #[pallet::constant] type MaxCreatorFee: Get; /// The shortest period of collecting subsidy for a Rikiddo market. @@ -1820,6 +1769,10 @@ mod pallet { UnregisteredForeignAsset, /// The start of the global dispute for this market happened already. GlobalDisputeExistsAlready, + /// The market has no dispute mechanism. + NoDisputeMechanism, + /// The dispute duration is positive but the market has dispute period. + NonZeroDisputePeriodOnTrustedMarket, /// The fee is too high. FeeTooHigh, } @@ -1855,7 +1808,7 @@ mod pallet { /// \[market_id, reject_reason\] MarketRejected(MarketIdOf, RejectReason), /// A market has been reported on. \[market_id, new_market_status, reported_outcome\] - MarketReported(MarketIdOf, MarketStatus, Report), + MarketReported(MarketIdOf, MarketStatus, ReportOf), /// A market has been resolved. \[market_id, new_market_status, real_outcome\] MarketResolved(MarketIdOf, MarketStatus, OutcomeReport), /// A proposed market has been requested edit by advisor. \[market_id, edit_reason\] @@ -2221,6 +2174,12 @@ mod pallet { /// Clears this market from being stored for automatic resolution. fn clear_auto_resolve(market_id: &MarketIdOf) -> Result<(u32, u32), DispatchError> { let market = >::market(market_id)?; + // If there's no dispute mechanism, this function is noop. FIXME This is an + // anti-pattern, but it makes benchmarking easier. + let dispute_mechanism = match market.dispute_mechanism { + Some(ref result) => result, + None => return Ok((0, 0)), + }; let (ids_len, mdm_len) = match market.status { MarketStatus::Reported => { let report = market.report.ok_or(Error::::MarketIsNotReported)?; @@ -2238,7 +2197,7 @@ mod pallet { MarketStatus::Disputed => { // TODO(#782): use multiple benchmarks paths for different dispute mechanisms let ResultWithWeightInfo { result: auto_resolve_block_opt, weight: _ } = - match market.dispute_mechanism { + match dispute_mechanism { MarketDisputeMechanism::Authorized => { T::Authorized::get_auto_resolve(market_id, &market) } @@ -2265,9 +2224,11 @@ mod pallet { /// The dispute mechanism is intended to clear its own storage here. fn clear_dispute_mechanism(market_id: &MarketIdOf) -> DispatchResult { let market = >::market(market_id)?; + let dispute_mechanism = + market.dispute_mechanism.as_ref().ok_or(Error::::NoDisputeMechanism)?; // TODO(#782): use multiple benchmarks paths for different dispute mechanisms - match market.dispute_mechanism { + match dispute_mechanism { MarketDisputeMechanism::Authorized => T::Authorized::clear(market_id, &market)?, MarketDisputeMechanism::Court => T::Court::clear(market_id, &market)?, MarketDisputeMechanism::SimpleDisputes => { @@ -2288,7 +2249,10 @@ mod pallet { T::AssetManager::free_balance(market.base_asset, &who) >= amount, Error::::NotEnoughBalance ); - ensure!(market.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); + ensure!( + matches!(market.scoring_rule, ScoringRule::CPMM | ScoringRule::Orderbook), + Error::::InvalidScoringRule + ); Self::ensure_market_is_active(&market)?; let market_account = >::market_account(market_id); @@ -2396,19 +2360,27 @@ mod pallet { fn ensure_market_deadlines_are_valid( deadlines: &Deadlines, + trusted: bool, ) -> DispatchResult { ensure!( deadlines.oracle_duration >= T::MinOracleDuration::get(), Error::::OracleDurationSmallerThanMinOracleDuration ); - ensure!( - deadlines.dispute_duration >= T::MinDisputeDuration::get(), - Error::::DisputeDurationSmallerThanMinDisputeDuration - ); - ensure!( - deadlines.dispute_duration <= T::MaxDisputeDuration::get(), - Error::::DisputeDurationGreaterThanMaxDisputeDuration - ); + if trusted { + ensure!( + deadlines.dispute_duration == Zero::zero(), + Error::::NonZeroDisputePeriodOnTrustedMarket + ); + } else { + ensure!( + deadlines.dispute_duration >= T::MinDisputeDuration::get(), + Error::::DisputeDurationSmallerThanMinDisputeDuration + ); + ensure!( + deadlines.dispute_duration <= T::MaxDisputeDuration::get(), + Error::::DisputeDurationGreaterThanMaxDisputeDuration + ); + } ensure!( deadlines.grace_period <= T::MaxGracePeriod::get(), Error::::GracePeriodGreaterThanMaxGracePeriod @@ -2557,6 +2529,8 @@ mod pallet { market_id: &MarketIdOf, market: &MarketOf, ) -> Result, DispatchError> { + let dispute_mechanism = + market.dispute_mechanism.as_ref().ok_or(Error::::NoDisputeMechanism)?; let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; let mut weight = Weight::zero(); @@ -2567,7 +2541,7 @@ mod pallet { let imbalance_left = Self::settle_bonds(market_id, market, &resolved_outcome, report)?; - let remainder = match market.dispute_mechanism { + let remainder = match dispute_mechanism { MarketDisputeMechanism::Authorized => { let res = T::Authorized::exchange( market_id, @@ -2622,7 +2596,9 @@ mod pallet { // Try to get the outcome of the MDM. If the MDM failed to resolve, default to // the oracle's report. if resolved_outcome_option.is_none() { - resolved_outcome_option = match market.dispute_mechanism { + let dispute_mechanism = + market.dispute_mechanism.as_ref().ok_or(Error::::NoDisputeMechanism)?; + resolved_outcome_option = match dispute_mechanism { MarketDisputeMechanism::Authorized => { let res = T::Authorized::on_resolution(market_id, market)?; weight = weight.saturating_add(res.weight); @@ -2654,7 +2630,7 @@ mod pallet { market_id: &MarketIdOf, market: &MarketOf, resolved_outcome: &OutcomeReport, - report: &Report, + report: &ReportOf, ) -> Result, DispatchError> { let mut overall_imbalance = NegativeImbalanceOf::::zero(); @@ -3065,9 +3041,9 @@ mod pallet { metadata: MultiHash, creation: MarketCreation, market_type: MarketType, - dispute_mechanism: MarketDisputeMechanism, + dispute_mechanism: Option, scoring_rule: ScoringRule, - report: Option>, + report: Option>, resolved_outcome: Option, bonds: MarketBonds>, ) -> Result, DispatchError> { @@ -3089,7 +3065,7 @@ mod pallet { let MultiHash::Sha3_384(multihash) = metadata; ensure!(multihash[0] == 0x15 && multihash[1] == 0x30, >::InvalidMultihash); Self::ensure_market_period_is_valid(&period)?; - Self::ensure_market_deadlines_are_valid(&deadlines)?; + Self::ensure_market_deadlines_are_valid(&deadlines, dispute_mechanism.is_none())?; Self::ensure_market_type_is_valid(&market_type)?; if scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma { @@ -3097,7 +3073,7 @@ mod pallet { } let status: MarketStatus = match creation { MarketCreation::Permissionless => match scoring_rule { - ScoringRule::CPMM => MarketStatus::Active, + ScoringRule::CPMM | ScoringRule::Orderbook => MarketStatus::Active, ScoringRule::RikiddoSigmoidFeeMarketEma => MarketStatus::CollectingSubsidy, }, MarketCreation::Advised => MarketStatus::Proposed, @@ -3120,6 +3096,108 @@ mod pallet { bonds, }) } + + fn report_market_with_dispute_mechanism( + origin: OriginFor, + market_id: MarketIdOf, + report: ReportOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin.clone())?; + >::mutate_market(&market_id, |market| { + let mut should_check_origin = false; + //NOTE: Saturating operation in following block may saturate to u32::MAX value + // but that will be the case after thousands of years time. So it is fine. + match market.period { + MarketPeriod::Block(ref range) => { + let grace_period_end = + range.end.saturating_add(market.deadlines.grace_period); + ensure!(grace_period_end <= report.at, Error::::NotAllowedToReportYet); + let oracle_duration_end = + grace_period_end.saturating_add(market.deadlines.oracle_duration); + if report.at <= oracle_duration_end { + should_check_origin = true; + } + } + MarketPeriod::Timestamp(ref range) => { + let grace_period_in_moments: MomentOf = + market.deadlines.grace_period.saturated_into::().into(); + let grace_period_in_ms = + grace_period_in_moments.saturating_mul(MILLISECS_PER_BLOCK.into()); + let grace_period_end = range.end.saturating_add(grace_period_in_ms); + let now = >::now(); + ensure!(grace_period_end <= now, Error::::NotAllowedToReportYet); + let oracle_duration_in_moments: MomentOf = + market.deadlines.oracle_duration.saturated_into::().into(); + let oracle_duration_in_ms = + oracle_duration_in_moments.saturating_mul(MILLISECS_PER_BLOCK.into()); + let oracle_duration_end = + grace_period_end.saturating_add(oracle_duration_in_ms); + if now <= oracle_duration_end { + should_check_origin = true; + } + } + } + + let sender_is_oracle = sender == market.oracle; + let origin_has_permission = T::ResolveOrigin::ensure_origin(origin).is_ok(); + let sender_is_outsider = !sender_is_oracle && !origin_has_permission; + + if should_check_origin { + ensure!( + sender_is_oracle || origin_has_permission, + Error::::ReporterNotOracle + ); + } else if sender_is_outsider { + let outsider_bond = T::OutsiderBond::get(); + + market.bonds.outsider = Some(Bond::new(sender.clone(), outsider_bond)); + + T::AssetManager::reserve_named( + &Self::reserve_id(), + Asset::Ztg, + &sender, + outsider_bond, + )?; + } + + market.report = Some(report.clone()); + market.status = MarketStatus::Reported; + + Ok(()) + })?; + + let market = >::market(&market_id)?; + let block_after_dispute_duration = + report.at.saturating_add(market.deadlines.dispute_duration); + let ids_len = MarketIdsPerReportBlock::::try_mutate( + block_after_dispute_duration, + |ids| -> Result { + ids.try_push(market_id).map_err(|_| >::StorageOverflow)?; + Ok(ids.len() as u32) + }, + )?; + + Ok(Some(T::WeightInfo::report_market_with_dispute_mechanism(ids_len)).into()) + } + + fn report_and_resolve_market( + origin: OriginFor, + market_id: MarketIdOf, + market_report: ReportOf, + ) -> DispatchResultWithPostInfo { + >::mutate_market(&market_id, |market| { + let sender = ensure_signed(origin.clone())?; + let sender_is_oracle = sender == market.oracle; + let origin_has_permission = T::ResolveOrigin::ensure_origin(origin).is_ok(); + ensure!(sender_is_oracle || origin_has_permission, Error::::ReporterNotOracle); + market.report = Some(market_report.clone()); + market.status = MarketStatus::Reported; + Ok(()) + })?; + let market = >::market(&market_id)?; + Self::on_resolution(&market_id, &market)?; + Ok(Some(T::WeightInfo::report_trusted_market()).into()) + } } fn remove_item(items: &mut BoundedVec, item: &I) { diff --git a/zrml/prediction-markets/src/migrations.rs b/zrml/prediction-markets/src/migrations.rs index 6a28e7444..e59e1294a 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -37,43 +37,54 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{traits::Saturating, Perbill}; use zeitgeist_primitives::types::{ - Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, - MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, + Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, }; -use zrml_market_commons::{MarketCommonsPalletApi, Pallet as MarketCommonsPallet}; +#[cfg(feature = "try-runtime")] +use zrml_market_commons::MarketCommonsPalletApi; +use zrml_market_commons::Pallet as MarketCommonsPallet; #[cfg(any(feature = "try-runtime", test))] const MARKET_COMMONS: &[u8] = b"MarketCommons"; #[cfg(any(feature = "try-runtime", test))] const MARKETS: &[u8] = b"Markets"; -const MARKET_COMMONS_REQUIRED_STORAGE_VERSION: u16 = 6; -const MARKET_COMMONS_NEXT_STORAGE_VERSION: u16 = 7; - -#[derive(Clone, Decode, Encode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct OldMarketBonds { - pub creation: Option>, - pub oracle: Option>, - pub outsider: Option>, -} +const MARKET_COMMONS_REQUIRED_STORAGE_VERSION: u16 = 7; +const MARKET_COMMONS_NEXT_STORAGE_VERSION: u16 = 8; #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub struct OldMarket { + /// Base asset of the market. pub base_asset: A, + /// Creator of this market. pub creator: AI, + /// Creation type. pub creation: MarketCreation, - 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 + /// hosted JSON. Currently limited to 66 bytes (see `MaxEncodedLen` implementation) pub metadata: Vec, + /// The type of the market. pub market_type: MarketType, + /// Market start and end pub period: MarketPeriod, + /// Market deadlines. pub deadlines: Deadlines, + /// The scoring rule used for the market. pub scoring_rule: ScoringRule, + /// The current status of the market. pub status: MarketStatus, + /// The report of the market. Only `Some` if it has been reported. pub report: Option>, + /// The resolved outcome. pub resolved_outcome: Option, + /// See [`MarketDisputeMechanism`]. pub dispute_mechanism: MarketDisputeMechanism, - pub bonds: OldMarketBonds, + /// The bonds reserved for this market. + pub bonds: MarketBonds, } type OldMarketOf = OldMarket< @@ -92,88 +103,59 @@ pub(crate) type Markets = StorageMap< OldMarketOf, >; -pub struct AddDisputeBondAndConvertCreatorFee(PhantomData); +pub struct MigrateMarkets(PhantomData); -impl OnRuntimeUpgrade - for AddDisputeBondAndConvertCreatorFee -{ +impl OnRuntimeUpgrade for MigrateMarkets { fn on_runtime_upgrade() -> Weight { let mut total_weight = T::DbWeight::get().reads(1); let market_commons_version = StorageVersion::get::>(); if market_commons_version != MARKET_COMMONS_REQUIRED_STORAGE_VERSION { log::info!( - "AddDisputeBondAndConvertCreatorFee: market-commons version is {:?}, but {:?} is \ - required", + "MigrateMarkets: market-commons version is {:?}, but {:?} is required", market_commons_version, MARKET_COMMONS_REQUIRED_STORAGE_VERSION, ); return total_weight; } - log::info!("AddDisputeBondAndConvertCreatorFee: Starting..."); - + log::info!("MigrateMarkets: Starting..."); let mut translated = 0u64; - zrml_market_commons::Markets::::translate::, _>( - |market_id, old_market| { - translated.saturating_inc(); - - let mut dispute_bond = None; - // SimpleDisputes is regarded in the following migration `MoveDataToSimpleDisputes` - if let MarketDisputeMechanism::Authorized = old_market.dispute_mechanism { - total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); - let old_disputes = crate::Disputes::::get(market_id); - if let Some(first_dispute) = old_disputes.first() { - let OldMarketDispute { at: _, by, outcome: _ } = first_dispute; - dispute_bond = Some(Bond::new(by.clone(), T::DisputeBond::get())); - } - } - - let new_market = Market { - base_asset: old_market.base_asset, - creator: old_market.creator, - creation: old_market.creation, - // Zero can be safely assumed here as it was hardcoded before - creator_fee: Perbill::zero(), - oracle: old_market.oracle, - metadata: old_market.metadata, - market_type: old_market.market_type, - period: old_market.period, - scoring_rule: old_market.scoring_rule, - status: old_market.status, - report: old_market.report, - resolved_outcome: old_market.resolved_outcome, - dispute_mechanism: old_market.dispute_mechanism, - deadlines: old_market.deadlines, - bonds: MarketBonds { - creation: old_market.bonds.creation, - oracle: old_market.bonds.oracle, - outsider: old_market.bonds.outsider, - dispute: dispute_bond, - }, - }; - - Some(new_market) - }, - ); - log::info!("AddDisputeBondAndConvertCreatorFee: Upgraded {} markets.", translated); + zrml_market_commons::Markets::::translate::, _>(|_, old_market| { + translated.saturating_inc(); + Some(Market { + base_asset: old_market.base_asset, + creator: old_market.creator, + creation: old_market.creation, + creator_fee: old_market.creator_fee, + oracle: old_market.oracle, + metadata: old_market.metadata, + market_type: old_market.market_type, + period: old_market.period, + scoring_rule: old_market.scoring_rule, + status: old_market.status, + report: old_market.report, + resolved_outcome: old_market.resolved_outcome, + dispute_mechanism: Some(old_market.dispute_mechanism), + deadlines: old_market.deadlines, + bonds: old_market.bonds, + }) + }); + log::info!("MigrateMarkets: Upgraded {} markets.", translated); total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); - StorageVersion::new(MARKET_COMMONS_NEXT_STORAGE_VERSION).put::>(); total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("AddDisputeBondAndConvertCreatorFee: Done!"); + log::info!("MigrateMarkets: Done!"); total_weight } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { use frame_support::pallet_prelude::Blake2_128Concat; - let old_markets = storage_key_iter::, OldMarketOf, Blake2_128Concat>( MARKET_COMMONS, MARKETS, ) .collect::>(); - let markets = Markets::::iter_keys().count() as u32; let decodable_markets = Markets::::iter_values().count() as u32; if markets != decodable_markets { @@ -183,9 +165,8 @@ impl OnRuntimeUpgrade markets ); } else { - log::info!("Markets: {}, Decodable Markets: {}", markets, decodable_markets); + log::info!("Markets: {}", markets); } - Ok(old_markets.encode()) } @@ -193,7 +174,7 @@ impl OnRuntimeUpgrade fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { let old_markets: BTreeMap, OldMarketOf> = Decode::decode(&mut &previous_state[..]) - .expect("Failed to decode state: Invalid state"); + .map_err(|_| "Failed to decode state: Invalid state")?; let new_market_count = >::market_iter().count(); assert_eq!(old_markets.len(), new_market_count); for (market_id, new_market) in >::market_iter() { @@ -203,6 +184,7 @@ impl OnRuntimeUpgrade assert_eq!(new_market.base_asset, old_market.base_asset); assert_eq!(new_market.creator, old_market.creator); assert_eq!(new_market.creation, old_market.creation); + assert_eq!(new_market.creator_fee, old_market.creator_fee); assert_eq!(new_market.oracle, old_market.oracle); assert_eq!(new_market.metadata, old_market.metadata); assert_eq!(new_market.market_type, old_market.market_type); @@ -212,36 +194,11 @@ impl OnRuntimeUpgrade assert_eq!(new_market.status, old_market.status); assert_eq!(new_market.report, old_market.report); assert_eq!(new_market.resolved_outcome, old_market.resolved_outcome); - assert_eq!(new_market.dispute_mechanism, old_market.dispute_mechanism); + assert_eq!(new_market.dispute_mechanism, Some(old_market.dispute_mechanism.clone())); assert_eq!(new_market.bonds.oracle, old_market.bonds.oracle); - assert_eq!(new_market.bonds.creation, old_market.bonds.creation); - assert_eq!(new_market.bonds.outsider, old_market.bonds.outsider); - // new fields - assert_eq!(new_market.creator_fee, Perbill::zero()); - // other dispute mechanisms are regarded in the migration after this migration - if let MarketDisputeMechanism::Authorized = new_market.dispute_mechanism { - let old_disputes = crate::Disputes::::get(market_id); - if let Some(first_dispute) = old_disputes.first() { - let OldMarketDispute { at: _, by, outcome: _ } = first_dispute; - assert_eq!( - new_market.bonds.dispute, - Some(Bond { - who: by.clone(), - value: T::DisputeBond::get(), - is_settled: false - }) - ); - } - } else { - assert_eq!(new_market.bonds.dispute, None); - } + assert_eq!(new_market.bonds, old_market.bonds); } - - log::info!( - "AddDisputeBondAndConvertCreatorFee: Market Counter post-upgrade is {}!", - new_market_count - ); - assert!(new_market_count > 0); + log::info!("MigrateMarkets: Market Counter post-upgrade is {}!", new_market_count); Ok(()) } } @@ -250,19 +207,21 @@ impl OnRuntimeUpgrade mod tests { use super::*; use crate::{ - mock::{DisputeBond, ExtBuilder, Runtime}, + mock::{ExtBuilder, Runtime}, MarketIdOf, MarketOf, }; use frame_support::{ - dispatch::fmt::Debug, migration::put_storage_value, Blake2_128Concat, StorageHasher, + dispatch::fmt::Debug, migration::put_storage_value, storage_root, Blake2_128Concat, + StateVersion, StorageHasher, }; + use test_case::test_case; use zrml_market_commons::MarketCommonsPalletApi; #[test] fn on_runtime_upgrade_increments_the_storage_version() { ExtBuilder::default().build().execute_with(|| { set_up_version(); - AddDisputeBondAndConvertCreatorFee::::on_runtime_upgrade(); + MigrateMarkets::::on_runtime_upgrade(); assert_eq!( StorageVersion::get::>(), MARKET_COMMONS_NEXT_STORAGE_VERSION @@ -274,51 +233,30 @@ mod tests { fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { ExtBuilder::default().build().execute_with(|| { // Don't set up chain to signal that storage is already up to date. - let (_, new_markets) = construct_old_new_tuple(None); + let (_, new_markets) = construct_old_new_tuple(MarketDisputeMechanism::Court); populate_test_data::, MarketOf>( MARKET_COMMONS, MARKETS, new_markets.clone(), ); - AddDisputeBondAndConvertCreatorFee::::on_runtime_upgrade(); - let actual = >::market(&0u128).unwrap(); - assert_eq!(actual, new_markets[0]); - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_markets_with_none_disputor() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let (old_markets, new_markets) = construct_old_new_tuple(None); - populate_test_data::, OldMarketOf>( - MARKET_COMMONS, - MARKETS, - old_markets, - ); - AddDisputeBondAndConvertCreatorFee::::on_runtime_upgrade(); - let actual = >::market(&0u128).unwrap(); - assert_eq!(actual, new_markets[0]); + let tmp = storage_root(StateVersion::V1); + MigrateMarkets::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); }); } - #[test] - fn on_runtime_upgrade_correctly_updates_markets_with_some_disputor() { + #[test_case(MarketDisputeMechanism::Authorized)] + #[test_case(MarketDisputeMechanism::Court)] + fn on_runtime_upgrade_correctly_updates_markets(dispute_mechanism: MarketDisputeMechanism) { ExtBuilder::default().build().execute_with(|| { set_up_version(); - let mut disputes = crate::Disputes::::get(0); - let disputor = crate::mock::EVE; - let dispute = - OldMarketDispute { at: 0, by: disputor, outcome: OutcomeReport::Categorical(0u16) }; - disputes.try_push(dispute).unwrap(); - crate::Disputes::::insert(0, disputes); - let (old_markets, new_markets) = construct_old_new_tuple(Some(disputor)); + let (old_markets, new_markets) = construct_old_new_tuple(dispute_mechanism); populate_test_data::, OldMarketOf>( MARKET_COMMONS, MARKETS, old_markets, ); - AddDisputeBondAndConvertCreatorFee::::on_runtime_upgrade(); + MigrateMarkets::::on_runtime_upgrade(); let actual = >::market(&0u128).unwrap(); assert_eq!(actual, new_markets[0]); }); @@ -330,12 +268,11 @@ mod tests { } fn construct_old_new_tuple( - disputor: Option, + dispute_mechanism: MarketDisputeMechanism, ) -> (Vec>, Vec>) { let base_asset = Asset::Ztg; let creator = 999; - let old_creator_fee = 0; - let new_creator_fee = Perbill::zero(); + let creator_fee = Perbill::from_parts(1); let oracle = 2; let metadata = vec![3, 4, 5]; let market_type = MarketType::Categorical(6); @@ -345,26 +282,14 @@ mod tests { let creation = MarketCreation::Permissionless; let report = None; let resolved_outcome = None; - let dispute_mechanism = MarketDisputeMechanism::Authorized; let deadlines = Deadlines::default(); - let old_bonds = OldMarketBonds { - creation: Some(Bond::new(creator, ::ValidityBond::get())), - oracle: Some(Bond::new(creator, ::OracleBond::get())), - outsider: Some(Bond::new(creator, ::OutsiderBond::get())), - }; - let dispute_bond = disputor.map(|disputor| Bond::new(disputor, DisputeBond::get())); - let new_bonds = MarketBonds { - creation: Some(Bond::new(creator, ::ValidityBond::get())), - oracle: Some(Bond::new(creator, ::OracleBond::get())), - outsider: Some(Bond::new(creator, ::OutsiderBond::get())), - dispute: dispute_bond, - }; + let bonds: MarketBonds<_, _> = Default::default(); let old_market = OldMarket { base_asset, creator, creation: creation.clone(), - creator_fee: old_creator_fee, + creator_fee, oracle, metadata: metadata.clone(), market_type: market_type.clone(), @@ -375,13 +300,13 @@ mod tests { resolved_outcome: resolved_outcome.clone(), dispute_mechanism: dispute_mechanism.clone(), deadlines, - bonds: old_bonds, + bonds: bonds.clone(), }; let new_market = Market { base_asset, creator, creation, - creator_fee: new_creator_fee, + creator_fee, oracle, metadata, market_type, @@ -390,9 +315,9 @@ mod tests { status, report, resolved_outcome, - dispute_mechanism, + dispute_mechanism: Some(dispute_mechanism), deadlines, - bonds: new_bonds, + bonds, }; (vec![old_market], vec![new_market]) } @@ -412,404 +337,6 @@ mod tests { } } -use frame_support::dispatch::EncodeLike; -use sp_runtime::SaturatedConversion; -use zeitgeist_primitives::types::{MarketDispute, OldMarketDispute}; - -const PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION: u16 = 6; -const PREDICTION_MARKETS_NEXT_STORAGE_VERSION: u16 = 7; - -#[cfg(feature = "try-runtime")] -type OldDisputesOf = frame_support::BoundedVec< - OldMarketDispute< - ::AccountId, - ::BlockNumber, - >, - ::MaxDisputes, ->; - -pub struct MoveDataToSimpleDisputes(PhantomData); - -impl OnRuntimeUpgrade - for MoveDataToSimpleDisputes -where - ::MarketId: EncodeLike< - <::MarketCommons as MarketCommonsPalletApi>::MarketId, - >, -{ - fn on_runtime_upgrade() -> Weight { - use orml_traits::NamedMultiReservableCurrency; - - let mut total_weight = T::DbWeight::get().reads(1); - let pm_version = StorageVersion::get::>(); - if pm_version != PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION { - log::info!( - "MoveDataToSimpleDisputes: prediction-markets version is {:?}, but {:?} is \ - required", - pm_version, - PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("MoveDataToSimpleDisputes: Starting..."); - - total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); - - // important drain disputes storage item from prediction markets pallet - for (market_id, old_disputes) in crate::Disputes::::drain() { - total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - if let Ok(market) = >::market(&market_id) { - match market.dispute_mechanism { - MarketDisputeMechanism::Authorized => continue, - // just transform SimpleDispute disputes - MarketDisputeMechanism::SimpleDisputes => (), - MarketDisputeMechanism::Court => continue, - } - } else { - log::warn!( - "MoveDataToSimpleDisputes: Could not find market with market id {:?}", - market_id - ); - } - - total_weight = total_weight.saturating_add(T::DbWeight::get().reads(1)); - let mut new_disputes = zrml_simple_disputes::Disputes::::get(market_id); - for (i, old_dispute) in old_disputes.iter().enumerate() { - let bond = zrml_simple_disputes::default_outcome_bond::(i); - let new_dispute = MarketDispute { - at: old_dispute.at, - by: old_dispute.by.clone(), - outcome: old_dispute.outcome.clone(), - bond, - }; - let res = new_disputes.try_push(new_dispute); - if res.is_err() { - log::error!( - "MoveDataToSimpleDisputes: Could not push dispute for market id {:?}", - market_id - ); - } - - // switch to new reserve identifier for simple disputes - let sd_reserve_id = >::reserve_id(); - let pm_reserve_id = >::reserve_id(); - - // charge weight defensivly for unreserve_named - // https://github.com/open-web3-stack/open-runtime-module-library/blob/24f0a8b6e04e1078f70d0437fb816337cdf4f64c/tokens/src/lib.rs#L1516-L1547 - total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(4, 3)); - let reserved_balance = ::AssetManager::reserved_balance_named( - &pm_reserve_id, - Asset::Ztg, - &old_dispute.by, - ); - if reserved_balance < bond.saturated_into::().saturated_into() { - // warns for battery station market id 386 - // https://discord.com/channels/737780518313000960/817041223201587230/958682619413934151 - log::warn!( - "MoveDataToSimpleDisputes: Could not unreserve {:?} for {:?} because \ - reserved balance is only {:?}. Market id: {:?}", - bond, - old_dispute.by, - reserved_balance, - market_id, - ); - } - ::AssetManager::unreserve_named( - &pm_reserve_id, - Asset::Ztg, - &old_dispute.by, - bond.saturated_into::().saturated_into(), - ); - - // charge weight defensivly for reserve_named - // https://github.com/open-web3-stack/open-runtime-module-library/blob/24f0a8b6e04e1078f70d0437fb816337cdf4f64c/tokens/src/lib.rs#L1486-L1499 - total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(3, 3)); - let res = ::AssetManager::reserve_named( - &sd_reserve_id, - Asset::Ztg, - &old_dispute.by, - bond.saturated_into::().saturated_into(), - ); - if res.is_err() { - log::error!( - "MoveDataToSimpleDisputes: Could not reserve bond for dispute caller {:?} \ - and market id {:?}", - old_dispute.by, - market_id - ); - } - } - - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - zrml_simple_disputes::Disputes::::insert(market_id, new_disputes); - } - - StorageVersion::new(PREDICTION_MARKETS_NEXT_STORAGE_VERSION).put::>(); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("MoveDataToSimpleDisputes: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - log::info!("MoveDataToSimpleDisputes: Start pre_upgrade!"); - - let old_disputes = crate::Disputes::::iter().collect::>(); - Ok(old_disputes.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - let old_disputes: BTreeMap, OldDisputesOf> = - Decode::decode(&mut &previous_state[..]) - .expect("Failed to decode state: Invalid state"); - - log::info!("MoveDataToSimpleDisputes: (post_upgrade) Start first try-runtime part!"); - - for (market_id, o) in old_disputes.iter() { - let market = >::market(market_id) - .expect(&format!("Market for market id {:?} not found", market_id)[..]); - - // market id is a reference, but we need the raw value to encode with the where clause - let disputes = zrml_simple_disputes::Disputes::::get(*market_id); - - match market.dispute_mechanism { - MarketDisputeMechanism::Authorized => { - let simple_disputes_count = disputes.iter().count(); - assert_eq!(simple_disputes_count, 0); - continue; - } - MarketDisputeMechanism::SimpleDisputes => { - let new_count = disputes.iter().count(); - let old_count = o.iter().count(); - assert_eq!(new_count, old_count); - } - MarketDisputeMechanism::Court => { - panic!("Court should not be contained at all.") - } - } - } - - log::info!("MoveDataToSimpleDisputes: (post_upgrade) Start second try-runtime part!"); - - assert!(crate::Disputes::::iter().count() == 0); - - for (market_id, new_disputes) in zrml_simple_disputes::Disputes::::iter() { - let old_disputes = old_disputes - .get(&market_id.saturated_into::().saturated_into()) - .expect(&format!("Disputes for market {:?} not found", market_id)[..]); - - let market = ::MarketCommons::market(&market_id) - .expect(&format!("Market for market id {:?} not found", market_id)[..]); - match market.dispute_mechanism { - MarketDisputeMechanism::Authorized => { - panic!("Authorized should not be contained in simple disputes."); - } - MarketDisputeMechanism::SimpleDisputes => (), - MarketDisputeMechanism::Court => { - panic!("Court should not be contained in simple disputes."); - } - } - - for (i, new_dispute) in new_disputes.iter().enumerate() { - let old_dispute = - old_disputes.get(i).expect(&format!("Dispute at index {} not found", i)[..]); - assert_eq!(new_dispute.at, old_dispute.at); - assert_eq!(new_dispute.by, old_dispute.by); - assert_eq!(new_dispute.outcome, old_dispute.outcome); - assert_eq!(new_dispute.bond, zrml_simple_disputes::default_outcome_bond::(i)); - } - } - - log::info!("MoveDataToSimpleDisputes: Done! (post_upgrade)"); - Ok(()) - } -} - -#[cfg(test)] -mod tests_simple_disputes_migration { - use super::*; - use crate::{ - mock::{DisputeBond, ExtBuilder, Runtime}, - MarketOf, - }; - use orml_traits::NamedMultiReservableCurrency; - use zrml_market_commons::MarketCommonsPalletApi; - - #[test] - fn on_runtime_upgrade_increments_the_storage_version() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - MoveDataToSimpleDisputes::::on_runtime_upgrade(); - assert_eq!( - StorageVersion::get::>(), - PREDICTION_MARKETS_NEXT_STORAGE_VERSION - ); - }); - } - - #[test] - fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - // Don't set up chain to signal that storage is already up to date. - let market_id = 0u128; - let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); - let dispute = MarketDispute { - at: 42u64, - by: 0u128, - outcome: OutcomeReport::Categorical(0u16), - bond: DisputeBond::get(), - }; - disputes.try_push(dispute.clone()).unwrap(); - zrml_simple_disputes::Disputes::::insert(market_id, disputes); - let market = get_market(MarketDisputeMechanism::SimpleDisputes); - >::push_market(market).unwrap(); - - MoveDataToSimpleDisputes::::on_runtime_upgrade(); - - let actual = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(actual, vec![dispute]); - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_simple_disputes() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let market_id = 0u128; - - let mut disputes = crate::Disputes::::get(0); - for i in 0..::MaxDisputes::get() { - let dispute = OldMarketDispute { - at: i as u64 + 42u64, - by: i as u128, - outcome: OutcomeReport::Categorical(i), - }; - disputes.try_push(dispute).unwrap(); - } - crate::Disputes::::insert(market_id, disputes); - let market = get_market(MarketDisputeMechanism::SimpleDisputes); - >::push_market(market).unwrap(); - - MoveDataToSimpleDisputes::::on_runtime_upgrade(); - - let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); - for i in 0..::MaxDisputes::get() { - let dispute = disputes.get_mut(i as usize).unwrap(); - - assert_eq!(dispute.at, i as u64 + 42u64); - assert_eq!(dispute.by, i as u128); - assert_eq!(dispute.outcome, OutcomeReport::Categorical(i)); - - let bond = zrml_simple_disputes::default_outcome_bond::(i as usize); - assert_eq!(dispute.bond, bond); - } - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_reserve_ids() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let market_id = 0u128; - - let mut disputes = crate::Disputes::::get(0); - for i in 0..::MaxDisputes::get() { - let dispute = OldMarketDispute { - at: i as u64 + 42u64, - by: i as u128, - outcome: OutcomeReport::Categorical(i), - }; - let bond = zrml_simple_disputes::default_outcome_bond::(i.into()); - let pm_reserve_id = crate::Pallet::::reserve_id(); - let res = ::AssetManager::reserve_named( - &pm_reserve_id, - Asset::Ztg, - &dispute.by, - bond.saturated_into::().saturated_into(), - ); - assert!(res.is_ok()); - disputes.try_push(dispute).unwrap(); - } - crate::Disputes::::insert(market_id, disputes); - let market = get_market(MarketDisputeMechanism::SimpleDisputes); - >::push_market(market).unwrap(); - - MoveDataToSimpleDisputes::::on_runtime_upgrade(); - - let mut disputes = zrml_simple_disputes::Disputes::::get(market_id); - for i in 0..::MaxDisputes::get() { - let dispute = disputes.get_mut(i as usize).unwrap(); - - let sd_reserve_id = zrml_simple_disputes::Pallet::::reserve_id(); - let reserved_balance = - ::AssetManager::reserved_balance_named( - &sd_reserve_id, - Asset::Ztg, - &dispute.by, - ); - let bond = zrml_simple_disputes::default_outcome_bond::(i.into()); - assert_eq!(reserved_balance, bond); - assert!(reserved_balance > 0); - - let pm_reserve_id = crate::Pallet::::reserve_id(); - let reserved_balance = - ::AssetManager::reserved_balance_named( - &pm_reserve_id, - Asset::Ztg, - &dispute.by, - ); - assert_eq!(reserved_balance, 0); - } - }); - } - - fn set_up_version() { - StorageVersion::new(PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION) - .put::>(); - } - - fn get_market(dispute_mechanism: MarketDisputeMechanism) -> MarketOf { - let base_asset = Asset::Ztg; - let creator = 999; - let creator_fee = Perbill::zero(); - let oracle = 2; - let metadata = vec![3, 4, 5]; - let market_type = MarketType::Categorical(6); - let period = MarketPeriod::Block(7..8); - let scoring_rule = ScoringRule::CPMM; - let status = MarketStatus::Disputed; - let creation = MarketCreation::Permissionless; - let report = None; - let resolved_outcome = None; - let deadlines = Deadlines::default(); - let bonds = MarketBonds { - creation: Some(Bond::new(creator, ::ValidityBond::get())), - oracle: Some(Bond::new(creator, ::OracleBond::get())), - outsider: None, - dispute: None, - }; - - Market { - base_asset, - creator, - creation, - creator_fee, - oracle, - metadata, - market_type, - period, - scoring_rule, - status, - report, - resolved_outcome, - dispute_mechanism, - deadlines, - bonds, - } - } -} - // We use these utilities to prevent having to make the swaps pallet a dependency of // prediciton-markets. The calls are based on the implementation of `StorageVersion`, found here: // https://github.com/paritytech/substrate/blob/bc7a1e6c19aec92bfa247d8ca68ec63e07061032/frame/support/src/traits/metadata.rs#L168-L230 diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 9b79c33e1..ab152fb8d 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -48,7 +48,7 @@ use zeitgeist_primitives::{ types::{ AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, - Moment, MultiHash, OutcomeReport, PoolStatus, ScalarPosition, ScoringRule, + Moment, MultiHash, OutcomeReport, PoolStatus, Report, ScalarPosition, ScoringRule, }, }; use zrml_global_disputes::{ @@ -115,7 +115,7 @@ fn simple_create_categorical_market( gen_metadata(2), creation, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), scoring_rule )); } @@ -136,7 +136,7 @@ fn simple_create_scalar_market( gen_metadata(2), creation, MarketType::Scalar(100..=200), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), scoring_rule )); } @@ -187,7 +187,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let market_id = 0; @@ -263,7 +263,7 @@ fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -277,7 +277,7 @@ fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -291,7 +291,7 @@ fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -323,7 +323,7 @@ fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { gen_metadata(2), MarketCreation::Permissionless, MarketType::Scalar(range), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::InvalidOutcomeRange @@ -350,7 +350,7 @@ fn create_market_fails_on_min_dispute_period() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::DisputeDurationSmallerThanMinDisputeDuration @@ -377,7 +377,7 @@ fn create_market_fails_on_min_oracle_duration() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::OracleDurationSmallerThanMinOracleDuration @@ -404,7 +404,7 @@ fn create_market_fails_on_max_dispute_period() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::DisputeDurationGreaterThanMaxDisputeDuration @@ -431,7 +431,7 @@ fn create_market_fails_on_max_grace_period() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::GracePeriodGreaterThanMaxGracePeriod @@ -458,7 +458,7 @@ fn create_market_fails_on_max_oracle_duration() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::OracleDurationGreaterThanMaxOracleDuration @@ -489,7 +489,7 @@ fn create_market_with_foreign_assets() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::InvalidBaseAsset, @@ -506,7 +506,7 @@ fn create_market_with_foreign_assets() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, ), Error::::UnregisteredForeignAsset, @@ -522,7 +522,7 @@ fn create_market_with_foreign_assets() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let market = MarketCommons::market(&0).unwrap(); @@ -1024,7 +1024,7 @@ fn admin_destroy_market_correctly_cleans_up_accounts() { get_deadlines(), gen_metadata(50), MarketType::Categorical(3), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), swap_fee, LIQUIDITY, vec![::MinWeight::get(); 3], @@ -1090,7 +1090,7 @@ fn admin_destroy_market_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1104,7 +1104,7 @@ fn admin_destroy_market_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1118,7 +1118,7 @@ fn admin_destroy_market_correctly_clears_auto_open_and_close_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1313,7 +1313,7 @@ fn it_does_not_create_market_with_too_few_categories() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(::MinCategories::get() - 1), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ), Error::::NotEnoughCategories @@ -1335,7 +1335,7 @@ fn it_does_not_create_market_with_too_many_categories() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(::MaxCategories::get() + 1), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ), Error::::TooManyCategories @@ -1816,7 +1816,7 @@ fn on_market_open_successfully_auto_opens_market_pool_with_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1848,7 +1848,7 @@ fn on_market_close_successfully_auto_closes_market_with_blocks() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1887,7 +1887,7 @@ fn on_market_open_successfully_auto_opens_market_with_timestamps() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1922,7 +1922,7 @@ fn on_market_close_successfully_auto_closes_market_with_timestamps() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1969,7 +1969,7 @@ fn on_market_open_successfully_auto_opens_multiple_markets_after_stall() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -1983,7 +1983,7 @@ fn on_market_open_successfully_auto_opens_multiple_markets_after_stall() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -2016,7 +2016,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -2030,7 +2030,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 0, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -2070,7 +2070,7 @@ fn on_initialize_skips_the_genesis_block() { get_deadlines(), gen_metadata(50), MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 123, LIQUIDITY, vec![::MinWeight::get(); category_count.into()], @@ -2165,7 +2165,7 @@ fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), Error::::InvalidMarketPeriod, @@ -2196,7 +2196,7 @@ fn create_categorical_market_fails_if_market_period_is_invalid( gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), Error::::InvalidMarketPeriod, @@ -2220,7 +2220,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), Error::::InvalidMarketPeriod, @@ -2238,7 +2238,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), Error::::InvalidMarketPeriod, @@ -2580,7 +2580,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); @@ -2739,7 +2739,7 @@ fn dispute_fails_disputed_already() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); @@ -2780,7 +2780,7 @@ fn dispute_fails_if_market_not_reported() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); @@ -2815,7 +2815,7 @@ fn dispute_reserves_dispute_bond() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); @@ -2861,7 +2861,7 @@ fn dispute_updates_market() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); @@ -2908,7 +2908,7 @@ fn dispute_emits_event() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); @@ -3209,7 +3209,7 @@ fn it_resolves_a_disputed_court_market() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Court, + Some(MarketDisputeMechanism::Court), ScoringRule::CPMM, )); @@ -3477,7 +3477,7 @@ fn it_appeals_a_court_market_to_global_dispute() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Court, + Some(MarketDisputeMechanism::Court), ScoringRule::CPMM, )); @@ -3597,7 +3597,7 @@ fn start_global_dispute_fails_on_wrong_mdm() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MaxDisputes::get() + 1), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); let market_id = MarketCommons::latest_market_id().unwrap(); @@ -3691,7 +3691,7 @@ fn create_market_and_deploy_assets_results_in_expected_balances_and_pool_params( get_deadlines(), metadata, market_type, - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), swap_fee, amount, weights, @@ -3918,7 +3918,7 @@ fn only_creator_can_edit_market() { get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ), Error::::EditorNotCreator @@ -3960,7 +3960,7 @@ fn edit_cycle_for_proposed_markets() { get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let edited_market = MarketCommons::market(&0).expect("Market not found"); @@ -4007,7 +4007,7 @@ fn edit_market_with_foreign_asset() { get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ), Error::::UnregisteredForeignAsset @@ -4023,7 +4023,7 @@ fn edit_market_with_foreign_asset() { get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM ), Error::::InvalidBaseAsset, @@ -4038,7 +4038,7 @@ fn edit_market_with_foreign_asset() { get_deadlines(), gen_metadata(2), MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let market = MarketCommons::market(&0).unwrap(); @@ -4060,7 +4060,7 @@ fn the_entire_market_lifecycle_works_with_timestamps() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); @@ -4100,7 +4100,7 @@ fn full_scalar_market_lifecycle() { gen_metadata(3), MarketCreation::Permissionless, MarketType::Scalar(10..=30), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); @@ -4309,7 +4309,7 @@ fn market_resolve_does_not_hold_liquidity_withdraw() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); deploy_swap_pool(MarketCommons::market(&0).unwrap(), 0).unwrap(); @@ -4351,7 +4351,7 @@ fn authorized_correctly_resolves_disputed_market() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); @@ -4497,7 +4497,7 @@ fn approve_market_correctly_unreserves_advisory_bond() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let market_id = 0; @@ -4535,7 +4535,7 @@ fn deploy_swap_pool_correctly_sets_weight_of_base_asset() { get_deadlines(), gen_metadata(50), MarketType::Categorical(3), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), 1, LIQUIDITY, weights, @@ -4563,7 +4563,7 @@ fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_short() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let amount = 123 * BASE; @@ -4601,7 +4601,7 @@ fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_long() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(category_count), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let amount = 123 * BASE; @@ -4642,7 +4642,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let alice_balance_before = Balances::free_balance(ALICE); @@ -4688,7 +4688,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let alice_balance_before = Balances::free_balance(ALICE); @@ -4754,7 +4754,7 @@ fn outsider_reports_wrong_outcome() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); @@ -4833,7 +4833,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); @@ -4879,7 +4879,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); @@ -4926,7 +4926,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let alice_balance_before = Balances::free_balance(ALICE); @@ -4977,7 +4977,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); @@ -5029,7 +5029,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); let alice_balance_before = Balances::free_balance(ALICE); @@ -5089,7 +5089,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); @@ -5147,7 +5147,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); @@ -5219,7 +5219,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM, )); @@ -5286,7 +5286,7 @@ fn report_fails_on_market_state_proposed() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); assert_noop!( @@ -5309,7 +5309,7 @@ fn report_fails_on_market_state_closed_for_advised_market() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); assert_noop!( @@ -5332,7 +5332,7 @@ fn report_fails_on_market_state_collecting_subsidy() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::RikiddoSigmoidFeeMarketEma )); assert_noop!( @@ -5355,7 +5355,7 @@ fn report_fails_on_market_state_insufficient_subsidy() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::RikiddoSigmoidFeeMarketEma )); let _ = MarketCommons::mutate_market(&0, |market| { @@ -5382,7 +5382,7 @@ fn report_fails_on_market_state_active() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); assert_noop!( @@ -5405,7 +5405,7 @@ fn report_fails_on_market_state_suspended() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let _ = MarketCommons::mutate_market(&0, |market| { @@ -5432,7 +5432,7 @@ fn report_fails_on_market_state_resolved() { gen_metadata(2), MarketCreation::Advised, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let _ = MarketCommons::mutate_market(&0, |market| { @@ -5459,7 +5459,7 @@ fn report_fails_if_reporter_is_not_the_oracle() { gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - MarketDisputeMechanism::SimpleDisputes, + Some(MarketDisputeMechanism::SimpleDisputes), ScoringRule::CPMM )); let market = MarketCommons::market(&0).unwrap(); @@ -5500,7 +5500,7 @@ fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); }); @@ -5528,7 +5528,7 @@ fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, )); }); @@ -5556,7 +5556,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_blocks() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), crate::Error::::MarketDurationTooLong, @@ -5587,7 +5587,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { gen_metadata(0), MarketCreation::Permissionless, MarketType::Categorical(3), - MarketDisputeMechanism::Authorized, + Some(MarketDisputeMechanism::Authorized), ScoringRule::CPMM, ), crate::Error::::MarketDurationTooLong, @@ -5635,7 +5635,7 @@ fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amo let metadata = gen_metadata(0x99); let MultiHash::Sha3_384(multihash) = metadata; let market_type = MarketType::Categorical(7); - let dispute_mechanism = MarketDisputeMechanism::Authorized; + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); let creator_fee = Perbill::from_parts(1); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(creator), @@ -5690,7 +5690,7 @@ fn create_cpmm_market_and_deploy_assets_sets_the_correct_market_parameters_and_r let MultiHash::Sha3_384(multihash) = metadata; let category_count = 7; let market_type = MarketType::Categorical(category_count); - let dispute_mechanism = MarketDisputeMechanism::Authorized; + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); let creator_fee = Perbill::from_parts(1); let lp_fee = 0; let weight = ::MinWeight::get(); @@ -5747,7 +5747,7 @@ fn create_market_functions_respect_fee_boundaries() { let scoring_rule = ScoringRule::CPMM; let market_type = MarketType::Categorical(category_count); let creation_type = MarketCreation::Permissionless; - let dispute_mechanism = MarketDisputeMechanism::Authorized; + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); let lp_fee = 0; assert_ok!(PredictionMarkets::create_market( @@ -5816,6 +5816,81 @@ fn create_market_functions_respect_fee_boundaries() { }); } +#[test] +fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(1..2), + Deadlines { + grace_period: 1, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::CPMM, + ), + Error::::NonZeroDisputePeriodOnTrustedMarket + ); + }); +} + +#[test] +fn trusted_market_complete_lifecycle() { + ExtBuilder::default().build().execute_with(|| { + let end = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::CPMM, + )); + let market_id = 0; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(FRED), + market_id, + BASE + )); + run_to_block(end); + let outcome = OutcomeReport::Categorical(1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + outcome.clone() + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); + assert_eq!(market.resolved_outcome, Some(outcome)); + assert_eq!(market.dispute_mechanism, None); + assert!(market.bonds.oracle.unwrap().is_settled); + assert_eq!(market.bonds.outsider, None); + assert_eq!(market.bonds.dispute, None); + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); + // Ensure that we don't accidentally leave any artifacts. + assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); + assert!(MarketIdsPerReportBlock::::iter().next().is_none()); + }); +} + fn deploy_swap_pool( market: Market>, market_id: u128, diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index a76c95954..3ef74599b 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -75,7 +75,8 @@ pub trait WeightInfoZeitgeist { fn redeem_shares_categorical() -> Weight; fn redeem_shares_scalar() -> Weight; fn reject_market(c: u32, o: u32, r: u32) -> Weight; - fn report(m: u32) -> Weight; + fn report_market_with_dispute_mechanism(m: u32) -> Weight; + fn report_trusted_market() -> Weight; fn sell_complete_set(a: u32) -> Weight; fn start_subsidy(a: u32) -> Weight; fn market_status_manager(b: u32, f: u32) -> Weight; @@ -646,29 +647,21 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } - /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) - fn report(_m: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `745` - // Estimated: `7036` - // Minimum execution time: 38_411 nanoseconds. - Weight::from_parts(43_779_882, 7036) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(541), added: 3016, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:64 w:64) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:64) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) + // Storage: MarketCommons Markets (r:1 w:1) + // Storage: Timestamp Now (r:1 w:0) + // Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) + fn report_market_with_dispute_mechanism(_m: u32) -> Weight { + Weight::from_ref_time(69_185_134) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn report_trusted_market() -> Weight { + Weight::from_ref_time(123) + } + // Storage: MarketCommons Markets (r:1 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Tokens Accounts (r:2 w:2) + // Storage: Tokens TotalIssuance (r:2 w:2) fn sell_complete_set(a: u32) -> Weight { // Proof Size summary in bytes: // Measured: `756 + a * (161 ±0)` diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 8304fa5ce..b89ee5713 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -186,10 +186,7 @@ mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let market = T::MarketCommons::market(&market_id)?; - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(&market)?; ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); ensure!(market.matches_outcome_report(&outcome), Error::::OutcomeMismatch); let report = market.report.as_ref().ok_or(Error::::MarketIsNotReported)?; @@ -249,6 +246,15 @@ mod pallet { Ok(()) } + #[inline] + fn ensure_dispute_mechanism(market: &MarketOf) -> DispatchResult { + ensure!( + market.dispute_mechanism == Some(MarketDisputeMechanism::SimpleDisputes), + Error::::MarketDoesNotHaveSimpleDisputesMechanism + ); + Ok(()) + } + fn get_auto_resolve( disputes: &[MarketDispute>], market: &MarketOf, @@ -322,10 +328,7 @@ mod pallet { _: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; let res = ResultWithWeightInfo { result: (), weight: T::WeightInfo::on_dispute_weight() }; @@ -337,10 +340,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); let disputes = Disputes::::get(market_id); @@ -370,10 +370,7 @@ mod pallet { resolved_outcome: &OutcomeReport, mut overall_imbalance: NegativeImbalanceOf, ) -> Result>, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; ensure!(market.status == MarketStatus::Disputed, Error::::InvalidMarketStatus); let disputes = Disputes::::get(market_id); @@ -427,7 +424,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> ResultWithWeightInfo> { - if market.dispute_mechanism != MarketDisputeMechanism::SimpleDisputes { + if market.dispute_mechanism != Some(MarketDisputeMechanism::SimpleDisputes) { return ResultWithWeightInfo { result: None, weight: T::WeightInfo::get_auto_resolve_weight(T::MaxDisputes::get()), @@ -448,10 +445,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; let disputes = >::get(market_id); let disputes_len = disputes.len() as u32; @@ -470,10 +464,7 @@ mod pallet { ResultWithWeightInfo>>, DispatchError, > { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; let disputes_len = >::decode_len(market_id).unwrap_or(0) as u32; @@ -496,10 +487,7 @@ mod pallet { market_id: &Self::MarketId, market: &MarketOf, ) -> Result, DispatchError> { - ensure!( - market.dispute_mechanism == MarketDisputeMechanism::SimpleDisputes, - Error::::MarketDoesNotHaveSimpleDisputesMechanism - ); + Self::ensure_dispute_mechanism(market)?; let mut disputes_len = 0u32; // `Disputes` is emtpy unless the market is disputed, so this is just a defensive @@ -553,7 +541,7 @@ where 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::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), metadata: Default::default(), oracle: T::PalletId::get().into_account_truncating(), period: zeitgeist_primitives::types::MarketPeriod::Block(Default::default()), diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index 383792a5a..c0ad3deaf 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -38,7 +38,7 @@ const DEFAULT_MARKET: MarketOf = Market { creator_fee: sp_runtime::Perbill::zero(), creator: 0, market_type: MarketType::Scalar(0..=100), - dispute_mechanism: MarketDisputeMechanism::SimpleDisputes, + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), metadata: vec![], oracle: 0, period: MarketPeriod::Block(0..100), @@ -54,7 +54,7 @@ const DEFAULT_MARKET: MarketOf = Market { fn on_dispute_denies_non_simple_disputes_markets() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::Court; + market.dispute_mechanism = Some(MarketDisputeMechanism::Court); assert_noop!( SimpleDisputes::on_dispute(&0, &market), Error::::MarketDoesNotHaveSimpleDisputesMechanism @@ -66,7 +66,7 @@ fn on_dispute_denies_non_simple_disputes_markets() { fn on_resolution_denies_non_simple_disputes_markets() { ExtBuilder::default().build().execute_with(|| { let mut market = DEFAULT_MARKET; - market.dispute_mechanism = MarketDisputeMechanism::Court; + market.dispute_mechanism = Some(MarketDisputeMechanism::Court); assert_noop!( SimpleDisputes::on_resolution(&0, &market), Error::::MarketDoesNotHaveSimpleDisputesMechanism diff --git a/zrml/swaps/src/benchmarks.rs b/zrml/swaps/src/benchmarks.rs index 033a68738..cf3b942ea 100644 --- a/zrml/swaps/src/benchmarks.rs +++ b/zrml/swaps/src/benchmarks.rs @@ -130,7 +130,7 @@ fn push_default_market(caller: T::AccountId, oracle: T::AccountId) -> creator_fee: Perbill::zero(), creator: caller, market_type: MarketType::Categorical(3), - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), metadata: vec![0; 50], oracle, period: MarketPeriod::Block(0u32.into()..1u32.into()), @@ -230,7 +230,7 @@ benchmarks! { creator_fee: Perbill::zero(), creator: caller.clone(), market_type: MarketType::Categorical(category_count), - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), metadata: vec![0; 50], oracle: caller.clone(), period: MarketPeriod::Block(0u32.into()..1u32.into()), @@ -271,7 +271,7 @@ benchmarks! { creator_fee: Perbill::zero(), creator: caller.clone(), market_type: MarketType::Scalar(0..=99), - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), metadata: vec![0; 50], oracle: caller.clone(), period: MarketPeriod::Block(0u32.into()..1u32.into()), diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 5f485959e..aaaf31a4f 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -1952,6 +1952,9 @@ mod pallet { let pool_amount = >::zero(); (pool_status, total_subsidy, total_weight, weights, pool_amount) } + ScoringRule::Orderbook => { + return Err(Error::::InvalidScoringRule.into()); + } }; let pool = Pool { assets: sorted_assets, @@ -2510,6 +2513,9 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; cost_before.checked_sub(&cost_after).ok_or(ArithmeticError::Overflow)? } + ScoringRule::Orderbook => { + return Err(Error::::InvalidScoringRule.into()); + } }; if let Some(maao) = min_asset_amount_out { @@ -2559,6 +2565,7 @@ mod pallet { ScoringRule::RikiddoSigmoidFeeMarketEma => Ok( T::WeightInfo::swap_exact_amount_in_rikiddo(pool.assets.len().saturated_into()), ), + ScoringRule::Orderbook => Err(Error::::InvalidScoringRule.into()), } } @@ -2666,6 +2673,9 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; cost_after.checked_sub(&cost_before).ok_or(ArithmeticError::Overflow)? } + ScoringRule::Orderbook => { + return Err(Error::::InvalidScoringRule.into()); + } }; if asset_in == pool.base_asset && !handle_fees && to_adjust_in_value { @@ -2727,6 +2737,7 @@ mod pallet { pool.assets.len().saturated_into(), )) } + ScoringRule::Orderbook => Err(Error::::InvalidScoringRule.into()), } } } diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index 1ab5585dc..9d44b1b9f 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -335,7 +335,7 @@ pub(super) fn mock_market( creator_fee: Perbill::from_parts(0), creator: DEFAULT_MARKET_CREATOR, market_type: MarketType::Categorical(categories), - dispute_mechanism: MarketDisputeMechanism::Authorized, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), metadata: vec![0; 50], oracle: DEFAULT_MARKET_ORACLE, period: MarketPeriod::Block(0..1), diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index 615611697..a34e2e719 100644 --- a/zrml/swaps/src/utils.rs +++ b/zrml/swaps/src/utils.rs @@ -216,6 +216,9 @@ where return Err(Error::::UnsupportedTrade.into()); } } + ScoringRule::Orderbook => { + return Err(Error::::InvalidScoringRule.into()); + } } let spot_price_after = @@ -230,6 +233,9 @@ where spot_price_before.saturating_sub(spot_price_after) < 20u8.into(), Error::::MathApproximation ), + ScoringRule::Orderbook => { + return Err(Error::::InvalidScoringRule.into()); + } } if let Some(max_price) = p.max_price { @@ -250,6 +256,9 @@ 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 => { + return Err(Error::::InvalidScoringRule.into()); + } } (p.event)(SwapEvent {