diff --git a/Cargo.lock b/Cargo.lock index f59d33291..3363f08a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "battery-station-runtime" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "common-runtime", @@ -1206,7 +1206,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "cumulus-pallet-xcmp-queue", @@ -14277,9 +14277,13 @@ dependencies = [ "time 0.3.24", ] +[[package]] +name = "zeitgeist-macros" +version = "0.4.3" + [[package]] name = "zeitgeist-node" -version = "0.4.2" +version = "0.4.3" dependencies = [ "battery-station-runtime", "cfg-if", @@ -14371,7 +14375,7 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arbitrary", "fixed", @@ -14394,7 +14398,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "common-runtime", @@ -14518,7 +14522,7 @@ dependencies = [ [[package]] name = "zrml-authorized" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14536,7 +14540,7 @@ dependencies = [ [[package]] name = "zrml-court" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arrayvec 0.7.4", "env_logger 0.10.1", @@ -14561,7 +14565,7 @@ dependencies = [ [[package]] name = "zrml-global-disputes" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14582,7 +14586,7 @@ dependencies = [ [[package]] name = "zrml-liquidity-mining" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14601,7 +14605,7 @@ dependencies = [ [[package]] name = "zrml-market-commons" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-support", @@ -14613,12 +14617,13 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", + "test-case", "zeitgeist-primitives", ] [[package]] name = "zrml-neo-swaps" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "env_logger 0.10.1", @@ -14643,7 +14648,6 @@ dependencies = [ "sp-api", "sp-io", "sp-runtime", - "substrate-fixed", "test-case", "typenum 1.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "xcm", @@ -14664,7 +14668,7 @@ dependencies = [ [[package]] name = "zrml-orderbook" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14699,7 +14703,7 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14721,7 +14725,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14743,7 +14747,6 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", - "substrate-fixed", "test-case", "xcm", "zeitgeist-primitives", @@ -14774,7 +14777,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets-runtime-api" -version = "0.4.2" +version = "0.4.3" dependencies = [ "parity-scale-codec", "sp-api", @@ -14783,7 +14786,7 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arbitrary", "cfg-if", @@ -14817,7 +14820,7 @@ dependencies = [ [[package]] name = "zrml-simple-disputes" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14838,7 +14841,7 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14855,7 +14858,7 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14869,16 +14872,16 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "scale-info", + "serde", "sp-api", "sp-arithmetic", "sp-io", "sp-runtime", - "substrate-fixed", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", "zrml-liquidity-mining", "zrml-market-commons", - "zrml-rikiddo", "zrml-swaps", "zrml-swaps-runtime-api", ] @@ -14899,7 +14902,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.2" +version = "0.4.3" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14912,7 +14915,7 @@ dependencies = [ [[package]] name = "zrml-swaps-runtime-api" -version = "0.4.2" +version = "0.4.3" dependencies = [ "parity-scale-codec", "sp-api", diff --git a/Cargo.toml b/Cargo.toml index 65b8416fe..1b0769fc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] default-members = [ + "macros", "node", "primitives", "runtime/common", @@ -23,6 +24,7 @@ default-members = [ "zrml/styx", ] members = [ + "macros", "node", "primitives", "runtime/common", @@ -228,6 +230,7 @@ zrml-swaps-rpc = { path = "zrml/swaps/rpc" } # Zeitgeist (wasm) common-runtime = { path = "runtime/common", default-features = false } +zeitgeist-macros = { path = "macros", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } zrml-court = { path = "zrml/court", default-features = false } diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index 84e5126ea..085fecad4 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -3,22 +3,21 @@ 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. +further elaborate on the style guide used in this repository. ## Comments - Comments **must** be wrapped at 100 chars per line. -- Comments **must** be formulated in markdown. -## Doc comments +## Comments and Docstrings - Documentation is written using Markdown syntax. - Function documentation should be kept lean and mean. Try to avoid documenting the parameters, and instead choose self-documenting parameter names. If parameters interact in a complex manner (for example, if two arguments of type `Vec` must have the same length), then add a paragraph explaining this. -- Begin every docstring with a meaningful one sentence description of the - function in third person. +- Begin every docstring with a meaningful one-sentence description of the + function in the third person. - Avoid WET documentation such as this: ```rust @@ -45,8 +44,9 @@ further elaborate the style guide used in this repository. (to make understanding the benchmarks easier). - Docstrings for dispatchables need not document the `origin` parameter, but should specify what origins the dispatchable may be called by. -- Docstrings for dispatchables **must** include the events that the dispatchable - emits. +- Docstrings for dispatchables **must** include the high-level events that the + dispatchable emits and state under which conditions these events are emitted. +- Document side-effects of functions. - Use `#![doc = include_str!("../README.md")]`. - Detail non-trivial algorithms in comments inside the function. @@ -58,6 +58,10 @@ An example of a good docstring would be this: /// /// May only be (successfully) called by `RejectOrigin`. The fraction of the advisory bond that is /// slashed is determined by `AdvisoryBondSlashPercentage`. +/// +/// # Emits +/// +/// - `MarketRejected` on success. pub fn reject_market( origin: OriginFor, #[pallet::compact] market_id: MarketIdOf, @@ -76,32 +80,29 @@ duplicating documentation. - Format code contained in macro invocations (`impl_benchmarks!`, `decl_runtime_apis!`, homebrew macros in `runtime/`, etc.) and attributes (`#[pallet::weight(...)`, etc.) manually. -- Add trailing commas in macro invocations manually, as rustfmt won't add them - automatically. - - ```rust - ensure!( - a_very_very_very_very_very_very_very_long_variable, - b_very_very_very_very_very_very_very_long_variable, // This comma is not ensured by rustfmt. - ) - ``` ## Code Style - Never use panickers. -- Prefer double turbofish `Vec::::new()` over single turbofish - `>::new()`. - All branches of match expressions **should** be explicit. Avoid using the catch-all `_ =>`. -- When changing enums, maintain the existing order and add variants only at the - end of the enum to prevent messing up indices. -- Maintain lexicographical ordering of traits in `#[derive(...)]` attributes. +- When removing variants from enums that are used in storage or emitted, then + explicitly state the scale index of each variant: + ```rust + enum E { + #[codec(index = 0)] + V1, + #[codec(index = 1)] + V2, + /// --- snip! --- + } + ``` ## Crate and Pallet Structure -- Don't dump all code into `lib.rs`. Split code multiple files (`types.rs`, +- Don't dump all code into `lib.rs`. Split code into multiple files (`types.rs`, `traits.rs`, etc.) or even modules (`types/`, `traits/`, etc.). -- Changes to pallets **must** retain order of dispatchables. +- Changes to pallets **must** retain the order of dispatchables. - Sort pallet contents in the following order: - `Config` trait - Type values @@ -113,4 +114,45 @@ duplicating documentation. - Hooks - Dispatchables - Pallet's public and private functions - - Trait impelmentations + - Trait implementations + +## Code Style and Design + +- Exceed 70 lines of code per function only in exceptional circumstances. Aim + for less. +- No `while` in production. All `for` loops must have a maximum number of + passes. +- Use depth checks when using recursion in production. Use recursion only if the + algorithm is defined using recursion. +- Avoid `mut` in production code if possible without much pain. +- Mark all extrinsics `transactional`, even if they satisfy + the verify-first/write-later principle. +- Avoid indentation over five levels; never go over seven levels. +- All public functions must be documented. Documentation of `pub(crate)` and + private functions is optional but encouraged. +- Keep modules lean. Only exceed 1,000 lines of code per file in exceptional + circumstances. Aim for less (except in `lib.rs`). Consider splitting modules + into separate files. Auto-generated files are excluded. + +## Workflow + +- Merges require one review. Additional reviews may be requested. +- Every merge into a feature branch requires a review. +- Aim for at most 500 LOC added per PR. Only exceed 1,000 LOC lines added in a + PR in exceptional circumstances. Plan ahead and break a large PR into smaller + PRs targeting a feature branch. Feature branches are exempt from this rule. +- Reviews take priority over most other tasks. +- Reviewing a PR should not take longer than two business days. Aim for shorter + PRs if the changes are complex. +- A PR should not be in flight (going from first `s:ready-for-review` to + `s:accepted`) for longer than two weeks. Aim for shorter PRs if the changes + are complex. + +## Testing + +- Aim for 100% code coverage, excluding only logic errors that are raised on + inconsistent state. In other words: All execution paths **should** be tested. + There should be a clear justification for every LOC without test coverage. +- For larger modules, use one test file per extrinsic for unit tests. Make unit + tests as decoupled as possible from other modules. Place end-to-end and + integration tests in extra files. diff --git a/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index e2070d9e9..70afccec1 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -12,6 +12,79 @@ As of 0.3.9, the changelog's format is based on components which query the chain's storage, the extrinsics or the runtime APIs/RPC interface. +## v0.5.0 + +[#1197]: https://github.com/zeitgeistpm/zeitgeist/pull/1197 +[#1178]: https://github.com/zeitgeistpm/zeitgeist/pull/1178 + +### Changes + +- ⚠️ Move the `zeitgeist_primitives::Pool` struct to `zrml_swaps::types::Pool` and change the following fields ([#1197]): + - Remove `market_id` + - Make `swap_fee` non-optional + - Remove `total_subsidy` + - Make `total_weight` non-optional + - Make `weights` non-optional +- ⚠️ Change the type of `liquidity_shares_manager` in `zrml_neo_swaps::types::Pool` from `zrml_neo_swaps::types::SoloLp` to `zrml_neo_swaps::types::LiquidityTree`. Details on the liquidity tree can be found in the `README.md` of zrml-neo-swaps and the documentation of the `LiquidityTree` object ([#1179]). + +### Migrations + +- Closed all CPMM pools. Withdrawals are still allowed. Creating new pools will + be impossible until further updates are deployed. ([#1197]) +- Remove all Rikiddo storage elements. ([#1197]) +- Migrate neo-swaps `Pools` storage. The market creator's liquidity position is translated into a position in the liquidity tree of the same value ([#1178]). + +### Removed + +- ⚠️ Remove the `Disputes` storage element from zrml-prediction-markets. + ([#1197]) +- ⚠️ Remove the following extrinsics from zrml-prediction-markets. ([#1197]): + - `create_cpmm_market_and_deploy_assets` + - `deploy_swap_pool_and_additional_liquidity` + - `deploy_swap_pool_for_market` +- ⚠️ Remove the following config values from zrml-prediction-markets ([#1197]): + - `MaxSubsidyPeriod` + - `MinSubsidyPeriod` + - `Swaps` +- Remove automatic arbitrage for CPMM pools. ([#1197]) +- ⚠️ Remove the following extrinsics from zrml-swaps ([#1197]): + - `admin_clean_up_pool` + - `pool_exit_subsidy` + - `pool_join_subsidy` +- ⚠️ Remove the following config values from zrml-swaps ([#1197]): + - `FixedTypeU` + - `FixedTypeS` + - `LiquidityMining` + - `MarketCommons` + - `MinSubsidy` + - `MinSubsidyPerAccount` + - `RikiddoSigmoidFeeMarketEma` +- ⚠️ Remove `CPMM` and `RikiddoSigmoidFeeMarketEma` from `ScoringRule`. + ([#1197]) +- ⚠️ Remove `Suspended`, `CollectingSubsidy` and `InsufficientSubsidy` from + `MarketStatus`. ([#1197]) + +### Deprecate + +- ⚠️ Deprecate the following storage elements of zrml-prediction-markets (will + be removed in v0.5.1; [#1197]): + - `MarketIdsPerOpenBlock` + - `MarketIdsPerOpenTimeFrame` + - `MarketsCollectingSubsidy` +- ⚠️ Deprecate the following storage elements of zrml-market-commons (will be + removed at an unspecified point in time; [#1197]): + - `MarketPool` +- ⚠️ Deprecate the following storage elements of zrml-swaps (will be removed in + v0.5.1; [#1197]): + - `SubsidyProviders` + - `PoolsCachedForArbitrage` + +## v0.4.3 + +### Removed + +- Remove old storage migrations + ## v0.4.2 [#1127]: https://github.com/zeitgeistpm/zeitgeist/pull/1127 diff --git a/docs/review_checklist.md b/docs/review_checklist.md index 90c94b2ce..3b9220c61 100644 --- a/docs/review_checklist.md +++ b/docs/review_checklist.md @@ -53,6 +53,7 @@ - [ ] The try-runtime passes without any warnings (substrate storage operations often just log a warning instead of failing, so these warnings usually point to problem which could break the storage). +- [ ] Ensure that the [STYLE_GUIDE] is observed. ## Events @@ -85,3 +86,4 @@ Additional info (similar to the remark emitted by anywhere in the chain storage. [docs.zeitgeist.pm]: docs.zeitgeist.pm +[STYLE_GUIDE]: ./STYLE_GUIDE.md diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 000000000..86492935c --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,5 @@ +[package] +authors = ["Zeitgeist PM "] +edition = "2021" +name = "zeitgeist-macros" +version = "0.4.3" diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 000000000..783c292e7 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,34 @@ +// Copyright 2023-2024 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 . + +/// Creates an `alloc::collections::BTreeMap` from the pattern `{ key => value, ... }`. +/// +/// ```ignore +/// // Example: +/// let m = create_b_tree_map!({ 0 => 1, 2 => 3 }); +/// assert_eq!(m[2], 3); +/// +/// // Overwriting a key:) +/// let m = create_b_tree_map!({ 0 => "foo", 0 => "bar" }); +/// assert_eq!(m[0], "bar"); +/// ``` +#[macro_export] +macro_rules! create_b_tree_map { + ({ $($key:expr => $value:expr),* $(,)? } $(,)?) => { + [$(($key, $value),)*].iter().cloned().collect::>() + } +} diff --git a/node/Cargo.toml b/node/Cargo.toml index 101edf598..59ef25dd7 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -187,7 +187,7 @@ description = "An evolving blockchain for prediction markets and futarchy." edition = "2021" homepage = "https://zeitgeist.pm" name = "zeitgeist-node" -version = "0.4.2" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 53d3b282e..4a4bca7b9 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -22,6 +22,7 @@ typenum = { workspace = true } [features] default = ["std"] mock = [] +runtime-benchmarks = [] std = [ "frame-support/std", "frame-system/std", @@ -38,4 +39,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-primitives" -version = "0.4.2" +version = "0.4.3" diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index bc9c61637..40c2ea37c 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -16,7 +16,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::types::{CategoryIndex, PoolId, SerdeWrapper}; +#[cfg(feature = "runtime-benchmarks")] +use crate::traits::ZeitgeistAssetEnumerator; +use crate::{ + traits::PoolSharesId, + types::{CategoryIndex, PoolId, SerdeWrapper}, +}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -53,6 +58,19 @@ pub enum Asset { ParimutuelShare(MI, CategoryIndex), } +impl PoolSharesId> for Asset { + fn pool_shares_id(pool_id: SerdeWrapper) -> Self { + Self::PoolShare(pool_id) + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl ZeitgeistAssetEnumerator for Asset { + fn create_asset_id(t: MI) -> Self { + Asset::CategoricalOutcome(t, 0) + } +} + /// In a scalar market, users can either choose a `Long` position, /// meaning that they think the outcome will be closer to the upper bound /// or a `Short` position meaning that they think the outcome will be closer diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 0e1fee734..112a07834 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -97,13 +97,9 @@ parameter_types! { pub const MaxMarketLifetime: BlockNumber = 100_000_000_000; pub const MaxOracleDuration: BlockNumber = 30; pub const MaxRejectReasonLen: u32 = 1024; - // 2_678_400_000 = 31 days. - pub const MaxSubsidyPeriod: Moment = 2_678_400_000; pub const MinCategories: u16 = 2; pub const MinDisputeDuration: BlockNumber = 2; pub const MinOracleDuration: BlockNumber = 2; - // 60_000 = 1 minute. Should be raised to something more reasonable in the future. - pub const MinSubsidyPeriod: Moment = 60_000; pub const OracleBond: Balance = 50 * CENT; pub const OutsiderBond: Balance = 2 * OracleBond::get(); pub const PmPalletId: PalletId = PalletId(*b"zge/pred"); @@ -129,8 +125,6 @@ parameter_types! { pub const MaxSwapFee: Balance = BASE / 10; // 10% pub const MaxTotalWeight: Balance = 50 * BASE; pub const MaxWeight: Balance = 50 * BASE; - pub const MinSubsidy: Balance = 100 * BASE; - pub const MinSubsidyPerAccount: Balance = MinSubsidy::get(); pub const MinWeight: Balance = BASE; pub const SwapsPalletId: PalletId = PalletId(*b"zge/swap"); } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5a3d424d8..3c571da82 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -26,8 +26,6 @@ mod market; pub mod math; mod max_runtime_usize; mod outcome_report; -mod pool; -mod pool_status; mod proxy_type; mod serde_wrapper; pub mod traits; diff --git a/primitives/src/market.rs b/primitives/src/market.rs index 1a9751ca2..02f3972ad 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{pool::ScoringRule, types::OutcomeReport}; +use crate::types::OutcomeReport; use alloc::vec::Vec; use core::ops::{Range, RangeInclusive}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -71,10 +71,7 @@ pub struct Market { impl Market { pub fn resolution_mechanism(&self) -> ResolutionMechanism { match self.scoring_rule { - ScoringRule::CPMM - | ScoringRule::Lmsr - | ScoringRule::Orderbook - | ScoringRule::RikiddoSigmoidFeeMarketEma => ResolutionMechanism::RedeemTokens, + ScoringRule::Lmsr | ScoringRule::Orderbook => ResolutionMechanism::RedeemTokens, ScoringRule::Parimutuel => ResolutionMechanism::Noop, } } @@ -220,14 +217,6 @@ pub struct GlobalDisputeItem { pub initial_vote_amount: Balance, } -// TODO to remove, when Disputes storage item is removed -#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct OldMarketDispute { - pub at: BlockNumber, - pub by: AccountId, - pub outcome: OutcomeReport, -} - #[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] pub struct MarketDispute { pub at: BlockNumber, @@ -294,6 +283,13 @@ pub struct Deadlines { pub dispute_duration: BN, } +#[derive(TypeInfo, Clone, Copy, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] +pub enum ScoringRule { + Lmsr, + Orderbook, + Parimutuel, +} + /// Defines the state of the market. #[derive(Clone, Copy, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] pub enum MarketStatus { @@ -302,14 +298,8 @@ pub enum MarketStatus { Proposed, /// Trading on the market is active. Active, - /// Trading on the market is temporarily paused. - Suspended, /// Trading on the market has concluded. Closed, - /// The market is collecting subsidy. - CollectingSubsidy, - /// The market was discarded due to insufficient subsidy. - InsufficientSubsidy, /// The market has been reported. Reported, /// The market outcome is being disputed. @@ -439,7 +429,7 @@ mod tests { oracle_duration: 1_u32, dispute_duration: 1_u32, }, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: MarketStatus::Active, report: None, resolved_outcome: None, diff --git a/primitives/src/math/fixed.rs b/primitives/src/math/fixed.rs index 425e48ac0..eff7434d5 100644 --- a/primitives/src/math/fixed.rs +++ b/primitives/src/math/fixed.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. diff --git a/primitives/src/math/mod.rs b/primitives/src/math/mod.rs index e89419a3a..216c57796 100644 --- a/primitives/src/math/mod.rs +++ b/primitives/src/math/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -17,3 +17,4 @@ pub mod checked_ops_res; pub mod fixed; +pub mod root; diff --git a/zrml/swaps/src/root.rs b/primitives/src/math/root.rs similarity index 97% rename from zrml/swaps/src/root.rs rename to primitives/src/math/root.rs index 9814fdde6..bb0cea540 100644 --- a/zrml/swaps/src/root.rs +++ b/primitives/src/math/root.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -69,7 +69,7 @@ use sp_runtime::traits::AtLeast32BitUnsigned; /// - `max`: The maximum value of the preimage /// - `max_iterations`: Break after this many iterations /// - `tol`: Break if the interval is smaller than this -pub(crate) fn calc_preimage( +pub fn calc_preimage( f: F, value: T, mut min: T, @@ -158,9 +158,8 @@ fn dist(x: T, y: T) -> T { #[cfg(test)] mod tests { use super::*; - use crate::fixed::bpowi; + use crate::{constants::BASE, math::fixed::FixedMul}; use test_case::test_case; - use zeitgeist_primitives::constants::BASE; const _1: u128 = BASE; const _2: u128 = 2 * BASE; @@ -207,8 +206,8 @@ mod tests { fn calc_preimage_works_with_increasing_polynomial(value: u128, expected: u128) { // f(x) = 2x^3 - x^2 - x + 1 is positive and increasing on [1, \infty]. let f = |x: u128| { - let third_order = 2 * bpowi(x, 3)?; - let second_order = bpowi(x, 2)?; + let third_order = 2 * x.bmul(x)?.bmul(x)?; + let second_order = x.bmul(x)?; // Add positive terms first to prevent underflow. Ok(third_order + _1 - second_order - x) }; @@ -225,7 +224,7 @@ mod tests { #[test_case(56_476_573_221, 15_574_893_554)] fn calc_preimage_works_with_decreasing_polynomial(value: u128, expected: u128) { // f(x) = -x^3 + x^2 + 7 is positive and decreasing on [1, 2]. - let f = |x: u128| Ok(_7 + bpowi(x, 2)? - bpowi(x, 3)?); + let f = |x: u128| Ok(_7 + x.bmul(x)? - x.bmul(x)?.bmul(x)?); let tolerance = _1_1000; let (preimage, _) = calc_preimage(f, value, _1, _2, usize::MAX, _1_1000).unwrap(); assert_approx!(preimage, expected, tolerance); diff --git a/primitives/src/pool.rs b/primitives/src/pool.rs deleted file mode 100644 index 2b1c70529..000000000 --- a/primitives/src/pool.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2023 Forecasting Technologies LTD. -// Copyright 2021-2022 Zeitgeist PM LLC. -// -// This file is part of Zeitgeist. -// -// Zeitgeist is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// Zeitgeist is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Zeitgeist. If not, see . - -use crate::{ - constants::MAX_ASSETS, - types::{Asset, PoolStatus}, -}; -use alloc::{collections::BTreeMap, vec::Vec}; -use parity_scale_codec::{Compact, Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::{RuntimeDebug, SaturatedConversion}; - -#[derive(TypeInfo, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] -pub struct Pool -where - MarketId: MaxEncodedLen, -{ - pub assets: Vec>, - pub base_asset: Asset, - pub market_id: MarketId, - pub pool_status: PoolStatus, - pub scoring_rule: ScoringRule, - pub swap_fee: Option, - pub total_subsidy: Option, - pub total_weight: Option, - pub weights: Option, u128>>, -} - -impl Pool -where - MarketId: MaxEncodedLen + Ord, -{ - pub fn bound(&self, asset: &Asset) -> bool { - if let Some(weights) = &self.weights { - return BTreeMap::get(weights, asset).is_some(); - } - - false - } -} - -impl MaxEncodedLen for Pool -where - Balance: MaxEncodedLen, - MarketId: MaxEncodedLen, -{ - fn max_encoded_len() -> usize { - let max_encoded_length_bytes = >::max_encoded_len(); - let b_tree_map_size = 1usize - .saturating_add(MAX_ASSETS.saturated_into::().saturating_mul( - >::max_encoded_len().saturating_add(u128::max_encoded_len()), - )) - .saturating_add(max_encoded_length_bytes); - - >::max_encoded_len() - .saturating_mul(MAX_ASSETS.saturated_into::()) - .saturating_add(max_encoded_length_bytes) - .saturating_add(>>::max_encoded_len()) - .saturating_add(MarketId::max_encoded_len()) - .saturating_add(PoolStatus::max_encoded_len()) - .saturating_add(ScoringRule::max_encoded_len()) - .saturating_add(>::max_encoded_len().saturating_mul(2)) - .saturating_add(>::max_encoded_len()) - .saturating_add(b_tree_map_size) - } -} - -#[derive(TypeInfo, Clone, Copy, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] -pub enum ScoringRule { - CPMM, - RikiddoSigmoidFeeMarketEma, - Lmsr, - Orderbook, - Parimutuel, -} diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index cea15e4d0..00fce342a 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -23,6 +23,7 @@ mod distribute_fees; mod market_commons_pallet_api; mod market_id; mod swaps; +mod zeitgeist_asset; mod zeitgeist_multi_reservable_currency; pub use complete_set_operations_api::CompleteSetOperationsApi; @@ -32,4 +33,5 @@ pub use distribute_fees::DistributeFees; pub use market_commons_pallet_api::MarketCommonsPalletApi; pub use market_id::MarketId; pub use swaps::Swaps; +pub use zeitgeist_asset::*; pub use zeitgeist_multi_reservable_currency::ZeitgeistAssetManager; diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index e5866f9f9..251365157 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -84,9 +84,6 @@ pub trait MarketCommonsPalletApi { /// Removes a market from the storage. fn remove_market(market_id: &Self::MarketId) -> DispatchResult; - /// Return the account id of a market's prize pool. - fn market_account(market_id: Self::MarketId) -> Self::AccountId; - // MarketPool /// Connects a pool identified by `pool_id` to a market identified by `market_id`. diff --git a/primitives/src/traits/swaps.rs b/primitives/src/traits/swaps.rs index b193dcfb0..e5c182701 100644 --- a/primitives/src/traits/swaps.rs +++ b/primitives/src/traits/swaps.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -16,16 +16,14 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::types::{ - Asset, MarketType, OutcomeReport, Pool, PoolId, ResultWithWeightInfo, ScoringRule, -}; +use crate::types::PoolId; use alloc::vec::Vec; use frame_support::dispatch::{DispatchError, Weight}; -use parity_scale_codec::MaxEncodedLen; pub trait Swaps { - type Balance: MaxEncodedLen; - type MarketId: MaxEncodedLen; + type Asset; + type Balance; + // TODO(#1216): Add weight type which implements `Into` and `From` /// Creates an initial active pool. /// @@ -41,16 +39,12 @@ pub trait Swaps { /// * `amount`: The amount of each asset added to the pool; **may** be `None` only if /// `scoring_rule` is `RikiddoSigmoidFeeMarketEma`. /// * `weights`: These are the denormalized weights (the raw weights). - #[allow(clippy::too_many_arguments)] fn create_pool( creator: AccountId, - assets: Vec>, - base_asset: Asset, - market_id: Self::MarketId, - scoring_rule: ScoringRule, - swap_fee: Option, - amount: Option, - weights: Option>, + assets: Vec, + swap_fee: Self::Balance, + amount: Self::Balance, + weights: Vec, ) -> Result; /// Close the specified pool. @@ -59,24 +53,6 @@ pub trait Swaps { /// Destroy CPMM pool, slash pool account assets and destroy pool shares of the liquidity providers. fn destroy_pool(pool_id: PoolId) -> Result; - /// Pool will be marked as `PoolStatus::Active`, if the market is currently in subsidy - /// state and all other conditions are met. Returns the result of the operation and - /// the total weight. If the result is false, not enough subsidy was gathered and the - /// state transition was aborted. - /// - /// # Arguments - /// - /// * `pool_id`: Unique pool identifier associated with the pool to be made active. - /// than the given value. - fn end_subsidy_phase(pool_id: PoolId) -> Result, DispatchError>; - - /// All supporters will receive their reserved funds back and the pool is destroyed. - /// - /// # Arguments - /// - /// * `pool_id`: Unique pool identifier associated with the pool to be destroyed. - fn destroy_pool_in_subsidy_phase(pool_id: PoolId) -> Result; - fn open_pool(pool_id: PoolId) -> Result; /// Pool - Exit with exact pool amount @@ -88,14 +64,14 @@ pub trait Swaps { /// /// * `who`: Liquidity Provider (LP). The account whose assets should be received. /// * `pool_id`: Unique pool identifier. - /// * `asset`: Asset leaving the pool. - /// * `asset_amount`: Asset amount that is leaving the pool. + /// * `asset`: Self::Asset leaving the pool. + /// * `asset_amount`: Self::Asset amount that is leaving the pool. /// * `max_pool_amount`: The calculated amount of assets for the pool must be equal or /// greater than the given value. fn pool_exit_with_exact_asset_amount( who: AccountId, pool_id: PoolId, - asset: Asset, + asset: Self::Asset, asset_amount: Self::Balance, max_pool_amount: Self::Balance, ) -> Result; @@ -109,37 +85,18 @@ pub trait Swaps { /// /// * `who`: Liquidity Provider (LP). The account whose assets should be received. /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. - /// * `asset_amount`: Asset amount that is entering the pool. + /// * `asset_in`: Self::Asset entering the pool. + /// * `asset_amount`: Self::Asset amount that is entering the pool. /// * `min_pool_amount`: The calculated amount for the pool must be equal or greater /// than the given value. fn pool_join_with_exact_asset_amount( who: AccountId, pool_id: PoolId, - asset_in: Asset, + asset_in: Self::Asset, asset_amount: Self::Balance, min_pool_amount: Self::Balance, ) -> Result; - /// Returns the pool instance of a corresponding `pool_id`. - fn pool(pool_id: PoolId) -> Result, DispatchError>; - - /// If the market is categorical, removes everything that is not ZTG or winning assets from the - /// selected pool. Additionally, it distributes the rewards to all pool share holders. - /// - /// # Arguments - /// - /// * `market_type`: Type of the market (e.g. categorical or scalar). - /// * `pool_id`: Unique pool identifier associated with the pool to be cleaned up. - /// * `outcome_report`: The resulting outcome. - /// * `winner_payout_account`: The account that exchanges winning assets against rewards. - fn clean_up_pool( - market_type: &MarketType, - pool_id: PoolId, - outcome_report: &OutcomeReport, - winner_payout_account: &AccountId, - ) -> Result; - /// Swap - Exact amount in /// /// Swaps a given `asset_amount_in` of the `asset_in/asset_out` pair to `pool_id`. @@ -148,9 +105,9 @@ pub trait Swaps { /// /// * `who`: The account whose assets should be transferred. /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. + /// * `asset_in`: Self::Asset entering the pool. /// * `asset_amount_in`: Amount that will be transferred from the provider to the pool. - /// * `asset_out`: Asset leaving the pool. + /// * `asset_out`: Self::Asset leaving the pool. /// * `min_asset_amount_out`: Minimum asset amount that can leave the pool. /// * `max_price`: Market price must be equal or less than the provided value. /// * `handle_fees`: Whether additional fees are handled or not (sets LP fee to 0) @@ -158,12 +115,11 @@ pub trait Swaps { fn swap_exact_amount_in( who: AccountId, pool_id: PoolId, - asset_in: Asset, + asset_in: Self::Asset, asset_amount_in: Self::Balance, - asset_out: Asset, + asset_out: Self::Asset, min_asset_amount_out: Option, max_price: Option, - handle_fees: bool, ) -> Result; /// Swap - Exact amount out @@ -174,9 +130,9 @@ pub trait Swaps { /// /// * `who`: The account whose assets should be transferred. /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. + /// * `asset_in`: Self::Asset entering the pool. /// * `max_amount_asset_in`: Maximum asset amount that can enter the pool. - /// * `asset_out`: Asset leaving the pool. + /// * `asset_out`: Self::Asset leaving the pool. /// * `asset_amount_out`: Amount that will be transferred from the pool to the provider. /// * `max_price`: Market price must be equal or less than the provided value. /// * `handle_fees`: Whether additional fees are handled or not (sets LP fee to 0) @@ -184,11 +140,10 @@ pub trait Swaps { fn swap_exact_amount_out( who: AccountId, pool_id: PoolId, - asset_in: Asset, + asset_in: Self::Asset, max_amount_asset_in: Option, - asset_out: Asset, + asset_out: Self::Asset, asset_amount_out: Self::Balance, max_price: Option, - handle_fees: bool, ) -> Result; } diff --git a/primitives/src/traits/zeitgeist_asset.rs b/primitives/src/traits/zeitgeist_asset.rs new file mode 100644 index 000000000..d14cc9ef9 --- /dev/null +++ b/primitives/src/traits/zeitgeist_asset.rs @@ -0,0 +1,37 @@ +// Copyright 2023-2024 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 . + +/// A trait for asset ID providers on Zeitgeist which have an ID for Balancer pool shares. +/// +/// # Generics +/// +/// - `P`: The pool ID type. +pub trait PoolSharesId

{ + /// Returns the ID of the pool shares asset of the pool specified by `pool_id`. + fn pool_shares_id(pool_id: P) -> Self; +} + +/// Helper trait that lets developers iterate over assets for testing and benchmarking. +/// +/// # Generics +/// +/// - `T`: The enumeration type. +#[cfg(feature = "runtime-benchmarks")] +pub trait ZeitgeistAssetEnumerator { + /// Maps `value` to an asset. The returned assets are pairwise distinct. + fn create_asset_id(t: T) -> Self; +} diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 27b61b513..417a71cc5 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -17,8 +17,8 @@ // along with Zeitgeist. If not, see . pub use crate::{ - asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, pool::*, - pool_status::PoolStatus, proxy_type::*, serde_wrapper::*, + asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, + serde_wrapper::*, }; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 1339f7a0e..557c420bf 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -420,7 +420,7 @@ force-debug = ["sp-debug-derive/force-debug"] authors = ["Zeitgeist PM "] edition = "2021" name = "battery-station-runtime" -version = "0.4.2" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs index d817d27cf..20d7350d3 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021 Centrifuge Foundation (centrifuge.io). // // This file is part of Zeitgeist. @@ -177,10 +177,11 @@ fn transfer_btc_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = btc(0); let initial_sovereign_balance = btc(100); let transfer_amount = btc(100); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_btc(None); - + treasury_initial_balance = Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -230,6 +231,13 @@ fn transfer_btc_sibling_to_zeitgeist() { Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) + ) }); } @@ -289,9 +297,12 @@ fn transfer_roc_from_relay_chain() { TestNet::reset(); let transfer_amount: Balance = roc(1); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_foreign_parent(None); + treasury_initial_balance = + Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()); }); RococoNet::execute_with(|| { @@ -311,6 +322,12 @@ fn transfer_roc_from_relay_chain() { let expected = transfer_amount - roc_fee(); let expected_adjusted = adjusted_balance(roc(1), expected); assert_eq!(Tokens::free_balance(FOREIGN_PARENT_ID, &BOB), expected_adjusted); + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(roc(1), roc_fee()) + ) }); } diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index f6a3792e9..de55e6b7c 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -54,15 +54,13 @@ use sp_version::NativeVersion; use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{constants::*, types::*}; use zrml_prediction_markets::Call::{ - buy_complete_set, create_cpmm_market_and_deploy_assets, create_market, - deploy_swap_pool_and_additional_liquidity, deploy_swap_pool_for_market, dispute, edit_market, - redeem_shares, report, sell_complete_set, + buy_complete_set, create_market, dispute, edit_market, redeem_shares, report, sell_complete_set, }; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; use zrml_swaps::Call::{ - pool_exit, pool_exit_with_exact_asset_amount, pool_exit_with_exact_pool_amount, pool_join, - pool_join_with_exact_asset_amount, pool_join_with_exact_pool_amount, swap_exact_amount_in, - swap_exact_amount_out, + force_pool_exit, pool_exit, pool_exit_with_exact_asset_amount, + pool_exit_with_exact_pool_amount, pool_join, pool_join_with_exact_asset_amount, + pool_join_with_exact_pool_amount, swap_exact_amount_in, swap_exact_amount_out, }; #[cfg(feature = "parachain")] use { @@ -102,7 +100,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 51, + spec_version: 52, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 26, @@ -125,18 +123,9 @@ impl Contains for ContractsCallfilter { RuntimeCall::PredictionMarkets(inner_call) => { match inner_call { buy_complete_set { .. } => true, - deploy_swap_pool_and_additional_liquidity { .. } => true, - deploy_swap_pool_for_market { .. } => true, dispute { .. } => true, - // Only allow CPMM markets using Authorized or Court dispute mechanism + // Only allow markets using Authorized or Court dispute mechanism create_market { - dispute_mechanism: - Some(MarketDisputeMechanism::Authorized) - | Some(MarketDisputeMechanism::Court), - scoring_rule: ScoringRule::CPMM, - .. - } => true, - create_cpmm_market_and_deploy_assets { dispute_mechanism: Some(MarketDisputeMechanism::Authorized) | Some(MarketDisputeMechanism::Court), @@ -146,7 +135,6 @@ impl Contains for ContractsCallfilter { dispute_mechanism: Some(MarketDisputeMechanism::Authorized) | Some(MarketDisputeMechanism::Court), - scoring_rule: ScoringRule::CPMM, .. } => true, redeem_shares { .. } => true, @@ -176,37 +164,27 @@ impl Contains for ContractsCallfilter { #[derive(scale_info::TypeInfo)] pub struct IsCallable; -// Currently disables Rikiddo. impl Contains for IsCallable { fn contains(call: &RuntimeCall) -> bool { #[allow(clippy::match_like_matches_macro)] match call { RuntimeCall::SimpleDisputes(_) => false, RuntimeCall::LiquidityMining(_) => false, - RuntimeCall::PredictionMarkets(inner_call) => { - match inner_call { - // Disable Rikiddo and SimpleDisputes markets - create_market { - scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. - } => false, - create_market { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - edit_market { - scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. - } => false, - create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - edit_market { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - _ => true, - } - } + RuntimeCall::PredictionMarkets(inner_call) => match inner_call { + create_market { + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), + .. + } => false, + edit_market { + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), + .. + } => false, + _ => true, + }, + RuntimeCall::Swaps(inner_call) => match inner_call { + force_pool_exit { .. } => true, + _ => false, + }, _ => true, } } diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs index adc09a7de..e472ce980 100644 --- a/runtime/battery-station/src/xcm_config/config.rs +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -29,7 +29,7 @@ use frame_support::{ traits::{ConstU8, Everything, Get, Nothing}, }; use orml_asset_registry::{AssetRegistryTrader, FixedRateAssetRegistryTrader}; -use orml_traits::{asset_registry::Inspect, location::AbsoluteReserveProvider, MultiCurrency}; +use orml_traits::{asset_registry::Inspect, location::AbsoluteReserveProvider}; use orml_xcm_support::{ DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset, }; @@ -150,13 +150,20 @@ pub type Trader = ( pub struct ToTreasury; impl TakeRevenue for ToTreasury { fn take_revenue(revenue: MultiAsset) { + use orml_traits::MultiCurrency; use xcm_executor::traits::Convert; - if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { + if let MultiAsset { id: Concrete(location), fun: Fungible(_amount) } = revenue { if let Ok(asset_id) = >::convert(location) { - let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + let adj_am = + AlignedFractionalMultiAssetTransactor::adjust_fractional_places(&revenue).fun; + + if let Fungible(amount) = adj_am { + let _ = + AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + } } } } diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index ca53c3aff..2c750dc9d 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -80,7 +80,7 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "common-runtime" -version = "0.4.2" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index a0a96428c..430ea89cc 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // Copyright 2019-2020 Parity Technologies (UK) Ltd. // @@ -55,14 +55,23 @@ macro_rules! decl_common_types { use orml_traits::MultiCurrency; use sp_runtime::{generic, DispatchError, DispatchResult, SaturatedConversion}; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; + use zrml_market_commons::migrations::MigrateScoringRuleAndMarketStatus; use zrml_neo_swaps::migration::MigrateToLiquidityTree; use zrml_orderbook::migrations::TranslateOrderStructure; + use zrml_prediction_markets::migrations::DrainDeprecatedStorage; + use zrml_swaps::migrations::MigratePools; pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; - type Migrations = (MigrateToLiquidityTree, TranslateOrderStructure); + type Migrations = ( + MigratePools, + DrainDeprecatedStorage, + MigrateScoringRuleAndMarketStatus, + TranslateOrderStructure, + MigrateToLiquidityTree, + ); pub type Executive = frame_executive::Executive< Runtime, @@ -365,7 +374,7 @@ macro_rules! create_runtime_with_additional_pallets { #[macro_export] macro_rules! impl_config_traits { - {} => { + () => { use common_runtime::weights; #[cfg(feature = "parachain")] use xcm_config::config::*; @@ -380,7 +389,8 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl cumulus_pallet_parachain_system::Config for Runtime { - type CheckAssociatedRelayNumber = cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; + type CheckAssociatedRelayNumber = + cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; type DmpMessageHandler = DmpQueue; type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); @@ -575,7 +585,10 @@ macro_rules! impl_config_traits { } pub struct CurrencyHooks(sp_std::marker::PhantomData); - impl orml_traits::currency::MutationHooks for CurrencyHooks { + impl + orml_traits::currency::MutationHooks + for CurrencyHooks + { type OnDust = orml_tokens::TransferDust; type OnKilledTokenAccount = (); type OnNewTokenAccount = (); @@ -671,7 +684,7 @@ macro_rules! impl_config_traits { impl pallet_contracts::Config for Runtime { type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type CallFilter = ContractsCallfilter; - type CallStack = [pallet_contracts::Frame::; 5]; + type CallStack = [pallet_contracts::Frame; 5]; type ChainExtension = (); type Currency = Balances; type DeletionQueueDepth = ContractsDeletionQueueDepth; @@ -721,7 +734,8 @@ macro_rules! impl_config_traits { /// Origin from which a proposal may be cancelled and its backers slashed. type CancelProposalOrigin = EnsureRootOrAllTechnicalCommittee; /// Origin for anyone able to veto proposals. - type VetoOrigin = pallet_collective::EnsureMember; + type VetoOrigin = + pallet_collective::EnsureMember; type CooloffPeriod = CooloffPeriod; type Slash = Treasury; type Scheduler = Scheduler; @@ -812,7 +826,10 @@ macro_rules! impl_config_traits { match self { ProxyType::Any => true, ProxyType::CancelProxy => { - matches!(c, RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. })) + matches!( + c, + RuntimeCall::Proxy(pallet_proxy::Call::reject_announcement { .. }) + ) } ProxyType::Governance => matches!( c, @@ -828,26 +845,28 @@ macro_rules! impl_config_traits { ProxyType::Staking => false, ProxyType::CreateEditMarket => matches!( c, - RuntimeCall::PredictionMarkets(zrml_prediction_markets::Call::create_market { .. }) - | RuntimeCall::PredictionMarkets( - zrml_prediction_markets::Call::edit_market { .. } - ) + RuntimeCall::PredictionMarkets( + zrml_prediction_markets::Call::create_market { .. } + ) | RuntimeCall::PredictionMarkets( + zrml_prediction_markets::Call::edit_market { .. } + ) ), ProxyType::ReportOutcome => matches!( c, - RuntimeCall::PredictionMarkets(zrml_prediction_markets::Call::report { .. }) + RuntimeCall::PredictionMarkets( + zrml_prediction_markets::Call::report { .. } + ) ), ProxyType::Dispute => matches!( c, - RuntimeCall::PredictionMarkets(zrml_prediction_markets::Call::dispute { .. }) + RuntimeCall::PredictionMarkets( + zrml_prediction_markets::Call::dispute { .. } + ) ), ProxyType::ProvideLiquidity => matches!( c, RuntimeCall::Swaps(zrml_swaps::Call::pool_join { .. }) | RuntimeCall::Swaps(zrml_swaps::Call::pool_exit { .. }) - | RuntimeCall::PredictionMarkets( - zrml_prediction_markets::Call::deploy_swap_pool_for_market { .. } - ) ), ProxyType::BuySellCompleteSets => matches!( c, @@ -877,12 +896,6 @@ macro_rules! impl_config_traits { | RuntimeCall::PredictionMarkets( zrml_prediction_markets::Call::sell_complete_set { .. } ) - | RuntimeCall::PredictionMarkets( - zrml_prediction_markets::Call::deploy_swap_pool_for_market { .. } - ) - | RuntimeCall::PredictionMarkets( - zrml_prediction_markets::Call::deploy_swap_pool_and_additional_liquidity { .. } - ) | RuntimeCall::Orderbook(zrml_orderbook::Call::place_order { .. }) | RuntimeCall::Orderbook(zrml_orderbook::Call::fill_order { .. }) | RuntimeCall::Orderbook(zrml_orderbook::Call::remove_order { .. }) @@ -1004,7 +1017,8 @@ macro_rules! impl_config_traits { type ProposalBondMaximum = ProposalBondMaximum; type RejectOrigin = EnsureRootOrTwoThirdsCouncil; type SpendFunds = Bounties; - type SpendOrigin = EnsureWithSuccess, AccountId, MaxTreasurySpend>; + type SpendOrigin = + EnsureWithSuccess, AccountId, MaxTreasurySpend>; type SpendPeriod = SpendPeriod; type WeightInfo = weights::pallet_treasury::WeightInfo; } @@ -1097,7 +1111,6 @@ macro_rules! impl_config_traits { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } @@ -1153,9 +1166,7 @@ macro_rules! impl_config_traits { type MaxGracePeriod = MaxGracePeriod; type MaxOracleDuration = MaxOracleDuration; type MinOracleDuration = MinOracleDuration; - type MaxSubsidyPeriod = MaxSubsidyPeriod; type MinCategories = MinCategories; - type MinSubsidyPeriod = MinSubsidyPeriod; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; type OracleBond = OracleBond; @@ -1171,7 +1182,6 @@ macro_rules! impl_config_traits { type AssetRegistry = AssetRegistry; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; - type Swaps = Swaps; type ValidityBond = ValidityBond; type WeightInfo = zrml_prediction_markets::weights::WeightInfo; } @@ -1221,15 +1231,9 @@ macro_rules! impl_config_traits { } impl zrml_swaps::Config for Runtime { + type Asset = Asset; type RuntimeEvent = RuntimeEvent; type ExitFee = ExitFee; - type FixedTypeU = FixedU128; - type FixedTypeS = FixedI128; - // LiquidityMining is currently unstable. - // NoopLiquidityMining will be applied only to mainnet once runtimes are separated. - type LiquidityMining = NoopLiquidityMining; - // type LiquidityMining = LiquidityMining; - type MarketCommons = MarketCommons; type MinAssets = MinAssets; type MaxAssets = MaxAssets; type MaxInRatio = MaxInRatio; @@ -1237,11 +1241,8 @@ macro_rules! impl_config_traits { type MaxSwapFee = MaxSwapFee; type MaxTotalWeight = MaxTotalWeight; type MaxWeight = MaxWeight; - type MinSubsidy = MinSubsidy; - type MinSubsidyPerAccount = MinSubsidyPerAccount; type MinWeight = MinWeight; type PalletId = SwapsPalletId; - type RikiddoSigmoidFeeMarketEma = RikiddoSigmoidFeeMarketEma; type AssetManager = AssetManager; type WeightInfo = zrml_swaps::weights::WeightInfo; } @@ -1285,7 +1286,7 @@ macro_rules! impl_config_traits { type PalletId = ParimutuelPalletId; type WeightInfo = zrml_parimutuel::weights::WeightInfo; } - } + }; } // Implement runtime apis @@ -1796,13 +1797,6 @@ macro_rules! create_runtime_api { fn pool_shares_id(pool_id: PoolId) -> Asset> { Asset::PoolShare(SerdeWrapper(pool_id)) } - - fn get_all_spot_prices( - pool_id: &PoolId, - with_fees: bool, - ) -> Result, Balance)>, DispatchError> { - Swaps::get_all_spot_prices(pool_id, with_fees) - } } #[cfg(feature = "try-runtime")] diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index e651cb961..6c8d91b0d 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -409,7 +409,7 @@ force-debug = ["sp-debug-derive/force-debug"] authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-runtime" -version = "0.4.2" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs index 5441f6517..2638f76f5 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021 Centrifuge Foundation (centrifuge.io). // // This file is part of Zeitgeist. @@ -177,10 +177,11 @@ fn transfer_btc_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = btc(0); let initial_sovereign_balance = btc(100); let transfer_amount = btc(100); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_btc(None); - + treasury_initial_balance = Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -230,6 +231,13 @@ fn transfer_btc_sibling_to_zeitgeist() { Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) + ) }); } @@ -292,10 +300,11 @@ fn transfer_eth_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = eth(0); let initial_sovereign_balance = eth(1); let transfer_amount = eth(1); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_eth(None); - + treasury_initial_balance = Tokens::free_balance(ETH_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(ETH_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -351,6 +360,13 @@ fn transfer_eth_sibling_to_zeitgeist() { Tokens::free_balance(ETH_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(ETH_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(eth(1), eth_fee()) + ) }); } diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 3a5a36e71..c57f3f4b0 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -90,7 +90,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 51, + spec_version: 52, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 26, @@ -102,7 +102,7 @@ pub type ContractsCallfilter = Nothing; #[derive(scale_info::TypeInfo)] pub struct IsCallable; -// Currently disables Court, Rikiddo and creation of markets using Court or SimpleDisputes +// Currently disables Rikiddo and creation of markets using SimpleDisputes // dispute mechanism. impl Contains for IsCallable { fn contains(runtime_call: &RuntimeCall) -> bool { @@ -119,15 +119,11 @@ impl Contains for IsCallable { set_code as set_code_contracts, }; use pallet_vesting::Call::force_vested_transfer; - - use zeitgeist_primitives::types::{ - MarketDisputeMechanism::{Court, SimpleDisputes}, - ScoringRule::RikiddoSigmoidFeeMarketEma, - }; + use zeitgeist_primitives::types::MarketDisputeMechanism::SimpleDisputes; use zrml_prediction_markets::Call::{ - admin_move_market_to_closed, admin_move_market_to_resolved, - create_cpmm_market_and_deploy_assets, create_market, edit_market, + admin_move_market_to_closed, admin_move_market_to_resolved, create_market, edit_market, }; + use zrml_swaps::Call::force_pool_exit; #[allow(clippy::match_like_matches_macro)] match runtime_call { @@ -161,29 +157,24 @@ impl Contains for IsCallable { }, // Membership is managed by the respective Membership instance RuntimeCall::Council(set_members { .. }) => false, - RuntimeCall::Court(_) => false, #[cfg(feature = "parachain")] RuntimeCall::DmpQueue(service_overweight { .. }) => false, - RuntimeCall::GlobalDisputes(_) => false, RuntimeCall::LiquidityMining(_) => false, RuntimeCall::PredictionMarkets(inner_call) => { match inner_call { - // Disable Rikiddo markets - create_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, - edit_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, - // Disable Court & SimpleDisputes dispute resolution mechanism - create_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, - edit_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, - create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(Court | SimpleDisputes), - .. - } => false, + // Disable SimpleDisputes dispute resolution mechanism + create_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, + edit_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, admin_move_market_to_closed { .. } => false, admin_move_market_to_resolved { .. } => false, _ => true, } } RuntimeCall::SimpleDisputes(_) => false, + RuntimeCall::Swaps(inner_call) => match inner_call { + force_pool_exit { .. } => true, + _ => false, + }, RuntimeCall::System(inner_call) => { match inner_call { // Some "waste" storage will never impact proper operation. diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs index 39ee8bbd7..f9424eaea 100644 --- a/runtime/zeitgeist/src/xcm_config/config.rs +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2023 Centrifuge Foundation (centrifuge.io). // // This file is part of Zeitgeist. @@ -31,7 +31,7 @@ use frame_support::{ traits::{ConstU8, Everything, Get, Nothing}, }; use orml_asset_registry::{AssetRegistryTrader, FixedRateAssetRegistryTrader}; -use orml_traits::{asset_registry::Inspect, location::AbsoluteReserveProvider, MultiCurrency}; +use orml_traits::{asset_registry::Inspect, location::AbsoluteReserveProvider}; use orml_xcm_support::{ DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset, }; @@ -152,13 +152,20 @@ pub type Trader = ( pub struct ToTreasury; impl TakeRevenue for ToTreasury { fn take_revenue(revenue: MultiAsset) { + use orml_traits::MultiCurrency; use xcm_executor::traits::Convert; - if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { + if let MultiAsset { id: Concrete(location), fun: Fungible(_amount) } = revenue { if let Ok(asset_id) = >::convert(location) { - let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + let adj_am = + AlignedFractionalMultiAssetTransactor::adjust_fractional_places(&revenue).fun; + + if let Fungible(amount) = adj_am { + let _ = + AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + } } } } diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 7543915c1..6d7a293b1 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -30,8 +30,8 @@ export ZEITGEIST_PALLETS=( zrml_authorized zrml_court zrml_global_disputes zrml_liquidity_mining zrml_neo_swaps \ zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ ) -export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-1000}" -export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-10}" +export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-20}" +export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-50}" export ZEITGEIST_WEIGHT_TEMPLATE="./misc/weight_template.hbs" export PROFILE="${PROFILE:-production}" diff --git a/scripts/update-copyright.sh b/scripts/update-copyright.sh index 258203422..865bacead 100755 --- a/scripts/update-copyright.sh +++ b/scripts/update-copyright.sh @@ -1,2 +1,2 @@ -RUST_FILES_CHANGED=$(git diff --name-only main | grep -E .*\.rs$) -check-license -w ${RUST_FILES_CHANGED} +RUST_FILES_CHANGED_NOT_DELETED=$(git diff --diff-filter=d --name-only main | grep -E '.*\.rs$') +check-license -w ${RUST_FILES_CHANGED_NOT_DELETED} diff --git a/zrml/authorized/Cargo.toml b/zrml/authorized/Cargo.toml index be6763a46..8dbfc6e58 100644 --- a/zrml/authorized/Cargo.toml +++ b/zrml/authorized/Cargo.toml @@ -39,4 +39,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-authorized" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 7d069e9e6..e491fc75e 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -392,7 +392,7 @@ where }, report: None, resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: MarketStatus::Disputed, bonds: MarketBonds::default(), early_close: None, diff --git a/zrml/authorized/src/migrations.rs b/zrml/authorized/src/migrations.rs index 13a374949..906e70698 100644 --- a/zrml/authorized/src/migrations.rs +++ b/zrml/authorized/src/migrations.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,59 +15,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -// 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 -// and previous migrations. -mod utility { - use crate::{BalanceOf, Config, MarketIdOf}; - use alloc::vec::Vec; - use frame_support::{ - migration::{get_storage_value, put_storage_value}, - storage::{storage_prefix, unhashed}, - traits::StorageVersion, - Blake2_128Concat, StorageHasher, - }; - use parity_scale_codec::Encode; - use zeitgeist_primitives::types::{Pool, PoolId}; - - #[allow(unused)] - const SWAPS: &[u8] = b"Swaps"; - #[allow(unused)] - const POOLS: &[u8] = b"Pools"; - #[allow(unused)] - fn storage_prefix_of_swaps_pallet() -> [u8; 32] { - storage_prefix(b"Swaps", b":__STORAGE_VERSION__:") - } - #[allow(unused)] - pub fn key_to_hash(key: K) -> Vec - where - H: StorageHasher, - K: Encode, - { - key.using_encoded(H::hash).as_ref().to_vec() - } - #[allow(unused)] - pub fn get_on_chain_storage_version_of_swaps_pallet() -> StorageVersion { - let key = storage_prefix_of_swaps_pallet(); - unhashed::get_or_default(&key) - } - #[allow(unused)] - pub fn put_storage_version_of_swaps_pallet(value: u16) { - let key = storage_prefix_of_swaps_pallet(); - unhashed::put(&key, &StorageVersion::new(value)); - } - #[allow(unused)] - pub fn get_pool(pool_id: PoolId) -> Option, MarketIdOf>> { - let hash = key_to_hash::(pool_id); - let pool_maybe = - get_storage_value::, MarketIdOf>>>(SWAPS, POOLS, &hash); - pool_maybe.unwrap_or(None) - } - #[allow(unused)] - pub fn set_pool(pool_id: PoolId, pool: Pool, MarketIdOf>) { - let hash = key_to_hash::(pool_id); - put_storage_value(SWAPS, POOLS, &hash, Some(pool)); - } -} diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 33a67f6c5..556f969c2 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -34,8 +34,7 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - AuthorizedPalletId, BlockHashCount, CorrectionPeriod, MaxReserves, MinimumPeriod, - PmPalletId, BASE, + AuthorizedPalletId, BlockHashCount, CorrectionPeriod, MaxReserves, MinimumPeriod, BASE, }, traits::DisputeResolutionApi, types::{ @@ -176,7 +175,6 @@ impl pallet_balances::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index 4095f712c..4e2c9eec6 100644 --- a/zrml/court/Cargo.toml +++ b/zrml/court/Cargo.toml @@ -47,4 +47,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-court" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 0abbc954e..39a810659 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -27,7 +27,7 @@ use crate::{ types::{CourtParticipantInfo, CourtPoolItem, CourtStatus, Draw, Vote}, AppealInfo, BalanceOf, Call, Config, CourtId, CourtPool, Courts, DelegatedStakesOf, MarketIdToCourtId, MarketOf, Pallet as Court, Pallet, Participants, RequestBlock, - SelectedDraws, VoteItem, + SelectedDraws, VoteItem, YearlyInflation, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::{account, benchmarks, whitelisted_caller}; @@ -77,7 +77,7 @@ where }), resolved_outcome: None, status: MarketStatus::Disputed, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, bonds: MarketBonds { creation: None, oracle: None, @@ -672,6 +672,7 @@ benchmarks! { >::set_block_number(T::InflationPeriod::get()); let now = >::block_number(); + YearlyInflation::::put(Perbill::from_percent(2)); }: { Court::::handle_inflation(now); } diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 1f51ae1ce..7407eac52 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -36,7 +36,7 @@ use zeitgeist_primitives::{ AggregationPeriod, AppealBond, AppealPeriod, BlockHashCount, BlocksPerYear, CourtPalletId, InflationPeriod, LockId, MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxDelegations, MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinJurorStake, MinimumPeriod, - PmPalletId, RequestInterval, VotePeriod, BASE, + RequestInterval, VotePeriod, BASE, }, traits::DisputeResolutionApi, types::{ @@ -205,7 +205,6 @@ impl pallet_balances::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index b32cb3f41..4592413e3 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -77,7 +77,7 @@ const DEFAULT_MARKET: MarketOf = Market { report: None, resolved_outcome: None, status: MarketStatus::Disputed, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, bonds: MarketBonds { creation: None, oracle: None, diff --git a/zrml/global-disputes/Cargo.toml b/zrml/global-disputes/Cargo.toml index b513c0707..8740e5b02 100644 --- a/zrml/global-disputes/Cargo.toml +++ b/zrml/global-disputes/Cargo.toml @@ -46,4 +46,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-global-disputes" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/global-disputes/src/mock.rs b/zrml/global-disputes/src/mock.rs index d678cd362..c49705d5f 100644 --- a/zrml/global-disputes/src/mock.rs +++ b/zrml/global-disputes/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -31,8 +31,8 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ AddOutcomePeriod, BlockHashCount, GdVotingPeriod, GlobalDisputeLockId, - GlobalDisputesPalletId, MaxReserves, MinOutcomeVoteAmount, MinimumPeriod, PmPalletId, - RemoveKeysLimit, VotingOutcomeFee, BASE, + GlobalDisputesPalletId, MaxReserves, MinOutcomeVoteAmount, MinimumPeriod, RemoveKeysLimit, + VotingOutcomeFee, BASE, }, traits::DisputeResolutionApi, types::{ @@ -173,7 +173,6 @@ impl pallet_timestamp::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/global-disputes/src/utils.rs b/zrml/global-disputes/src/utils.rs index 080feb8d6..e282ce2fd 100644 --- a/zrml/global-disputes/src/utils.rs +++ b/zrml/global-disputes/src/utils.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -54,7 +54,7 @@ where }, report: None, resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: zeitgeist_primitives::types::MarketStatus::Disputed, bonds: Default::default(), early_close: None, diff --git a/zrml/liquidity-mining/Cargo.toml b/zrml/liquidity-mining/Cargo.toml index 3d50e0075..ce3c51820 100644 --- a/zrml/liquidity-mining/Cargo.toml +++ b/zrml/liquidity-mining/Cargo.toml @@ -41,4 +41,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-liquidity-mining" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/liquidity-mining/src/mock.rs b/zrml/liquidity-mining/src/mock.rs index a85e574ce..b4fa05cbc 100644 --- a/zrml/liquidity-mining/src/mock.rs +++ b/zrml/liquidity-mining/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -30,7 +30,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, ExistentialDeposit, LiquidityMiningPalletId, MaxLocks, MaxReserves, - MinimumPeriod, PmPalletId, BASE, + MinimumPeriod, BASE, }, types::{ AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, @@ -107,7 +107,6 @@ impl pallet_balances::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/liquidity-mining/src/tests.rs b/zrml/liquidity-mining/src/tests.rs index 153f6c106..420f0dae9 100644 --- a/zrml/liquidity-mining/src/tests.rs +++ b/zrml/liquidity-mining/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -220,7 +220,7 @@ fn create_default_market(market_id: u128, period: Range) { report: None, resolved_outcome: None, status: MarketStatus::Closed, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, bonds: MarketBonds::default(), early_close: None, }, diff --git a/zrml/market-commons/Cargo.toml b/zrml/market-commons/Cargo.toml index 8321bc159..215042a49 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -12,6 +12,7 @@ env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } +test-case = { workspace = true } zeitgeist-primitives = { workspace = true, features = ["mock", "default"] } [features] @@ -32,4 +33,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-market-commons" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index acd8391b4..5fb809959 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -38,16 +38,15 @@ mod pallet { ensure, pallet_prelude::{StorageMap, StorageValue, ValueQuery}, storage::PrefixIterator, - traits::{Get, Hooks, StorageVersion, Time}, - Blake2_128Concat, PalletId, Parameter, + traits::{Hooks, StorageVersion, Time}, + Blake2_128Concat, Parameter, }; use parity_scale_codec::{FullCodec, MaxEncodedLen}; use sp_runtime::{ traits::{ - AccountIdConversion, AtLeast32Bit, AtLeast32BitUnsigned, MaybeSerializeDeserialize, - Member, Saturating, + AtLeast32Bit, AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member, Saturating, }, - DispatchError, SaturatedConversion, + DispatchError, }; use zeitgeist_primitives::{ math::checked_ops_res::CheckedAddRes, @@ -55,16 +54,14 @@ mod pallet { }; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); - - type BalanceOf = ::Balance; - type MarketOf = Market< - ::AccountId, - BalanceOf, - ::BlockNumber, - MomentOf, - Asset>, - >; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(10); + + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type AssetOf = Asset>; + pub(crate) type BalanceOf = ::Balance; + pub(crate) type BlockNumberOf = ::BlockNumber; + pub(crate) type MarketOf = + Market, BalanceOf, BlockNumberOf, MomentOf, AssetOf>; pub type MarketIdOf = ::MarketId; pub type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; @@ -91,11 +88,6 @@ mod pallet { + Member + Parameter; - // TODO(#837): Remove when on-chain arbitrage is removed! - /// The prefix used to calculate the prize pool accounts. - #[pallet::constant] - type PredictionMarketsPalletId: Get; - /// Time tracker type Timestamp: Time; } @@ -145,7 +137,7 @@ mod pallet { T: Config, ::Timestamp: Time, { - type AccountId = T::AccountId; + type AccountId = AccountIdOf; type BlockNumber = T::BlockNumber; type Balance = T::Balance; type MarketId = T::MarketId; @@ -197,13 +189,6 @@ mod pallet { Ok(()) } - // TODO(#837): Remove when on-chain arbitrage is removed! - #[inline] - fn market_account(market_id: Self::MarketId) -> Self::AccountId { - T::PredictionMarketsPalletId::get() - .into_sub_account_truncating(market_id.saturated_into::()) - } - // MarketPool fn insert_market_pool(market_id: Self::MarketId, pool_id: PoolId) -> DispatchResult { @@ -242,8 +227,10 @@ mod pallet { #[pallet::storage] pub type MarketCounter = StorageValue<_, T::MarketId, ValueQuery>; - /// Maps a market id to a related pool id. It is up to the caller to keep and sync valid + /// Maps a market ID to a related pool ID. It is up to the caller to keep and sync valid /// existent markets with valid existent pools. + /// + /// Beware! DEPRECATED as of v0.5.0. #[pallet::storage] pub type MarketPool = StorageMap<_, Blake2_128Concat, T::MarketId, PoolId>; } diff --git a/zrml/market-commons/src/migrations.rs b/zrml/market-commons/src/migrations.rs index 361b3949a..95890edb8 100644 --- a/zrml/market-commons/src/migrations.rs +++ b/zrml/market-commons/src/migrations.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -16,60 +16,477 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -// 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 -// and previous migrations. -mod utility { - use alloc::vec::Vec; - use frame_support::{ - storage::{storage_prefix, unhashed}, - traits::StorageVersion, - StorageHasher, - }; - use parity_scale_codec::Encode; +use crate::{ + AccountIdOf, AssetOf, BalanceOf, BlockNumberOf, Config, MarketIdOf, MomentOf, + Pallet as MarketCommons, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use frame_support::{ + log, + pallet_prelude::{Blake2_128Concat, StorageVersion, Weight}, + traits::{Get, OnRuntimeUpgrade}, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{Perbill, RuntimeDebug, Saturating}; +use zeitgeist_primitives::types::{ + Deadlines, EarlyClose, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, +}; - #[allow(unused)] - pub fn storage_prefix_of_market_common_pallet() -> [u8; 32] { - storage_prefix(b"MarketCommons", b":__STORAGE_VERSION__:") +#[cfg(feature = "try-runtime")] +use { + alloc::collections::BTreeMap, frame_support::migration::storage_key_iter, + zeitgeist_primitives::types::MarketId, +}; + +#[cfg(any(feature = "try-runtime", test))] +const MARKET_COMMONS: &[u8] = b"MarketCommons"; +#[cfg(any(feature = "try-runtime", test))] +const MARKETS: &[u8] = b"Markets"; + +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct OldMarket { + pub base_asset: A, + pub creator: AI, + pub creation: MarketCreation, + pub creator_fee: Perbill, + pub oracle: AI, + pub metadata: Vec, + pub market_type: MarketType, + pub period: MarketPeriod, + pub deadlines: Deadlines, + pub scoring_rule: OldScoringRule, + pub status: OldMarketStatus, + pub report: Option>, + pub resolved_outcome: Option, + pub dispute_mechanism: Option, + pub bonds: MarketBonds, + pub early_close: Option>, +} + +type OldMarketOf = + OldMarket, BalanceOf, BlockNumberOf, MomentOf, AssetOf>; + +#[derive(TypeInfo, Clone, Copy, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] +pub enum OldScoringRule { + CPMM, + RikiddoSigmoidFeeMarketEma, + Lmsr, + Orderbook, + Parimutuel, +} + +#[derive(Clone, Copy, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub enum OldMarketStatus { + Proposed, + Active, + Suspended, + Closed, + CollectingSubsidy, + InsufficientSubsidy, + Reported, + Disputed, + Resolved, +} + +const MARKET_COMMONS_REQUIRED_STORAGE_VERSION: u16 = 9; +const MARKET_COMMONS_NEXT_STORAGE_VERSION: u16 = 10; + +#[frame_support::storage_alias] +pub(crate) type Markets = + StorageMap, Blake2_128Concat, MarketIdOf, OldMarketOf>; + +pub struct MigrateScoringRuleAndMarketStatus(PhantomData); + +/// Deletes all Rikiddo markets from storage and migrates CPMM markets to LMSR. +impl OnRuntimeUpgrade for MigrateScoringRuleAndMarketStatus +where + T: Config, +{ + 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!( + "MigrateScoringRuleAndMarketStatus: market-commons version is {:?}, but {:?} is \ + required", + market_commons_version, + MARKET_COMMONS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("MigrateScoringRuleAndMarketStatus: Starting..."); + + let mut translated = 0u64; + crate::Markets::::translate::, _>(|_, old_market| { + // We proceed by deleting markets which use the Rikiddo scoring rule or have a status + // that was removed. + translated.saturating_inc(); + let scoring_rule = match old_market.scoring_rule { + OldScoringRule::RikiddoSigmoidFeeMarketEma => return None, + OldScoringRule::CPMM | OldScoringRule::Lmsr => ScoringRule::Lmsr, + OldScoringRule::Orderbook => ScoringRule::Orderbook, + OldScoringRule::Parimutuel => ScoringRule::Parimutuel, + }; + let status = match old_market.status { + OldMarketStatus::Proposed => MarketStatus::Proposed, + OldMarketStatus::Active => MarketStatus::Active, + OldMarketStatus::Suspended => return None, + OldMarketStatus::Closed => MarketStatus::Closed, + OldMarketStatus::CollectingSubsidy => return None, + OldMarketStatus::InsufficientSubsidy => return None, + OldMarketStatus::Reported => MarketStatus::Reported, + OldMarketStatus::Disputed => MarketStatus::Disputed, + OldMarketStatus::Resolved => MarketStatus::Resolved, + }; + let new_market = 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, + deadlines: old_market.deadlines, + scoring_rule, + status, + report: old_market.report, + resolved_outcome: old_market.resolved_outcome, + dispute_mechanism: old_market.dispute_mechanism, + bonds: old_market.bonds, + early_close: old_market.early_close, + }; + Some(new_market) + }); + log::info!("MigrateScoringRuleAndMarketStatus: 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!("MigrateScoringRuleAndMarketStatus: Done!"); + total_weight } - #[allow(unused)] - pub fn get_on_chain_storage_version_of_market_commons_pallet() -> StorageVersion { - let key = storage_prefix_of_market_common_pallet(); - unhashed::get_or_default(&key) + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let old_markets = storage_key_iter::, OldMarketOf, Blake2_128Concat>( + MARKET_COMMONS, + MARKETS, + ) + .collect::>(); + let markets = Markets::::iter_keys().count(); + let decodable_markets = Markets::::iter_values().count(); + if markets == decodable_markets { + log::info!("All {} markets could successfully be decoded.", markets); + } else { + log::error!( + "Can only decode {} of {} markets - others will be dropped.", + decodable_markets, + markets + ); + } + + Ok(old_markets.encode()) } - #[allow(unused)] - pub fn put_storage_version_of_market_commons_pallet(value: u16) { - let key = storage_prefix_of_market_common_pallet(); - unhashed::put(&key, &StorageVersion::new(value)); + #[cfg(feature = "try-runtime")] + fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { + let old_markets: BTreeMap> = + Decode::decode(&mut &previous_state[..]).unwrap(); + let old_market_count = old_markets.len(); + let new_market_count = Markets::::iter().count(); + assert_eq!(old_market_count, new_market_count); + log::info!( + "MigrateScoringRuleAndMarketStatus: Market counter post-upgrade is {}!", + new_market_count + ); + Ok(()) } +} - #[allow(unused)] - const SWAPS: &[u8] = b"Swaps"; - #[allow(unused)] - const POOLS: &[u8] = b"Pools"; - #[allow(unused)] - fn storage_prefix_of_swaps_pallet() -> [u8; 32] { - storage_prefix(b"Swaps", b":__STORAGE_VERSION__:") +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ExtBuilder, Runtime}; + use alloc::fmt::Debug; + use frame_support::{migration::put_storage_value, storage_root, StorageHasher}; + use sp_runtime::{Perbill, StateVersion}; + use test_case::test_case; + use zeitgeist_primitives::types::{Asset, Bond, MarketId}; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + MigrateScoringRuleAndMarketStatus::::on_runtime_upgrade(); + assert_eq!( + StorageVersion::get::>(), + MARKET_COMMONS_NEXT_STORAGE_VERSION + ); + }); } + + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Proposed), + Some((ScoringRule::Lmsr, MarketStatus::Proposed)) + )] + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Active), + Some((ScoringRule::Lmsr, MarketStatus::Active)) + )] + #[test_case((OldScoringRule::CPMM, OldMarketStatus::Suspended), None)] + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Closed), + Some((ScoringRule::Lmsr, MarketStatus::Closed)) + )] + #[test_case((OldScoringRule::CPMM, OldMarketStatus::CollectingSubsidy), None)] + #[test_case((OldScoringRule::CPMM, OldMarketStatus::InsufficientSubsidy), None)] + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Reported), + Some((ScoringRule::Lmsr, MarketStatus::Reported)) + )] + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Disputed), + Some((ScoringRule::Lmsr, MarketStatus::Disputed)) + )] + #[test_case( + (OldScoringRule::CPMM, OldMarketStatus::Resolved), + Some((ScoringRule::Lmsr, MarketStatus::Resolved)) + )] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Proposed), None)] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Active), None)] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Suspended), None)] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Closed), None)] + #[test_case( + (OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::CollectingSubsidy), + None + )] + #[test_case( + (OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::InsufficientSubsidy), + None + )] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Reported), None)] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Disputed), None)] + #[test_case((OldScoringRule::RikiddoSigmoidFeeMarketEma, OldMarketStatus::Resolved), None)] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Proposed), + Some((ScoringRule::Lmsr, MarketStatus::Proposed)) + )] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Active), + Some((ScoringRule::Lmsr, MarketStatus::Active)) + )] + #[test_case((OldScoringRule::Lmsr, OldMarketStatus::Suspended), None)] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Closed), + Some((ScoringRule::Lmsr, MarketStatus::Closed)) + )] + #[test_case((OldScoringRule::Lmsr, OldMarketStatus::CollectingSubsidy), None)] + #[test_case((OldScoringRule::Lmsr, OldMarketStatus::InsufficientSubsidy), None)] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Reported), + Some((ScoringRule::Lmsr, MarketStatus::Reported)) + )] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Disputed), + Some((ScoringRule::Lmsr, MarketStatus::Disputed)) + )] + #[test_case( + (OldScoringRule::Lmsr, OldMarketStatus::Resolved), + Some((ScoringRule::Lmsr, MarketStatus::Resolved)) + )] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Proposed), + Some((ScoringRule::Orderbook, MarketStatus::Proposed)) + )] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Active), + Some((ScoringRule::Orderbook, MarketStatus::Active)) + )] + #[test_case((OldScoringRule::Orderbook, OldMarketStatus::Suspended), None)] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Closed), + Some((ScoringRule::Orderbook, MarketStatus::Closed)) + )] + #[test_case((OldScoringRule::Orderbook, OldMarketStatus::CollectingSubsidy), None)] + #[test_case((OldScoringRule::Orderbook, OldMarketStatus::InsufficientSubsidy), None)] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Reported), + Some((ScoringRule::Orderbook, MarketStatus::Reported)) + )] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Disputed), + Some((ScoringRule::Orderbook, MarketStatus::Disputed)) + )] + #[test_case( + (OldScoringRule::Orderbook, OldMarketStatus::Resolved), + Some((ScoringRule::Orderbook, MarketStatus::Resolved)) + )] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Proposed), + Some((ScoringRule::Parimutuel, MarketStatus::Proposed)) + )] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Active), + Some((ScoringRule::Parimutuel, MarketStatus::Active)) + )] + #[test_case((OldScoringRule::Parimutuel, OldMarketStatus::Suspended), None)] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Closed), + Some((ScoringRule::Parimutuel, MarketStatus::Closed)) + )] + #[test_case((OldScoringRule::Parimutuel, OldMarketStatus::CollectingSubsidy), None)] + #[test_case((OldScoringRule::Parimutuel, OldMarketStatus::InsufficientSubsidy), None)] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Reported), + Some((ScoringRule::Parimutuel, MarketStatus::Reported)) + )] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Disputed), + Some((ScoringRule::Parimutuel, MarketStatus::Disputed)) + )] + #[test_case( + (OldScoringRule::Parimutuel, OldMarketStatus::Resolved), + Some((ScoringRule::Parimutuel, MarketStatus::Resolved)) + )] + fn on_runtime_upgrade_works_as_expected( + old_data: (OldScoringRule, OldMarketStatus), + new_data: Option<(ScoringRule, MarketStatus)>, + ) { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let base_asset = Asset::Ztg; + let creator = 0; + let creation = MarketCreation::Permissionless; + let creator_fee = Perbill::from_rational(1u32, 1_000u32); + let oracle = 2; + let metadata = vec![0x03; 50]; + let market_type = MarketType::Categorical(4); + let period = MarketPeriod::Block(5..6); + let deadlines = Deadlines { grace_period: 7, oracle_duration: 8, dispute_duration: 9 }; + let report = Some(Report { at: 13, by: 14, outcome: OutcomeReport::Categorical(10) }); + let resolved_outcome = None; + let dispute_mechanism = Some(MarketDisputeMechanism::Court); + let bonds = MarketBonds { + creation: Some(Bond::new(11, 12)), + oracle: None, + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + }; + let early_close = None; + let (old_scoring_rule, old_market_status) = old_data; + let old_market = OldMarket { + base_asset, + creator, + creation: creation.clone(), + creator_fee, + oracle, + metadata: metadata.clone(), + market_type: market_type.clone(), + period: period.clone(), + deadlines, + scoring_rule: old_scoring_rule, + status: old_market_status, + report: report.clone(), + resolved_outcome: resolved_outcome.clone(), + dispute_mechanism: dispute_mechanism.clone(), + bonds: bonds.clone(), + early_close: early_close.clone(), + }; + let opt_new_market = if let Some((new_scoring_rule, new_status)) = new_data { + Some(Market { + base_asset, + creator, + creation, + creator_fee, + oracle, + metadata, + market_type, + period, + deadlines, + scoring_rule: new_scoring_rule, + status: new_status, + report, + resolved_outcome, + dispute_mechanism, + bonds, + early_close, + }) + } else { + None + }; + // Don't set up chain to signal that storage is already up to date. + populate_test_data::>( + MARKET_COMMONS, + MARKETS, + vec![old_market], + ); + MigrateScoringRuleAndMarketStatus::::on_runtime_upgrade(); + + let actual = crate::Markets::::get(0); + assert_eq!(actual, opt_new_market); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(MARKET_COMMONS_NEXT_STORAGE_VERSION) + .put::>(); + let market = Market { + base_asset: Asset::>::ForeignAsset(0), + creator: 1, + creation: MarketCreation::Permissionless, + creator_fee: Perbill::from_rational(2u32, 3u32), + oracle: 4, + metadata: vec![0x05; 50], + market_type: MarketType::Categorical(999), + period: MarketPeriod::, MomentOf>::Block(6..7), + deadlines: Deadlines { grace_period: 7, oracle_duration: 8, dispute_duration: 9 }, + scoring_rule: ScoringRule::Parimutuel, + status: MarketStatus::Active, + report: Some(Report { at: 13, by: 14, outcome: OutcomeReport::Categorical(10) }), + resolved_outcome: None, + dispute_mechanism: Some(MarketDisputeMechanism::Court), + bonds: MarketBonds { + creation: Some(Bond::new(11, 12)), + oracle: None, + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + }, + early_close: None, + }; + crate::Markets::::insert(333, market); + let tmp = storage_root(StateVersion::V1); + MigrateScoringRuleAndMarketStatus::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + fn set_up_version() { + StorageVersion::new(MARKET_COMMONS_REQUIRED_STORAGE_VERSION) + .put::>(); + } + #[allow(unused)] - pub fn key_to_hash(key: K) -> Vec + fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) where H: StorageHasher, - K: Encode, + K: TryFrom + Encode, + V: Encode + Clone, + >::Error: Debug, { - key.using_encoded(H::hash).as_ref().to_vec() - } - #[allow(unused)] - pub fn get_on_chain_storage_version_of_swaps_pallet() -> StorageVersion { - let key = storage_prefix_of_swaps_pallet(); - unhashed::get_or_default(&key) - } - #[allow(unused)] - pub fn put_storage_version_of_swaps_pallet(value: u16) { - let key = storage_prefix_of_swaps_pallet(); - unhashed::put(&key, &StorageVersion::new(value)); + for (key, value) in data.iter().enumerate() { + let storage_hash = K::try_from(key).unwrap().using_encoded(H::hash).as_ref().to_vec(); + put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); + } } } diff --git a/zrml/market-commons/src/mock.rs b/zrml/market-commons/src/mock.rs index e0a89f972..bf692a1a8 100644 --- a/zrml/market-commons/src/mock.rs +++ b/zrml/market-commons/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -25,7 +25,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, }; use zeitgeist_primitives::{ - constants::mock::{BlockHashCount, MaxReserves, MinimumPeriod, PmPalletId}, + constants::mock::{BlockHashCount, MaxReserves, MinimumPeriod}, types::{ AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, @@ -49,7 +49,6 @@ construct_runtime!( impl crate::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index 6c7f0716e..b9817d223 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -46,7 +46,7 @@ const MARKET_DUMMY: Market"] edition = "2021" name = "zrml-neo-swaps" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/neo-swaps/docs/docs.pdf b/zrml/neo-swaps/docs/docs.pdf index 28af3262d..d0bfd0ca6 100644 Binary files a/zrml/neo-swaps/docs/docs.pdf and b/zrml/neo-swaps/docs/docs.pdf differ diff --git a/zrml/neo-swaps/docs/docs.tex b/zrml/neo-swaps/docs/docs.tex index 560e8639a..3b18abca4 100644 --- a/zrml/neo-swaps/docs/docs.tex +++ b/zrml/neo-swaps/docs/docs.tex @@ -217,7 +217,7 @@ \section{Numerical Issues} \item When buying, then the $\ln$ argument must satisfy $e^{x/b} - 1 + e^{-r_i/b} \geq c$ and $x \leq M$ (to avoid overflows). \end{itemize} -The last restriction may need some elaboration. We actually allow $e^{-r_i/b}$ to underflow to zero during calculation, but only provided that the remaining term $e^{x/b} - 1$ is large enough to avoid this to cause any damage. In fact, on the interval $[c, \infty)$, the logarithm has a derivative of $\leq c^{-1} = 10$, so all rounding errors in the argument of $\ln$ cause (only) ten times the error in the calculation of $\ln$. +The last restriction may need some elaboration. We actually allow $e^{-r_i/b}$ to underflow to zero during calculation, but only provided that the remaining term $e^{x/b} - 1$ is large enough to avoid this to cause any damage. In fact, on the interval $[c, \infty)$, the logarithm has derivative $\leq c^{-1} = 10$, so all rounding errors in the argument of $\ln$ cause (only) ten times the error in the calculation of $\ln$. How \emph{bad} are these restrictions? The restriction $x \leq M$ is completely irrelevant: Suppose Alice executes a trade of $y(x)$ units of outcome $i$ for $x = M$ dollars, the maximum allowed value. Let $q = 1 - e^{-r_i/b} \in (0, 1)$. Then \begin{align*} diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index dfde6ec4d..fde8b8c01 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -279,7 +279,7 @@ where mod benchmarks { use super::*; - /// FIXME Replace hardcoded variant with `{ MAX_ASSETS as u32 }` as soon as possible. + /// TODO(#1221): Replace hardcoded variant with `{ MAX_ASSETS as u32 }` as soon as possible. #[benchmark] fn buy(n: Linear<2, 128>) { let alice = whitelisted_caller(); diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index a87329ae5..ed0a3f30a 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -758,8 +758,8 @@ mod pallet { T::MultiCurrency::withdraw(asset, &pool.account_id, remaining)?; Ok(()) }; - // FIXME We will withdraw all remaining funds (the "buffer"). This is an ugly - // hack and frame_system should offer the option to whitelist accounts. + // TODO(#1220): We will withdraw all remaining funds (the "buffer"). This is an + // ugly hack and frame_system should offer the option to whitelist accounts. withdraw_remaining(&pool.collateral)?; // Clear left-over tokens. These naturally occur in the form of exit fees. for asset in pool.assets().iter() { @@ -875,8 +875,8 @@ mod pallet { liquidity_shares_manager: LiquidityTree::new(who.clone(), amount)?, swap_fee, }; - // FIXME Ensure that the existential deposit doesn't kill fees. This is an ugly hack and - // system should offer the option to whitelist accounts. + // TODO(#1220): Ensure that the existential deposit doesn't kill fees. This is an ugly + // hack and system should offer the option to whitelist accounts. T::MultiCurrency::transfer( pool.collateral, &who, @@ -927,7 +927,7 @@ mod pallet { Ok(FeeDistribution { remaining, swap_fees, external_fees }) } - // FIXME Carbon copy of a function in prediction-markets. To be removed later. + // TODO(#1218): Carbon copy of a function in prediction-markets. To be removed later. fn outcomes(market_id: MarketIdOf) -> Result>, DispatchError> { let market = T::MarketCommons::market(&market_id)?; Ok(match market.market_type { diff --git a/zrml/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs index 51e605c87..ac04b7ce7 100644 --- a/zrml/neo-swaps/src/migration.rs +++ b/zrml/neo-swaps/src/migration.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -55,11 +55,11 @@ pub struct MigrateToLiquidityTree(PhantomData); impl OnRuntimeUpgrade for MigrateToLiquidityTree { fn on_runtime_upgrade() -> Weight { let mut total_weight = T::DbWeight::get().reads(1); - let market_commons_version = StorageVersion::get::>(); - if market_commons_version != NEO_SWAPS_REQUIRED_STORAGE_VERSION { + let neo_swaps_version = StorageVersion::get::>(); + if neo_swaps_version != NEO_SWAPS_REQUIRED_STORAGE_VERSION { log::info!( - "MigrateToLiquidityTree: market-commons version is {:?}, but {:?} is required", - market_commons_version, + "MigrateToLiquidityTree: neo-swaps version is {:?}, but {:?} is required", + neo_swaps_version, NEO_SWAPS_REQUIRED_STORAGE_VERSION, ); return total_weight; diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index b361d473a..a6d519fd8 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -39,37 +39,33 @@ use sp_runtime::{ traits::{BlakeTwo256, Get, IdentityLookup, Zero}, DispatchResult, Percent, SaturatedConversion, }; -use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; #[cfg(feature = "parachain")] use zeitgeist_primitives::types::Asset; use zeitgeist_primitives::{ constants::mock::{ AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, - BalanceFractionalDecimals, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, - CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, - CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, - CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDeposits, ExitFee, - GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, - InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, MaxApprovals, MaxAssets, - MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, MaxDisputes, - MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxInRatio, MaxLiquidityTreeDepth, - MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxOwners, MaxRejectReasonLen, - MaxReserves, MaxSelectedDraws, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, - MaxYearlyInflation, MinAssets, MinCategories, MinDisputeDuration, MinJurorStake, - MinOracleDuration, MinOutcomeVoteAmount, MinSubsidy, MinSubsidyPeriod, MinWeight, - MinimumPeriod, NeoMaxSwapFee, NeoSwapsPalletId, OutcomeBond, OutcomeFactor, OutsiderBond, - PmPalletId, RemoveKeysLimit, RequestInterval, SimpleDisputesPalletId, SwapsPalletId, - TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, + BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, + CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, + CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CorrectionPeriod, CourtPalletId, + ExistentialDeposit, ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, + GlobalDisputeLockId, GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, + LockId, MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, + MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, + MaxLiquidityTreeDepth, MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOwners, + MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinCategories, + MinDisputeDuration, MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, + NeoMaxSwapFee, NeoSwapsPalletId, OutcomeBond, OutcomeFactor, OutsiderBond, PmPalletId, + RemoveKeysLimit, RequestInterval, SimpleDisputesPalletId, TreasuryPalletId, VotePeriod, + VotingOutcomeFee, CENT, }, math::fixed::FixedMul, traits::{DeployPoolApi, DistributeFees}, types::{ AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, }, }; use zrml_neo_swaps::BalanceOf; -use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; pub const ALICE: AccountIdTest = 0; #[allow(unused)] @@ -97,7 +93,6 @@ parameter_types! { pub storage NeoMinSwapFee: Balance = 0; } parameter_types! { - pub const MinSubsidyPerAccount: Balance = BASE; pub const AdvisoryBond: Balance = 0; pub const AdvisoryBondSlashPercentage: Percent = Percent::from_percent(10); pub const OracleBond: Balance = 0; @@ -175,10 +170,8 @@ construct_runtime!( MarketCommons: zrml_market_commons::{Pallet, Storage}, PredictionMarkets: zrml_prediction_markets::{Event, Pallet, Storage}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, - RikiddoSigmoidFeeMarketEma: zrml_rikiddo::{Pallet, Storage}, SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage}, GlobalDisputes: zrml_global_disputes::{Event, Pallet, Storage}, - Swaps: zrml_swaps::{Call, Event, Pallet}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, Tokens: orml_tokens::{Config, Event, Pallet, Storage}, @@ -200,21 +193,6 @@ impl crate::Config for Runtime { impl pallet_randomness_collective_flip::Config for Runtime {} -impl zrml_rikiddo::Config for Runtime { - type Timestamp = Timestamp; - type Balance = Balance; - type FixedTypeU = FixedU128; - type FixedTypeS = FixedI128; - type BalanceFractionalDecimals = BalanceFractionalDecimals; - type PoolId = PoolId; - type Rikiddo = RikiddoSigmoidMV< - Self::FixedTypeU, - Self::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - >; -} - impl zrml_prediction_markets::Config for Runtime { type AdvisoryBond = AdvisoryBond; type AdvisoryBondSlashPercentage = AdvisoryBondSlashPercentage; @@ -246,10 +224,8 @@ impl zrml_prediction_markets::Config for Runtime { type MaxDisputeDuration = MaxDisputeDuration; type MaxGracePeriod = MaxGracePeriod; type MaxOracleDuration = MaxOracleDuration; - type MaxSubsidyPeriod = MaxSubsidyPeriod; type MaxMarketLifetime = MaxMarketLifetime; type MinCategories = MinCategories; - type MinSubsidyPeriod = MinSubsidyPeriod; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; type OracleBond = OracleBond; @@ -261,7 +237,6 @@ impl zrml_prediction_markets::Config for Runtime { type AssetManager = AssetManager; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; - type Swaps = Swaps; type ValidityBond = ValidityBond; type WeightInfo = zrml_prediction_markets::weights::WeightInfo; } @@ -377,7 +352,6 @@ impl pallet_balances::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } @@ -417,29 +391,6 @@ impl zrml_global_disputes::Config for Runtime { type WeightInfo = zrml_global_disputes::weights::WeightInfo; } -impl zrml_swaps::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type ExitFee = ExitFee; - type FixedTypeU = ::FixedTypeU; - type FixedTypeS = ::FixedTypeS; - type LiquidityMining = LiquidityMining; - type MarketCommons = MarketCommons; - type MaxAssets = MaxAssets; - type MaxInRatio = MaxInRatio; - type MaxOutRatio = MaxOutRatio; - type MaxSwapFee = MaxSwapFee; - type MaxTotalWeight = MaxTotalWeight; - type MaxWeight = MaxWeight; - type MinAssets = MinAssets; - type MinSubsidy = MinSubsidy; - type MinSubsidyPerAccount = MinSubsidyPerAccount; - type MinWeight = MinWeight; - type PalletId = SwapsPalletId; - type RikiddoSigmoidFeeMarketEma = RikiddoSigmoidFeeMarketEma; - type AssetManager = AssetManager; - type WeightInfo = zrml_swaps::weights::WeightInfo; -} - impl pallet_treasury::Config for Runtime { type ApproveOrigin = EnsureSignedBy; type Burn = (); @@ -472,7 +423,7 @@ pub struct ExtBuilder { balances: Vec<(AccountIdTest, Balance)>, } -// FIXME Remove this in favor of adding whatever the account need in the individual tests. +// TODO(#1222): Remove this in favor of adding whatever the account need in the individual tests. #[allow(unused)] impl Default for ExtBuilder { fn default() -> Self { diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index a613b5fae..a6babc2df 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -148,10 +148,7 @@ fn buy_fails_on_market_not_found() { } #[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Suspended)] #[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::CollectingSubsidy)] -#[test_case(MarketStatus::InsufficientSubsidy)] #[test_case(MarketStatus::Reported)] #[test_case(MarketStatus::Disputed)] #[test_case(MarketStatus::Resolved)] diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index 17283f349..689dd76c0 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -187,10 +187,7 @@ fn deploy_pool_fails_on_market_not_found() { } #[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Suspended)] #[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::CollectingSubsidy)] -#[test_case(MarketStatus::InsufficientSubsidy)] #[test_case(MarketStatus::Reported)] #[test_case(MarketStatus::Disputed)] #[test_case(MarketStatus::Resolved)] @@ -240,11 +237,11 @@ fn deploy_pool_fails_on_duplicate_pool() { }); } -#[test] -fn deploy_pool_fails_on_invalid_trading_mechanism() { +#[test_case(ScoringRule::Orderbook)] +#[test_case(ScoringRule::Parimutuel)] +fn deploy_pool_fails_on_invalid_trading_mechanism(scoring_rule: ScoringRule) { ExtBuilder::default().build().execute_with(|| { - let market_id = - create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::CPMM); + let market_id = create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), scoring_rule); assert_noop!( NeoSwaps::deploy_pool( RuntimeOrigin::signed(ALICE), diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index cc0912264..76c940460 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -155,10 +155,7 @@ fn join_fails_on_market_not_found() { } #[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Suspended)] #[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::CollectingSubsidy)] -#[test_case(MarketStatus::InsufficientSubsidy)] #[test_case(MarketStatus::Reported)] #[test_case(MarketStatus::Disputed)] #[test_case(MarketStatus::Resolved)] diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index 6c0bd9fc7..ae6aa501f 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -142,10 +142,7 @@ fn sell_fails_on_market_not_found() { } #[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Suspended)] #[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::CollectingSubsidy)] -#[test_case(MarketStatus::InsufficientSubsidy)] #[test_case(MarketStatus::Reported)] #[test_case(MarketStatus::Disputed)] #[test_case(MarketStatus::Resolved)] diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index 598edc1b4..301dc89d0 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -118,6 +118,8 @@ where } } +// TODO(#1214): Replace BTreeMap with BoundedBTreeMap and remove the unnecessary `MaxEncodedLen` +// implementation. impl> MaxEncodedLen for Pool where T::AccountId: MaxEncodedLen, diff --git a/zrml/neo-swaps/src/types/solo_lp.rs b/zrml/neo-swaps/src/types/solo_lp.rs index 13f9c4cde..503a72bbe 100644 --- a/zrml/neo-swaps/src/types/solo_lp.rs +++ b/zrml/neo-swaps/src/types/solo_lp.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +// TODO(#1212): Remove in v0.5.1. + use crate::{traits::LiquiditySharesManager, BalanceOf, Config, Error}; use frame_support::ensure; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -24,7 +26,6 @@ use sp_runtime::{ DispatchError, DispatchResult, RuntimeDebug, }; -// Deprecated as of v0.5.0. TODO Remove in 0.5.1! #[derive(TypeInfo, MaxEncodedLen, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] #[scale_info(skip_type_params(T))] pub struct SoloLp { diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index 508240347..dd4963def 100644 --- a/zrml/orderbook/Cargo.toml +++ b/zrml/orderbook/Cargo.toml @@ -57,4 +57,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-orderbook" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/orderbook/src/mock.rs b/zrml/orderbook/src/mock.rs index 321b73584..1909806c6 100644 --- a/zrml/orderbook/src/mock.rs +++ b/zrml/orderbook/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -31,7 +31,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, - MaxReserves, MinimumPeriod, OrderbookPalletId, PmPalletId, BASE, + MaxReserves, MinimumPeriod, OrderbookPalletId, BASE, }, traits::DistributeFees, types::{ @@ -176,7 +176,6 @@ impl pallet_timestamp::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/orderbook/src/tests.rs b/zrml/orderbook/src/tests.rs index b40db571e..e5b46e42d 100644 --- a/zrml/orderbook/src/tests.rs +++ b/zrml/orderbook/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -31,8 +31,6 @@ use zrml_market_commons::{Error as MError, MarketCommonsPalletApi, Markets}; #[test_case(ScoringRule::Parimutuel; "Parimutuel")] #[test_case(ScoringRule::Lmsr; "LMSR")] -#[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; @@ -58,10 +56,7 @@ fn place_order_fails_with_wrong_scoring_rule(scoring_rule: ScoringRule) { } #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Suspended; "suspended")] #[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] #[test_case(MarketStatus::Reported; "reported")] #[test_case(MarketStatus::Disputed; "disputed")] #[test_case(MarketStatus::Resolved; "resolved")] @@ -90,10 +85,7 @@ fn place_order_fails_if_market_status_not_active(status: MarketStatus) { } #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Suspended; "suspended")] #[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] #[test_case(MarketStatus::Reported; "reported")] #[test_case(MarketStatus::Disputed; "disputed")] #[test_case(MarketStatus::Resolved; "resolved")] diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index 422361dbf..52e1b0d8a 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -45,4 +45,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-parimutuel" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index 4cb7da5d1..e4cfcfaac 100644 --- a/zrml/parimutuel/src/mock.rs +++ b/zrml/parimutuel/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -33,7 +33,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxReserves, MinBetSize, - MinimumPeriod, ParimutuelPalletId, PmPalletId, BASE, + MinimumPeriod, ParimutuelPalletId, BASE, }, traits::DistributeFees, types::{ @@ -147,7 +147,6 @@ impl pallet_balances::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/parimutuel/src/tests/buy.rs b/zrml/parimutuel/src/tests/buy.rs index 0df053bb8..1ca6b13b5 100644 --- a/zrml/parimutuel/src/tests/buy.rs +++ b/zrml/parimutuel/src/tests/buy.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -101,10 +101,8 @@ fn buy_fails_if_asset_not_parimutuel_share() { }); } -#[test_case(ScoringRule::CPMM; "cpmm")] #[test_case(ScoringRule::Orderbook; "orderbook")] #[test_case(ScoringRule::Lmsr; "lmsr")] -#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "rikiddo sigmoid fee market ema")] fn buy_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { ExtBuilder::default().build().execute_with(|| { let market_id = 0; @@ -124,10 +122,7 @@ fn buy_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { } #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Suspended; "suspended")] #[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] #[test_case(MarketStatus::Reported; "reported")] #[test_case(MarketStatus::Disputed; "disputed")] fn buy_fails_if_market_status_is_not_active(status: MarketStatus) { diff --git a/zrml/parimutuel/src/tests/claim.rs b/zrml/parimutuel/src/tests/claim.rs index 7c704a121..5103b97b9 100644 --- a/zrml/parimutuel/src/tests/claim.rs +++ b/zrml/parimutuel/src/tests/claim.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -174,10 +174,7 @@ fn claim_rewards_fails_if_market_type_is_scalar() { #[test_case(MarketStatus::Active; "active")] #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Suspended; "suspended")] #[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] #[test_case(MarketStatus::Reported; "reported")] #[test_case(MarketStatus::Disputed; "disputed")] fn claim_rewards_fails_if_not_resolved(status: MarketStatus) { @@ -194,10 +191,8 @@ fn claim_rewards_fails_if_not_resolved(status: MarketStatus) { }); } -#[test_case(ScoringRule::CPMM; "cpmm")] #[test_case(ScoringRule::Orderbook; "orderbook")] #[test_case(ScoringRule::Lmsr; "lmsr")] -#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "rikiddo sigmoid fee market ema")] fn claim_rewards_fails_if_scoring_rule_not_parimutuel(scoring_rule: ScoringRule) { ExtBuilder::default().build().execute_with(|| { let market_id = 0; diff --git a/zrml/parimutuel/src/tests/refund.rs b/zrml/parimutuel/src/tests/refund.rs index f346551c3..14e5c7a48 100644 --- a/zrml/parimutuel/src/tests/refund.rs +++ b/zrml/parimutuel/src/tests/refund.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -46,10 +46,7 @@ fn refund_fails_if_not_parimutuel_outcome() { #[test_case(MarketStatus::Active; "active")] #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Suspended; "suspended")] #[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] #[test_case(MarketStatus::Reported; "reported")] #[test_case(MarketStatus::Disputed; "disputed")] fn refund_fails_if_market_not_resolved(status: MarketStatus) { @@ -68,10 +65,8 @@ fn refund_fails_if_market_not_resolved(status: MarketStatus) { }); } -#[test_case(ScoringRule::CPMM; "cpmm")] #[test_case(ScoringRule::Orderbook; "orderbook")] #[test_case(ScoringRule::Lmsr; "lmsr")] -#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "rikiddo sigmoid fee market ema")] fn refund_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { ExtBuilder::default().build().execute_with(|| { let market_id = 0; diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 8cbb47232..3d1af3594 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -28,7 +28,6 @@ pallet-timestamp = { workspace = true, optional = true } pallet-treasury = { workspace = true, optional = true } sp-api = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } -substrate-fixed = { workspace = true, optional = true } xcm = { workspace = true, optional = true } zrml-prediction-markets-runtime-api = { workspace = true, optional = true } zrml-rikiddo = { workspace = true, optional = true } @@ -51,7 +50,6 @@ mock = [ "serde/default", "sp-api/default", "sp-io/default", - "substrate-fixed", "zeitgeist-primitives/mock", "zrml-prediction-markets-runtime-api/default", "zrml-rikiddo/default", @@ -97,4 +95,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-prediction-markets" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/prediction-markets/fuzz/pm_full_workflow.rs b/zrml/prediction-markets/fuzz/pm_full_workflow.rs index 38316d3a4..a0beb9583 100644 --- a/zrml/prediction-markets/fuzz/pm_full_workflow.rs +++ b/zrml/prediction-markets/fuzz/pm_full_workflow.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -57,7 +57,7 @@ fuzz_target!(|data: Data| { market_creation(data.create_scalar_market_creation), MarketType::Scalar(data.create_scalar_market_outcome_range), market_dispute_mechanism(data.create_scalar_market_dispute_mechanism), - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let _ = PredictionMarkets::on_initialize(2); diff --git a/zrml/prediction-markets/runtime-api/Cargo.toml b/zrml/prediction-markets/runtime-api/Cargo.toml index d4873b2a0..24211e93d 100644 --- a/zrml/prediction-markets/runtime-api/Cargo.toml +++ b/zrml/prediction-markets/runtime-api/Cargo.toml @@ -15,4 +15,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-prediction-markets-runtime-api" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 9b94d1314..f5cb54eb8 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -35,20 +35,19 @@ use frame_support::{ use frame_system::RawOrigin; use orml_traits::MultiCurrency; use sp_runtime::{ - traits::{One, SaturatedConversion, Saturating, Zero}, + traits::{SaturatedConversion, Saturating, Zero}, Perbill, }; use zeitgeist_primitives::{ constants::mock::{ - CloseEarlyProtectionTimeFramePeriod, CloseEarlyTimeFramePeriod, MaxSwapFee, MinWeight, - BASE, CENT, MILLISECS_PER_BLOCK, + CloseEarlyProtectionTimeFramePeriod, CloseEarlyTimeFramePeriod, BASE, CENT, + MILLISECS_PER_BLOCK, }, math::fixed::{BaseProvider, ZeitgeistBase}, - traits::{DisputeApi, Swaps}, + traits::DisputeApi, types::{ Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, MaxRuntimeUsize, MultiHash, OutcomeReport, PoolStatus, ScoringRule, - SubsidyUntil, + MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; use zrml_authorized::Pallet as AuthorizedPallet; @@ -110,7 +109,7 @@ fn create_market_common( scoring_rule, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - let market_id = >::latest_market_id()?; + let market_id = zrml_market_commons::Pallet::::latest_market_id()?; Ok((caller, market_id)) } @@ -125,13 +124,13 @@ fn create_close_and_report_market( let (caller, market_id) = create_market_common::( permission, options, - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(period), Some(MarketDisputeMechanism::Court), )?; Call::::admin_move_market_to_closed { market_id } .dispatch_bypass_filter(T::CloseOrigin::try_successful_origin().unwrap())?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end: u32 = match market.period { MarketPeriod::Timestamp(range) => range.end.saturated_into::(), _ => { @@ -153,7 +152,7 @@ fn setup_redeem_shares_common( let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, market_type.clone(), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; @@ -172,7 +171,7 @@ fn setup_redeem_shares_common( let close_origin = T::CloseOrigin::try_successful_origin().unwrap(); let resolve_origin = T::ResolveOrigin::try_successful_origin().unwrap(); Call::::admin_move_market_to_closed { market_id }.dispatch_bypass_filter(close_origin)?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end: u32 = match market.period { MarketPeriod::Timestamp(range) => range.end.saturated_into::(), _ => { @@ -189,35 +188,24 @@ fn setup_redeem_shares_common( Ok((caller, market_id)) } -fn setup_reported_categorical_market_with_pool( +fn setup_reported_categorical_market( categories: u32, report_outcome: OutcomeReport, -) -> Result<(T::AccountId, MarketIdOf), &'static str> { +) -> Result<(T::AccountId, MarketIdOf), &'static str> +where + T: Config + pallet_timestamp::Config, +{ let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(categories.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; - let max_swap_fee: BalanceOf = MaxSwapFee::get().saturated_into(); - let min_liquidity: BalanceOf = LIQUIDITY.saturated_into(); - Call::::buy_complete_set { market_id, amount: min_liquidity } - .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - let weight_len: usize = MaxRuntimeUsize::from(categories).into(); - let weights = vec![MinWeight::get(); weight_len]; - Pallet::::deploy_swap_pool_for_market( - RawOrigin::Signed(caller.clone()).into(), - market_id, - max_swap_fee, - min_liquidity, - weights, - )?; - Call::::admin_move_market_to_closed { market_id } .dispatch_bypass_filter(T::CloseOrigin::try_successful_origin().unwrap())?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end: u32 = match market.period { MarketPeriod::Timestamp(range) => range.end.saturated_into::(), _ => { @@ -249,7 +237,6 @@ benchmarks! { } admin_move_market_to_closed { - let o in 0..63; let c in 0..63; let range_start: MomentOf = 100_000u64.saturated_into(); @@ -257,18 +244,11 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), Some(MarketDisputeMechanism::Court), )?; - for i in 0..o { - MarketIdsPerOpenTimeFrame::::try_mutate( - Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), - ).unwrap(); - } - for i in 0..c { MarketIdsPerCloseTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_end), @@ -289,7 +269,7 @@ benchmarks! { OutcomeReport::Scalar(u128::MAX), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let report_at = market.report.unwrap().at; let resolves_at = report_at.saturating_add(market.deadlines.dispute_duration); @@ -316,16 +296,16 @@ benchmarks! { let r in 0..63; let categories = T::MaxCategories::get(); - let (_, market_id) = setup_reported_categorical_market_with_pool::( + let (_, market_id) = setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(0u16), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let report_at = market.report.unwrap().at; let resolves_at = report_at.saturating_add(market.deadlines.dispute_duration); @@ -357,12 +337,12 @@ benchmarks! { OutcomeReport::Scalar(u128::MAX), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let outcome = OutcomeReport::Scalar(0); let disputor = account("disputor", 1, 0); @@ -373,7 +353,7 @@ benchmarks! { ).unwrap(); Pallet::::dispute(RawOrigin::Signed(disputor).into(), market_id)?; - let now = >::block_number(); + let now = frame_system::Pallet::::block_number(); AuthorizedPallet::::authorize_market_outcome( T::AuthorizedDisputeResolutionOrigin::try_successful_origin().unwrap(), market_id.into(), @@ -405,12 +385,12 @@ benchmarks! { let categories = T::MaxCategories::get(); let (caller, market_id) = - setup_reported_categorical_market_with_pool::( + setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(2) )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -431,8 +411,8 @@ benchmarks! { OutcomeReport::Categorical(0), )?; - let market = >::market(&market_id)?; - let now = >::block_number(); + let market = zrml_market_commons::Pallet::::market(&market_id)?; + let now = frame_system::Pallet::::block_number(); let resolves_at = now.saturating_add(::CorrectionPeriod::get()); for i in 0..r { MarketIdsPerDisputeBlock::::try_mutate( @@ -457,7 +437,7 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; @@ -471,7 +451,7 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; @@ -486,7 +466,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; @@ -500,8 +480,8 @@ benchmarks! { let (caller, oracle, deadlines, metadata) = create_market_common_parameters::(true)?; - let range_end = T::MaxSubsidyPeriod::get(); - let period = MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..range_end); + let range_end = 200_000; + let period = MarketPeriod::Timestamp(100_000..range_end); for i in 0..m { MarketIdsPerCloseTimeFrame::::try_mutate( @@ -520,7 +500,7 @@ benchmarks! { MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr ) edit_market { @@ -528,7 +508,7 @@ benchmarks! { let market_type = MarketType::Categorical(T::MaxCategories::get()); let dispute_mechanism = Some(MarketDisputeMechanism::Court); - let scoring_rule = ScoringRule::CPMM; + let scoring_rule = ScoringRule::Lmsr; 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); @@ -547,7 +527,7 @@ benchmarks! { scoring_rule, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - let market_id = >::latest_market_id()?; + let market_id = zrml_market_commons::Pallet::::latest_market_id()?; let approve_origin = T::ApproveOrigin::try_successful_origin().unwrap(); let edit_reason = vec![0_u8; 1024]; @@ -578,105 +558,6 @@ benchmarks! { scoring_rule ) - deploy_swap_pool_for_market_future_pool { - let a in (T::MinCategories::get().into())..T::MaxCategories::get().into(); - let o in 0..63; - - let range_start: MomentOf = 100_000u64.saturated_into(); - let range_end: MomentOf = 1_000_000u64.saturated_into(); - let (caller, market_id) = create_market_common::( - MarketCreation::Permissionless, - MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, - Some(MarketPeriod::Timestamp(range_start..range_end)), - Some(MarketDisputeMechanism::Court), - )?; - - assert!( - Pallet::::calculate_time_frame_of_moment(>::now()) - < Pallet::::calculate_time_frame_of_moment(range_start) - ); - - for i in 0..o { - MarketIdsPerOpenTimeFrame::::try_mutate( - Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), - ).unwrap(); - } - - let prev_len = MarketIdsPerOpenTimeFrame::::get( - Pallet::::calculate_time_frame_of_moment(range_start)).len(); - - let max_swap_fee: BalanceOf:: = MaxSwapFee::get().saturated_into(); - let min_liquidity: BalanceOf:: = LIQUIDITY.saturated_into(); - Pallet::::buy_complete_set( - RawOrigin::Signed(caller.clone()).into(), - market_id, - min_liquidity, - )?; - - let weight_len: usize = MaxRuntimeUsize::from(a).into(); - let weights = vec![MinWeight::get(); weight_len]; - - let call = Call::::deploy_swap_pool_for_market { - market_id, - swap_fee: max_swap_fee, - amount: min_liquidity, - weights, - }; - }: { - call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; - } verify { - let current_len = MarketIdsPerOpenTimeFrame::::get( - Pallet::::calculate_time_frame_of_moment(range_start), - ) - .len(); - assert_eq!(current_len, prev_len + 1); - } - - deploy_swap_pool_for_market_open_pool { - let a in (T::MinCategories::get().into())..T::MaxCategories::get().into(); - - // We need to ensure, that period range start is now, - // because we would like to open the pool now - let range_start: MomentOf = >::now(); - let range_end: MomentOf = 1_000_000u64.saturated_into(); - let (caller, market_id) = create_market_common::( - MarketCreation::Permissionless, - MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, - Some(MarketPeriod::Timestamp(range_start..range_end)), - Some(MarketDisputeMechanism::Court), - )?; - - let market = >::market(&market_id.saturated_into())?; - - let max_swap_fee: BalanceOf:: = MaxSwapFee::get().saturated_into(); - let min_liquidity: BalanceOf:: = LIQUIDITY.saturated_into(); - Pallet::::buy_complete_set( - RawOrigin::Signed(caller.clone()).into(), - market_id, - min_liquidity, - )?; - - let weight_len: usize = MaxRuntimeUsize::from(a).into(); - let weights = vec![MinWeight::get(); weight_len]; - - let call = Call::::deploy_swap_pool_for_market { - market_id, - swap_fee: max_swap_fee, - amount: min_liquidity, - weights, - }; - }: { - call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())?; - } verify { - let market_pool_id = - >::market_pool(&market_id.saturated_into())?; - let pool = T::Swaps::pool(market_pool_id)?; - assert_eq!(pool.pool_status, PoolStatus::Active); - } - start_global_dispute { let m in 1..CacheSize::get(); let n in 1..CacheSize::get(); @@ -689,7 +570,7 @@ benchmarks! { OutcomeReport::Scalar(u128::MAX), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Court); Ok(()) })?; @@ -701,18 +582,18 @@ benchmarks! { market_ids_1.try_push(i.saturated_into()).unwrap(); } - >::on_initialize(1u32.into()); - >::set_block_number(1u32.into()); + zrml_court::Pallet::::on_initialize(1u32.into()); + frame_system::Pallet::::set_block_number(1u32.into()); let min_amount = ::MinJurorStake::get(); - for i in 0..>::necessary_draws_weight(0usize) { + for i in 0..zrml_court::Pallet::::necessary_draws_weight(0usize) { let juror: T::AccountId = account("Jurori", i.try_into().unwrap(), 0); ::AssetManager::deposit( Asset::Ztg, &juror, (u128::MAX / 2).saturated_into(), ).unwrap(); - >::join_court( + zrml_court::Pallet::::join_court( RawOrigin::Signed(juror.clone()).into(), min_amount + i.saturated_into(), )?; @@ -729,7 +610,7 @@ benchmarks! { } .dispatch_bypass_filter(RawOrigin::Signed(disputor).into())?; - let market = >::market(&market_id.saturated_into()).unwrap(); + let market = zrml_market_commons::Pallet::::market(&market_id.saturated_into()).unwrap(); let appeal_end = T::Court::get_auto_resolve(&market_id, &market).result.unwrap(); let mut market_ids_2: BoundedVec, CacheSize> = BoundedVec::try_from( vec![market_id], @@ -739,9 +620,9 @@ benchmarks! { } MarketIdsPerDisputeBlock::::insert(appeal_end, market_ids_2); - >::set_block_number(appeal_end - 1u64.saturated_into::()); + frame_system::Pallet::::set_block_number(appeal_end - 1u64.saturated_into::()); - let now = >::block_number(); + let now = frame_system::Pallet::::block_number(); let add_outcome_end = now + ::GlobalDisputes::get_add_outcome_period(); @@ -763,12 +644,12 @@ benchmarks! { report_outcome, )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let call = Call::::dispute { market_id }; }: { @@ -779,39 +660,39 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, - Some(MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..T::MaxSubsidyPeriod::get())), + ScoringRule::Lmsr, + Some(MarketPeriod::Timestamp(100_000..200_000)), Some(MarketDisputeMechanism::Court), )?; - let market = >::market(&market_id.saturated_into())?; + let market = zrml_market_commons::Pallet::::market(&market_id.saturated_into())?; }: { Pallet::::handle_expired_advised_market(&market_id, market)? } internal_resolve_categorical_reported { let categories = T::MaxCategories::get(); - let (_, market_id) = setup_reported_categorical_market_with_pool::( + let (_, market_id) = setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(1u16), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; }: { Pallet::::on_resolution(&market_id, &market)?; } verify { - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; assert_eq!(market.status, MarketStatus::Resolved); } internal_resolve_categorical_disputed { let categories = T::MaxCategories::get(); let (caller, market_id) = - setup_reported_categorical_market_with_pool::( + setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(1u16) )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -826,11 +707,11 @@ benchmarks! { market_id.into(), OutcomeReport::Categorical(0), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; }: { Pallet::::on_resolution(&market_id, &market)?; } verify { - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; assert_eq!(market.status, MarketStatus::Resolved); } @@ -840,11 +721,11 @@ benchmarks! { MarketType::Scalar(0u128..=u128::MAX), OutcomeReport::Scalar(u128::MAX), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; }: { Pallet::::on_resolution(&market_id, &market)?; } verify { - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; assert_eq!(market.status, MarketStatus::Resolved); } @@ -854,11 +735,11 @@ benchmarks! { MarketType::Scalar(0u128..=u128::MAX), OutcomeReport::Scalar(u128::MAX), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; Pallet::::dispute( RawOrigin::Signed(caller).into(), market_id, @@ -869,11 +750,11 @@ benchmarks! { market_id.into(), OutcomeReport::Scalar(0), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; }: { Pallet::::on_resolution(&market_id, &market)?; } verify { - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; assert_eq!(market.status, MarketStatus::Resolved); } @@ -882,25 +763,6 @@ benchmarks! { let now = 2u64.saturated_into::(); }: { Pallet::::on_initialize(now) } - // Benchmark iteration and market validity check without ending subsidy / discarding market. - process_subsidy_collecting_markets_raw { - // Number of markets collecting subsidy. - let a in 0..10; - - let market_info = SubsidyUntil { - market_id: MarketIdOf::::zero(), - period: MarketPeriod::Block(T::BlockNumber::one()..T::BlockNumber::one()) - }; - - let markets = BoundedVec::try_from(vec![market_info; a as usize]).unwrap(); - >::put(markets); - }: { - Pallet::::process_subsidy_collecting_markets( - T::BlockNumber::zero(), - MomentOf::::zero() - ); - } - redeem_shares_categorical { let (caller, market_id) = setup_redeem_shares_common::( MarketType::Categorical(T::MaxCategories::get()) @@ -915,7 +777,6 @@ benchmarks! { reject_market { let c in 0..63; - let o in 0..63; let r in 0..::MaxRejectReasonLen::get(); let range_start: MomentOf = 100_000u64.saturated_into(); @@ -923,18 +784,11 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), Some(MarketDisputeMechanism::Court), )?; - for i in 0..o { - MarketIdsPerOpenTimeFrame::::try_mutate( - Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), - ).unwrap(); - } - for i in 0..c { MarketIdsPerCloseTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_end), @@ -951,17 +805,17 @@ benchmarks! { let m in 0..63; // ensure range.start is now to get the heaviest path - let range_start: MomentOf = >::now(); + let range_start: MomentOf = zrml_market_commons::Pallet::::now(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), Some(MarketDisputeMechanism::Court), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { // ensure sender is oracle to succeed extrinsic call market.oracle = caller.clone(); Ok(()) @@ -970,7 +824,7 @@ benchmarks! { let outcome = OutcomeReport::Categorical(0); let close_origin = T::CloseOrigin::try_successful_origin().unwrap(); Pallet::::admin_move_market_to_closed(close_origin, market_id)?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end : u32 = match market.period { MarketPeriod::Timestamp(range) => { range.end.saturated_into::() @@ -998,7 +852,7 @@ benchmarks! { report_trusted_market { pallet_timestamp::Pallet::::set_timestamp(0u32.into()); - let start: MomentOf = >::now(); + let start: MomentOf = zrml_market_commons::Pallet::::now(); let end: MomentOf = 1_000_000u64.saturated_into(); let (caller, oracle, _, metadata) = create_market_common_parameters::(false)?; Call::::create_market { @@ -1015,10 +869,10 @@ benchmarks! { creation: MarketCreation::Permissionless, market_type: MarketType::Categorical(3), dispute_mechanism: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - let market_id = >::latest_market_id()?; + let market_id = zrml_market_commons::Pallet::::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); @@ -1032,7 +886,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, Some(MarketDisputeMechanism::Court), )?; @@ -1044,26 +898,9 @@ benchmarks! { )?; }: _(RawOrigin::Signed(caller), market_id, amount) - start_subsidy { - // Total event outcome assets. - let a in (T::MinCategories::get().into())..T::MaxCategories::get().into(); - - // Create advised rikiddo market with a assets (advised -> start_subsidy not invoked). - let (caller, market_id) = create_market_common::( - MarketCreation::Advised, - MarketType::Categorical(a.saturated_into()), - ScoringRule::RikiddoSigmoidFeeMarketEma, - Some(MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..T::MaxSubsidyPeriod::get())), - Some(MarketDisputeMechanism::Court), - )?; - let mut market_clone = None; - >::mutate_market(&market_id, |market| { - market.status = MarketStatus::CollectingSubsidy; - market_clone = Some(market.clone()); - Ok(()) - })?; - }: { Pallet::::start_subsidy(&market_clone.unwrap(), market_id)? } - + // Benchmarks `market_status_manager` for any type of cache by using `MarketIdsPerClose*` as + // sample. If `MarketIdsPerClose*` ever gets removed and we want to keep using + // `market_status_manager`, we need to benchmark it with a different cache. market_status_manager { let b in 1..31; let f in 1..31; @@ -1075,7 +912,7 @@ benchmarks! { create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Block(start_block..end_block)), Some(MarketDisputeMechanism::Court), ).unwrap(); @@ -1087,44 +924,42 @@ benchmarks! { create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), Some(MarketDisputeMechanism::Court), ).unwrap(); } let block_number: T::BlockNumber = Zero::zero(); - let last_time_frame: TimeFrame = Zero::zero(); for i in 1..=b { - >::try_mutate(block_number, |ids| { + MarketIdsPerCloseBlock::::try_mutate(block_number, |ids| { ids.try_push(i.into()) }).unwrap(); } + let last_time_frame: TimeFrame = Zero::zero(); let last_offset: TimeFrame = last_time_frame + 1.saturated_into::(); //* quadratic complexity should not be allowed in substrate blockchains //* assume at first that the last time frame is one block before the current time frame let t = 0; let current_time_frame: TimeFrame = last_offset + t.saturated_into::(); for i in 1..=f { - >::try_mutate(current_time_frame, |ids| { - // + 31 to not conflict with the markets of MarketIdsPerOpenBlock + MarketIdsPerCloseTimeFrame::::try_mutate(current_time_frame, |ids| { + // + 31 to not conflict with the markets of MarketIdsPerCloseBlock ids.try_push((i + 31).into()) }).unwrap(); } }: { Pallet::::market_status_manager::< _, - MarketIdsPerOpenBlock, - MarketIdsPerOpenTimeFrame, + MarketIdsPerCloseBlock, + MarketIdsPerCloseTimeFrame, >( block_number, last_time_frame, current_time_frame, - |market_id, market| { - // noop, because weight is already measured somewhere else - Ok(()) - }, + // noop, because weight is already measured somewhere else + |market_id, market| Ok(()), ) .unwrap(); } @@ -1140,12 +975,12 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), Some(MarketDisputeMechanism::Court), )?; // ensure market is reported - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.status = MarketStatus::Reported; Ok(()) })?; @@ -1176,15 +1011,6 @@ benchmarks! { ).unwrap(); } - process_subsidy_collecting_markets_dummy { - let current_block: T::BlockNumber = 0u64.saturated_into::(); - let current_time: MomentOf = 0u64.saturated_into::>(); - let markets = BoundedVec::try_from(Vec::new()).unwrap(); - >::put(markets); - }: { - let _ = >::process_subsidy_collecting_markets(current_block, current_time); - } - schedule_early_close_as_authority { let o in 0..63; let n in 0..63; @@ -1194,7 +1020,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1206,7 +1032,7 @@ benchmarks! { ).unwrap(); } - let now_time = >::now(); + let now_time = zrml_market_commons::Pallet::::now(); let new_range_end: MomentOf = now_time + CloseEarlyProtectionTimeFramePeriod::get(); for i in 0..n { @@ -1229,7 +1055,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1241,7 +1067,7 @@ benchmarks! { ).unwrap(); } - let now_time = >::now(); + let now_time = zrml_market_commons::Pallet::::now(); let new_range_end: MomentOf = now_time + CloseEarlyProtectionTimeFramePeriod::get(); for i in 0..n { @@ -1274,12 +1100,12 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let market_creator = market.creator.clone(); for i in 0..o { @@ -1289,7 +1115,7 @@ benchmarks! { ).unwrap(); } - let now_time = >::now(); + let now_time = zrml_market_commons::Pallet::::now(); let new_range_end: MomentOf = now_time + CloseEarlyTimeFramePeriod::get(); for i in 0..n { @@ -1312,7 +1138,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1324,7 +1150,7 @@ benchmarks! { market_id, )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let new_range_end = match market.period { MarketPeriod::Timestamp(range) => { range.end @@ -1363,7 +1189,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1376,7 +1202,7 @@ benchmarks! { market_id, )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let new_range_end = match market.period { MarketPeriod::Timestamp(range) => { range.end @@ -1411,7 +1237,7 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1434,7 +1260,6 @@ benchmarks! { }: { call.dispatch_bypass_filter(close_origin)? } close_trusted_market { - let o in 0..63; let c in 0..63; let range_start: MomentOf = 100_000u64.saturated_into(); @@ -1442,18 +1267,11 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), None, )?; - for i in 0..o { - MarketIdsPerOpenTimeFrame::::try_mutate( - Pallet::::calculate_time_frame_of_moment(range_start), - |ids| ids.try_push(i.into()), - ).unwrap(); - } - for i in 0..c { MarketIdsPerCloseTimeFrame::::try_mutate( Pallet::::calculate_time_frame_of_moment(range_end), @@ -1505,6 +1323,39 @@ benchmarks! { CENT.saturated_into() ) + manually_close_market { + let o in 1..63; + + let range_start: MomentOf = 100_000u64.saturated_into(); + let range_end: MomentOf = 1_000_000u64.saturated_into(); + + let (caller, market_id) = create_market_common::( + MarketCreation::Permissionless, + MarketType::Categorical(T::MaxCategories::get()), + ScoringRule::Lmsr, + Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), + )?; + + let now = 1_500_000u32; + assert!(range_end < now as u64); + + let range_end_time_frame = Pallet::::calculate_time_frame_of_moment(range_end); + let range_end_time_frame = Pallet::::calculate_time_frame_of_moment(range_end); + for i in 1..o { + MarketIdsPerCloseTimeFrame::::try_mutate(range_end_time_frame, |ids| { + ids.try_push((i + 1).into()) + }).unwrap(); + } + + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { + market.status = MarketStatus::Active; + Ok(()) + })?; + + pallet_timestamp::Pallet::::set_timestamp(now.into()); + }: manually_close_market(RawOrigin::Signed(caller), market_id) + impl_benchmark_test_suite!( PredictionMarket, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index d45196e49..b1144dd33 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -50,6 +50,8 @@ mod pallet { transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use sp_runtime::traits::AccountIdConversion; + use zeitgeist_primitives::types::SubsidyUntil; #[cfg(feature = "parachain")] use {orml_traits::asset_registry::Inspect, zeitgeist_primitives::types::CustomMetadata}; @@ -64,13 +66,13 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, Swaps, ZeitgeistAssetManager, + DisputeResolutionApi, ZeitgeistAssetManager, }, types::{ Asset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, MultiHash, OldMarketDispute, OutcomeReport, Report, ResultWithWeightInfo, - ScalarPosition, ScoringRule, SubsidyUntil, + MarketType, MultiHash, OutcomeReport, Report, ResultWithWeightInfo, ScalarPosition, + ScoringRule, }, }; use zrml_global_disputes::{types::InitialItem, GlobalDisputesPalletApi}; @@ -80,6 +82,11 @@ mod pallet { /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); const LOG_TARGET: &str = "runtime::zrml-prediction-markets"; + /// The maximum number of blocks between the [`LastTimeFrame`] + /// and the current timestamp in block number allowed to recover + /// the automatic market openings and closings from a chain stall. + /// Currently 10 blocks is 2 minutes (assuming block time is 12 seconds). + pub(crate) const MAX_RECOVERY_TIME_FRAMES: TimeFrame = 10; pub(crate) type BalanceOf = ::Balance; pub(crate) type AccountIdOf = ::AccountId; @@ -332,30 +339,20 @@ mod pallet { // Within the same block, operations that interact with the activeness of the same // market will behave differently before and after this call. #[pallet::call_index(1)] - #[pallet::weight(( - T::WeightInfo::admin_move_market_to_closed( - CacheSize::get(), CacheSize::get()), Pays::No - ) - )] + #[pallet::weight((T::WeightInfo::admin_move_market_to_closed(CacheSize::get()), Pays::No))] #[transactional] pub fn admin_move_market_to_closed( origin: OriginFor, #[pallet::compact] market_id: MarketIdOf, ) -> DispatchResultWithPostInfo { - // TODO(#638): Handle Rikiddo markets! T::CloseOrigin::ensure_origin(origin)?; let market = >::market(&market_id)?; Self::ensure_market_is_active(&market)?; - let open_ids_len = Self::clear_auto_open(&market_id)?; let close_ids_len = Self::clear_auto_close(&market_id)?; Self::close_market(&market_id)?; Self::set_market_end(&market_id)?; // The CloseOrigin should not pay fees for providing this service - Ok(( - Some(T::WeightInfo::admin_move_market_to_closed(open_ids_len, close_ids_len)), - Pays::No, - ) - .into()) + Ok((Some(T::WeightInfo::admin_move_market_to_closed(close_ids_len)), Pays::No).into()) } /// Allows the `ResolveOrigin` to immediately move a reported or disputed @@ -433,10 +430,8 @@ mod pallet { origin: OriginFor, #[pallet::compact] market_id: MarketIdOf, ) -> DispatchResultWithPostInfo { - // TODO(#787): Handle Rikiddo benchmarks! T::ApproveOrigin::ensure_origin(origin)?; - let mut extra_weight = Weight::zero(); - let mut status = MarketStatus::Active; + let new_status = MarketStatus::Active; >::mutate_market(&market_id, |m| { ensure!(m.status == MarketStatus::Proposed, Error::::MarketIsNotProposed); @@ -444,30 +439,15 @@ mod pallet { !MarketIdsForEdit::::contains_key(market_id), Error::::MarketEditRequestAlreadyInProgress ); - - match m.scoring_rule { - ScoringRule::CPMM - | ScoringRule::Lmsr - | ScoringRule::Parimutuel - | ScoringRule::Orderbook => { - m.status = MarketStatus::Active; - } - ScoringRule::RikiddoSigmoidFeeMarketEma => { - m.status = MarketStatus::CollectingSubsidy; - status = MarketStatus::CollectingSubsidy; - extra_weight = Self::start_subsidy(m, market_id)?; - } - } - + m.status = new_status; Ok(()) })?; Self::unreserve_creation_bond(&market_id)?; - Self::deposit_event(Event::MarketApproved(market_id, status)); + Self::deposit_event(Event::MarketApproved(market_id, new_status)); // The ApproveOrigin should not pay fees for providing this service - Ok((Some(T::WeightInfo::approve_market().saturating_add(extra_weight)), Pays::No) - .into()) + Ok((Some(T::WeightInfo::approve_market()), Pays::No).into()) } /// Request an edit to a proposed market. @@ -601,92 +581,6 @@ mod pallet { Ok((Some(weight)).into()) } - /// Create a permissionless market, buy complete sets and deploy a pool with specified - /// liquidity. - /// - /// # Arguments - /// - /// * `oracle`: The oracle of the market who will report the correct outcome. - /// * `period`: The active period of the market. - /// * `metadata`: A hash pointer to the metadata of the market. - /// * `market_type`: The type of the market. - /// * `dispute_mechanism`: The market dispute mechanism. - /// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee) - /// * `amount`: The amount of each token to add to the pool. - /// * `weights`: The relative denormalized weight of each asset price. - /// - /// # Weight - /// - /// Complexity: - /// - create_market: `O(n)`, where `n` is the number of market ids, - /// which close at the same time as the specified market. - /// - buy_complete_set: `O(n)`, where `n` is the number of outcome assets - /// for the categorical market. - /// - deploy_swap_pool_for_market_open_pool: `O(n)`, - /// where n is the number of outcome assets for the categorical market. - /// - deploy_swap_pool_for_market_future_pool: `O(n + m)`, - /// where `n` is the number of outcome assets for the categorical market - /// and `m` is the number of market ids, - /// which open at the same time as the specified market. - #[pallet::call_index(7)] - #[pallet::weight( - T::WeightInfo::create_market(CacheSize::get()) - .saturating_add(T::WeightInfo::buy_complete_set(T::MaxCategories::get().into())) - .saturating_add( - T::WeightInfo::deploy_swap_pool_for_market_open_pool(weights.len() as u32) - .max(T::WeightInfo::deploy_swap_pool_for_market_future_pool( - weights.len() as u32, CacheSize::get() - ) - )) - )] - #[transactional] - pub fn create_cpmm_market_and_deploy_assets( - origin: OriginFor, - base_asset: Asset>, - creator_fee: Perbill, - oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, - metadata: MultiHash, - market_type: MarketType, - dispute_mechanism: Option, - #[pallet::compact] swap_fee: BalanceOf, - #[pallet::compact] amount: BalanceOf, - weights: Vec, - ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin.clone())?; - - let create_market_weight = Self::create_market( - origin.clone(), - base_asset, - creator_fee, - oracle, - period, - deadlines, - metadata, - MarketCreation::Permissionless, - market_type.clone(), - dispute_mechanism, - ScoringRule::CPMM, - )? - .actual_weight - .ok_or(Error::::UnexpectedNoneInPostInfo)?; - - // Deploy the swap pool and populate it. - let market_id = >::latest_market_id()?; - let deploy_and_populate_weight = Self::deploy_swap_pool_and_additional_liquidity( - origin, - market_id, - swap_fee, - amount, - weights.clone(), - )? - .actual_weight - .ok_or(Error::::UnexpectedNoneInPostInfo)?; - - Ok(Some(create_market_weight.saturating_add(deploy_and_populate_weight)).into()) - } - /// Creates a market. /// /// # Weight @@ -709,7 +603,6 @@ mod pallet { dispute_mechanism: Option, scoring_rule: ScoringRule, ) -> DispatchResultWithPostInfo { - // TODO(#787): Handle Rikiddo benchmarks! let sender = ensure_signed(origin)?; let (ids_len, _) = Self::do_create_market( sender, @@ -761,7 +654,6 @@ mod pallet { dispute_mechanism: Option, scoring_rule: ScoringRule, ) -> DispatchResultWithPostInfo { - // TODO(#787): Handle Rikiddo benchmarks! let sender = ensure_signed(origin)?; ensure!( MarketIdsForEdit::::contains_key(market_id), @@ -801,183 +693,6 @@ mod pallet { Ok(Some(T::WeightInfo::edit_market(ids_amount)).into()) } - /// Buy complete sets and deploy a pool with specified liquidity for a market. - /// - /// # Arguments - /// - /// * `market_id`: The id of the market. - /// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee) - /// * `amount`: The amount of each token to add to the pool. - /// * `weights`: The relative denormalized weight of each outcome asset. The sum of the - /// weights must be less or equal to _half_ of the `MaxTotalWeight` constant of the - /// swaps pallet. - /// - /// # Weight - /// - /// Complexity: - /// - buy_complete_set: `O(n)`, - /// where `n` is the number of outcome assets for the categorical market. - /// - deploy_swap_pool_for_market_open_pool: `O(n)`, - /// where `n` is the number of outcome assets for the categorical market. - /// - deploy_swap_pool_for_market_future_pool: `O(n + m)`, - /// where `n` is the number of outcome assets for the categorical market, - /// and `m` is the number of market ids, - /// which open at the same time as the specified market. - #[pallet::call_index(10)] - #[pallet::weight( - T::WeightInfo::buy_complete_set(T::MaxCategories::get().into()) - .saturating_add( - T::WeightInfo::deploy_swap_pool_for_market_open_pool(weights.len() as u32) - .max( - T::WeightInfo::deploy_swap_pool_for_market_future_pool( - weights.len() as u32, CacheSize::get() - )) - ) - )] - #[transactional] - pub fn deploy_swap_pool_and_additional_liquidity( - origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, - #[pallet::compact] swap_fee: BalanceOf, - #[pallet::compact] amount: BalanceOf, - weights: Vec, - ) -> DispatchResultWithPostInfo { - ensure_signed(origin.clone())?; - let weight_bcs = Self::buy_complete_set(origin.clone(), market_id, amount)? - .actual_weight - .ok_or(Error::::UnexpectedNoneInPostInfo)?; - let weight_deploy = - Self::deploy_swap_pool_for_market(origin, market_id, swap_fee, amount, weights)? - .actual_weight - .ok_or(Error::::UnexpectedNoneInPostInfo)?; - Ok(Some(weight_bcs.saturating_add(weight_deploy)).into()) - } - - /// Deploy a pool with specified liquidity for a market. - /// - /// The sender must have enough funds to cover all of the required shares to seed the pool. - /// - /// # Arguments - /// - /// * `market_id`: The id of the market. - /// * `swap_fee`: The swap fee, specified as fixed-point ratio (0.1 equals 10% fee) - /// * `amount`: The amount of each token to add to the pool. - /// * `weights`: The relative denormalized weight of each outcome asset. The sum of the - /// weights must be less or equal to _half_ of the `MaxTotalWeight` constant of the - /// swaps pallet. - /// - /// # Weight - /// - /// Complexity: - /// - deploy_swap_pool_for_market_open_pool: `O(n)`, - /// where `n` is the number of outcome assets for the categorical market. - /// - deploy_swap_pool_for_market_future_pool: `O(n + m)`, - /// where `n` is the number of outcome assets for the categorical market, - /// and `m` is the number of market ids, - /// which open at the same time as the specified market. - #[pallet::call_index(11)] - #[pallet::weight( - T::WeightInfo::deploy_swap_pool_for_market_open_pool(weights.len() as u32) - .max( - T::WeightInfo::deploy_swap_pool_for_market_future_pool( - weights.len() as u32, CacheSize::get() - ) - ) - )] - #[transactional] - pub fn deploy_swap_pool_for_market( - origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, - #[pallet::compact] swap_fee: BalanceOf, - #[pallet::compact] amount: BalanceOf, - mut weights: Vec, - ) -> DispatchResultWithPostInfo { - let sender = ensure_signed(origin)?; - - let market = >::market(&market_id)?; - ensure!(market.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); - Self::ensure_market_is_active(&market)?; - - let mut assets = Self::outcome_assets(market_id, &market); - let weights_len = weights.len() as u32; - // although this extrinsic is transactional and this check is inside Swaps::create_pool - // the iteration over weights happens still before the check in Swaps::create_pool - // this could stall the chain, because a malicious user puts a large vector in - ensure!(weights.len() == assets.len(), Error::::WeightsLenMustEqualAssetsLen); - - assets.push(market.base_asset); - - let base_asset_weight = weights.iter().fold(0u128, |acc, val| acc.saturating_add(*val)); - weights.push(base_asset_weight); - - let pool_id = T::Swaps::create_pool( - sender, - assets, - market.base_asset, - market_id, - ScoringRule::CPMM, - Some(swap_fee), - Some(amount), - Some(weights), - )?; - - // Open the pool now or cache it for later - let ids_len: Option = match market.period { - MarketPeriod::Block(ref range) => { - let current_block = >::block_number(); - let open_block = range.start; - if current_block < open_block { - let ids_len = MarketIdsPerOpenBlock::::try_mutate( - open_block, - |ids| -> Result { - ids.try_push(market_id).map_err(|_| >::StorageOverflow)?; - Ok(ids.len() as u32) - }, - )?; - Some(ids_len) - } else { - T::Swaps::open_pool(pool_id)?; - None - } - } - MarketPeriod::Timestamp(ref range) => { - let current_time_frame = Self::calculate_time_frame_of_moment( - >::now(), - ); - let open_time_frame = Self::calculate_time_frame_of_moment(range.start); - if current_time_frame < open_time_frame { - let ids_len = MarketIdsPerOpenTimeFrame::::try_mutate( - open_time_frame, - |ids| -> Result { - ids.try_push(market_id).map_err(|_| >::StorageOverflow)?; - Ok(ids.len() as u32) - }, - )?; - Some(ids_len) - } else { - T::Swaps::open_pool(pool_id)?; - None - } - } - }; - - // This errors if a pool already exists! - >::insert_market_pool(market_id, pool_id)?; - match ids_len { - Some(market_ids_len) => { - Ok(Some(T::WeightInfo::deploy_swap_pool_for_market_future_pool( - weights_len, - market_ids_len, - )) - .into()) - } - None => { - Ok(Some(T::WeightInfo::deploy_swap_pool_for_market_open_pool(weights_len)) - .into()) - } - } - } - /// Redeems the winning shares of a prediction market. /// /// # Weight @@ -995,7 +710,7 @@ mod pallet { let sender = ensure_signed(origin)?; let market = >::market(&market_id)?; - let market_account = >::market_account(market_id); + let market_account = Self::market_account(market_id); ensure!(market.status == MarketStatus::Resolved, Error::::MarketIsNotResolved); ensure!(market.is_redeemable(), Error::::InvalidResolutionMechanism); @@ -1139,11 +854,7 @@ mod pallet { /// which close at the same time as the specified market. #[pallet::call_index(13)] #[pallet::weight(( - T::WeightInfo::reject_market( - CacheSize::get(), - CacheSize::get(), - reject_reason.len() as u32, - ), + T::WeightInfo::reject_market(CacheSize::get(), reject_reason.len() as u32), Pays::No, ))] #[transactional] @@ -1154,7 +865,6 @@ mod pallet { ) -> DispatchResultWithPostInfo { T::RejectOrigin::ensure_origin(origin)?; let market = >::market(&market_id)?; - let open_ids_len = Self::clear_auto_open(&market_id)?; let close_ids_len = Self::clear_auto_close(&market_id)?; let reject_reason: RejectReason = reject_reason .try_into() @@ -1162,10 +872,7 @@ mod pallet { let reject_reason_len = reject_reason.len() as u32; Self::do_reject_market(&market_id, market, reject_reason)?; // The RejectOrigin should not pay fees for providing this service - Ok(( - Some(T::WeightInfo::reject_market(close_ids_len, open_ids_len, reject_reason_len)), - Pays::No, - ) + Ok((Some(T::WeightInfo::reject_market(close_ids_len, reject_reason_len)), Pays::No) .into()) } @@ -1730,7 +1437,7 @@ mod pallet { /// and `m` is the number of market ids, /// which close at the same time as the specified market. #[pallet::call_index(21)] - #[pallet::weight(T::WeightInfo::close_trusted_market(CacheSize::get(), CacheSize::get()))] + #[pallet::weight(T::WeightInfo::close_trusted_market(CacheSize::get()))] #[transactional] pub fn close_trusted_market( origin: OriginFor, @@ -1741,11 +1448,61 @@ mod pallet { ensure!(market.creator == who, Error::::CallerNotMarketCreator); ensure!(market.dispute_mechanism.is_none(), Error::::MarketIsNotTrusted); Self::ensure_market_is_active(&market)?; - let open_ids_len = Self::clear_auto_open(&market_id)?; let close_ids_len = Self::clear_auto_close(&market_id)?; Self::close_market(&market_id)?; Self::set_market_end(&market_id)?; - Ok(Some(T::WeightInfo::close_trusted_market(open_ids_len, close_ids_len)).into()) + Ok(Some(T::WeightInfo::close_trusted_market(close_ids_len)).into()) + } + + /// Allows the manual closing for "broken" markets. + /// A market is "broken", if an unexpected chain stall happened + /// and the auto close was scheduled during this time. + /// + /// # Weight + /// + /// Complexity: `O(n)`, + /// and `n` is the number of market ids, + /// which close at the same time as the specified market. + #[pallet::call_index(22)] + #[pallet::weight(T::WeightInfo::manually_close_market(CacheSize::get()))] + #[transactional] + pub fn manually_close_market( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + + let market = zrml_market_commons::Pallet::::market(&market_id)?; + let now = zrml_market_commons::Pallet::::now(); + let range = match &market.period { + MarketPeriod::Block(_) => { + return Err(Error::::NotAllowedForBlockBasedMarkets.into()); + } + MarketPeriod::Timestamp(ref range) => range, + }; + + let close_ids_len = if range.end <= now { + let range_end_time_frame = Self::calculate_time_frame_of_moment(range.end); + let close_ids_len = MarketIdsPerCloseTimeFrame::::try_mutate( + range_end_time_frame, + |ids| -> Result { + let ids_len = ids.len() as u32; + let position = ids + .iter() + .position(|i| i == &market_id) + .ok_or(Error::::MarketNotInCloseTimeFrameList)?; + let _ = ids.swap_remove(position); + Ok(ids_len) + }, + )?; + Self::on_market_close(&market_id, market)?; + Self::set_market_end(&market_id)?; + close_ids_len + } else { + return Err(Error::::MarketPeriodEndNotAlreadyReachedYet.into()); + }; + + Ok(Some(T::WeightInfo::manually_close_market(close_ids_len)).into()) } } @@ -1868,10 +1625,6 @@ mod pallet { #[pallet::constant] type MaxCategories: Get; - /// The shortest period of collecting subsidy for a Rikiddo market. - #[pallet::constant] - type MaxSubsidyPeriod: Get>; - /// The minimum number of categories available for categorical markets. #[pallet::constant] type MinCategories: Get; @@ -1880,10 +1633,6 @@ mod pallet { #[pallet::constant] type MaxCreatorFee: Get; - /// The shortest period of collecting subsidy for a Rikiddo market. - #[pallet::constant] - type MinSubsidyPeriod: Get>; - /// The maximum number of disputes allowed on any single market. #[pallet::constant] type MaxDisputes: Get; @@ -1970,9 +1719,6 @@ mod pallet { /// Handler for slashed funds. type Slash: OnUnbalanced>; - /// Swaps pallet API - type Swaps: Swaps, MarketId = MarketIdOf>; - /// The base amount of currency that must be bonded for a permissionless market, /// guaranteeing that it will resolve as anything but `Invalid`. #[pallet::constant] @@ -2102,6 +1848,12 @@ mod pallet { CallerNotMarketCreator, /// The market is not trusted. MarketIsNotTrusted, + /// The operation is not allowed for market with a block period. + NotAllowedForBlockBasedMarkets, + /// The market is not in the close time frame list. + MarketNotInCloseTimeFrameList, + /// The market period end was not already reached yet. + MarketPeriodEndNotAlreadyReachedYet, } #[pallet::event] @@ -2120,11 +1872,6 @@ mod pallet { MarketCreated(MarketIdOf, T::AccountId, MarketOf), /// A market has been destroyed. \[market_id\] MarketDestroyed(MarketIdOf), - /// A market was started after gathering enough subsidy. \[market_id, new_market_status\] - MarketStartedWithSubsidy(MarketIdOf, MarketStatus), - /// A market was discarded after failing to gather enough subsidy. - /// \[market_id, new_market_status\] - MarketInsufficientSubsidy(MarketIdOf, MarketStatus), /// A market has been closed. \[market_id\] MarketClosed(MarketIdOf), /// A market has been scheduled to close early. @@ -2165,6 +1912,8 @@ mod pallet { ), /// The global dispute was started. \[market_id\] GlobalDisputeStarted(MarketIdOf), + /// The recovery limit for timestamp based markets was reached due to a prolonged chain stall. + RecoveryLimitReached { last_time_frame: TimeFrame, limit_time_frame: TimeFrame }, } #[pallet::hooks] @@ -2173,14 +1922,6 @@ mod pallet { fn on_initialize(now: T::BlockNumber) -> Weight { let mut total_weight: Weight = Weight::zero(); - // TODO(#808): Use weight when Rikiddo is ready - let _ = Self::process_subsidy_collecting_markets( - now, - >::now(), - ); - total_weight = total_weight - .saturating_add(T::WeightInfo::process_subsidy_collecting_markets_dummy()); - // If we are at genesis or the first block the timestamp is be undefined. No // market needs to be opened or closed on blocks #0 or #1, so we skip the // evaluation. Without this check, new chains starting from genesis will hang up, @@ -2203,25 +1944,6 @@ mod pallet { LastTimeFrame::::get().unwrap_or_else(|| current_time_frame.saturating_sub(1)); let _ = with_transaction(|| { - let open = Self::market_status_manager::< - _, - MarketIdsPerOpenBlock, - MarketIdsPerOpenTimeFrame, - >( - now, - last_time_frame, - current_time_frame, - |market_id, _| { - let weight = Self::open_market(market_id)?; - total_weight = total_weight.saturating_add(weight); - Ok(()) - }, - ); - - total_weight = total_weight.saturating_add(open.unwrap_or_else(|_| { - T::WeightInfo::market_status_manager(CacheSize::get(), CacheSize::get()) - })); - let close = Self::market_status_manager::< _, MarketIdsPerCloseBlock, @@ -2266,7 +1988,7 @@ mod pallet { LastTimeFrame::::set(Some(current_time_frame)); total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - match open.and(close).and(resolve) { + match close.and(resolve) { Err(err) => { Self::deposit_event(Event::BadOnInitialize); log::error!( @@ -2289,18 +2011,7 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - // TODO(#986) after storage migration of release-dispute-system branch is complete, delete this Disputes storage item - /// For each market, this holds the dispute information for each dispute that's - /// been issued. - #[pallet::storage] - pub type Disputes = StorageMap< - _, - Blake2_128Concat, - MarketIdOf, - BoundedVec, T::MaxDisputes>, - ValueQuery, - >; - + // TODO(#1212): Remove in v0.5.1. #[pallet::storage] pub type MarketIdsPerOpenBlock = StorageMap< _, @@ -2310,6 +2021,7 @@ mod pallet { ValueQuery, >; + // TODO(#1212): Remove in v0.5.1. #[pallet::storage] pub type MarketIdsPerOpenTimeFrame = StorageMap< _, @@ -2364,6 +2076,13 @@ mod pallet { ValueQuery, >; + /// Contains market_ids for which advisor has requested edit. + /// Value for given market_id represents the reason for the edit. + #[pallet::storage] + pub type MarketIdsForEdit = + StorageMap<_, Twox64Concat, MarketIdOf, EditReason>; + + // TODO(#1212): Remove in v0.5.1. /// Contains a list of all markets that are currently collecting subsidy and the deadline. // All the values are "cached" here. Results in data duplication, but speeds up the iteration // over every market significantly (otherwise 25µs per relevant market per block). @@ -2374,12 +2093,6 @@ mod pallet { ValueQuery, >; - /// Contains market_ids for which advisor has requested edit. - /// Value for given market_id represents the reason for the edit. - #[pallet::storage] - pub type MarketIdsForEdit = - StorageMap<_, Twox64Concat, MarketIdOf, EditReason>; - impl Pallet { impl_unreserve_bond!(unreserve_creation_bond, creation); impl_unreserve_bond!(unreserve_oracle_bond, oracle); @@ -2401,6 +2114,11 @@ mod pallet { impl_is_bond_pending!(is_close_request_bond_pending, close_request); impl_is_bond_pending!(is_dispute_bond_pending, dispute); + #[inline] + pub(crate) fn market_account(market_id: MarketIdOf) -> AccountIdOf { + T::PalletId::get().into_sub_account_truncating(market_id.saturated_into::()) + } + #[require_transactional] fn do_create_market( who: T::AccountId, @@ -2453,11 +2171,7 @@ mod pallet { )?; let market_id = >::push_market(market.clone())?; - let market_account = >::market_account(market_id); - - if market.status == MarketStatus::CollectingSubsidy { - let _ = Self::start_subsidy(&market, market_id)?; - } + let market_account = Self::market_account(market_id); let ids_amount: u32 = Self::insert_auto_close(&market_id)?; @@ -2538,40 +2252,10 @@ mod pallet { Ok(close_ids_len) } - // Manually remove market from cache for auto open. - fn clear_auto_open(market_id: &MarketIdOf) -> Result { - let market = >::market(market_id)?; - - // No-op if market isn't cached for auto open according to its state. - match market.status { - MarketStatus::Active | MarketStatus::Proposed => (), - _ => return Ok(0u32), - }; - - let open_ids_len = match market.period { - MarketPeriod::Block(range) => { - MarketIdsPerOpenBlock::::mutate(range.start, |ids| -> u32 { - let ids_len = ids.len() as u32; - remove_item::, _>(ids, market_id); - ids_len - }) - } - MarketPeriod::Timestamp(range) => { - let time_frame = Self::calculate_time_frame_of_moment(range.start); - MarketIdsPerOpenTimeFrame::::mutate(time_frame, |ids| -> u32 { - let ids_len = ids.len() as u32; - remove_item::, _>(ids, market_id); - ids_len - }) - } - }; - Ok(open_ids_len) - } - /// 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 + // If there's no dispute mechanism, this function is noop. TODO(#782) This is an // anti-pattern, but it makes benchmarking easier. let dispute_mechanism = match market.dispute_mechanism { Some(ref result) => result, @@ -2627,15 +2311,9 @@ mod pallet { ensure!(amount != BalanceOf::::zero(), Error::::ZeroAmount); let market = >::market(&market_id)?; - ensure!( - matches!( - market.scoring_rule, - ScoringRule::CPMM | ScoringRule::Lmsr | ScoringRule::Orderbook - ), - Error::::InvalidScoringRule - ); + ensure!(market.is_redeemable(), Error::::InvalidScoringRule); - let market_account = >::market_account(market_id); + let market_account = Self::market_account(market_id); ensure!( T::AssetManager::free_balance(market.base_asset, &market_account) >= amount, "Market account does not have sufficient reserves.", @@ -2684,16 +2362,10 @@ mod pallet { T::AssetManager::free_balance(market.base_asset, &who) >= amount, Error::::NotEnoughBalance ); - ensure!( - matches!( - market.scoring_rule, - ScoringRule::CPMM | ScoringRule::Lmsr | ScoringRule::Orderbook - ), - Error::::InvalidScoringRule - ); + ensure!(market.is_redeemable(), Error::::InvalidScoringRule); Self::ensure_market_is_active(&market)?; - let market_account = >::market_account(market_id); + let market_account = Self::market_account(market_id); T::AssetManager::transfer(market.base_asset, &who, &market_account, amount)?; let assets = Self::outcome_assets(market_id, &market); @@ -2854,45 +2526,6 @@ mod pallet { Ok(()) } - fn ensure_market_start_is_in_time( - period: &MarketPeriod>, - ) -> DispatchResult { - let interval = match period { - MarketPeriod::Block(range) => { - let interval_blocks: u128 = range - .start - .saturating_sub(>::block_number()) - .saturated_into(); - interval_blocks.saturating_mul(MILLISECS_PER_BLOCK.into()) - } - MarketPeriod::Timestamp(range) => range - .start - .saturating_sub(>::now()) - .saturated_into(), - }; - - ensure!( - >::saturated_from(interval) >= T::MinSubsidyPeriod::get(), - >::MarketStartTooSoon - ); - ensure!( - >::saturated_from(interval) <= T::MaxSubsidyPeriod::get(), - >::MarketStartTooLate - ); - Ok(()) - } - - pub(crate) fn open_market(market_id: &MarketIdOf) -> Result { - // Is no-op if market has no pool. This should never happen, but it's safer to not - // error in this case. - let mut total_weight = T::DbWeight::get().reads(1); // (For the `market_pool` read) - if let Ok(pool_id) = >::market_pool(market_id) { - let open_pool_weight = T::Swaps::open_pool(pool_id)?; - total_weight = total_weight.saturating_add(open_pool_weight); - } - Ok(total_weight) - } - pub(crate) fn close_market(market_id: &MarketIdOf) -> Result { >::mutate_market(market_id, |market| { ensure!(market.status == MarketStatus::Active, Error::::InvalidMarketStatus); @@ -2923,10 +2556,6 @@ mod pallet { Ok(()) })?; let mut total_weight = T::DbWeight::get().reads_writes(1, 1); - if let Ok(pool_id) = >::market_pool(market_id) { - let close_pool_weight = T::Swaps::close_pool(pool_id)?; - total_weight = total_weight.saturating_add(close_pool_weight); - }; Self::deposit_event(Event::MarketClosed(*market_id)); total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); Ok(total_weight) @@ -3169,8 +2798,6 @@ mod pallet { } _ => return Err(Error::::InvalidMarketStatus.into()), }; - let clean_up_weight = Self::clean_up_pool(market, market_id, &resolved_outcome)?; - total_weight = total_weight.saturating_add(clean_up_weight); // TODO: https://github.com/zeitgeistpm/zeitgeist/issues/815 // Following call should return weight consumed by it. T::LiquidityMining::distribute_market_incentives(market_id)?; @@ -3191,168 +2818,6 @@ mod pallet { Ok(total_weight.saturating_add(Self::calculate_internal_resolve_weight(market))) } - pub(crate) fn process_subsidy_collecting_markets( - current_block: T::BlockNumber, - current_time: MomentOf, - ) -> Weight { - let mut total_weight: Weight = Weight::zero(); - let dbweight = T::DbWeight::get(); - let one_read = T::DbWeight::get().reads(1); - let one_write = T::DbWeight::get().writes(1); - - let retain_closure = |subsidy_info: &SubsidyUntil< - T::BlockNumber, - MomentOf, - MarketIdOf, - >| { - let market_ready = match &subsidy_info.period { - MarketPeriod::Block(period) => period.start <= current_block, - MarketPeriod::Timestamp(period) => period.start <= current_time, - }; - - if market_ready { - let pool_id = - >::market_pool(&subsidy_info.market_id); - total_weight.saturating_add(one_read); - - if let Ok(pool_id) = pool_id { - let end_subsidy_result = T::Swaps::end_subsidy_phase(pool_id); - - if let Ok(result) = end_subsidy_result { - total_weight = total_weight.saturating_add(result.weight); - - if result.result { - // Sufficient subsidy, activate market. - let mutate_result = >::mutate_market( - &subsidy_info.market_id, - |m| { - m.status = MarketStatus::Active; - Ok(()) - }, - ); - - total_weight = - total_weight.saturating_add(one_read).saturating_add(one_write); - - if let Err(err) = mutate_result { - log::error!( - target: LOG_TARGET, - "Cannot find market associated to \ - market id. market_id: {:?}, error: {:?}", - subsidy_info.market_id, - err - ); - return true; - } - - Self::deposit_event(Event::MarketStartedWithSubsidy( - subsidy_info.market_id, - MarketStatus::Active, - )); - } else { - // Insufficient subsidy, cleanly remove pool and close market. - let destroy_result = - T::Swaps::destroy_pool_in_subsidy_phase(pool_id); - - if let Err(err) = destroy_result { - log::error!( - target: LOG_TARGET, - "Cannot destroy pool with missing \ - subsidy. market_id: {:?}, error: {:?}", - subsidy_info.market_id, - err - ); - return true; - } else if let Ok(weight) = destroy_result { - total_weight = total_weight.saturating_add(weight); - } - - let market_result = >::mutate_market( - &subsidy_info.market_id, - |m| { - m.status = MarketStatus::InsufficientSubsidy; - - // Unreserve funds reserved during market creation - if m.creation == MarketCreation::Permissionless { - Self::unreserve_creation_bond(&subsidy_info.market_id)?; - } - // AdvisoryBond was already returned when the market - // was approved. Approval is inevitable to reach this. - Self::unreserve_oracle_bond(&subsidy_info.market_id)?; - - total_weight = total_weight - .saturating_add(dbweight.reads(2)) - .saturating_add(dbweight.writes(2)); - Ok(()) - }, - ); - - if let Err(err) = market_result { - log::error!( - target: LOG_TARGET, - "Cannot find market associated to \ - market id. market_id: {:?}, error: {:?}", - subsidy_info.market_id, - err - ); - return true; - } - - // `remove_market_pool` can only error due to missing pool, but - // above we ensured that the pool exists. - let _ = >::remove_market_pool( - &subsidy_info.market_id, - ); - total_weight = - total_weight.saturating_add(one_read).saturating_add(one_write); - Self::deposit_event(Event::MarketInsufficientSubsidy( - subsidy_info.market_id, - MarketStatus::InsufficientSubsidy, - )); - } - - return false; - } else if let Err(err) = end_subsidy_result { - log::error!( - target: LOG_TARGET, - "An error occured during end of subsidy phase. - pool_id: {:?}, market_id: {:?}, error: {:?}", - pool_id, - subsidy_info.market_id, - err - ); - } - } else if let Err(err) = pool_id { - log::error!( - target: LOG_TARGET, - "Cannot find pool associated to market. - market_id: {:?}, error: {:?}", - subsidy_info.market_id, - err - ); - return true; - } - } - - true - }; - - let mut weight_basis = Weight::zero(); - >::mutate( - |e: &mut BoundedVec< - SubsidyUntil, MarketIdOf>, - _, - >| { - weight_basis = T::WeightInfo::process_subsidy_collecting_markets_raw( - e.len().saturated_into(), - ); - e.retain(retain_closure); - }, - ); - - weight_basis.saturating_add(total_weight) - } - /// The reserve ID of the prediction-markets pallet. #[inline] pub fn reserve_id() -> [u8; 8] { @@ -3386,7 +2851,25 @@ mod pallet { MarketIdsPerBlock::remove(block_number); let mut time_frame_ids_len = 0u32; - for time_frame in last_time_frame.saturating_add(1)..=current_time_frame { + let start = last_time_frame.saturating_add(1); + let end = current_time_frame; + let diff = end.saturating_sub(start); + let start = if diff > MAX_RECOVERY_TIME_FRAMES { + log::warn!( + target: LOG_TARGET, + "Could not recover all time frames since the last time frame {:?}.", + last_time_frame, + ); + let limit_time_frame = end.saturating_sub(MAX_RECOVERY_TIME_FRAMES); + Self::deposit_event(Event::RecoveryLimitReached { + last_time_frame, + limit_time_frame, + }); + limit_time_frame + } else { + start + }; + for time_frame in start..=end { let market_ids_per_time_frame = MarketIdsPerTimeFrame::get(time_frame); time_frame_ids_len = time_frame_ids_len.saturating_add(market_ids_per_time_frame.len() as u32); @@ -3434,65 +2917,6 @@ mod pallet { )) } - // If a market has a pool that is `Active`, then changes from `Active` to `Clean`. If - // the market does not exist or the market does not have a pool, does nothing. - fn clean_up_pool( - market: &MarketOf, - market_id: &MarketIdOf, - outcome_report: &OutcomeReport, - ) -> Result { - let pool_id = if let Ok(el) = >::market_pool(market_id) { - el - } else { - return Ok(T::DbWeight::get().reads(1)); - }; - let market_account = >::market_account(*market_id); - let weight = T::Swaps::clean_up_pool( - &market.market_type, - pool_id, - outcome_report, - &market_account, - )?; - Ok(weight.saturating_add(T::DbWeight::get().reads(2))) - } - - // Creates a pool for the market and registers the market in the list of markets - // currently collecting subsidy. - pub(crate) fn start_subsidy( - market: &MarketOf, - market_id: MarketIdOf, - ) -> Result { - ensure!( - market.status == MarketStatus::CollectingSubsidy, - Error::::MarketIsNotCollectingSubsidy - ); - - let mut assets = Self::outcome_assets(market_id, market); - assets.push(market.base_asset); - let total_assets = assets.len(); - - let pool_id = T::Swaps::create_pool( - market.creator.clone(), - assets, - market.base_asset, - market_id, - market.scoring_rule, - None, - None, - None, - )?; - - // This errors if a pool already exists! - >::insert_market_pool(market_id, pool_id)?; - >::try_mutate(|markets| { - markets - .try_push(SubsidyUntil { market_id, period: market.period.clone() }) - .map_err(|_| >::StorageOverflow) - })?; - - Ok(T::WeightInfo::start_subsidy(total_assets.saturated_into())) - } - fn construct_market( base_asset: Asset>, creator: T::AccountId, @@ -3530,17 +2954,8 @@ mod pallet { Self::ensure_market_deadlines_are_valid(&deadlines, dispute_mechanism.is_none())?; Self::ensure_market_type_is_valid(&market_type)?; - if scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma { - Self::ensure_market_start_is_in_time(&period)?; - } let status: MarketStatus = match creation { - MarketCreation::Permissionless => match scoring_rule { - ScoringRule::CPMM - | ScoringRule::Lmsr - | ScoringRule::Parimutuel - | ScoringRule::Orderbook => MarketStatus::Active, - ScoringRule::RikiddoSigmoidFeeMarketEma => MarketStatus::CollectingSubsidy, - }, + MarketCreation::Permissionless => MarketStatus::Active, MarketCreation::Advised => MarketStatus::Proposed, }; Ok(Market { diff --git a/zrml/prediction-markets/src/migrations.rs b/zrml/prediction-markets/src/migrations.rs index 72c952afc..ce968a678 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -16,11 +16,138 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use crate::{ + Config, MarketIdsPerOpenBlock, MarketIdsPerOpenTimeFrame, MarketsCollectingSubsidy, + Pallet as PredictionMarkets, +}; +use core::marker::PhantomData; +use frame_support::{ + log, + pallet_prelude::{StorageVersion, Weight}, + traits::{Get, OnRuntimeUpgrade}, +}; + +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; + +const PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION: u16 = 7; +const PREDICTION_MARKETS_NEXT_STORAGE_VERSION: u16 = 8; + +pub struct DrainDeprecatedStorage(PhantomData); + +impl OnRuntimeUpgrade for DrainDeprecatedStorage +where + T: Config, +{ + fn on_runtime_upgrade() -> Weight { + let mut total_weight = T::DbWeight::get().reads(1); + let prediction_markets_version = StorageVersion::get::>(); + if prediction_markets_version != PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION { + log::info!( + "DrainDeprecatedStorage: prediction-markets version is {:?}, but {:?} is required", + prediction_markets_version, + PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("DrainDeprecatedStorage: Starting..."); + let mut reads_writes = 1u64; // For killing MarketsCollectingSubsidy + reads_writes = + reads_writes.saturating_add(MarketIdsPerOpenBlock::::drain().count() as u64); + reads_writes = + reads_writes.saturating_add(MarketIdsPerOpenTimeFrame::::drain().count() as u64); + MarketsCollectingSubsidy::::kill(); + log::info!("DrainDeprecatedStorage: Drained {} keys.", reads_writes); + total_weight = total_weight + .saturating_add(T::DbWeight::get().reads_writes(reads_writes, reads_writes)); + StorageVersion::new(PREDICTION_MARKETS_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("DrainDeprecatedStorage: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), &'static str> { + if MarketIdsPerOpenBlock::::iter().count() != 0 { + return Err("DrainDeprecatedStorage: MarketIdsPerOpenBlock is not empty!"); + } + if MarketIdsPerOpenTimeFrame::::iter().count() != 0 { + return Err("DrainDeprecatedStorage: MarketIdsPerOpenTimeFrame is not empty!"); + } + if MarketsCollectingSubsidy::::exists() { + return Err("DrainDeprecatedStorage: MarketsCollectingSubsidy still exists!"); + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; - use frame_support::{dispatch::fmt::Debug, migration::put_storage_value, StorageHasher}; + use crate::{ + mock::{ExtBuilder, Runtime}, + CacheSize, + }; + use frame_support::{ + dispatch::fmt::Debug, migration::put_storage_value, storage_root, StorageHasher, + }; use parity_scale_codec::Encode; + use sp_runtime::{traits::ConstU32, BoundedVec, StateVersion}; + use zeitgeist_primitives::types::{MarketPeriod, SubsidyUntil}; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + DrainDeprecatedStorage::::on_runtime_upgrade(); + assert_eq!( + StorageVersion::get::>(), + PREDICTION_MARKETS_NEXT_STORAGE_VERSION + ); + }); + } + + #[test] + fn on_runtime_upgrade_works() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + set_up_storage(); + DrainDeprecatedStorage::::on_runtime_upgrade(); + assert_eq!(MarketIdsPerOpenBlock::::iter().count(), 0); + assert_eq!(MarketIdsPerOpenTimeFrame::::iter().count(), 0); + assert!(!MarketsCollectingSubsidy::::exists()); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(PREDICTION_MARKETS_NEXT_STORAGE_VERSION) + .put::>(); + set_up_storage(); + let tmp = storage_root(StateVersion::V1); + DrainDeprecatedStorage::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + fn set_up_version() { + StorageVersion::new(PREDICTION_MARKETS_REQUIRED_STORAGE_VERSION) + .put::>(); + } + + fn set_up_storage() { + let market_ids_per_open_block: BoundedVec<_, CacheSize> = vec![1, 2, 3].try_into().unwrap(); + MarketIdsPerOpenBlock::::insert(1, market_ids_per_open_block); + let market_ids_per_open_time_frame: BoundedVec<_, CacheSize> = + vec![4, 5, 6].try_into().unwrap(); + MarketIdsPerOpenTimeFrame::::insert(2, market_ids_per_open_time_frame); + let subsidy_until: BoundedVec<_, ConstU32<16>> = + vec![SubsidyUntil { market_id: 7, period: MarketPeriod::Block(8..9) }] + .try_into() + .unwrap(); + MarketsCollectingSubsidy::::put(subsidy_until); + } #[allow(unused)] fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) @@ -37,30 +164,11 @@ mod tests { } } -// 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 -// and previous migrations. mod utility { - use crate::{BalanceOf, Config, MarketIdOf}; use alloc::vec::Vec; - use frame_support::{ - migration::{get_storage_value, put_storage_value}, - storage::{storage_prefix, unhashed}, - traits::StorageVersion, - Blake2_128Concat, StorageHasher, - }; + use frame_support::StorageHasher; use parity_scale_codec::Encode; - use zeitgeist_primitives::types::{Pool, PoolId}; - #[allow(unused)] - const SWAPS: &[u8] = b"Swaps"; - #[allow(unused)] - const POOLS: &[u8] = b"Pools"; - #[allow(unused)] - fn storage_prefix_of_swaps_pallet() -> [u8; 32] { - storage_prefix(b"Swaps", b":__STORAGE_VERSION__:") - } #[allow(unused)] pub fn key_to_hash(key: K) -> Vec where @@ -69,26 +177,4 @@ mod utility { { key.using_encoded(H::hash).as_ref().to_vec() } - #[allow(unused)] - pub fn get_on_chain_storage_version_of_swaps_pallet() -> StorageVersion { - let key = storage_prefix_of_swaps_pallet(); - unhashed::get_or_default(&key) - } - #[allow(unused)] - pub fn put_storage_version_of_swaps_pallet(value: u16) { - let key = storage_prefix_of_swaps_pallet(); - unhashed::put(&key, &StorageVersion::new(value)); - } - #[allow(unused)] - pub fn get_pool(pool_id: PoolId) -> Option, MarketIdOf>> { - let hash = key_to_hash::(pool_id); - let pool_maybe = - get_storage_value::, MarketIdOf>>>(SWAPS, POOLS, &hash); - pool_maybe.unwrap_or(None) - } - #[allow(unused)] - pub fn set_pool(pool_id: PoolId, pool: Pool, MarketIdOf>) { - let hash = key_to_hash::(pool_id); - put_storage_value(SWAPS, POOLS, &hash, Some(pool)); - } } diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index d1017c081..40cad9a65 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -37,33 +37,29 @@ use sp_runtime::{ DispatchError, DispatchResult, }; use std::cell::RefCell; -use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{ constants::mock::{ AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, - BalanceFractionalDecimals, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, - CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, - CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, - CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDeposits, ExitFee, - GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, - InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, MaxApprovals, MaxAssets, - MaxCategories, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, - MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxInRatio, - MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxOwners, MaxRejectReasonLen, - MaxReserves, MaxSelectedDraws, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, - MaxYearlyInflation, MinAssets, MinCategories, MinDisputeDuration, MinJurorStake, - MinOracleDuration, MinOutcomeVoteAmount, MinSubsidy, MinSubsidyPeriod, MinWeight, - MinimumPeriod, OutcomeBond, OutcomeFactor, OutsiderBond, PmPalletId, RemoveKeysLimit, - RequestInterval, SimpleDisputesPalletId, SwapsPalletId, TreasuryPalletId, VotePeriod, - VotingOutcomeFee, BASE, CENT, MILLISECS_PER_BLOCK, + BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, + CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, + CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CorrectionPeriod, CourtPalletId, + ExistentialDeposit, ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, + GlobalDisputeLockId, GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, + LockId, MaxAppeals, MaxApprovals, MaxCategories, MaxCourtParticipants, MaxCreatorFee, + MaxDelegations, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, + MaxGracePeriod, MaxMarketLifetime, MaxOracleDuration, MaxOwners, MaxRejectReasonLen, + MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinCategories, MinDisputeDuration, + MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, OutcomeBond, + OutcomeFactor, OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, + SimpleDisputesPalletId, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, + MILLISECS_PER_BLOCK, }, traits::DeployPoolApi, types::{ AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, MarketId, Moment, PoolId, SerdeWrapper, UncheckedExtrinsicTest, + CurrencyId, Hash, Index, MarketId, Moment, SerdeWrapper, UncheckedExtrinsicTest, }, }; -use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; @@ -149,7 +145,6 @@ ord_parameter_types! { pub const Sudo: AccountIdTest = SUDO; } parameter_types! { - pub const MinSubsidyPerAccount: Balance = BASE; pub const AdvisoryBond: Balance = 11 * CENT; pub const AdvisoryBondSlashPercentage: Percent = Percent::from_percent(10); pub const OracleBond: Balance = 25 * CENT; @@ -172,10 +167,8 @@ construct_runtime!( MarketCommons: zrml_market_commons::{Pallet, Storage}, PredictionMarkets: prediction_markets::{Event, Pallet, Storage}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, - RikiddoSigmoidFeeMarketEma: zrml_rikiddo::{Pallet, Storage}, SimpleDisputes: zrml_simple_disputes::{Event, Pallet, Storage}, GlobalDisputes: zrml_global_disputes::{Event, Pallet, Storage}, - Swaps: zrml_swaps::{Call, Event, Pallet}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, Tokens: orml_tokens::{Config, Event, Pallet, Storage}, @@ -212,10 +205,8 @@ impl crate::Config for Runtime { type MaxDisputeDuration = MaxDisputeDuration; type MaxGracePeriod = MaxGracePeriod; type MaxOracleDuration = MaxOracleDuration; - type MaxSubsidyPeriod = MaxSubsidyPeriod; type MaxMarketLifetime = MaxMarketLifetime; type MinCategories = MinCategories; - type MinSubsidyPeriod = MinSubsidyPeriod; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; type OracleBond = OracleBond; @@ -229,7 +220,6 @@ impl crate::Config for Runtime { type AssetManager = AssetManager; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; - type Swaps = Swaps; type ValidityBond = ValidityBond; type WeightInfo = prediction_markets::weights::WeightInfo; } @@ -366,25 +356,9 @@ impl zrml_liquidity_mining::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } -impl zrml_rikiddo::Config for Runtime { - type Timestamp = Timestamp; - type Balance = Balance; - type FixedTypeU = FixedU128; - type FixedTypeS = FixedI128; - type BalanceFractionalDecimals = BalanceFractionalDecimals; - type PoolId = PoolId; - type Rikiddo = RikiddoSigmoidMV< - Self::FixedTypeU, - Self::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - >; -} - impl zrml_simple_disputes::Config for Runtime { type Currency = Balances; type RuntimeEvent = RuntimeEvent; @@ -414,29 +388,6 @@ impl zrml_global_disputes::Config for Runtime { type WeightInfo = zrml_global_disputes::weights::WeightInfo; } -impl zrml_swaps::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type ExitFee = ExitFee; - type FixedTypeU = ::FixedTypeU; - type FixedTypeS = ::FixedTypeS; - type LiquidityMining = LiquidityMining; - type MarketCommons = MarketCommons; - type MaxAssets = MaxAssets; - type MaxInRatio = MaxInRatio; - type MaxOutRatio = MaxOutRatio; - type MaxSwapFee = MaxSwapFee; - type MaxTotalWeight = MaxTotalWeight; - type MaxWeight = MaxWeight; - type MinAssets = MinAssets; - type MinSubsidy = MinSubsidy; - type MinSubsidyPerAccount = MinSubsidyPerAccount; - type MinWeight = MinWeight; - type PalletId = SwapsPalletId; - type RikiddoSigmoidFeeMarketEma = RikiddoSigmoidFeeMarketEma; - type AssetManager = AssetManager; - type WeightInfo = zrml_swaps::weights::WeightInfo; -} - impl pallet_treasury::Config for Runtime { type ApproveOrigin = EnsureSignedBy; type Burn = (); diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 177c2eca6..0b3b6cbaf 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -23,14 +23,13 @@ extern crate alloc; use crate::{ mock::*, Config, Error, Event, LastTimeFrame, MarketIdsForEdit, MarketIdsPerCloseBlock, - MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerOpenBlock, - MarketIdsPerReportBlock, TimeFrame, + MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, }; use alloc::collections::BTreeMap; use core::ops::{Range, RangeInclusive}; use frame_support::{ - assert_err, assert_noop, assert_ok, - dispatch::{DispatchError, DispatchResultWithPostInfo}, + assert_noop, assert_ok, + dispatch::DispatchError, traits::{NamedReservableCurrency, OnInitialize}, }; use sp_runtime::{traits::BlakeTwo256, Perquintill}; @@ -40,18 +39,17 @@ use zrml_court::{types::*, Error as CError}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use sp_arithmetic::Perbill; -use sp_runtime::traits::{AccountIdConversion, Hash, SaturatedConversion, Zero}; +use sp_runtime::traits::{Hash, SaturatedConversion, Zero}; use zeitgeist_primitives::{ constants::mock::{ CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, MaxAppeals, MaxSelectedDraws, MinJurorStake, OutcomeBond, OutcomeFactor, OutsiderBond, BASE, CENT, MILLISECS_PER_BLOCK, }, - traits::Swaps as SwapsPalletApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, Market, MarketBonds, - MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, - Moment, MultiHash, OutcomeReport, PoolStatus, Report, ScalarPosition, ScoringRule, + AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, MarketBonds, MarketCreation, + MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, + MultiHash, OutcomeReport, Report, ScalarPosition, ScoringRule, }, }; use zrml_global_disputes::{ @@ -59,8 +57,7 @@ use zrml_global_disputes::{ GlobalDisputesPalletApi, Outcomes, PossessionOf, }; use zrml_market_commons::MarketCommonsPalletApi; -use zrml_swaps::Pools; -const LIQUIDITY: u128 = 100 * BASE; + const SENTINEL_AMOUNT: u128 = BASE; fn get_deadlines() -> Deadlines<::BlockNumber> { @@ -145,10 +142,7 @@ fn simple_create_scalar_market( } #[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Suspended)] #[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::CollectingSubsidy)] -#[test_case(MarketStatus::InsufficientSubsidy)] #[test_case(MarketStatus::Reported)] #[test_case(MarketStatus::Disputed)] #[test_case(MarketStatus::Resolved)] @@ -158,7 +152,7 @@ fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { Asset::Ztg, MarketCreation::Permissionless, 0..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; assert_ok!(MarketCommons::mutate_market(&market_id, |market| { @@ -199,7 +193,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumb Asset::Ztg, MarketCreation::Permissionless, now..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); run_blocks(3); let market_id = 0; @@ -236,7 +230,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; let market = MarketCommons::market(&market_id).unwrap(); @@ -276,15 +270,13 @@ fn admin_move_market_to_closed_fails_if_market_does_not_exist() { #[test_case(MarketStatus::Disputed; "disputed")] #[test_case(MarketStatus::Resolved; "resolved")] #[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient subsidy")] fn admin_move_market_to_closed_fails_if_market_is_not_active(market_status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( Asset::Ztg, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; assert_ok!(MarketCommons::mutate_market(&market_id, |market| { @@ -299,10 +291,10 @@ fn admin_move_market_to_closed_fails_if_market_is_not_active(market_status: Mark } #[test] -fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { +fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { ExtBuilder::default().build().execute_with(|| { let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -310,13 +302,12 @@ fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { MarketPeriod::Block(22..66), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -324,35 +315,15 @@ fn admin_move_market_to_closed_correctly_clears_auto_open_and_close_blocks() { MarketPeriod::Block(33..66), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(22..33), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0)); - let auto_close = MarketIdsPerCloseBlock::::get(66); - assert_eq!(auto_close.len(), 1); - assert_eq!(auto_close[0], 1); - - let auto_open = MarketIdsPerOpenBlock::::get(22); - assert_eq!(auto_open.len(), 1); - assert_eq!(auto_open[0], 2); + let auto_close = MarketIdsPerCloseBlock::::get(66).into_inner(); + assert_eq!(auto_close, vec![1]); }); } @@ -372,7 +343,7 @@ fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { MarketCreation::Permissionless, MarketType::Scalar(range), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidOutcomeRange ); @@ -399,7 +370,7 @@ fn create_market_fails_on_min_dispute_period() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::DisputeDurationSmallerThanMinDisputeDuration ); @@ -426,7 +397,7 @@ fn create_market_fails_on_min_oracle_duration() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::OracleDurationSmallerThanMinOracleDuration ); @@ -453,7 +424,7 @@ fn create_market_fails_on_max_dispute_period() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::DisputeDurationGreaterThanMaxDisputeDuration ); @@ -480,7 +451,7 @@ fn create_market_fails_on_max_grace_period() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::GracePeriodGreaterThanMaxGracePeriod ); @@ -507,7 +478,7 @@ fn create_market_fails_on_max_oracle_duration() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::OracleDurationGreaterThanMaxOracleDuration ); @@ -538,7 +509,7 @@ fn create_market_with_foreign_assets() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidBaseAsset, ); @@ -555,7 +526,7 @@ fn create_market_with_foreign_assets() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::UnregisteredForeignAsset, ); @@ -571,7 +542,7 @@ fn create_market_with_foreign_assets() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market = MarketCommons::market(&0).unwrap(); assert_eq!(market.base_asset, Asset::ForeignAsset(100)); @@ -587,7 +558,7 @@ fn admin_move_market_to_resolved_resolves_reported_market() { base_asset, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; @@ -648,11 +619,10 @@ fn admin_move_market_to_resolved_resolves_reported_market() { }); } -#[test_case(MarketStatus::Active; "Active")] -#[test_case(MarketStatus::Closed; "Closed")] -#[test_case(MarketStatus::CollectingSubsidy; "CollectingSubsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "InsufficientSubsidy")] -fn admin_move_market_to_resovled_fails_if_market_is_not_reported_or_disputed( +#[test_case(MarketStatus::Active)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Resolved)] +fn admin_move_market_to_resolved_fails_if_market_is_not_reported_or_disputed( market_status: MarketStatus, ) { ExtBuilder::default().build().execute_with(|| { @@ -660,7 +630,7 @@ fn admin_move_market_to_resovled_fails_if_market_is_not_reported_or_disputed( Asset::Ztg, MarketCreation::Permissionless, 0..33, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; assert_ok!(MarketCommons::mutate_market(&market_id, |market| { @@ -684,7 +654,7 @@ fn it_creates_binary_markets() { Asset::Ztg, MarketCreation::Permissionless, 0..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // check the correct amount was reserved @@ -696,7 +666,7 @@ fn it_creates_binary_markets() { Asset::Ztg, MarketCreation::Advised, 0..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let new_alice_reserved = Balances::reserved_balance(ALICE); @@ -716,11 +686,11 @@ fn create_categorical_market_deposits_the_correct_event() { Asset::Ztg, MarketCreation::Permissionless, 1..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; let market = MarketCommons::market(&market_id).unwrap(); - let market_account = MarketCommons::market_account(market_id); + let market_account = PredictionMarkets::market_account(market_id); System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); }); } @@ -733,11 +703,11 @@ fn create_scalar_market_deposits_the_correct_event() { Asset::Ztg, MarketCreation::Permissionless, 1..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; let market = MarketCommons::market(&market_id).unwrap(); - let market_account = MarketCommons::market_account(market_id); + let market_account = PredictionMarkets::market_account(market_id); System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); }); } @@ -757,7 +727,7 @@ fn it_does_not_create_market_with_too_few_categories() { MarketCreation::Advised, MarketType::Categorical(::MinCategories::get() - 1), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr ), Error::::NotEnoughCategories ); @@ -779,7 +749,7 @@ fn it_does_not_create_market_with_too_many_categories() { MarketCreation::Advised, MarketType::Categorical(::MaxCategories::get() + 1), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr ), Error::::TooManyCategories ); @@ -794,7 +764,7 @@ fn it_allows_advisory_origin_to_approve_markets() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -824,7 +794,7 @@ fn it_allows_request_edit_origin_to_request_edits_for_markets() { Asset::Ztg, MarketCreation::Advised, 2..4, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -865,7 +835,7 @@ fn request_edit_fails_on_bad_origin() { Asset::Ztg, MarketCreation::Advised, 2..4, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -889,7 +859,7 @@ fn edit_request_fails_if_edit_reason_is_too_long() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -913,7 +883,7 @@ fn market_with_edit_request_cannot_be_approved() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -941,7 +911,7 @@ fn it_allows_the_advisory_origin_to_reject_markets() { Asset::Ztg, MarketCreation::Advised, 4..6, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -974,7 +944,7 @@ fn reject_errors_if_reject_reason_is_too_long() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -998,7 +968,7 @@ fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -1028,7 +998,7 @@ fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { base_asset, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check @@ -1093,19 +1063,19 @@ fn reject_market_clears_auto_close_blocks() { Asset::Ztg, MarketCreation::Advised, 33..66, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, 22..66, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); simple_create_categorical_market( Asset::Ztg, MarketCreation::Advised, 22..33, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let reject_reason: Vec = vec![0; ::MaxRejectReasonLen::get() as usize]; @@ -1138,7 +1108,7 @@ fn on_market_close_auto_rejects_expired_advised_market() { base_asset, MarketCreation::Advised, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market_id = 0; @@ -1184,7 +1154,7 @@ fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { base_asset, MarketCreation::Advised, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); run_to_block(2); let market_id = 0; @@ -1224,118 +1194,134 @@ fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { } #[test] -fn on_market_open_successfully_auto_opens_market_pool_with_blocks() { +fn on_market_close_successfully_auto_closes_market_with_blocks() { ExtBuilder::default().build().execute_with(|| { - let start = 33; - let end = 66; + let end = 33; let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Block(start..end), + MarketPeriod::Block(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); let market_id = 0; - let pool_id = MarketCommons::market_pool(&market_id).unwrap(); - run_to_block(start - 1); - let pool_before_open = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_before_open.pool_status, PoolStatus::Initialized); + run_to_block(end - 1); + let market_before_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_before_close.status, MarketStatus::Active); + + run_to_block(end); + let market_after_close = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); - run_to_block(start); - let pool_after_open = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_after_open.pool_status, PoolStatus::Active); + System::assert_last_event(Event::MarketClosed(market_id).into()); }); } #[test] -fn on_market_close_successfully_auto_closes_market_with_blocks() { +fn on_market_close_successfully_auto_closes_market_with_timestamps() { ExtBuilder::default().build().execute_with(|| { - let end = 33; + let end: Moment = (2 * MILLISECS_PER_BLOCK).into(); let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Block(0..end), + MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); let market_id = 0; - let pool_id = MarketCommons::market_pool(&market_id).unwrap(); - run_to_block(end - 1); + // (Check that the market doesn't close too soon) + set_timestamp_for_on_initialize(end - 1); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! let market_before_close = MarketCommons::market(&market_id).unwrap(); assert_eq!(market_before_close.status, MarketStatus::Active); - let pool_before_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_before_close.pool_status, PoolStatus::Active); - run_to_block(end); + set_timestamp_for_on_initialize(end); + run_blocks(1); let market_after_close = MarketCommons::market(&market_id).unwrap(); assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); System::assert_last_event(Event::MarketClosed(market_id).into()); }); } #[test] -fn on_market_open_successfully_auto_opens_market_with_timestamps() { +fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. ExtBuilder::default().build().execute_with(|| { - let start: Moment = (33 * MILLISECS_PER_BLOCK).into(); - let end: Moment = (66 * MILLISECS_PER_BLOCK).into(); + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Timestamp(start..end), + MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, )); - let market_id = 0; - let pool_id = MarketCommons::market_pool(&market_id).unwrap(); - // (Check that the market doesn't close too soon) - set_timestamp_for_on_initialize(start - 1); - run_blocks(1); // Trigger hook! - let pool_before_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_before_close.pool_status, PoolStatus::Initialized); + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize(9 * MILLISECS_PER_BLOCK as u64); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + System::assert_has_event(Event::MarketClosed(0).into()); - set_timestamp_for_on_initialize(start); - run_blocks(1); // Trigger hook! - let pool_after_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Active); + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Closed); + System::assert_has_event(Event::MarketClosed(1).into()); }); } #[test] -fn on_market_close_successfully_auto_closes_market_with_timestamps() { +fn on_market_close_market_status_manager_exceeds_max_recovery_time_frames_after_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. ExtBuilder::default().build().execute_with(|| { - let end: Moment = (2 * MILLISECS_PER_BLOCK).into(); + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -1343,84 +1329,114 @@ fn on_market_close_successfully_auto_closes_market_with_timestamps() { MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Timestamp(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, )); - let market_id = 0; - let pool_id = MarketCommons::market_pool(&market_id).unwrap(); - // (Check that the market doesn't close too soon) - set_timestamp_for_on_initialize(end - 1); + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - let market_before_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_before_close.status, MarketStatus::Active); - let pool_before_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_before_close.pool_status, PoolStatus::Active); - set_timestamp_for_on_initialize(end); - run_blocks(1); - let market_after_close = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); + System::assert_last_event( + Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.into(), + ); - System::assert_last_event(Event::MarketClosed(market_id).into()); + // still active, not closed, because recovery limit reached + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); }); } #[test] -fn on_market_open_successfully_auto_opens_multiple_markets_after_stall() { - // We check that `on_market_open` works correctly even if a block takes much longer than 12sec +fn manually_close_market_after_long_stall() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec // to be produced and multiple markets are involved. ExtBuilder::default().build().execute_with(|| { // Mock last time frame to prevent it from defaulting. LastTimeFrame::::set(Some(0)); - let start: Moment = (33 * MILLISECS_PER_BLOCK).into(); - let end: Moment = (666 * MILLISECS_PER_BLOCK).into(); + let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Timestamp(start..end), + MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Timestamp(start..end), + MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize(end / 2); + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - assert_eq!(Swaps::pool(0).unwrap().pool_status, PoolStatus::Active); - assert_eq!(Swaps::pool(1).unwrap().pool_status, PoolStatus::Active); + let new_end = >::now(); + assert_ne!(end, new_end); + + // still active, not closed, because recovery limit reached + let market_after_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let market_after_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_close.status, MarketStatus::Active); + + let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![0, 1]); + + assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0)); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![1]); + let market_after_manual_close = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_manual_close.status, MarketStatus::Closed); + + assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); + assert_ok!(PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 1)); + assert_eq!(MarketIdsPerCloseTimeFrame::::get(range_end_time_frame), vec![]); + let market_after_manual_close = MarketCommons::market(&1).unwrap(); + assert_eq!(market_after_manual_close.status, MarketStatus::Closed); + assert_eq!(market_after_manual_close.period, MarketPeriod::Timestamp(0..new_end)); }); } #[test] -fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { +fn manually_close_market_fails_if_market_not_in_close_time_frame_list() { // We check that `on_market_close` works correctly even if a block takes much longer than 12sec // to be produced and multiple markets are involved. ExtBuilder::default().build().execute_with(|| { @@ -1429,7 +1445,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { let end: Moment = (5 * MILLISECS_PER_BLOCK).into(); let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -1437,42 +1453,63 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + + // remove market from open time frame list + let range_end_time_frame = crate::Pallet::::calculate_time_frame_of_moment(end); + crate::MarketIdsPerCloseTimeFrame::::remove(range_end_time_frame); + + // This block takes much longer than 12sec, but markets and pools still close correctly. + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2! + + assert_noop!( + PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), + Error::::MarketNotInCloseTimeFrameList + ); + }); +} + +#[test] +fn manually_close_market_fails_if_not_allowed_for_block_based_markets() { + // We check that `on_market_close` works correctly even if a block takes much longer than 12sec + // to be produced and multiple markets are involved. + ExtBuilder::default().build().execute_with(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let category_count = 3; + let end = 5; + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), ALICE, - MarketPeriod::Timestamp(0..end), + MarketPeriod::Block(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize(10 * end); + set_timestamp_for_on_initialize( + end + (crate::MAX_RECOVERY_TIME_FRAMES + 1) * MILLISECS_PER_BLOCK as u64, + ); run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - let market_after_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(0).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); - System::assert_has_event(Event::MarketClosed(0).into()); - - let market_after_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(1).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); - System::assert_has_event(Event::MarketClosed(1).into()); + assert_noop!( + PredictionMarkets::manually_close_market(RuntimeOrigin::signed(ALICE), 0), + Error::::NotAllowedForBlockBasedMarkets + ); }); } @@ -1483,7 +1520,7 @@ fn on_initialize_skips_the_genesis_block() { let end: Moment = (blocks * MILLISECS_PER_BLOCK).into(); ExtBuilder::default().build().execute_with(|| { let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -1491,11 +1528,10 @@ fn on_initialize_skips_the_genesis_block() { MarketPeriod::Timestamp(0..end), get_deadlines(), gen_metadata(50), + MarketCreation::Permissionless, MarketType::Categorical(category_count), Some(MarketDisputeMechanism::SimpleDisputes), - 123, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], + ScoringRule::Lmsr, )); // Blocknumber = 0 @@ -1524,7 +1560,7 @@ fn it_allows_to_buy_a_complete_set() { base_asset, MarketCreation::Permissionless, 0..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // Allows someone to generate a complete set @@ -1539,7 +1575,7 @@ fn it_allows_to_buy_a_complete_set() { assert_eq!(bal, CENT); } - let market_account = MarketCommons::market_account(0); + let market_account = PredictionMarkets::market_account(0); let bal = AssetManager::free_balance(base_asset, &BOB); assert_eq!(bal, 1_000 * BASE - CENT); @@ -1564,7 +1600,7 @@ fn it_does_not_allow_to_buy_a_complete_set_on_pending_advised_market() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_noop!( PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), @@ -1588,7 +1624,7 @@ fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidMarketPeriod, ); @@ -1619,7 +1655,7 @@ fn create_categorical_market_fails_if_market_period_is_invalid( MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidMarketPeriod, ); @@ -1643,7 +1679,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidMarketPeriod, ); @@ -1661,7 +1697,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::InvalidMarketPeriod, ); @@ -1675,7 +1711,7 @@ fn it_does_not_allow_zero_amounts_in_buy_complete_set() { Asset::Ztg, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_noop!( PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 0), @@ -1691,7 +1727,7 @@ fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { base_asset, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_noop!( PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 10000 * BASE), @@ -1706,95 +1742,6 @@ fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { test(Asset::ForeignAsset(100)); }); } -#[test] -fn it_allows_to_deploy_a_pool() { - let test = |base_asset: Asset| { - // Creates a permissionless market. - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - ScoringRule::CPMM, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 100 * BASE)); - - assert_ok!(Balances::transfer( - RuntimeOrigin::signed(BOB), - ::PalletId::get().into_account_truncating(), - 100 * BASE - )); - - assert_ok!(PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(BOB), - 0, - ::MaxSwapFee::get(), - LIQUIDITY, - vec![::MinWeight::get(); 2], - )); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn deploy_swap_pool_for_market_fails_if_market_has_a_pool() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::CPMM, - ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 200 * BASE)); - assert_ok!(PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(BOB), - 0, - ::MaxSwapFee::get(), - LIQUIDITY, - vec![::MinWeight::get(); 2], - )); - assert_noop!( - PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(BOB), - 0, - ::MaxSwapFee::get(), - LIQUIDITY, - vec![::MinWeight::get(); 2], - ), - zrml_market_commons::Error::::PoolAlreadyExists, - ); - }); -} - -#[test] -fn it_does_not_allow_to_deploy_a_pool_on_pending_advised_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::CPMM, - ); - - assert_noop!( - PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(BOB), - 0, - ::MaxSwapFee::get(), - LIQUIDITY, - vec![::MinWeight::get(); 2], - ), - Error::::MarketIsNotActive, - ); - }); -} #[test] fn it_allows_to_sell_a_complete_set() { @@ -1805,7 +1752,7 @@ fn it_allows_to_sell_a_complete_set() { base_asset, MarketCreation::Permissionless, 0..2, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); @@ -1843,7 +1790,7 @@ fn it_does_not_allow_zero_amounts_in_sell_complete_set() { Asset::Ztg, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_noop!( PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 0), @@ -1859,7 +1806,7 @@ fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { base_asset, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT)); assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(0, 1), &BOB, CENT), 0); @@ -1908,7 +1855,7 @@ fn it_allows_to_report_the_outcome_of_a_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); @@ -1954,7 +1901,7 @@ fn report_fails_before_grace_period_is_over() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); run_to_block(end); @@ -1978,7 +1925,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); @@ -2026,7 +1973,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); @@ -2060,7 +2007,7 @@ fn report_fails_on_mismatched_outcome_for_categorical_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); let grace_period = end + market.deadlines.grace_period; @@ -2083,7 +2030,7 @@ fn report_fails_on_out_of_range_outcome_for_categorical_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); let grace_period = end + market.deadlines.grace_period; @@ -2106,7 +2053,7 @@ fn report_fails_on_mismatched_outcome_for_scalar_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); let grace_period = end + market.deadlines.grace_period; @@ -2129,7 +2076,7 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // Run to the end of the trading phase. @@ -2185,7 +2132,7 @@ fn dispute_fails_disputed_already() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); // Run to the end of the trading phase. @@ -2226,7 +2173,7 @@ fn dispute_fails_if_market_not_reported() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); // Run to the end of the trading phase. @@ -2261,7 +2208,7 @@ fn dispute_reserves_dispute_bond() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); // Run to the end of the trading phase. @@ -2300,7 +2247,7 @@ fn schedule_early_close_emits_event() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // just to ensure events are emitted @@ -2334,7 +2281,7 @@ fn dispute_early_close_emits_event() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // just to ensure events are emitted @@ -2361,7 +2308,7 @@ fn reject_early_close_emits_event() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // just to ensure events are emitted @@ -2390,7 +2337,7 @@ fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // just to ensure events are emitted @@ -2418,7 +2365,7 @@ fn reject_early_close_fails_if_state_is_rejected() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // just to ensure events are emitted @@ -2454,7 +2401,7 @@ fn sudo_schedule_early_close_at_block_works() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2525,7 +2472,7 @@ fn sudo_schedule_early_close_at_timeframe_works() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2594,7 +2541,7 @@ fn schedule_early_close_block_fails_if_early_close_request_too_late() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); run_to_block(end - 1); @@ -2626,7 +2573,7 @@ fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); run_to_block(end.saturating_div(MILLISECS_PER_BLOCK.into()) - 1); @@ -2655,7 +2602,7 @@ fn schedule_early_close_as_market_creator_works() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2730,7 +2677,7 @@ fn dispute_early_close_from_market_creator_works() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2798,7 +2745,7 @@ fn settles_early_close_bonds_with_resolution_in_state_disputed() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2852,7 +2799,7 @@ fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creato MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2893,7 +2840,7 @@ fn dispute_early_close_fails_if_scheduled_as_sudo() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2925,7 +2872,7 @@ fn dispute_early_close_fails_if_already_disputed() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -2963,7 +2910,7 @@ fn reject_early_close_resets_to_old_market_period() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -3003,7 +2950,7 @@ fn reject_early_close_settles_bonds() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -3057,7 +3004,7 @@ fn dispute_early_close_fails_if_already_rejected() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -3098,7 +3045,7 @@ fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market_id = 0; @@ -3167,7 +3114,7 @@ fn dispute_updates_market() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); // Run to the end of the trading phase. @@ -3214,7 +3161,7 @@ fn dispute_emits_event() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); // Run to the end of the trading phase. @@ -3247,7 +3194,7 @@ fn it_allows_anyone_to_report_an_unreported_market() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let market = MarketCommons::market(&0).unwrap(); @@ -3284,7 +3231,7 @@ fn it_correctly_resolves_a_market_that_was_reported_on() { Asset::Ztg, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); @@ -3343,7 +3290,7 @@ fn it_resolves_a_disputed_market() { base_asset, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); @@ -3515,7 +3462,7 @@ fn it_resolves_a_disputed_court_market() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -3783,7 +3730,7 @@ fn it_appeals_a_court_market_to_global_dispute() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -3861,8 +3808,6 @@ fn it_appeals_a_court_market_to_global_dispute() { } #[test_case(MarketStatus::Active; "active")] -#[test_case(MarketStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient_subsidy")] #[test_case(MarketStatus::Closed; "closed")] #[test_case(MarketStatus::Proposed; "proposed")] #[test_case(MarketStatus::Resolved; "resolved")] @@ -3873,7 +3818,7 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { Asset::Ztg, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { @@ -3903,7 +3848,7 @@ fn start_global_dispute_fails_on_wrong_mdm() { MarketCreation::Permissionless, MarketType::Categorical(::MaxDisputes::get() + 1), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = MarketCommons::latest_market_id().unwrap(); @@ -3939,7 +3884,7 @@ fn it_allows_to_redeem_shares() { base_asset, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); @@ -4002,224 +3947,6 @@ fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule }); } -#[test] -fn create_market_and_deploy_assets_results_in_expected_balances_and_pool_params() { - let test = |base_asset: Asset| { - let oracle = ALICE; - let period = MarketPeriod::Block(0..42); - let metadata = gen_metadata(42); - let category_count = 4; - let market_type = MarketType::Categorical(category_count); - let swap_fee = ::MaxSwapFee::get(); - let amount = 123 * BASE; - let pool_id = 0; - let weight = ::MinWeight::get(); - let weights = vec![weight; category_count.into()]; - let base_asset_weight = (category_count as u128) * weight; - let total_weight = 2 * base_asset_weight; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - oracle, - period, - get_deadlines(), - metadata, - market_type, - Some(MarketDisputeMechanism::SimpleDisputes), - swap_fee, - amount, - weights, - )); - let market_id = 0; - - let pool_account = Swaps::pool_account_id(&pool_id); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 0), &ALICE), 0); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 1), &ALICE), 0); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 2), &ALICE), 0); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 3), &ALICE), 0); - - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 0), &pool_account), amount); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 1), &pool_account), amount); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 2), &pool_account), amount); - assert_eq!(Tokens::free_balance(Asset::CategoricalOutcome(0, 3), &pool_account), amount); - assert_eq!(AssetManager::free_balance(base_asset, &pool_account), amount); - - let pool = Pools::::get(0).unwrap(); - let assets_expected = vec![ - Asset::CategoricalOutcome(market_id, 0), - Asset::CategoricalOutcome(market_id, 1), - Asset::CategoricalOutcome(market_id, 2), - Asset::CategoricalOutcome(market_id, 3), - base_asset, - ]; - assert_eq!(pool.assets, assets_expected); - assert_eq!(pool.base_asset, base_asset); - assert_eq!(pool.market_id, market_id); - assert_eq!(pool.scoring_rule, ScoringRule::CPMM); - assert_eq!(pool.swap_fee, Some(swap_fee)); - assert_eq!(pool.total_subsidy, None); - assert_eq!(pool.total_subsidy, None); - assert_eq!(pool.total_weight, Some(total_weight)); - let pool_weights = pool.weights.unwrap(); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 0)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 1)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 2)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 3)], weight); - assert_eq!(pool_weights[&base_asset], base_asset_weight); - }; - - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_activates_market_with_sufficient_subsidy() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let min_subsidy = ::MinSubsidy::get(); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(ALICE), 0, min_subsidy)); - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert_eq!(subsidy_queue.len(), 0); - assert_eq!(MarketCommons::market(&0).unwrap().status, MarketStatus::Active); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_blocks_market_with_insufficient_subsidy() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let subsidy = ::MinSubsidy::get() / 3; - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(ALICE), 0, subsidy)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(BOB), 0, subsidy)); - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert_eq!(subsidy_queue.len(), 0); - assert_eq!(MarketCommons::market(&0).unwrap().status, MarketStatus::InsufficientSubsidy); - - // Check that the balances are correctly unreserved. - assert_eq!(AssetManager::reserved_balance(base_asset, &ALICE), 0); - assert_eq!(AssetManager::reserved_balance(base_asset, &BOB), 0); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_keeps_market_in_subsidy_queue_until_end_of_subsidy_phase() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period + 42..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - - // Run to block where 2 markets are ready and process all markets. - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert!(subsidy_queue.len() == 1); - assert!(subsidy_queue[0].market_id == 0); - assert!(MarketCommons::market(&0).unwrap().status == MarketStatus::CollectingSubsidy); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn start_subsidy_creates_pool_and_starts_subsidy() { - let test = |base_asset: Asset| { - // Create advised categorical market using Rikiddo. - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 1337..1338, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let market_id = 0; - let mut market = MarketCommons::market(&market_id).unwrap(); - - // Ensure and set correct market status. - assert_err!( - PredictionMarkets::start_subsidy(&market, market_id), - Error::::MarketIsNotCollectingSubsidy - ); - assert_ok!(MarketCommons::mutate_market(&market_id, |market_inner| { - market_inner.status = MarketStatus::CollectingSubsidy; - market = market_inner.clone(); - Ok(()) - })); - - // Pool was created and market was registered for state transition into active. - assert_ok!(PredictionMarkets::start_subsidy(&market, market_id)); - assert_ok!(MarketCommons::market_pool(&market_id)); - let mut inserted = false; - - for market in crate::MarketsCollectingSubsidy::::get() { - if market.market_id == market_id { - inserted = true; - } - } - - assert!(inserted); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - #[test] fn only_creator_can_edit_market() { ExtBuilder::default().build().execute_with(|| { @@ -4228,7 +3955,7 @@ fn only_creator_can_edit_market() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -4254,7 +3981,7 @@ fn only_creator_can_edit_market() { gen_metadata(2), MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr ), Error::::EditorNotCreator ); @@ -4270,7 +3997,7 @@ fn edit_cycle_for_proposed_markets() { Asset::Ztg, MarketCreation::Advised, 2..4, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -4296,7 +4023,7 @@ fn edit_cycle_for_proposed_markets() { gen_metadata(2), MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); let edited_market = MarketCommons::market(&0).expect("Market not found"); System::assert_last_event(Event::MarketEdited(0, edited_market).into()); @@ -4316,7 +4043,7 @@ fn edit_market_with_foreign_asset() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); // make sure it's in status proposed @@ -4343,7 +4070,7 @@ fn edit_market_with_foreign_asset() { gen_metadata(2), MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr ), Error::::UnregisteredForeignAsset ); @@ -4359,7 +4086,7 @@ fn edit_market_with_foreign_asset() { gen_metadata(2), MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr ), Error::::InvalidBaseAsset, ); @@ -4374,7 +4101,7 @@ fn edit_market_with_foreign_asset() { gen_metadata(2), MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market = MarketCommons::market(&0).unwrap(); assert_eq!(market.base_asset, Asset::ForeignAsset(100)); @@ -4396,7 +4123,7 @@ fn the_entire_market_lifecycle_works_with_timestamps() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); // is ok @@ -4436,7 +4163,7 @@ fn full_scalar_market_lifecycle() { MarketCreation::Permissionless, MarketType::Scalar(10..=30), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); assert_ok!(PredictionMarkets::buy_complete_set( @@ -4599,7 +4326,7 @@ fn reject_market_fails_on_permissionless_market() { Asset::Ztg, MarketCreation::Permissionless, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); let reject_reason: Vec = vec![0; ::MaxRejectReasonLen::get() as usize]; @@ -4618,7 +4345,7 @@ fn reject_market_fails_on_approved_market() { Asset::Ztg, MarketCreation::Advised, 0..1, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); let reject_reason: Vec = @@ -4630,47 +4357,6 @@ fn reject_market_fails_on_approved_market() { }); } -#[test] -fn market_resolve_does_not_hold_liquidity_withdraw() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - )); - deploy_swap_pool(MarketCommons::market(&0).unwrap(), 0).unwrap(); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(ALICE), 0, BASE)); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * BASE)); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(CHARLIE), - 0, - 3 * BASE - )); - let market = MarketCommons::market(&0).unwrap(); - - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(2) - )); - - run_to_block(grace_period + market.deadlines.dispute_duration + 2); - assert_ok!(Swaps::pool_exit(RuntimeOrigin::signed(FRED), 0, BASE * 100, vec![0, 0])); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(BOB), 0)); - }) -} - #[test] fn authorized_correctly_resolves_disputed_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. @@ -4687,7 +4373,7 @@ fn authorized_correctly_resolves_disputed_market() { MarketCreation::Permissionless, MarketType::Categorical(::MinCategories::get()), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); @@ -4833,7 +4519,7 @@ fn approve_market_correctly_unreserves_advisory_bond() { MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; let alice_balance_before = Balances::free_balance(ALICE); @@ -4853,113 +4539,6 @@ fn approve_market_correctly_unreserves_advisory_bond() { }); } -#[test] -fn deploy_swap_pool_correctly_sets_weight_of_base_asset() { - ExtBuilder::default().build().execute_with(|| { - let weights = vec![ - ::MinWeight::get() + 11, - ::MinWeight::get() + 22, - ::MinWeight::get() + 33, - ]; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(0..42), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(3), - Some(MarketDisputeMechanism::SimpleDisputes), - 1, - LIQUIDITY, - weights, - )); - let pool = >::get(0).unwrap(); - let pool_weights = pool.weights.unwrap(); - assert_eq!( - pool_weights[&Asset::Ztg], - 3 * ::MinWeight::get() + 66 - ); - }); -} - -#[test] -fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_short() { - ExtBuilder::default().build().execute_with(|| { - let category_count = 5; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - )); - let amount = 123 * BASE; - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), ALICE, 2 * amount, 0)); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(ALICE), 0, amount)); - // Attempt to create a pool with four weights; but we need five instead (base asset not - // counted). - assert_noop!( - PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(ALICE), - 0, - 1, - amount, - vec![ - ::MinWeight::get(); - (category_count - 1).into() - ], - ), - Error::::WeightsLenMustEqualAssetsLen, - ); - }); -} - -#[test] -fn deploy_swap_pool_for_market_returns_error_if_weights_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - let category_count = 5; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - )); - let amount = 123 * BASE; - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), ALICE, 2 * amount, 0)); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(ALICE), 0, amount)); - // Attempt to create a pool with six weights; but we need five instead (base asset not - // counted). - assert_noop!( - PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(ALICE), - 0, - ::MaxSwapFee::get(), - amount, - vec![ - ::MinWeight::get(); - (category_count + 1).into() - ], - ), - Error::::WeightsLenMustEqualAssetsLen, - ); - }); -} - #[test] fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() { @@ -4978,7 +4557,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let alice_balance_before = Balances::free_balance(ALICE); check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); @@ -5024,7 +4603,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let alice_balance_before = Balances::free_balance(ALICE); check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); @@ -5090,7 +4669,7 @@ fn outsider_reports_wrong_outcome() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let outsider = CHARLIE; @@ -5169,7 +4748,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); let alice_balance_before = Balances::free_balance(ALICE); @@ -5215,7 +4794,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); let alice_balance_before = Balances::free_balance(ALICE); @@ -5262,7 +4841,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let alice_balance_before = Balances::free_balance(ALICE); check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); @@ -5313,7 +4892,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); let alice_balance_before = Balances::free_balance(ALICE); @@ -5365,7 +4944,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let alice_balance_before = Balances::free_balance(ALICE); check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); @@ -5425,7 +5004,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); let alice_balance_before = Balances::free_balance(ALICE); @@ -5483,7 +5062,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let alice_balance_before = Balances::free_balance(ALICE); @@ -5555,7 +5134,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let outsider = CHARLIE; @@ -5622,7 +5201,7 @@ fn report_fails_on_market_state_proposed() { MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); assert_noop!( PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), @@ -5645,58 +5224,8 @@ fn report_fails_on_market_state_closed_for_advised_market() { MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_collecting_subsidy() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(100_000_000..200_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::RikiddoSigmoidFeeMarketEma - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_insufficient_subsidy() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(100_000_000..200_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::RikiddoSigmoidFeeMarketEma + ScoringRule::Lmsr )); - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::InsufficientSubsidy; - Ok(()) - }); assert_noop!( PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), Error::::MarketIsNotClosed, @@ -5718,7 +5247,7 @@ fn report_fails_on_market_state_active() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); assert_noop!( PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), @@ -5727,33 +5256,6 @@ fn report_fails_on_market_state_active() { }); } -#[test] -fn report_fails_on_market_state_suspended() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - )); - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::Suspended; - Ok(()) - }); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - #[test] fn report_fails_on_market_state_resolved() { ExtBuilder::default().build().execute_with(|| { @@ -5768,7 +5270,7 @@ fn report_fails_on_market_state_resolved() { MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); let _ = MarketCommons::mutate_market(&0, |market| { market.status = MarketStatus::Resolved; @@ -5795,7 +5297,7 @@ fn report_fails_if_reporter_is_not_the_oracle() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); let market = MarketCommons::market(&0).unwrap(); set_timestamp_for_on_initialize(100_000_000); @@ -5836,7 +5338,7 @@ fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); }); } @@ -5864,7 +5366,7 @@ fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); }); } @@ -5892,7 +5394,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_blocks() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), crate::Error::::MarketDurationTooLong, ); @@ -5923,7 +5425,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Authorized), - ScoringRule::CPMM, + ScoringRule::Lmsr, ), crate::Error::::MarketDurationTooLong, ); @@ -5932,7 +5434,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { #[test_case( MarketCreation::Advised, - ScoringRule::CPMM, + ScoringRule::Lmsr, MarketStatus::Proposed, MarketBonds { creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), @@ -5945,7 +5447,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { )] #[test_case( MarketCreation::Permissionless, - ScoringRule::CPMM, + ScoringRule::Lmsr, MarketStatus::Active, MarketBonds { creation: Some(Bond::new(ALICE, ::ValidityBond::get())), @@ -6079,194 +5581,6 @@ fn create_market_and_deploy_pool_works() { }); } -#[test] -fn create_cpmm_market_and_deploy_assets_sets_the_correct_market_parameters_and_reserves_the_correct_amount() - { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let oracle = BOB; - let bonds = MarketBonds { - creation: Some(Bond::new(ALICE, ::ValidityBond::get())), - oracle: Some(Bond::new(ALICE, ::OracleBond::get())), - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - }; - let period = MarketPeriod::Block(1..2); - let deadlines = Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }; - let metadata = gen_metadata(0x99); - let MultiHash::Sha3_384(multihash) = metadata; - let category_count = 7; - let market_type = MarketType::Categorical(category_count); - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let creator_fee = Perbill::from_parts(1); - let lp_fee = 0; - let weight = ::MinWeight::get(); - let weights = vec![weight; category_count.into()]; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(creator), - Asset::Ztg, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata, - market_type.clone(), - dispute_mechanism.clone(), - lp_fee, - LIQUIDITY, - weights.clone(), - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.creator, creator); - assert_eq!(market.creation, MarketCreation::Permissionless); - assert_eq!(market.creator_fee, creator_fee); - assert_eq!(market.oracle, oracle); - assert_eq!(market.metadata, multihash); - assert_eq!(market.market_type, market_type); - assert_eq!(market.period, period); - assert_eq!(market.deadlines, deadlines); - assert_eq!(market.scoring_rule, ScoringRule::CPMM); - assert_eq!(market.status, MarketStatus::Active); - assert_eq!(market.report, None); - assert_eq!(market.resolved_outcome, None); - assert_eq!(market.dispute_mechanism, dispute_mechanism); - assert_eq!(market.bonds, bonds); - }); -} - -#[test] -fn create_market_and_deploy_pool_errors() { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let oracle = BOB; - let period = MarketPeriod::Block(1..2); - let deadlines = Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }; - let metadata = gen_metadata(0x99); - let market_type = MarketType::Categorical(7); - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let amount = 1234567890; - let swap_prices = vec![50 * CENT, 50 * CENT]; - let swap_fee = CENT; - DeployPoolMock::return_error(); - assert_noop!( - PredictionMarkets::create_market_and_deploy_pool( - RuntimeOrigin::signed(creator), - Asset::Ztg, - Perbill::zero(), - oracle, - period.clone(), - deadlines, - metadata, - market_type.clone(), - dispute_mechanism.clone(), - amount, - swap_prices.clone(), - swap_fee, - ), - DispatchError::Other("neo-swaps"), - ); - }); -} - -#[test] -fn create_market_functions_respect_fee_boundaries() { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let oracle = BOB; - let base_asset = Asset::Ztg; - let mut creator_fee = ::MaxCreatorFee::get(); - let period = MarketPeriod::Block(1..2); - let deadlines = Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }; - let metadata = gen_metadata(0x99); - let category_count = 3; - let weight = ::MinWeight::get(); - let weights = vec![weight; category_count.into()]; - let scoring_rule = ScoringRule::CPMM; - let market_type = MarketType::Categorical(category_count); - let creation_type = MarketCreation::Permissionless; - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let lp_fee = 0; - - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(creator), - base_asset, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata.clone(), - creation_type.clone(), - market_type.clone(), - dispute_mechanism.clone(), - scoring_rule, - )); - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(creator), - base_asset, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata.clone(), - market_type.clone(), - dispute_mechanism.clone(), - lp_fee, - LIQUIDITY, - weights.clone(), - )); - - creator_fee = creator_fee + Perbill::from_parts(1); - - assert_err!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(creator), - base_asset, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata.clone(), - creation_type.clone(), - market_type.clone(), - dispute_mechanism.clone(), - scoring_rule, - ), - Error::::FeeTooHigh - ); - assert_err!( - PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(creator), - base_asset, - creator_fee, - oracle, - period, - deadlines, - metadata, - market_type, - dispute_mechanism, - lp_fee, - LIQUIDITY, - weights, - ), - Error::::FeeTooHigh - ); - }); -} - #[test] fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { ExtBuilder::default().build().execute_with(|| { @@ -6286,7 +5600,7 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { MarketCreation::Permissionless, MarketType::Categorical(3), None, - ScoringRule::CPMM, + ScoringRule::Lmsr, ), Error::::NonZeroDisputePeriodOnTrustedMarket ); @@ -6312,7 +5626,7 @@ fn trusted_market_complete_lifecycle() { MarketCreation::Permissionless, MarketType::Categorical(3), None, - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; assert_ok!(PredictionMarkets::buy_complete_set( @@ -6362,7 +5676,7 @@ fn close_trusted_market_works() { MarketCreation::Permissionless, MarketType::Categorical(3), None, - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -6417,7 +5731,7 @@ fn close_trusted_market_fails_if_not_trusted() { MarketCreation::Permissionless, MarketType::Categorical(3), Some(MarketDisputeMechanism::Court), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -6439,14 +5753,11 @@ fn close_trusted_market_fails_if_not_trusted() { }); } -#[test_case(MarketStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient_subsidy")] #[test_case(MarketStatus::Closed; "closed")] #[test_case(MarketStatus::Proposed; "proposed")] #[test_case(MarketStatus::Resolved; "resolved")] #[test_case(MarketStatus::Disputed; "disputed")] #[test_case(MarketStatus::Reported; "report")] -#[test_case(MarketStatus::Suspended; "suspended")] fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { let end = 10; @@ -6466,7 +5777,7 @@ fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { MarketCreation::Permissionless, MarketType::Categorical(3), None, - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -6485,26 +5796,6 @@ fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { }); } -fn deploy_swap_pool( - market: Market>, - market_id: u128, -) -> DispatchResultWithPostInfo { - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), 0, 100 * BASE)); - assert_ok!(Balances::transfer( - RuntimeOrigin::signed(FRED), - ::PalletId::get().into_account_truncating(), - 100 * BASE - )); - let outcome_assets_len = PredictionMarkets::outcome_assets(market_id, &market).len(); - PredictionMarkets::deploy_swap_pool_for_market( - RuntimeOrigin::signed(FRED), - 0, - ::MaxSwapFee::get(), - LIQUIDITY, - vec![::MinWeight::get(); outcome_assets_len], - ) -} - // Common code of `scalar_market_correctly_resolves_*` fn scalar_market_correctly_resolves_common(base_asset: Asset, reported_value: u128) { let end = 100; @@ -6512,7 +5803,7 @@ fn scalar_market_correctly_resolves_common(base_asset: Asset, reported base_asset, MarketCreation::Permissionless, 0..end, - ScoringRule::CPMM, + ScoringRule::Lmsr, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); assert_ok!(Tokens::transfer( diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index f9f8c7872..2c9ae0bc8 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -49,7 +49,7 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_prediction_markets (automatically generated) pub trait WeightInfoZeitgeist { - fn admin_move_market_to_closed(o: u32, c: u32) -> Weight; + fn admin_move_market_to_closed(c: u32) -> Weight; fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight; fn admin_move_market_to_resolved_categorical_reported(r: u32) -> Weight; fn admin_move_market_to_resolved_scalar_disputed(r: u32) -> Weight; @@ -59,8 +59,7 @@ pub trait WeightInfoZeitgeist { fn buy_complete_set(a: u32) -> Weight; fn create_market(m: u32) -> Weight; fn edit_market(m: u32) -> Weight; - fn deploy_swap_pool_for_market_future_pool(a: u32, o: u32) -> Weight; - fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight; + fn deploy_swap_pool_for_market(a: u32) -> Weight; fn start_global_dispute(m: u32, n: u32) -> Weight; fn dispute_authorized() -> Weight; fn handle_expired_advised_market() -> Weight; @@ -72,7 +71,7 @@ pub trait WeightInfoZeitgeist { fn process_subsidy_collecting_markets_raw(a: u32) -> Weight; fn redeem_shares_categorical() -> Weight; fn redeem_shares_scalar() -> Weight; - fn reject_market(c: u32, o: u32, r: u32) -> Weight; + fn reject_market(c: u32, r: u32) -> Weight; fn report_market_with_dispute_mechanism(m: u32) -> Weight; fn report_trusted_market() -> Weight; fn sell_complete_set(a: u32) -> Weight; @@ -87,7 +86,8 @@ pub trait WeightInfoZeitgeist { fn dispute_early_close(o: u32, n: u32) -> Weight; fn reject_early_close_after_authority(o: u32, n: u32) -> Weight; fn reject_early_close_after_dispute() -> Weight; - fn close_trusted_market(o: u32, c: u32) -> Weight; + fn close_trusted_market(c: u32) -> Weight; + fn manually_close_market(o: u32) -> Weight; } /// Weight functions for zrml_prediction_markets (automatically generated) @@ -105,14 +105,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) /// The range of component `o` is `[0, 63]`. /// The range of component `c` is `[0, 63]`. - fn admin_move_market_to_closed(o: u32, c: u32) -> Weight { + fn admin_move_market_to_closed(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `792 + o * (16 ±0) + c * (16 ±0)` // Estimated: `13229` // Minimum execution time: 54_250 nanoseconds. Weight::from_parts(59_415_334, 13229) - // Standard Error: 2_729 - .saturating_add(Weight::from_parts(17_380, 0).saturating_mul(o.into())) // Standard Error: 2_729 .saturating_add(Weight::from_parts(62_317, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(5)) @@ -318,46 +316,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, 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 MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: MarketCommons MarketPool (r:1 w:1) /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) /// Storage: Swaps Pools (r:0 w:1) /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) /// The range of component `a` is `[2, 64]`. - /// The range of component `o` is `[0, 63]`. - fn deploy_swap_pool_for_market_future_pool(a: u32, _o: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `1246 + a * (118 ±0) + o * (16 ±0)` - // Estimated: `17938 + a * (5196 ±0)` - // Minimum execution time: 182_860 nanoseconds. - Weight::from_parts(136_165_903, 17938) - // Standard Error: 43_594 - .saturating_add(Weight::from_parts(34_079_556, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(7)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 5196).saturating_mul(a.into())) - } - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: Swaps NextPoolId (r:1 w:1) - /// Proof: Swaps NextPoolId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:129 w:129) - /// 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) - /// Storage: Tokens TotalIssuance (r:1 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, 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: MarketCommons MarketPool (r:1 w:1) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Swaps Pools (r:0 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// The range of component `a` is `[2, 64]`. - fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight { + fn deploy_swap_pool_for_market(a: u32) -> Weight { // Proof Size summary in bytes: // Measured: `1112 + a * (119 ±0)` // Estimated: `14413 + a * (5196 ±0)` @@ -594,7 +558,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `c` is `[0, 63]`. /// The range of component `o` is `[0, 63]`. /// The range of component `r` is `[0, 1024]`. - fn reject_market(c: u32, o: u32, r: u32) -> Weight { + fn reject_market(c: u32, r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `723 + c * (16 ±0) + o * (16 ±0)` // Estimated: `13927` @@ -602,8 +566,6 @@ impl WeightInfoZeitgeist for WeightInfo { Weight::from_parts(102_137_848, 13927) // Standard Error: 4_276 .saturating_add(Weight::from_parts(41_013, 0).saturating_mul(c.into())) - // Standard Error: 4_276 - .saturating_add(Weight::from_parts(18_082, 0).saturating_mul(o.into())) // Standard Error: 262 .saturating_add(Weight::from_parts(2_551, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) @@ -904,14 +866,32 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - fn close_trusted_market(o: u32, c: u32) -> Weight { + /// Storage: MarketCommons Markets (r:1 w:1) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, 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 MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: MarketCommons MarketPool (r:1 w:0) + /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// The range of component `o` is `[1, 63]`. + fn manually_close_market(o: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `2255` + // Estimated: `9568` + // Minimum execution time: 29_000 nanoseconds. + Weight::from_parts(31_439_103, 9568) + // Standard Error: 431 + .saturating_add(Weight::from_parts(756, 0).saturating_mul(o.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn close_trusted_market(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `792 + o * (16 ±0) + c * (16 ±0)` // Estimated: `13229` // Minimum execution time: 54_250 nanoseconds. Weight::from_parts(59_415_334, 13229) - // Standard Error: 2_729 - .saturating_add(Weight::from_parts(17_380, 0).saturating_mul(o.into())) // Standard Error: 2_729 .saturating_add(Weight::from_parts(62_317, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(5)) diff --git a/zrml/rikiddo/Cargo.toml b/zrml/rikiddo/Cargo.toml index e3158e353..8859b1f7e 100644 --- a/zrml/rikiddo/Cargo.toml +++ b/zrml/rikiddo/Cargo.toml @@ -44,4 +44,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-rikiddo" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/simple-disputes/Cargo.toml b/zrml/simple-disputes/Cargo.toml index 1054899b7..e85f005a0 100644 --- a/zrml/simple-disputes/Cargo.toml +++ b/zrml/simple-disputes/Cargo.toml @@ -42,4 +42,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-simple-disputes" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index a29ce31ab..c0466b4c7 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -576,7 +576,7 @@ where by: T::PalletId::get().into_account_truncating(), }), resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: zeitgeist_primitives::types::MarketStatus::Disputed, bonds: MarketBonds::default(), early_close: None, diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index ef8643982..468589c52 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -31,7 +31,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxDisputes, MaxReserves, - MinimumPeriod, OutcomeBond, OutcomeFactor, PmPalletId, SimpleDisputesPalletId, BASE, + MinimumPeriod, OutcomeBond, OutcomeFactor, SimpleDisputesPalletId, BASE, }, traits::DisputeResolutionApi, types::{ @@ -185,7 +185,6 @@ impl orml_tokens::Config for Runtime { impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index f217b9ba7..03ee1e578 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -45,7 +45,7 @@ const DEFAULT_MARKET: MarketOf = Market { deadlines: Deadlines { grace_period: 1_u64, oracle_duration: 1_u64, dispute_duration: 1_u64 }, report: None, resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: MarketStatus::Disputed, bonds: MarketBonds { creation: None, diff --git a/zrml/styx/Cargo.toml b/zrml/styx/Cargo.toml index 44edbd03f..95889877d 100644 --- a/zrml/styx/Cargo.toml +++ b/zrml/styx/Cargo.toml @@ -37,4 +37,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-styx" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 298a9fe34..80abf4639 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -5,12 +5,11 @@ frame-system = { workspace = true } orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"] } sp-arithmetic = { workspace = true } sp-runtime = { workspace = true } -substrate-fixed = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-liquidity-mining = { workspace = true } -zrml-rikiddo = { workspace = true } # Mock @@ -27,6 +26,7 @@ zrml-swaps-runtime-api = { workspace = true, optional = true } [dev-dependencies] more-asserts = { workspace = true } test-case = { workspace = true } +zeitgeist-macros = { workspace = true } zrml-swaps = { workspace = true, features = ["mock"] } [features] @@ -47,6 +47,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "zeitgeist-primitives/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", @@ -55,10 +56,8 @@ std = [ "orml-traits/std", "parity-scale-codec/std", "sp-runtime/std", - "substrate-fixed/std", "zeitgeist-primitives/std", "zrml-liquidity-mining/std", - "zrml-rikiddo/std", ] try-runtime = [ "frame-support/try-runtime", @@ -68,4 +67,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/swaps/fuzz/create_pool.rs b/zrml/swaps/fuzz/create_pool.rs index 0438e0b8a..932212420 100644 --- a/zrml/swaps/fuzz/create_pool.rs +++ b/zrml/swaps/fuzz/create_pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -19,9 +19,9 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use zeitgeist_primitives::{traits::Swaps as SwapsTrait, types::ScoringRule}; +use zeitgeist_primitives::traits::Swaps as SwapsTrait; -use zrml_swaps::mock::{ExtBuilder, Swaps, DEFAULT_MARKET_ID}; +use zrml_swaps::mock::{ExtBuilder, Swaps}; mod utils; use utils::{construct_asset, PoolCreationData}; @@ -32,9 +32,6 @@ fuzz_target!(|data: PoolCreationData| { let _ = Swaps::create_pool( data.origin, data.assets.into_iter().map(construct_asset).collect(), - construct_asset(data.base_asset), - DEFAULT_MARKET_ID, - ScoringRule::CPMM, data.swap_fee, data.amount, data.weights, diff --git a/zrml/swaps/fuzz/utils.rs b/zrml/swaps/fuzz/utils.rs index 6c4272ecf..eaf3f17fe 100644 --- a/zrml/swaps/fuzz/utils.rs +++ b/zrml/swaps/fuzz/utils.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -29,9 +29,9 @@ use zeitgeist_primitives::{ MaxAssets, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, BASE, CENT, }, traits::Swaps as SwapsTrait, - types::{Asset, PoolId, ScalarPosition, ScoringRule, SerdeWrapper}, + types::{Asset, PoolId, ScalarPosition, SerdeWrapper}, }; -use zrml_swaps::mock::{Swaps, DEFAULT_MARKET_ID}; +use zrml_swaps::mock::Swaps; pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { let (module, seed0, seed1) = seed; @@ -48,8 +48,8 @@ pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { } } -fn construct_swap_fee(swap_fee: u128) -> Option { - Some(swap_fee % MaxSwapFee::get()) +fn construct_swap_fee(swap_fee: u128) -> u128 { + swap_fee % MaxSwapFee::get() } #[derive(Debug)] @@ -69,12 +69,9 @@ impl ValidPoolData { match Swaps::create_pool( self.origin, self.assets.into_iter().map(construct_asset).collect(), - construct_asset(self.base_asset), - DEFAULT_MARKET_ID, - ScoringRule::CPMM, construct_swap_fee(self.swap_fee), - Some(self.amount), - Some(self.weights), + self.amount, + self.weights, ) { Ok(pool_id) => pool_id, Err(e) => panic!("Pool creation failed unexpectedly. Error: {:?}", e), @@ -208,7 +205,7 @@ pub struct PoolCreationData { pub origin: u128, pub assets: Vec<(u8, u128, u16)>, pub base_asset: (u8, u128, u16), - pub swap_fee: Option, - pub amount: Option, - pub weights: Option>, + pub swap_fee: u128, + pub amount: u128, + pub weights: Vec, } diff --git a/zrml/swaps/rpc/Cargo.toml b/zrml/swaps/rpc/Cargo.toml index eeb1d9331..f162e166d 100644 --- a/zrml/swaps/rpc/Cargo.toml +++ b/zrml/swaps/rpc/Cargo.toml @@ -11,4 +11,4 @@ zrml-swaps-runtime-api = { workspace = true, features = ["default"] } authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps-rpc" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/swaps/rpc/src/lib.rs b/zrml/swaps/rpc/src/lib.rs index 403d1da39..4cad2388f 100644 --- a/zrml/swaps/rpc/src/lib.rs +++ b/zrml/swaps/rpc/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -34,7 +34,6 @@ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, MaybeDisplay, MaybeFromStr, NumberFor}, }; -use std::collections::BTreeMap; use zeitgeist_primitives::types::{Asset, SerdeWrapper}; pub use zrml_swaps_runtime_api::SwapsApi as SwapsRuntimeApi; @@ -77,14 +76,6 @@ where with_fees: bool, blocks: Vec, ) -> RpcResult>>; - - #[method(name = "swaps_getAllSpotPrices")] - async fn get_all_spot_prices( - &self, - pool_id: PoolId, - with_fees: bool, - blocks: Vec, - ) -> RpcResult, Balance)>>>; } /// A struct that implements the [`SwapsApi`]. @@ -216,38 +207,4 @@ where }) .collect() } - - async fn get_all_spot_prices( - &self, - pool_id: PoolId, - with_fees: bool, - blocks: Vec>, - ) -> RpcResult, Vec<(Asset, Balance)>>> { - let api = self.client.runtime_api(); - Ok(blocks - .into_iter() - .map( - |block| -> Result<(NumberFor, Vec<(Asset, Balance)>), CallError> { - let hash = BlockId::number(block); - let prices: Vec<(Asset, Balance)> = api - .get_all_spot_prices(&hash, &pool_id, with_fees) - .map_err(|e| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Unable to get_all_spot_prices: ApiError.", - Some(format!("{:?}", e)), - )) - })? - .map_err(|e| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Unable to get_all_spot_prices: DispatchError.", - Some(format!("{:?}", e)), - )) - })?; - Ok((block, prices)) - }, - ) - .collect::, _>>()?) - } } diff --git a/zrml/swaps/runtime-api/Cargo.toml b/zrml/swaps/runtime-api/Cargo.toml index 63d545d46..00c0c3276 100644 --- a/zrml/swaps/runtime-api/Cargo.toml +++ b/zrml/swaps/runtime-api/Cargo.toml @@ -18,4 +18,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps-runtime-api" -version = "0.4.2" +version = "0.4.3" diff --git a/zrml/swaps/runtime-api/src/lib.rs b/zrml/swaps/runtime-api/src/lib.rs index 437b0a53d..b76ca8038 100644 --- a/zrml/swaps/runtime-api/src/lib.rs +++ b/zrml/swaps/runtime-api/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -19,13 +19,9 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::{Codec, Decode, MaxEncodedLen}; -use sp_runtime::{ - traits::{MaybeDisplay, MaybeFromStr}, - DispatchError, -}; -use sp_std::vec::Vec; -use zeitgeist_primitives::types::{Asset, Pool, SerdeWrapper}; +use parity_scale_codec::{Codec, MaxEncodedLen}; +use sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; +use zeitgeist_primitives::types::{Asset, SerdeWrapper}; sp_api::decl_runtime_apis! { pub trait SwapsApi where @@ -33,19 +29,16 @@ sp_api::decl_runtime_apis! { AccountId: Codec, Balance: Codec + MaybeDisplay + MaybeFromStr + MaxEncodedLen, MarketId: Codec + MaxEncodedLen, - Pool: Decode, { fn pool_shares_id(pool_id: PoolId) -> Asset>; + fn pool_account_id(pool_id: &PoolId) -> AccountId; + fn get_spot_price( pool_id: &PoolId, asset_in: &Asset, asset_out: &Asset, with_fees: bool, ) -> SerdeWrapper; - fn get_all_spot_prices( - pool_id: &PoolId, - with_fees: bool - ) -> Result, Balance)>, DispatchError>; } } diff --git a/zrml/swaps/src/arbitrage.rs b/zrml/swaps/src/arbitrage.rs deleted file mode 100644 index 304d8ff0a..000000000 --- a/zrml/swaps/src/arbitrage.rs +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2022-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 . - -//! Traits and implementation for automatic arbitrage of CPMM pools. - -use crate::{math::calc_spot_price, root::calc_preimage}; -use alloc::collections::btree_map::BTreeMap; -use parity_scale_codec::MaxEncodedLen; -use sp_runtime::{ - traits::{AtLeast32Bit, AtLeast32BitUnsigned}, - SaturatedConversion, -}; -use zeitgeist_primitives::{ - constants::BASE, - types::{Asset, Pool}, -}; - -type Fixed = u128; - -const TOLERANCE: Fixed = BASE / 1_000; // 0.001 - -/// This trait implements approximations for on-chain arbitrage of CPMM pools using bisection. -/// -/// All calculations depend on on-chain, which are passed using the `balances` parameter. -pub(crate) trait ArbitrageForCpmm -where - MarketId: MaxEncodedLen, -{ - /// Calculate the total spot price (sum of all spot prices of outcome tokens). - /// - /// Arguments: - /// - /// * `balances`: Maps assets to their current balance. - fn calc_total_spot_price( - &self, - balances: &BTreeMap, Balance>, - ) -> Result; - - /// Approximate the amount to mint/sell to move the total spot price close to `1`. - /// - /// Arguments: - /// - /// * `balances`: Maps assets to their current balance. - /// * `max_iterations`: Maximum number of iterations allowed in the bisection method. - fn calc_arbitrage_amount_mint_sell( - &self, - balances: &BTreeMap, Balance>, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str>; - - /// Approximate the amount to buy/burn to move the total spot price close to `1`. - /// - /// Arguments: - /// - /// * `balances`: Maps assets to their current balance. - /// * `max_iterations`: Maximum number of iterations allowed in the bisection method. - fn calc_arbitrage_amount_buy_burn( - &self, - balances: &BTreeMap, Balance>, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str>; -} - -impl ArbitrageForCpmm for Pool -where - Balance: AtLeast32BitUnsigned + Copy, - MarketId: MaxEncodedLen + AtLeast32Bit + Copy, - Pool: ArbitrageForCpmmHelper, -{ - fn calc_total_spot_price( - &self, - balances: &BTreeMap, Balance>, - ) -> Result { - let weights = self.weights.as_ref().ok_or("Unexpectedly found no weights in pool.")?; - let balance_in = balances - .get(&self.base_asset) - .cloned() - .ok_or("Base asset balance missing")? - .saturated_into(); - let weight_in = weights - .get(&self.base_asset) - .cloned() - .ok_or("Unexpectedly found no weight for base asset")?; - let mut result: Fixed = 0; - for asset in self.assets.iter().filter(|a| **a != self.base_asset) { - let balance_out: Fixed = balances - .get(asset) - .cloned() - .ok_or("Asset balance missing from BTreeMap")? - .saturated_into(); - let weight_out = weights - .get(asset) - .cloned() - .ok_or("Unexpectedly found no weight for outcome asset.")?; - // We're deliberately _not_ using the pool's swap fee! - result = result.saturating_add(calc_spot_price( - balance_in, - weight_in, - balance_out, - weight_out, - 0, - )?); - } - Ok(result) - } - - fn calc_arbitrage_amount_mint_sell( - &self, - balances: &BTreeMap, Balance>, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str> { - self.calc_arbitrage_amount_common(balances, |a| *a == self.base_asset, max_iterations) - } - - fn calc_arbitrage_amount_buy_burn( - &self, - balances: &BTreeMap, Balance>, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str> { - self.calc_arbitrage_amount_common(balances, |a| *a != self.base_asset, max_iterations) - } -} - -trait ArbitrageForCpmmHelper -where - MarketId: MaxEncodedLen, -{ - /// Common code of `Arbitrage::calc_arbitrage_amount_*`. - /// - /// The only difference between the two `calc_arbitrage_amount_*` functions is that they - /// increase/decrease different assets. The `cond` parameter is `true` on assets that must be - /// decreased, `false` otherwise. - /// - /// # Arguments - /// - /// - `balances`: Maps assets to their balance in the pool. - /// - `cond`: Returns `true` if the asset's balance must be decreased, `false` otherwise. - /// - `max_iterations`: The maximum number of iterations to use in the bisection method. - fn calc_arbitrage_amount_common( - &self, - balances: &BTreeMap, Balance>, - cond: F, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str> - where - F: Fn(&Asset) -> bool; -} - -impl ArbitrageForCpmmHelper for Pool -where - Balance: AtLeast32BitUnsigned + Copy, - MarketId: MaxEncodedLen + AtLeast32Bit + Copy, -{ - fn calc_arbitrage_amount_common( - &self, - balances: &BTreeMap, Balance>, - cond: F, - max_iterations: usize, - ) -> Result<(Balance, usize), &'static str> - where - F: Fn(&Asset) -> bool, - { - let smallest_balance: Fixed = balances - .iter() - .filter_map(|(a, b)| if cond(a) { Some(b) } else { None }) - .min() - .cloned() - .ok_or("calc_arbitrage_amount_common: Cannot find any matching assets")? - .saturated_into(); - let calc_total_spot_price_after_arbitrage = |amount: Fixed| -> Result { - let shifted_balances = balances - .iter() - .map(|(asset, bal)| { - if cond(asset) { - (*asset, bal.saturating_sub(amount.saturated_into())) - } else { - (*asset, bal.saturating_add(amount.saturated_into())) - } - }) - .collect::>(); - self.calc_total_spot_price(&shifted_balances) - }; - // We use `smallest_balance / 2` so we never reduce a balance to zero. - let (preimage, iterations) = calc_preimage::( - calc_total_spot_price_after_arbitrage, - BASE, - 0, - smallest_balance / 2, - max_iterations, - TOLERANCE, - )?; - Ok((preimage.saturated_into(), iterations)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - use zeitgeist_primitives::{ - constants::CENT, - types::{Asset, PoolStatus, ScoringRule}, - }; - - type MarketId = u128; - - const _1: u128 = BASE; - const _2: u128 = 2 * BASE; - const _3: u128 = 3 * BASE; - const _4: u128 = 4 * BASE; - const _5: u128 = 5 * BASE; - const _6: u128 = 6 * BASE; - const _7: u128 = 7 * BASE; - const _8: u128 = 8 * BASE; - const _9: u128 = 9 * BASE; - const _10: u128 = 10 * BASE; - const _100: u128 = 100 * BASE; - const _125: u128 = 125 * BASE; - const _150: u128 = 150 * BASE; - const _1_4: u128 = BASE / 4; - const _1_10: u128 = BASE / 10; - const _1_1000: u128 = BASE / 1_000; - - // Macro for comparing fixed point u128. - #[allow(unused_macros)] - macro_rules! assert_approx { - ($left:expr, $right:expr, $precision:expr $(,)?) => { - match (&$left, &$right, &$precision) { - (left_val, right_val, precision_val) => { - let diff = if *left_val > *right_val { - *left_val - *right_val - } else { - *right_val - *left_val - }; - if diff > $precision { - panic!("{} is not {}-close to {}", *left_val, *precision_val, *right_val); - } - } - } - }; - } - - // Some of these tests are taken from our Python Balancer playground, some as snapshots from - // the Zeitgeist chain. - #[test_case(vec![_3, _1, _1, _1], vec![_1, _1, _1, _1], _1 - 1)] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _100], _1)] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _150], 8_333_333_333)] - #[test_case(vec![_7, _3, _4], vec![_100, _100, _150], 8_095_238_096)] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _150], 7_111_111_111)] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _100], 9_333_333_334)] - #[test_case(vec![_6, _3, _3], vec![_125, _100, _100], 12_500_000_000)] - #[test_case(vec![_6, _3, _3], vec![_125, _100, _150], 10_416_666_667)] - #[test_case(vec![_7, _3, _4], vec![_125, _100, _150], 10_119_047_619)] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _150], 8_888_888_889)] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _100], 11_666_666_666)] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _100], 15_000_000_000)] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _150], 12_500_000_000)] - #[test_case(vec![_7, _3, _4], vec![_150, _100, _150], 12_142_857_143)] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _150], 10_666_666_667)] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _100], 14_000_000_000)] - #[test_case( - vec![_4, _1, _1, _1, _1], - vec![ - 5_371_011_843_167, - 1_697_583_448_000, - 5_399_900_980_000, - 7_370_000_000_000, - 7_367_296_940_400 - ], - 14_040_918_578 - )] - fn calc_total_spot_price_calculates_correct_values( - weights: Vec, - balances: Vec, - expected: u128, - ) { - let pool = construct_pool(None, weights.clone()); - let balances = collect_balances_into_map(pool.assets.clone(), balances); - assert_eq!(pool.calc_total_spot_price(&balances).unwrap(), expected); - // Test that swap fees make no difference! - let pool = construct_pool(Some(_1_10), weights); - assert_eq!(pool.calc_total_spot_price(&balances).unwrap(), expected); - } - - #[test] - fn calc_total_spot_price_errors_if_asset_balance_is_missing() { - let pool = construct_pool(None, vec![_3, _1, _1, _1]); - let balances = collect_balances_into_map(pool.assets[..2].into(), vec![_1; 3]); - assert_eq!( - pool.calc_total_spot_price(&balances), - Err("Asset balance missing from BTreeMap"), - ); - } - - // The first case shouldn't be validated, as it is an example where the algorithm overshoots the - // target: - // #[test_case(vec![_3, _1, _1, _1], vec![_1, _1, _1, _1], 0)] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _100], 0)] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _150], 97_877_502_440)] - #[test_case(vec![_7, _3, _4], vec![_100, _100, _150], 115_013_122_558)] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _150], 202_188_491_820)] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _100], 35_530_090_331)] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _150], 77_810_287_474)] - // 10_000_000_000 = 1 * BASE - // 1_000_000_000_000 = 100 * BASE - fn calc_arbitrage_amount_buy_burn_calculates_correct_results( - weights: Vec, - balances: Vec, - expected: u128, - ) { - let pool = construct_pool(None, weights); - let balances = collect_balances_into_map(pool.assets.clone(), balances); - let (amount, _) = pool.calc_arbitrage_amount_buy_burn(&balances, 30).unwrap(); - assert_eq!(amount, expected); - } - - #[test_case(vec![_3, _1, _1, _1], vec![_1, _1, _1, _1])] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _100])] - #[test_case(vec![_6, _3, _3], vec![_100, _100, _150])] - #[test_case(vec![_7, _3, _4], vec![_100, _100, _150])] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _150])] - #[test_case(vec![_9, _3, _6], vec![_100, _125, _100])] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _150])] - fn calc_arbitrage_amount_buy_burn_reconfigures_pool_correctly( - weights: Vec, - balances: Vec, - ) { - let pool = construct_pool(None, weights); - let mut balances = collect_balances_into_map(pool.assets.clone(), balances); - let (amount, _) = pool.calc_arbitrage_amount_buy_burn(&balances, 30).unwrap(); - *balances.get_mut(&pool.assets[0]).unwrap() += amount; - for asset in &pool.assets[1..] { - *balances.get_mut(asset).unwrap() -= amount; - } - // It's an iffy question what to use as `precision` parameter here. The precision depends - // on the derivative of the total spot price function `f` in ZIP-1 on the interval `[0, - // amount]`. - assert_approx!(pool.calc_total_spot_price(&balances).unwrap(), _1, CENT); - } - - #[test] - fn calc_arbitrage_amount_buy_burn_errors_if_asset_balance_is_missing() { - let pool = construct_pool(None, vec![_3, _1, _1, _1]); - let balances = collect_balances_into_map(pool.assets[..2].into(), vec![_1; 3]); - assert_eq!( - pool.calc_arbitrage_amount_buy_burn(&balances, usize::MAX), - Err("Asset balance missing from BTreeMap"), - ); - } - - #[test_case(vec![_6, _3, _3], vec![_125, _100, _100], 124_998_092_650)] - #[test_case(vec![_6, _3, _3], vec![_125, _100, _150], 24_518_966_674)] - #[test_case(vec![_7, _3, _4], vec![_125, _100, _150], 7_200_241_088)] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _100], 88_872_909_544)] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _100], 250_001_907_347)] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _150], 147_359_848_021)] - #[test_case(vec![_7, _3, _4], vec![_150, _100, _150], 129_919_052_122)] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _150], 46_697_616_576)] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _100], 213_369_369_505)] - fn calc_arbitrage_amount_mint_sell_calculates_correct_results( - weights: Vec, - balances: Vec, - expected: u128, - ) { - let pool = construct_pool(None, weights); - let balances = collect_balances_into_map(pool.assets.clone(), balances); - let (amount, _) = pool.calc_arbitrage_amount_mint_sell(&balances, 30).unwrap(); - assert_eq!(amount, expected); - } - - #[test_case(vec![_6, _3, _3], vec![_125, _100, _100])] - #[test_case(vec![_6, _3, _3], vec![_125, _100, _150])] - #[test_case(vec![_7, _3, _4], vec![_125, _100, _150])] - #[test_case(vec![_9, _3, _6], vec![_125, _125, _100])] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _100])] - #[test_case(vec![_6, _3, _3], vec![_150, _100, _150])] - #[test_case(vec![_7, _3, _4], vec![_150, _100, _150])] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _150])] - #[test_case(vec![_9, _3, _6], vec![_150, _125, _100])] - fn calc_arbitrage_amount_mint_sell_reconfigures_pool_correctly( - weights: Vec, - balances: Vec, - ) { - let pool = construct_pool(None, weights); - let mut balances = collect_balances_into_map(pool.assets.clone(), balances); - let (amount, _) = pool.calc_arbitrage_amount_mint_sell(&balances, 30).unwrap(); - *balances.get_mut(&pool.assets[0]).unwrap() -= amount; - for asset in &pool.assets[1..] { - *balances.get_mut(asset).unwrap() += amount; - } - // It's an iffy question what to use as `precision` parameter here. The precision depends - // on the derivative of the total spot price function `f` in ZIP-1 on the interval `[0, - // amount]`. - assert_approx!(pool.calc_total_spot_price(&balances).unwrap(), _1, CENT); - } - - #[test] - fn calc_arbitrage_amount_mint_sell_errors_if_asset_balance_is_missing() { - let pool = construct_pool(None, vec![_3, _1, _1, _1]); - let balances = collect_balances_into_map(pool.assets[..2].into(), vec![_1; 3]); - assert_eq!( - pool.calc_arbitrage_amount_mint_sell(&balances, usize::MAX), - Err("Asset balance missing from BTreeMap"), - ); - } - - fn construct_pool( - swap_fee: Option, - weights: Vec, - ) -> Pool { - let fake_market_id = 0; - let assets = (0..weights.len()) - .map(|i| Asset::CategoricalOutcome(fake_market_id, i as u16)) - .collect::>(); - let total_weight = weights.iter().sum(); - let weights = assets.clone().into_iter().zip(weights).collect::>(); - Pool { - assets: assets.clone(), - base_asset: assets[0], - market_id: 0u8.into(), - pool_status: PoolStatus::Active, // Doesn't play any role. - scoring_rule: ScoringRule::CPMM, - swap_fee, - total_subsidy: None, - total_weight: Some(total_weight), - weights: Some(weights), - } - } - - fn collect_balances_into_map( - assets: Vec>, - balances: Vec, - ) -> BTreeMap, Balance> { - assets.into_iter().zip(balances).collect::>() - } -} diff --git a/zrml/swaps/src/benchmarks.rs b/zrml/swaps/src/benchmarks.rs index 78b542014..d4d414be0 100644 --- a/zrml/swaps/src/benchmarks.rs +++ b/zrml/swaps/src/benchmarks.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -29,742 +29,136 @@ use super::*; #[cfg(test)] use crate::Pallet as Swaps; -use crate::{pallet::ARBITRAGE_MAX_ITERATIONS, Config, Event, MarketIdOf}; -use frame_benchmarking::{account, benchmarks, vec, whitelisted_caller, Vec}; -use frame_support::{ - dispatch::{DispatchResult, UnfilteredDispatchable}, - traits::Get, - weights::Weight, -}; +use crate::{types::PoolStatus, AssetOf, Config, Event}; +use frame_benchmarking::{benchmarks, vec, whitelisted_caller, Vec}; +use frame_support::traits::Get; use frame_system::RawOrigin; use orml_traits::MultiCurrency; -use sp_runtime::{ - traits::{CheckedSub, SaturatedConversion, Zero}, - DispatchError, Perbill, -}; +use sp_runtime::traits::{SaturatedConversion, Zero}; use zeitgeist_primitives::{ constants::{BASE, CENT}, math::fixed::FixedMul, - traits::{MarketCommonsPalletApi, Swaps as _}, - types::{ - Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, - MarketPeriod, MarketStatus, MarketType, OutcomeReport, PoolId, PoolStatus, ScoringRule, - }, + traits::{Swaps as SwapsApi, ZeitgeistAssetEnumerator}, }; -const DEFAULT_CREATOR_FEE: Perbill = Perbill::from_perthousand(1); const LIQUIDITY: u128 = 100 * BASE; -type MarketOf = zeitgeist_primitives::types::Market< - ::AccountId, - BalanceOf, - <::MarketCommons as MarketCommonsPalletApi>::BlockNumber, - <::MarketCommons as MarketCommonsPalletApi>::Moment, - Asset<<::MarketCommons as MarketCommonsPalletApi>::MarketId>, ->; - fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -fn set_default_creator_fee(market_id: MarketIdOf) -> DispatchResult { - T::MarketCommons::mutate_market(&market_id, |market: &mut MarketOf| { - market.creator_fee = DEFAULT_CREATOR_FEE; - Ok(()) - }) -} - -// Generates `acc_total` accounts, of which `acc_asset` account do own `asset` -fn generate_accounts_with_assets( - acc_total: u32, - acc_asset: u16, - acc_amount: BalanceOf, -) -> Result, &'static str> { - let mut accounts = Vec::new(); - - for i in 0..acc_total { - let acc = account("AssetHolder", i, 0); - - for j in 0..acc_asset { - let asset = Asset::CategoricalOutcome::>(0u32.into(), j); - T::AssetManager::deposit(asset, &acc, acc_amount)?; - } - - accounts.push(acc); - } - - Ok(accounts) -} - -// Generates ``asset_count`` assets -fn generate_assets( +fn generate_assets( owner: &T::AccountId, asset_count: usize, - asset_amount: Option>, -) -> Vec>> { - let mut assets: Vec>> = Vec::new(); - - let asset_amount_unwrapped: BalanceOf = { - match asset_amount { - Some(ac) => ac, - _ => LIQUIDITY.saturated_into(), - } - }; - - // Generate MaxAssets assets and generate enough liquidity + opt_asset_amount: Option>, +) -> (Vec>, BalanceOf) +where + T: Config, + T::Asset: ZeitgeistAssetEnumerator, +{ + let mut assets: Vec> = Vec::new(); + let asset_amount = opt_asset_amount.unwrap_or_else(|| LIQUIDITY.saturated_into()); for i in 0..asset_count { - let asset = Asset::CategoricalOutcome(0u32.into(), i.saturated_into()); + let asset = T::Asset::create_asset_id(i as u128); assets.push(asset); - - T::AssetManager::deposit(asset, owner, asset_amount_unwrapped).unwrap() + T::AssetManager::deposit(asset, owner, asset_amount).unwrap() } - - assets + (assets, asset_amount) } -fn push_default_market(caller: T::AccountId, oracle: T::AccountId) -> MarketIdOf { - let market = Market { - base_asset: Asset::Ztg, - creation: MarketCreation::Permissionless, - creator_fee: Perbill::zero(), - creator: caller, - market_type: MarketType::Categorical(3), - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - metadata: vec![0; 50], - oracle, - period: MarketPeriod::Block(0u32.into()..1u32.into()), - deadlines: Deadlines { - grace_period: 1_u32.into(), - oracle_duration: 1_u32.into(), - dispute_duration: 1_u32.into(), - }, - report: None, - resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, - status: MarketStatus::Active, - bonds: MarketBonds::default(), - early_close: None, - }; - - T::MarketCommons::push_market(market).unwrap() -} - -fn initialize_pool( - caller: &T::AccountId, - asset_count: Option, - asset_amount: Option>, - scoring_rule: ScoringRule, - weights: Option>, -) -> (PoolId, Asset>, Vec>>, MarketIdOf) { - let asset_count_unwrapped: usize = { - match asset_count { - Some(ac) => ac, - _ => T::MaxAssets::get().into(), - } - }; - - let assets = generate_assets::(caller, asset_count_unwrapped, asset_amount); - let some_weights = if weights.is_some() { - weights - } else { - Some(vec![T::MinWeight::get(); asset_count_unwrapped]) - }; - let base_asset = *assets.last().unwrap(); - let market_id = push_default_market::(caller.clone(), caller.clone()); - +// Creates a pool containing `asset_count` (default: max assets) assets. +// Returns `PoolId`, `Vec>`, ``MarketId` +fn bench_create_pool( + caller: T::AccountId, + asset_count: usize, + opt_asset_amount: Option>, + opt_weights: Option>, + open: bool, +) -> (u128, Vec>, BalanceOf) +where + T: Config, + T::Asset: ZeitgeistAssetEnumerator, +{ + let (assets, asset_amount) = generate_assets::(&caller, asset_count, opt_asset_amount); + let weights = opt_weights.unwrap_or_else(|| vec![T::MinWeight::get(); asset_count]); let pool_id = Pallet::::create_pool( caller.clone(), assets.clone(), - base_asset, - market_id, - scoring_rule, - if scoring_rule == ScoringRule::CPMM { Some(Zero::zero()) } else { None }, - if scoring_rule == ScoringRule::CPMM { Some(LIQUIDITY.saturated_into()) } else { None }, - if scoring_rule == ScoringRule::CPMM { some_weights } else { None }, + Zero::zero(), + asset_amount, + weights, ) .unwrap(); - - (pool_id, base_asset, assets, market_id) -} - -// Creates a pool containing `asset_count` (default: max assets) assets. -// Returns `PoolId`, `Vec>`, ``MarketId` -fn bench_create_pool( - caller: T::AccountId, - asset_count: Option, - asset_amount: Option>, - scoring_rule: ScoringRule, - subsidize: bool, - weights: Option>, -) -> (u128, Vec>>, MarketIdOf) { - let (pool_id, base_asset, assets, market_id) = - initialize_pool::(&caller, asset_count, asset_amount, scoring_rule, weights); - - if scoring_rule == ScoringRule::CPMM { - let _ = Pallet::::open_pool(pool_id); - } - - if subsidize { - let min_subsidy = T::MinSubsidy::get(); - T::AssetManager::deposit(base_asset, &caller, min_subsidy).unwrap(); - let _ = Call::::pool_join_subsidy { pool_id, amount: T::MinSubsidy::get() } - .dispatch_bypass_filter(RawOrigin::Signed(caller).into()) - .unwrap(); - let _ = Pallet::::end_subsidy_phase(pool_id).unwrap(); + if open { + Pallet::::open_pool(pool_id).unwrap(); } - - (pool_id, assets, market_id) + (pool_id, assets, asset_amount) } benchmarks! { - admin_clean_up_pool_cpmm_categorical { - // We're excluding the case of two assets, which would leave us with only one outcome - // token and cause `create_market` to error. - let a in 3..T::MaxAssets::get().into(); - let category_count = (a - 1) as u16; - let caller: T::AccountId = whitelisted_caller(); - let market_id = T::MarketCommons::push_market( - Market { - base_asset: Asset::Ztg, - creation: MarketCreation::Permissionless, - creator_fee: Perbill::zero(), - creator: caller.clone(), - market_type: MarketType::Categorical(category_count), - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - metadata: vec![0; 50], - oracle: caller.clone(), - period: MarketPeriod::Block(0u32.into()..1u32.into()), - deadlines: Deadlines { - grace_period: 1_u32.into(), - oracle_duration: 1_u32.into(), - dispute_duration: 1_u32.into(), - }, - report: None, - resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, - status: MarketStatus::Active, - bonds: MarketBonds::default(), - early_close: None, - } - )?; - let pool_id: PoolId = 0; - let _ = T::MarketCommons::insert_market_pool(market_id, pool_id); - let _ = bench_create_pool::( - caller, - Some(a as usize), - None, - ScoringRule::CPMM, - false, - None, - ); - let _ = Pallet::::mutate_pool(pool_id, |pool| { - pool.pool_status = PoolStatus::Closed; - Ok(()) - }); - }: admin_clean_up_pool(RawOrigin::Root, market_id, OutcomeReport::Categorical(0)) - - admin_clean_up_pool_cpmm_scalar { - let caller: T::AccountId = whitelisted_caller(); - let market_id = T::MarketCommons::push_market( - Market { - base_asset: Asset::Ztg, - creation: MarketCreation::Permissionless, - creator_fee: Perbill::zero(), - creator: caller.clone(), - market_type: MarketType::Scalar(0..=99), - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - metadata: vec![0; 50], - oracle: caller.clone(), - period: MarketPeriod::Block(0u32.into()..1u32.into()), - deadlines: Deadlines { - grace_period: 1_u32.into(), - oracle_duration: 1_u32.into(), - dispute_duration: 1_u32.into(), - }, - report: None, - resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, - status: MarketStatus::Active, - bonds: MarketBonds::default(), - early_close: None, - } - )?; - let pool_id: PoolId = 0; - let asset_count = 3; - let _ = T::MarketCommons::insert_market_pool(market_id, pool_id); - let _ = bench_create_pool::( - caller, - Some(asset_count), - None, - ScoringRule::CPMM, - false, - None, - ); - let _ = Pallet::::mutate_pool(pool_id, |pool| { - pool.pool_status = PoolStatus::Closed; - Ok(()) - }); - }: admin_clean_up_pool(RawOrigin::Root, market_id, OutcomeReport::Scalar(33)) - - // This is a worst-case benchmark for arbitraging a number of pools. - apply_to_cached_pools_execute_arbitrage { - let a in 0..63; // The number of cached pools. - - let caller: T::AccountId = whitelisted_caller(); - let asset_count = T::MaxAssets::get(); - let balance: BalanceOf = (10_000_000_000 * BASE).saturated_into(); - let total_amount_required = balance * a.saturated_into(); - let assets = generate_assets::(&caller, asset_count.into(), Some(total_amount_required)); - let base_asset = *assets.last().unwrap(); - - // Set weights to [1, 1, ..., 1, 64]. - let outcome_count = asset_count - 1; - let outcome_weight = T::MinWeight::get(); - let mut weights = vec![outcome_weight; (asset_count - 1) as usize]; - weights.push(outcome_count as u128 * outcome_weight); - - // Create `a` pools with huge balances and only a relatively small difference between them - // to cause maximum iterations. - for i in 0..a { - let market_id = push_default_market::(caller.clone(), caller.clone()); - let pool_id = Pallet::::create_pool( - caller.clone(), - assets.clone(), - base_asset, - market_id, - ScoringRule::CPMM, - Some(Zero::zero()), - Some(balance), - Some(weights.clone()), - ) - .unwrap(); - - let pool_account_id = Pallet::::pool_account_id(&pool_id); - T::AssetManager::withdraw( - base_asset, - &pool_account_id, - balance / 9u8.saturated_into() - ) - .unwrap(); - - // Add enough funds for arbitrage to the prize pool. - T::AssetManager::deposit( - base_asset, - &T::MarketCommons::market_account(market_id), - balance, - ) - .unwrap(); - - PoolsCachedForArbitrage::::insert(pool_id, ()); - } - let mutation = - |pool_id: PoolId| Pallet::::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS); - }: { - Pallet::::apply_to_cached_pools(a, mutation, Weight::MAX) - } verify { - // Ensure that all pools have been arbitraged. - assert_eq!(PoolsCachedForArbitrage::::iter().count(), 0); - } - - apply_to_cached_pools_noop { - let a in 0..63; // The number of cached pools. - for i in 0..a { - let pool_id: PoolId = i.into(); - PoolsCachedForArbitrage::::insert(pool_id, ()); - } - let noop = |_: PoolId| -> Result { Ok(Weight::zero()) }; - }: { - Pallet::::apply_to_cached_pools(a, noop, Weight::MAX) - } - - destroy_pool_in_subsidy_phase { - // Total subsidy providers - let a in 0..10; - let min_assets_plus_base_asset = 3u16; - - // Create pool with assets - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, _, _) = bench_create_pool::( - caller, - Some(min_assets_plus_base_asset.into()), - None, - ScoringRule::RikiddoSigmoidFeeMarketEma, - false, - None, - ); - let amount = T::MinSubsidy::get(); - - // Create a accounts, add MinSubsidy base assets and join subsidy - let accounts = - generate_accounts_with_assets::(a, min_assets_plus_base_asset, amount).unwrap(); - - // Join subsidy with each account - for account in accounts { - let _ = Call::::pool_join_subsidy { pool_id, amount } - .dispatch_bypass_filter(RawOrigin::Signed(account).into())?; - } - }: { Pallet::::destroy_pool_in_subsidy_phase(pool_id)? } - - distribute_pool_share_rewards { - // Total accounts - let a in 10..20; - // Total pool share holders - let b in 0..10; - - let min_assets_plus_base_asset = 3u16; - let amount = T::MinSubsidy::get(); - - // Create a accounts, add MinSubsidy base assets - let accounts = - generate_accounts_with_assets::(a, min_assets_plus_base_asset, amount).unwrap(); - - let (pool_id, _, _) = bench_create_pool::( - accounts[0].clone(), - Some(min_assets_plus_base_asset.into()), - None, - ScoringRule::RikiddoSigmoidFeeMarketEma, - false, - None, - ); - - // Join subsidy with b accounts - for account in accounts[0..b.saturated_into()].iter() { - let _ = Call::::pool_join_subsidy { pool_id, amount } - .dispatch_bypass_filter(RawOrigin::Signed(account.clone()).into())?; - } - - Pallet::::end_subsidy_phase(pool_id)?; - let pool = >::get(pool_id).unwrap(); - }: { - Pallet::::distribute_pool_share_rewards( - &pool, - pool_id, - pool.base_asset, - Asset::CategoricalOutcome(1337u16.saturated_into(), 1337u16.saturated_into()), - &account("ScrapCollector", 0, 0) - ); - } - - end_subsidy_phase { - // Total assets - let a in (T::MinAssets::get().into())..T::MaxAssets::get().into(); - // Total subsidy providers - let b in 0..10; - - // Create pool with a assets - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, _, _) = bench_create_pool::( - caller, - Some(a.saturated_into()), - None, - ScoringRule::RikiddoSigmoidFeeMarketEma, - false, - None, - ); - let amount = T::MinSubsidy::get(); - - // Create b accounts, add MinSubsidy base assets and join subsidy - let accounts = generate_accounts_with_assets::(b, a.saturated_into(), amount).unwrap(); - - // Join subsidy with each account - for account in accounts { - let _ = Call::::pool_join_subsidy { pool_id, amount } - .dispatch_bypass_filter(RawOrigin::Signed(account).into())?; - } - }: { Pallet::::end_subsidy_phase(pool_id)? } - - execute_arbitrage_buy_burn { - let a in 2..T::MaxAssets::get().into(); // The number of assets in the pool. - let asset_count = a as usize; - let iteration_count = ARBITRAGE_MAX_ITERATIONS; - - let caller: T::AccountId = whitelisted_caller(); - let balance: BalanceOf = (10_000_000_000 * BASE).saturated_into(); - let assets = generate_assets::(&caller, asset_count, Some(balance)); - let base_asset = *assets.last().unwrap(); - - // Set weights to [1, 1, ..., 1, a]. - let outcome_count = asset_count - 1; - let outcome_weight = T::MinWeight::get(); - let mut weights = vec![outcome_weight; outcome_count]; - weights.push(outcome_count as u128 * outcome_weight); - - // Create a pool with huge balances and only a relatively small difference between them to - // cause at least 30 iterations. - let market_id = push_default_market::(caller.clone(), caller.clone()); - let pool_id = Pallet::::create_pool( - caller, - assets.clone(), - base_asset, - market_id, - ScoringRule::CPMM, - Some(Zero::zero()), - Some(balance), - Some(weights.clone()), - ) - .unwrap(); - let pool_account_id = Pallet::::pool_account_id(&pool_id); - let asset = *assets.last().unwrap(); - T::AssetManager::withdraw( - asset, - &pool_account_id, - balance / 9u8.saturated_into(), - ) - .unwrap(); - let balance_before = T::AssetManager::free_balance(asset, &pool_account_id); - - // Add enough funds for arbitrage to the prize pool. - T::AssetManager::deposit( - base_asset, - &T::MarketCommons::market_account(market_id), - (u128::MAX / 2).saturated_into(), - ) - .unwrap(); - }: { - // In order to cap the number of iterations, we just set the `max_iterations` to `b`. - Pallet::::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS)? - } verify { - // We don't care about the exact arbitrage amount and just want to verify that the correct - // event was emitted. - let arbitrage_amount = - T::AssetManager::free_balance(asset, &pool_account_id) - balance_before; - assert_last_event::(Event::ArbitrageBuyBurn::(pool_id, arbitrage_amount).into()); - } - - execute_arbitrage_mint_sell { - let a in 2..T::MaxAssets::get().into(); // The number of assets in the pool. - let asset_count = a as usize; - - let caller: T::AccountId = whitelisted_caller(); - let balance: BalanceOf = (10_000_000_000 * BASE).saturated_into(); - let assets = generate_assets::(&caller, asset_count, Some(balance)); - let base_asset = *assets.last().unwrap(); - - // Set weights to [1, 1, ..., 1, a]. - let outcome_count = asset_count - 1; - let outcome_weight = T::MinWeight::get(); - let mut weights = vec![outcome_weight; outcome_count]; - weights.push(outcome_count as u128 * outcome_weight); - - // Create a pool with huge balances and only a relatively small difference between them to - // cause at least 30 iterations. - let market_id = push_default_market::(caller.clone(), caller.clone()); - let pool_id = Pallet::::create_pool( - caller, - assets.clone(), - base_asset, - market_id, - ScoringRule::CPMM, - Some(Zero::zero()), - Some(balance), - Some(weights.clone()), - ) - .unwrap(); - let pool_account_id = Pallet::::pool_account_id(&pool_id); - for asset in assets.iter().filter(|a| **a != base_asset) { - T::AssetManager::withdraw( - *asset, - &pool_account_id, - balance / 9u8.saturated_into(), - ) - .unwrap(); - } - let asset = assets[0]; - let balance_before = T::AssetManager::free_balance(asset, &pool_account_id); - }: { - // In order to cap the number of iterations, we just set the `max_iterations` to `b`. - Pallet::::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS)? - } verify { - // We don't care about the exact arbitrage amount and just want to verify that the correct - // event was emitted. - let arbitrage_amount = - T::AssetManager::free_balance(asset, &pool_account_id) - balance_before; - assert_last_event::(Event::ArbitrageMintSell::(pool_id, arbitrage_amount).into()); - } - - execute_arbitrage_skipped { - let a in 2..T::MaxAssets::get().into(); // The number of assets in the pool. - let asset_count = a as usize; - - let caller: T::AccountId = whitelisted_caller(); - let balance: BalanceOf = (10_000_000_000 * BASE).saturated_into(); - let assets = generate_assets::(&caller, asset_count, Some(balance)); - let base_asset = *assets.last().unwrap(); - - // Set weights to [1, 1, ..., 1, a]. - let outcome_count = asset_count - 1; - let outcome_weight = T::MinWeight::get(); - let mut weights = vec![outcome_weight; outcome_count]; - weights.push(outcome_count as u128 * outcome_weight); - - // Create a pool with equal balances to ensure that the total spot price is equal to 1. - let market_id = push_default_market::(caller.clone(), caller.clone()); - let pool_id = Pallet::::create_pool( - caller, - assets, - base_asset, - market_id, - ScoringRule::CPMM, - Some(Zero::zero()), - Some(balance), - Some(weights.clone()), - ) - .unwrap(); - }: { - // In order to cap the number of iterations, we just set the `max_iterations` to `b`. - Pallet::::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS)? - } verify { - assert_last_event::(Event::ArbitrageSkipped::(pool_id).into()); + where_clause { + where + T::Asset: ZeitgeistAssetEnumerator, } pool_exit { let a in 2 .. T::MaxAssets::get().into(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some((2u128 * LIQUIDITY).saturated_into()), - ScoringRule::CPMM, - false, - None, - ); - let pool_amount = (LIQUIDITY / 2u128).saturated_into(); + let (pool_id, _, asset_amount) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); + let pool_amount = (asset_amount / 2u8.into()).saturated_into(); let min_assets_out = vec![0u32.into(); a as usize]; }: _(RawOrigin::Signed(caller), pool_id, pool_amount, min_assets_out) - pool_exit_subsidy { - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller.clone(), - None, - Some(T::MinSubsidy::get()), - ScoringRule::RikiddoSigmoidFeeMarketEma, - false, - None, - ); - let _ = Call::::pool_join_subsidy { - pool_id, - amount: T::MinSubsidy::get(), - } - .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - }: _(RawOrigin::Signed(caller), pool_id, T::MinSubsidy::get()) - pool_exit_with_exact_asset_amount { let a = T::MaxAssets::get(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - None, - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, assets, asset_amount) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); let asset_amount: BalanceOf = BASE.saturated_into(); - let pool_amount = LIQUIDITY.saturated_into(); + let pool_amount = asset_amount.saturated_into(); }: _(RawOrigin::Signed(caller), pool_id, assets[0], asset_amount, pool_amount) pool_exit_with_exact_pool_amount { let a = T::MaxAssets::get(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - None, - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, assets, ..) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); let min_asset_amount = 0u32.into(); let pool_amount: BalanceOf = CENT.saturated_into(); }: _(RawOrigin::Signed(caller), pool_id, assets[0], pool_amount, min_asset_amount) pool_join { - let a in 2 .. T::MaxAssets::get().into(); + let a in 2 .. T::MaxAssets::get().into(); // asset_count let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some((2u128 * LIQUIDITY).saturated_into()), - ScoringRule::CPMM, - false, - None, - ); - let pool_amount = LIQUIDITY.saturated_into(); - let max_assets_in = vec![LIQUIDITY.saturated_into(); a as usize]; + let (pool_id, _, asset_amount) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); + generate_assets::(&caller, a as usize, Some(asset_amount)); + let pool_amount = asset_amount.saturated_into(); + let max_assets_in = vec![u128::MAX.saturated_into(); a as usize]; }: _(RawOrigin::Signed(caller), pool_id, pool_amount, max_assets_in) - pool_join_subsidy { - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller.clone(), - None, - Some(T::MinSubsidy::get()), - ScoringRule::RikiddoSigmoidFeeMarketEma, - false, - None, - ); - }: _(RawOrigin::Signed(caller), pool_id, T::MinSubsidy::get()) - pool_join_with_exact_asset_amount { let a = T::MaxAssets::get(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some((2u128 * LIQUIDITY).saturated_into()), - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, assets, ..) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); let asset_amount: BalanceOf = BASE.saturated_into(); + generate_assets::(&caller, a as usize, Some(asset_amount)); let min_pool_amount = 0u32.into(); }: _(RawOrigin::Signed(caller), pool_id, assets[0], asset_amount, min_pool_amount) pool_join_with_exact_pool_amount { + // TODO(#1219): Explicitly state liquidity here! let a = T::MaxAssets::get(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some((2u128 * LIQUIDITY).saturated_into()), - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, assets, asset_amount) = + bench_create_pool::(caller.clone(), a as usize, None, None, true); let pool_amount = BASE.saturated_into(); - let max_asset_amount: BalanceOf = LIQUIDITY.saturated_into(); + generate_assets::(&caller, a as usize, Some(asset_amount)); + let max_asset_amount: BalanceOf = u128::MAX.saturated_into(); }: _(RawOrigin::Signed(caller), pool_id, assets[0], pool_amount, max_asset_amount) - clean_up_pool_categorical_without_reward_distribution { - // Total possible outcomes - let a in 3..T::MaxAssets::get().into(); - - let amount = T::MinSubsidy::get(); - - // Create pool with a assets - let caller = whitelisted_caller(); - - let (pool_id, _, _) = bench_create_pool::( - caller, - Some(a.saturated_into()), - None, - ScoringRule::CPMM, - false, - None, - ); - let _ = Pallet::::mutate_pool(pool_id, |pool| { - pool.pool_status = PoolStatus::Closed; - Ok(()) - }); - }: { - Pallet::::clean_up_pool_categorical( - pool_id, - &OutcomeReport::Categorical(0), - &account("ScrapCollector", 0, 0), - )?; - } - swap_exact_amount_in_cpmm { // We're trying to get as many iterations in `bpow_approx` as possible. Experiments have // shown that y = 3/4, weight_ratio=1/2 (almost) maximizes the number of iterations for @@ -781,13 +175,11 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); let (pool_id, assets, market_id) = bench_create_pool::( caller.clone(), - Some(asset_count as usize), + asset_count as usize, Some(balance), - ScoringRule::CPMM, - false, Some(weights), + true, ); - set_default_creator_fee::(market_id)?; let asset_in = assets[0]; T::AssetManager::deposit(asset_in, &caller, u64::MAX.saturated_into()).unwrap(); let asset_out = assets[asset_count as usize - 1]; @@ -803,30 +195,6 @@ benchmarks! { max_price ) - swap_exact_amount_in_rikiddo { - let a in 3 .. T::MaxAssets::get().into(); - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some(BASE.saturated_into()), - ScoringRule::RikiddoSigmoidFeeMarketEma, - true, - None, - ); - let asset_amount_in: BalanceOf = BASE.saturated_into(); - let min_asset_amount_out: Option> = Some(0u32.into()); - let max_price = Some((BASE * 1024).saturated_into()); - }: swap_exact_amount_in( - RawOrigin::Signed(caller), - pool_id, - assets[0], - asset_amount_in, - *assets.last().unwrap(), - min_asset_amount_out, - max_price - ) - swap_exact_amount_out_cpmm { // We're trying to get as many iterations in `bpow_approx` as possible. Experiments have // shown that y = 3/2, weight_ratio=1/4 (almost) maximizes the number of iterations for @@ -835,11 +203,7 @@ benchmarks! { // amount_out = 1/3 * balance_out, weight_out = 1, weight_in = 4. let asset_count = T::MaxAssets::get(); let balance: BalanceOf = LIQUIDITY.saturated_into(); - let mut asset_amount_out: BalanceOf = balance.bmul(T::MaxOutRatio::get()).unwrap(); - asset_amount_out = Perbill::one() - .checked_sub(&DEFAULT_CREATOR_FEE) - .unwrap() - .mul_floor(asset_amount_out); + let asset_amount_out: BalanceOf = balance.bmul(T::MaxOutRatio::get()).unwrap(); let weight_out = T::MinWeight::get(); let weight_in = 4 * weight_out; let mut weights = vec![weight_out; asset_count as usize]; @@ -847,13 +211,11 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); let (pool_id, assets, market_id) = bench_create_pool::( caller.clone(), - Some(asset_count as usize), + asset_count as usize, Some(balance), - ScoringRule::CPMM, - false, Some(weights), + true, ); - set_default_creator_fee::(market_id)?; let asset_in = assets[0]; T::AssetManager::deposit(asset_in, &caller, u64::MAX.saturated_into()).unwrap(); let asset_out = assets[asset_count as usize - 1]; @@ -869,91 +231,45 @@ benchmarks! { max_price ) - swap_exact_amount_out_rikiddo { - let a in 3 .. T::MaxAssets::get().into(); - let caller: T::AccountId = whitelisted_caller(); - let (pool_id, assets, ..) = bench_create_pool::( - caller.clone(), - Some(a as usize), - Some(BASE.saturated_into()), - ScoringRule::RikiddoSigmoidFeeMarketEma, - true, - None, - ); - let max_asset_amount_in: Option> = Some((BASE * 1024).saturated_into()); - let asset_amount_out: BalanceOf = BASE.saturated_into(); - let max_price = Some((BASE * 1024).saturated_into()); - }: swap_exact_amount_out( - RawOrigin::Signed(caller), - pool_id, - *assets.last().unwrap(), - max_asset_amount_in, - assets[0], - asset_amount_out, - max_price - ) - open_pool { let a in 2..T::MaxAssets::get().into(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = initialize_pool::( - &caller, - Some(a as usize), - None, - ScoringRule::CPMM, - None, - ); + let (pool_id, ..) = bench_create_pool::(caller, a as usize, None, None, false); let pool = Pallet::::pool_by_id(pool_id).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Initialized); + assert_eq!(pool.status, PoolStatus::Closed); }: { Pallet::::open_pool(pool_id).unwrap(); } verify { let pool = Pallet::::pool_by_id(pool_id).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Active); + assert_eq!(pool.status, PoolStatus::Open); } close_pool { let a in 2..T::MaxAssets::get().into(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller, - Some(a as usize), - None, - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, ..) = bench_create_pool::(caller, a as usize, None, None, true); let pool = Pallet::::pool_by_id(pool_id).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Active); + assert_eq!(pool.status, PoolStatus::Open); }: { Pallet::::close_pool(pool_id).unwrap(); } verify { let pool = Pallet::::pool_by_id(pool_id).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Closed); + assert_eq!(pool.status, PoolStatus::Closed); } destroy_pool { let a in 2..T::MaxAssets::get().into(); let caller: T::AccountId = whitelisted_caller(); - let (pool_id, ..) = bench_create_pool::( - caller, - Some(a as usize), - None, - ScoringRule::CPMM, - false, - None, - ); + let (pool_id, ..) = bench_create_pool::(caller, a as usize, None, None, true); assert!(Pallet::::pool_by_id(pool_id).is_ok()); }: { Pallet::::destroy_pool(pool_id).unwrap(); } verify { assert!(Pallet::::pool_by_id(pool_id).is_err()); - assert_last_event::(Event::PoolDestroyed::( - pool_id, - ).into()); + assert_last_event::(Event::PoolDestroyed::(pool_id).into()); } impl_benchmark_test_suite!( diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 677e080f1..64fafebad 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -31,15 +31,14 @@ extern crate alloc; #[macro_use] mod utils; -mod arbitrage; mod benchmarks; mod events; pub mod fixed; pub mod math; pub mod migrations; pub mod mock; -mod root; mod tests; +mod types; pub mod weights; pub use pallet::*; @@ -47,8 +46,8 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{ - arbitrage::ArbitrageForCpmm, events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, + types::{Pool, PoolStatus}, utils::{ pool_exit_with_exact_amount, pool_join_with_exact_amount, swap_exact_amount, PoolExitWithExactAmountParams, PoolJoinWithExactAmountParams, PoolParams, @@ -60,114 +59,44 @@ mod pallet { use core::marker::PhantomData; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Weight}, - ensure, log, - pallet_prelude::{StorageDoubleMap, StorageMap, StorageValue, ValueQuery}, - storage::{with_transaction, TransactionOutcome}, - traits::{Get, Hooks, IsType, StorageVersion}, - transactional, Blake2_128Concat, PalletId, Twox64Concat, - }; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::OriginFor}; - use orml_traits::{BalanceStatus, MultiCurrency, MultiReservableCurrency}; - use parity_scale_codec::{Decode, Encode}; - use sp_arithmetic::{ - traits::{CheckedSub, Saturating, Zero}, - Perbill, + ensure, + pallet_prelude::{OptionQuery, StorageDoubleMap, StorageMap, StorageValue, ValueQuery}, + traits::{Get, IsType, StorageVersion}, + transactional, Blake2_128Concat, PalletError, PalletId, Parameter, Twox64Concat, }; + use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use orml_traits::MultiCurrency; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; + use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{ - traits::AccountIdConversion, DispatchError, DispatchResult, SaturatedConversion, - }; - use substrate_fixed::{ - traits::{FixedSigned, FixedUnsigned, LossyFrom}, - types::{ - extra::{U127, U128, U24, U31, U32}, - I9F23, U1F127, - }, - FixedI128, FixedI32, FixedU128, FixedU32, + traits::{AccountIdConversion, MaybeSerializeDeserialize, Member}, + DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::{ constants::CENT, math::{ - checked_ops_res::{CheckedAddRes, CheckedMulRes, CheckedSubRes}, - fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, - }, - traits::{MarketCommonsPalletApi, Swaps, ZeitgeistAssetManager}, - types::{ - Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, - ScoringRule, SerdeWrapper, + checked_ops_res::{CheckedAddRes, CheckedMulRes}, + fixed::FixedMul, }, - }; - use zrml_liquidity_mining::LiquidityMiningPalletApi; - use zrml_rikiddo::{ - constants::{EMA_LONG, EMA_SHORT}, - traits::RikiddoMVPallet, - types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}, + traits::{PoolSharesId, Swaps, ZeitgeistAssetManager}, + types::{PoolId, SerdeWrapper}, }; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); - const LOG_TARGET: &str = "runtime::zrml-swaps"; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); - 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 AssetOf = + <::AssetManager as MultiCurrency>>::CurrencyId; + pub(crate) type BalanceOf = + <::AssetManager as MultiCurrency>>::Balance; + pub(crate) type PoolOf = Pool, BalanceOf>; - pub(crate) const ARBITRAGE_MAX_ITERATIONS: usize = 30; - const ARBITRAGE_THRESHOLD: u128 = CENT; const MIN_BALANCE: u128 = CENT; - const ON_IDLE_MIN_WEIGHT: Weight = Weight::from_parts(1_000_000, 100_000); #[pallet::call] impl Pallet { - /// Clean up the pool of a resolved market. - /// - /// # Arguments - /// - /// - `origin`: The root origin. - /// - `market_id`: The id of the market that the pool belongs to. - /// - `outcome_report`: The report that resolved the market. - /// - /// # Weight - /// - /// Complexity: `O(1)` if the market is scalar, `O(n)` where `n` is the number of - /// assets in the pool if the market is categorical. - #[pallet::call_index(0)] - #[pallet::weight( - T::WeightInfo::admin_clean_up_pool_cpmm_categorical(T::MaxAssets::get() as u32) - .max(T::WeightInfo::admin_clean_up_pool_cpmm_scalar()) - )] - #[transactional] - pub fn admin_clean_up_pool( - origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, - outcome_report: OutcomeReport, - ) -> DispatchResultWithPostInfo { - // TODO(#785): This is not properly benchmarked for Rikiddo yet! - ensure_root(origin)?; - let market = T::MarketCommons::market(&market_id)?; - let pool_id = T::MarketCommons::market_pool(&market_id)?; - Self::clean_up_pool( - &market.market_type, - pool_id, - &outcome_report, - &Self::pool_account_id(&pool_id), - )?; - let weight_info = match market.market_type { - MarketType::Scalar(_) => T::WeightInfo::admin_clean_up_pool_cpmm_scalar(), - // This is a time-efficient way of getting the number of assets, but makes the - // assumption that `assets = category_count + 1`. This is definitely a code smell - // and a result of not separating `prediction-markets` from `swaps` properly in - // this function. - MarketType::Categorical(category_count) => { - T::WeightInfo::admin_clean_up_pool_cpmm_categorical( - category_count.saturating_add(1) as u32, - ) - } - }; - Ok(Some(weight_info).into()) - } - /// Pool - Exit /// /// Retrieves a given set of assets from `pool_id` to `origin`. @@ -200,126 +129,7 @@ mod pallet { min_assets_out: Vec>, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); - let who_clone = who.clone(); - let pool = Self::pool_by_id(pool_id)?; - // If the pool is still in use, prevent a pool drain. - Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; - let pool_account_id = Pallet::::pool_account_id(&pool_id); - let params = PoolParams { - asset_bounds: min_assets_out, - event: |evt| Self::deposit_event(Event::PoolExit(evt)), - pool_account_id: &pool_account_id, - pool_amount, - pool_id, - pool: &pool, - transfer_asset: |amount, amount_bound, asset| { - Self::ensure_minimum_balance(pool_id, &pool, asset, amount)?; - ensure!(amount >= amount_bound, Error::::LimitOut); - T::LiquidityMining::remove_shares(&who, &pool.market_id, amount); - T::AssetManager::transfer(asset, &pool_account_id, &who, amount)?; - Ok(()) - }, - transfer_pool: || { - Self::burn_pool_shares(pool_id, &who, pool_amount)?; - Ok(()) - }, - fee: |amount: BalanceOf| { - let exit_fee_amount = amount.bmul(Self::calc_exit_fee(&pool))?; - Ok(exit_fee_amount) - }, - who: who_clone, - }; - crate::utils::pool::<_, _, _, _, T>(params) - } - - /// Pool - Remove subsidty from a pool that uses the Rikiddo scoring rule. - /// - /// Unreserves `pool_amount` of the base currency from being used as subsidy. - /// If `amount` is greater than the amount reserved for subsidy by `origin`, - /// then the whole amount reserved for subsidy will be unreserved. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be unreserved. - /// * `pool_id`: Unique pool identifier. - /// * `amount`: The amount of base currency that should be removed from subsidy. - /// - /// # Weight - /// - /// Complexity: O(1) - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::pool_exit_subsidy())] - #[transactional] - pub fn pool_exit_subsidy( - origin: OriginFor, - #[pallet::compact] pool_id: PoolId, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(amount != Zero::zero(), Error::::ZeroAmount); - - >::try_mutate(pool_id, |pool_opt| { - let pool = pool_opt.as_mut().ok_or(Error::::PoolDoesNotExist)?; - - ensure!( - pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma, - Error::::InvalidScoringRule - ); - let base_asset = pool.base_asset; - let mut real_amount = amount; - let transferred; - - if let Some(subsidy) = >::get(pool_id, &who) { - if amount > subsidy { - real_amount = subsidy; - } - // If the account would be left with less than the minimum subsidy per account, - // then withdraw all their subsidy instead. - if subsidy.saturating_sub(amount) < T::MinSubsidyPerAccount::get() { - real_amount = subsidy; - } - - let missing = T::AssetManager::unreserve(base_asset, &who, real_amount); - transferred = real_amount.saturating_sub(missing); - let zero_balance = BalanceOf::::zero(); - - if missing > zero_balance { - log::warn!( - target: LOG_TARGET, - "Data inconsistency: More subsidy provided than currently \ - reserved. - Pool: {:?}, User: {:?}, Unreserved: {:?}, Previously reserved: {:?}", - pool_id, - who, - transferred, - subsidy - ); - } - - let new_amount = subsidy.saturating_sub(transferred); - let total_subsidy = pool.total_subsidy.ok_or(Error::::PoolMissingSubsidy)?; - - if new_amount > zero_balance && missing == zero_balance { - >::insert(pool_id, &who, new_amount); - pool.total_subsidy = Some(total_subsidy.checked_sub_res(&transferred)?); - } else { - >::remove(pool_id, &who); - pool.total_subsidy = Some(total_subsidy.checked_sub_res(&subsidy)?); - } - } else { - return Err(Error::::NoSubsidyProvided.into()); - } - - Self::deposit_event(Event::::PoolExitSubsidy( - base_asset, - amount, - CommonPoolEventParams { pool_id, who }, - transferred, - )); - - Ok(()) - }) + Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) } /// Pool - Exit with exact pool amount @@ -345,7 +155,7 @@ mod pallet { pub fn pool_exit_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] asset_amount: BalanceOf, #[pallet::compact] max_pool_amount: BalanceOf, ) -> DispatchResult { @@ -383,7 +193,7 @@ mod pallet { pub fn pool_exit_with_exact_pool_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] min_asset_amount: BalanceOf, ) -> DispatchResult { @@ -403,9 +213,9 @@ mod pallet { asset_balance.saturated_into(), Self::pool_weight_rslt(&pool, &asset)?, total_supply.saturated_into(), - pool.total_weight.ok_or(Error::::PoolMissingWeight)?.saturated_into(), + pool.total_weight.saturated_into(), pool_amount.saturated_into(), - pool.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into(), + pool.swap_fee.saturated_into(), T::ExitFee::get().saturated_into(), )? .saturated_into(); @@ -416,11 +226,9 @@ mod pallet { Error::::MaxOutRatio ); Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; - T::LiquidityMining::remove_shares(&who, &pool_ref.market_id, asset_amount); Ok(asset_amount) }, bound: min_asset_amount, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), ensure_balance: |_| Ok(()), event: |evt| Self::deposit_event(Event::PoolExitWithExactPoolAmount(evt)), who: who_clone, @@ -428,7 +236,7 @@ mod pallet { pool_id, pool: pool_ref, }; - pool_exit_with_exact_amount::<_, _, _, _, _, T>(params) + pool_exit_with_exact_amount::<_, _, _, _, T>(params) } /// Pool - Join @@ -464,10 +272,7 @@ mod pallet { let who = ensure_signed(origin)?; ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); let pool = Self::pool_by_id(pool_id)?; - ensure!( - matches!(pool.pool_status, PoolStatus::Initialized | PoolStatus::Active), - Error::::InvalidPoolStatus, - ); + ensure!(pool.status == PoolStatus::Open, Error::::InvalidPoolStatus); let pool_account_id = Pallet::::pool_account_id(&pool_id); let params = PoolParams { @@ -480,7 +285,6 @@ mod pallet { transfer_asset: |amount, amount_bound, asset| { ensure!(amount <= amount_bound, Error::::LimitIn); T::AssetManager::transfer(asset, &who, &pool_account_id, amount)?; - T::LiquidityMining::add_shares(who.clone(), pool.market_id, amount); Ok(()) }, transfer_pool: || Self::mint_pool_shares(pool_id, &who, pool_amount), @@ -492,72 +296,6 @@ mod pallet { Ok(Some(T::WeightInfo::pool_join(pool.assets.len().saturated_into())).into()) } - /// Pool - Add subsidy to a pool that uses the Rikiddo scoring rule. - /// - /// Reserves `pool_amount` of the base currency to be added as subsidy on pool activation. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be reserved. - /// * `pool_id`: Unique pool identifier. - /// * `amount`: The amount of base currency that should be added to subsidy. - /// - /// # Weight - /// - /// Complexity: O(1) - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::pool_join_subsidy())] - #[transactional] - pub fn pool_join_subsidy( - origin: OriginFor, - #[pallet::compact] pool_id: PoolId, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(amount != Zero::zero(), Error::::ZeroAmount); - - >::try_mutate(pool_id, |pool_opt| { - let pool = pool_opt.as_mut().ok_or(Error::::PoolDoesNotExist)?; - - ensure!( - pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma, - Error::::InvalidScoringRule - ); - let base_asset = pool.base_asset; - T::AssetManager::reserve(base_asset, &who, amount)?; - - let total_subsidy = pool.total_subsidy.ok_or(Error::::PoolMissingSubsidy)?; - >::try_mutate::<_, _, _, DispatchError, _>( - pool_id, - &who, - |user_subsidy| { - if let Some(prev_val) = user_subsidy { - *prev_val = prev_val.saturating_add(amount); - } else { - // If the account adds subsidy for the first time, ensure that it's - // larger than the minimum amount. - ensure!( - amount >= T::MinSubsidyPerAccount::get(), - Error::::InvalidSubsidyAmount - ); - *user_subsidy = Some(amount); - } - - pool.total_subsidy = Some(total_subsidy.saturating_add(amount)); - Ok(()) - }, - )?; - - Self::deposit_event(Event::PoolJoinSubsidy( - base_asset, - amount, - CommonPoolEventParams { pool_id, who }, - )); - - Ok(()) - }) - } - /// Pool - Join with exact asset amount /// /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, @@ -581,7 +319,7 @@ mod pallet { pub fn pool_join_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, #[pallet::compact] asset_amount: BalanceOf, #[pallet::compact] min_pool_amount: BalanceOf, ) -> DispatchResult { @@ -619,7 +357,7 @@ mod pallet { pub fn pool_join_with_exact_pool_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] max_asset_amount: BalanceOf, ) -> DispatchResult { @@ -636,9 +374,9 @@ mod pallet { asset_balance.saturated_into(), Self::pool_weight_rslt(&pool, &asset)?, total_supply.saturated_into(), - pool.total_weight.ok_or(Error::::PoolMissingWeight)?.saturated_into(), + pool.total_weight.saturated_into(), pool_amount.saturated_into(), - pool.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into(), + pool.swap_fee.saturated_into(), )? .saturated_into(); ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); @@ -647,11 +385,9 @@ mod pallet { asset_amount <= asset_balance.checked_mul_res(&T::MaxInRatio::get())?, Error::::MaxInRatio ); - T::LiquidityMining::add_shares(who.clone(), pool.market_id, asset_amount); Ok(asset_amount) }, bound: max_asset_amount, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), event: |evt| Self::deposit_event(Event::PoolJoinWithExactPoolAmount(evt)), pool_account_id: &pool_account_id, pool_amount: |_, _| Ok(pool_amount), @@ -659,7 +395,7 @@ mod pallet { pool: &pool, who: who_clone, }; - pool_join_with_exact_amount::<_, _, _, _, T>(params) + pool_join_with_exact_amount::<_, _, _, T>(params) } /// Swap - Exact amount in @@ -680,16 +416,15 @@ mod pallet { /// /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of /// assets if the scoring rule is Rikiddo. - // TODO(#790): Replace with maximum of CPMM and Rikiddo benchmark! #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::swap_exact_amount_in_cpmm())] #[transactional] pub fn swap_exact_amount_in( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, #[pallet::compact] asset_amount_in: BalanceOf, - asset_out: Asset>, + asset_out: AssetOf, min_asset_amount_out: Option>, max_price: Option>, ) -> DispatchResultWithPostInfo { @@ -702,7 +437,6 @@ mod pallet { asset_out, min_asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } @@ -725,16 +459,15 @@ mod pallet { /// /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of /// assets if the scoring rule is Rikiddo. - // TODO(#790): Replace with maximum of CPMM and Rikiddo benchmark! #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::swap_exact_amount_out_cpmm())] #[transactional] pub fn swap_exact_amount_out( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, max_asset_amount_in: Option>, - asset_out: Asset>, + asset_out: AssetOf, #[pallet::compact] asset_amount_out: BalanceOf, max_price: Option>, ) -> DispatchResultWithPostInfo { @@ -747,59 +480,50 @@ mod pallet { asset_out, asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } + + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::pool_exit( + min_assets_out.len().min(T::MaxAssets::get().into()) as u32 + ))] + #[transactional] + pub fn force_pool_exit( + origin: OriginFor, + who: T::AccountId, + #[pallet::compact] pool_id: PoolId, + #[pallet::compact] pool_amount: BalanceOf, + min_assets_out: Vec>, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) + } } #[pallet::config] pub trait Config: frame_system::Config { + /// Shares of outcome assets and native currency + type AssetManager: ZeitgeistAssetManager; + + type Asset: Parameter + + Member + + Copy + + MaxEncodedLen + + MaybeSerializeDeserialize + + Ord + + TypeInfo + + PoolSharesId>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The weight information for swap's dispatchable functions. + type WeightInfo: WeightInfoZeitgeist; + /// The fee for exiting a pool. #[pallet::constant] type ExitFee: Get>; - /// Will be used for the fractional part of the fixed point numbers - /// Calculation: Select FixedTYPE, such that TYPE = the type of Balance (i.e. FixedU128) - /// Select the generic UWIDTH = floor(log2(10.pow(fractional_decimals))) - type FixedTypeU: Decode - + Encode - + FixedUnsigned - + From - + LossyFrom> - + LossyFrom> - + LossyFrom>; - - /// Will be used for the fractional part of the fixed point numbers - /// Calculation: Select FixedTYPE, such that it is the signed variant of FixedTypeU - /// It is possible to reduce the fractional bit count by one, effectively eliminating - /// conversion overflows when the MSB of the unsigned fixed type is set, but in exchange - /// Reducing the fractional precision by one bit. - type FixedTypeS: Decode - + Encode - + FixedSigned - + From - + LossyFrom> - + LossyFrom> - + LossyFrom - + LossyFrom> - + PartialOrd; - - type LiquidityMining: LiquidityMiningPalletApi< - AccountId = Self::AccountId, - Balance = BalanceOf, - BlockNumber = Self::BlockNumber, - MarketId = MarketIdOf, - >; - - type MarketCommons: MarketCommonsPalletApi< - AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, - Balance = BalanceOf, - >; - #[pallet::constant] type MaxAssets: Get; @@ -822,40 +546,12 @@ mod pallet { /// The minimum amount of assets in a pool. type MinAssets: Get; - /// The minimum amount of subsidy required to state transit a market into active state. - /// Must be greater than 0, but can be arbitrarily close to 0. - #[pallet::constant] - type MinSubsidy: Get>; - - /// The minimum amount of subsidy that each subsidy provider must contribute. - #[pallet::constant] - type MinSubsidyPerAccount: Get>; - #[pallet::constant] type MinWeight: Get; /// The module identifier. #[pallet::constant] type PalletId: Get; - - /// The Rikiddo instance that uses a sigmoid fee and ema of market volume - type RikiddoSigmoidFeeMarketEma: RikiddoMVPallet< - Balance = BalanceOf, - PoolId = PoolId, - FU = Self::FixedTypeU, - Rikiddo = RikiddoSigmoidMV< - Self::FixedTypeU, - Self::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - >, - >; - - /// Shares of outcome assets and native currency - type AssetManager: ZeitgeistAssetManager>>; - - /// The weight information for swap's dispatchable functions. - type WeightInfo: WeightInfoZeitgeist; } #[pallet::error] @@ -876,17 +572,12 @@ mod pallet { InsufficientBalance, /// Liquidity provided to new CPMM pool is less than the minimum allowed balance. InsufficientLiquidity, - /// The market was not started since the subsidy goal was not reached. - InsufficientSubsidy, /// Could not create CPMM pool since no amount was specified. InvalidAmountArgument, /// Could not create CPMM pool since no fee was supplied. InvalidFeeArgument, /// Dispatch called on pool with invalid status. InvalidPoolStatus, - /// A function that is only valid for pools with specific scoring rules was called for a - /// pool with another scoring rule. - InvalidScoringRule, /// A function was called for a swaps pool that does not fulfill the state requirement. InvalidStateTransition, /// Could not create CPMM pool since no weights were supplied. @@ -911,9 +602,6 @@ mod pallet { /// The total weight of all assets within a CPMM pool is above a treshhold specified /// by a constant. MaxTotalWeight, - /// It was tried to remove subsidy from a pool which does not have subsidy provided by - /// the address that tried to remove the subsidy. - NoSubsidyProvided, /// The pool in question does not exist. PoolDoesNotExist, /// A pool balance dropped below the allowed minimum. @@ -946,6 +634,20 @@ mod pallet { WinningAssetNotFound, /// Some amount in a transaction equals zero. ZeroAmount, + /// An unexpected error occurred. This is the result of faulty pallet logic and should be + /// reported to the pallet maintainers. + Unexpected(UnexpectedError), + } + + #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] + pub enum UnexpectedError { + StorageOverflow, + } + + impl From for Error { + fn from(error: UnexpectedError) -> Error { + Error::::Unexpected(error) + } } #[pallet::event] @@ -954,18 +656,12 @@ mod pallet { where T: Config, { - /// Buy-burn arbitrage was executed on a CPMM pool. \[pool_id, amount\] - ArbitrageBuyBurn(PoolId, BalanceOf), - /// Mint-sell arbitrage was executed on a CPMM pool. \[pool_id, amount\] - ArbitrageMintSell(PoolId, BalanceOf), - /// Arbitrage was skipped on a CPMM pool. \[pool_id\] - ArbitrageSkipped(PoolId), /// Share holder rewards were distributed. \[pool_id, num_accounts_rewarded, amount\] DistributeShareHolderRewards(PoolId, u64, BalanceOf), /// A new pool has been created. \[CommonPoolEventParams, pool, pool_amount, pool_account\] PoolCreate( CommonPoolEventParams<::AccountId>, - Pool, MarketIdOf>, + PoolOf, BalanceOf, T::AccountId, ), @@ -976,71 +672,24 @@ mod pallet { /// A pool was opened. \[pool_id\] PoolActive(PoolId), /// Someone has exited a pool. \[PoolAssetsEvent\] - PoolExit( - PoolAssetsEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Someone has (partially) exited a pool by removing subsidy. \[asset, bound, pool_id, who, amount\] - PoolExitSubsidy( - Asset>, - BalanceOf, - CommonPoolEventParams<::AccountId>, - BalanceOf, - ), + PoolExit(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), /// Exits a pool given an exact amount of an asset. \[PoolAssetEvent\] PoolExitWithExactAssetAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Exits a pool given an exact pool's amount. \[PoolAssetEvent\] PoolExitWithExactPoolAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Someone has joined a pool. \[PoolAssetsEvent\] - PoolJoin( - PoolAssetsEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Someone has joined a pool by providing subsidy. \[asset, amount, pool_id, who\] - PoolJoinSubsidy( - Asset>, - BalanceOf, - CommonPoolEventParams<::AccountId>, - ), + PoolJoin(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), /// Joins a pool given an exact amount of an asset. \[PoolAssetEvent\] PoolJoinWithExactAssetAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Joins a pool given an exact pool's amount. \[PoolAssetEvent\] PoolJoinWithExactPoolAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Total subsidy collected for a pool. \[pool_id, \[(provider, subsidy), ...\], total_subsidy\] - SubsidyCollected( - PoolId, - Vec<(::AccountId, BalanceOf)>, - BalanceOf, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Pool was manually destroyed. \[pool_id\] PoolDestroyed(PoolId), @@ -1051,29 +700,11 @@ mod pallet { ), /// An exact amount of an asset is entering the pool. \[SwapEvent\] SwapExactAmountIn( - SwapEvent<::AccountId, Asset>, BalanceOf>, + SwapEvent<::AccountId, AssetOf, BalanceOf>, ), /// An exact amount of an asset is leaving the pool. \[SwapEvent\] SwapExactAmountOut( - SwapEvent<::AccountId, Asset>, BalanceOf>, - ), - /// Fees were paid to the market creator. \[market_id , payer, payee, amount, asset\] - MarketCreatorFeesPaid( - MarketIdOf, - ::AccountId, - ::AccountId, - BalanceOf, - Asset>, - ), - /// Fee payment to market creator failed (usually due to existential deposit requirements) - /// \[market_id, payer, payee, amount, asset, error\] - MarketCreatorFeePaymentFailed( - MarketIdOf, - ::AccountId, - ::AccountId, - BalanceOf, - Asset>, - DispatchError, + SwapEvent<::AccountId, AssetOf, BalanceOf>, ), } @@ -1084,18 +715,15 @@ mod pallet { #[pallet::storage] #[pallet::getter(fn pools)] - pub type Pools = StorageMap< - _, - Blake2_128Concat, - PoolId, - Option, MarketIdOf>>, - ValueQuery, - >; + pub(crate) type Pools = + StorageMap<_, Blake2_128Concat, PoolId, PoolOf, OptionQuery>; + // TODO(#1212): Remove in v0.5.1. #[pallet::storage] #[pallet::getter(fn pools_cached_for_arbitrage)] pub type PoolsCachedForArbitrage = StorageMap<_, Twox64Concat, PoolId, ()>; + // TODO(#1212): Remove in v0.5.1. #[pallet::storage] #[pallet::getter(fn subsidy_providers)] pub type SubsidyProviders = @@ -1103,419 +731,76 @@ mod pallet { #[pallet::storage] #[pallet::getter(fn next_pool_id)] - pub type NextPoolId = StorageValue<_, PoolId, ValueQuery>; - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight { - if remaining_weight.all_lt(ON_IDLE_MIN_WEIGHT) { - return Weight::zero(); - } - Self::execute_arbitrage_all(remaining_weight / 2) - } - } + pub(crate) type NextPoolId = StorageValue<_, PoolId, ValueQuery>; impl Pallet { - pub(crate) fn distribute_pool_share_rewards( - pool: &Pool, MarketIdOf>, - pool_id: PoolId, - base_asset: Asset>, - winning_asset: Asset>, - winner_payout_account: &T::AccountId, - ) -> Weight { - // CPMM handling of market profit not supported - if pool.scoring_rule == ScoringRule::CPMM { - return Weight::from_ref_time(1_000_000); - } - - // Total pool shares - let shares_id = Self::pool_shares_id(pool_id); - let total_pool_shares = T::AssetManager::total_issuance(shares_id); - - // Total AMM balance - let pool_account = Self::pool_account_id(&pool_id); - let total_amm_funds = T::AssetManager::free_balance(base_asset, &pool_account); - - // Total winning shares - // The pool account still holds the winning asset, burn it. - let free_balance = T::AssetManager::free_balance(winning_asset, &pool_account); - let _ = T::AssetManager::withdraw(winning_asset, &pool_account, free_balance); - let total_winning_assets = T::AssetManager::total_issuance(winning_asset); - - // Profit = AMM balance - total winning shares - let amm_profit_checked = total_amm_funds.checked_sub(&total_winning_assets); - let amm_profit = if let Some(profit) = amm_profit_checked { - profit - } else { - // In case the AMM balance does not suffice to pay out every winner, the pool - // rewards will not be distributed (requires investigation and update). - log::error!( - target: LOG_TARGET, - "The AMM balance does not suffice to pay out every winner. - market_id: {:?}, pool_id: {:?}, total AMM balance: {:?}, total reward value: \ - {:?}", - pool.market_id, - pool_id, - total_amm_funds, - total_winning_assets - ); - return T::DbWeight::get().reads(6); - }; - - // Iterate through every share holder and exchange shares for rewards. - let (total_accounts_num, share_accounts) = - T::AssetManager::accounts_by_currency_id(shares_id).unwrap_or((0usize, vec![])); - let share_accounts_num = share_accounts.len(); - - for share_holder in share_accounts { - let share_holder_account = share_holder.0; - let share_holder_balance = share_holder.1.free; - let reward_pct_unadjusted = - share_holder_balance.bdiv(total_pool_shares).unwrap_or(0u8.into()); - - // Seems like bdiv does arithmetic rounding. To ensure that we will have enough - // reward for everyone and not run into an error, we'll round down in any case. - let reward_pct = reward_pct_unadjusted.saturating_sub(1u8.into()); - let holder_reward_unadjusted = amm_profit.bmul(reward_pct).unwrap_or(0u8.into()); - - // Same for bmul. - let holder_reward = holder_reward_unadjusted.saturating_sub(1u8.into()); - - let transfer_result = T::AssetManager::transfer( - base_asset, - &pool_account, - &share_holder_account, - holder_reward.saturated_into(), - ); - - // Should be impossible. - if let Err(err) = transfer_result { - let current_amm_holding = - T::AssetManager::free_balance(base_asset, &pool_account); - log::error!( - target: LOG_TARGET, - "The AMM failed to pay out the share holder reward. - market_id: {:?}, pool_id: {:?}, current AMM holding: {:?}, - transfer size: {:?}, to: {:?}, error: {:?}", - pool.market_id, - pool_id, - current_amm_holding, - holder_reward, - share_holder_account, - err, - ); - - if current_amm_holding < holder_reward.saturated_into() { - let _ = T::AssetManager::transfer( - base_asset, - &pool_account, - &share_holder_account, - current_amm_holding.saturated_into(), - ); - } - } - - // We can use the lightweight withdraw here, since pool shares are not reserved. - // We can ignore the result since the balance to transfer is the query result of - // the free balance (always sufficient). - let _ = T::AssetManager::withdraw( - shares_id, - &share_holder_account, - share_holder_balance, - ); - } - - let remaining_pool_funds = T::AssetManager::free_balance(base_asset, &pool_account); - // Transfer winner payout - Infallible since the balance was just read from storage. - let _ = T::AssetManager::transfer( - base_asset, - &pool_account, - winner_payout_account, - remaining_pool_funds, - ); - - Self::deposit_event(Event::::DistributeShareHolderRewards( - pool_id, - share_accounts_num.saturated_into(), - amm_profit, - )); - - T::WeightInfo::distribute_pool_share_rewards( - total_accounts_num.saturated_into(), - share_accounts_num.saturated_into(), - ) - } - - /// Execute arbitrage on as many cached pools until `weight` is spent. - /// - /// Arguments: - /// - /// * `weight`: The maximum amount of weight allowed to spend by this function. - fn execute_arbitrage_all(weight: Weight) -> Weight { - // The time complexity of `apply_cached_pools` is `O(pool_count)`; we calculate the - // minimum number of pools we can handle. - let overhead = T::WeightInfo::apply_to_cached_pools_execute_arbitrage(0); - let extra_weight_per_pool = - T::WeightInfo::apply_to_cached_pools_execute_arbitrage(1).saturating_sub(overhead); - // The division can fail if the benchmark of `apply_to_cached_pools` is not linear in - // the number of pools. This shouldn't ever happen, but if it does, we ensure that - // `pool_count` is zero (this isn't really a runtime error). - let weight_minus_overhead = weight.saturating_sub(overhead); - let max_pool_count_by_ref_time = weight_minus_overhead - .ref_time() - .checked_div(extra_weight_per_pool.ref_time()) - .unwrap_or_else(|| { - debug_assert!( - false, - "Unexpected zero division when calculating arbitrage ref time" - ); - 0_u64 - }); - let max_pool_count_by_proof_size = weight_minus_overhead - .proof_size() - .checked_div(extra_weight_per_pool.proof_size()) - .unwrap_or_else(|| { - debug_assert!( - false, - "Unexpected zero division when calculating arbitrage proof size" - ); - 0_u64 - }); - let max_pool_count = max_pool_count_by_ref_time.min(max_pool_count_by_proof_size); - if max_pool_count == 0_u64 { - return weight; - } - Self::apply_to_cached_pools( - max_pool_count.saturated_into(), - |pool_id| Self::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS), - extra_weight_per_pool, - ) - } - - /// Apply a `mutation` to all pools cached for arbitrage (but at most `pool_count` many) - /// and return the actual weight consumed. - /// - /// Arguments: - /// - /// * `pool_count`: The maximum number of pools to apply the `mutation` to. - /// * `mutation`: The `mutation` that is applied to the pools. - /// * `max_weight_per_pool`: The maximum weight consumed by `mutation` per pool. - pub(crate) fn apply_to_cached_pools( - pool_count: u32, - mutation: F, - max_weight_per_pool: Weight, - ) -> Weight - where - F: Fn(PoolId) -> Result, - { - let mut total_weight = T::WeightInfo::apply_to_cached_pools_noop(pool_count); - for (pool_id, _) in PoolsCachedForArbitrage::::drain().take(pool_count as usize) { - // The mutation should never fail, but if it does, we just assume we - // consumed all the weight and rollback the pool. - let _ = with_transaction(|| match mutation(pool_id) { - Err(err) => { - log::warn!( - target: LOG_TARGET, - "Arbitrage unexpectedly failed on pool {:?} with error: {:?}", - pool_id, - err, - ); - total_weight = total_weight.saturating_add(max_weight_per_pool); - TransactionOutcome::Rollback(err.into()) - } - Ok(weight) => { - total_weight = total_weight.saturating_add(weight); - TransactionOutcome::Commit(Ok(())) - } - }); - } - total_weight - } - - /// Execute arbitrage on a single pool. - /// - /// Arguments: - /// - /// * `pool_id`: The id of the pool to arbitrage. - /// * `max_iterations`: The maximum number of iterations allowed in the bisection method. - pub(crate) fn execute_arbitrage( + fn do_pool_exit( + who: T::AccountId, pool_id: PoolId, - max_iterations: usize, - ) -> Result { + pool_amount: BalanceOf, + min_assets_out: Vec>, + ) -> DispatchResult { + ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); + let who_clone = who.clone(); let pool = Self::pool_by_id(pool_id)?; - let pool_account = Self::pool_account_id(&pool_id); - let balances = pool - .assets - .iter() - .map(|a| (*a, T::AssetManager::free_balance(*a, &pool_account))) - .collect::>(); - let asset_count = pool.assets.len() as u32; - let outcome_tokens = pool.assets.iter().filter(|a| **a != pool.base_asset); - let total_spot_price = pool.calc_total_spot_price(&balances)?; - - if total_spot_price - > ZeitgeistBase::::get()?.checked_add_res(&ARBITRAGE_THRESHOLD)? - { - let (amount, _) = - pool.calc_arbitrage_amount_mint_sell(&balances, max_iterations)?; - // Ensure that executing arbitrage doesn't decrease the base asset balance below - // the minimum allowed balance. - let balance_base_asset = - balances.get(&pool.base_asset).ok_or(Error::::AssetNotInPool)?; - let max_amount = - balance_base_asset.saturating_sub(Self::min_balance(pool.base_asset)); - let amount = amount.min(max_amount); - // We're faking a `buy_complete_sets` operation by transfering to the market prize - // pool. - T::AssetManager::transfer( - pool.base_asset, - &pool_account, - &T::MarketCommons::market_account(pool.market_id), - amount, - )?; - for t in outcome_tokens { - T::AssetManager::deposit(*t, &pool_account, amount)?; - } - Self::deposit_event(Event::ArbitrageMintSell(pool_id, amount)); - Ok(T::WeightInfo::execute_arbitrage_mint_sell(asset_count)) - } else if total_spot_price - < ZeitgeistBase::::get()?.checked_sub_res(&ARBITRAGE_THRESHOLD)? - { - let (amount, _) = pool.calc_arbitrage_amount_buy_burn(&balances, max_iterations)?; - // Ensure that executing arbitrage doesn't decrease the outcome asset balances below - // the minimum allowed balance. - let (smallest_outcome, smallest_outcome_balance) = balances - .iter() - .filter(|(k, _)| **k != pool.base_asset) - .min_by(|(_, v1), (_, v2)| v1.cmp(v2)) - .ok_or(Error::::AssetNotInPool)?; - let max_amount = - smallest_outcome_balance.saturating_sub(Self::min_balance(*smallest_outcome)); - let amount = amount.min(max_amount); - T::AssetManager::transfer( - pool.base_asset, - &T::MarketCommons::market_account(pool.market_id), - &pool_account, - amount, - )?; - for t in outcome_tokens { - T::AssetManager::withdraw(*t, &pool_account, amount)?; - } - Self::deposit_event(Event::ArbitrageBuyBurn(pool_id, amount)); - Ok(T::WeightInfo::execute_arbitrage_buy_burn(asset_count)) - } else { - Self::deposit_event(Event::ArbitrageSkipped(pool_id)); - Ok(T::WeightInfo::execute_arbitrage_skipped(asset_count)) - } + // If the pool is still in use, prevent a pool drain. + Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; + let pool_account_id = Pallet::::pool_account_id(&pool_id); + let params = PoolParams { + asset_bounds: min_assets_out, + event: |evt| Self::deposit_event(Event::PoolExit(evt)), + pool_account_id: &pool_account_id, + pool_amount, + pool_id, + pool: &pool, + transfer_asset: |amount, amount_bound, asset| { + Self::ensure_minimum_balance(pool_id, &pool, asset, amount)?; + ensure!(amount >= amount_bound, Error::::LimitOut); + T::AssetManager::transfer(asset, &pool_account_id, &who, amount)?; + Ok(()) + }, + transfer_pool: || { + Self::burn_pool_shares(pool_id, &who, pool_amount)?; + Ok(()) + }, + fee: |amount: BalanceOf| { + let exit_fee_amount = amount.bmul(Self::calc_exit_fee(&pool))?; + Ok(exit_fee_amount) + }, + who: who_clone, + }; + crate::utils::pool::<_, _, _, _, T>(params) } pub fn get_spot_price( pool_id: &PoolId, - asset_in: &Asset>, - asset_out: &Asset>, + asset_in: &AssetOf, + asset_out: &AssetOf, with_fees: bool, ) -> Result, DispatchError> { let pool = Self::pool_by_id(*pool_id)?; ensure!(pool.assets.binary_search(asset_in).is_ok(), Error::::AssetNotInPool); ensure!(pool.assets.binary_search(asset_out).is_ok(), Error::::AssetNotInPool); let pool_account = Self::pool_account_id(pool_id); + let balance_in = T::AssetManager::free_balance(*asset_in, &pool_account); + let balance_out = T::AssetManager::free_balance(*asset_out, &pool_account); + let in_weight = Self::pool_weight_rslt(&pool, asset_in)?; + let out_weight = Self::pool_weight_rslt(&pool, asset_out)?; - if pool.scoring_rule == ScoringRule::CPMM { - let balance_in = T::AssetManager::free_balance(*asset_in, &pool_account); - let balance_out = T::AssetManager::free_balance(*asset_out, &pool_account); - let in_weight = Self::pool_weight_rslt(&pool, asset_in)?; - let out_weight = Self::pool_weight_rslt(&pool, asset_out)?; - - let swap_fee = if with_fees { - let swap_fee = pool.swap_fee.ok_or(Error::::SwapFeeMissing)?; - let market = T::MarketCommons::market(&pool.market_id)?; - market - .creator_fee - .mul_floor(ZeitgeistBase::::get()?) - .checked_add(swap_fee.try_into().map_err(|_| Error::::SwapFeeTooHigh)?) - .ok_or(Error::::SwapFeeTooHigh)? - } else { - BalanceOf::::zero().saturated_into() - }; - - return Ok(crate::math::calc_spot_price( - balance_in.saturated_into(), - in_weight, - balance_out.saturated_into(), - out_weight, - swap_fee, - )? - .saturated_into()); - } - - // TODO(#880): For now rikiddo does not respect with_fees flag. - // Price when using Rikiddo. - ensure!(pool.pool_status == PoolStatus::Active, Error::::PoolIsNotActive); - let mut balances = Vec::new(); - let base_asset = pool.base_asset; - - // Fees are estimated here. The error scales with the fee. For the future, we'll have - // to figure out how to extract the fee out of the price when using Rikiddo. - if asset_in == asset_out { - return T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)? - .checked_add_res(&ZeitgeistBase::get()?); - } - - let mut balance_in = BalanceOf::::zero(); - let mut balance_out = BalanceOf::::zero(); - - for asset in pool.assets.iter().filter(|asset| **asset != base_asset) { - let issuance = T::AssetManager::total_issuance(*asset); - - if asset == asset_in { - balance_in = issuance; - } else if asset == asset_out { - balance_out = issuance; - } - - balances.push(issuance); - } - - if *asset_in == base_asset { - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_out, &balances) - } else if *asset_out == base_asset { - let price_with_inverse_fee = ZeitgeistBase::>::get()? - .bdiv(T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_in, &balances)?)?; - let fee_pct = T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)?; - let fee_plus_one = - ZeitgeistBase::>::get()?.checked_add_res(&fee_pct)?; - let price_with_fee = - fee_plus_one.bmul(price_with_inverse_fee.bmul(fee_plus_one)?)?; - Ok(price_with_fee) + let swap_fee = if with_fees { + pool.swap_fee.saturated_into() } else { - let price_without_fee = - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_out, &balances)?.bdiv( - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_in, &balances)?, - )?; - let fee_pct = T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)?; - let fee_plus_one = - ZeitgeistBase::>::get()?.checked_add_res(&fee_pct)?; - let price_with_fee = fee_plus_one.bmul(price_without_fee)?; - Ok(price_with_fee) - } - } + BalanceOf::::zero().saturated_into() + }; - // Returns vector of pairs `(a, p)` where `a` ranges over all assets in the pool and `p` is - // the spot price of swapping the base asset for `a` (including swap fees if `with_fees` is - // `true`). - pub fn get_all_spot_prices( - pool_id: &PoolId, - with_fees: bool, - ) -> Result>, BalanceOf)>, DispatchError> { - let pool = Self::pool_by_id(*pool_id)?; - pool.assets - .into_iter() - .map(|asset| { - let spot_price = - Self::get_spot_price(pool_id, &pool.base_asset, &asset, with_fees)?; - Ok((asset, spot_price)) - }) - .collect() + Ok(crate::math::calc_spot_price( + balance_in.saturated_into(), + in_weight, + balance_out.saturated_into(), + out_weight, + swap_fee, + )? + .saturated_into()) } #[inline] @@ -1524,7 +809,7 @@ mod pallet { } /// The minimum allowed balance of `asset` in a liquidity pool. - pub(crate) fn min_balance(asset: Asset>) -> BalanceOf { + pub(crate) fn min_balance(asset: AssetOf) -> BalanceOf { T::AssetManager::minimum_balance(asset).max(MIN_BALANCE.saturated_into()) } @@ -1537,10 +822,7 @@ mod pallet { /// /// **Should** only be called if `assets` is non-empty. Note that the existence of a pool /// with the specified `pool_id` is not mandatory. - pub(crate) fn min_balance_of_pool( - pool_id: PoolId, - assets: &[Asset>], - ) -> BalanceOf { + pub(crate) fn min_balance_of_pool(pool_id: PoolId, assets: &[AssetOf]) -> BalanceOf { assets .iter() .map(|asset| Self::min_balance(*asset)) @@ -1551,10 +833,10 @@ mod pallet { fn ensure_minimum_liquidity_shares( pool_id: PoolId, - pool: &Pool, MarketIdOf>, + pool: &PoolOf, amount: BalanceOf, ) -> DispatchResult { - if pool.pool_status == PoolStatus::Clean { + if pool.status == PoolStatus::Closed { return Ok(()); } let pool_shares_id = Self::pool_shares_id(pool_id); @@ -1567,12 +849,12 @@ mod pallet { fn ensure_minimum_balance( pool_id: PoolId, - pool: &Pool, MarketIdOf>, - asset: Asset>, + pool: &PoolOf, + asset: AssetOf, amount: BalanceOf, ) -> DispatchResult { // No need to prevent a clean pool from getting drained. - if pool.pool_status == PoolStatus::Clean { + if pool.status == PoolStatus::Closed { return Ok(()); } let pool_account = Self::pool_account_id(&pool_id); @@ -1582,69 +864,6 @@ mod pallet { Ok(()) } - fn handle_creator_fee_transfer( - market_id: MarketIdOf, - fee_asset: Asset>, - payer: T::AccountId, - payee: T::AccountId, - fee_amount: BalanceOf, - ) { - if let Err(err) = T::AssetManager::transfer(fee_asset, &payer, &payee, fee_amount) { - Self::deposit_event(Event::MarketCreatorFeePaymentFailed( - market_id, payer, payee, fee_amount, fee_asset, err, - )); - } else { - Self::deposit_event(Event::MarketCreatorFeesPaid( - market_id, payer, payee, fee_amount, fee_asset, - )); - } - } - - // Infallible, should fee transfer fail, the informant will keep the fees and an event is emitted. - #[allow(clippy::too_many_arguments)] - fn handle_creator_fees( - amount: BalanceOf, - fee_asset: Asset>, - base_asset: Asset>, - fee: Perbill, - payee: T::AccountId, - payer: T::AccountId, - pool_id: PoolId, - market_id: MarketIdOf, - ) { - if fee.is_zero() || payee == payer { - return; - }; - - let mut fee_amount = fee.mul_floor(amount); - - if fee_asset != base_asset { - let balance_before = T::AssetManager::free_balance(base_asset, &payer); - let swap_result = >::swap_exact_amount_in( - payer.clone(), - pool_id, - fee_asset, - fee_amount, - base_asset, - None, - Some(>::saturated_from(u128::MAX)), - true, - ); - - if swap_result.is_err() { - Self::handle_creator_fee_transfer( - market_id, fee_asset, payer, payee, fee_amount, - ); - return; - } - - let balance_after = T::AssetManager::free_balance(base_asset, &payer); - fee_amount = balance_after.saturating_sub(balance_before); - } - - Self::handle_creator_fee_transfer(market_id, base_asset, payer, payee, fee_amount); - } - pub(crate) fn burn_pool_shares( pool_id: PoolId, from: &T::AccountId, @@ -1667,7 +886,7 @@ mod pallet { #[inline] pub(crate) fn check_provided_values_len_must_equal_assets_len( - assets: &[Asset>], + assets: &[AssetOf], provided_values: &[U], ) -> Result<(), Error> where @@ -1679,11 +898,9 @@ mod pallet { Ok(()) } - pub(crate) fn check_if_pool_is_active( - pool: &Pool, MarketIdOf>, - ) -> DispatchResult { - match pool.pool_status { - PoolStatus::Active => Ok(()), + pub(crate) fn ensure_pool_is_active(pool: &PoolOf) -> DispatchResult { + match pool.status { + PoolStatus::Open => Ok(()), _ => Err(Error::::PoolIsNotActive.into()), } } @@ -1697,13 +914,11 @@ mod pallet { T::AssetManager::deposit(shares_id, to, amount) } - pub(crate) fn pool_shares_id(pool_id: PoolId) -> Asset> { - Asset::PoolShare(SerdeWrapper(pool_id)) + pub(crate) fn pool_shares_id(pool_id: PoolId) -> AssetOf { + T::Asset::pool_shares_id(SerdeWrapper(pool_id)) } - pub fn pool_by_id( - pool_id: PoolId, - ) -> Result, MarketIdOf>, DispatchError> + pub fn pool_by_id(pool_id: PoolId) -> Result, DispatchError> where T: Config, { @@ -1722,7 +937,7 @@ mod pallet { // Mutates a stored pool. Returns `Err` if `pool_id` does not exist. pub(crate) fn mutate_pool(pool_id: PoolId, mut cb: F) -> DispatchResult where - F: FnMut(&mut Pool, MarketIdOf>) -> DispatchResult, + F: FnMut(&mut PoolOf) -> DispatchResult, { >::try_mutate(pool_id, |pool| { let pool = if let Some(el) = pool { @@ -1734,81 +949,16 @@ mod pallet { }) } - fn pool_weight_rslt( - pool: &Pool, MarketIdOf>, - asset: &Asset>, - ) -> Result> { - pool.weights - .as_ref() - .ok_or(Error::::PoolMissingWeight)? - .get(asset) - .cloned() - .ok_or(Error::::AssetNotBound) - } - - /// Remove losing assets from the pool and distribute Rikiddo rewards. - /// - /// # Weight - /// - /// Complexity: `O(n)` where `n` is the number of assets in the pool. - pub(crate) fn clean_up_pool_categorical( - pool_id: PoolId, - outcome_report: &OutcomeReport, - winner_payout_account: &T::AccountId, - ) -> Result { - let mut extra_weight = Weight::zero(); - let mut total_assets = 0; - - Self::mutate_pool(pool_id, |pool| { - // Find winning asset, remove losing assets from pool - let base_asset = pool.base_asset; - let mut winning_asset: Result<_, DispatchError> = - Err(Error::::WinningAssetNotFound.into()); - if let OutcomeReport::Categorical(winning_asset_idx) = outcome_report { - pool.assets.retain(|el| { - if let Asset::CategoricalOutcome(_, idx) = *el { - if idx == *winning_asset_idx { - winning_asset = Ok(*el); - return true; - }; - } - - *el == base_asset - }); - } - - total_assets = pool.assets.len(); - - let winning_asset_unwrapped = winning_asset?; - - if pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma { - T::RikiddoSigmoidFeeMarketEma::destroy(pool_id)?; - let distribute_weight = Self::distribute_pool_share_rewards( - pool, - pool_id, - base_asset, - winning_asset_unwrapped, - winner_payout_account, - ); - extra_weight = extra_weight.saturating_add(T::DbWeight::get().writes(1)); - extra_weight = extra_weight.saturating_add(distribute_weight); - } - - Ok(()) - })?; - - Ok(T::WeightInfo::clean_up_pool_categorical_without_reward_distribution( - total_assets.saturated_into(), - ) - .saturating_add(extra_weight)) + fn pool_weight_rslt(pool: &PoolOf, asset: &AssetOf) -> Result> { + pool.weights.get(asset).cloned().ok_or(Error::::AssetNotBound) } /// Calculate the exit fee percentage for `pool`. - fn calc_exit_fee(pool: &Pool, MarketIdOf>) -> BalanceOf { + fn calc_exit_fee(pool: &PoolOf) -> BalanceOf { // We don't charge exit fees on closed or cleaned up pools (no need to punish LPs for // leaving the pool)! - match pool.pool_status { - PoolStatus::Active => T::ExitFee::get().saturated_into(), + match pool.status { + PoolStatus::Open => T::ExitFee::get().saturated_into(), _ => 0u128.saturated_into(), } } @@ -1818,8 +968,8 @@ mod pallet { where T: Config, { + type Asset = AssetOf; type Balance = BalanceOf; - type MarketId = MarketIdOf; /// Creates an initial active pool. /// @@ -1830,7 +980,6 @@ mod pallet { /// * `assets`: The assets that are used in the pool. /// * `base_asset`: The base asset in a prediction market swap pool (usually a currency). /// * `market_id`: The market id of the market the pool belongs to. - /// * `scoring_rule`: The scoring rule that's used to determine the asset prices. /// * `swap_fee`: The fee applied to each swap on a CPMM pool, specified as fixed-point /// ratio (0.1 equals 10% swap fee) /// * `amount`: The amount of each asset added to the pool; **may** be `None` only if @@ -1839,24 +988,18 @@ mod pallet { #[frame_support::transactional] fn create_pool( who: T::AccountId, - assets: Vec>>, - base_asset: Asset>, - market_id: MarketIdOf, - scoring_rule: ScoringRule, - swap_fee: Option>, - amount: Option>, - weights: Option>, + assets: Vec>, + swap_fee: BalanceOf, + amount: BalanceOf, + weights: Vec, ) -> Result { ensure!(assets.len() <= usize::from(T::MaxAssets::get()), Error::::TooManyAssets); ensure!(assets.len() >= usize::from(T::MinAssets::get()), Error::::TooFewAssets); - ensure!(assets.contains(&base_asset), Error::::BaseAssetNotFound); let next_pool_id = Self::inc_next_pool_id()?; let pool_shares_id = Self::pool_shares_id(next_pool_id); let pool_account = Self::pool_account_id(&next_pool_id); let mut map = BTreeMap::new(); - let market = T::MarketCommons::market(&market_id)?; let mut total_weight = 0; - let amount_unwrapped = amount.unwrap_or_else(BalanceOf::::zero); let mut sorted_assets = assets.clone(); sorted_assets.sort(); let has_duplicates = sorted_assets @@ -1865,119 +1008,48 @@ mod pallet { .fold(false, |acc, (&x, &y)| acc || x == y); ensure!(!has_duplicates, Error::::SomeIdenticalAssets); - let (pool_status, total_subsidy, total_weight, weights, pool_amount) = - match scoring_rule { - ScoringRule::CPMM => { - ensure!(amount.is_some(), Error::::InvalidAmountArgument); - // `amount` must be larger than all minimum balances. As we deposit `amount` - // liquidity shares, we must also ensure that `amount` is larger than the - // existential deposit of the liquidity shares. - ensure!( - amount_unwrapped >= Self::min_balance_of_pool(next_pool_id, &assets), - Error::::InsufficientLiquidity - ); - - let swap_fee_unwrapped = swap_fee.ok_or(Error::::InvalidFeeArgument)?; - let total_fee = market - .creator_fee - .mul_floor(ZeitgeistBase::::get()?) - .checked_add( - swap_fee_unwrapped - .try_into() - .map_err(|_| Error::::SwapFeeTooHigh)?, - ) - .ok_or(Error::::SwapFeeTooHigh)?; - - let total_fee_as_balance = >::try_from(total_fee) - .map_err(|_| Error::::SwapFeeTooHigh)?; - - ensure!( - total_fee_as_balance <= T::MaxSwapFee::get(), - Error::::SwapFeeTooHigh - ); - ensure!( - total_fee <= ZeitgeistBase::::get()?, - Error::::SwapFeeTooHigh - ); - - let weights_unwrapped = weights.ok_or(Error::::InvalidWeightArgument)?; - Self::check_provided_values_len_must_equal_assets_len( - &assets, - &weights_unwrapped, - )?; - - for (asset, weight) in assets.iter().copied().zip(weights_unwrapped) { - let free_balance = T::AssetManager::free_balance(asset, &who); - ensure!( - free_balance >= amount_unwrapped, - Error::::InsufficientBalance - ); - ensure!(weight >= T::MinWeight::get(), Error::::BelowMinimumWeight); - ensure!(weight <= T::MaxWeight::get(), Error::::AboveMaximumWeight); - map.insert(asset, weight); - total_weight = total_weight.checked_add_res(&weight)?; - T::AssetManager::transfer( - asset, - &who, - &pool_account, - amount_unwrapped, - )?; - } - - ensure!( - total_weight <= T::MaxTotalWeight::get(), - Error::::MaxTotalWeight - ); - T::AssetManager::deposit(pool_shares_id, &who, amount_unwrapped)?; - - let pool_status = PoolStatus::Initialized; - let total_subsidy = None; - let total_weight = Some(total_weight); - let weights = Some(map); - let pool_amount = amount_unwrapped; - (pool_status, total_subsidy, total_weight, weights, pool_amount) - } - ScoringRule::RikiddoSigmoidFeeMarketEma => { - let mut rikiddo_instance: RikiddoSigmoidMV< - T::FixedTypeU, - T::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - > = Default::default(); - rikiddo_instance.ma_short.config.ema_period = EMA_SHORT; - rikiddo_instance.ma_long.config.ema_period = EMA_LONG; - rikiddo_instance.ma_long.config.ema_period_estimate_after = Some(EMA_SHORT); - T::RikiddoSigmoidFeeMarketEma::create(next_pool_id, rikiddo_instance)?; - - let pool_status = PoolStatus::CollectingSubsidy; - let total_subsidy = Some(BalanceOf::::zero()); - let total_weight = None; - let weights = None; - let pool_amount = BalanceOf::::zero(); - (pool_status, total_subsidy, total_weight, weights, pool_amount) - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - }; + // `amount` must be larger than all minimum balances. As we deposit `amount` + // liquidity shares, we must also ensure that `amount` is larger than the + // existential deposit of the liquidity shares. + ensure!( + amount >= Self::min_balance_of_pool(next_pool_id, &assets), + Error::::InsufficientLiquidity + ); + + ensure!(swap_fee <= T::MaxSwapFee::get(), Error::::SwapFeeTooHigh); + Self::check_provided_values_len_must_equal_assets_len(&assets, &weights)?; + + for (asset, weight) in assets.iter().copied().zip(weights) { + let free_balance = T::AssetManager::free_balance(asset, &who); + ensure!(free_balance >= amount, Error::::InsufficientBalance); + ensure!(weight >= T::MinWeight::get(), Error::::BelowMinimumWeight); + ensure!(weight <= T::MaxWeight::get(), Error::::AboveMaximumWeight); + map.insert(asset, weight); + total_weight = total_weight.checked_add_res(&weight)?; + T::AssetManager::transfer(asset, &who, &pool_account, amount)?; + } + + ensure!(total_weight <= T::MaxTotalWeight::get(), Error::::MaxTotalWeight); + T::AssetManager::deposit(pool_shares_id, &who, amount)?; + let pool = Pool { - assets: sorted_assets, - base_asset, - market_id, - pool_status, - scoring_rule, + assets: sorted_assets + .try_into() + .map_err(|_| Error::::Unexpected(UnexpectedError::StorageOverflow))?, swap_fee, - total_subsidy, + status: PoolStatus::Closed, total_weight, - weights, + weights: map + .try_into() + .map_err(|_| Error::::Unexpected(UnexpectedError::StorageOverflow))?, }; - >::insert(next_pool_id, Some(pool.clone())); + Pools::::insert(next_pool_id, pool.clone()); Self::deposit_event(Event::PoolCreate( CommonPoolEventParams { pool_id: next_pool_id, who }, pool, - pool_amount, + amount, pool_account, )); @@ -1986,17 +1058,10 @@ mod pallet { fn close_pool(pool_id: PoolId) -> Result { let asset_len = - >::try_mutate(pool_id, |pool| -> Result { - let pool = if let Some(el) = pool { - el - } else { - return Err(Error::::PoolDoesNotExist.into()); - }; - ensure!( - matches!(pool.pool_status, PoolStatus::Initialized | PoolStatus::Active), - Error::::InvalidStateTransition, - ); - pool.pool_status = PoolStatus::Closed; + >::try_mutate(pool_id, |opt_pool| -> Result { + let pool = opt_pool.as_mut().ok_or(Error::::PoolDoesNotExist)?; + ensure!(pool.status == PoolStatus::Open, Error::::InvalidStateTransition); + pool.status = PoolStatus::Closed; Ok(pool.assets.len() as u32) })?; Self::deposit_event(Event::PoolClosed(pool_id)); @@ -2026,214 +1091,10 @@ mod pallet { Ok(T::WeightInfo::destroy_pool(asset_len)) } - /// All supporters will receive their reserved funds back and the pool is destroyed. - /// - /// # Arguments - /// - /// * `pool_id`: Unique pool identifier associated with the pool to be destroyed. - fn destroy_pool_in_subsidy_phase(pool_id: PoolId) -> Result { - let mut total_providers = 0usize; - - Self::mutate_pool(pool_id, |pool| { - // Ensure all preconditions are met. - if pool.pool_status != PoolStatus::CollectingSubsidy { - return Err(Error::::InvalidStateTransition.into()); - } - - let base_asset = pool.base_asset; - - let mut providers_and_pool_shares = vec![]; - for provider in >::drain_prefix(pool_id) { - let missing = T::AssetManager::unreserve(base_asset, &provider.0, provider.1); - debug_assert!( - missing.is_zero(), - "Could not unreserve all of the amount. asset: {:?}, who: {:?}, amount: \ - {:?}, missing: {:?}", - base_asset, - &provider.0, - provider.1, - missing, - ); - total_providers = total_providers.saturating_add(1); - providers_and_pool_shares.push(provider); - } - - if pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma { - T::RikiddoSigmoidFeeMarketEma::destroy(pool_id)? - } - - Self::deposit_event(Event::PoolDestroyedInSubsidyPhase( - pool_id, - providers_and_pool_shares, - )); - - Ok(()) - })?; - - Pools::::remove(pool_id); - Ok(T::WeightInfo::destroy_pool_in_subsidy_phase(total_providers.saturated_into())) - } - - /// Pool will be marked as `PoolStatus::Active`, if the market is currently in subsidy - /// state and all other conditions are met. Returns result=true if everything succeeded, - /// result=false if not enough subsidy was collected and an error in all other cases. - /// - /// # Arguments - /// - /// * `pool_id`: Unique pool identifier associated with the pool to be made active. - /// than the given value. - fn end_subsidy_phase(pool_id: PoolId) -> Result, DispatchError> { - let do_mutate = || { - let mut total_providers = 0usize; - let mut total_assets = 0; - - let result = Self::mutate_pool(pool_id, |pool| { - // Ensure all preconditions are met. - if pool.pool_status != PoolStatus::CollectingSubsidy { - return Err(Error::::InvalidStateTransition.into()); - } - - let total_subsidy = pool.total_subsidy.ok_or(Error::::PoolMissingSubsidy)?; - ensure!(total_subsidy >= T::MinSubsidy::get(), Error::::InsufficientSubsidy); - let base_asset = pool.base_asset; - let pool_account = Pallet::::pool_account_id(&pool_id); - let pool_shares_id = Self::pool_shares_id(pool_id); - let mut account_created = false; - let mut total_balance = BalanceOf::::zero(); - total_assets = pool.assets.len(); - let mut providers_and_pool_shares = vec![]; - - // Transfer all reserved funds to the pool account and distribute pool shares. - for provider in >::drain_prefix(pool_id) { - total_providers = total_providers.saturating_add(1); - let provider_address = provider.0; - let subsidy = provider.1; - - if !account_created { - let missing = - T::AssetManager::unreserve(base_asset, &provider_address, subsidy); - debug_assert!( - missing.is_zero(), - "Could not unreserve all of the amount. asset: {:?}, who: {:?}, \ - amount: {:?}, missing: {:?}", - base_asset, - &provider_address, - subsidy, - missing, - ); - T::AssetManager::transfer( - base_asset, - &provider_address, - &pool_account, - subsidy, - )?; - total_balance = subsidy; - T::AssetManager::deposit(pool_shares_id, &provider_address, subsidy)?; - account_created = true; - providers_and_pool_shares.push((provider_address, subsidy)); - continue; - } - - let remaining = T::AssetManager::repatriate_reserved( - base_asset, - &provider_address, - &pool_account, - subsidy, - BalanceStatus::Free, - )?; - let transferred = subsidy.saturating_sub(remaining); - - if transferred != subsidy { - log::warn!( - target: LOG_TARGET, - "Data inconsistency: In end_subsidy_phase - More subsidy \ - provided than currently reserved. - Pool: {:?}, User: {:?}, Unreserved: {:?}, Previously reserved: {:?}", - pool_id, - provider_address, - transferred, - subsidy - ); - } - - T::AssetManager::deposit(pool_shares_id, &provider_address, transferred)?; - total_balance = total_balance.saturating_add(transferred); - providers_and_pool_shares.push((provider_address, transferred)); - } - - ensure!(total_balance >= T::MinSubsidy::get(), Error::::InsufficientSubsidy); - pool.total_subsidy = Some(total_balance); - - // Assign the initial set of outstanding assets to the pool account. - let outstanding_assets_per_event = - T::RikiddoSigmoidFeeMarketEma::initial_outstanding_assets( - pool_id, - pool.assets.len().saturated_into::().saturating_sub(1), - total_balance, - )?; - - for asset in pool.assets.iter().filter(|e| **e != base_asset) { - T::AssetManager::deposit( - *asset, - &pool_account, - outstanding_assets_per_event, - )?; - } - - pool.pool_status = PoolStatus::Active; - Self::deposit_event(Event::SubsidyCollected( - pool_id, - providers_and_pool_shares, - total_balance, - )); - Ok(()) - }); - - if let Err(err) = result { - if err == Error::::InsufficientSubsidy.into() { - return Ok(ResultWithWeightInfo { - result: false, - weight: T::WeightInfo::end_subsidy_phase( - total_assets.saturated_into(), - total_providers.saturated_into(), - ), - }); - } - - return Err(err); - } - - Ok(ResultWithWeightInfo { - result: true, - weight: T::WeightInfo::end_subsidy_phase( - total_assets.saturated_into(), - total_providers.saturated_into(), - ), - }) - }; - - with_transaction(|| { - let output = do_mutate(); - match output { - Ok(res) => { - if res.result { - TransactionOutcome::Commit(Ok(res)) - } else { - TransactionOutcome::Rollback(Ok(res)) - } - } - Err(err) => TransactionOutcome::Rollback(Err(err)), - } - }) - } - fn open_pool(pool_id: PoolId) -> Result { Self::mutate_pool(pool_id, |pool| -> DispatchResult { - ensure!( - pool.pool_status == PoolStatus::Initialized, - Error::::InvalidStateTransition - ); - pool.pool_status = PoolStatus::Active; + ensure!(pool.status == PoolStatus::Closed, Error::::InvalidStateTransition); + pool.status = PoolStatus::Open; Ok(()) })?; let pool = Pools::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; @@ -2259,7 +1120,7 @@ mod pallet { fn pool_exit_with_exact_asset_amount( who: T::AccountId, pool_id: PoolId, - asset: Asset>, + asset: AssetOf, asset_amount: BalanceOf, max_pool_amount: BalanceOf, ) -> Result { @@ -2272,7 +1133,6 @@ mod pallet { asset, asset_amount: |_, _| Ok(asset_amount), bound: max_pool_amount, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), ensure_balance: |asset_balance: BalanceOf| { ensure!( asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, @@ -2285,19 +1145,15 @@ mod pallet { asset_balance.saturated_into(), Self::pool_weight_rslt(pool_ref, &asset)?, total_supply.saturated_into(), - pool_ref - .total_weight - .ok_or(Error::::PoolMissingWeight)? - .saturated_into(), + pool_ref.total_weight.saturated_into(), asset_amount.saturated_into(), - pool_ref.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into(), + pool_ref.swap_fee.saturated_into(), T::ExitFee::get().saturated_into(), )? .saturated_into(); ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); ensure!(pool_amount <= max_pool_amount, Error::::LimitIn); Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; - T::LiquidityMining::remove_shares(&who, &pool_ref.market_id, asset_amount); Ok(pool_amount) }, event: |evt| Self::deposit_event(Event::PoolExitWithExactAssetAmount(evt)), @@ -2306,7 +1162,7 @@ mod pallet { pool: pool_ref, }; let weight = T::WeightInfo::pool_exit_with_exact_asset_amount(); - pool_exit_with_exact_amount::<_, _, _, _, _, T>(params).map(|_| weight) + pool_exit_with_exact_amount::<_, _, _, _, T>(params).map(|_| weight) } /// Pool - Join with exact asset amount @@ -2326,7 +1182,7 @@ mod pallet { fn pool_join_with_exact_asset_amount( who: T::AccountId, pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, asset_amount: BalanceOf, min_pool_amount: BalanceOf, ) -> Result { @@ -2340,7 +1196,6 @@ mod pallet { asset: asset_in, asset_amount: |_, _| Ok(asset_amount), bound: min_pool_amount, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), pool_amount: move |asset_balance: BalanceOf, total_supply: BalanceOf| { let mul: BalanceOf = asset_balance.bmul(T::MaxInRatio::get())?; ensure!(asset_amount <= mul, Error::::MaxInRatio); @@ -2348,16 +1203,12 @@ mod pallet { asset_balance.saturated_into(), Self::pool_weight_rslt(pool_ref, &asset_in)?, total_supply.saturated_into(), - pool_ref - .total_weight - .ok_or(Error::::PoolMissingWeight)? - .saturated_into(), + pool_ref.total_weight.saturated_into(), asset_amount.saturated_into(), - pool_ref.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into(), + pool_ref.swap_fee.saturated_into(), )? .saturated_into(); ensure!(pool_amount >= min_pool_amount, Error::::LimitOut); - T::LiquidityMining::add_shares(who.clone(), pool_ref.market_id, asset_amount); Ok(pool_amount) }, event: |evt| Self::deposit_event(Event::PoolJoinWithExactAssetAmount(evt)), @@ -2367,53 +1218,7 @@ mod pallet { pool: pool_ref, }; let weight = T::WeightInfo::pool_join_with_exact_asset_amount(); - pool_join_with_exact_amount::<_, _, _, _, T>(params).map(|_| weight) - } - - fn pool(pool_id: PoolId) -> Result>, DispatchError> { - Self::pool_by_id(pool_id) - } - - /// Remove losing assets and distribute Rikiddo pool share rewards. - /// - /// # Arguments - /// - /// * `market_type`: Type of the market. - /// * `pool_id`: Unique pool identifier associated with the pool to be made closed. - /// * `outcome_report`: The reported outcome. - /// * `winner_payout_account`: The account that exchanges winning assets against rewards. - /// - /// # Errors - /// - /// * Returns `Error::::PoolDoesNotExist` if there is no pool with `pool_id`. - /// * Returns `Error::::WinningAssetNotFound` if the reported asset is not found in the - /// pool and the scoring rule is Rikiddo. - /// * Returns `Error::::InvalidStateTransition` if the pool is not closed - #[frame_support::transactional] - fn clean_up_pool( - market_type: &MarketType, - pool_id: PoolId, - outcome_report: &OutcomeReport, - winner_payout_account: &T::AccountId, - ) -> Result { - let mut weight = Weight::zero(); - Self::mutate_pool(pool_id, |pool| { - ensure!(pool.pool_status == PoolStatus::Closed, Error::::InvalidStateTransition); - pool.pool_status = PoolStatus::Clean; - Ok(()) - })?; - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // mutate_pool - if let MarketType::Categorical(_) = market_type { - let extra_weight = Self::clean_up_pool_categorical( - pool_id, - outcome_report, - winner_payout_account, - )?; - weight = weight.saturating_add(extra_weight); - } - Self::deposit_event(Event::::PoolCleanedUp(pool_id)); - // (No extra work required for scalar markets!) - Ok(weight) + pool_join_with_exact_amount::<_, _, _, T>(params).map(|_| weight) } /// Swap - Exact amount in @@ -2434,12 +1239,11 @@ mod pallet { fn swap_exact_amount_in( who: T::AccountId, pool_id: PoolId, - asset_in: Asset>, - mut asset_amount_in: BalanceOf, - asset_out: Asset>, + asset_in: AssetOf, + asset_amount_in: BalanceOf, + asset_out: AssetOf, min_asset_amount_out: Option>, max_price: Option>, - handle_fees: bool, ) -> Result { ensure!( min_asset_amount_out.is_some() || max_price.is_some(), @@ -2448,103 +1252,33 @@ mod pallet { let pool = Pallet::::pool_by_id(pool_id)?; let pool_account_id = Pallet::::pool_account_id(&pool_id); - let market = T::MarketCommons::market(&pool.market_id)?; - let creator_fee = market.creator_fee; - let mut fees_handled = false; - - if asset_in == pool.base_asset && !handle_fees { - Self::handle_creator_fees( - asset_amount_in, - asset_in, - pool.base_asset, - creator_fee, - market.creator.clone(), - who.clone(), - pool_id, - pool.market_id, - ); - - let fee_amount = creator_fee.mul_floor(asset_amount_in); - asset_amount_in = asset_amount_in.saturating_sub(fee_amount); - fees_handled = true; - } ensure!( T::AssetManager::free_balance(asset_in, &who) >= asset_amount_in, Error::::InsufficientBalance ); - let balance_before = T::AssetManager::free_balance(asset_out, &who); let params = SwapExactAmountParams { + // TODO(#1215): This probably doesn't need to be a closure. asset_amounts: || { - let asset_amount_out = match pool.scoring_rule { - ScoringRule::CPMM => { - let balance_out = - T::AssetManager::free_balance(asset_out, &pool_account_id); - let balance_in = - T::AssetManager::free_balance(asset_in, &pool_account_id); - ensure!( - asset_amount_in <= balance_in.bmul(T::MaxInRatio::get())?, - Error::::MaxInRatio - ); - let swap_fee = if handle_fees { - 0u128 - } else { - pool.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into() - }; - crate::math::calc_out_given_in( - balance_in.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_in)?, - balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?, - asset_amount_in.saturated_into(), - swap_fee, - )? - .saturated_into() - } - ScoringRule::RikiddoSigmoidFeeMarketEma => { - let base_asset = pool.base_asset; - ensure!(asset_out == base_asset, Error::::UnsupportedTrade); - ensure!(asset_in != asset_out, Error::::UnsupportedTrade); - - let mut outstanding_before = Vec::>::with_capacity( - pool.assets.len().saturating_sub(1), - ); - let mut outstanding_after = Vec::>::with_capacity( - pool.assets.len().saturating_sub(1), - ); - - for asset in pool.assets.iter().filter(|e| **e != base_asset) { - let total_amount = T::AssetManager::total_issuance(*asset); - outstanding_before.push(total_amount); - - if *asset == asset_in { - outstanding_after - .push(total_amount.saturating_sub(asset_amount_in)); - } else { - outstanding_after.push(total_amount); - } - } - - let cost_before = - T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_before)?; - let cost_after = - T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; - cost_before.checked_sub_res(&cost_after)? - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - }; + let balance_out = T::AssetManager::free_balance(asset_out, &pool_account_id); + let balance_in = T::AssetManager::free_balance(asset_in, &pool_account_id); + ensure!( + asset_amount_in <= balance_in.bmul(T::MaxInRatio::get())?, + Error::::MaxInRatio + ); + let asset_amount_out: BalanceOf = crate::math::calc_out_given_in( + balance_in.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_in)?, + balance_out.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_out)?, + asset_amount_in.saturated_into(), + pool.swap_fee.saturated_into(), + )? + .saturated_into(); if let Some(maao) = min_asset_amount_out { - let asset_amount_out_check = if fees_handled { - asset_amount_out - } else { - asset_amount_out.saturating_sub(creator_fee.mul_floor(asset_amount_out)) - }; - - ensure!(asset_amount_out_check >= maao, Error::::LimitOut); + ensure!(asset_amount_out >= maao, Error::::LimitOut); } Self::ensure_minimum_balance(pool_id, &pool, asset_out, asset_amount_out)?; @@ -2554,7 +1288,6 @@ mod pallet { asset_bound: min_asset_amount_out, asset_in, asset_out, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), event: |evt| Self::deposit_event(Event::SwapExactAmountIn(evt)), max_price, pool_account_id: &pool_account_id, @@ -2562,33 +1295,9 @@ mod pallet { pool: &pool, who: who.clone(), }; - swap_exact_amount::<_, _, _, T>(params)?; - - if !fees_handled && !handle_fees { - let balance_after = T::AssetManager::free_balance(asset_out, &who); - let asset_amount_out = balance_after.saturating_sub(balance_before); - - Self::handle_creator_fees( - asset_amount_out, - asset_out, - pool.base_asset, - creator_fee, - market.creator.clone(), - who.clone(), - pool_id, - pool.market_id, - ); - } + swap_exact_amount::<_, _, T>(params)?; - match pool.scoring_rule { - ScoringRule::CPMM => Ok(T::WeightInfo::swap_exact_amount_in_cpmm()), - ScoringRule::RikiddoSigmoidFeeMarketEma => Ok( - T::WeightInfo::swap_exact_amount_in_rikiddo(pool.assets.len().saturated_into()), - ), - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - Err(Error::::InvalidScoringRule.into()) - } - } + Ok(T::WeightInfo::swap_exact_amount_in_cpmm()) } /// Swap - Exact amount out @@ -2609,114 +1318,38 @@ mod pallet { fn swap_exact_amount_out( who: T::AccountId, pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, max_asset_amount_in: Option>, - asset_out: Asset>, - mut asset_amount_out: BalanceOf, + asset_out: AssetOf, + asset_amount_out: BalanceOf, max_price: Option>, - handle_fees: bool, ) -> Result { let pool = Pallet::::pool_by_id(pool_id)?; let pool_account_id = Pallet::::pool_account_id(&pool_id); ensure!(max_asset_amount_in.is_some() || max_price.is_some(), Error::::LimitMissing); Self::ensure_minimum_balance(pool_id, &pool, asset_out, asset_amount_out)?; - let market = T::MarketCommons::market(&pool.market_id)?; - let creator_fee = market.creator_fee; - let mut fee_amount = BalanceOf::::zero(); - - let to_adjust_in_value = if asset_in == pool.base_asset { - // Can't adjust the value inside the anonymous function for asset_amounts - true - } else { - fee_amount = creator_fee.mul_floor(asset_amount_out); - asset_amount_out = asset_amount_out.saturating_add(fee_amount); - false - }; let params = SwapExactAmountParams { asset_amounts: || { let balance_out = T::AssetManager::free_balance(asset_out, &pool_account_id); - let asset_amount_in = match pool.scoring_rule { - ScoringRule::CPMM => { - ensure!( - asset_amount_out <= balance_out.bmul(T::MaxOutRatio::get(),)?, - Error::::MaxOutRatio, - ); - - let balance_in = - T::AssetManager::free_balance(asset_in, &pool_account_id); - let swap_fee = if handle_fees { - 0u128 - } else { - pool.swap_fee.ok_or(Error::::PoolMissingFee)?.saturated_into() - }; - crate::math::calc_in_given_out( - balance_in.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_in)?, - balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?, - asset_amount_out.saturated_into(), - swap_fee, - )? - .saturated_into() - } - ScoringRule::RikiddoSigmoidFeeMarketEma => { - let base_asset = pool.base_asset; - ensure!(asset_in == base_asset, Error::::UnsupportedTrade); - ensure!(asset_in != asset_out, Error::::UnsupportedTrade); - - let mut outstanding_before = Vec::>::with_capacity( - pool.assets.len().saturating_sub(1), - ); - let mut outstanding_after = Vec::>::with_capacity( - pool.assets.len().saturating_sub(1), - ); - - for asset in pool.assets.iter().filter(|e| **e != base_asset) { - let total_amount = T::AssetManager::total_issuance(*asset); - outstanding_before.push(total_amount); - - if *asset == asset_out { - outstanding_after - .push(total_amount.saturating_add(asset_amount_out)); - } else { - outstanding_after.push(total_amount); - } - } - - let cost_before = - T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_before)?; - let cost_after = - T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; - cost_after.checked_sub_res(&cost_before)? - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - }; - - if asset_in == pool.base_asset && !handle_fees && to_adjust_in_value { - Self::handle_creator_fees( - asset_amount_in, - asset_in, - pool.base_asset, - creator_fee, - market.creator.clone(), - who.clone(), - pool_id, - pool.market_id, - ); - } + ensure!( + asset_amount_out <= balance_out.bmul(T::MaxOutRatio::get(),)?, + Error::::MaxOutRatio, + ); + + let balance_in = T::AssetManager::free_balance(asset_in, &pool_account_id); + let asset_amount_in: BalanceOf = crate::math::calc_in_given_out( + balance_in.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_in)?, + balance_out.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_out)?, + asset_amount_out.saturated_into(), + pool.swap_fee.saturated_into(), + )? + .saturated_into(); if let Some(maai) = max_asset_amount_in { - let asset_amount_in_check = if to_adjust_in_value { - let fee_amount = creator_fee.mul_floor(asset_amount_in); - asset_amount_in.saturating_add(fee_amount) - } else { - asset_amount_in - }; - - ensure!(asset_amount_in_check <= maai, Error::::LimitIn); + ensure!(asset_amount_in <= maai, Error::::LimitIn); } Ok([asset_amount_in, asset_amount_out]) @@ -2724,7 +1357,6 @@ mod pallet { asset_bound: max_asset_amount_in, asset_in, asset_out, - cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), event: |evt| Self::deposit_event(Event::SwapExactAmountOut(evt)), max_price, pool_account_id: &pool_account_id, @@ -2732,34 +1364,9 @@ mod pallet { pool: &pool, who: who.clone(), }; - swap_exact_amount::<_, _, _, T>(params)?; - - if !to_adjust_in_value && !handle_fees { - asset_amount_out = asset_amount_out.saturating_sub(fee_amount); - - Self::handle_creator_fees( - asset_amount_out, - asset_out, - pool.base_asset, - creator_fee, - market.creator.clone(), - who.clone(), - pool_id, - pool.market_id, - ); - } + swap_exact_amount::<_, _, T>(params)?; - match pool.scoring_rule { - ScoringRule::CPMM => Ok(T::WeightInfo::swap_exact_amount_out_cpmm()), - ScoringRule::RikiddoSigmoidFeeMarketEma => { - Ok(T::WeightInfo::swap_exact_amount_out_rikiddo( - pool.assets.len().saturated_into(), - )) - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - Err(Error::::InvalidScoringRule.into()) - } - } + Ok(T::WeightInfo::swap_exact_amount_out_cpmm()) } } } diff --git a/zrml/swaps/src/migrations.rs b/zrml/swaps/src/migrations.rs index b9f09b652..88b9fd515 100644 --- a/zrml/swaps/src/migrations.rs +++ b/zrml/swaps/src/migrations.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -20,3 +21,388 @@ // (, contact@balancer.finance) in the // balancer-core repository // . + +use crate::{ + types::{Pool, PoolStatus}, + BalanceOf, Config, Pallet as Swaps, PoolOf, PoolsCachedForArbitrage, SubsidyProviders, +}; +use alloc::{collections::BTreeMap, vec::Vec}; +use core::marker::PhantomData; +use frame_support::{ + log, + pallet_prelude::{Blake2_128Concat, StorageVersion, Weight}, + traits::{Get, OnRuntimeUpgrade}, +}; +use parity_scale_codec::{Compact, Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{RuntimeDebug, SaturatedConversion, Saturating}; +use zeitgeist_primitives::{ + constants::MAX_ASSETS, + types::{Asset, MarketId, PoolId, ScoringRule}, +}; + +#[cfg(feature = "try-runtime")] +use frame_support::migration::storage_key_iter; + +#[cfg(any(feature = "try-runtime", test))] +const SWAPS: &[u8] = b"Swaps"; +#[cfg(any(feature = "try-runtime", test))] +const POOLS: &[u8] = b"Pools"; + +#[derive(TypeInfo, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] +pub struct OldPool +where + MarketId: MaxEncodedLen, +{ + pub assets: Vec>, + pub base_asset: Asset, + pub market_id: MarketId, + pub pool_status: OldPoolStatus, + pub scoring_rule: OldScoringRule, + pub swap_fee: Option, + pub total_subsidy: Option, + pub total_weight: Option, + pub weights: Option, u128>>, +} + +impl MaxEncodedLen for OldPool +where + Balance: MaxEncodedLen, + MarketId: MaxEncodedLen, +{ + fn max_encoded_len() -> usize { + let max_encoded_length_bytes = >::max_encoded_len(); + let b_tree_map_size = 1usize + .saturating_add(MAX_ASSETS.saturated_into::().saturating_mul( + >::max_encoded_len().saturating_add(u128::max_encoded_len()), + )) + .saturating_add(max_encoded_length_bytes); + + >::max_encoded_len() + .saturating_mul(MAX_ASSETS.saturated_into::()) + .saturating_add(max_encoded_length_bytes) + .saturating_add(>>::max_encoded_len()) + .saturating_add(MarketId::max_encoded_len()) + .saturating_add(PoolStatus::max_encoded_len()) + .saturating_add(ScoringRule::max_encoded_len()) + .saturating_add(>::max_encoded_len().saturating_mul(2)) + .saturating_add(>::max_encoded_len()) + .saturating_add(b_tree_map_size) + } +} + +#[derive(TypeInfo, Clone, Copy, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] +pub enum OldScoringRule { + CPMM, + RikiddoSigmoidFeeMarketEma, + Lmsr, + Orderbook, + Parimutuel, +} + +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + parity_scale_codec::Decode, + parity_scale_codec::Encode, + parity_scale_codec::MaxEncodedLen, + scale_info::TypeInfo, + Clone, + Copy, + Debug, + Eq, + Ord, + PartialEq, + PartialOrd, +)] +pub enum OldPoolStatus { + Active, + CollectingSubsidy, + Closed, + Clean, + Initialized, +} + +pub(crate) type OldPoolOf = OldPool, MarketId>; + +#[frame_support::storage_alias] +pub(crate) type Pools = + StorageMap, Blake2_128Concat, PoolId, Option>>; + +const SWAPS_REQUIRED_STORAGE_VERSION: u16 = 3; +const SWAPS_NEXT_STORAGE_VERSION: u16 = 4; + +#[frame_support::storage_alias] +pub(crate) type Markets = StorageMap, Blake2_128Concat, PoolId, OldPoolOf>; + +pub struct MigratePools(PhantomData); + +/// Deletes all Rikiddo markets from storage, migrates CPMM markets to their new storage layout and +/// closes them. Due to us abstracting `MarketId` away from the `Asset` type of the `Config` object, +/// we require that the old asset type `Asset` be convertible to the generic `T::Asset`. +/// The migration also clears the `SubsidyProviders` and `PoolsCachedForArbitrage` storage elements. +impl OnRuntimeUpgrade for MigratePools +where + T: Config, + ::Asset: From>, +{ + fn on_runtime_upgrade() -> Weight { + let mut total_weight = T::DbWeight::get().reads(1); + let swaps_version = StorageVersion::get::>(); + if swaps_version != SWAPS_REQUIRED_STORAGE_VERSION { + log::info!( + "MigratePools: swaps version is {:?}, but {:?} is required", + swaps_version, + SWAPS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("MigratePools: Starting..."); + + let mut reads_writes = 0u64; + crate::Pools::::translate::>, _>(|_, opt_old_pool| { + // We proceed by deleting Rikiddo pools; CPMM pools are migrated to the new version + // _and_ closed (because their respective markets are being switched to LMSR). + reads_writes.saturating_inc(); + let old_pool = opt_old_pool?; + if old_pool.scoring_rule != OldScoringRule::CPMM { + return None; + } + // These conversions should all be infallible. + let assets_unbounded = + old_pool.assets.into_iter().map(Into::into).collect::>(); + let assets = assets_unbounded.try_into().ok()?; + let status = match old_pool.pool_status { + OldPoolStatus::Active => PoolStatus::Closed, + OldPoolStatus::CollectingSubsidy => return None, + OldPoolStatus::Closed => PoolStatus::Closed, + OldPoolStatus::Clean => PoolStatus::Closed, + OldPoolStatus::Initialized => PoolStatus::Closed, + }; + let swap_fee = old_pool.swap_fee?; + let weights_unbounded = old_pool + .weights? + .into_iter() + .map(|(k, v)| (k.into(), v)) + .collect::>(); + let weights = weights_unbounded.try_into().ok()?; + let total_weight = old_pool.total_weight?; + let new_pool: PoolOf = Pool { assets, status, swap_fee, total_weight, weights }; + Some(new_pool) + }); + log::info!("MigratePools: Upgraded {} pools.", reads_writes); + reads_writes = reads_writes.saturating_add(SubsidyProviders::::drain().count() as u64); + reads_writes = + reads_writes.saturating_add(PoolsCachedForArbitrage::::drain().count() as u64); + total_weight = total_weight + .saturating_add(T::DbWeight::get().reads_writes(reads_writes, reads_writes)); + StorageVersion::new(SWAPS_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("MigratePools: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let old_pools = + storage_key_iter::>, Blake2_128Concat>(SWAPS, POOLS) + .collect::>(); + let pools = Pools::::iter_keys().count(); + let decodable_pools = Pools::::iter_values().count(); + if pools == decodable_pools { + log::info!("All {} pools could successfully be decoded.", pools); + } else { + log::error!( + "Can only decode {} of {} pools - others will be dropped.", + decodable_pools, + pools + ); + } + Ok(old_pools.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { + let old_pools: BTreeMap>> = + Decode::decode(&mut &previous_state[..]).unwrap(); + let old_pool_count = old_pools.len(); + let new_pool_count = crate::Pools::::iter().count(); + assert_eq!(old_pool_count, new_pool_count); + log::info!("MigratePools: Pool counter post-upgrade is {}!", new_pool_count); + if PoolsCachedForArbitrage::::iter().count() != 0 { + return Err("MigratePools: PoolsCachedForArbitrage is not empty!"); + } + if SubsidyProviders::::iter().count() != 0 { + return Err("MigratePools: SubsidyProviders is not empty!"); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ExtBuilder, Runtime}; + use alloc::fmt::Debug; + use frame_support::{migration::put_storage_value, storage_root, StorageHasher}; + use sp_runtime::StateVersion; + use test_case::test_case; + use zeitgeist_macros::create_b_tree_map; + use zeitgeist_primitives::types::Asset; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + MigratePools::::on_runtime_upgrade(); + assert_eq!(StorageVersion::get::>(), SWAPS_NEXT_STORAGE_VERSION); + }); + } + + #[test] + fn on_runtime_upgrade_clears_storage_correctly() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + PoolsCachedForArbitrage::::insert(4, ()); + SubsidyProviders::::insert(1, 2, 3); + MigratePools::::on_runtime_upgrade(); + assert_eq!(PoolsCachedForArbitrage::::iter().count(), 0); + assert_eq!(SubsidyProviders::::iter().count(), 0); + }); + } + + #[test_case(OldPoolStatus::Active)] + #[test_case(OldPoolStatus::Closed)] + #[test_case(OldPoolStatus::Clean)] + #[test_case(OldPoolStatus::Initialized)] + fn on_runtime_upgrade_works_as_expected_with_cpmm(old_pool_status: OldPoolStatus) { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let base_asset = Asset::ForeignAsset(4); + let market_id = 1; + let assets = vec![ + Asset::CategoricalOutcome(market_id, 0), + Asset::CategoricalOutcome(market_id, 1), + Asset::CategoricalOutcome(market_id, 2), + base_asset, + ]; + let swap_fee = 5; + let total_weight = 8; + let weights = create_b_tree_map!({ + Asset::CategoricalOutcome(market_id, 0) => 1, + Asset::CategoricalOutcome(market_id, 1) => 2, + Asset::CategoricalOutcome(market_id, 2) => 1, + base_asset => 8, + }); + let opt_old_pool = Some(OldPool { + assets: assets.clone(), + base_asset, + market_id, + pool_status: old_pool_status, + scoring_rule: OldScoringRule::CPMM, + swap_fee: Some(swap_fee), + total_subsidy: None, + total_weight: Some(total_weight), + weights: Some(weights.clone()), + }); + populate_test_data::>>( + SWAPS, + POOLS, + vec![opt_old_pool], + ); + MigratePools::::on_runtime_upgrade(); + let actual = crate::Pools::::get(0); + let expected = Some(Pool { + assets: assets.try_into().unwrap(), + status: PoolStatus::Closed, + swap_fee, + total_weight, + weights: weights.try_into().unwrap(), + }); + assert_eq!(actual, expected); + }); + } + + #[test_case(OldPoolStatus::Active)] + #[test_case(OldPoolStatus::CollectingSubsidy)] + #[test_case(OldPoolStatus::Closed)] + #[test_case(OldPoolStatus::Clean)] + #[test_case(OldPoolStatus::Initialized)] + fn on_runtime_upgrade_works_as_expected_with_rikiddo(pool_status: OldPoolStatus) { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let base_asset = Asset::ForeignAsset(4); + let market_id = 1; + let assets = vec![ + Asset::CategoricalOutcome(market_id, 0), + Asset::CategoricalOutcome(market_id, 1), + Asset::CategoricalOutcome(market_id, 2), + base_asset, + ]; + let opt_old_pool = Some(OldPool { + assets: assets.clone(), + base_asset, + market_id, + pool_status, + scoring_rule: OldScoringRule::RikiddoSigmoidFeeMarketEma, + swap_fee: Some(5), + total_subsidy: Some(123), + total_weight: None, + weights: None, + }); + populate_test_data::>>( + SWAPS, + POOLS, + vec![opt_old_pool], + ); + MigratePools::::on_runtime_upgrade(); + let actual = crate::Pools::::get(0); + assert_eq!(actual, None); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(SWAPS_NEXT_STORAGE_VERSION).put::>(); + let assets = + vec![Asset::ForeignAsset(0), Asset::ForeignAsset(1), Asset::ForeignAsset(2)]; + let weights = create_b_tree_map!({ + Asset::ForeignAsset(0) => 3, + Asset::ForeignAsset(1) => 4, + Asset::ForeignAsset(2) => 5, + }); + let pool = Pool { + assets: assets.try_into().unwrap(), + status: PoolStatus::Open, + swap_fee: 4, + total_weight: 12, + weights: weights.try_into().unwrap(), + }; + crate::Pools::::insert(0, pool); + PoolsCachedForArbitrage::::insert(4, ()); + SubsidyProviders::::insert(1, 2, 3); + let tmp = storage_root(StateVersion::V1); + MigratePools::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + fn set_up_version() { + StorageVersion::new(SWAPS_REQUIRED_STORAGE_VERSION).put::>(); + } + + #[allow(unused)] + fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) + where + H: StorageHasher, + K: TryFrom + Encode, + V: Encode + Clone, + >::Error: Debug, + { + for (key, value) in data.iter().enumerate() { + let storage_hash = K::try_from(key).unwrap().using_encoded(H::hash).as_ref().to_vec(); + put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); + } + } +} diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index 85f8ba551..cf07fb91e 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -34,29 +34,21 @@ use frame_support::{ traits::{Contains, Everything}, }; use orml_traits::parameter_type_with_key; -use sp_arithmetic::Perbill; use sp_runtime::{ testing::Header, traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, - DispatchError, }; -use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{ constants::mock::{ - BalanceFractionalDecimals, BlockHashCount, ExistentialDeposit, GetNativeCurrencyId, - LiquidityMiningPalletId, MaxAssets, MaxInRatio, MaxLocks, MaxOutRatio, MaxReserves, - MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinSubsidy, MinWeight, MinimumPeriod, - PmPalletId, SwapsPalletId, BASE, + BlockHashCount, ExistentialDeposit, GetNativeCurrencyId, MaxAssets, MaxInRatio, MaxLocks, + MaxOutRatio, MaxReserves, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, + MinimumPeriod, SwapsPalletId, BASE, }, types::{ AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Deadlines, Hash, Index, Market, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, PoolId, - ScoringRule, SerdeWrapper, UncheckedExtrinsicTest, + CurrencyId, Hash, Index, MarketId, Moment, PoolId, SerdeWrapper, UncheckedExtrinsicTest, }, }; -use zrml_market_commons::MarketCommonsPalletApi; -use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; @@ -77,16 +69,11 @@ pub const BASE_ASSET: Asset = if let Some(asset) = ASSETS.last() { panic!("Invalid asset vector"); }; -pub const DEFAULT_MARKET_ID: MarketId = 0; -pub const DEFAULT_MARKET_ORACLE: AccountIdTest = DAVE; -pub const DEFAULT_MARKET_CREATOR: AccountIdTest = DAVE; - pub type UncheckedExtrinsic = UncheckedExtrinsicTest; // Mocked exit fee for easier calculations parameter_types! { pub storage ExitFeeMock: Balance = BASE / 10; - pub const MinSubsidyPerAccount: Balance = BASE; } construct_runtime!( @@ -98,9 +85,6 @@ construct_runtime!( { Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, Currencies: orml_currencies::{Pallet}, - LiquidityMining: zrml_liquidity_mining::{Config, Event, Pallet}, - MarketCommons: zrml_market_commons::{Pallet, Storage}, - RikiddoSigmoidFeeMarketEma: zrml_rikiddo::{Pallet, Storage}, Swaps: zrml_swaps::{Call, Event, Pallet}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, @@ -111,12 +95,9 @@ construct_runtime!( pub type AssetManager = Currencies; impl crate::Config for Runtime { + type Asset = Asset; type RuntimeEvent = RuntimeEvent; type ExitFee = ExitFeeMock; - type FixedTypeU = ::FixedTypeU; - type FixedTypeS = ::FixedTypeS; - type LiquidityMining = LiquidityMining; - type MarketCommons = MarketCommons; type MaxAssets = MaxAssets; type MaxInRatio = MaxInRatio; type MaxOutRatio = MaxOutRatio; @@ -124,11 +105,8 @@ impl crate::Config for Runtime { type MaxTotalWeight = MaxTotalWeight; type MaxWeight = MaxWeight; type MinAssets = MinAssets; - type MinSubsidy = MinSubsidy; - type MinSubsidyPerAccount = MinSubsidyPerAccount; type MinWeight = MinWeight; type PalletId = SwapsPalletId; - type RikiddoSigmoidFeeMarketEma = RikiddoSigmoidFeeMarketEma; type AssetManager = AssetManager; type WeightInfo = zrml_swaps::weights::WeightInfo; } @@ -184,7 +162,7 @@ where frame_support::PalletId: AccountIdConversion, { fn contains(ai: &AccountIdTest) -> bool { - let pallets = vec![LiquidityMiningPalletId::get(), PmPalletId::get(), SwapsPalletId::get()]; + let pallets = vec![SwapsPalletId::get()]; if let Some(pallet_id) = frame_support::PalletId::try_from_sub_account::(ai) { return pallets.contains(&pallet_id.0); @@ -228,37 +206,12 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -impl zrml_liquidity_mining::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type MarketCommons = MarketCommons; - type MarketId = MarketId; - type PalletId = LiquidityMiningPalletId; - type WeightInfo = zrml_liquidity_mining::weights::WeightInfo; -} - impl zrml_market_commons::Config for Runtime { type Balance = Balance; type MarketId = MarketId; - type PredictionMarketsPalletId = PmPalletId; type Timestamp = Timestamp; } -impl zrml_rikiddo::Config for Runtime { - type Timestamp = Timestamp; - type Balance = Balance; - type FixedTypeU = FixedU128; - type FixedTypeS = FixedI128; - type BalanceFractionalDecimals = BalanceFractionalDecimals; - type PoolId = PoolId; - type Rikiddo = RikiddoSigmoidMV< - Self::FixedTypeU, - Self::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - >; -} - impl pallet_timestamp::Config for Runtime { type MinimumPeriod = MinimumPeriod; type Moment = Moment; @@ -290,13 +243,7 @@ impl ExtBuilder { .assimilate_storage(&mut storage) .unwrap(); - let mut ext = sp_io::TestExternalities::from(storage); - - ext.execute_with(|| { - MarketCommons::push_market(mock_market(4)).unwrap(); - }); - - ext + sp_io::TestExternalities::from(storage) } } @@ -320,35 +267,5 @@ sp_api::mock_impl_runtime_apis! { fn pool_shares_id(pool_id: PoolId) -> Asset> { Asset::PoolShare(SerdeWrapper(pool_id)) } - - fn get_all_spot_prices( - pool_id: &PoolId, - with_fees: bool - ) -> Result, Balance)>, DispatchError> { - Swaps::get_all_spot_prices(pool_id, with_fees) - } - } -} - -pub(super) fn mock_market( - categories: u16, -) -> Market> { - Market { - base_asset: BASE_ASSET, - creation: MarketCreation::Permissionless, - creator_fee: Perbill::from_parts(0), - creator: DEFAULT_MARKET_CREATOR, - market_type: MarketType::Categorical(categories), - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - metadata: vec![0; 50], - oracle: DEFAULT_MARKET_ORACLE, - period: MarketPeriod::Block(0..1), - deadlines: Deadlines::default(), - report: None, - resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, - status: MarketStatus::Active, - bonds: MarketBonds::default(), - early_close: None, } } diff --git a/zrml/swaps/src/tests.rs b/zrml/swaps/src/tests.rs index 4ee87499e..c12fbd0c1 100644 --- a/zrml/swaps/src/tests.rs +++ b/zrml/swaps/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -27,32 +27,21 @@ use crate::{ events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - math::{calc_in_given_out, calc_out_given_in, calc_spot_price}, + math::calc_out_given_in, mock::*, - BalanceOf, Config, Error, Event, MarketIdOf, PoolsCachedForArbitrage, SubsidyProviders, - ARBITRAGE_MAX_ITERATIONS, -}; -use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, error::BadOrigin, traits::Hooks, - weights::Weight, + types::PoolStatus, + AssetOf, BalanceOf, Config, Error, Event, }; +use frame_support::{assert_err, assert_noop, assert_ok}; use more_asserts::{assert_ge, assert_le}; -use orml_traits::{GetByKey, MultiCurrency, MultiReservableCurrency}; -use sp_arithmetic::{traits::SaturatedConversion, Perbill}; -use sp_runtime::DispatchResult; +use orml_traits::MultiCurrency; #[allow(unused_imports)] use test_case::test_case; use zeitgeist_primitives::{ constants::BASE, traits::Swaps as _, - types::{ - AccountIdTest, Asset, MarketId, MarketType, OutcomeReport, PoolId, PoolStatus, ScoringRule, - }, + types::{Asset, MarketId, PoolId}, }; -use zrml_market_commons::MarketCommonsPalletApi; -use zrml_rikiddo::traits::RikiddoMVPallet; - -pub const SENTINEL_AMOUNT: u128 = 123456789; const _1_2: u128 = BASE / 2; const _1_10: u128 = BASE / 10; @@ -89,14 +78,6 @@ const DEFAULT_POOL_ID: PoolId = 0; const DEFAULT_LIQUIDITY: u128 = _100; const DEFAULT_WEIGHT: u128 = _2; -type MarketOf = zeitgeist_primitives::types::Market< - AccountIdTest, - BalanceOf, - ::BlockNumber, - ::Moment, - Asset, ->; - // Macro for comparing fixed point u128. #[allow(unused_macros)] macro_rules! assert_approx { @@ -120,7 +101,7 @@ macro_rules! assert_approx { #[test_case(vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D, ASSET_E, ASSET_A]; "start and end")] #[test_case(vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D, ASSET_E, ASSET_E]; "successive at end")] #[test_case(vec![ASSET_A, ASSET_B, ASSET_C, ASSET_A, ASSET_E, ASSET_D]; "start and middle")] -fn create_pool_fails_with_duplicate_assets(assets: Vec>>) { +fn create_pool_fails_with_duplicate_assets(assets: Vec>) { ExtBuilder::default().build().execute_with(|| { assets.iter().cloned().for_each(|asset| { let _ = Currencies::deposit(asset, &BOB, _10000); @@ -130,12 +111,9 @@ fn create_pool_fails_with_duplicate_assets(assets: Vec Swaps::create_pool( BOB, assets, - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec![DEFAULT_WEIGHT; asset_count]), + DEFAULT_LIQUIDITY, + vec![DEFAULT_WEIGHT; asset_count], ), Error::::SomeIdenticalAssets ); @@ -145,7 +123,7 @@ fn create_pool_fails_with_duplicate_assets(assets: Vec #[test] fn destroy_pool_fails_if_pool_does_not_exist() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_noop!(Swaps::destroy_pool(42), Error::::PoolDoesNotExist); }); } @@ -153,7 +131,7 @@ fn destroy_pool_fails_if_pool_does_not_exist() { #[test] fn destroy_pool_correctly_cleans_up_pool() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); let alice_balance_before = [ Currencies::free_balance(ASSET_A, &ALICE), Currencies::free_balance(ASSET_B, &ALICE), @@ -161,7 +139,7 @@ fn destroy_pool_correctly_cleans_up_pool() { Currencies::free_balance(ASSET_D, &ALICE), ]; assert_ok!(Swaps::destroy_pool(DEFAULT_POOL_ID)); - assert_err!(Swaps::pool(DEFAULT_POOL_ID), Error::::PoolDoesNotExist); + assert_err!(Swaps::pool_by_id(DEFAULT_POOL_ID), Error::::PoolDoesNotExist); // Ensure that funds _outside_ of the pool are not impacted! // TODO(#792): Remove pool shares. let total_pool_shares = Currencies::total_issuance(Swaps::pool_shares_id(DEFAULT_POOL_ID)); @@ -173,7 +151,7 @@ fn destroy_pool_correctly_cleans_up_pool() { fn destroy_pool_emits_correct_event() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Swaps::destroy_pool(DEFAULT_POOL_ID)); System::assert_last_event(Event::PoolDestroyed(DEFAULT_POOL_ID).into()); }); @@ -182,7 +160,7 @@ fn destroy_pool_emits_correct_event() { #[test] fn allows_the_full_user_lifecycle() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _5, vec!(_25, _25, _25, _25),) @@ -262,9 +240,9 @@ fn allows_the_full_user_lifecycle() { #[test] fn assets_must_be_bounded() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::mutate_pool(0, |pool| { - pool.weights.as_mut().unwrap().remove(&ASSET_B); + pool.weights.remove(&ASSET_B); Ok(()) })); @@ -362,35 +340,21 @@ fn create_pool_generates_a_new_pool_with_correct_parameters_for_cpmm() { ASSETS.iter().cloned().for_each(|asset| { assert_ok!(Currencies::deposit(asset, &BOB, amount)); }); - assert_ok!(Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(1), - Some(amount), - Some(vec!(_4, _3, _2, _1)), - )); + assert_ok!(Swaps::create_pool(BOB, ASSETS.to_vec(), 1, amount, vec!(_4, _3, _2, _1),)); let next_pool_after = Swaps::next_pool_id(); assert_eq!(next_pool_after, 1); let pool = Swaps::pools(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.assets, ASSETS); - assert_eq!(pool.base_asset, BASE_ASSET); - assert_eq!(pool.market_id, DEFAULT_MARKET_ID); - assert_eq!(pool.pool_status, PoolStatus::Initialized); - assert_eq!(pool.scoring_rule, ScoringRule::CPMM); - assert_eq!(pool.swap_fee, Some(1)); - assert_eq!(pool.total_subsidy, None); - assert_eq!(pool.total_weight.unwrap(), _10); + assert_eq!(pool.assets.clone().into_inner(), ASSETS); + assert_eq!(pool.swap_fee, 1); + assert_eq!(pool.total_weight, _10); - assert_eq!(*pool.weights.as_ref().unwrap().get(&ASSET_A).unwrap(), _4); - assert_eq!(*pool.weights.as_ref().unwrap().get(&ASSET_B).unwrap(), _3); - assert_eq!(*pool.weights.as_ref().unwrap().get(&ASSET_C).unwrap(), _2); - assert_eq!(*pool.weights.as_ref().unwrap().get(&ASSET_D).unwrap(), _1); + assert_eq!(*pool.weights.get(&ASSET_A).unwrap(), _4); + assert_eq!(*pool.weights.get(&ASSET_B).unwrap(), _3); + assert_eq!(*pool.weights.get(&ASSET_C).unwrap(), _2); + assert_eq!(*pool.weights.get(&ASSET_D).unwrap(), _1); let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); System::assert_last_event( @@ -405,215 +369,16 @@ fn create_pool_generates_a_new_pool_with_correct_parameters_for_cpmm() { }); } -#[test] -fn create_pool_generates_a_new_pool_with_correct_parameters_for_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - let next_pool_before = Swaps::next_pool_id(); - assert_eq!(next_pool_before, 0); - - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - - let next_pool_after = Swaps::next_pool_id(); - assert_eq!(next_pool_after, 1); - let pool = Swaps::pools(DEFAULT_POOL_ID).unwrap(); - - assert_eq!(pool.assets, ASSETS.to_vec()); - assert_eq!(pool.base_asset, ASSET_D); - assert_eq!(pool.pool_status, PoolStatus::CollectingSubsidy); - assert_eq!(pool.scoring_rule, ScoringRule::RikiddoSigmoidFeeMarketEma); - assert_eq!(pool.swap_fee, None); - assert_eq!(pool.total_subsidy, Some(0)); - assert_eq!(pool.total_weight, None); - assert_eq!(pool.weights, None); - }); -} - -#[test] -fn destroy_pool_in_subsidy_phase_returns_subsidy_and_closes_pool() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Errors trigger correctly. - assert_noop!( - Swaps::destroy_pool_in_subsidy_phase(DEFAULT_POOL_ID), - Error::::PoolDoesNotExist - ); - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::destroy_pool_in_subsidy_phase(DEFAULT_POOL_ID), - Error::::InvalidStateTransition - ); - - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let pool_id = 1; - // Reserve some funds for subsidy - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), pool_id, _25)); - assert_ok!(Currencies::deposit(ASSET_D, &BOB, _26)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(BOB), pool_id, _26)); - assert_eq!(Currencies::reserved_balance(ASSET_D, &ALICE), _25); - assert_eq!(Currencies::reserved_balance(ASSET_D, &BOB), _26); - - assert_ok!(Swaps::destroy_pool_in_subsidy_phase(pool_id)); - // Rserved balanced was returned and all storage cleared. - assert_eq!(Currencies::reserved_balance(ASSET_D, &ALICE), 0); - assert_eq!(Currencies::reserved_balance(ASSET_D, &BOB), 0); - assert!(!crate::SubsidyProviders::::contains_key(pool_id, ALICE)); - assert!(!crate::Pools::::contains_key(pool_id)); - System::assert_last_event( - Event::PoolDestroyedInSubsidyPhase(pool_id, vec![(BOB, _26), (ALICE, _25)]).into(), - ); - }); -} - -#[test] -fn distribute_pool_share_rewards() { - ExtBuilder::default().build().execute_with(|| { - // Create Rikiddo pool - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - let subsidy_per_acc = ::MinSubsidy::get(); - let asset_per_acc = subsidy_per_acc / 10; - let base_asset = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().base_asset; - let winning_asset = ASSET_A; - - // Join subsidy with some providers - let subsidy_providers: Vec = (1000..1010).collect(); - subsidy_providers.iter().for_each(|provider| { - assert_ok!(Currencies::deposit(base_asset, provider, subsidy_per_acc)); - assert_ok!(Swaps::pool_join_subsidy( - RuntimeOrigin::signed(*provider), - DEFAULT_POOL_ID, - subsidy_per_acc - )); - }); - - // End subsidy phase - assert_ok!(Swaps::end_subsidy_phase(DEFAULT_POOL_ID)); - - // Buy some winning outcome assets with other accounts and remember how many - let asset_holders: Vec = (1010..1020).collect(); - asset_holders.iter().for_each(|asset_holder| { - assert_ok!(Currencies::deposit(base_asset, asset_holder, asset_per_acc + 20)); - assert_ok!(Swaps::swap_exact_amount_out( - RuntimeOrigin::signed(*asset_holder), - DEFAULT_POOL_ID, - base_asset, - Some(asset_per_acc + 20), - winning_asset, - asset_per_acc, - Some(_5), - )); - }); - let total_winning_assets = asset_holders.len().saturated_into::() * asset_per_acc; - - // Distribute pool share rewards - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - let winner_payout_account: AccountIdTest = 1337; - Swaps::distribute_pool_share_rewards( - &pool, - DEFAULT_POOL_ID, - base_asset, - winning_asset, - &winner_payout_account, - ); - - // Check if every subsidy provider got their fair share (percentage) - assert_ne!(Currencies::total_balance(base_asset, &subsidy_providers[0]), 0); - - for idx in 1..subsidy_providers.len() { - assert_eq!( - Currencies::total_balance(base_asset, &subsidy_providers[idx - 1]), - Currencies::total_balance(base_asset, &subsidy_providers[idx]) - ); - } - - // Check if the winning asset holders can be paid out. - let winner_payout_acc_balance = - Currencies::total_balance(base_asset, &winner_payout_account); - assert!(total_winning_assets <= winner_payout_acc_balance); - // Ensure the remaining "dust" is tiny - assert!(winner_payout_acc_balance - total_winning_assets < BASE / 1_000_000); - }); -} - -#[test] -fn end_subsidy_phase_distributes_shares_and_outcome_assets() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::end_subsidy_phase(DEFAULT_POOL_ID), - Error::::InvalidStateTransition - ); - assert_noop!(Swaps::end_subsidy_phase(1), Error::::PoolDoesNotExist); - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let pool_id = 1; - assert_storage_noop!(Swaps::end_subsidy_phase(pool_id).unwrap()); - - // Reserve some funds for subsidy - let min_subsidy = ::MinSubsidy::get(); - let subsidy_alice = min_subsidy; - let subsidy_bob = min_subsidy + _25; - assert_ok!(Currencies::deposit(ASSET_D, &ALICE, subsidy_alice)); - assert_ok!(Currencies::deposit(ASSET_D, &BOB, subsidy_bob)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(ALICE), pool_id, min_subsidy)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(BOB), pool_id, subsidy_bob)); - assert!(Swaps::end_subsidy_phase(pool_id).unwrap().result); - - // Check that subsidy was deposited, shares were distributed in exchange, the initial - // outstanding event outcome assets are assigned to the pool account and pool is active. - assert_eq!(Currencies::reserved_balance(ASSET_D, &ALICE), 0); - assert_eq!(Currencies::reserved_balance(ASSET_D, &BOB), 0); - - let pool_shares_id = Swaps::pool_shares_id(pool_id); - assert_eq!(Currencies::total_balance(pool_shares_id, &ALICE), subsidy_alice); - assert_eq!(Currencies::total_balance(pool_shares_id, &BOB), subsidy_bob); - - let pool_account_id = Swaps::pool_account_id(&pool_id); - let total_subsidy = Currencies::total_balance(ASSET_D, &pool_account_id); - let total_subsidy_expected = subsidy_alice + subsidy_bob; - assert_eq!(total_subsidy, total_subsidy_expected); - System::assert_last_event( - Event::SubsidyCollected( - pool_id, - vec![(BOB, subsidy_bob), (ALICE, subsidy_alice)], - total_subsidy_expected, - ) - .into(), - ); - let initial_outstanding_assets = RikiddoSigmoidFeeMarketEma::initial_outstanding_assets( - pool_id, - (ASSETS.len() - 1).saturated_into::(), - total_subsidy, - ) - .unwrap(); - let balance_asset_a = Currencies::total_balance(ASSET_A, &pool_account_id); - let balance_asset_b = Currencies::total_balance(ASSET_B, &pool_account_id); - let balance_asset_c = Currencies::total_balance(ASSET_C, &pool_account_id); - assert!(balance_asset_a == initial_outstanding_assets); - assert!(balance_asset_a == balance_asset_b && balance_asset_b == balance_asset_c); - assert_eq!(Swaps::pool_by_id(pool_id).unwrap().pool_status, PoolStatus::Active); - }); -} - -#[test_case(PoolStatus::Initialized; "Initialized")] #[test_case(PoolStatus::Closed; "Closed")] -#[test_case(PoolStatus::Clean; "Clean")] fn single_asset_operations_and_swaps_fail_on_invalid_status_before_clean(status: PoolStatus) { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // For this test, we need to give Alice some pool shares, as well. We don't do this in // `create_initial_pool_...` so that there are exacly 100 pool shares, making computations // in other tests easier. assert_ok!(Currencies::deposit(Swaps::pool_shares_id(DEFAULT_POOL_ID), &ALICE, _25)); assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = status; + pool.status = status; Ok(()) })); @@ -682,7 +447,7 @@ fn single_asset_operations_and_swaps_fail_on_invalid_status_before_clean(status: #[test] fn pool_join_fails_if_pool_is_closed() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); assert_noop!( Swaps::pool_join( @@ -696,88 +461,6 @@ fn pool_join_fails_if_pool_is_closed() { }); } -#[test] -fn most_operations_fail_if_pool_is_clean() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - assert_ok!(Swaps::clean_up_pool( - &MarketType::Categorical(0), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(if let Asset::CategoricalOutcome(_, idx) = ASSET_A { - idx - } else { - 0 - }), - &Default::default() - )); - - assert_noop!( - Swaps::pool_join(RuntimeOrigin::signed(ALICE), DEFAULT_POOL_ID, _1, vec![_10]), - Error::::InvalidPoolStatus, - ); - assert_noop!( - Swaps::pool_exit_with_exact_asset_amount( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - _1, - _2 - ), - Error::::PoolIsNotActive - ); - assert_noop!( - Swaps::pool_exit_with_exact_pool_amount( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - _1, - _1_2 - ), - Error::::PoolIsNotActive - ); - assert_noop!( - Swaps::pool_join_with_exact_asset_amount( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_E, - 1, - 1 - ), - Error::::PoolIsNotActive - ); - assert_noop!( - Swaps::pool_join_with_exact_pool_amount(alice_signed(), DEFAULT_POOL_ID, ASSET_E, 1, 1), - Error::::PoolIsNotActive - ); - assert_ok!(Currencies::deposit(ASSET_A, &ALICE, u64::MAX.into())); - assert_noop!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - u64::MAX.into(), - ASSET_B, - Some(_1), - Some(_1), - ), - Error::::PoolIsNotActive - ); - assert_noop!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - Some(u64::MAX.into()), - ASSET_B, - _1, - Some(_1), - ), - Error::::PoolIsNotActive - ); - }); -} - #[test_case(_3, _3, _100, _100, 0, 10_000_000_000, 10_000_000_000)] #[test_case(_3, _3, _100, _150, 0, 6_666_666_667, 6_666_666_667)] #[test_case(_3, _4, _100, _100, 0, 13_333_333_333, 13_333_333_333)] @@ -808,12 +491,9 @@ fn get_spot_price_returns_correct_results_cpmm( assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(swap_fee), - Some(amount_in_pool), - Some(vec!(weight_in, weight_out, _2, _3)) + swap_fee, + amount_in_pool, + vec!(weight_in, weight_out, _2, _3), )); let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); @@ -835,116 +515,10 @@ fn get_spot_price_returns_correct_results_cpmm( }); } -#[test] -fn get_spot_price_returns_correct_results_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - assert_noop!( - Swaps::get_spot_price(&DEFAULT_POOL_ID, &ASSETS[0], &ASSETS[0], true), - Error::::PoolIsNotActive - ); - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, 0); - - // Asset out, base currency in. Should receive about 1/3 -> price about 3 - let price_base_in = - Swaps::get_spot_price(&DEFAULT_POOL_ID, &ASSETS[0], &BASE_ASSET, true).unwrap(); - // Between 0.3 and 0.4 - assert!(price_base_in > 28 * BASE / 10 && price_base_in < 31 * BASE / 10); - // Base currency in, asset out. Price about 3. - let price_base_out = - Swaps::get_spot_price(&DEFAULT_POOL_ID, &BASE_ASSET, &ASSETS[0], true).unwrap(); - // Between 2.9 and 3.1 - assert!(price_base_out > 3 * BASE / 10 && price_base_out < 4 * BASE / 10); - // Asset in, asset out. Price about 1. - let price_asset_in_out = - Swaps::get_spot_price(&DEFAULT_POOL_ID, &ASSETS[0], &ASSETS[1], true).unwrap(); - // Between 0.9 and 1.1 - assert!(price_asset_in_out > 9 * BASE / 10 && price_asset_in_out < 11 * BASE / 10); - }); -} - -#[test] -fn get_all_spot_price_returns_correct_results_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - assert_noop!( - Swaps::get_spot_price(&DEFAULT_POOL_ID, &ASSETS[0], &ASSETS[0], true), - Error::::PoolIsNotActive - ); - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, 0); - - let prices = - Swaps::get_all_spot_prices(&DEFAULT_POOL_ID, false).expect("get_all_spot_prices fails"); - // Base currency in, asset out. - // Price Between 0.3 and 0.4 - for (asset, price) in prices { - // ASSETS.last() is base_asset - if asset != *ASSETS.last().expect("no last asset") { - assert!(price > 3 * BASE / 10 && price < 4 * BASE / 10); - } - } - }); -} - -#[test_case(_3, _3, _2, _2, 0, 15_000_000_000, 15_000_000_000, 10_000_000_000, 10_000_000_000)] -#[test_case(_3, _3, _1, _3, 0, 10_000_000_000, 10_000_000_000, 3_333_333_333, 10_000_000_000)] -#[test_case(_3, _4, _1, _1, 0, 30_000_000_000, 40_000_000_000, 10_000_000_000, 10_000_000_000)] -#[test_case(_3, _4, _10, _4, 0, 7_500_000_000, 10_000_000_000, 25_000_000_000, 10_000_000_000)] -#[test_case(_3, _6, _4, _5, 0, 6_000_000_000, 12_000_000_000, 8_000_000_000, 10_000_000_000)] -#[test_case(_3, _3, _4, _5, 0, 6_000_000_000, 6_000_000_000, 8_000_000_000, 10_000_000_000)] -#[test_case(_3, _3, _10, _10, _1_10, 3_333_333_333, 3_333_333_333, 11_111_111_111, 11_111_111_111)] -#[test_case(_3, _3, _10, _5, _1_10, 6_666_666_667, 6_666_666_667, 22_222_222_222, 11_111_111_111)] -#[test_case(_3, _4, _10, _10, _1_10, 3_333_333_333, 4_444_444_444, 11_111_111_111, 11_111_111_111)] -#[test_case(_3, _4, _10, _5, _1_10, 6_666_666_667, 8_888_888_889, 22_222_222_222, 11_111_111_111)] -#[test_case(_3, _6, _2, _5, _1_10, 6_666_666_667, 13_333_333_333, 4_444_444_444, 11_111_111_111)] -#[test_case(_3, _6, _2, _10, _1_10, 3_333_333_333, 6_666_666_667, 2_222_222_222, 11_111_111_111)] -fn get_all_spot_prices_returns_correct_results_cpmm( - weight_a: u128, - weight_b: u128, - weight_c: u128, - weight_d: u128, - swap_fee: BalanceOf, - exp_spot_price_a_with_fees: BalanceOf, - exp_spot_price_b_with_fees: BalanceOf, - exp_spot_price_c_with_fees: BalanceOf, - exp_spot_price_d_with_fees: BalanceOf, -) { - ExtBuilder::default().build().execute_with(|| { - ASSETS.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _100)); - }); - assert_ok!(Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(swap_fee), - Some(_100), - Some(vec!(weight_a, weight_b, weight_c, weight_d)) - )); - - // Gets spot prices for all assets against base_asset. - let prices = - Swaps::get_all_spot_prices(&DEFAULT_POOL_ID, true).expect("get_all_spot_prices failed"); - for (asset, price) in prices { - if asset == ASSET_A { - assert_eq!(exp_spot_price_a_with_fees, price); - } else if asset == ASSET_B { - assert_eq!(exp_spot_price_b_with_fees, price); - } else if asset == ASSET_C { - assert_eq!(exp_spot_price_c_with_fees, price); - } else if asset == ASSET_D { - assert_eq!(exp_spot_price_d_with_fees, price); - } - } - }); -} - #[test] fn in_amount_must_be_equal_or_less_than_max_in_ratio() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Currencies::deposit(ASSET_A, &ALICE, u64::MAX.into())); @@ -975,12 +549,9 @@ fn pool_exit_with_exact_asset_amount_satisfies_max_out_ratio_constraints() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(amount_in_pool), - Some(vec!(_2, _2, _2, _5)), + amount_in_pool, + vec!(_2, _2, _2, _5), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -1009,12 +580,9 @@ fn pool_exit_with_exact_pool_amount_satisfies_max_in_ratio_constraints() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(amount_in_pool), - Some(vec!(_2, _2, _2, _5)), + amount_in_pool, + vec!(_2, _2, _2, _5), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -1043,12 +611,9 @@ fn pool_join_with_exact_asset_amount_satisfies_max_in_ratio_constraints() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(amount_in_pool), - Some(vec!(_2, _2, _2, _5)), + amount_in_pool, + vec!(_2, _2, _2, _5), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); let asset_amount = DEFAULT_LIQUIDITY; @@ -1083,12 +648,9 @@ fn pool_join_with_exact_pool_amount_satisfies_max_out_ratio_constraints() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(amount_in_pool), - Some(vec!(_2, _2, _2, _5)), + amount_in_pool, + vec!(_2, _2, _2, _5), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); let max_asset_amount = _10000; @@ -1107,27 +669,10 @@ fn pool_join_with_exact_pool_amount_satisfies_max_out_ratio_constraints() { }); } -#[test] -fn admin_clean_up_pool_fails_if_origin_is_not_root() { - ExtBuilder::default().build().execute_with(|| { - let idx = if let Asset::CategoricalOutcome(_, idx) = ASSET_A { idx } else { 0 }; - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_ok!(MarketCommons::insert_market_pool(DEFAULT_MARKET_ID, DEFAULT_POOL_ID)); - assert_noop!( - Swaps::admin_clean_up_pool( - alice_signed(), - DEFAULT_POOL_ID, - OutcomeReport::Categorical(idx) - ), - BadOrigin - ); - }); -} - #[test] fn out_amount_must_be_equal_or_less_than_max_out_ratio() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_noop!( Swaps::swap_exact_amount_out( @@ -1147,7 +692,7 @@ fn out_amount_must_be_equal_or_less_than_max_out_ratio() { #[test] fn pool_join_or_exit_raises_on_zero_value() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_noop!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, 0, vec!(_1, _1, _1, _1)), @@ -1159,6 +704,17 @@ fn pool_join_or_exit_raises_on_zero_value() { Error::::ZeroAmount ); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + 0, + vec!(_1, _1, _1, _1) + ), + Error::::ZeroAmount + ); + assert_noop!( Swaps::pool_join_with_exact_pool_amount(alice_signed(), DEFAULT_POOL_ID, ASSET_A, 0, 0), Error::::ZeroAmount @@ -1198,7 +754,7 @@ fn pool_exit_decreases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); @@ -1232,7 +788,7 @@ fn pool_exit_decreases_correct_pool_parameters() { fn pool_exit_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_exit( RuntimeOrigin::signed(BOB), DEFAULT_POOL_ID, @@ -1257,7 +813,7 @@ fn pool_exit_emits_correct_events() { fn pool_exit_decreases_correct_pool_parameters_with_exit_fee() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_exit( RuntimeOrigin::signed(BOB), @@ -1293,41 +849,39 @@ fn pool_exit_decreases_correct_pool_parameters_with_exit_fee() { } #[test] -fn pool_exit_decreases_correct_pool_parameters_on_cleaned_up_pool() { - // Test is the same as +fn force_pool_exit_decreases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { + ::ExitFee::set(&0u128); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_ok!(MarketCommons::insert_market_pool(DEFAULT_MARKET_ID, DEFAULT_POOL_ID)); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - assert_ok!(Swaps::admin_clean_up_pool( - RuntimeOrigin::root(), + + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, DEFAULT_POOL_ID, - OutcomeReport::Categorical(65), + _1, + vec!(_1, _1, _1, _1), )); - assert_ok!(Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1),)); System::assert_last_event( Event::PoolExit(PoolAssetsEvent { - assets: vec![ASSET_A, ASSET_D], - bounds: vec![_1, _1], + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![_1, _1, _1, _1], cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: 0 }, - transferred: vec![_1 + 1, _1 + 1], + transferred: vec![_1 + 1, _1 + 1, _1 + 1, _1 + 1], pool_amount: _1, }) .into(), ); assert_all_parameters( - [_25 + 1, _24, _24, _25 + 1], + [_25 + 1, _25 + 1, _25 + 1, _25 + 1], 0, - // Note: Although the asset is deleted from the pool, the assets B/C still remain on the - // pool account. [ DEFAULT_LIQUIDITY - 1, - DEFAULT_LIQUIDITY + _1, - DEFAULT_LIQUIDITY + _1, + DEFAULT_LIQUIDITY - 1, + DEFAULT_LIQUIDITY - 1, DEFAULT_LIQUIDITY - 1, ], DEFAULT_LIQUIDITY, @@ -1336,123 +890,69 @@ fn pool_exit_decreases_correct_pool_parameters_on_cleaned_up_pool() { } #[test] -fn pool_exit_subsidy_unreserves_correct_values() { +fn force_pool_exit_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { - // Events cannot be emitted on block zero... frame_system::Pallet::::set_block_number(1); - - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - - // Add some subsidy - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - let mut reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let mut noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - let mut total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, _25); - assert_eq!(reserved, noted); - assert_eq!(reserved, total_subsidy); - - // Exit 5 subsidy and see if the storage is consistent - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _5)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, noted); - assert_eq!(reserved, total_subsidy); - System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - _5, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - _5, - ) - .into(), - ); - - // Exit the remaining subsidy (in fact, we attempt to exit with more than remaining!) and - // see if the storage is consistent - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - assert!(>::get(DEFAULT_POOL_ID, ALICE).is_none()); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert_eq!(reserved, total_subsidy); + create_initial_pool_with_funds_for_alice(0, true); + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _1, + vec!(1, 2, 3, 4), + )); + let amount = _1 - BASE / 10; // Subtract 10% fees! System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - _25, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - _20, - ) + Event::PoolExit(PoolAssetsEvent { + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![1, 2, 3, 4], + cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: BOB }, + transferred: vec![amount; 4], + pool_amount: _1, + }) .into(), ); - - // Add some subsidy, manually remove some reserved balance (create inconsistency) - // and check if the internal values are adjusted to the inconsistency. - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - assert_eq!(Currencies::unreserve(ASSET_D, &ALICE, _20), 0); - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _20)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - assert!(>::get(DEFAULT_POOL_ID, ALICE).is_none()); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert_eq!(reserved, total_subsidy); }); } #[test] -fn pool_exit_subsidy_fails_if_no_subsidy_is_provided() { +fn force_pool_exit_decreases_correct_pool_parameters_with_exit_fee() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::NoSubsidyProvided - ); - }); -} + frame_system::Pallet::::set_block_number(1); + create_initial_pool_with_funds_for_alice(0, true); -#[test] -fn pool_exit_subsidy_fails_if_amount_is_zero() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, 0), - Error::::ZeroAmount - ); - }); -} + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _10, + vec!(_1, _1, _1, _1), + )); -#[test] -fn pool_exit_subsidy_fails_if_pool_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::PoolDoesNotExist - ); - }); -} + let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); + let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); + assert_eq!(Currencies::free_balance(ASSET_A, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_B, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_C, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_D, &BOB), _9); + assert_eq!(Currencies::free_balance(pool_shares_id, &BOB), DEFAULT_LIQUIDITY - _10); + assert_eq!(Currencies::free_balance(ASSET_A, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_B, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_C, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_D, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::total_issuance(pool_shares_id), DEFAULT_LIQUIDITY - _10); -#[test] -fn pool_exit_subsidy_fails_if_scoring_rule_is_not_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::InvalidScoringRule + System::assert_last_event( + Event::PoolExit(PoolAssetsEvent { + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![_1, _1, _1, _1], + cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: BOB }, + transferred: vec![_9, _9, _9, _9], + pool_amount: _10, + }) + .into(), ); - }); + }) } #[test_case(49_999_999_665, 12_272_234_300, 0, 0; "no_fees")] @@ -1470,7 +970,7 @@ fn pool_exit_with_exact_pool_amount_exchanges_correct_values( let asset_amount_joined = _5; ::ExitFee::set(&exit_fee); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -1530,7 +1030,7 @@ fn pool_exit_with_exact_asset_amount_exchanges_correct_values( let asset_amount_joined = _5; ::ExitFee::set(&exit_fee); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -1582,7 +1082,7 @@ fn pool_exit_with_exact_asset_amount_exchanges_correct_values( fn pool_exit_is_not_allowed_with_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Alice has no pool shares! assert_noop!( @@ -1599,11 +1099,44 @@ fn pool_exit_is_not_allowed_with_insufficient_funds() { }) } +#[test] +fn force_pool_exit_is_not_allowed_with_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + create_initial_pool_with_funds_for_alice(0, true); + + // Alice has no pool shares! + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _1, + vec!(0, 0, 0, 0) + ), + Error::::InsufficientBalance, + ); + + // Now Alice has 25 pool shares! + let _ = Currencies::deposit(Swaps::pool_shares_id(DEFAULT_POOL_ID), &ALICE, _25); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _26, + vec!(0, 0, 0, 0) + ), + Error::::InsufficientBalance, + ); + }) +} + #[test] fn pool_join_increases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _5, vec!(_25, _25, _25, _25),) @@ -1626,7 +1159,7 @@ fn pool_join_increases_correct_pool_parameters() { fn pool_join_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); System::assert_last_event( Event::PoolJoin(PoolAssetsEvent { @@ -1641,159 +1174,6 @@ fn pool_join_emits_correct_events() { }); } -#[test] -fn pool_join_subsidy_reserves_correct_values() { - ExtBuilder::default().build().execute_with(|| { - // Events cannot be emitted on block zero... - frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _20)); - let mut reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let mut noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - assert_eq!(reserved, _20); - assert_eq!(reserved, noted); - assert_eq!(reserved, Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap()); - System::assert_last_event( - Event::PoolJoinSubsidy( - ASSET_D, - _20, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - ) - .into(), - ); - - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _5)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - assert_eq!(reserved, _25); - assert_eq!(reserved, noted); - assert_eq!(reserved, Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap()); - assert_storage_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _5).unwrap_or(()) - ); - System::assert_last_event( - Event::PoolJoinSubsidy( - ASSET_D, - _5, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - ) - .into(), - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_amount_is_zero() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_pool_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::PoolDoesNotExist - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_scoring_rule_is_not_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::InvalidScoringRule - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_subsidy_is_below_min_per_account() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_join_subsidy( - alice_signed(), - 0, - ::MinSubsidyPerAccount::get() - 1 - ), - Error::::InvalidSubsidyAmount, - ); - }); -} - -#[test] -fn pool_join_subsidy_with_small_amount_is_ok_if_account_is_already_a_provider() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let large_amount = ::MinSubsidyPerAccount::get(); - let small_amount = 1; - let total_amount = large_amount + small_amount; - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, large_amount)); - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, small_amount)); - let reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - let total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, total_amount); - assert_eq!(noted, total_amount); - assert_eq!(total_subsidy, total_amount); - }); -} - -#[test] -fn pool_exit_subsidy_unreserves_remaining_subsidy_if_below_min_per_account() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let large_amount = ::MinSubsidyPerAccount::get(); - let small_amount = 1; - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, large_amount)); - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, small_amount)); - let reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let noted = >::get(DEFAULT_POOL_ID, ALICE); - let total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert!(noted.is_none()); - assert_eq!(total_subsidy, 0); - System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - small_amount, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - large_amount, - ) - .into(), - ); - }); -} - #[test_case(_1, 2_490_679_300, 0; "without_swap_fee")] #[test_case(_1, 2_304_521_500, _1_10; "with_swap_fee")] fn pool_join_with_exact_asset_amount_exchanges_correct_values( @@ -1803,7 +1183,7 @@ fn pool_join_with_exact_asset_amount_exchanges_correct_values( ) { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); let bound = 0; let alice_sent = _1; assert_ok!(Swaps::pool_join_with_exact_asset_amount( @@ -1846,7 +1226,7 @@ fn pool_join_with_exact_pool_amount_exchanges_correct_values( ) { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); let bound = _5; assert_ok!(Swaps::pool_join_with_exact_pool_amount( alice_signed(), @@ -1882,7 +1262,7 @@ fn pool_join_with_exact_pool_amount_exchanges_correct_values( #[test] fn provided_values_len_must_equal_assets_len() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_noop!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _5, vec![]), Error::::ProvidedValuesLenMustEqualAssetsLen @@ -1891,91 +1271,15 @@ fn provided_values_len_must_equal_assets_len() { Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _5, vec![]), Error::::ProvidedValuesLenMustEqualAssetsLen ); - }); -} - -#[test] -fn clean_up_pool_leaves_only_correct_assets() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - assert_ok!(Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - )); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Clean); - assert_eq!(Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().assets, vec![ASSET_A, ASSET_D]); - System::assert_last_event(Event::PoolCleanedUp(DEFAULT_POOL_ID).into()); - }); -} - -#[test] -fn clean_up_pool_handles_rikiddo_pools_properly() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - - // We need to forcefully close the pool (Rikiddo pools are not allowed to be cleaned - // up when CollectingSubsidy). - assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = PoolStatus::Closed; - Ok(()) - })); - - assert_ok!(Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - )); - - // Rikiddo instance does not exist anymore. - assert_storage_noop!(RikiddoSigmoidFeeMarketEma::clear(DEFAULT_POOL_ID).unwrap_or(())); - }); -} - -#[test_case(PoolStatus::Active; "active")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(PoolStatus::Initialized; "initialized")] -fn clean_up_pool_fails_if_pool_is_not_closed(pool_status: PoolStatus) { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; - Ok(()) - })); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - assert_noop!( - Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - ), - Error::::InvalidStateTransition - ); - }); -} - -#[test] -fn clean_up_pool_fails_if_winning_asset_is_not_found() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); assert_noop!( - Swaps::clean_up_pool( - &MarketType::Categorical(1337), + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, DEFAULT_POOL_ID, - &OutcomeReport::Categorical(1337), - &Default::default() + _5, + vec![] ), - Error::::WinningAssetNotFound + Error::::ProvidedValuesLenMustEqualAssetsLen ); }); } @@ -1986,7 +1290,7 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm() { let asset_bound = Some(_1 / 2); let max_price = Some(_2); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::swap_exact_amount_in( alice_signed(), DEFAULT_POOL_ID, @@ -2033,12 +1337,9 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm_with_fees() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(BASE / 10), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + BASE / 10, + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -2087,7 +1388,7 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm_with_fees() { fn swap_exact_amount_in_fails_if_no_limit_is_specified() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); assert_noop!( Swaps::swap_exact_amount_in( alice_signed(), @@ -2106,7 +1407,7 @@ fn swap_exact_amount_in_fails_if_no_limit_is_specified() { #[test] fn swap_exact_amount_in_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected amount to receive from trading BASE of A for B. See // swap_exact_amount_in_exchanges_correct_values_with_cpmm for details. let expected_amount = 9900990100; @@ -2128,7 +1429,7 @@ fn swap_exact_amount_in_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm #[test] fn swap_exact_amount_in_fails_if_max_price_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // We're swapping 1:1, but due to slippage the price will exceed _1, so this should raise an // error: assert_noop!( @@ -2146,73 +1447,13 @@ fn swap_exact_amount_in_fails_if_max_price_is_not_satisfied_with_cpmm() { }); } -#[test] -fn swap_exact_amount_in_exchanges_correct_values_with_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, true); - - // Generate funds, add subsidy and start pool. - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, _1); - assert_ok!(Currencies::deposit(ASSET_A, &ALICE, _1)); - - // Check if unsupport trades are catched (base_asset in || asset_in == asset_out). - assert_noop!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - _1, - ASSET_B, - Some(_1 / 2), - Some(_2), - ), - Error::::UnsupportedTrade - ); - assert_noop!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - _1, - ASSET_D, - Some(_1 / 2), - Some(_2), - ), - Error::::UnsupportedTrade - ); - assert_ok!(Currencies::withdraw(ASSET_D, &ALICE, _1)); - - // Check if the trade is executed. - let asset_a_issuance = Currencies::total_issuance(ASSET_A); - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - _1, - ASSET_D, - Some(0), - Some(_20), - )); - - // Check if the balances were updated accordingly. - let asset_a_issuance_after = Currencies::total_issuance(ASSET_A); - let alice_balance_a_after = Currencies::total_balance(ASSET_A, &ALICE); - let alice_balance_d_after = Currencies::total_balance(ASSET_D, &ALICE); - assert_eq!(asset_a_issuance - asset_a_issuance_after, _1); - assert_eq!(alice_balance_a_after, 0); - - // Received base_currency greater than 0.3 and smaller than 0.4 - assert!(alice_balance_d_after > 3 * BASE / 10 && alice_balance_d_after < 4 * BASE / 10); - }); -} - #[test] fn swap_exact_amount_out_exchanges_correct_values_with_cpmm() { let asset_bound = Some(_2); let max_price = Some(_3); ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::swap_exact_amount_out( alice_signed(), DEFAULT_POOL_ID, @@ -2259,12 +1500,9 @@ fn swap_exact_amount_out_exchanges_correct_values_with_cpmm_with_fees() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(BASE / 10), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + BASE / 10, + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -2311,7 +1549,7 @@ fn swap_exact_amount_out_exchanges_correct_values_with_cpmm_with_fees() { fn swap_exact_amount_out_fails_if_no_limit_is_specified() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); assert_noop!( Swaps::swap_exact_amount_out( alice_signed(), @@ -2330,7 +1568,7 @@ fn swap_exact_amount_out_fails_if_no_limit_is_specified() { #[test] fn swap_exact_amount_out_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected amount of A to swap in for receiving BASE of B. See // swap_exact_amount_out_exchanges_correct_values_with_cpmm for details! let expected_amount = 10101010100; @@ -2352,7 +1590,7 @@ fn swap_exact_amount_out_fails_if_min_asset_amount_out_is_not_satisfied_with_cpm #[test] fn swap_exact_amount_out_fails_if_max_price_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // We're swapping 1:1, but due to slippage the price will exceed 1, so this should raise an // error: assert_noop!( @@ -2370,65 +1608,6 @@ fn swap_exact_amount_out_fails_if_max_price_is_not_satisfied_with_cpmm() { }); } -#[test] -fn swap_exact_amount_out_exchanges_correct_values_with_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, true); - - // Generate funds, add subsidy and start pool. - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, (BASE * 4) / 10); - - // Check if unsupport trades are catched (base_asset out || asset_in == asset_out). - assert_noop!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_B, - Some(_20), - ASSET_D, - _1, - Some(_20), - ), - Error::::UnsupportedTrade - ); - assert_noop!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - Some(_2), - ASSET_D, - _1, - Some(_2), - ), - Error::::UnsupportedTrade - ); - - // Check if the trade is executed. - let asset_a_issuance = Currencies::total_issuance(ASSET_A); - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - Some(_1), - ASSET_A, - _1, - Some(_20), - )); - - // Check if the balances were updated accordingly. - let asset_a_issuance_after = Currencies::total_issuance(ASSET_A); - let alice_balance_a_after = Currencies::total_balance(ASSET_A, &ALICE); - let alice_balance_d_after = Currencies::total_balance(ASSET_D, &ALICE); - assert_eq!(asset_a_issuance_after - asset_a_issuance, _1); - assert_eq!(alice_balance_a_after, _1); - - // Left over base currency must be less than 0.1 - assert!(alice_balance_d_after < BASE / 10); - }); -} - #[test] fn create_pool_fails_on_too_many_assets() { ExtBuilder::default().build().execute_with(|| { @@ -2442,16 +1621,7 @@ fn create_pool_fails_on_too_many_assets() { }); assert_noop!( - Swaps::create_pool( - BOB, - assets.clone(), - *assets.last().unwrap(), - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(weights), - ), + Swaps::create_pool(BOB, assets.clone(), 0, DEFAULT_LIQUIDITY, weights,), Error::::TooManyAssets ); }); @@ -2464,37 +1634,15 @@ fn create_pool_fails_on_too_few_assets() { Swaps::create_pool( BOB, vec!(ASSET_A), - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::TooFewAssets ); }); } -#[test] -fn create_pool_fails_if_base_asset_is_not_in_asset_vector() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::create_pool( - BOB, - vec!(ASSET_A, ASSET_B, ASSET_C), - ASSET_D, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), - ), - Error::::BaseAssetNotFound - ); - }); -} - #[test] fn create_pool_fails_if_swap_fee_is_too_high() { ExtBuilder::default().build().execute_with(|| { @@ -2506,12 +1654,9 @@ fn create_pool_fails_if_swap_fee_is_too_high() { Swaps::create_pool( BOB, ASSETS.to_vec(), - ASSET_D, - 0, - ScoringRule::CPMM, - Some(::MaxSwapFee::get() + 1), - Some(amount), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + ::MaxSwapFee::get() + 1, + amount, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::SwapFeeTooHigh ); @@ -2519,32 +1664,46 @@ fn create_pool_fails_if_swap_fee_is_too_high() { } #[test] -fn create_pool_fails_if_swap_fee_is_unspecified_for_cpmm() { +fn join_pool_exit_pool_does_not_create_extra_tokens() { ExtBuilder::default().build().execute_with(|| { - let amount = _100; + create_initial_pool_with_funds_for_alice(0, true); + ASSETS.iter().cloned().for_each(|asset| { - let _ = Currencies::deposit(asset, &BOB, amount); + let _ = Currencies::deposit(asset, &CHARLIE, _100); }); - assert_noop!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - ASSET_D, - 0, - ScoringRule::CPMM, - None, - Some(amount), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), - ), - Error::::InvalidFeeArgument - ); + + let amount = 123_456_789_123; // Strange number to force rounding errors! + assert_ok!(Swaps::pool_join( + RuntimeOrigin::signed(CHARLIE), + DEFAULT_POOL_ID, + amount, + vec![_10000, _10000, _10000, _10000] + )); + assert_ok!(Swaps::pool_exit( + RuntimeOrigin::signed(CHARLIE), + DEFAULT_POOL_ID, + amount, + vec![0, 0, 0, 0] + )); + + // Check that the pool retains more tokens than before, and that Charlie loses some tokens + // due to fees. + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + assert_ge!(Currencies::free_balance(ASSET_A, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_B, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_C, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_D, &pool_account_id), _100); + assert_le!(Currencies::free_balance(ASSET_A, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_B, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_C, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_D, &CHARLIE), _100); }); } #[test] -fn join_pool_exit_pool_does_not_create_extra_tokens() { +fn join_pool_force_exit_pool_does_not_create_extra_tokens() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); ASSETS.iter().cloned().for_each(|asset| { let _ = Currencies::deposit(asset, &CHARLIE, _100); @@ -2557,8 +1716,9 @@ fn join_pool_exit_pool_does_not_create_extra_tokens() { amount, vec![_10000, _10000, _10000, _10000] )); - assert_ok!(Swaps::pool_exit( - RuntimeOrigin::signed(CHARLIE), + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(DAVE), + CHARLIE, DEFAULT_POOL_ID, amount, vec![0, 0, 0, 0] @@ -2588,17 +1748,14 @@ fn create_pool_fails_on_weight_below_minimum_weight() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!( + DEFAULT_LIQUIDITY, + vec!( DEFAULT_WEIGHT, ::MinWeight::get() - 1, DEFAULT_WEIGHT, DEFAULT_WEIGHT - )), + ), ), Error::::BelowMinimumWeight, ); @@ -2615,17 +1772,14 @@ fn create_pool_fails_on_weight_above_maximum_weight() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!( + DEFAULT_LIQUIDITY, + vec!( DEFAULT_WEIGHT, ::MaxWeight::get() + 1, DEFAULT_WEIGHT, DEFAULT_WEIGHT - )), + ), ), Error::::AboveMaximumWeight, ); @@ -2640,16 +1794,7 @@ fn create_pool_fails_on_total_weight_above_maximum_total_weight() { }); let weight = ::MaxTotalWeight::get() / 4 + 100; assert_noop!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec![weight; 4]), - ), + Swaps::create_pool(BOB, ASSETS.to_vec(), 0, DEFAULT_LIQUIDITY, vec![weight; 4],), Error::::MaxTotalWeight, ); }); @@ -2666,12 +1811,9 @@ fn create_pool_fails_on_insufficient_liquidity() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(min_balance - 1), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + min_balance - 1, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::InsufficientLiquidity, ); @@ -2690,12 +1832,9 @@ fn create_pool_succeeds_on_min_liquidity() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(min_balance), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + min_balance, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_all_parameters( [0; 4], @@ -2717,12 +1856,9 @@ fn create_pool_transfers_the_correct_amount_of_tokens() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(_1234), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + _1234, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); @@ -2747,14 +1883,12 @@ fn close_pool_fails_if_pool_does_not_exist() { }); } -#[test_case(PoolStatus::Closed; "closed")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -fn close_pool_fails_if_pool_is_not_active_or_initialized(pool_status: PoolStatus) { +#[test_case(PoolStatus::Closed)] +fn close_pool_fails_if_pool_is_not_active_or_initialized(status: PoolStatus) { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; + pool.status = status; Ok(()) })); assert_noop!(Swaps::close_pool(0), Error::::InvalidStateTransition); @@ -2765,10 +1899,10 @@ fn close_pool_fails_if_pool_is_not_active_or_initialized(pool_status: PoolStatus fn close_pool_succeeds_and_emits_correct_event_if_pool_exists() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Closed); + let pool = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap(); + assert_eq!(pool.status, PoolStatus::Closed); System::assert_last_event(Event::PoolClosed(DEFAULT_POOL_ID).into()); }); } @@ -2780,15 +1914,12 @@ fn open_pool_fails_if_pool_does_not_exist() { }); } -#[test_case(PoolStatus::Active; "active")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(PoolStatus::Closed; "closed")] -fn open_pool_fails_if_pool_is_not_closed(pool_status: PoolStatus) { +#[test_case(PoolStatus::Open)] +fn open_pool_fails_if_pool_is_not_closed(status: PoolStatus) { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(1), true); + create_initial_pool(1, true); assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; + pool.status = status; Ok(()) })); assert_noop!(Swaps::open_pool(DEFAULT_POOL_ID), Error::::InvalidStateTransition); @@ -2806,16 +1937,13 @@ fn open_pool_succeeds_and_emits_correct_event_if_pool_exists() { assert_ok!(Swaps::create_pool( BOB, vec![ASSET_D, ASSET_B, ASSET_C, ASSET_A], - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(amount), - Some(vec!(_1, _2, _3, _4)), + amount, + vec!(_1, _2, _3, _4), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Active); + let pool = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap(); + assert_eq!(pool.status, PoolStatus::Open); System::assert_last_event(Event::PoolActive(DEFAULT_POOL_ID).into()); }); } @@ -2823,7 +1951,7 @@ fn open_pool_succeeds_and_emits_correct_event_if_pool_exists() { #[test] fn pool_join_fails_if_max_assets_in_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_noop!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1 - 1, _1)), Error::::LimitIn, @@ -2834,7 +1962,7 @@ fn pool_join_fails_if_max_assets_in_is_violated() { #[test] fn pool_join_with_exact_asset_amount_fails_if_min_pool_tokens_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected pool amount when joining with exactly BASE A. let expected_pool_amount = 2490679300; assert_noop!( @@ -2853,7 +1981,7 @@ fn pool_join_with_exact_asset_amount_fails_if_min_pool_tokens_is_violated() { #[test] fn pool_join_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected asset amount required to joining for BASE pool share. let expected_asset_amount = 40604010000; assert_noop!( @@ -2872,12 +2000,22 @@ fn pool_join_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { #[test] fn pool_exit_fails_if_min_assets_out_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1))); assert_noop!( Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1 + 1, _1)), Error::::LimitOut, ); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _1, + vec!(_1, _1, _1 + 1, _1) + ), + Error::::LimitOut, + ); }); } @@ -2885,7 +2023,7 @@ fn pool_exit_fails_if_min_assets_out_is_violated() { fn pool_exit_with_exact_asset_amount_fails_if_min_pool_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&(BASE / 10)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -2912,7 +2050,7 @@ fn pool_exit_with_exact_asset_amount_fails_if_min_pool_amount_is_violated() { fn pool_exit_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&(BASE / 10)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); let asset_before_join = Currencies::free_balance(ASSET_A, &ALICE); assert_ok!(Swaps::pool_join_with_exact_pool_amount( alice_signed(), @@ -2946,19 +2084,15 @@ fn create_pool_correctly_associates_weights_with_assets() { assert_ok!(Swaps::create_pool( BOB, vec![ASSET_D, ASSET_B, ASSET_C, ASSET_A], - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(_1, _2, _3, _4)), + DEFAULT_LIQUIDITY, + vec!(_1, _2, _3, _4), )); - let pool = Swaps::pool(0).unwrap(); - let pool_weights = pool.weights.unwrap(); - assert_eq!(pool_weights[&ASSET_A], _4); - assert_eq!(pool_weights[&ASSET_B], _2); - assert_eq!(pool_weights[&ASSET_C], _3); - assert_eq!(pool_weights[&ASSET_D], _1); + let pool = Swaps::pool_by_id(0).unwrap(); + assert_eq!(pool.weights[&ASSET_A], _4); + assert_eq!(pool.weights[&ASSET_B], _2); + assert_eq!(pool.weights[&ASSET_C], _3); + assert_eq!(pool.weights[&ASSET_D], _1); }); } @@ -2970,7 +2104,7 @@ fn single_asset_join_and_exit_are_inverse() { ::ExitFee::set(&0); let asset = ASSET_B; let amount_in = _1; - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Currencies::deposit(asset, &ALICE, amount_in)); assert_ok!(Swaps::pool_join_with_exact_asset_amount( RuntimeOrigin::signed(ALICE), @@ -3006,7 +2140,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { let amount_out_single_asset_ops = ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0); - create_initial_pool(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool(swap_fee, true); assert_ok!(Currencies::deposit(asset_in, &ALICE, amount_in)); assert_ok!(Swaps::pool_join_with_exact_asset_amount( RuntimeOrigin::signed(ALICE), @@ -3027,7 +2161,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { }); let amount_out_swap = ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool(swap_fee, true); assert_ok!(Currencies::deposit(asset_in, &ALICE, amount_in)); assert_ok!(Swaps::swap_exact_amount_in( RuntimeOrigin::signed(ALICE), @@ -3049,7 +2183,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { fn pool_join_with_uneven_balances() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _50)); assert_ok!(Swaps::pool_join( @@ -3071,7 +2205,7 @@ fn pool_exit_fails_if_balances_drop_too_low() { // We drop the balances below `Swaps::min_balance(...)`, but liquidity remains above // `Swaps::min_balance(...)`. ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::withdraw( @@ -3109,7 +2243,7 @@ fn pool_exit_fails_if_liquidity_drops_too_low() { // We drop the liquidity below `Swaps::min_balance(...)`, but balances remains above // `Swaps::min_balance(...)`. ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's 1000 left of each asset. @@ -3131,11 +2265,84 @@ fn pool_exit_fails_if_liquidity_drops_too_low() { }); } +#[test] +fn force_pool_exit_fails_if_balances_drop_too_low() { + ExtBuilder::default().build().execute_with(|| { + // We drop the balances below `Swaps::min_balance(...)`, but liquidity remains above + // `Swaps::min_balance(...)`. + ::ExitFee::set(&0u128); + create_initial_pool_with_funds_for_alice(1, true); + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + + assert_ok!(Currencies::withdraw( + ASSET_A, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_A) + )); + assert_ok!(Currencies::withdraw( + ASSET_B, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_B) + )); + assert_ok!(Currencies::withdraw( + ASSET_C, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_C) + )); + assert_ok!(Currencies::withdraw( + ASSET_D, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_D) + )); + + // We withdraw 99% of it, leaving 0.01 of each asset, which is below minimum balance. + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _10, + vec![0; 4] + ), + Error::::PoolDrain, + ); + }); +} + +#[test] +fn force_pool_exit_fails_if_liquidity_drops_too_low() { + ExtBuilder::default().build().execute_with(|| { + // We drop the liquidity below `Swaps::min_balance(...)`, but balances remains above + // `Swaps::min_balance(...)`. + ::ExitFee::set(&0u128); + create_initial_pool_with_funds_for_alice(1, true); + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + + // There's 1000 left of each asset. + assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_B, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_C, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_D, &pool_account_id, _900)); + + // We withdraw too much liquidity but leave enough of each asset. + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _100 - Swaps::min_balance(Swaps::pool_shares_id(DEFAULT_POOL_ID)) + 1, + vec![0; 4] + ), + Error::::PoolDrain, + ); + }); +} + #[test] fn swap_exact_amount_in_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3179,7 +2386,7 @@ fn swap_exact_amount_in_fails_if_balances_drop_too_low() { fn swap_exact_amount_out_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3223,7 +2430,7 @@ fn swap_exact_amount_out_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_pool_amount_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3265,7 +2472,7 @@ fn pool_exit_with_exact_pool_amount_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_pool_amount_fails_if_liquidity_drops_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _10000)); @@ -3296,7 +2503,7 @@ fn pool_exit_with_exact_pool_amount_fails_if_liquidity_drops_too_low() { fn pool_exit_with_exact_asset_amount_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3339,7 +2546,7 @@ fn pool_exit_with_exact_asset_amount_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_asset_amount_fails_if_liquidity_drops_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); // Reduce amount of liquidity so that doing the withdraw doesn't cause a `Min*Ratio` error! let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); @@ -3359,995 +2566,48 @@ fn pool_exit_with_exact_asset_amount_fails_if_liquidity_drops_too_low() { }); } -#[test] -fn trading_functions_cache_pool_ids() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); - - assert_ok!(Swaps::pool_join_with_exact_pool_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _2, - u128::MAX, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_join_with_exact_asset_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _2, - 0, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_exit_with_exact_asset_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - u128::MAX, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_exit_with_exact_pool_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - 0, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::swap_exact_amount_in( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - ASSET_B, - Some(0), - None, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::swap_exact_amount_out( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - Some(u128::MAX), - ASSET_B, - _1, - None, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - }); +fn alice_signed() -> RuntimeOrigin { + RuntimeOrigin::signed(ALICE) } -#[test] -fn on_idle_skips_arbitrage_if_price_does_not_exceed_threshold() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); +fn create_initial_pool(swap_fee: BalanceOf, deposit: bool) { + if deposit { + ASSETS.iter().cloned().for_each(|asset| { + assert_ok!(Currencies::deposit(asset, &BOB, _100)); }); - // Outcome weights sum to the weight of the base asset, and we create no imbalances, so - // total spot price is equal to 1. - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - ASSET_A, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec![_3, _1, _1, _1]), - )); - // Force the pool into the cache. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - System::assert_has_event(Event::ArbitrageSkipped(DEFAULT_POOL_ID).into()); - }); + } + let pool_id = Swaps::next_pool_id(); + assert_ok!(Swaps::create_pool( + BOB, + ASSETS.to_vec(), + swap_fee, + DEFAULT_LIQUIDITY, + vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT], + )); + assert_ok!(Swaps::open_pool(pool_id)); } -#[test] -fn on_idle_arbitrages_pools_with_mint_sell() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); +fn create_initial_pool_with_funds_for_alice(swap_fee: BalanceOf, deposit: bool) { + create_initial_pool(swap_fee, deposit); + let _ = Currencies::deposit(ASSET_A, &ALICE, _25); + let _ = Currencies::deposit(ASSET_B, &ALICE, _25); + let _ = Currencies::deposit(ASSET_C, &ALICE, _25); + let _ = Currencies::deposit(ASSET_D, &ALICE, _25); +} - // Withdraw a certain amount of outcome tokens to push the total spot price above 1 - // (ASSET_A is the base asset, all other assets are considered outcomes). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = _25; - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, amount_removed)); +fn assert_all_parameters( + alice_assets: [u128; 4], + alice_pool_assets: u128, + pool_assets: [u128; 4], + total_issuance: u128, +) { + let pai = Swaps::pool_account_id(&DEFAULT_POOL_ID); + let psi = Swaps::pool_shares_id(DEFAULT_POOL_ID); - // Force arbitrage hook. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - - let arbitrage_amount = 49_537_658_690; - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance - arbitrage_amount, - ); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance + arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance + arbitrage_amount); - assert_eq!( - Currencies::free_balance(ASSET_B, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), arbitrage_amount); - System::assert_has_event( - Event::ArbitrageMintSell(DEFAULT_POOL_ID, arbitrage_amount).into(), - ); - }); -} - -#[test] -fn on_idle_arbitrages_pools_with_buy_burn() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw a certain amount of base tokens to push the total spot price below 1 (ASSET_A - // is the base asset, all other assets are considered outcomes). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = _25; - assert_ok!(Currencies::withdraw(base_asset, &pool_account_id, amount_removed)); - - // Deposit funds into the prize pool to ensure that the transfers don't fail. - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - let arbitrage_amount = 125_007_629_394; // "Should" be 125_000_000_000. - assert_ok!(Currencies::deposit( - base_asset, - &market_account_id, - arbitrage_amount + SENTINEL_AMOUNT, - )); - - // Force arbitrage hook. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - assert_eq!(Currencies::free_balance(ASSET_B, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), SENTINEL_AMOUNT); - System::assert_has_event(Event::ArbitrageBuyBurn(DEFAULT_POOL_ID, arbitrage_amount).into()); - }); -} - -#[test] -fn apply_to_cached_pools_only_drains_requested_pools() { - ExtBuilder::default().build().execute_with(|| { - let pool_count = 5; - for pool_id in 0..pool_count { - // Force the pool into the cache. - PoolsCachedForArbitrage::::insert(pool_id, ()); - } - let number_of_pools_to_retain: u32 = 3; - Swaps::apply_to_cached_pools( - pool_count.saturated_into::() - number_of_pools_to_retain, - |_| Ok(Weight::zero()), - Weight::MAX, - ); - assert_eq!( - PoolsCachedForArbitrage::::iter().count(), - number_of_pools_to_retain as usize, - ); - }); -} - -#[test] -fn execute_arbitrage_correctly_observes_min_balance_buy_burn() { - ExtBuilder::default().build().execute_with(|| { - // Static requirement for this test is that base asset and outcome tokens have similar - // minimum balance. - assert!(Swaps::min_balance(ASSET_A) <= 3 * Swaps::min_balance(ASSET_B) / 2); - let min_balance = Swaps::min_balance(ASSET_A); - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw most base tokens to push the total spot price below 1 and most of one of the - // outcome tokens to trigger an arbitrage that would cause minimum balances to be violated. - // The total spot price should be slightly above 1/3. - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = balance - 3 * min_balance / 2; - assert_ok!(Currencies::withdraw(base_asset, &pool_account_id, amount_removed)); - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, amount_removed)); - - // Deposit funds into the prize pool to ensure that the transfers don't fail. - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - let arbitrage_amount = min_balance / 2; - assert_ok!(Currencies::deposit( - base_asset, - &market_account_id, - arbitrage_amount + SENTINEL_AMOUNT, - )); - - assert_ok!(Swaps::execute_arbitrage(DEFAULT_POOL_ID, ARBITRAGE_MAX_ITERATIONS)); - - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - assert_eq!(Currencies::free_balance(ASSET_B, &pool_account_id), min_balance); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), SENTINEL_AMOUNT); - System::assert_has_event(Event::ArbitrageBuyBurn(DEFAULT_POOL_ID, arbitrage_amount).into()); - }); -} - -#[test] -fn execute_arbitrage_observes_min_balances_mint_sell() { - ExtBuilder::default().build().execute_with(|| { - // Static assumptions for this test are that minimum balances of all assets are similar. - assert!(Swaps::min_balance(ASSET_A) >= Swaps::min_balance(ASSET_B)); - assert_eq!(Swaps::min_balance(ASSET_B), Swaps::min_balance(ASSET_C)); - assert_eq!(Swaps::min_balance(ASSET_B), Swaps::min_balance(ASSET_D)); - let min_balance = Swaps::min_balance(ASSET_A); - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw a certain amount of outcome tokens to push the total spot price above 1 - // (`ASSET_A` is the base asset, all other assets are considered outcomes). The exact choice - // of balance for `ASSET_A` ensures that after one iteration, we slightly overshoot the - // target (see below). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - assert_ok!(Currencies::withdraw( - ASSET_A, - &pool_account_id, - balance - 39 * min_balance / 30, // little less than 4 / 3 - )); - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, balance - min_balance)); - assert_ok!(Currencies::withdraw(ASSET_C, &pool_account_id, balance - min_balance)); - assert_ok!(Currencies::withdraw(ASSET_D, &pool_account_id, balance - min_balance)); - - // The arbitrage amount of mint-sell should always be so that min_balances are not - // violated (provided the pool starts in valid state), _except_ when the approximation - // overshoots the target. We simulate this by using exactly one iteration. - assert_ok!(Swaps::execute_arbitrage(DEFAULT_POOL_ID, 1)); - - // Using only one iteration means that the arbitrage amount will be slightly more than - // 9 / 30 * min_balance, but that would result in a violation of the minimum balance of the - // base asset, so we instead arbitrage the maximum allowed amount: - let arbitrage_amount = 9 * min_balance / 30; - assert_eq!(Currencies::free_balance(base_asset, &pool_account_id), min_balance); - assert_eq!( - Currencies::free_balance(ASSET_B, &pool_account_id), - min_balance + arbitrage_amount, - ); - assert_eq!( - Currencies::free_balance(ASSET_C, &pool_account_id), - min_balance + arbitrage_amount, - ); - assert_eq!( - Currencies::free_balance(ASSET_D, &pool_account_id), - min_balance + arbitrage_amount, - ); - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), arbitrage_amount); - System::assert_has_event( - Event::ArbitrageMintSell(DEFAULT_POOL_ID, arbitrage_amount).into(), - ); - }); -} - -#[test_case( - 0, - Perbill::from_parts( - u32::try_from( - 1 + ((::MaxSwapFee::get() * 1_000_000_000 - ) / BASE)).unwrap() - ); "creator_fee_only" - ) -] -#[test_case( - 1 + BASE / 100 * 5, - Perbill::from_parts( - u32::try_from( - (::MaxSwapFee::get() * 1_000_000_000 / 2 - ) / BASE).unwrap() - ); "sum_of_all_fees" - ) -] -fn create_pool_respects_total_fee_limits(swap_fee: u128, creator_fee: Perbill) { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - ASSETS.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - assert_err!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - DEFAULT_MARKET_ID, - ScoringRule::CPMM, - Some(swap_fee), - Some(DEFAULT_LIQUIDITY), - Some(vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT]), - ), - Error::::SwapFeeTooHigh - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_charged_correctly( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let ed = ::ExistentialDeposits::get(&BASE_ASSET); - assert_ok!(>::deposit( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - ed - )); - - let market_creator_balance_before = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - let asset_amount_in = _1; - let min_asset_amount_out = Some(_1 / 2); - let max_price = None; - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - true, - &creator_fee, - swap_fee, - asset_amount_in, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - min_asset_amount_out, - max_price, - )); - - let market_creator_balance_after = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - - assert_eq!(market_creator_balance_after - market_creator_balance_before, expected_fee); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_respects_min_amount_out( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let min_asset_amount_out = _1; - // Does not regard market creator fee - let asset_amount_in = calc_in_given_out( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - min_asset_amount_out, - swap_fee, - ) - .unwrap(); - - assert_err!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - Some(min_asset_amount_out), - None, - ), - Error::::LimitOut - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_respects_max_price( - asset_in: Asset, - asset_out: Asset, -) { - let mut max_spot_price = 0; - let asset_amount_in = _1; - - ExtBuilder::default().build().execute_with(|| { - let swap_fee = 0; - - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - None, - Some(u128::MAX), - ),); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_after = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_after = Currencies::free_balance(asset_out, &pool_account); - - // Does not regard market creator fee - max_spot_price = calc_spot_price( - pool_balance_in_after, - DEFAULT_WEIGHT, - pool_balance_out_after, - DEFAULT_WEIGHT, - swap_fee, - ) - .unwrap(); - }); - - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - assert_err!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - None, - Some(max_spot_price), - ), - Error::::BadLimitPrice - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_with_creator_fee_respects_existential_deposit( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - // TODO(#1097) - using 10x ed since otherwise a MathApproximation bug is emitted. - let asset_amount = ::ExistentialDeposits::get(&BASE_ASSET) - .saturating_sub(1) - * 10; - - if asset_amount == 0 { - return; - } - - frame_system::Pallet::::set_block_number(1); - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Currencies::withdraw( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR) - )); - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - true, - &creator_fee, - swap_fee, - asset_amount, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount, - asset_out, - Some(asset_amount / 2), - None, - )); - System::assert_has_event( - Event::MarketCreatorFeePaymentFailed( - DEFAULT_MARKET_ID, - ALICE, - DEFAULT_MARKET_CREATOR, - expected_fee, - BASE_ASSET, - orml_tokens::Error::::ExistentialDeposit.into(), - ) - .into(), - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_charged_correctly( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let ed = ::ExistentialDeposits::get(&BASE_ASSET); - assert_ok!(>::deposit( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - ed - )); - - let market_creator_balance_before = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - let asset_amount_out = _1; - let max_asset_amount_in = Some(_2); - let max_price = None; - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - false, - &creator_fee, - swap_fee, - asset_amount_out, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - max_asset_amount_in, - asset_out, - asset_amount_out, - max_price, - )); - - let market_creator_balance_after = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - - assert_eq!(market_creator_balance_after - market_creator_balance_before, expected_fee); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_respects_max_amount_in( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let max_asset_amount_in = _1; - // Does not regard market creator fee - let asset_amount_out = calc_out_given_in( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - max_asset_amount_in, - swap_fee, - ) - .unwrap(); - - assert_err!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(max_asset_amount_in), - asset_out, - asset_amount_out, - None, - ), - Error::::LimitIn - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_respects_max_price( - asset_in: Asset, - asset_out: Asset, -) { - let mut max_spot_price = 0; - let asset_amount_out = _1; - - ExtBuilder::default().build().execute_with(|| { - let swap_fee = 0; - - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - None, - asset_out, - asset_amount_out, - Some(u128::MAX), - ),); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_after = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_after = Currencies::free_balance(asset_out, &pool_account); - - // Does not regard market creator fee - max_spot_price = calc_spot_price( - pool_balance_in_after, - DEFAULT_WEIGHT, - pool_balance_out_after, - DEFAULT_WEIGHT, - swap_fee, - ) - .unwrap(); - }); - - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - assert_err!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - None, - asset_out, - asset_amount_out, - Some(max_spot_price), - ), - Error::::BadLimitPrice - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_swaps_correct_amount_out( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let alice_balance_out_before = Currencies::free_balance(asset_out, &ALICE); - let asset_amount_out = _1; - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(u128::MAX), - asset_out, - asset_amount_out, - None, - )); - - let alice_balance_out_after = Currencies::free_balance(asset_out, &ALICE); - assert_eq!(alice_balance_out_after - alice_balance_out_before, asset_amount_out); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_with_creator_fee_respects_existential_deposit( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - let asset_amount = ::ExistentialDeposits::get(&BASE_ASSET) - .saturating_sub(1); - - if asset_amount == 0 { - return; - } - - frame_system::Pallet::::set_block_number(1); - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Currencies::withdraw( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR) - )); - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - false, - &creator_fee, - swap_fee, - asset_amount, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(2 * asset_amount), - asset_out, - asset_amount, - None, - )); - System::assert_has_event( - Event::MarketCreatorFeePaymentFailed( - DEFAULT_MARKET_ID, - ALICE, - DEFAULT_MARKET_CREATOR, - expected_fee, - BASE_ASSET, - orml_tokens::Error::::ExistentialDeposit.into(), - ) - .into(), - ); - }); -} - -fn alice_signed() -> RuntimeOrigin { - RuntimeOrigin::signed(ALICE) -} - -// Must be called before the swap happens. -fn expected_creator_fee( - pool_id: &PoolId, - swap_in: bool, - creator_fee: &Perbill, - swap_fee: BalanceOf, - asset_amount: BalanceOf, - asset_in: Asset, - asset_out: Asset, -) -> BalanceOf { - let pool_account = Swaps::pool_account_id(pool_id); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let pool_balance_base_out_before = Currencies::free_balance(BASE_ASSET, &pool_account); - - let expected_amount_without_fee = if swap_in { - calc_out_given_in( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - asset_amount, - swap_fee, - ) - .unwrap() - } else { - calc_in_given_out( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - asset_amount, - swap_fee, - ) - .unwrap() - }; - - if asset_in == BASE_ASSET { - if swap_in { - creator_fee.mul_floor(asset_amount) - } else { - creator_fee.mul_floor(expected_amount_without_fee) - } - } else if asset_out == BASE_ASSET { - if swap_in { - creator_fee.mul_floor(expected_amount_without_fee) - } else { - creator_fee.mul_floor(asset_amount) - } - } else if swap_in { - let fee_before_swap = creator_fee.mul_floor(expected_amount_without_fee); - calc_out_given_in( - DEFAULT_LIQUIDITY - expected_amount_without_fee, - DEFAULT_WEIGHT, - pool_balance_base_out_before, - DEFAULT_WEIGHT, - fee_before_swap, - swap_fee, - ) - .unwrap() - } else { - let fee_before_swap = creator_fee.mul_floor(asset_amount); - calc_out_given_in( - DEFAULT_LIQUIDITY - asset_amount - fee_before_swap, - DEFAULT_WEIGHT, - pool_balance_base_out_before, - DEFAULT_WEIGHT, - fee_before_swap, - swap_fee, - ) - .unwrap() - } -} - -fn create_initial_pool( - scoring_rule: ScoringRule, - swap_fee: Option>, - deposit: bool, -) { - if deposit { - ASSETS.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _100)); - }); - } - - let pool_id = Swaps::next_pool_id(); - assert_ok!(Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - DEFAULT_MARKET_ID, - scoring_rule, - swap_fee, - if scoring_rule == ScoringRule::CPMM { Some(DEFAULT_LIQUIDITY) } else { None }, - if scoring_rule == ScoringRule::CPMM { - Some(vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT]) - } else { - None - }, - )); - if scoring_rule == ScoringRule::CPMM { - assert_ok!(Swaps::open_pool(pool_id)); - } -} - -fn create_initial_pool_with_funds_for_alice( - scoring_rule: ScoringRule, - swap_fee: Option>, - deposit: bool, -) { - create_initial_pool(scoring_rule, swap_fee, deposit); - let _ = Currencies::deposit(ASSET_A, &ALICE, _25); - let _ = Currencies::deposit(ASSET_B, &ALICE, _25); - let _ = Currencies::deposit(ASSET_C, &ALICE, _25); - let _ = Currencies::deposit(ASSET_D, &ALICE, _25); -} - -fn assert_all_parameters( - alice_assets: [u128; 4], - alice_pool_assets: u128, - pool_assets: [u128; 4], - total_issuance: u128, -) { - let pai = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let psi = Swaps::pool_shares_id(DEFAULT_POOL_ID); - - assert_eq!(Currencies::free_balance(ASSET_A, &ALICE), alice_assets[0]); - assert_eq!(Currencies::free_balance(ASSET_B, &ALICE), alice_assets[1]); - assert_eq!(Currencies::free_balance(ASSET_C, &ALICE), alice_assets[2]); - assert_eq!(Currencies::free_balance(ASSET_D, &ALICE), alice_assets[3]); + assert_eq!(Currencies::free_balance(ASSET_A, &ALICE), alice_assets[0]); + assert_eq!(Currencies::free_balance(ASSET_B, &ALICE), alice_assets[1]); + assert_eq!(Currencies::free_balance(ASSET_C, &ALICE), alice_assets[2]); + assert_eq!(Currencies::free_balance(ASSET_D, &ALICE), alice_assets[3]); assert_eq!(Currencies::free_balance(psi, &ALICE), alice_pool_assets); @@ -4357,22 +2617,3 @@ fn assert_all_parameters( assert_eq!(Currencies::free_balance(ASSET_D, &pai), pool_assets[3]); assert_eq!(Currencies::total_issuance(psi), total_issuance); } - -fn set_creator_fee(market_id: MarketId, fee: Perbill) -> DispatchResult { - MarketCommons::mutate_market(&market_id, |market: &mut MarketOf| { - market.creator_fee = fee; - Ok(()) - }) -} - -// Subsidize and start a Rikiddo pool. Extra is the amount of additional base asset added to who. -fn subsidize_and_start_rikiddo_pool( - pool_id: PoolId, - who: &::AccountId, - extra: crate::BalanceOf, -) { - let min_subsidy = ::MinSubsidy::get(); - assert_ok!(Currencies::deposit(ASSET_D, who, min_subsidy + extra)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(*who), pool_id, min_subsidy)); - assert!(Swaps::end_subsidy_phase(pool_id).unwrap().result); -} diff --git a/zrml/swaps/src/types/mod.rs b/zrml/swaps/src/types/mod.rs new file mode 100644 index 000000000..2bf0149cb --- /dev/null +++ b/zrml/swaps/src/types/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023-2024 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 . + +mod pool; + +pub(crate) use pool::*; diff --git a/primitives/src/pool_status.rs b/zrml/swaps/src/types/pool.rs similarity index 54% rename from primitives/src/pool_status.rs rename to zrml/swaps/src/types/pool.rs index 1d9873968..79fe8c234 100644 --- a/primitives/src/pool_status.rs +++ b/zrml/swaps/src/types/pool.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,6 +16,40 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use frame_support::storage::{bounded_btree_map::BoundedBTreeMap, bounded_vec::BoundedVec}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Get, RuntimeDebug}; +use zeitgeist_primitives::constants::MAX_ASSETS; + +pub struct MaxAssets; + +impl Get for MaxAssets { + fn get() -> u32 { + MAX_ASSETS as u32 + } +} + +// TODO(#1213): Replace `u128` with `PoolWeight` type which implements `Into` and +// `From`! Or just replace it with `Balance`. +#[derive(TypeInfo, Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] +pub struct Pool { + pub assets: BoundedVec, + pub status: PoolStatus, + pub swap_fee: Balance, + pub total_weight: u128, + pub weights: BoundedBTreeMap, +} + +impl Pool +where + Asset: Ord, +{ + pub fn bound(&self, asset: &Asset) -> bool { + self.weights.get(asset).is_some() + } +} + /// The status of a pool. Closely related to the lifecycle of a market. #[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] @@ -33,13 +68,7 @@ )] pub enum PoolStatus { /// Shares can be normally negotiated. - Active, - /// No trading is allowed. The pool is waiting to be subsidized. - CollectingSubsidy, + Open, /// No trading/adding liquidity is allowed. Closed, - /// The pool has been cleaned up, usually after the corresponding market has been resolved. - Clean, - /// The pool has just been created. - Initialized, } diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index 875d9a33c..cf0979ebd 100644 --- a/zrml/swaps/src/utils.rs +++ b/zrml/swaps/src/utils.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -24,38 +24,32 @@ use crate::{ events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - BalanceOf, Config, Error, MarketIdOf, Pallet, + AssetOf, BalanceOf, Config, Error, Pallet, PoolOf, }; use alloc::vec::Vec; use frame_support::{dispatch::DispatchResult, ensure}; use orml_traits::MultiCurrency; -use sp_runtime::{ - traits::{Saturating, Zero}, - DispatchError, -}; +use sp_runtime::{traits::Zero, DispatchError}; use zeitgeist_primitives::{ math::{ checked_ops_res::CheckedSubRes, fixed::{FixedDiv, FixedMul}, }, - types::{Asset, Pool, PoolId, ScoringRule}, + types::PoolId, }; -use zrml_rikiddo::traits::RikiddoMVPallet; // Common code for `pool_exit_with_exact_pool_amount` and `pool_exit_with_exact_asset_amount` methods. -pub(crate) fn pool_exit_with_exact_amount( - mut p: PoolExitWithExactAmountParams<'_, F1, F2, F3, F4, F5, T>, +pub(crate) fn pool_exit_with_exact_amount( + mut p: PoolExitWithExactAmountParams<'_, F1, F2, F3, F4, T>, ) -> DispatchResult where F1: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, - F2: FnMut(), - F3: FnMut(BalanceOf) -> DispatchResult, - F4: FnMut(PoolAssetEvent>, BalanceOf>), - F5: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, + F2: FnMut(BalanceOf) -> DispatchResult, + F3: FnMut(PoolAssetEvent, BalanceOf>), + F4: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, T: Config, { - Pallet::::check_if_pool_is_active(p.pool)?; - ensure!(p.pool.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); + Pallet::::ensure_pool_is_active(p.pool)?; ensure!(p.pool.bound(&p.asset), Error::::AssetNotBound); let pool_account = Pallet::::pool_account_id(&p.pool_id); @@ -71,7 +65,6 @@ where Pallet::::burn_pool_shares(p.pool_id, &p.who, pool_amount)?; T::AssetManager::transfer(p.asset, &pool_account, &p.who, asset_amount)?; - (p.cache_for_arbitrage)(); (p.event)(PoolAssetEvent { asset: p.asset, bound: p.bound, @@ -84,18 +77,16 @@ where } // Common code for `pool_join_with_exact_asset_amount` and `pool_join_with_exact_pool_amount` methods. -pub(crate) fn pool_join_with_exact_amount( - mut p: PoolJoinWithExactAmountParams<'_, F1, F2, F3, F4, T>, +pub(crate) fn pool_join_with_exact_amount( + mut p: PoolJoinWithExactAmountParams<'_, F1, F2, F3, T>, ) -> DispatchResult where F1: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, - F2: FnMut(), - F3: FnMut(PoolAssetEvent>, BalanceOf>), - F4: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, + F2: FnMut(PoolAssetEvent, BalanceOf>), + F3: FnMut(BalanceOf, BalanceOf) -> Result, DispatchError>, T: Config, { - ensure!(p.pool.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); - Pallet::::check_if_pool_is_active(p.pool)?; + Pallet::::ensure_pool_is_active(p.pool)?; let pool_shares_id = Pallet::::pool_shares_id(p.pool_id); let pool_account_id = Pallet::::pool_account_id(&p.pool_id); let total_issuance = T::AssetManager::total_issuance(pool_shares_id); @@ -109,7 +100,6 @@ where Pallet::::mint_pool_shares(p.pool_id, &p.who, pool_amount)?; T::AssetManager::transfer(p.asset, &p.who, &pool_account_id, asset_amount)?; - (p.cache_for_arbitrage)(); (p.event)(PoolAssetEvent { asset: p.asset, bound: p.bound, @@ -124,13 +114,12 @@ where // Common code for `pool_join` and `pool_exit` methods. pub(crate) fn pool(mut p: PoolParams<'_, F1, F2, F3, F4, T>) -> DispatchResult where - F1: FnMut(PoolAssetsEvent>, BalanceOf>), - F2: FnMut(BalanceOf, BalanceOf, Asset>) -> DispatchResult, + F1: FnMut(PoolAssetsEvent, BalanceOf>), + F2: FnMut(BalanceOf, BalanceOf, AssetOf) -> DispatchResult, F3: FnMut() -> DispatchResult, F4: FnMut(BalanceOf) -> Result, DispatchError>, T: Config, { - ensure!(p.pool.scoring_rule == ScoringRule::CPMM, Error::::InvalidScoringRule); let pool_shares_id = Pallet::::pool_shares_id(p.pool_id); let total_issuance = T::AssetManager::total_issuance(pool_shares_id); @@ -153,7 +142,7 @@ where (p.transfer_pool)()?; (p.event)(PoolAssetsEvent { - assets: p.pool.assets.clone(), + assets: p.pool.assets.clone().into_inner(), bounds: p.asset_bounds, cpep: CommonPoolEventParams { pool_id: p.pool_id, who: p.who }, transferred, @@ -164,23 +153,19 @@ where } // Common code for `swap_exact_amount_in` and `swap_exact_amount_out` methods. -pub(crate) fn swap_exact_amount( - mut p: SwapExactAmountParams<'_, F1, F2, F3, T>, +pub(crate) fn swap_exact_amount( + mut p: SwapExactAmountParams<'_, F1, F2, T>, ) -> DispatchResult where F1: FnMut() -> Result<[BalanceOf; 2], DispatchError>, - F2: FnMut(), - F3: FnMut(SwapEvent>, BalanceOf>), + F2: FnMut(SwapEvent, BalanceOf>), T: crate::Config, { - Pallet::::check_if_pool_is_active(p.pool)?; + Pallet::::ensure_pool_is_active(p.pool)?; ensure!(p.pool.assets.binary_search(&p.asset_in).is_ok(), Error::::AssetNotInPool); ensure!(p.pool.assets.binary_search(&p.asset_out).is_ok(), Error::::AssetNotInPool); - - if p.pool.scoring_rule == ScoringRule::CPMM { - ensure!(p.pool.bound(&p.asset_in), Error::::AssetNotBound); - ensure!(p.pool.bound(&p.asset_out), Error::::AssetNotBound); - } + ensure!(p.pool.bound(&p.asset_in), Error::::AssetNotBound); + ensure!(p.pool.bound(&p.asset_out), Error::::AssetNotBound); let spot_price_before = Pallet::::get_spot_price(&p.pool_id, &p.asset_in, &p.asset_out, true)?; @@ -194,74 +179,22 @@ where let [asset_amount_in, asset_amount_out] = (p.asset_amounts)()?; - match p.pool.scoring_rule { - ScoringRule::CPMM => { - T::AssetManager::transfer(p.asset_in, &p.who, p.pool_account_id, asset_amount_in)?; - T::AssetManager::transfer(p.asset_out, p.pool_account_id, &p.who, asset_amount_out)?; - (p.cache_for_arbitrage)(); - } - ScoringRule::RikiddoSigmoidFeeMarketEma => { - let base_asset = p.pool.base_asset; - - if p.asset_in == base_asset { - T::AssetManager::transfer(p.asset_in, &p.who, p.pool_account_id, asset_amount_in)?; - T::AssetManager::deposit(p.asset_out, &p.who, asset_amount_out)?; - } else if p.asset_out == base_asset { - // We can use the lightweight withdraw here, since event assets are not reserved. - T::AssetManager::withdraw(p.asset_in, &p.who, asset_amount_in)?; - T::AssetManager::transfer( - p.asset_out, - p.pool_account_id, - &p.who, - asset_amount_out, - )?; - } else { - // Just for safety, should already be checked in p.asset_amounts. - return Err(Error::::UnsupportedTrade.into()); - } - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - } + T::AssetManager::transfer(p.asset_in, &p.who, p.pool_account_id, asset_amount_in)?; + T::AssetManager::transfer(p.asset_out, p.pool_account_id, &p.who, asset_amount_out)?; let spot_price_after = Pallet::::get_spot_price(&p.pool_id, &p.asset_in, &p.asset_out, true)?; - // Allow little tolerance - match p.pool.scoring_rule { - ScoringRule::CPMM => { - ensure!(spot_price_after >= spot_price_before, Error::::MathApproximation) - } - ScoringRule::RikiddoSigmoidFeeMarketEma => ensure!( - spot_price_before.saturating_sub(spot_price_after) < 20u8.into(), - Error::::MathApproximation - ), - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - } + ensure!(spot_price_after >= spot_price_before, Error::::MathApproximation); if let Some(max_price) = p.max_price { ensure!(spot_price_after <= max_price, Error::::BadLimitPrice); } - match p.pool.scoring_rule { - ScoringRule::CPMM => ensure!( - spot_price_before_without_fees <= asset_amount_in.bdiv(asset_amount_out)?, - Error::::MathApproximation - ), - ScoringRule::RikiddoSigmoidFeeMarketEma => { - // Currently the only allowed trades are base_currency <-> event asset. We count the - // volume in base_currency. - let base_asset = p.pool.base_asset; - let volume = if p.asset_in == base_asset { asset_amount_in } else { asset_amount_out }; - T::RikiddoSigmoidFeeMarketEma::update_volume(p.pool_id, volume)?; - } - ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { - return Err(Error::::InvalidScoringRule.into()); - } - } + ensure!( + spot_price_before_without_fees <= asset_amount_in.bdiv(asset_amount_out)?, + Error::::MathApproximation + ); (p.event)(SwapEvent { asset_amount_in, @@ -276,36 +209,34 @@ where Ok(()) } -pub(crate) struct PoolExitWithExactAmountParams<'a, F1, F2, F3, F4, F5, T> +pub(crate) struct PoolExitWithExactAmountParams<'a, F1, F2, F3, F4, T> where T: Config, { pub(crate) asset_amount: F1, - pub(crate) asset: Asset>, + pub(crate) asset: AssetOf, pub(crate) bound: BalanceOf, - pub(crate) cache_for_arbitrage: F2, - pub(crate) ensure_balance: F3, - pub(crate) event: F4, + pub(crate) ensure_balance: F2, + pub(crate) event: F3, pub(crate) who: T::AccountId, - pub(crate) pool_amount: F5, + pub(crate) pool_amount: F4, pub(crate) pool_id: PoolId, - pub(crate) pool: &'a Pool, MarketIdOf>, + pub(crate) pool: &'a PoolOf, } -pub(crate) struct PoolJoinWithExactAmountParams<'a, F1, F2, F3, F4, T> +pub(crate) struct PoolJoinWithExactAmountParams<'a, F1, F2, F3, T> where T: Config, { - pub(crate) asset: Asset>, + pub(crate) asset: AssetOf, pub(crate) asset_amount: F1, pub(crate) bound: BalanceOf, - pub(crate) cache_for_arbitrage: F2, - pub(crate) event: F3, + pub(crate) event: F2, pub(crate) who: T::AccountId, pub(crate) pool_account_id: &'a T::AccountId, - pub(crate) pool_amount: F4, + pub(crate) pool_amount: F3, pub(crate) pool_id: PoolId, - pub(crate) pool: &'a Pool, MarketIdOf>, + pub(crate) pool: &'a PoolOf, } pub(crate) struct PoolParams<'a, F1, F2, F3, F4, T> @@ -317,26 +248,25 @@ where pub(crate) pool_account_id: &'a T::AccountId, pub(crate) pool_amount: BalanceOf, pub(crate) pool_id: PoolId, - pub(crate) pool: &'a Pool, MarketIdOf>, + pub(crate) pool: &'a PoolOf, pub(crate) transfer_asset: F2, pub(crate) transfer_pool: F3, pub(crate) fee: F4, pub(crate) who: T::AccountId, } -pub(crate) struct SwapExactAmountParams<'a, F1, F2, F3, T> +pub(crate) struct SwapExactAmountParams<'a, F1, F2, T> where T: Config, { pub(crate) asset_amounts: F1, pub(crate) asset_bound: Option>, - pub(crate) asset_in: Asset>, - pub(crate) asset_out: Asset>, - pub(crate) cache_for_arbitrage: F2, - pub(crate) event: F3, + pub(crate) asset_in: AssetOf, + pub(crate) asset_out: AssetOf, + pub(crate) event: F2, pub(crate) max_price: Option>, pub(crate) pool_account_id: &'a T::AccountId, pub(crate) pool_id: PoolId, - pub(crate) pool: &'a Pool, MarketIdOf>, + pub(crate) pool: &'a PoolOf, pub(crate) who: T::AccountId, } diff --git a/zrml/swaps/src/weights.rs b/zrml/swaps/src/weights.rs index 84850809d..32deb55cc 100644 --- a/zrml/swaps/src/weights.rs +++ b/zrml/swaps/src/weights.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-05`, STEPS: `12`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `mkl-mac`, CPU: `` +//! EXECUTION: `Some(Native)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=12 +// --repeat=2 // --pallet=zrml_swaps // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -49,29 +49,14 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_swaps (automatically generated) pub trait WeightInfoZeitgeist { - fn admin_clean_up_pool_cpmm_categorical(a: u32) -> Weight; - fn admin_clean_up_pool_cpmm_scalar() -> Weight; - fn apply_to_cached_pools_execute_arbitrage(a: u32) -> Weight; - fn apply_to_cached_pools_noop(a: u32) -> Weight; - fn destroy_pool_in_subsidy_phase(a: u32) -> Weight; - fn distribute_pool_share_rewards(a: u32, b: u32) -> Weight; - fn end_subsidy_phase(a: u32, b: u32) -> Weight; - fn execute_arbitrage_buy_burn(a: u32) -> Weight; - fn execute_arbitrage_mint_sell(a: u32) -> Weight; - fn execute_arbitrage_skipped(a: u32) -> Weight; fn pool_exit(a: u32) -> Weight; - fn pool_exit_subsidy() -> Weight; fn pool_exit_with_exact_asset_amount() -> Weight; fn pool_exit_with_exact_pool_amount() -> Weight; fn pool_join(a: u32) -> Weight; - fn pool_join_subsidy() -> Weight; fn pool_join_with_exact_asset_amount() -> Weight; fn pool_join_with_exact_pool_amount() -> Weight; - fn clean_up_pool_categorical_without_reward_distribution(a: u32) -> Weight; fn swap_exact_amount_in_cpmm() -> Weight; - fn swap_exact_amount_in_rikiddo(a: u32) -> Weight; fn swap_exact_amount_out_cpmm() -> Weight; - fn swap_exact_amount_out_rikiddo(a: u32) -> Weight; fn open_pool(a: u32) -> Weight; fn close_pool(a: u32) -> Weight; fn destroy_pool(a: u32) -> Weight; @@ -80,225 +65,8 @@ pub trait WeightInfoZeitgeist { /// Weight functions for zrml_swaps (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:0) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// The range of component `a` is `[3, 65]`. - fn admin_clean_up_pool_cpmm_categorical(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `697 + a * (54 ±0)` - // Estimated: `11802` - // Minimum execution time: 42_711 nanoseconds. - Weight::from_parts(48_961_657, 11802) - // Standard Error: 3_690 - .saturating_add(Weight::from_parts(405_599, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:0) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - fn admin_clean_up_pool_cpmm_scalar() -> Weight { - // Proof Size summary in bytes: - // Measured: `889` - // Estimated: `11802` - // Minimum execution time: 38_860 nanoseconds. - Weight::from_parts(44_610_000, 11802) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: Swaps PoolsCachedForArbitrage (r:64 w:63) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) - /// Storage: Swaps Pools (r:63 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4158 w:4158) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:63 w:0) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:64) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// The range of component `a` is `[0, 63]`. - fn apply_to_cached_pools_execute_arbitrage(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `3289 + a * (11514 ±0)` - // Estimated: `163651 + a * (182700 ±0)` - // Minimum execution time: 980 nanoseconds. - Weight::from_parts(1_120_000, 163651) - // Standard Error: 702_682 - .saturating_add(Weight::from_parts(2_532_422_802, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(43)) - .saturating_add(T::DbWeight::get().reads((70_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(42)) - .saturating_add(T::DbWeight::get().writes((67_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 182700).saturating_mul(a.into())) - } - /// Storage: Swaps PoolsCachedForArbitrage (r:64 w:63) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) - /// The range of component `a` is `[0, 63]`. - fn apply_to_cached_pools_noop(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `27 + a * (27 ±0)` - // Estimated: `2499 + a * (2499 ±0)` - // Minimum execution time: 930 nanoseconds. - Weight::from_parts(1_080_000, 2499) - // Standard Error: 9_607 - .saturating_add(Weight::from_parts(9_241_406, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2499).saturating_mul(a.into())) - } - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Swaps SubsidyProviders (r:11 w:10) - /// Proof: Swaps SubsidyProviders (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:10 w:10) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:1) - /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) - /// The range of component `a` is `[0, 10]`. - fn destroy_pool_in_subsidy_phase(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `839 + a * (297 ±0)` - // Estimated: `11476 + a * (5153 ±0)` - // Minimum execution time: 30_680 nanoseconds. - Weight::from_parts(43_174_694, 11476) - // Standard Error: 60_923 - .saturating_add(Weight::from_parts(21_498_479, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 5153).saturating_mul(a.into())) - } - /// Storage: Tokens TotalIssuance (r:2 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:76 w:21) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:11 w:10) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// The range of component `a` is `[10, 20]`. - /// The range of component `b` is `[0, 10]`. - fn distribute_pool_share_rewards(a: u32, b: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `696 + a * (414 ±0) + b * (161 ±0)` - // Estimated: `19084 + a * (7887 ±5) + b * (5500 ±5)` - // Minimum execution time: 494_361 nanoseconds. - Weight::from_parts(128_925_631, 19084) - // Standard Error: 194_399 - .saturating_add(Weight::from_parts(25_535_158, 0).saturating_mul(a.into())) - // Standard Error: 194_399 - .saturating_add(Weight::from_parts(41_414_460, 0).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(b.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b.into()))) - .saturating_add(Weight::from_parts(0, 7887).saturating_mul(a.into())) - .saturating_add(Weight::from_parts(0, 5500).saturating_mul(b.into())) - } - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Swaps SubsidyProviders (r:11 w:10) - /// Proof: Swaps SubsidyProviders (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:85 w:85) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:11 w:11) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:65 w:65) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:0) - /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) - /// The range of component `a` is `[2, 65]`. - /// The range of component `b` is `[0, 10]`. - fn end_subsidy_phase(a: u32, b: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + a * (169 ±0) + b * (1159 ±0)` - // Estimated: `14083 + b * (10358 ±0) + a * (5116 ±0)` - // Minimum execution time: 14_140 nanoseconds. - Weight::from_parts(16_380_000, 14083) - // Standard Error: 88_786 - .saturating_add(Weight::from_parts(20_000_763, 0).saturating_mul(a.into())) - // Standard Error: 589_656 - .saturating_add(Weight::from_parts(89_504_168, 0).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(b.into()))) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes((6_u64).saturating_mul(b.into()))) - .saturating_add(Weight::from_parts(0, 10358).saturating_mul(b.into())) - .saturating_add(Weight::from_parts(0, 5116).saturating_mul(a.into())) - } - /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:66 w:66) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:0) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:64) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// The range of component `a` is `[2, 65]`. - fn execute_arbitrage_buy_burn(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `799 + a * (215 ±0)` - // Estimated: `13849 + a * (5005 ±0)` - // Minimum execution time: 104_740 nanoseconds. - Weight::from_parts(48_573_155, 13849) - // Standard Error: 57_601 - .saturating_add(Weight::from_parts(41_534_603, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 5005).saturating_mul(a.into())) - } - /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:66 w:66) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:1) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:64) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// The range of component `a` is `[2, 65]`. - fn execute_arbitrage_mint_sell(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `597 + a * (215 ±0)` - // Estimated: `16456 + a * (5005 ±0)` - // Minimum execution time: 110_061 nanoseconds. - Weight::from_parts(79_326_730, 16456) - // Standard Error: 53_179 - .saturating_add(Weight::from_parts(37_383_766, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 5005).saturating_mul(a.into())) - } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:65 w:0) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// The range of component `a` is `[2, 65]`. - fn execute_arbitrage_skipped(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `440 + a * (167 ±0)` - // Estimated: `6126 + a * (2598 ±0)` - // Minimum execution time: 30_291 nanoseconds. - Weight::from_parts(27_039_143, 6126) - // Standard Error: 12_934 - .saturating_add(Weight::from_parts(5_092_297, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2598).saturating_mul(a.into())) - } - /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:131 w:131) @@ -308,73 +76,54 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_exit(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1049 + a * (286 ±0)` - // Estimated: `13849 + a * (5196 ±0)` - // Minimum execution time: 112_690 nanoseconds. - Weight::from_parts(64_232_862, 13849) - // Standard Error: 43_303 - .saturating_add(Weight::from_parts(27_803_335, 0).saturating_mul(a.into())) + // Measured: `843 + a * (176 ±0)` + // Estimated: `13777 + a * (5196 ±0)` + // Minimum execution time: 93_000 nanoseconds. + Weight::from_parts(47_981_727, 13777) + // Standard Error: 611_716 + .saturating_add(Weight::from_parts(20_686_950, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) .saturating_add(Weight::from_parts(0, 5196).saturating_mul(a.into())) } - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Swaps SubsidyProviders (r:1 w:1) - /// Proof: Swaps SubsidyProviders (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - fn pool_exit_subsidy() -> Weight { - // Proof Size summary in bytes: - // Measured: `2493` - // Estimated: `11279` - // Minimum execution time: 53_010 nanoseconds. - Weight::from_parts(62_221_000, 11279) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:3 w:3) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn pool_exit_with_exact_asset_amount() -> Weight { // Proof Size summary in bytes: - // Measured: `5426` - // Estimated: `19045` - // Minimum execution time: 115_681 nanoseconds. - Weight::from_parts(135_621_000, 19045) + // Measured: `5546` + // Estimated: `18973` + // Minimum execution time: 97_000 nanoseconds. + Weight::from_parts(112_000_000, 18973) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:3 w:3) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn pool_exit_with_exact_pool_amount() -> Weight { // Proof Size summary in bytes: - // Measured: `5426` - // Estimated: `19045` - // Minimum execution time: 115_811 nanoseconds. - Weight::from_parts(135_051_000, 19045) + // Measured: `5546` + // Estimated: `18973` + // Minimum execution time: 92_000 nanoseconds. + Weight::from_parts(172_000_000, 18973) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:131 w:131) @@ -382,203 +131,108 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_join(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `910 + a * (286 ±0)` - // Estimated: `11242 + a * (5196 ±0)` - // Minimum execution time: 101_520 nanoseconds. - Weight::from_parts(58_997_986, 11242) - // Standard Error: 42_155 - .saturating_add(Weight::from_parts(27_298_629, 0).saturating_mul(a.into())) + // Measured: `723 + a * (289 ±0)` + // Estimated: `11170 + a * (5196 ±0)` + // Minimum execution time: 78_000 nanoseconds. + Weight::from_parts(58_433_548, 11170) + // Standard Error: 1_367_662 + .saturating_add(Weight::from_parts(17_750_119, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) .saturating_add(Weight::from_parts(0, 5196).saturating_mul(a.into())) } - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Swaps SubsidyProviders (r:1 w:1) - /// Proof: Swaps SubsidyProviders (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) - fn pool_join_subsidy() -> Weight { - // Proof Size summary in bytes: - // Measured: `2391` - // Estimated: `11279` - // Minimum execution time: 53_740 nanoseconds. - Weight::from_parts(59_311_000, 11279) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:3 w:3) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn pool_join_with_exact_asset_amount() -> Weight { // Proof Size summary in bytes: - // Measured: `5981` - // Estimated: `16438` - // Minimum execution time: 97_960 nanoseconds. - Weight::from_parts(114_620_000, 16438) + // Measured: `6229` + // Estimated: `16366` + // Minimum execution time: 90_000 nanoseconds. + Weight::from_parts(92_000_000, 16366) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:3 w:3) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn pool_join_with_exact_pool_amount() -> Weight { // Proof Size summary in bytes: - // Measured: `5981` - // Estimated: `16438` - // Minimum execution time: 98_781 nanoseconds. - Weight::from_parts(108_660_000, 16438) + // Measured: `6229` + // Estimated: `16366` + // Minimum execution time: 89_000 nanoseconds. + Weight::from_parts(122_000_000, 16366) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// The range of component `a` is `[3, 65]`. - fn clean_up_pool_categorical_without_reward_distribution(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `209 + a * (54 ±0)` - // Estimated: `6126` - // Minimum execution time: 10_910 nanoseconds. - Weight::from_parts(12_518_659, 6126) - // Standard Error: 1_147 - .saturating_add(Weight::from_parts(210_863, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn swap_exact_amount_in_cpmm() -> Weight { // Proof Size summary in bytes: - // Measured: `5526` - // Estimated: `22278` - // Minimum execution time: 176_270 nanoseconds. - Weight::from_parts(201_411_000, 22278) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:3 w:3) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:1) - /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:0) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// The range of component `a` is `[3, 65]`. - fn swap_exact_amount_in_rikiddo(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `2160 + a * (83 ±0)` - // Estimated: `28014 + a * (2352 ±1)` - // Minimum execution time: 206_661 nanoseconds. - Weight::from_parts(170_386_948, 28014) - // Standard Error: 41_047 - .saturating_add(Weight::from_parts(20_812_338, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(Weight::from_parts(0, 2352).saturating_mul(a.into())) + // Measured: `5144` + // Estimated: `19053` + // Minimum execution time: 136_000 nanoseconds. + Weight::from_parts(138_000_000, 19053) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Swaps PoolsCachedForArbitrage (r:0 w:1) - /// Proof: Swaps PoolsCachedForArbitrage (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) fn swap_exact_amount_out_cpmm() -> Weight { // Proof Size summary in bytes: - // Measured: `5526` - // Estimated: `22278` - // Minimum execution time: 172_410 nanoseconds. - Weight::from_parts(179_920_000, 22278) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: Swaps Pools (r:1 w:0) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:3) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:64 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:1) - /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// The range of component `a` is `[3, 65]`. - fn swap_exact_amount_out_rikiddo(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `2074 + a * (85 ±0)` - // Estimated: `28005 + a * (2352 ±1)` - // Minimum execution time: 187_630 nanoseconds. - Weight::from_parts(116_665_688, 28005) - // Standard Error: 55_493 - .saturating_add(Weight::from_parts(35_177_058, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(Weight::from_parts(0, 2352).saturating_mul(a.into())) + // Measured: `5144` + // Estimated: `19053` + // Minimum execution time: 138_000 nanoseconds. + Weight::from_parts(144_000_000, 19053) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// The range of component `a` is `[2, 65]`. fn open_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `209 + a * (54 ±0)` - // Estimated: `6126` - // Minimum execution time: 17_780 nanoseconds. - Weight::from_parts(21_764_096, 6126) - // Standard Error: 1_984 - .saturating_add(Weight::from_parts(341_951, 0).saturating_mul(a.into())) + // Measured: `168 + a * (54 ±0)` + // Estimated: `6054` + // Minimum execution time: 15_000 nanoseconds. + Weight::from_parts(16_932_645, 6054) + // Standard Error: 85_332 + .saturating_add(Weight::from_parts(437_804, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// The range of component `a` is `[2, 65]`. fn close_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `209 + a * (54 ±0)` - // Estimated: `6126` - // Minimum execution time: 15_590 nanoseconds. - Weight::from_parts(18_163_492, 6126) - // Standard Error: 1_494 - .saturating_add(Weight::from_parts(219_903, 0).saturating_mul(a.into())) + // Measured: `168 + a * (54 ±0)` + // Estimated: `6054` + // Minimum execution time: 13_000 nanoseconds. + Weight::from_parts(15_889_049, 6054) + // Standard Error: 76_504 + .saturating_add(Weight::from_parts(243_907, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Swaps Pools (r:1 w:1) - /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:65 w:65) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) @@ -588,12 +242,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn destroy_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `610 + a * (215 ±0)` - // Estimated: `8733 + a * (5116 ±0)` - // Minimum execution time: 80_150 nanoseconds. - Weight::from_parts(34_885_456, 8733) - // Standard Error: 42_393 - .saturating_add(Weight::from_parts(26_784_533, 0).saturating_mul(a.into())) + // Measured: `568 + a * (214 ±0)` + // Estimated: `8661 + a * (5116 ±0)` + // Minimum execution time: 63_000 nanoseconds. + Weight::from_parts(54_961_907, 8661) + // Standard Error: 596_971 + .saturating_add(Weight::from_parts(16_406_692, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(2))