From 3d483134d925077dc29fbfdaf8ab0b3ab930e5f5 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Mon, 6 Nov 2023 09:03:39 +0100 Subject: [PATCH 001/104] Merge release v0.4.2 (#1174) * Update versions (#1168) * Add updated weights from reference machine (#1169) * Add license header Co-authored-by: Chralt --------- Co-authored-by: Chralt --- Cargo.lock | 42 +- node/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- runtime/battery-station/Cargo.toml | 2 +- runtime/battery-station/src/lib.rs | 4 +- runtime/common/Cargo.toml | 2 +- .../src/weights/cumulus_pallet_xcmp_queue.rs | 10 +- runtime/common/src/weights/frame_system.rs | 50 +- runtime/common/src/weights/orml_currencies.rs | 26 +- runtime/common/src/weights/orml_tokens.rs | 32 +- .../src/weights/pallet_author_inherent.rs | 6 +- .../src/weights/pallet_author_mapping.rs | 22 +- .../src/weights/pallet_author_slot_filter.rs | 6 +- runtime/common/src/weights/pallet_balances.rs | 30 +- runtime/common/src/weights/pallet_bounties.rs | 54 +- .../common/src/weights/pallet_collective.rs | 122 +-- .../common/src/weights/pallet_contracts.rs | 944 +++++++++--------- .../common/src/weights/pallet_democracy.rs | 122 +-- runtime/common/src/weights/pallet_identity.rs | 154 ++- .../common/src/weights/pallet_membership.rs | 58 +- runtime/common/src/weights/pallet_multisig.rs | 70 +- .../src/weights/pallet_parachain_staking.rs | 210 ++-- runtime/common/src/weights/pallet_preimage.rs | 60 +- runtime/common/src/weights/pallet_proxy.rs | 94 +- .../common/src/weights/pallet_scheduler.rs | 74 +- .../common/src/weights/pallet_timestamp.rs | 10 +- runtime/common/src/weights/pallet_treasury.rs | 34 +- runtime/common/src/weights/pallet_utility.rs | 34 +- runtime/common/src/weights/pallet_vesting.rs | 82 +- runtime/zeitgeist/Cargo.toml | 2 +- runtime/zeitgeist/src/lib.rs | 4 +- zrml/authorized/Cargo.toml | 2 +- zrml/authorized/src/weights.rs | 52 +- zrml/court/Cargo.toml | 2 +- zrml/court/src/weights.rs | 206 ++-- zrml/global-disputes/Cargo.toml | 2 +- zrml/global-disputes/src/weights.rs | 80 +- zrml/liquidity-mining/Cargo.toml | 2 +- zrml/liquidity-mining/src/weights.rs | 6 +- zrml/market-commons/Cargo.toml | 2 +- zrml/neo-swaps/Cargo.toml | 2 +- zrml/neo-swaps/src/weights.rs | 56 +- zrml/orderbook/Cargo.toml | 2 +- zrml/orderbook/src/weights.rs | 44 +- zrml/parimutuel/Cargo.toml | 2 +- zrml/parimutuel/src/weights.rs | 51 +- zrml/prediction-markets/Cargo.toml | 2 +- .../prediction-markets/runtime-api/Cargo.toml | 2 +- zrml/prediction-markets/src/weights.rs | 596 +++++------ zrml/rikiddo/Cargo.toml | 2 +- zrml/simple-disputes/Cargo.toml | 2 +- zrml/styx/Cargo.toml | 2 +- zrml/styx/src/weights.rs | 10 +- zrml/swaps/Cargo.toml | 2 +- zrml/swaps/rpc/Cargo.toml | 2 +- zrml/swaps/runtime-api/Cargo.toml | 2 +- zrml/swaps/src/weights.rs | 248 ++--- 57 files changed, 1870 insertions(+), 1875 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7e33ea0a..258481776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "battery-station-runtime" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cfg-if", "common-runtime", @@ -1205,7 +1205,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cfg-if", "cumulus-pallet-xcmp-queue", @@ -14278,7 +14278,7 @@ dependencies = [ [[package]] name = "zeitgeist-node" -version = "0.4.1" +version = "0.4.2" dependencies = [ "battery-station-runtime", "cfg-if", @@ -14370,7 +14370,7 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.1" +version = "0.4.2" dependencies = [ "arbitrary", "fixed", @@ -14392,7 +14392,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cfg-if", "common-runtime", @@ -14515,7 +14515,7 @@ dependencies = [ [[package]] name = "zrml-authorized" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14532,7 +14532,7 @@ dependencies = [ [[package]] name = "zrml-court" -version = "0.4.1" +version = "0.4.2" dependencies = [ "arrayvec 0.7.4", "frame-benchmarking", @@ -14556,7 +14556,7 @@ dependencies = [ [[package]] name = "zrml-global-disputes" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14576,7 +14576,7 @@ dependencies = [ [[package]] name = "zrml-liquidity-mining" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14594,7 +14594,7 @@ dependencies = [ [[package]] name = "zrml-market-commons" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-support", "frame-system", @@ -14610,7 +14610,7 @@ dependencies = [ [[package]] name = "zrml-neo-swaps" -version = "0.4.1" +version = "0.4.2" dependencies = [ "fixed", "frame-benchmarking", @@ -14654,7 +14654,7 @@ dependencies = [ [[package]] name = "zrml-orderbook" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14687,7 +14687,7 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14708,7 +14708,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14760,7 +14760,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets-runtime-api" -version = "0.4.1" +version = "0.4.2" dependencies = [ "parity-scale-codec", "sp-api", @@ -14769,7 +14769,7 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.1" +version = "0.4.2" dependencies = [ "arbitrary", "cfg-if", @@ -14802,7 +14802,7 @@ dependencies = [ [[package]] name = "zrml-simple-disputes" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14822,7 +14822,7 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14838,7 +14838,7 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.1" +version = "0.4.2" dependencies = [ "frame-benchmarking", "frame-support", @@ -14881,7 +14881,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.1" +version = "0.4.2" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14894,7 +14894,7 @@ dependencies = [ [[package]] name = "zrml-swaps-runtime-api" -version = "0.4.1" +version = "0.4.2" dependencies = [ "parity-scale-codec", "sp-api", diff --git a/node/Cargo.toml b/node/Cargo.toml index bf169fa24..101edf598 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.1" +version = "0.4.2" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index bca638965..903a91f70 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -37,4 +37,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-primitives" -version = "0.4.1" +version = "0.4.2" diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 5cc792b90..0d3543a38 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -417,7 +417,7 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "battery-station-runtime" -version = "0.4.1" +version = "0.4.2" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index 1475c43e7..e793cad60 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -102,10 +102,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 50, + spec_version: 51, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 25, + transaction_version: 26, state_version: 1, }; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 1e6d468f3..ca53c3aff 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.1" +version = "0.4.2" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs index 2e0f4aea3..28629b098 100644 --- a/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for cumulus_pallet_xcmp_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `142` // Estimated: `637` - // Minimum execution time: 9_990 nanoseconds. - Weight::from_parts(10_420_000, 637) + // Minimum execution time: 10_160 nanoseconds. + Weight::from_parts(10_570_000, 637) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,8 +69,8 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `142` // Estimated: `637` - // Minimum execution time: 10_040 nanoseconds. - Weight::from_parts(10_910_000, 637) + // Minimum execution time: 8_410 nanoseconds. + Weight::from_parts(10_630_000, 637) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/frame_system.rs b/runtime/common/src/weights/frame_system.rs index 7761269e0..344171162 100644 --- a/runtime/common/src/weights/frame_system.rs +++ b/runtime/common/src/weights/frame_system.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,20 +57,20 @@ impl frame_system::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_890 nanoseconds. - Weight::from_parts(12_270_000, 0) - // Standard Error: 5 - .saturating_add(Weight::from_parts(2_541, 0).saturating_mul(b.into())) + // Minimum execution time: 13_050 nanoseconds. + Weight::from_parts(68_712_625, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(2_640, 0).saturating_mul(b.into())) } /// Storage: System Digest (r:1 w:1) /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) @@ -80,8 +80,8 @@ impl frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: - // Measured: `50 + p * (69 ±0)` - // Estimated: `53 + p * (70 ±0)` - // Minimum execution time: 7_060 nanoseconds. - Weight::from_parts(7_320_000, 53) - // Standard Error: 8_444 - .saturating_add(Weight::from_parts(2_133_080, 0).saturating_mul(p.into())) + // Measured: `48 + p * (69 ±0)` + // Estimated: `52 + p * (70 ±0)` + // Minimum execution time: 8_030 nanoseconds. + Weight::from_parts(8_190_000, 52) + // Standard Error: 7_062 + .saturating_add(Weight::from_parts(2_180_112, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } diff --git a/runtime/common/src/weights/orml_currencies.rs b/runtime/common/src/weights/orml_currencies.rs index 3288d6395..f66c2045c 100644 --- a/runtime/common/src/weights/orml_currencies.rs +++ b/runtime/common/src/weights/orml_currencies.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for orml_currencies //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -55,10 +55,10 @@ impl orml_currencies::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer_non_native_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1719` + // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 80_790 nanoseconds. - Weight::from_parts(81_670_000, 7803) + // Minimum execution time: 82_441 nanoseconds. + Weight::from_parts(84_561_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -68,8 +68,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1461` // Estimated: `2607` - // Minimum execution time: 67_250 nanoseconds. - Weight::from_parts(81_560_000, 2607) + // Minimum execution time: 67_971 nanoseconds. + Weight::from_parts(69_580_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,10 +81,10 @@ impl orml_currencies::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn update_balance_non_native_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1327` + // Measured: `1361` // Estimated: `7723` - // Minimum execution time: 56_660 nanoseconds. - Weight::from_parts(68_620_000, 7723) + // Minimum execution time: 56_770 nanoseconds. + Weight::from_parts(58_510_000, 7723) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -94,8 +94,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1401` // Estimated: `2607` - // Minimum execution time: 55_400 nanoseconds. - Weight::from_parts(68_250_000, 2607) + // Minimum execution time: 57_050 nanoseconds. + Weight::from_parts(66_200_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,8 +105,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1525` // Estimated: `2607` - // Minimum execution time: 54_051 nanoseconds. - Weight::from_parts(54_801_000, 2607) + // Minimum execution time: 54_111 nanoseconds. + Weight::from_parts(55_320_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/orml_tokens.rs b/runtime/common/src/weights/orml_tokens.rs index 81e74493b..28a68af8f 100644 --- a/runtime/common/src/weights/orml_tokens.rs +++ b/runtime/common/src/weights/orml_tokens.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for orml_tokens //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -55,10 +55,10 @@ impl orml_tokens::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `1719` + // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 80_511 nanoseconds. - Weight::from_parts(98_030_000, 7803) + // Minimum execution time: 81_270 nanoseconds. + Weight::from_parts(83_770_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -68,10 +68,10 @@ impl orml_tokens::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer_all() -> Weight { // Proof Size summary in bytes: - // Measured: `1719` + // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 84_540 nanoseconds. - Weight::from_parts(102_440_000, 7803) + // Minimum execution time: 85_521 nanoseconds. + Weight::from_parts(101_970_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -81,10 +81,10 @@ impl orml_tokens::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer_keep_alive() -> Weight { // Proof Size summary in bytes: - // Measured: `1543` + // Measured: `1577` // Estimated: `7803` - // Minimum execution time: 66_630 nanoseconds. - Weight::from_parts(80_230_000, 7803) + // Minimum execution time: 65_911 nanoseconds. + Weight::from_parts(81_130_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -94,10 +94,10 @@ impl orml_tokens::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn force_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `1719` + // Measured: `1753` // Estimated: `10410` - // Minimum execution time: 74_130 nanoseconds. - Weight::from_parts(80_780_000, 10410) + // Minimum execution time: 73_730 nanoseconds. + Weight::from_parts(76_910_000, 10410) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -109,10 +109,10 @@ impl orml_tokens::WeightInfo for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn set_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `1327` + // Measured: `1361` // Estimated: `7723` - // Minimum execution time: 56_380 nanoseconds. - Weight::from_parts(57_350_000, 7723) + // Minimum execution time: 66_870 nanoseconds. + Weight::from_parts(68_280_000, 7723) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/common/src/weights/pallet_author_inherent.rs b/runtime/common/src/weights/pallet_author_inherent.rs index 022cd20b5..ae904fda0 100644 --- a/runtime/common/src/weights/pallet_author_inherent.rs +++ b/runtime/common/src/weights/pallet_author_inherent.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_author_inherent //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, 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` @@ -68,8 +68,8 @@ impl pallet_author_inherent::weights::WeightInfo for We // Proof Size summary in bytes: // Measured: `572` // Estimated: `7316` - // Minimum execution time: 37_751 nanoseconds. - Weight::from_parts(45_860_000, 7316) + // Minimum execution time: 45_881 nanoseconds. + Weight::from_parts(46_831_000, 7316) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_author_mapping.rs b/runtime/common/src/weights/pallet_author_mapping.rs index fff81461a..4a90dc514 100644 --- a/runtime/common/src/weights/pallet_author_mapping.rs +++ b/runtime/common/src/weights/pallet_author_mapping.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_author_mapping //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,8 +62,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `462` // Estimated: `6006` - // Minimum execution time: 42_590 nanoseconds. - Weight::from_parts(44_770_000, 6006) + // Minimum execution time: 39_380 nanoseconds. + Weight::from_parts(48_610_000, 6006) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `407` // Estimated: `5764` - // Minimum execution time: 36_420 nanoseconds. - Weight::from_parts(38_180_000, 5764) + // Minimum execution time: 36_621 nanoseconds. + Weight::from_parts(38_220_000, 5764) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -90,8 +90,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `583` // Estimated: `6248` - // Minimum execution time: 49_801 nanoseconds. - Weight::from_parts(51_321_000, 6248) + // Minimum execution time: 51_020 nanoseconds. + Weight::from_parts(52_870_000, 6248) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -105,8 +105,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `689` // Estimated: `8935` - // Minimum execution time: 56_490 nanoseconds. - Weight::from_parts(58_100_000, 8935) + // Minimum execution time: 47_300 nanoseconds. + Weight::from_parts(58_340_000, 8935) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -118,8 +118,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `513` // Estimated: `8451` - // Minimum execution time: 43_710 nanoseconds. - Weight::from_parts(45_410_000, 8451) + // Minimum execution time: 38_050 nanoseconds. + Weight::from_parts(45_481_000, 8451) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/common/src/weights/pallet_author_slot_filter.rs b/runtime/common/src/weights/pallet_author_slot_filter.rs index dead8cd3b..55db2d0fb 100644 --- a/runtime/common/src/weights/pallet_author_slot_filter.rs +++ b/runtime/common/src/weights/pallet_author_slot_filter.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_author_slot_filter //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,7 +58,7 @@ impl pallet_author_slot_filter::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_440 nanoseconds. - Weight::from_parts(12_830_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 11_041 nanoseconds. + Weight::from_parts(13_590_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/runtime/common/src/weights/pallet_balances.rs b/runtime/common/src/weights/pallet_balances.rs index fe7bb1a67..c2d889284 100644 --- a/runtime/common/src/weights/pallet_balances.rs +++ b/runtime/common/src/weights/pallet_balances.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1585` // Estimated: `5214` - // Minimum execution time: 90_550 nanoseconds. - Weight::from_parts(93_890_000, 5214) + // Minimum execution time: 93_020 nanoseconds. + Weight::from_parts(113_901_000, 5214) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -69,8 +69,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1409` // Estimated: `2607` - // Minimum execution time: 58_960 nanoseconds. - Weight::from_parts(60_610_000, 2607) + // Minimum execution time: 60_390 nanoseconds. + Weight::from_parts(73_620_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,8 +80,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1559` // Estimated: `2607` - // Minimum execution time: 47_200 nanoseconds. - Weight::from_parts(48_020_000, 2607) + // Minimum execution time: 46_590 nanoseconds. + Weight::from_parts(57_410_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -91,8 +91,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1559` // Estimated: `2607` - // Minimum execution time: 52_630 nanoseconds. - Weight::from_parts(63_420_000, 2607) + // Minimum execution time: 52_560 nanoseconds. + Weight::from_parts(64_171_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -102,8 +102,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1581` // Estimated: `7821` - // Minimum execution time: 88_440 nanoseconds. - Weight::from_parts(91_800_000, 7821) + // Minimum execution time: 90_000 nanoseconds. + Weight::from_parts(110_571_000, 7821) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -113,8 +113,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1409` // Estimated: `2607` - // Minimum execution time: 68_880 nanoseconds. - Weight::from_parts(83_690_000, 2607) + // Minimum execution time: 70_510 nanoseconds. + Weight::from_parts(84_700_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -124,8 +124,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1443` // Estimated: `2607` - // Minimum execution time: 41_420 nanoseconds. - Weight::from_parts(42_381_000, 2607) + // Minimum execution time: 45_320 nanoseconds. + Weight::from_parts(50_600_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_bounties.rs b/runtime/common/src/weights/pallet_bounties.rs index 8609dddc2..599b456d3 100644 --- a/runtime/common/src/weights/pallet_bounties.rs +++ b/runtime/common/src/weights/pallet_bounties.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -65,10 +65,10 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `141` // Estimated: `3106` - // Minimum execution time: 33_190 nanoseconds. - Weight::from_parts(42_262_327, 3106) - // Standard Error: 70 - .saturating_add(Weight::from_parts(1_293, 0).saturating_mul(d.into())) + // Minimum execution time: 37_401 nanoseconds. + Weight::from_parts(44_892_227, 3106) + // Standard Error: 73 + .saturating_add(Weight::from_parts(1_733, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -80,8 +80,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `229` // Estimated: `3553` - // Minimum execution time: 18_290 nanoseconds. - Weight::from_parts(20_010_000, 3553) + // Minimum execution time: 18_550 nanoseconds. + Weight::from_parts(23_220_000, 3553) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -91,8 +91,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `249` // Estimated: `2656` - // Minimum execution time: 15_910 nanoseconds. - Weight::from_parts(19_160_000, 2656) + // Minimum execution time: 15_770 nanoseconds. + Weight::from_parts(16_330_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,8 +104,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `641` // Estimated: `7870` - // Minimum execution time: 55_340 nanoseconds. - Weight::from_parts(63_641_000, 7870) + // Minimum execution time: 55_190 nanoseconds. + Weight::from_parts(67_611_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -117,8 +117,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `457` // Estimated: `5263` - // Minimum execution time: 32_510 nanoseconds. - Weight::from_parts(36_520_000, 5263) + // Minimum execution time: 31_291 nanoseconds. + Weight::from_parts(37_790_000, 5263) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -128,8 +128,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `289` // Estimated: `2656` - // Minimum execution time: 24_210 nanoseconds. - Weight::from_parts(25_970_000, 2656) + // Minimum execution time: 24_201 nanoseconds. + Weight::from_parts(30_110_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +143,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `674` // Estimated: `10477` - // Minimum execution time: 98_621 nanoseconds. - Weight::from_parts(105_541_000, 10477) + // Minimum execution time: 98_680 nanoseconds. + Weight::from_parts(120_110_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -158,8 +158,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `541` // Estimated: `7870` - // Minimum execution time: 57_460 nanoseconds. - Weight::from_parts(71_010_000, 7870) + // Minimum execution time: 58_310 nanoseconds. + Weight::from_parts(70_830_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -173,8 +173,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `818` // Estimated: `10477` - // Minimum execution time: 74_401 nanoseconds. - Weight::from_parts(75_600_000, 10477) + // Minimum execution time: 74_601 nanoseconds. + Weight::from_parts(89_900_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -184,8 +184,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `289` // Estimated: `2656` - // Minimum execution time: 23_890 nanoseconds. - Weight::from_parts(29_120_000, 2656) + // Minimum execution time: 24_050 nanoseconds. + Weight::from_parts(29_480_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -200,10 +200,10 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `98 + b * (357 ±0)` // Estimated: `897 + b * (7870 ±0)` - // Minimum execution time: 6_800 nanoseconds. - Weight::from_parts(7_080_000, 897) - // Standard Error: 137_583 - .saturating_add(Weight::from_parts(46_651_854, 0).saturating_mul(b.into())) + // Minimum execution time: 6_590 nanoseconds. + Weight::from_parts(6_790_000, 897) + // Standard Error: 110_731 + .saturating_add(Weight::from_parts(48_237_374, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/runtime/common/src/weights/pallet_collective.rs b/runtime/common/src/weights/pallet_collective.rs index 8eae1c505..d29e0f58a 100644 --- a/runtime/common/src/weights/pallet_collective.rs +++ b/runtime/common/src/weights/pallet_collective.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -67,12 +67,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `0 + m * (8195 ±0) + p * (3227 ±0)` // Estimated: `33167 + m * (19751 ±60) + p * (10255 ±23)` - // Minimum execution time: 26_380 nanoseconds. - Weight::from_parts(27_200_000, 33167) - // Standard Error: 310_569 - .saturating_add(Weight::from_parts(22_981_512, 0).saturating_mul(m.into())) - // Standard Error: 121_936 - .saturating_add(Weight::from_parts(14_449_717, 0).saturating_mul(p.into())) + // Minimum execution time: 31_030 nanoseconds. + Weight::from_parts(34_031_000, 33167) + // Standard Error: 302_126 + .saturating_add(Weight::from_parts(24_404_344, 0).saturating_mul(m.into())) + // Standard Error: 118_621 + .saturating_add(Weight::from_parts(14_920_443, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -88,12 +88,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `100 + m * (32 ±0)` // Estimated: `596 + m * (32 ±0)` - // Minimum execution time: 26_230 nanoseconds. - Weight::from_parts(30_170_565, 596) - // Standard Error: 379 - .saturating_add(Weight::from_parts(2_989, 0).saturating_mul(b.into())) - // Standard Error: 3_916 - .saturating_add(Weight::from_parts(27_154, 0).saturating_mul(m.into())) + // Minimum execution time: 25_280 nanoseconds. + Weight::from_parts(32_839_166, 596) + // Standard Error: 351 + .saturating_add(Weight::from_parts(1_690, 0).saturating_mul(b.into())) + // Standard Error: 3_619 + .saturating_add(Weight::from_parts(4_276, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } @@ -103,14 +103,16 @@ impl pallet_collective::weights::WeightInfo for WeightI /// Proof Skipped: AdvisoryCommittee ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[1, 100]`. - fn propose_execute(_b: u32, m: u32) -> Weight { + fn propose_execute(b: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `100 + m * (32 ±0)` // Estimated: `3172 + m * (64 ±0)` - // Minimum execution time: 32_460 nanoseconds. - Weight::from_parts(40_858_212, 3172) - // Standard Error: 8_079 - .saturating_add(Weight::from_parts(30_321, 0).saturating_mul(m.into())) + // Minimum execution time: 31_490 nanoseconds. + Weight::from_parts(31_614_460, 3172) + // Standard Error: 648 + .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(b.into())) + // Standard Error: 6_684 + .saturating_add(Weight::from_parts(82_588, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } @@ -131,14 +133,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `565 + m * (32 ±0) + p * (33 ±0)` // Estimated: `6570 + m * (160 ±0) + p * (170 ±0)` - // Minimum execution time: 38_570 nanoseconds. - Weight::from_parts(33_301_285, 6570) - // Standard Error: 900 - .saturating_add(Weight::from_parts(14_182, 0).saturating_mul(b.into())) - // Standard Error: 9_395 - .saturating_add(Weight::from_parts(19_064, 0).saturating_mul(m.into())) - // Standard Error: 3_616 - .saturating_add(Weight::from_parts(190_814, 0).saturating_mul(p.into())) + // Minimum execution time: 43_310 nanoseconds. + Weight::from_parts(37_615_320, 6570) + // Standard Error: 862 + .saturating_add(Weight::from_parts(15_233, 0).saturating_mul(b.into())) + // Standard Error: 9_003 + .saturating_add(Weight::from_parts(21_795, 0).saturating_mul(m.into())) + // Standard Error: 3_466 + .saturating_add(Weight::from_parts(196_387, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 160).saturating_mul(m.into())) @@ -153,10 +155,8 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `1240 + m * (64 ±0)` // Estimated: `5448 + m * (128 ±0)` - // Minimum execution time: 39_530 nanoseconds. - Weight::from_parts(48_928_423, 5448) - // Standard Error: 6_452 - .saturating_add(Weight::from_parts(80_145, 0).saturating_mul(m.into())) + // Minimum execution time: 40_050 nanoseconds. + Weight::from_parts(53_695_231, 5448) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 128).saturating_mul(m.into())) @@ -175,10 +175,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `683 + m * (64 ±0) + p * (33 ±0)` // Estimated: `6017 + m * (260 ±0) + p * (136 ±0)` - // Minimum execution time: 40_280 nanoseconds. - Weight::from_parts(52_288_034, 6017) - // Standard Error: 3_896 - .saturating_add(Weight::from_parts(158_441, 0).saturating_mul(p.into())) + // Minimum execution time: 40_470 nanoseconds. + Weight::from_parts(46_112_135, 6017) + // Standard Error: 10_933 + .saturating_add(Weight::from_parts(78_433, 0).saturating_mul(m.into())) + // Standard Error: 4_155 + .saturating_add(Weight::from_parts(161_800, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 260).saturating_mul(m.into())) @@ -199,14 +201,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `926 + b * (1 ±0) + m * (64 ±0) + p * (36 ±0)` // Estimated: `9916 + b * (4 ±0) + m * (248 ±0) + p * (144 ±0)` - // Minimum execution time: 61_820 nanoseconds. - Weight::from_parts(52_654_648, 9916) - // Standard Error: 1_197 - .saturating_add(Weight::from_parts(15_264, 0).saturating_mul(b.into())) - // Standard Error: 12_652 - .saturating_add(Weight::from_parts(37_155, 0).saturating_mul(m.into())) - // Standard Error: 4_808 - .saturating_add(Weight::from_parts(226_001, 0).saturating_mul(p.into())) + // Minimum execution time: 56_840 nanoseconds. + Weight::from_parts(66_416_383, 9916) + // Standard Error: 1_152 + .saturating_add(Weight::from_parts(662, 0).saturating_mul(b.into())) + // Standard Error: 12_177 + .saturating_add(Weight::from_parts(21_417, 0).saturating_mul(m.into())) + // Standard Error: 4_628 + .saturating_add(Weight::from_parts(223_068, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 4).saturating_mul(b.into())) @@ -229,12 +231,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `703 + m * (64 ±0) + p * (33 ±0)` // Estimated: `7250 + m * (325 ±0) + p * (170 ±0)` - // Minimum execution time: 44_160 nanoseconds. - Weight::from_parts(42_940_363, 7250) - // Standard Error: 9_820 - .saturating_add(Weight::from_parts(89_601, 0).saturating_mul(m.into())) - // Standard Error: 3_732 - .saturating_add(Weight::from_parts(180_778, 0).saturating_mul(p.into())) + // Minimum execution time: 44_370 nanoseconds. + Weight::from_parts(53_029_434, 7250) + // Standard Error: 10_566 + .saturating_add(Weight::from_parts(62_376, 0).saturating_mul(m.into())) + // Standard Error: 4_015 + .saturating_add(Weight::from_parts(159_872, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 325).saturating_mul(m.into())) @@ -257,14 +259,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `946 + b * (1 ±0) + m * (64 ±0) + p * (36 ±0)` // Estimated: `11505 + b * (5 ±0) + m * (310 ±0) + p * (180 ±0)` - // Minimum execution time: 60_170 nanoseconds. - Weight::from_parts(66_445_852, 11505) - // Standard Error: 1_181 - .saturating_add(Weight::from_parts(4_518, 0).saturating_mul(b.into())) - // Standard Error: 12_489 - .saturating_add(Weight::from_parts(2_497, 0).saturating_mul(m.into())) - // Standard Error: 4_746 - .saturating_add(Weight::from_parts(234_158, 0).saturating_mul(p.into())) + // Minimum execution time: 66_090 nanoseconds. + Weight::from_parts(66_646_932, 11505) + // Standard Error: 1_083 + .saturating_add(Weight::from_parts(11_363, 0).saturating_mul(b.into())) + // Standard Error: 11_446 + .saturating_add(Weight::from_parts(56_961, 0).saturating_mul(m.into())) + // Standard Error: 4_350 + .saturating_add(Weight::from_parts(223_823, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 5).saturating_mul(b.into())) @@ -282,10 +284,10 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `258 + p * (32 ±0)` // Estimated: `1266 + p * (96 ±0)` - // Minimum execution time: 23_860 nanoseconds. - Weight::from_parts(30_109_336, 1266) - // Standard Error: 2_409 - .saturating_add(Weight::from_parts(136_871, 0).saturating_mul(p.into())) + // Minimum execution time: 23_990 nanoseconds. + Weight::from_parts(29_777_910, 1266) + // Standard Error: 3_885 + .saturating_add(Weight::from_parts(172_382, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 96).saturating_mul(p.into())) diff --git a/runtime/common/src/weights/pallet_contracts.rs b/runtime/common/src/weights/pallet_contracts.rs index 754fc5760..642253dbb 100644 --- a/runtime/common/src/weights/pallet_contracts.rs +++ b/runtime/common/src/weights/pallet_contracts.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `42` // Estimated: `0` - // Minimum execution time: 6_080 nanoseconds. - Weight::from_parts(6_450_000, 0).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 6_390 nanoseconds. + Weight::from_parts(6_800_000, 0).saturating_add(T::DbWeight::get().reads(1)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) @@ -68,10 +68,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `414 + k * (69 ±0)` // Estimated: `0` - // Minimum execution time: 18_210 nanoseconds. - Weight::from_parts(7_024_373, 0) - // Standard Error: 10_987 - .saturating_add(Weight::from_parts(1_742_324, 0).saturating_mul(k.into())) + // Minimum execution time: 16_110 nanoseconds. + Weight::from_parts(17_498_174, 0) + // Standard Error: 8_764 + .saturating_add(Weight::from_parts(1_845_920, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -83,10 +83,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `214 + q * (33 ±0)` // Estimated: `0` - // Minimum execution time: 6_330 nanoseconds. - Weight::from_parts(16_618_983, 0) - // Standard Error: 17_949 - .saturating_add(Weight::from_parts(2_022_944, 0).saturating_mul(q.into())) + // Minimum execution time: 5_320 nanoseconds. + Weight::from_parts(17_768_678, 0) + // Standard Error: 16_408 + .saturating_add(Weight::from_parts(2_245_997, 0).saturating_mul(q.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -99,10 +99,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `203 + c * (1 ±0)` // Estimated: `0` - // Minimum execution time: 61_320 nanoseconds. - Weight::from_parts(111_795_683, 0) - // Standard Error: 345 - .saturating_add(Weight::from_parts(100_736, 0).saturating_mul(c.into())) + // Minimum execution time: 61_031 nanoseconds. + Weight::from_parts(96_901_594, 0) + // Standard Error: 496 + .saturating_add(Weight::from_parts(104_553, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -121,10 +121,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `675` // Estimated: `0` - // Minimum execution time: 651_081 nanoseconds. - Weight::from_parts(663_809_385, 0) - // Standard Error: 221 - .saturating_add(Weight::from_parts(57_437, 0).saturating_mul(c.into())) + // Minimum execution time: 548_392 nanoseconds. + Weight::from_parts(731_430_648, 0) + // Standard Error: 274 + .saturating_add(Weight::from_parts(59_880, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -151,14 +151,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `157` // Estimated: `0` - // Minimum execution time: 6_484_362 nanoseconds. - Weight::from_parts(797_808_303, 0) - // Standard Error: 692 - .saturating_add(Weight::from_parts(180_720, 0).saturating_mul(c.into())) - // Standard Error: 40 - .saturating_add(Weight::from_parts(2_893, 0).saturating_mul(i.into())) - // Standard Error: 40 - .saturating_add(Weight::from_parts(3_010, 0).saturating_mul(s.into())) + // Minimum execution time: 6_948_252 nanoseconds. + Weight::from_parts(842_384_814, 0) + // Standard Error: 832 + .saturating_add(Weight::from_parts(181_029, 0).saturating_mul(c.into())) + // Standard Error: 49 + .saturating_add(Weight::from_parts(2_922, 0).saturating_mul(i.into())) + // Standard Error: 49 + .saturating_add(Weight::from_parts(3_510, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(9)) } @@ -182,12 +182,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `433` // Estimated: `0` - // Minimum execution time: 2_556_385 nanoseconds. - Weight::from_parts(712_729_592, 0) + // Minimum execution time: 2_540_808 nanoseconds. + Weight::from_parts(595_650_884, 0) // Standard Error: 48 - .saturating_add(Weight::from_parts(1_946, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(2_130, 0).saturating_mul(i.into())) // Standard Error: 48 - .saturating_add(Weight::from_parts(2_947, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(3_126, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -205,8 +205,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `727` // Estimated: `0` - // Minimum execution time: 218_010 nanoseconds. - Weight::from_parts(232_030_000, 0) + // Minimum execution time: 228_391 nanoseconds. + Weight::from_parts(278_201_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -223,10 +223,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `42` // Estimated: `0` - // Minimum execution time: 663_371 nanoseconds. - Weight::from_parts(728_818_764, 0) - // Standard Error: 1_131 - .saturating_add(Weight::from_parts(180_003, 0).saturating_mul(c.into())) + // Minimum execution time: 602_562 nanoseconds. + Weight::from_parts(697_000_805, 0) + // Standard Error: 1_063 + .saturating_add(Weight::from_parts(188_416, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -242,8 +242,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `218` // Estimated: `0` - // Minimum execution time: 39_610 nanoseconds. - Weight::from_parts(46_320_000, 0) + // Minimum execution time: 37_290 nanoseconds. + Weight::from_parts(45_580_000, 0) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -257,8 +257,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `567` // Estimated: `0` - // Minimum execution time: 41_910 nanoseconds. - Weight::from_parts(48_090_000, 0) + // Minimum execution time: 42_671 nanoseconds. + Weight::from_parts(51_910_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -277,10 +277,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `697 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 536_911 nanoseconds. - Weight::from_parts(649_038_013, 0) - // Standard Error: 731_408 - .saturating_add(Weight::from_parts(38_688_760, 0).saturating_mul(r.into())) + // Minimum execution time: 535_552 nanoseconds. + Weight::from_parts(684_787_809, 0) + // Standard Error: 678_512 + .saturating_add(Weight::from_parts(40_079_220, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -299,10 +299,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `749 + r * (19218 ±0)` // Estimated: `0` - // Minimum execution time: 539_701 nanoseconds. - Weight::from_parts(398_198_074, 0) - // Standard Error: 2_231_810 - .saturating_add(Weight::from_parts(461_745_248, 0).saturating_mul(r.into())) + // Minimum execution time: 536_032 nanoseconds. + Weight::from_parts(483_493_910, 0) + // Standard Error: 1_504_096 + .saturating_add(Weight::from_parts(458_565_900, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -322,10 +322,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `741 + r * (19539 ±0)` // Estimated: `0` - // Minimum execution time: 540_831 nanoseconds. - Weight::from_parts(454_109_442, 0) - // Standard Error: 2_462_833 - .saturating_add(Weight::from_parts(544_978_174, 0).saturating_mul(r.into())) + // Minimum execution time: 539_622 nanoseconds. + Weight::from_parts(404_765_458, 0) + // Standard Error: 1_736_557 + .saturating_add(Weight::from_parts(546_753_159, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -345,10 +345,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `704 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 539_531 nanoseconds. - Weight::from_parts(661_621_217, 0) - // Standard Error: 548_577 - .saturating_add(Weight::from_parts(43_931_981, 0).saturating_mul(r.into())) + // Minimum execution time: 537_862 nanoseconds. + Weight::from_parts(702_633_145, 0) + // Standard Error: 668_343 + .saturating_add(Weight::from_parts(41_612_969, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -367,10 +367,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 533_071 nanoseconds. - Weight::from_parts(652_770_497, 0) - // Standard Error: 442_690 - .saturating_add(Weight::from_parts(20_990_918, 0).saturating_mul(r.into())) + // Minimum execution time: 533_112 nanoseconds. + Weight::from_parts(653_370_992, 0) + // Standard Error: 440_087 + .saturating_add(Weight::from_parts(22_783_189, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -389,10 +389,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `698 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 536_961 nanoseconds. - Weight::from_parts(654_344_945, 0) - // Standard Error: 481_495 - .saturating_add(Weight::from_parts(35_850_715, 0).saturating_mul(r.into())) + // Minimum execution time: 534_612 nanoseconds. + Weight::from_parts(679_142_053, 0) + // Standard Error: 471_149 + .saturating_add(Weight::from_parts(35_590_449, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -411,10 +411,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `699 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 537_131 nanoseconds. - Weight::from_parts(666_668_449, 0) - // Standard Error: 471_768 - .saturating_add(Weight::from_parts(35_356_154, 0).saturating_mul(r.into())) + // Minimum execution time: 534_341 nanoseconds. + Weight::from_parts(650_082_231, 0) + // Standard Error: 500_514 + .saturating_add(Weight::from_parts(37_595_102, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -433,10 +433,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `873 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 536_771 nanoseconds. - Weight::from_parts(712_769_271, 0) - // Standard Error: 755_664 - .saturating_add(Weight::from_parts(158_233_444, 0).saturating_mul(r.into())) + // Minimum execution time: 534_442 nanoseconds. + Weight::from_parts(671_569_610, 0) + // Standard Error: 834_847 + .saturating_add(Weight::from_parts(171_590_994, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -455,10 +455,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `708 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_211 nanoseconds. - Weight::from_parts(659_103_045, 0) - // Standard Error: 884_226 - .saturating_add(Weight::from_parts(35_977_888, 0).saturating_mul(r.into())) + // Minimum execution time: 532_762 nanoseconds. + Weight::from_parts(657_616_211, 0) + // Standard Error: 899_157 + .saturating_add(Weight::from_parts(37_407_507, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -477,10 +477,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `706 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 536_701 nanoseconds. - Weight::from_parts(638_397_534, 0) - // Standard Error: 547_407 - .saturating_add(Weight::from_parts(37_004_290, 0).saturating_mul(r.into())) + // Minimum execution time: 534_672 nanoseconds. + Weight::from_parts(666_305_025, 0) + // Standard Error: 674_283 + .saturating_add(Weight::from_parts(37_129_240, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -499,10 +499,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `703 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 535_401 nanoseconds. - Weight::from_parts(615_726_727, 0) - // Standard Error: 618_091 - .saturating_add(Weight::from_parts(38_987_676, 0).saturating_mul(r.into())) + // Minimum execution time: 534_661 nanoseconds. + Weight::from_parts(657_489_104, 0) + // Standard Error: 521_523 + .saturating_add(Weight::from_parts(36_933_388, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -521,10 +521,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 536_921 nanoseconds. - Weight::from_parts(639_353_814, 0) - // Standard Error: 491_104 - .saturating_add(Weight::from_parts(37_746_710, 0).saturating_mul(r.into())) + // Minimum execution time: 535_102 nanoseconds. + Weight::from_parts(676_467_296, 0) + // Standard Error: 600_497 + .saturating_add(Weight::from_parts(35_420_595, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -545,10 +545,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `809 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 539_572 nanoseconds. - Weight::from_parts(774_674_044, 0) - // Standard Error: 865_107 - .saturating_add(Weight::from_parts(153_164_182, 0).saturating_mul(r.into())) + // Minimum execution time: 535_841 nanoseconds. + Weight::from_parts(683_857_279, 0) + // Standard Error: 715_815 + .saturating_add(Weight::from_parts(160_158_895, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -567,10 +567,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `661 + r * (320 ±0)` // Estimated: `0` - // Minimum execution time: 197_671 nanoseconds. - Weight::from_parts(239_987_306, 0) - // Standard Error: 229_192 - .saturating_add(Weight::from_parts(17_255_992, 0).saturating_mul(r.into())) + // Minimum execution time: 195_350 nanoseconds. + Weight::from_parts(235_736_945, 0) + // Standard Error: 237_408 + .saturating_add(Weight::from_parts(17_693_780, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -589,10 +589,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `696 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 537_261 nanoseconds. - Weight::from_parts(692_611_711, 0) - // Standard Error: 454_523 - .saturating_add(Weight::from_parts(30_093_959, 0).saturating_mul(r.into())) + // Minimum execution time: 534_762 nanoseconds. + Weight::from_parts(645_918_150, 0) + // Standard Error: 738_459 + .saturating_add(Weight::from_parts(35_776_568, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -611,10 +611,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1176` // Estimated: `0` - // Minimum execution time: 569_711 nanoseconds. - Weight::from_parts(687_286_602, 0) - // Standard Error: 39_549 - .saturating_add(Weight::from_parts(12_178_012, 0).saturating_mul(n.into())) + // Minimum execution time: 568_112 nanoseconds. + Weight::from_parts(839_531_613, 0) + // Standard Error: 50_315 + .saturating_add(Weight::from_parts(12_273_119, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -629,14 +629,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_return(r: u32) -> Weight { + fn seal_return(_r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `684 + r * (8 ±0)` // Estimated: `0` - // Minimum execution time: 531_732 nanoseconds. - Weight::from_parts(645_719_612, 0) - // Standard Error: 9_218_034 - .saturating_add(Weight::from_parts(95_501_187, 0).saturating_mul(r.into())) + // Minimum execution time: 530_752 nanoseconds. + Weight::from_parts(664_866_575, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -655,10 +653,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694` // Estimated: `0` - // Minimum execution time: 534_212 nanoseconds. - Weight::from_parts(625_914_857, 0) - // Standard Error: 9_816 - .saturating_add(Weight::from_parts(404_875, 0).saturating_mul(n.into())) + // Minimum execution time: 533_101 nanoseconds. + Weight::from_parts(626_179_160, 0) + // Standard Error: 10_080 + .saturating_add(Weight::from_parts(389_208, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -681,10 +679,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `726 + r * (285 ±0)` // Estimated: `0` - // Minimum execution time: 534_742 nanoseconds. - Weight::from_parts(639_771_534, 0) - // Standard Error: 9_422_723 - .saturating_add(Weight::from_parts(110_289_165, 0).saturating_mul(r.into())) + // Minimum execution time: 533_922 nanoseconds. + Weight::from_parts(662_814_704, 0) + // Standard Error: 10_208_426 + .saturating_add(Weight::from_parts(65_759_495, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -707,10 +705,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `775 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 536_012 nanoseconds. - Weight::from_parts(653_666_893, 0) - // Standard Error: 971_714 - .saturating_add(Weight::from_parts(202_771_445, 0).saturating_mul(r.into())) + // Minimum execution time: 534_572 nanoseconds. + Weight::from_parts(647_025_351, 0) + // Standard Error: 1_159_894 + .saturating_add(Weight::from_parts(207_568_972, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -729,10 +727,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 532_351 nanoseconds. - Weight::from_parts(677_960_612, 0) - // Standard Error: 1_147_129 - .saturating_add(Weight::from_parts(390_084_131, 0).saturating_mul(r.into())) + // Minimum execution time: 529_741 nanoseconds. + Weight::from_parts(613_973_354, 0) + // Standard Error: 984_670 + .saturating_add(Weight::from_parts(405_101_002, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -752,12 +750,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1630 + t * (2608 ±0) + n * (8 ±0)` // Estimated: `0` - // Minimum execution time: 1_925_725 nanoseconds. - Weight::from_parts(836_333_823, 0) - // Standard Error: 4_005_545 - .saturating_add(Weight::from_parts(331_741_261, 0).saturating_mul(t.into())) - // Standard Error: 1_100_116 - .saturating_add(Weight::from_parts(123_812_557, 0).saturating_mul(n.into())) + // Minimum execution time: 1_973_097 nanoseconds. + Weight::from_parts(946_904_790, 0) + // Standard Error: 3_997_989 + .saturating_add(Weight::from_parts(347_080_943, 0).saturating_mul(t.into())) + // Standard Error: 1_098_041 + .saturating_add(Weight::from_parts(124_035_590, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -778,10 +776,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 223_610 nanoseconds. - Weight::from_parts(284_805_905, 0) - // Standard Error: 387_522 - .saturating_add(Weight::from_parts(26_538_794, 0).saturating_mul(r.into())) + // Minimum execution time: 223_441 nanoseconds. + Weight::from_parts(290_935_537, 0) + // Standard Error: 241_403 + .saturating_add(Weight::from_parts(30_440_276, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -792,10 +790,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `720 + r * (23420 ±0)` // Estimated: `0` - // Minimum execution time: 535_061 nanoseconds. - Weight::from_parts(551_506_930, 0) - // Standard Error: 2_878_212 - .saturating_add(Weight::from_parts(787_236_547, 0).saturating_mul(r.into())) + // Minimum execution time: 533_902 nanoseconds. + Weight::from_parts(577_630_485, 0) + // Standard Error: 2_408_816 + .saturating_add(Weight::from_parts(821_568_409, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -808,10 +806,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `12402 + n * (12006 ±0)` // Estimated: `0` - // Minimum execution time: 763_351 nanoseconds. - Weight::from_parts(1_211_260_729, 0) - // Standard Error: 3_403_829 - .saturating_add(Weight::from_parts(162_413_215, 0).saturating_mul(n.into())) + // Minimum execution time: 758_442 nanoseconds. + Weight::from_parts(1_221_798_222, 0) + // Standard Error: 3_439_887 + .saturating_add(Weight::from_parts(184_887_572, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(52)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(50)) @@ -824,10 +822,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14990 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 761_872 nanoseconds. - Weight::from_parts(1_184_329_650, 0) - // Standard Error: 3_092_883 - .saturating_add(Weight::from_parts(110_336_252, 0).saturating_mul(n.into())) + // Minimum execution time: 756_943 nanoseconds. + Weight::from_parts(1_236_797_439, 0) + // Standard Error: 3_349_250 + .saturating_add(Weight::from_parts(106_340_610, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(49)) @@ -840,10 +838,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `720 + r * (23100 ±0)` // Estimated: `0` - // Minimum execution time: 539_481 nanoseconds. - Weight::from_parts(457_076_217, 0) - // Standard Error: 3_483_351 - .saturating_add(Weight::from_parts(809_752_536, 0).saturating_mul(r.into())) + // Minimum execution time: 536_812 nanoseconds. + Weight::from_parts(544_422_299, 0) + // Standard Error: 3_373_768 + .saturating_add(Weight::from_parts(809_752_042, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -856,10 +854,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14670 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 710_242 nanoseconds. - Weight::from_parts(1_168_973_359, 0) - // Standard Error: 3_093_181 - .saturating_add(Weight::from_parts(110_932_443, 0).saturating_mul(n.into())) + // Minimum execution time: 704_063 nanoseconds. + Weight::from_parts(1_168_926_033, 0) + // Standard Error: 3_012_712 + .saturating_add(Weight::from_parts(113_252_251, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -872,10 +870,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `730 + r * (23740 ±0)` // Estimated: `0` - // Minimum execution time: 539_211 nanoseconds. - Weight::from_parts(530_631_252, 0) - // Standard Error: 2_386_741 - .saturating_add(Weight::from_parts(643_843_165, 0).saturating_mul(r.into())) + // Minimum execution time: 540_782 nanoseconds. + Weight::from_parts(604_103_979, 0) + // Standard Error: 2_452_163 + .saturating_add(Weight::from_parts(659_347_493, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -887,10 +885,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `15321 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 677_852 nanoseconds. - Weight::from_parts(1_089_244_381, 0) - // Standard Error: 2_973_981 - .saturating_add(Weight::from_parts(274_426_904, 0).saturating_mul(n.into())) + // Minimum execution time: 678_092 nanoseconds. + Weight::from_parts(1_155_612_178, 0) + // Standard Error: 3_624_142 + .saturating_add(Weight::from_parts(278_889_523, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -902,10 +900,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `723 + r * (23100 ±0)` // Estimated: `0` - // Minimum execution time: 539_381 nanoseconds. - Weight::from_parts(540_275_019, 0) - // Standard Error: 2_191_374 - .saturating_add(Weight::from_parts(604_970_501, 0).saturating_mul(r.into())) + // Minimum execution time: 537_101 nanoseconds. + Weight::from_parts(554_645_766, 0) + // Standard Error: 2_641_073 + .saturating_add(Weight::from_parts(632_807_668, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -917,10 +915,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14673 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 667_112 nanoseconds. - Weight::from_parts(1_008_021_253, 0) - // Standard Error: 2_607_471 - .saturating_add(Weight::from_parts(107_952_488, 0).saturating_mul(n.into())) + // Minimum execution time: 665_912 nanoseconds. + Weight::from_parts(1_040_953_721, 0) + // Standard Error: 2_696_149 + .saturating_add(Weight::from_parts(106_678_921, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -932,10 +930,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `731 + r * (23740 ±0)` // Estimated: `0` - // Minimum execution time: 537_281 nanoseconds. - Weight::from_parts(587_427_860, 0) - // Standard Error: 3_200_681 - .saturating_add(Weight::from_parts(792_532_230, 0).saturating_mul(r.into())) + // Minimum execution time: 538_482 nanoseconds. + Weight::from_parts(422_000_029, 0) + // Standard Error: 3_577_317 + .saturating_add(Weight::from_parts(856_722_439, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -948,10 +946,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `15322 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 716_841 nanoseconds. - Weight::from_parts(1_118_888_191, 0) - // Standard Error: 3_224_328 - .saturating_add(Weight::from_parts(294_328_666, 0).saturating_mul(n.into())) + // Minimum execution time: 713_993 nanoseconds. + Weight::from_parts(1_267_575_312, 0) + // Standard Error: 3_924_466 + .saturating_add(Weight::from_parts(272_437_447, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -972,10 +970,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1322 + r * (3601 ±0)` // Estimated: `0` - // Minimum execution time: 539_721 nanoseconds. - Weight::from_parts(165_304_477, 0) - // Standard Error: 4_834_017 - .saturating_add(Weight::from_parts(2_369_426_888, 0).saturating_mul(r.into())) + // Minimum execution time: 537_791 nanoseconds. + Weight::from_parts(617_094_789, 0) + // Standard Error: 2_893_420 + .saturating_add(Weight::from_parts(2_340_438_170, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -996,10 +994,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `948 + r * (20495 ±0)` // Estimated: `0` - // Minimum execution time: 540_662 nanoseconds. - Weight::from_parts(563_471_000, 0) - // Standard Error: 48_067_636 - .saturating_add(Weight::from_parts(51_370_582_192, 0).saturating_mul(r.into())) + // Minimum execution time: 538_052 nanoseconds. + Weight::from_parts(543_252_000, 0) + // Standard Error: 39_197_014 + .saturating_add(Weight::from_parts(50_755_358_343, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((160_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1020,10 +1018,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0 + r * (71670 ±0)` // Estimated: `0` - // Minimum execution time: 540_822 nanoseconds. - Weight::from_parts(642_842_000, 0) - // Standard Error: 31_689_979 - .saturating_add(Weight::from_parts(50_731_582_616, 0).saturating_mul(r.into())) + // Minimum execution time: 548_332 nanoseconds. + Weight::from_parts(664_512_000, 0) + // Standard Error: 38_649_522 + .saturating_add(Weight::from_parts(49_980_161_132, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((150_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1045,12 +1043,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `21128 + t * (15897 ±0)` // Estimated: `0` - // Minimum execution time: 16_785_218 nanoseconds. - Weight::from_parts(15_606_491_784, 0) - // Standard Error: 59_687_361 - .saturating_add(Weight::from_parts(2_212_944_886, 0).saturating_mul(t.into())) - // Standard Error: 89_497 - .saturating_add(Weight::from_parts(13_703_042, 0).saturating_mul(c.into())) + // Minimum execution time: 16_839_323 nanoseconds. + Weight::from_parts(16_275_890_867, 0) + // Standard Error: 71_746_414 + .saturating_add(Weight::from_parts(2_118_955_274, 0).saturating_mul(t.into())) + // Standard Error: 107_579 + .saturating_add(Weight::from_parts(13_869_639, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(167)) .saturating_add(T::DbWeight::get().reads((81_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(163)) @@ -1075,10 +1073,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1512 + r * (25573 ±0)` // Estimated: `0` - // Minimum execution time: 543_002 nanoseconds. - Weight::from_parts(626_922_000, 0) - // Standard Error: 81_700_194 - .saturating_add(Weight::from_parts(61_961_443_354, 0).saturating_mul(r.into())) + // Minimum execution time: 543_001 nanoseconds. + Weight::from_parts(593_271_000, 0) + // Standard Error: 75_051_630 + .saturating_add(Weight::from_parts(61_709_863_004, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().reads((400_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -1105,12 +1103,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `5505 + t * (68 ±0)` // Estimated: `0` - // Minimum execution time: 193_252_546 nanoseconds. - Weight::from_parts(7_216_274_270, 0) - // Standard Error: 683_658 - .saturating_add(Weight::from_parts(207_345_815, 0).saturating_mul(i.into())) - // Standard Error: 683_658 - .saturating_add(Weight::from_parts(211_926_306, 0).saturating_mul(s.into())) + // Minimum execution time: 198_323_873 nanoseconds. + Weight::from_parts(8_482_437_089, 0) + // Standard Error: 473_750_492 + .saturating_add(Weight::from_parts(1_198_807_215, 0).saturating_mul(t.into())) + // Standard Error: 772_554 + .saturating_add(Weight::from_parts(213_368_181, 0).saturating_mul(i.into())) + // Standard Error: 772_554 + .saturating_add(Weight::from_parts(211_682_191, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(249)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(247)) @@ -1127,14 +1127,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_hash_sha2_256(r: u32) -> Weight { + fn seal_hash_sha2_256(_r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `691 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 534_281 nanoseconds. - Weight::from_parts(665_764_412, 0) - // Standard Error: 9_718_022 - .saturating_add(Weight::from_parts(109_500_787, 0).saturating_mul(r.into())) + // Minimum execution time: 534_132 nanoseconds. + Weight::from_parts(676_132_087, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1153,10 +1151,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1493` // Estimated: `0` - // Minimum execution time: 582_641 nanoseconds. - Weight::from_parts(1_100_556_254, 0) - // Standard Error: 112_735 - .saturating_add(Weight::from_parts(88_211_294, 0).saturating_mul(n.into())) + // Minimum execution time: 583_172 nanoseconds. + Weight::from_parts(654_967_242, 0) + // Standard Error: 193_658 + .saturating_add(Weight::from_parts(91_163_648, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1175,10 +1173,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 533_241 nanoseconds. - Weight::from_parts(644_259_789, 0) - // Standard Error: 9_463_982 - .saturating_add(Weight::from_parts(68_584_310, 0).saturating_mul(r.into())) + // Minimum execution time: 532_132 nanoseconds. + Weight::from_parts(677_504_183, 0) + // Standard Error: 11_506_745 + .saturating_add(Weight::from_parts(100_381_316, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1197,10 +1195,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 615_671 nanoseconds. - Weight::from_parts(53_246_097, 0) - // Standard Error: 477_322 - .saturating_add(Weight::from_parts(345_985_242, 0).saturating_mul(n.into())) + // Minimum execution time: 620_762 nanoseconds. + Weight::from_parts(208_832_043, 0) + // Standard Error: 638_611 + .saturating_add(Weight::from_parts(356_290_502, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1219,10 +1217,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 532_691 nanoseconds. - Weight::from_parts(662_839_577, 0) - // Standard Error: 9_757_528 - .saturating_add(Weight::from_parts(57_827_122, 0).saturating_mul(r.into())) + // Minimum execution time: 531_411 nanoseconds. + Weight::from_parts(663_543_053, 0) + // Standard Error: 11_568_076 + .saturating_add(Weight::from_parts(47_810_346, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1241,10 +1239,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 589_882 nanoseconds. - Weight::from_parts(1_564_409_247, 0) - // Standard Error: 185_985 - .saturating_add(Weight::from_parts(154_060_125, 0).saturating_mul(n.into())) + // Minimum execution time: 588_752 nanoseconds. + Weight::from_parts(614_542_000, 0) + // Standard Error: 177_639 + .saturating_add(Weight::from_parts(161_643_433, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1259,14 +1257,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_hash_blake2_128(r: u32) -> Weight { + fn seal_hash_blake2_128(_r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 532_572 nanoseconds. - Weight::from_parts(651_928_026, 0) - // Standard Error: 9_830_694 - .saturating_add(Weight::from_parts(50_668_673, 0).saturating_mul(r.into())) + // Minimum execution time: 530_851 nanoseconds. + Weight::from_parts(667_968_451, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1285,10 +1281,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 593_642 nanoseconds. - Weight::from_parts(459_685_646, 0) - // Standard Error: 261_286 - .saturating_add(Weight::from_parts(156_975_231, 0).saturating_mul(n.into())) + // Minimum execution time: 585_602 nanoseconds. + Weight::from_parts(658_555_116, 0) + // Standard Error: 279_324 + .saturating_add(Weight::from_parts(159_288_859, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1307,10 +1303,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `737 + r * (6083 ±0)` // Estimated: `0` - // Minimum execution time: 535_422 nanoseconds. - Weight::from_parts(646_575_214, 0) - // Standard Error: 9_859_269 - .saturating_add(Weight::from_parts(5_582_068_285, 0).saturating_mul(r.into())) + // Minimum execution time: 535_382 nanoseconds. + Weight::from_parts(670_345_814, 0) + // Standard Error: 12_790_892 + .saturating_add(Weight::from_parts(5_618_499_485, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1329,10 +1325,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `706 + r * (3362 ±0)` // Estimated: `0` - // Minimum execution time: 536_091 nanoseconds. - Weight::from_parts(663_700_069, 0) - // Standard Error: 9_333_561 - .saturating_add(Weight::from_parts(1_275_587_330, 0).saturating_mul(r.into())) + // Minimum execution time: 534_352 nanoseconds. + Weight::from_parts(655_424_953, 0) + // Standard Error: 10_053_361 + .saturating_add(Weight::from_parts(1_196_174_946, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1353,10 +1349,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0 + r * (79300 ±0)` // Estimated: `0` - // Minimum execution time: 538_192 nanoseconds. - Weight::from_parts(564_532_000, 0) - // Standard Error: 9_620_215 - .saturating_add(Weight::from_parts(2_831_904_081, 0).saturating_mul(r.into())) + // Minimum execution time: 535_492 nanoseconds. + Weight::from_parts(570_292_000, 0) + // Standard Error: 9_317_085 + .saturating_add(Weight::from_parts(2_915_076_582, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((225_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1377,10 +1373,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `689 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 539_472 nanoseconds. - Weight::from_parts(639_174_493, 0) - // Standard Error: 438_161 - .saturating_add(Weight::from_parts(22_597_406, 0).saturating_mul(r.into())) + // Minimum execution time: 537_632 nanoseconds. + Weight::from_parts(657_899_518, 0) + // Standard Error: 481_203 + .saturating_add(Weight::from_parts(22_629_133, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1399,10 +1395,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1387 + r * (3140 ±0)` // Estimated: `0` - // Minimum execution time: 542_231 nanoseconds. - Weight::from_parts(726_365_773, 0) - // Standard Error: 500_528 - .saturating_add(Weight::from_parts(32_524_637, 0).saturating_mul(r.into())) + // Minimum execution time: 539_212 nanoseconds. + Weight::from_parts(734_683_366, 0) + // Standard Error: 499_077 + .saturating_add(Weight::from_parts(34_230_055, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1423,10 +1419,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `692 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 537_261 nanoseconds. - Weight::from_parts(639_006_806, 0) - // Standard Error: 1_058_530 - .saturating_add(Weight::from_parts(23_119_410, 0).saturating_mul(r.into())) + // Minimum execution time: 542_631 nanoseconds. + Weight::from_parts(668_665_110, 0) + // Standard Error: 482_581 + .saturating_add(Weight::from_parts(18_074_689, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -1435,519 +1431,519 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_580 nanoseconds. - Weight::from_parts(2_443_311, 0) - // Standard Error: 6_313 - .saturating_add(Weight::from_parts(603_267, 0).saturating_mul(r.into())) + // Minimum execution time: 1_630 nanoseconds. + Weight::from_parts(2_955_102, 0) + // Standard Error: 6_860 + .saturating_add(Weight::from_parts(592_425, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_820 nanoseconds. - Weight::from_parts(8_316_285, 0) - // Standard Error: 31_119 - .saturating_add(Weight::from_parts(1_944_211, 0).saturating_mul(r.into())) + // Minimum execution time: 1_730 nanoseconds. + Weight::from_parts(10_644_917, 0) + // Standard Error: 25_684 + .saturating_add(Weight::from_parts(1_679_787, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_820 nanoseconds. - Weight::from_parts(8_651_501, 0) - // Standard Error: 18_486 - .saturating_add(Weight::from_parts(1_657_320, 0).saturating_mul(r.into())) + // Minimum execution time: 1_880 nanoseconds. + Weight::from_parts(6_059_713, 0) + // Standard Error: 29_527 + .saturating_add(Weight::from_parts(1_743_677, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(2_360_904, 0) - // Standard Error: 19_617 - .saturating_add(Weight::from_parts(1_653_653, 0).saturating_mul(r.into())) + // Minimum execution time: 1_600 nanoseconds. + Weight::from_parts(2_399_198, 0) + // Standard Error: 13_033 + .saturating_add(Weight::from_parts(1_580_765, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_410 nanoseconds. - Weight::from_parts(194_485, 0) - // Standard Error: 21_482 - .saturating_add(Weight::from_parts(2_898_697, 0).saturating_mul(r.into())) + // Minimum execution time: 1_390 nanoseconds. + Weight::from_parts(62_941, 0) + // Standard Error: 21_738 + .saturating_add(Weight::from_parts(2_766_819, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530 nanoseconds. - Weight::from_parts(2_168_789, 0) - // Standard Error: 7_963 - .saturating_add(Weight::from_parts(940_840, 0).saturating_mul(r.into())) + // Minimum execution time: 1_640 nanoseconds. + Weight::from_parts(2_534_460, 0) + // Standard Error: 8_493 + .saturating_add(Weight::from_parts(926_397, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_370 nanoseconds. - Weight::from_parts(1_383_747, 0) - // Standard Error: 9_217 - .saturating_add(Weight::from_parts(1_309_015, 0).saturating_mul(r.into())) + // Minimum execution time: 1_630 nanoseconds. + Weight::from_parts(1_997_345, 0) + // Standard Error: 11_639 + .saturating_add(Weight::from_parts(1_327_716, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_360 nanoseconds. - Weight::from_parts(1_684_707, 0) - // Standard Error: 13_009 - .saturating_add(Weight::from_parts(1_703_750, 0).saturating_mul(r.into())) + // Minimum execution time: 1_500 nanoseconds. + Weight::from_parts(1_603_143, 0) + // Standard Error: 11_826 + .saturating_add(Weight::from_parts(1_679_173, 0).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_360 nanoseconds. - Weight::from_parts(6_277_009, 0) - // Standard Error: 480 - .saturating_add(Weight::from_parts(9_377, 0).saturating_mul(e.into())) + // Minimum execution time: 5_740 nanoseconds. + Weight::from_parts(6_848_621, 0) + // Standard Error: 345 + .saturating_add(Weight::from_parts(5_503, 0).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_510 nanoseconds. - Weight::from_parts(13_032_556, 0) - // Standard Error: 48_362 - .saturating_add(Weight::from_parts(4_516_132, 0).saturating_mul(r.into())) + // Minimum execution time: 1_460 nanoseconds. + Weight::from_parts(7_424_094, 0) + // Standard Error: 34_669 + .saturating_add(Weight::from_parts(4_915_951, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600 nanoseconds. - Weight::from_parts(7_753_942, 0) - // Standard Error: 46_845 - .saturating_add(Weight::from_parts(5_489_614, 0).saturating_mul(r.into())) + // Minimum execution time: 1_850 nanoseconds. + Weight::from_parts(6_753_900, 0) + // Standard Error: 48_303 + .saturating_add(Weight::from_parts(5_582_232, 0).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_470 nanoseconds. - Weight::from_parts(12_027_580, 0) - // Standard Error: 3_145 - .saturating_add(Weight::from_parts(279_413, 0).saturating_mul(p.into())) + // Minimum execution time: 7_630 nanoseconds. + Weight::from_parts(11_477_522, 0) + // Standard Error: 3_997 + .saturating_add(Weight::from_parts(324_801, 0).saturating_mul(p.into())) } /// The range of component `l` is `[0, 1024]`. fn instr_call_per_local(l: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_991 nanoseconds. - Weight::from_parts(18_311_842, 0) - // Standard Error: 2_054 - .saturating_add(Weight::from_parts(157_440, 0).saturating_mul(l.into())) + // Minimum execution time: 6_320 nanoseconds. + Weight::from_parts(13_022_522, 0) + // Standard Error: 2_869 + .saturating_add(Weight::from_parts(171_599, 0).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250 nanoseconds. - Weight::from_parts(6_900_572, 0) - // Standard Error: 7_652 - .saturating_add(Weight::from_parts(712_909, 0).saturating_mul(r.into())) + // Minimum execution time: 5_500 nanoseconds. + Weight::from_parts(7_235_171, 0) + // Standard Error: 7_322 + .saturating_add(Weight::from_parts(722_209, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_470 nanoseconds. - Weight::from_parts(5_728_865, 0) - // Standard Error: 8_503 - .saturating_add(Weight::from_parts(1_063_278, 0).saturating_mul(r.into())) + // Minimum execution time: 5_460 nanoseconds. + Weight::from_parts(6_543_158, 0) + // Standard Error: 9_723 + .saturating_add(Weight::from_parts(1_075_029, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_300 nanoseconds. - Weight::from_parts(5_252_543, 0) - // Standard Error: 12_599 - .saturating_add(Weight::from_parts(1_366_745, 0).saturating_mul(r.into())) + // Minimum execution time: 4_660 nanoseconds. + Weight::from_parts(6_543_532, 0) + // Standard Error: 10_775 + .saturating_add(Weight::from_parts(1_286_049, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530 nanoseconds. - Weight::from_parts(3_548_668, 0) - // Standard Error: 11_522 - .saturating_add(Weight::from_parts(1_367_025, 0).saturating_mul(r.into())) + // Minimum execution time: 1_740 nanoseconds. + Weight::from_parts(5_132_745, 0) + // Standard Error: 11_822 + .saturating_add(Weight::from_parts(1_335_886, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_570 nanoseconds. - Weight::from_parts(1_658_922, 0) - // Standard Error: 21_156 - .saturating_add(Weight::from_parts(1_876_767, 0).saturating_mul(r.into())) + // Minimum execution time: 1_550 nanoseconds. + Weight::from_parts(2_308_029, 0) + // Standard Error: 13_767 + .saturating_add(Weight::from_parts(1_812_020, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_610 nanoseconds. - Weight::from_parts(1_759_828, 0) - // Standard Error: 12_254 - .saturating_add(Weight::from_parts(1_348_159, 0).saturating_mul(r.into())) + // Minimum execution time: 1_940 nanoseconds. + Weight::from_parts(1_188_446, 0) + // Standard Error: 17_898 + .saturating_add(Weight::from_parts(1_367_101, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530 nanoseconds. - Weight::from_parts(1_875_416, 0) - // Standard Error: 1_400_829 - .saturating_add(Weight::from_parts(372_521_183, 0).saturating_mul(r.into())) + // Minimum execution time: 1_620 nanoseconds. + Weight::from_parts(2_001_616, 0) + // Standard Error: 1_874_724 + .saturating_add(Weight::from_parts(405_491_583, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530 nanoseconds. - Weight::from_parts(2_705_490, 0) - // Standard Error: 7_228 - .saturating_add(Weight::from_parts(850_311, 0).saturating_mul(r.into())) + // Minimum execution time: 1_600 nanoseconds. + Weight::from_parts(3_615_630, 0) + // Standard Error: 8_138 + .saturating_add(Weight::from_parts(861_404, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_370 nanoseconds. - Weight::from_parts(2_930_080, 0) - // Standard Error: 9_617 - .saturating_add(Weight::from_parts(862_253, 0).saturating_mul(r.into())) + // Minimum execution time: 1_440 nanoseconds. + Weight::from_parts(2_724_879, 0) + // Standard Error: 8_882 + .saturating_add(Weight::from_parts(865_619, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_380 nanoseconds. - Weight::from_parts(2_687_818, 0) - // Standard Error: 9_474 - .saturating_add(Weight::from_parts(878_607, 0).saturating_mul(r.into())) + // Minimum execution time: 1_770 nanoseconds. + Weight::from_parts(3_299_778, 0) + // Standard Error: 9_856 + .saturating_add(Weight::from_parts(875_967, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_410 nanoseconds. - Weight::from_parts(2_600_835, 0) - // Standard Error: 8_146 - .saturating_add(Weight::from_parts(882_563, 0).saturating_mul(r.into())) + // Minimum execution time: 1_650 nanoseconds. + Weight::from_parts(2_361_703, 0) + // Standard Error: 12_413 + .saturating_add(Weight::from_parts(905_102, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_400 nanoseconds. - Weight::from_parts(2_413_368, 0) - // Standard Error: 7_762 - .saturating_add(Weight::from_parts(877_860, 0).saturating_mul(r.into())) + // Minimum execution time: 1_450 nanoseconds. + Weight::from_parts(2_740_861, 0) + // Standard Error: 8_377 + .saturating_add(Weight::from_parts(935_400, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_540 nanoseconds. - Weight::from_parts(3_016_168, 0) - // Standard Error: 9_915 - .saturating_add(Weight::from_parts(858_278, 0).saturating_mul(r.into())) + // Minimum execution time: 1_510 nanoseconds. + Weight::from_parts(4_248_233, 0) + // Standard Error: 8_536 + .saturating_add(Weight::from_parts(880_675, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530 nanoseconds. - Weight::from_parts(3_143_407, 0) - // Standard Error: 12_258 - .saturating_add(Weight::from_parts(873_497, 0).saturating_mul(r.into())) + // Minimum execution time: 1_450 nanoseconds. + Weight::from_parts(2_308_635, 0) + // Standard Error: 11_590 + .saturating_add(Weight::from_parts(926_551, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_400 nanoseconds. - Weight::from_parts(2_854_402, 0) - // Standard Error: 15_623 - .saturating_add(Weight::from_parts(1_217_855, 0).saturating_mul(r.into())) + // Minimum execution time: 1_600 nanoseconds. + Weight::from_parts(3_635_193, 0) + // Standard Error: 13_678 + .saturating_add(Weight::from_parts(1_135_022, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_580 nanoseconds. - Weight::from_parts(2_503_273, 0) - // Standard Error: 11_348 - .saturating_add(Weight::from_parts(1_200_185, 0).saturating_mul(r.into())) + // Minimum execution time: 1_440 nanoseconds. + Weight::from_parts(4_333_685, 0) + // Standard Error: 11_208 + .saturating_add(Weight::from_parts(1_117_647, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_470 nanoseconds. - Weight::from_parts(2_729_405, 0) - // Standard Error: 10_074 - .saturating_add(Weight::from_parts(1_164_450, 0).saturating_mul(r.into())) + // Minimum execution time: 1_420 nanoseconds. + Weight::from_parts(3_017_200, 0) + // Standard Error: 14_098 + .saturating_add(Weight::from_parts(1_156_701, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550 nanoseconds. - Weight::from_parts(2_179_957, 0) - // Standard Error: 10_417 - .saturating_add(Weight::from_parts(1_186_362, 0).saturating_mul(r.into())) + // Minimum execution time: 1_470 nanoseconds. + Weight::from_parts(2_813_602, 0) + // Standard Error: 14_605 + .saturating_add(Weight::from_parts(1_187_315, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_360 nanoseconds. - Weight::from_parts(2_615_244, 0) - // Standard Error: 10_618 - .saturating_add(Weight::from_parts(1_169_329, 0).saturating_mul(r.into())) + // Minimum execution time: 1_410 nanoseconds. + Weight::from_parts(4_405_895, 0) + // Standard Error: 14_237 + .saturating_add(Weight::from_parts(1_136_192, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_490 nanoseconds. - Weight::from_parts(2_971_659, 0) - // Standard Error: 15_246 - .saturating_add(Weight::from_parts(1_176_602, 0).saturating_mul(r.into())) + // Minimum execution time: 1_580 nanoseconds. + Weight::from_parts(4_529_323, 0) + // Standard Error: 12_311 + .saturating_add(Weight::from_parts(1_109_286, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_630 nanoseconds. - Weight::from_parts(2_791_256, 0) - // Standard Error: 14_364 - .saturating_add(Weight::from_parts(1_184_329, 0).saturating_mul(r.into())) + // Minimum execution time: 1_400 nanoseconds. + Weight::from_parts(2_160_653, 0) + // Standard Error: 12_773 + .saturating_add(Weight::from_parts(1_177_547, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_380 nanoseconds. - Weight::from_parts(4_049_989, 0) - // Standard Error: 13_210 - .saturating_add(Weight::from_parts(1_178_694, 0).saturating_mul(r.into())) + // Minimum execution time: 1_570 nanoseconds. + Weight::from_parts(2_052_661, 0) + // Standard Error: 14_481 + .saturating_add(Weight::from_parts(1_179_417, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_540 nanoseconds. - Weight::from_parts(3_307_499, 0) - // Standard Error: 11_871 - .saturating_add(Weight::from_parts(1_139_042, 0).saturating_mul(r.into())) + // Minimum execution time: 1_640 nanoseconds. + Weight::from_parts(3_584_030, 0) + // Standard Error: 13_594 + .saturating_add(Weight::from_parts(1_135_684, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_360 nanoseconds. - Weight::from_parts(2_896_896, 0) - // Standard Error: 12_827 - .saturating_add(Weight::from_parts(1_182_536, 0).saturating_mul(r.into())) + // Minimum execution time: 1_440 nanoseconds. + Weight::from_parts(3_398_760, 0) + // Standard Error: 9_956 + .saturating_add(Weight::from_parts(1_136_977, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_480 nanoseconds. - Weight::from_parts(2_560_901, 0) - // Standard Error: 13_888 - .saturating_add(Weight::from_parts(1_163_989, 0).saturating_mul(r.into())) + // Minimum execution time: 1_460 nanoseconds. + Weight::from_parts(2_489_958, 0) + // Standard Error: 10_550 + .saturating_add(Weight::from_parts(1_148_385, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_370 nanoseconds. - Weight::from_parts(2_186_227, 0) - // Standard Error: 12_707 - .saturating_add(Weight::from_parts(1_155_905, 0).saturating_mul(r.into())) + // Minimum execution time: 1_470 nanoseconds. + Weight::from_parts(2_184_614, 0) + // Standard Error: 13_847 + .saturating_add(Weight::from_parts(1_158_666, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_520 nanoseconds. - Weight::from_parts(2_921_367, 0) - // Standard Error: 10_273 - .saturating_add(Weight::from_parts(1_158_241, 0).saturating_mul(r.into())) + // Minimum execution time: 1_620 nanoseconds. + Weight::from_parts(3_821_258, 0) + // Standard Error: 14_517 + .saturating_add(Weight::from_parts(1_151_944, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_400 nanoseconds. - Weight::from_parts(2_368_358, 0) - // Standard Error: 10_082 - .saturating_add(Weight::from_parts(1_299_003, 0).saturating_mul(r.into())) + // Minimum execution time: 1_640 nanoseconds. + Weight::from_parts(4_219_927, 0) + // Standard Error: 14_360 + .saturating_add(Weight::from_parts(1_269_226, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_380 nanoseconds. - Weight::from_parts(2_382_023, 0) - // Standard Error: 10_781 - .saturating_add(Weight::from_parts(1_215_481, 0).saturating_mul(r.into())) + // Minimum execution time: 1_610 nanoseconds. + Weight::from_parts(4_103_772, 0) + // Standard Error: 13_814 + .saturating_add(Weight::from_parts(1_166_162, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_380 nanoseconds. - Weight::from_parts(2_581_719, 0) - // Standard Error: 15_661 - .saturating_add(Weight::from_parts(1_378_443, 0).saturating_mul(r.into())) + // Minimum execution time: 1_440 nanoseconds. + Weight::from_parts(2_837_444, 0) + // Standard Error: 11_492 + .saturating_add(Weight::from_parts(1_353_230, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_900 nanoseconds. - Weight::from_parts(1_602_690, 0) - // Standard Error: 17_371 - .saturating_add(Weight::from_parts(1_321_186, 0).saturating_mul(r.into())) + // Minimum execution time: 1_390 nanoseconds. + Weight::from_parts(2_324_540, 0) + // Standard Error: 12_241 + .saturating_add(Weight::from_parts(1_260_290, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_390 nanoseconds. - Weight::from_parts(3_176_503, 0) - // Standard Error: 13_539 - .saturating_add(Weight::from_parts(1_186_142, 0).saturating_mul(r.into())) + // Minimum execution time: 1_410 nanoseconds. + Weight::from_parts(3_622_678, 0) + // Standard Error: 11_988 + .saturating_add(Weight::from_parts(1_108_472, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_380 nanoseconds. - Weight::from_parts(2_039_473, 0) - // Standard Error: 11_923 - .saturating_add(Weight::from_parts(1_224_869, 0).saturating_mul(r.into())) + // Minimum execution time: 1_370 nanoseconds. + Weight::from_parts(1_528_294, 0) + // Standard Error: 13_141 + .saturating_add(Weight::from_parts(1_216_523, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_420 nanoseconds. - Weight::from_parts(1_641_181, 0) - // Standard Error: 12_267 - .saturating_add(Weight::from_parts(1_208_173, 0).saturating_mul(r.into())) + // Minimum execution time: 1_560 nanoseconds. + Weight::from_parts(3_488_953, 0) + // Standard Error: 10_562 + .saturating_add(Weight::from_parts(1_128_100, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_360 nanoseconds. - Weight::from_parts(2_632_227, 0) - // Standard Error: 12_418 - .saturating_add(Weight::from_parts(1_162_257, 0).saturating_mul(r.into())) + // Minimum execution time: 1_510 nanoseconds. + Weight::from_parts(3_112_301, 0) + // Standard Error: 12_563 + .saturating_add(Weight::from_parts(1_158_825, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_540 nanoseconds. - Weight::from_parts(2_614_080, 0) - // Standard Error: 13_070 - .saturating_add(Weight::from_parts(1_162_537, 0).saturating_mul(r.into())) + // Minimum execution time: 1_440 nanoseconds. + Weight::from_parts(2_421_867, 0) + // Standard Error: 10_727 + .saturating_add(Weight::from_parts(1_162_108, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_510 nanoseconds. - Weight::from_parts(2_699_148, 0) - // Standard Error: 13_024 - .saturating_add(Weight::from_parts(1_141_654, 0).saturating_mul(r.into())) + // Minimum execution time: 1_470 nanoseconds. + Weight::from_parts(3_888_383, 0) + // Standard Error: 12_429 + .saturating_add(Weight::from_parts(1_115_583, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_370 nanoseconds. - Weight::from_parts(3_021_931, 0) - // Standard Error: 13_442 - .saturating_add(Weight::from_parts(1_142_088, 0).saturating_mul(r.into())) + // Minimum execution time: 1_410 nanoseconds. + Weight::from_parts(2_561_771, 0) + // Standard Error: 10_053 + .saturating_add(Weight::from_parts(1_161_925, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_510 nanoseconds. - Weight::from_parts(2_912_589, 0) - // Standard Error: 11_442 - .saturating_add(Weight::from_parts(1_180_228, 0).saturating_mul(r.into())) + // Minimum execution time: 1_420 nanoseconds. + Weight::from_parts(3_075_199, 0) + // Standard Error: 12_838 + .saturating_add(Weight::from_parts(1_171_290, 0).saturating_mul(r.into())) } } diff --git a/runtime/common/src/weights/pallet_democracy.rs b/runtime/common/src/weights/pallet_democracy.rs index 17b87abdf..bcaab3f85 100644 --- a/runtime/common/src/weights/pallet_democracy.rs +++ b/runtime/common/src/weights/pallet_democracy.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -64,8 +64,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `4835` // Estimated: `23413` - // Minimum execution time: 52_690 nanoseconds. - Weight::from_parts(69_500_000, 23413) + // Minimum execution time: 51_540 nanoseconds. + Weight::from_parts(57_860_000, 23413) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3591` // Estimated: `5705` - // Minimum execution time: 45_320 nanoseconds. - Weight::from_parts(51_990_000, 5705) + // Minimum execution time: 43_800 nanoseconds. + Weight::from_parts(46_960_000, 5705) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -90,8 +90,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3500` // Estimated: `12732` - // Minimum execution time: 60_661 nanoseconds. - Weight::from_parts(66_820_000, 12732) + // Minimum execution time: 61_480 nanoseconds. + Weight::from_parts(69_830_000, 12732) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -105,8 +105,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3522` // Estimated: `12732` - // Minimum execution time: 60_240 nanoseconds. - Weight::from_parts(76_170_000, 12732) + // Minimum execution time: 60_080 nanoseconds. + Weight::from_parts(78_800_000, 12732) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -118,8 +118,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `295` // Estimated: `5192` - // Minimum execution time: 29_900 nanoseconds. - Weight::from_parts(30_530_000, 5192) + // Minimum execution time: 29_690 nanoseconds. + Weight::from_parts(35_980_000, 5192) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -139,8 +139,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `6251` // Estimated: `31427` - // Minimum execution time: 117_140 nanoseconds. - Weight::from_parts(137_421_000, 31427) + // Minimum execution time: 115_081 nanoseconds. + Weight::from_parts(132_430_000, 31427) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -152,8 +152,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3419` // Estimated: `6344` - // Minimum execution time: 23_770 nanoseconds. - Weight::from_parts(26_360_000, 6344) + // Minimum execution time: 20_001 nanoseconds. + Weight::from_parts(20_720_000, 6344) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -163,8 +163,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_370 nanoseconds. - Weight::from_parts(6_320_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_560 nanoseconds. + Weight::from_parts(6_880_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy NextExternal (r:0 w:1) /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) @@ -172,8 +172,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_480 nanoseconds. - Weight::from_parts(6_660_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_450 nanoseconds. + Weight::from_parts(6_790_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy NextExternal (r:1 w:1) /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) @@ -185,8 +185,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `179` // Estimated: `1126` - // Minimum execution time: 28_320 nanoseconds. - Weight::from_parts(28_990_000, 1126) + // Minimum execution time: 28_470 nanoseconds. + Weight::from_parts(34_400_000, 1126) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -198,8 +198,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3448` // Estimated: `6344` - // Minimum execution time: 32_350 nanoseconds. - Weight::from_parts(40_660_000, 6344) + // Minimum execution time: 35_710 nanoseconds. + Weight::from_parts(36_730_000, 6344) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -213,8 +213,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `6122` // Estimated: `28116` - // Minimum execution time: 98_910 nanoseconds. - Weight::from_parts(110_540_000, 28116) + // Minimum execution time: 94_120 nanoseconds. + Weight::from_parts(110_771_000, 28116) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -224,8 +224,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_880 nanoseconds. - Weight::from_parts(13_320_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 12_860 nanoseconds. + Weight::from_parts(13_300_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy LowestUnbaked (r:1 w:1) /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -238,10 +238,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `174 + r * (125 ±0)` // Estimated: `998 + r * (2684 ±0)` - // Minimum execution time: 13_410 nanoseconds. - Weight::from_parts(23_309_206, 998) - // Standard Error: 74_188 - .saturating_add(Weight::from_parts(4_814_618, 0).saturating_mul(r.into())) + // Minimum execution time: 12_920 nanoseconds. + Weight::from_parts(23_702_780, 998) + // Standard Error: 46_221 + .saturating_add(Weight::from_parts(4_492_691, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -264,10 +264,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `174 + r * (125 ±0)` // Estimated: `19318 + r * (2684 ±0)` - // Minimum execution time: 18_940 nanoseconds. - Weight::from_parts(25_529_183, 19318) - // Standard Error: 44_140 - .saturating_add(Weight::from_parts(4_676_383, 0).saturating_mul(r.into())) + // Minimum execution time: 19_170 nanoseconds. + Weight::from_parts(30_529_069, 19318) + // Standard Error: 66_363 + .saturating_add(Weight::from_parts(4_703_710, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -284,10 +284,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `897 + r * (147 ±0)` // Estimated: `22596 + r * (2684 ±0)` - // Minimum execution time: 57_420 nanoseconds. - Weight::from_parts(77_659_104, 22596) - // Standard Error: 54_298 - .saturating_add(Weight::from_parts(6_088_195, 0).saturating_mul(r.into())) + // Minimum execution time: 56_800 nanoseconds. + Weight::from_parts(74_850_031, 22596) + // Standard Error: 58_350 + .saturating_add(Weight::from_parts(5_921_222, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -303,10 +303,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `522 + r * (147 ±0)` // Estimated: `12548 + r * (2684 ±0)` - // Minimum execution time: 29_781 nanoseconds. - Weight::from_parts(47_236_269, 12548) - // Standard Error: 62_245 - .saturating_add(Weight::from_parts(6_041_250, 0).saturating_mul(r.into())) + // Minimum execution time: 33_660 nanoseconds. + Weight::from_parts(54_095_840, 12548) + // Standard Error: 79_513 + .saturating_add(Weight::from_parts(5_832_475, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -319,8 +319,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_300 nanoseconds. - Weight::from_parts(5_420_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_120 nanoseconds. + Weight::from_parts(5_630_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy VotingOf (r:1 w:1) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) @@ -329,12 +329,14 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn unlock_remove(_r: u32) -> Weight { + fn unlock_remove(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `554` // Estimated: `12655` - // Minimum execution time: 31_790 nanoseconds. - Weight::from_parts(44_745_537, 12655) + // Minimum execution time: 31_930 nanoseconds. + Weight::from_parts(43_131_431, 12655) + // Standard Error: 8_315 + .saturating_add(Weight::from_parts(5_162, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -349,10 +351,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `555 + r * (22 ±0)` // Estimated: `12655` - // Minimum execution time: 34_520 nanoseconds. - Weight::from_parts(40_974_401, 12655) - // Standard Error: 10_005 - .saturating_add(Weight::from_parts(88_759, 0).saturating_mul(r.into())) + // Minimum execution time: 35_201 nanoseconds. + Weight::from_parts(40_141_301, 12655) + // Standard Error: 8_747 + .saturating_add(Weight::from_parts(96_832, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -365,10 +367,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `760 + r * (26 ±0)` // Estimated: `8958` - // Minimum execution time: 25_680 nanoseconds. - Weight::from_parts(33_394_344, 8958) - // Standard Error: 8_826 - .saturating_add(Weight::from_parts(103_660, 0).saturating_mul(r.into())) + // Minimum execution time: 23_131 nanoseconds. + Weight::from_parts(29_833_309, 8958) + // Standard Error: 5_916 + .saturating_add(Weight::from_parts(124_672, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -381,10 +383,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `760 + r * (26 ±0)` // Estimated: `8958` - // Minimum execution time: 23_900 nanoseconds. - Weight::from_parts(36_794_123, 8958) - // Standard Error: 7_646 - .saturating_add(Weight::from_parts(81_950, 0).saturating_mul(r.into())) + // Minimum execution time: 26_630 nanoseconds. + Weight::from_parts(32_266_893, 8958) + // Standard Error: 5_138 + .saturating_add(Weight::from_parts(90_624, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_identity.rs b/runtime/common/src/weights/pallet_identity.rs index 59f56ee5a..627d9add2 100644 --- a/runtime/common/src/weights/pallet_identity.rs +++ b/runtime/common/src/weights/pallet_identity.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -59,10 +59,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `64 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 17_610 nanoseconds. - Weight::from_parts(21_350_382, 952) - // Standard Error: 44_356 - .saturating_add(Weight::from_parts(343_656, 0).saturating_mul(r.into())) + // Minimum execution time: 19_230 nanoseconds. + Weight::from_parts(22_092_935, 952) + // Standard Error: 60_132 + .saturating_add(Weight::from_parts(298_384, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -74,10 +74,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `474 + r * (5 ±0)` // Estimated: `7313` - // Minimum execution time: 27_770 nanoseconds. - Weight::from_parts(44_700_966, 7313) - // Standard Error: 13_278 - .saturating_add(Weight::from_parts(681_563, 0).saturating_mul(x.into())) + // Minimum execution time: 25_480 nanoseconds. + Weight::from_parts(45_732_528, 7313) + // Standard Error: 11_669 + .saturating_add(Weight::from_parts(623_699, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -92,10 +92,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `101` // Estimated: `11894 + s * (2589 ±0)` - // Minimum execution time: 13_780 nanoseconds. - Weight::from_parts(33_310_670, 11894) - // Standard Error: 65_194 - .saturating_add(Weight::from_parts(4_921_454, 0).saturating_mul(s.into())) + // Minimum execution time: 12_700 nanoseconds. + Weight::from_parts(36_155_572, 11894) + // Standard Error: 48_562 + .saturating_add(Weight::from_parts(4_605_038, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(s.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -113,10 +113,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `226 + p * (32 ±0)` // Estimated: `11894` - // Minimum execution time: 13_680 nanoseconds. - Weight::from_parts(38_465_682, 11894) - // Standard Error: 26_252 - .saturating_add(Weight::from_parts(1_822_648, 0).saturating_mul(p.into())) + // Minimum execution time: 13_930 nanoseconds. + Weight::from_parts(34_268_734, 11894) + // Standard Error: 33_963 + .saturating_add(Weight::from_parts(1_902_476, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) @@ -130,18 +130,16 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// The range of component `r` is `[1, 8]`. /// The range of component `s` is `[0, 64]`. /// The range of component `x` is `[0, 64]`. - fn clear_identity(r: u32, s: u32, x: u32) -> Weight { + fn clear_identity(_r: u32, s: u32, x: u32) -> Weight { // Proof Size summary in bytes: // Measured: `535 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` // Estimated: `11894` - // Minimum execution time: 52_430 nanoseconds. - Weight::from_parts(36_908_427, 11894) - // Standard Error: 248_441 - .saturating_add(Weight::from_parts(884_272, 0).saturating_mul(r.into())) - // Standard Error: 28_929 - .saturating_add(Weight::from_parts(1_841_528, 0).saturating_mul(s.into())) - // Standard Error: 28_929 - .saturating_add(Weight::from_parts(362_073, 0).saturating_mul(x.into())) + // Minimum execution time: 52_030 nanoseconds. + Weight::from_parts(52_304_738, 11894) + // Standard Error: 27_639 + .saturating_add(Weight::from_parts(1_796_839, 0).saturating_mul(s.into())) + // Standard Error: 27_639 + .saturating_add(Weight::from_parts(280_343, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -156,12 +154,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `431 + r * (57 ±0) + x * (66 ±0)` // Estimated: `8265` - // Minimum execution time: 40_920 nanoseconds. - Weight::from_parts(43_816_841, 8265) - // Standard Error: 135_906 - .saturating_add(Weight::from_parts(557_377, 0).saturating_mul(r.into())) - // Standard Error: 15_786 - .saturating_add(Weight::from_parts(688_806, 0).saturating_mul(x.into())) + // Minimum execution time: 38_201 nanoseconds. + Weight::from_parts(40_514_631, 8265) + // Standard Error: 109_831 + .saturating_add(Weight::from_parts(521_267, 0).saturating_mul(r.into())) + // Standard Error: 12_757 + .saturating_add(Weight::from_parts(723_546, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -169,16 +167,14 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Proof: Identity IdentityOf (max_values: None, max_size: Some(4838), added: 7313, mode: MaxEncodedLen) /// The range of component `r` is `[1, 8]`. /// The range of component `x` is `[0, 64]`. - fn cancel_request(r: u32, x: u32) -> Weight { + fn cancel_request(_r: u32, x: u32) -> Weight { // Proof Size summary in bytes: // Measured: `430 + x * (66 ±0)` // Estimated: `7313` - // Minimum execution time: 35_980 nanoseconds. - Weight::from_parts(36_749_200, 7313) - // Standard Error: 138_120 - .saturating_add(Weight::from_parts(586_787, 0).saturating_mul(r.into())) - // Standard Error: 16_043 - .saturating_add(Weight::from_parts(768_786, 0).saturating_mul(x.into())) + // Minimum execution time: 36_100 nanoseconds. + Weight::from_parts(46_093_276, 7313) + // Standard Error: 14_527 + .saturating_add(Weight::from_parts(650_226, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -189,10 +185,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 12_370 nanoseconds. - Weight::from_parts(15_237_902, 952) - // Standard Error: 32_737 - .saturating_add(Weight::from_parts(118_042, 0).saturating_mul(r.into())) + // Minimum execution time: 11_600 nanoseconds. + Weight::from_parts(14_311_547, 952) + // Standard Error: 30_165 + .saturating_add(Weight::from_parts(312_857, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -203,10 +199,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 10_850 nanoseconds. - Weight::from_parts(12_698_527, 952) - // Standard Error: 25_788 - .saturating_add(Weight::from_parts(198_654, 0).saturating_mul(r.into())) + // Minimum execution time: 11_420 nanoseconds. + Weight::from_parts(13_852_062, 952) + // Standard Error: 41_801 + .saturating_add(Weight::from_parts(58_707, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -217,10 +213,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 10_450 nanoseconds. - Weight::from_parts(12_866_798, 952) - // Standard Error: 25_985 - .saturating_add(Weight::from_parts(181_753, 0).saturating_mul(r.into())) + // Minimum execution time: 10_180 nanoseconds. + Weight::from_parts(12_701_169, 952) + // Standard Error: 25_078 + .saturating_add(Weight::from_parts(66_396, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -230,14 +226,16 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Proof: Identity IdentityOf (max_values: None, max_size: Some(4838), added: 7313, mode: MaxEncodedLen) /// The range of component `r` is `[1, 7]`. /// The range of component `x` is `[0, 64]`. - fn provide_judgement(_r: u32, x: u32) -> Weight { + fn provide_judgement(r: u32, x: u32) -> Weight { // Proof Size summary in bytes: // Measured: `509 + r * (57 ±0) + x * (66 ±0)` // Estimated: `8265` - // Minimum execution time: 31_990 nanoseconds. - Weight::from_parts(37_587_960, 8265) - // Standard Error: 16_939 - .saturating_add(Weight::from_parts(1_118_824, 0).saturating_mul(x.into())) + // Minimum execution time: 28_990 nanoseconds. + Weight::from_parts(31_391_358, 8265) + // Standard Error: 166_351 + .saturating_add(Weight::from_parts(536_354, 0).saturating_mul(r.into())) + // Standard Error: 17_000 + .saturating_add(Weight::from_parts(1_040_806, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -256,12 +254,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `954 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` // Estimated: `17108` - // Minimum execution time: 74_930 nanoseconds. - Weight::from_parts(88_168_965, 17108) - // Standard Error: 33_257 - .saturating_add(Weight::from_parts(1_755_177, 0).saturating_mul(s.into())) - // Standard Error: 33_257 - .saturating_add(Weight::from_parts(307_613, 0).saturating_mul(x.into())) + // Minimum execution time: 75_361 nanoseconds. + Weight::from_parts(82_306_973, 17108) + // Standard Error: 33_223 + .saturating_add(Weight::from_parts(1_805_205, 0).saturating_mul(s.into())) + // Standard Error: 33_223 + .saturating_add(Weight::from_parts(242_228, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -277,10 +275,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `355 + s * (41 ±0)` // Estimated: `14483` - // Minimum execution time: 37_950 nanoseconds. - Weight::from_parts(49_565_683, 14483) - // Standard Error: 13_413 - .saturating_add(Weight::from_parts(136_156, 0).saturating_mul(s.into())) + // Minimum execution time: 38_960 nanoseconds. + Weight::from_parts(49_299_144, 14483) + // Standard Error: 10_280 + .saturating_add(Weight::from_parts(66_362, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -293,10 +291,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `464 + s * (7 ±0)` // Estimated: `9902` - // Minimum execution time: 19_230 nanoseconds. - Weight::from_parts(24_941_270, 9902) - // Standard Error: 6_110 - .saturating_add(Weight::from_parts(1_832, 0).saturating_mul(s.into())) + // Minimum execution time: 19_540 nanoseconds. + Weight::from_parts(22_842_434, 9902) + // Standard Error: 8_528 + .saturating_add(Weight::from_parts(62_406, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -311,10 +309,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `544 + s * (39 ±0)` // Estimated: `14483` - // Minimum execution time: 39_900 nanoseconds. - Weight::from_parts(46_984_850, 14483) - // Standard Error: 11_417 - .saturating_add(Weight::from_parts(153_327, 0).saturating_mul(s.into())) + // Minimum execution time: 44_010 nanoseconds. + Weight::from_parts(50_338_094, 14483) + // Standard Error: 11_434 + .saturating_add(Weight::from_parts(72_877, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -327,10 +325,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `469 + s * (42 ±0)` // Estimated: `7170` - // Minimum execution time: 30_560 nanoseconds. - Weight::from_parts(39_702_408, 7170) - // Standard Error: 14_042 - .saturating_add(Weight::from_parts(21_188, 0).saturating_mul(s.into())) + // Minimum execution time: 29_920 nanoseconds. + Weight::from_parts(35_959_708, 7170) + // Standard Error: 7_561 + .saturating_add(Weight::from_parts(81_229, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_membership.rs b/runtime/common/src/weights/pallet_membership.rs index 11b7a0ae0..5dac0f811 100644 --- a/runtime/common/src/weights/pallet_membership.rs +++ b/runtime/common/src/weights/pallet_membership.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -65,10 +65,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `237 + m * (64 ±0)` // Estimated: `4900 + m * (192 ±0)` - // Minimum execution time: 28_540 nanoseconds. - Weight::from_parts(31_571_960, 4900) - // Standard Error: 5_294 - .saturating_add(Weight::from_parts(67_727, 0).saturating_mul(m.into())) + // Minimum execution time: 27_350 nanoseconds. + Weight::from_parts(30_991_548, 4900) + // Standard Error: 4_777 + .saturating_add(Weight::from_parts(67_925, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -88,10 +88,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 32_620 nanoseconds. - Weight::from_parts(36_167_124, 5739) - // Standard Error: 6_458 - .saturating_add(Weight::from_parts(72_252, 0).saturating_mul(m.into())) + // Minimum execution time: 29_760 nanoseconds. + Weight::from_parts(40_730_106, 5739) + // Standard Error: 10_014 + .saturating_add(Weight::from_parts(6_852, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -111,10 +111,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 31_710 nanoseconds. - Weight::from_parts(36_940_570, 5739) - // Standard Error: 6_184 - .saturating_add(Weight::from_parts(88_886, 0).saturating_mul(m.into())) + // Minimum execution time: 33_020 nanoseconds. + Weight::from_parts(35_957_095, 5739) + // Standard Error: 5_726 + .saturating_add(Weight::from_parts(102_602, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -134,10 +134,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 32_290 nanoseconds. - Weight::from_parts(33_529_250, 5739) - // Standard Error: 44_044 - .saturating_add(Weight::from_parts(385_019, 0).saturating_mul(m.into())) + // Minimum execution time: 32_760 nanoseconds. + Weight::from_parts(37_298_323, 5739) + // Standard Error: 6_902 + .saturating_add(Weight::from_parts(247_508, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -157,10 +157,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 34_460 nanoseconds. - Weight::from_parts(37_785_815, 5739) - // Standard Error: 5_395 - .saturating_add(Weight::from_parts(78_047, 0).saturating_mul(m.into())) + // Minimum execution time: 34_380 nanoseconds. + Weight::from_parts(39_553_425, 5739) + // Standard Error: 5_627 + .saturating_add(Weight::from_parts(51_990, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -176,10 +176,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `136 + m * (32 ±0)` // Estimated: `3833 + m * (32 ±0)` - // Minimum execution time: 13_330 nanoseconds. - Weight::from_parts(16_510_704, 3833) - // Standard Error: 2_505 - .saturating_add(Weight::from_parts(32_863, 0).saturating_mul(m.into())) + // Minimum execution time: 14_780 nanoseconds. + Weight::from_parts(16_653_218, 3833) + // Standard Error: 4_995 + .saturating_add(Weight::from_parts(41_193, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) @@ -193,10 +193,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_440 nanoseconds. - Weight::from_parts(6_377_372, 0) - // Standard Error: 851 - .saturating_add(Weight::from_parts(246, 0).saturating_mul(m.into())) + // Minimum execution time: 5_420 nanoseconds. + Weight::from_parts(6_328_054, 0) + // Standard Error: 816 + .saturating_add(Weight::from_parts(2_277, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/runtime/common/src/weights/pallet_multisig.rs b/runtime/common/src/weights/pallet_multisig.rs index f01fd8c8e..6675fc14d 100644 --- a/runtime/common/src/weights/pallet_multisig.rs +++ b/runtime/common/src/weights/pallet_multisig.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,10 +57,10 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 16_870 nanoseconds. - Weight::from_parts(20_386_848, 0) - // Standard Error: 59 - .saturating_add(Weight::from_parts(1_018, 0).saturating_mul(z.into())) + // Minimum execution time: 17_090 nanoseconds. + Weight::from_parts(22_547_170, 0) + // Standard Error: 48 + .saturating_add(Weight::from_parts(769, 0).saturating_mul(z.into())) } /// Storage: Multisig Multisigs (r:1 w:1) /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3350), added: 5825, mode: MaxEncodedLen) @@ -70,12 +70,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `339 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 47_620 nanoseconds. - Weight::from_parts(48_400_987, 5825) - // Standard Error: 10_215 - .saturating_add(Weight::from_parts(101_930, 0).saturating_mul(s.into())) - // Standard Error: 100 - .saturating_add(Weight::from_parts(2_251, 0).saturating_mul(z.into())) + // Minimum execution time: 50_160 nanoseconds. + Weight::from_parts(52_808_971, 5825) + // Standard Error: 10_853 + .saturating_add(Weight::from_parts(48_861, 0).saturating_mul(s.into())) + // Standard Error: 106 + .saturating_add(Weight::from_parts(2_424, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,12 +87,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `283` // Estimated: `5825` - // Minimum execution time: 36_400 nanoseconds. - Weight::from_parts(37_069_062, 5825) - // Standard Error: 10_653 - .saturating_add(Weight::from_parts(75_829, 0).saturating_mul(s.into())) - // Standard Error: 104 - .saturating_add(Weight::from_parts(2_289, 0).saturating_mul(z.into())) + // Minimum execution time: 36_181 nanoseconds. + Weight::from_parts(39_999_128, 5825) + // Standard Error: 8_544 + .saturating_add(Weight::from_parts(15_566, 0).saturating_mul(s.into())) + // Standard Error: 83 + .saturating_add(Weight::from_parts(2_593, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,12 +106,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `425 + s * (33 ±0)` // Estimated: `8432` - // Minimum execution time: 57_181 nanoseconds. - Weight::from_parts(49_742_488, 8432) - // Standard Error: 9_821 - .saturating_add(Weight::from_parts(135_641, 0).saturating_mul(s.into())) - // Standard Error: 96 - .saturating_add(Weight::from_parts(2_897, 0).saturating_mul(z.into())) + // Minimum execution time: 57_120 nanoseconds. + Weight::from_parts(43_350_397, 8432) + // Standard Error: 15_458 + .saturating_add(Weight::from_parts(198_127, 0).saturating_mul(s.into())) + // Standard Error: 151 + .saturating_add(Weight::from_parts(3_362, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -122,10 +122,10 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `343 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 39_380 nanoseconds. - Weight::from_parts(44_478_141, 5825) - // Standard Error: 11_125 - .saturating_add(Weight::from_parts(93_737, 0).saturating_mul(s.into())) + // Minimum execution time: 38_630 nanoseconds. + Weight::from_parts(45_470_511, 5825) + // Standard Error: 13_700 + .saturating_add(Weight::from_parts(97_958, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -136,24 +136,22 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `283` // Estimated: `5825` - // Minimum execution time: 25_370 nanoseconds. - Weight::from_parts(29_768_675, 5825) - // Standard Error: 5_802 - .saturating_add(Weight::from_parts(91_962, 0).saturating_mul(s.into())) + // Minimum execution time: 25_900 nanoseconds. + Weight::from_parts(32_176_176, 5825) + // Standard Error: 5_037 + .saturating_add(Weight::from_parts(66_127, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Multisig Multisigs (r:1 w:1) /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3350), added: 5825, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. - fn cancel_as_multi(s: u32) -> Weight { + fn cancel_as_multi(_s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `491 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 41_141 nanoseconds. - Weight::from_parts(45_843_060, 5825) - // Standard Error: 6_256 - .saturating_add(Weight::from_parts(90_462, 0).saturating_mul(s.into())) + // Minimum execution time: 41_270 nanoseconds. + Weight::from_parts(56_230_114, 5825) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_parachain_staking.rs b/runtime/common/src/weights/pallet_parachain_staking.rs index a998f5c1e..94a68df27 100644 --- a/runtime/common/src/weights/pallet_parachain_staking.rs +++ b/runtime/common/src/weights/pallet_parachain_staking.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_parachain_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `119` // Estimated: `614` - // Minimum execution time: 21_000 nanoseconds. - Weight::from_parts(21_560_000, 614) + // Minimum execution time: 18_800 nanoseconds. + Weight::from_parts(19_820_000, 614) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,8 +69,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `119` // Estimated: `614` - // Minimum execution time: 74_830 nanoseconds. - Weight::from_parts(76_050_000, 614) + // Minimum execution time: 83_421 nanoseconds. + Weight::from_parts(95_940_000, 614) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,8 +80,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `52` // Estimated: `547` - // Minimum execution time: 15_690 nanoseconds. - Weight::from_parts(18_860_000, 547) + // Minimum execution time: 15_710 nanoseconds. + Weight::from_parts(19_280_000, 547) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -91,8 +91,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `52` // Estimated: `547` - // Minimum execution time: 17_800 nanoseconds. - Weight::from_parts(18_990_000, 547) + // Minimum execution time: 15_150 nanoseconds. + Weight::from_parts(19_240_000, 547) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -102,8 +102,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `28` // Estimated: `523` - // Minimum execution time: 16_110 nanoseconds. - Weight::from_parts(20_060_000, 523) + // Minimum execution time: 16_560 nanoseconds. + Weight::from_parts(20_240_000, 523) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -113,8 +113,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `27` // Estimated: `522` - // Minimum execution time: 15_230 nanoseconds. - Weight::from_parts(18_580_000, 522) + // Minimum execution time: 15_630 nanoseconds. + Weight::from_parts(19_030_000, 522) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -126,8 +126,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `147` // Estimated: `1284` - // Minimum execution time: 81_010 nanoseconds. - Weight::from_parts(82_491_000, 1284) + // Minimum execution time: 67_041 nanoseconds. + Weight::from_parts(68_000_000, 1284) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -152,10 +152,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1898 + x * (49 ±0)` // Estimated: `22371 + x * (300 ±0)` - // Minimum execution time: 58_821 nanoseconds. - Weight::from_parts(88_226_852, 22371) - // Standard Error: 2_170 - .saturating_add(Weight::from_parts(203_954, 0).saturating_mul(x.into())) + // Minimum execution time: 66_950 nanoseconds. + Weight::from_parts(87_411_011, 22371) + // Standard Error: 2_278 + .saturating_add(Weight::from_parts(208_670, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(7)) .saturating_add(Weight::from_parts(0, 300).saturating_mul(x.into())) @@ -169,10 +169,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `990 + x * (48 ±0)` // Estimated: `4794 + x * (98 ±0)` - // Minimum execution time: 29_940 nanoseconds. - Weight::from_parts(35_101_831, 4794) - // Standard Error: 2_107 - .saturating_add(Weight::from_parts(181_319, 0).saturating_mul(x.into())) + // Minimum execution time: 36_050 nanoseconds. + Weight::from_parts(40_438_357, 4794) + // Standard Error: 2_205 + .saturating_add(Weight::from_parts(178_269, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 98).saturating_mul(x.into())) @@ -200,10 +200,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `238 + x * (595 ±0)` // Estimated: `18229 + x * (12995 ±0)` - // Minimum execution time: 98_890 nanoseconds. - Weight::from_parts(120_431_000, 18229) - // Standard Error: 164_048 - .saturating_add(Weight::from_parts(44_538_186, 0).saturating_mul(x.into())) + // Minimum execution time: 101_280 nanoseconds. + Weight::from_parts(122_190_000, 18229) + // Standard Error: 216_165 + .saturating_add(Weight::from_parts(47_163_489, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -219,10 +219,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `946 + x * (48 ±0)` // Estimated: `4704 + x * (98 ±0)` - // Minimum execution time: 25_480 nanoseconds. - Weight::from_parts(34_363_058, 4704) - // Standard Error: 1_860 - .saturating_add(Weight::from_parts(190_331, 0).saturating_mul(x.into())) + // Minimum execution time: 30_160 nanoseconds. + Weight::from_parts(34_225_633, 4704) + // Standard Error: 2_459 + .saturating_add(Weight::from_parts(196_665, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 98).saturating_mul(x.into())) @@ -235,8 +235,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `371` // Estimated: `3712` - // Minimum execution time: 28_600 nanoseconds. - Weight::from_parts(35_191_000, 3712) + // Minimum execution time: 41_220 nanoseconds. + Weight::from_parts(59_570_000, 3712) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -248,8 +248,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `322` // Estimated: `3614` - // Minimum execution time: 28_530 nanoseconds. - Weight::from_parts(34_870_000, 3614) + // Minimum execution time: 43_800 nanoseconds. + Weight::from_parts(57_670_000, 3614) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -267,8 +267,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `650` // Estimated: `11796` - // Minimum execution time: 60_160 nanoseconds. - Weight::from_parts(73_960_000, 11796) + // Minimum execution time: 75_390 nanoseconds. + Weight::from_parts(105_331_000, 11796) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -278,8 +278,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `216` // Estimated: `2691` - // Minimum execution time: 27_790 nanoseconds. - Weight::from_parts(28_520_000, 2691) + // Minimum execution time: 25_120 nanoseconds. + Weight::from_parts(29_190_000, 2691) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -297,8 +297,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `670` // Estimated: `11856` - // Minimum execution time: 63_530 nanoseconds. - Weight::from_parts(72_810_000, 11856) + // Minimum execution time: 66_220 nanoseconds. + Weight::from_parts(75_400_000, 11856) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -308,8 +308,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `236` // Estimated: `2711` - // Minimum execution time: 25_630 nanoseconds. - Weight::from_parts(26_851_000, 2711) + // Minimum execution time: 23_360 nanoseconds. + Weight::from_parts(26_850_000, 2711) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -333,12 +333,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `2374 + x * (103 ±0) + y * (52 ±0)` // Estimated: `25391 + x * (530 ±0) + y * (265 ±0)` - // Minimum execution time: 99_641 nanoseconds. - Weight::from_parts(106_657_198, 25391) - // Standard Error: 11_960 - .saturating_add(Weight::from_parts(335_640, 0).saturating_mul(x.into())) - // Standard Error: 3_923 - .saturating_add(Weight::from_parts(206_453, 0).saturating_mul(y.into())) + // Minimum execution time: 113_761 nanoseconds. + Weight::from_parts(117_983_111, 25391) + // Standard Error: 14_091 + .saturating_add(Weight::from_parts(327_450, 0).saturating_mul(x.into())) + // Standard Error: 4_622 + .saturating_add(Weight::from_parts(179_482, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) .saturating_add(Weight::from_parts(0, 530).saturating_mul(x.into())) @@ -352,8 +352,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 24_570 nanoseconds. - Weight::from_parts(36_420_000, 5302) + // Minimum execution time: 30_450 nanoseconds. + Weight::from_parts(34_650_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -380,10 +380,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `755 + x * (558 ±0)` // Estimated: `26542 + x * (13492 ±2)` - // Minimum execution time: 85_821 nanoseconds. - Weight::from_parts(97_710_000, 26542) - // Standard Error: 91_916 - .saturating_add(Weight::from_parts(35_618_260, 0).saturating_mul(x.into())) + // Minimum execution time: 100_680 nanoseconds. + Weight::from_parts(122_370_000, 26542) + // Standard Error: 170_841 + .saturating_add(Weight::from_parts(39_189_991, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -398,8 +398,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 32_170 nanoseconds. - Weight::from_parts(33_590_000, 5546) + // Minimum execution time: 31_101 nanoseconds. + Weight::from_parts(37_080_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -411,8 +411,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 29_300 nanoseconds. - Weight::from_parts(30_310_000, 5302) + // Minimum execution time: 27_590 nanoseconds. + Weight::from_parts(33_460_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -436,8 +436,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1066` // Estimated: `23667` - // Minimum execution time: 74_040 nanoseconds. - Weight::from_parts(102_570_000, 23667) + // Minimum execution time: 75_210 nanoseconds. + Weight::from_parts(105_021_000, 23667) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -449,8 +449,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 30_200 nanoseconds. - Weight::from_parts(33_190_000, 5302) + // Minimum execution time: 32_550 nanoseconds. + Weight::from_parts(34_180_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -476,8 +476,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1243` // Estimated: `28447` - // Minimum execution time: 107_580 nanoseconds. - Weight::from_parts(124_161_000, 28447) + // Minimum execution time: 91_421 nanoseconds. + Weight::from_parts(113_260_000, 28447) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -501,8 +501,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1188` // Estimated: `24399` - // Minimum execution time: 80_350 nanoseconds. - Weight::from_parts(106_250_000, 24399) + // Minimum execution time: 78_020 nanoseconds. + Weight::from_parts(105_651_000, 24399) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -514,8 +514,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 31_770 nanoseconds. - Weight::from_parts(33_300_000, 5546) + // Minimum execution time: 33_220 nanoseconds. + Weight::from_parts(34_500_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -527,8 +527,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 34_390 nanoseconds. - Weight::from_parts(37_020_000, 5546) + // Minimum execution time: 31_110 nanoseconds. + Weight::from_parts(34_490_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -550,8 +550,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `438` // Estimated: `11670` - // Minimum execution time: 47_650 nanoseconds. - Weight::from_parts(56_820_000, 11670) + // Minimum execution time: 57_050 nanoseconds. + Weight::from_parts(58_430_000, 11670) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -564,10 +564,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `118 + y * (48 ±0)` // Estimated: `5180 + y * (96 ±0)` - // Minimum execution time: 8_841 nanoseconds. - Weight::from_parts(11_059_228, 5180) - // Standard Error: 1_865 - .saturating_add(Weight::from_parts(113_973, 0).saturating_mul(y.into())) + // Minimum execution time: 9_300 nanoseconds. + Weight::from_parts(11_069_735, 5180) + // Standard Error: 2_533 + .saturating_add(Weight::from_parts(128_697, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(Weight::from_parts(0, 96).saturating_mul(y.into())) } @@ -593,12 +593,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0 + x * (5122 ±0) + y * (2400 ±0)` // Estimated: `13898 + x * (26124 ±53) + y * (6816 ±26)` - // Minimum execution time: 37_510 nanoseconds. - Weight::from_parts(38_080_000, 13898) - // Standard Error: 166_418 - .saturating_add(Weight::from_parts(29_457_528, 0).saturating_mul(x.into())) - // Standard Error: 82_988 - .saturating_add(Weight::from_parts(2_891_258, 0).saturating_mul(y.into())) + // Minimum execution time: 36_200 nanoseconds. + Weight::from_parts(43_250_000, 13898) + // Standard Error: 182_967 + .saturating_add(Weight::from_parts(30_359_281, 0).saturating_mul(x.into())) + // Standard Error: 91_241 + .saturating_add(Weight::from_parts(2_857_843, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -621,10 +621,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `485 + y * (219 ±0)` // Estimated: `16898 + y * (3483 ±0)` - // Minimum execution time: 59_690 nanoseconds. - Weight::from_parts(25_763_992, 16898) - // Standard Error: 57_185 - .saturating_add(Weight::from_parts(20_957_127, 0).saturating_mul(y.into())) + // Minimum execution time: 59_200 nanoseconds. + Weight::from_parts(39_786_426, 16898) + // Standard Error: 129_057 + .saturating_add(Weight::from_parts(22_159_064, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(y.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -635,8 +635,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_030 nanoseconds. - Weight::from_parts(2_400_000, 0) + // Minimum execution time: 1_930 nanoseconds. + Weight::from_parts(2_270_000, 0) } /// Storage: ParachainStaking DelegatorState (r:1 w:0) /// Proof Skipped: ParachainStaking DelegatorState (max_values: None, max_size: None, mode: Measured) @@ -648,12 +648,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `718 + x * (34 ±0) + y * (48 ±0)` // Estimated: `6134 + x * (70 ±0) + y * (98 ±0)` - // Minimum execution time: 46_110 nanoseconds. - Weight::from_parts(52_488_804, 6134) - // Standard Error: 1_411 - .saturating_add(Weight::from_parts(89_837, 0).saturating_mul(x.into())) - // Standard Error: 4_224 - .saturating_add(Weight::from_parts(78_304, 0).saturating_mul(y.into())) + // Minimum execution time: 46_420 nanoseconds. + Weight::from_parts(53_869_963, 6134) + // Standard Error: 1_713 + .saturating_add(Weight::from_parts(85_404, 0).saturating_mul(x.into())) + // Standard Error: 5_128 + .saturating_add(Weight::from_parts(70_357, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 70).saturating_mul(x.into())) @@ -684,14 +684,14 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0 + x * (84 ±0) + y * (33 ±0) + z * (114 ±0)` // Estimated: `127262 + x * (367 ±0) + y * (73 ±0) + z * (230 ±1)` - // Minimum execution time: 123_121 nanoseconds. - Weight::from_parts(112_671_030, 127262) - // Standard Error: 3_101 - .saturating_add(Weight::from_parts(255_873, 0).saturating_mul(x.into())) - // Standard Error: 3_110 - .saturating_add(Weight::from_parts(37_579, 0).saturating_mul(y.into())) - // Standard Error: 10_953 - .saturating_add(Weight::from_parts(286_333, 0).saturating_mul(z.into())) + // Minimum execution time: 124_000 nanoseconds. + Weight::from_parts(113_273_209, 127262) + // Standard Error: 4_496 + .saturating_add(Weight::from_parts(250_836, 0).saturating_mul(x.into())) + // Standard Error: 4_508 + .saturating_add(Weight::from_parts(38_880, 0).saturating_mul(y.into())) + // Standard Error: 15_879 + .saturating_add(Weight::from_parts(321_534, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(8)) .saturating_add(Weight::from_parts(0, 367).saturating_mul(x.into())) @@ -704,8 +704,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `139` // Estimated: `2607` - // Minimum execution time: 27_490 nanoseconds. - Weight::from_parts(28_400_000, 2607) + // Minimum execution time: 26_010 nanoseconds. + Weight::from_parts(28_280_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_preimage.rs b/runtime/common/src/weights/pallet_preimage.rs index 6a31eefe0..58eda6a9a 100644 --- a/runtime/common/src/weights/pallet_preimage.rs +++ b/runtime/common/src/weights/pallet_preimage.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -61,10 +61,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `179` // Estimated: `2566` - // Minimum execution time: 38_630 nanoseconds. - Weight::from_parts(39_230_000, 2566) - // Standard Error: 5 - .saturating_add(Weight::from_parts(3_044, 0).saturating_mul(s.into())) + // Minimum execution time: 38_370 nanoseconds. + Weight::from_parts(39_390_000, 2566) + // Standard Error: 6 + .saturating_add(Weight::from_parts(3_081, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -77,10 +77,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 25_050 nanoseconds. - Weight::from_parts(25_620_000, 2566) - // Standard Error: 4 - .saturating_add(Weight::from_parts(2_998, 0).saturating_mul(s.into())) + // Minimum execution time: 23_661 nanoseconds. + Weight::from_parts(24_500_000, 2566) + // Standard Error: 5 + .saturating_add(Weight::from_parts(3_039, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -93,10 +93,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 22_440 nanoseconds. - Weight::from_parts(22_760_000, 2566) + // Minimum execution time: 24_910 nanoseconds. + Weight::from_parts(25_440_000, 2566) // Standard Error: 6 - .saturating_add(Weight::from_parts(3_008, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(3_036, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -108,8 +108,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `357` // Estimated: `2566` - // Minimum execution time: 61_020 nanoseconds. - Weight::from_parts(70_960_000, 2566) + // Minimum execution time: 60_480 nanoseconds. + Weight::from_parts(69_720_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -121,8 +121,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 41_470 nanoseconds. - Weight::from_parts(49_260_000, 2566) + // Minimum execution time: 43_861 nanoseconds. + Weight::from_parts(49_840_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -132,8 +132,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `220` // Estimated: `2566` - // Minimum execution time: 37_960 nanoseconds. - Weight::from_parts(43_310_000, 2566) + // Minimum execution time: 39_420 nanoseconds. + Weight::from_parts(44_010_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +143,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 26_950 nanoseconds. - Weight::from_parts(29_941_000, 2566) + // Minimum execution time: 26_310 nanoseconds. + Weight::from_parts(30_330_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,8 +154,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `42` // Estimated: `2566` - // Minimum execution time: 29_710 nanoseconds. - Weight::from_parts(34_790_000, 2566) + // Minimum execution time: 31_400 nanoseconds. + Weight::from_parts(34_880_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -165,8 +165,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 14_720 nanoseconds. - Weight::from_parts(16_600_000, 2566) + // Minimum execution time: 14_320 nanoseconds. + Weight::from_parts(17_110_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +178,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 41_630 nanoseconds. - Weight::from_parts(47_050_000, 2566) + // Minimum execution time: 40_830 nanoseconds. + Weight::from_parts(48_820_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -189,8 +189,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 14_760 nanoseconds. - Weight::from_parts(15_880_000, 2566) + // Minimum execution time: 14_470 nanoseconds. + Weight::from_parts(16_390_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -200,8 +200,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 16_090 nanoseconds. - Weight::from_parts(18_230_000, 2566) + // Minimum execution time: 15_280 nanoseconds. + Weight::from_parts(16_370_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_proxy.rs b/runtime/common/src/weights/pallet_proxy.rs index f36120d24..76e7767ff 100644 --- a/runtime/common/src/weights/pallet_proxy.rs +++ b/runtime/common/src/weights/pallet_proxy.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -59,10 +59,10 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo Weight { + fn remove_announcement(a: u32, _p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `404 + a * (72 ±0)` // Estimated: `7443` - // Minimum execution time: 27_220 nanoseconds. - Weight::from_parts(31_906_629, 7443) - // Standard Error: 19_587 - .saturating_add(Weight::from_parts(295_108, 0).saturating_mul(a.into())) - // Standard Error: 20_237 - .saturating_add(Weight::from_parts(60_064, 0).saturating_mul(p.into())) + // Minimum execution time: 27_440 nanoseconds. + Weight::from_parts(36_058_061, 7443) + // Standard Error: 17_046 + .saturating_add(Weight::from_parts(173_021, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -115,10 +113,10 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo Weight { + fn remove_proxy(_p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `122 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 29_330 nanoseconds. - Weight::from_parts(35_210_658, 3844) - // Standard Error: 14_296 - .saturating_add(Weight::from_parts(41_370, 0).saturating_mul(p.into())) + // Minimum execution time: 29_570 nanoseconds. + Weight::from_parts(38_706_072, 3844) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Proxy Proxies (r:1 w:1) /// Proof: Proxy Proxies (max_values: None, max_size: Some(1369), added: 3844, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. - fn remove_proxies(_p: u32) -> Weight { + fn remove_proxies(p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `122 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 23_490 nanoseconds. - Weight::from_parts(31_470_503, 3844) + // Minimum execution time: 23_940 nanoseconds. + Weight::from_parts(27_806_486, 3844) + // Standard Error: 11_421 + .saturating_add(Weight::from_parts(78_604, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -190,10 +188,10 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `35` // Estimated: `503` - // Minimum execution time: 6_290 nanoseconds. - Weight::from_parts(6_710_000, 503) + // Minimum execution time: 6_280 nanoseconds. + Weight::from_parts(6_660_000, 503) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -70,10 +70,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 5_270 nanoseconds. - Weight::from_parts(13_039_898, 13142) - // Standard Error: 8_846 - .saturating_add(Weight::from_parts(518_519, 0).saturating_mul(s.into())) + // Minimum execution time: 5_500 nanoseconds. + Weight::from_parts(13_195_112, 13142) + // Standard Error: 12_609 + .saturating_add(Weight::from_parts(584_778, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,8 +81,8 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_910 nanoseconds. - Weight::from_parts(8_570_000, 0) + // Minimum execution time: 8_840 nanoseconds. + Weight::from_parts(12_220_000, 0) } /// Storage: Preimage PreimageFor (r:1 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) @@ -93,10 +93,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `211 + s * (1 ±0)` // Estimated: `5252 + s * (1 ±0)` - // Minimum execution time: 30_710 nanoseconds. - Weight::from_parts(31_240_000, 5252) - // Standard Error: 6 - .saturating_add(Weight::from_parts(2_359, 0).saturating_mul(s.into())) + // Minimum execution time: 31_230 nanoseconds. + Weight::from_parts(31_790_000, 5252) + // Standard Error: 7 + .saturating_add(Weight::from_parts(2_427, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) @@ -107,29 +107,29 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_100 nanoseconds. - Weight::from_parts(10_480_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 10_810 nanoseconds. + Weight::from_parts(11_990_000, 0).saturating_add(T::DbWeight::get().writes(1)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_790 nanoseconds. - Weight::from_parts(8_450_000, 0) + // Minimum execution time: 8_410 nanoseconds. + Weight::from_parts(9_010_000, 0) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_810 nanoseconds. - Weight::from_parts(4_120_000, 0) + // Minimum execution time: 3_910 nanoseconds. + Weight::from_parts(4_870_000, 0) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_710 nanoseconds. - Weight::from_parts(3_910_000, 0) + // Minimum execution time: 4_180 nanoseconds. + Weight::from_parts(4_950_000, 0) } /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) @@ -138,10 +138,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 19_070 nanoseconds. - Weight::from_parts(29_611_427, 13142) - // Standard Error: 13_345 - .saturating_add(Weight::from_parts(499_616, 0).saturating_mul(s.into())) + // Minimum execution time: 27_770 nanoseconds. + Weight::from_parts(32_473_125, 13142) + // Standard Error: 15_953 + .saturating_add(Weight::from_parts(471_984, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,10 +154,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 24_350 nanoseconds. - Weight::from_parts(30_773_500, 13142) - // Standard Error: 32_975 - .saturating_add(Weight::from_parts(782_930, 0).saturating_mul(s.into())) + // Minimum execution time: 27_441 nanoseconds. + Weight::from_parts(30_393_509, 13142) + // Standard Error: 15_436 + .saturating_add(Weight::from_parts(835_686, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -170,10 +170,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `297 + s * (189 ±0)` // Estimated: `15669` - // Minimum execution time: 23_080 nanoseconds. - Weight::from_parts(37_350_318, 15669) - // Standard Error: 15_749 - .saturating_add(Weight::from_parts(515_008, 0).saturating_mul(s.into())) + // Minimum execution time: 24_280 nanoseconds. + Weight::from_parts(39_886_526, 15669) + // Standard Error: 24_317 + .saturating_add(Weight::from_parts(501_616, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -186,10 +186,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `321 + s * (189 ±0)` // Estimated: `15669` - // Minimum execution time: 30_040 nanoseconds. - Weight::from_parts(34_370_715, 15669) - // Standard Error: 18_561 - .saturating_add(Weight::from_parts(820_922, 0).saturating_mul(s.into())) + // Minimum execution time: 30_500 nanoseconds. + Weight::from_parts(36_611_230, 15669) + // Standard Error: 16_371 + .saturating_add(Weight::from_parts(775_681, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_timestamp.rs b/runtime/common/src/weights/pallet_timestamp.rs index 7969dd2c5..572f73ec3 100644 --- a/runtime/common/src/weights/pallet_timestamp.rs +++ b/runtime/common/src/weights/pallet_timestamp.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -60,8 +60,8 @@ impl pallet_timestamp::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `256` // Estimated: `1006` - // Minimum execution time: 21_870 nanoseconds. - Weight::from_parts(27_420_000, 1006) + // Minimum execution time: 22_700 nanoseconds. + Weight::from_parts(27_141_000, 1006) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,7 +69,7 @@ impl pallet_timestamp::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `128` // Estimated: `0` - // Minimum execution time: 8_710 nanoseconds. - Weight::from_parts(8_920_000, 0) + // Minimum execution time: 8_751 nanoseconds. + Weight::from_parts(9_010_000, 0) } } diff --git a/runtime/common/src/weights/pallet_treasury.rs b/runtime/common/src/weights/pallet_treasury.rs index 7ff7a7ce6..43c0ca822 100644 --- a/runtime/common/src/weights/pallet_treasury.rs +++ b/runtime/common/src/weights/pallet_treasury.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,8 +62,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `42` // Estimated: `1396` - // Minimum execution time: 23_240 nanoseconds. - Weight::from_parts(27_170_000, 1396) + // Minimum execution time: 23_510 nanoseconds. + Weight::from_parts(24_020_000, 1396) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `179` // Estimated: `499` - // Minimum execution time: 36_060 nanoseconds. - Weight::from_parts(37_371_000, 499) + // Minimum execution time: 36_650 nanoseconds. + Weight::from_parts(37_840_000, 499) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -88,8 +88,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `545` // Estimated: `7797` - // Minimum execution time: 56_050 nanoseconds. - Weight::from_parts(57_720_000, 7797) + // Minimum execution time: 51_540 nanoseconds. + Weight::from_parts(62_330_000, 7797) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -102,10 +102,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `500 + p * (8 ±0)` // Estimated: `3480` - // Minimum execution time: 16_690 nanoseconds. - Weight::from_parts(21_951_831, 3480) - // Standard Error: 3_747 - .saturating_add(Weight::from_parts(44_124, 0).saturating_mul(p.into())) + // Minimum execution time: 17_280 nanoseconds. + Weight::from_parts(21_235_363, 3480) + // Standard Error: 5_654 + .saturating_add(Weight::from_parts(90_177, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,8 +115,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `127` // Estimated: `897` - // Minimum execution time: 13_300 nanoseconds. - Weight::from_parts(16_380_000, 897) + // Minimum execution time: 11_930 nanoseconds. + Weight::from_parts(12_280_000, 897) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,10 +137,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `383 + p * (318 ±0)` // Estimated: `5423 + p * (7797 ±0)` - // Minimum execution time: 63_260 nanoseconds. - Weight::from_parts(116_268_411, 5423) - // Standard Error: 350_429 - .saturating_add(Weight::from_parts(44_963_637, 0).saturating_mul(p.into())) + // Minimum execution time: 57_160 nanoseconds. + Weight::from_parts(63_741_900, 5423) + // Standard Error: 309_254 + .saturating_add(Weight::from_parts(48_798_272, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(5)) diff --git a/runtime/common/src/weights/pallet_utility.rs b/runtime/common/src/weights/pallet_utility.rs index c061aa025..3868dd5fa 100644 --- a/runtime/common/src/weights/pallet_utility.rs +++ b/runtime/common/src/weights/pallet_utility.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,43 +57,43 @@ impl pallet_utility::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_540 nanoseconds. - Weight::from_parts(42_450_876, 0) - // Standard Error: 19_377 - .saturating_add(Weight::from_parts(6_442_903, 0).saturating_mul(c.into())) + // Minimum execution time: 11_100 nanoseconds. + Weight::from_parts(59_724_347, 0) + // Standard Error: 39_385 + .saturating_add(Weight::from_parts(6_610_000, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_110 nanoseconds. - Weight::from_parts(7_720_000, 0) + // Minimum execution time: 7_340 nanoseconds. + Weight::from_parts(9_110_000, 0) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_020 nanoseconds. - Weight::from_parts(10_270_000, 0) - // Standard Error: 34_495 - .saturating_add(Weight::from_parts(6_855_533, 0).saturating_mul(c.into())) + // Minimum execution time: 9_930 nanoseconds. + Weight::from_parts(31_588_737, 0) + // Standard Error: 20_855 + .saturating_add(Weight::from_parts(6_941_164, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_750 nanoseconds. - Weight::from_parts(16_900_000, 0) + // Minimum execution time: 12_720 nanoseconds. + Weight::from_parts(14_040_000, 0) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_940 nanoseconds. - Weight::from_parts(37_181_467, 0) - // Standard Error: 46_659 - .saturating_add(Weight::from_parts(6_630_416, 0).saturating_mul(c.into())) + // Minimum execution time: 10_960 nanoseconds. + Weight::from_parts(49_724_719, 0) + // Standard Error: 59_993 + .saturating_add(Weight::from_parts(6_735_206, 0).saturating_mul(c.into())) } } diff --git a/runtime/common/src/weights/pallet_vesting.rs b/runtime/common/src/weights/pallet_vesting.rs index cf7ad7385..8dfcf5a38 100644 --- a/runtime/common/src/weights/pallet_vesting.rs +++ b/runtime/common/src/weights/pallet_vesting.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,12 +62,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `405 + l * (25 ±0) + s * (40 ±0)` // Estimated: `7418` - // Minimum execution time: 38_200 nanoseconds. - Weight::from_parts(40_661_819, 7418) - // Standard Error: 11_194 - .saturating_add(Weight::from_parts(135_384, 0).saturating_mul(l.into())) - // Standard Error: 19_917 - .saturating_add(Weight::from_parts(143_297, 0).saturating_mul(s.into())) + // Minimum execution time: 38_890 nanoseconds. + Weight::from_parts(42_969_294, 7418) + // Standard Error: 12_255 + .saturating_add(Weight::from_parts(40_030, 0).saturating_mul(l.into())) + // Standard Error: 21_805 + .saturating_add(Weight::from_parts(81_302, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -81,12 +81,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `405 + l * (25 ±0) + s * (40 ±0)` // Estimated: `7418` - // Minimum execution time: 38_110 nanoseconds. - Weight::from_parts(42_040_682, 7418) - // Standard Error: 13_831 - .saturating_add(Weight::from_parts(98_586, 0).saturating_mul(l.into())) - // Standard Error: 24_608 - .saturating_add(Weight::from_parts(140_692, 0).saturating_mul(s.into())) + // Minimum execution time: 38_010 nanoseconds. + Weight::from_parts(39_519_436, 7418) + // Standard Error: 11_747 + .saturating_add(Weight::from_parts(103_431, 0).saturating_mul(l.into())) + // Standard Error: 20_901 + .saturating_add(Weight::from_parts(128_041, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -102,10 +102,10 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `544 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 43_910 nanoseconds. - Weight::from_parts(56_595_372, 10025) - // Standard Error: 13_637 - .saturating_add(Weight::from_parts(19_765, 0).saturating_mul(l.into())) + // Minimum execution time: 43_310 nanoseconds. + Weight::from_parts(54_519_294, 10025) + // Standard Error: 12_505 + .saturating_add(Weight::from_parts(56_976, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -117,16 +117,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. - fn vest_other_unlocked(l: u32, s: u32) -> Weight { + fn vest_other_unlocked(l: u32, _s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `544 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 44_170 nanoseconds. - Weight::from_parts(51_279_266, 10025) - // Standard Error: 12_844 - .saturating_add(Weight::from_parts(38_493, 0).saturating_mul(l.into())) - // Standard Error: 22_852 - .saturating_add(Weight::from_parts(42_143, 0).saturating_mul(s.into())) + // Minimum execution time: 43_180 nanoseconds. + Weight::from_parts(49_079_525, 10025) + // Standard Error: 10_464 + .saturating_add(Weight::from_parts(74_298, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -142,8 +140,8 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `615 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 63_280 nanoseconds. - Weight::from_parts(79_723_425, 10025) + // Minimum execution time: 62_560 nanoseconds. + Weight::from_parts(83_541_824, 10025) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -159,10 +157,10 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `754 + l * (25 ±0) + s * (40 ±0)` // Estimated: `12632` - // Minimum execution time: 68_090 nanoseconds. - Weight::from_parts(84_279_135, 12632) - // Standard Error: 19_014 - .saturating_add(Weight::from_parts(5_149, 0).saturating_mul(l.into())) + // Minimum execution time: 66_010 nanoseconds. + Weight::from_parts(74_974_962, 12632) + // Standard Error: 17_154 + .saturating_add(Weight::from_parts(76_153, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -178,12 +176,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `542 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 46_130 nanoseconds. - Weight::from_parts(53_606_659, 10025) - // Standard Error: 14_163 - .saturating_add(Weight::from_parts(28_639, 0).saturating_mul(l.into())) - // Standard Error: 26_155 - .saturating_add(Weight::from_parts(1_003, 0).saturating_mul(s.into())) + // Minimum execution time: 44_480 nanoseconds. + Weight::from_parts(49_199_052, 10025) + // Standard Error: 14_207 + .saturating_add(Weight::from_parts(89_429, 0).saturating_mul(l.into())) + // Standard Error: 26_237 + .saturating_add(Weight::from_parts(43_668, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -195,16 +193,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. - fn unlocking_merge_schedules(l: u32, s: u32) -> Weight { + fn unlocking_merge_schedules(l: u32, _s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `542 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 45_860 nanoseconds. - Weight::from_parts(51_687_722, 10025) - // Standard Error: 12_517 - .saturating_add(Weight::from_parts(111_660, 0).saturating_mul(l.into())) - // Standard Error: 23_115 - .saturating_add(Weight::from_parts(19_215, 0).saturating_mul(s.into())) + // Minimum execution time: 45_050 nanoseconds. + Weight::from_parts(54_111_045, 10025) + // Standard Error: 14_322 + .saturating_add(Weight::from_parts(67_882, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 170503a16..a51c91cbf 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -406,7 +406,7 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-runtime" -version = "0.4.1" +version = "0.4.2" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 1e8a0b2ea..5d7bf6ae8 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -90,10 +90,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 50, + spec_version: 51, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 25, + transaction_version: 26, state_version: 1, }; diff --git a/zrml/authorized/Cargo.toml b/zrml/authorized/Cargo.toml index 743f29e8c..616618158 100644 --- a/zrml/authorized/Cargo.toml +++ b/zrml/authorized/Cargo.toml @@ -38,4 +38,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-authorized" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index 193980e98..9a1cadd91 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_authorized //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-11`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-25`, STEPS: `10`, REPEAT: `1000`, 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` @@ -64,7 +64,7 @@ pub trait WeightInfoZeitgeist { pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) @@ -72,25 +72,25 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[1, 63]`. fn authorize_market_outcome_first_report(m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `820 + m * (22 ±0)` - // Estimated: `9058` - // Minimum execution time: 37_130 nanoseconds. - Weight::from_parts(42_650_357, 9058) - // Standard Error: 2_289 - .saturating_add(Weight::from_parts(99_108, 0).saturating_mul(m.into())) + // Measured: `823 + m * (22 ±0)` + // Estimated: `9194` + // Minimum execution time: 37_880 nanoseconds. + Weight::from_parts(44_691_872, 9194) + // Standard Error: 2_332 + .saturating_add(Weight::from_parts(27_672, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn authorize_market_outcome_existing_report() -> Weight { // Proof Size summary in bytes: - // Measured: `574` - // Estimated: `5541` - // Minimum execution time: 29_800 nanoseconds. - Weight::from_parts(32_130_000, 5541) + // Measured: `577` + // Estimated: `5677` + // Minimum execution time: 30_361 nanoseconds. + Weight::from_parts(34_830_000, 5677) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -98,8 +98,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240 nanoseconds. - Weight::from_parts(320_000, 0) + // Minimum execution time: 300 nanoseconds. + Weight::from_parts(360_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -107,8 +107,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `217` // Estimated: `2524` - // Minimum execution time: 8_460 nanoseconds. - Weight::from_parts(10_380_000, 2524) + // Minimum execution time: 8_330 nanoseconds. + Weight::from_parts(10_160_000, 2524) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -116,8 +116,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_330 nanoseconds. - Weight::from_parts(2_730_000, 0) + // Minimum execution time: 2_440 nanoseconds. + Weight::from_parts(2_710_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -125,22 +125,22 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `217` // Estimated: `2524` - // Minimum execution time: 7_610 nanoseconds. - Weight::from_parts(9_330_000, 2524).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 7_620 nanoseconds. + Weight::from_parts(9_170_000, 2524).saturating_add(T::DbWeight::get().reads(1)) } fn has_failed_weight() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 250 nanoseconds. + // Minimum execution time: 260 nanoseconds. Weight::from_parts(330_000, 0) } fn on_global_dispute_weight() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 230 nanoseconds. - Weight::from_parts(300_000, 0) + // Minimum execution time: 270 nanoseconds. + Weight::from_parts(340_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:0 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -148,7 +148,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_210 nanoseconds. - Weight::from_parts(2_660_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 2_150 nanoseconds. + Weight::from_parts(2_470_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index 66e6961b0..f88784959 100644 --- a/zrml/court/Cargo.toml +++ b/zrml/court/Cargo.toml @@ -46,4 +46,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-court" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/court/src/weights.rs b/zrml/court/src/weights.rs index 3842a44cd..bfc2762fe 100644 --- a/zrml/court/src/weights.rs +++ b/zrml/court/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_court //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -83,12 +83,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `j` is `[0, 999]`. fn join_court(j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1096 + j * (72 ±0)` + // Measured: `1130 + j * (72 ±0)` // Estimated: `78997` - // Minimum execution time: 41_860 nanoseconds. - Weight::from_parts(51_772_322, 78997) - // Standard Error: 340 - .saturating_add(Weight::from_parts(128_923, 0).saturating_mul(j.into())) + // Minimum execution time: 42_390 nanoseconds. + Weight::from_parts(50_893_889, 78997) + // Standard Error: 356 + .saturating_add(Weight::from_parts(131_671, 0).saturating_mul(j.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -104,12 +104,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + j * (74 ±0) + d * (685 ±0)` // Estimated: `78997 + d * (2726 ±0)` - // Minimum execution time: 71_010 nanoseconds. - Weight::from_parts(33_681_143, 78997) - // Standard Error: 474 - .saturating_add(Weight::from_parts(175_394, 0).saturating_mul(j.into())) - // Standard Error: 103_515 - .saturating_add(Weight::from_parts(10_265_315, 0).saturating_mul(d.into())) + // Minimum execution time: 71_251 nanoseconds. + Weight::from_parts(42_003_599, 78997) + // Standard Error: 489 + .saturating_add(Weight::from_parts(169_094, 0).saturating_mul(j.into())) + // Standard Error: 106_757 + .saturating_add(Weight::from_parts(9_146_226, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -122,12 +122,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `j` is `[0, 999]`. fn prepare_exit_court(j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1034 + j * (72 ±0)` + // Measured: `1068 + j * (72 ±0)` // Estimated: `75223` - // Minimum execution time: 26_980 nanoseconds. - Weight::from_parts(35_113_806, 75223) - // Standard Error: 268 - .saturating_add(Weight::from_parts(105_698, 0).saturating_mul(j.into())) + // Minimum execution time: 27_380 nanoseconds. + Weight::from_parts(35_546_685, 75223) + // Standard Error: 287 + .saturating_add(Weight::from_parts(106_675, 0).saturating_mul(j.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -137,10 +137,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) fn exit_court_remove() -> Weight { // Proof Size summary in bytes: - // Measured: `273` + // Measured: `307` // Estimated: `6500` - // Minimum execution time: 37_360 nanoseconds. - Weight::from_parts(43_201_000, 6500) + // Minimum execution time: 37_730 nanoseconds. + Weight::from_parts(43_521_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -150,10 +150,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) fn exit_court_set() -> Weight { // Proof Size summary in bytes: - // Measured: `273` + // Measured: `307` // Estimated: `6500` - // Minimum execution time: 35_770 nanoseconds. - Weight::from_parts(40_940_000, 6500) + // Minimum execution time: 36_050 nanoseconds. + Weight::from_parts(43_831_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -164,19 +164,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `416 + d * (53 ±0)` + // Measured: `450 + d * (53 ±0)` // Estimated: `155273` - // Minimum execution time: 52_471 nanoseconds. - Weight::from_parts(63_337_425, 155273) - // Standard Error: 476 - .saturating_add(Weight::from_parts(117_559, 0).saturating_mul(d.into())) + // Minimum execution time: 52_671 nanoseconds. + Weight::from_parts(62_979_576, 155273) + // Standard Error: 465 + .saturating_add(Weight::from_parts(118_018, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Court CourtIdToMarketId (r:1 w:0) /// Proof: Court CourtIdToMarketId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Court Participants (r:1 w:0) /// Proof: Court Participants (max_values: None, max_size: Some(251), added: 2726, mode: MaxEncodedLen) /// Storage: Court Courts (r:1 w:0) @@ -186,19 +186,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn denounce_vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1526 + d * (53 ±0)` - // Estimated: `163531` - // Minimum execution time: 56_570 nanoseconds. - Weight::from_parts(64_504_390, 163531) - // Standard Error: 618 - .saturating_add(Weight::from_parts(164_325, 0).saturating_mul(d.into())) + // Measured: `1563 + d * (53 ±0)` + // Estimated: `163667` + // Minimum execution time: 56_160 nanoseconds. + Weight::from_parts(66_113_555, 163667) + // Standard Error: 640 + .saturating_add(Weight::from_parts(164_953, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Court CourtIdToMarketId (r:1 w:0) /// Proof: Court CourtIdToMarketId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Court Participants (r:1 w:0) /// Proof: Court Participants (max_values: None, max_size: Some(251), added: 2726, mode: MaxEncodedLen) /// Storage: Court Courts (r:1 w:0) @@ -208,12 +208,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn reveal_vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2108 + d * (53 ±0)` - // Estimated: `163531` - // Minimum execution time: 88_011 nanoseconds. - Weight::from_parts(104_678_220, 163531) - // Standard Error: 712 - .saturating_add(Weight::from_parts(113_726, 0).saturating_mul(d.into())) + // Measured: `2145 + d * (53 ±0)` + // Estimated: `163667` + // Minimum execution time: 89_220 nanoseconds. + Weight::from_parts(105_277_297, 163667) + // Standard Error: 689 + .saturating_add(Weight::from_parts(114_305, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -222,7 +222,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court CourtIdToMarketId (r:1 w:0) /// Proof: Court CourtIdToMarketId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Court SelectedDraws (r:1 w:1) /// Proof: Court SelectedDraws (max_values: None, max_size: Some(149974), added: 152449, mode: MaxEncodedLen) /// Storage: Court CourtPool (r:1 w:1) @@ -246,13 +246,13 @@ impl WeightInfoZeitgeist for WeightInfo { fn appeal(j: u32, a: u32, _r: u32, _f: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0 + j * (132 ±0) + a * (35486 ±0) + r * (16 ±0) + f * (16 ±0)` - // Estimated: `515303 + j * (203 ±1) + a * (314898 ±368)` - // Minimum execution time: 3_473_644 nanoseconds. - Weight::from_parts(3_775_379_000, 515303) - // Standard Error: 27_336 - .saturating_add(Weight::from_parts(6_465_312, 0).saturating_mul(j.into())) - // Standard Error: 9_755_849 - .saturating_add(Weight::from_parts(4_399_915_858, 0).saturating_mul(a.into())) + // Estimated: `515439 + j * (203 ±1) + a * (314898 ±368)` + // Minimum execution time: 3_470_854 nanoseconds. + Weight::from_parts(3_772_958_000, 515439) + // Standard Error: 25_467 + .saturating_add(Weight::from_parts(6_401_836, 0).saturating_mul(j.into())) + // Standard Error: 9_089_079 + .saturating_add(Weight::from_parts(4_296_122_826, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(109)) .saturating_add(T::DbWeight::get().reads((116_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(100)) @@ -271,12 +271,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[5, 510]`. fn reassign_court_stakes(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `911 + d * (587 ±0)` + // Measured: `945 + d * (587 ±0)` // Estimated: `157880 + d * (5333 ±0)` - // Minimum execution time: 147_721 nanoseconds. - Weight::from_parts(151_251_000, 157880) - // Standard Error: 34_391 - .saturating_add(Weight::from_parts(66_838_948, 0).saturating_mul(d.into())) + // Minimum execution time: 148_160 nanoseconds. + Weight::from_parts(151_951_000, 157880) + // Standard Error: 38_869 + .saturating_add(Weight::from_parts(66_881_062, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -289,8 +289,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_430 nanoseconds. - Weight::from_parts(14_040_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 12_650 nanoseconds. + Weight::from_parts(14_450_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Court YearlyInflation (r:1 w:0) /// Proof: Court YearlyInflation (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -303,10 +303,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + j * (243 ±0)` // Estimated: `72996 + j * (2607 ±0)` - // Minimum execution time: 32_920 nanoseconds. - Weight::from_parts(34_440_000, 72996) - // Standard Error: 11_101 - .saturating_add(Weight::from_parts(19_885_586, 0).saturating_mul(j.into())) + // Minimum execution time: 33_280 nanoseconds. + Weight::from_parts(34_480_000, 72996) + // Standard Error: 9_174 + .saturating_add(Weight::from_parts(19_968_654, 0).saturating_mul(j.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(j.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(j.into()))) @@ -323,12 +323,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[0, 3]`. fn select_participants(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `84018 + a * (19595 ±0)` + // Measured: `84052 + a * (19595 ±0)` // Estimated: `133335 + a * (162878 ±713)` - // Minimum execution time: 1_519_144 nanoseconds. - Weight::from_parts(838_962_523, 133335) - // Standard Error: 19_763_528 - .saturating_add(Weight::from_parts(3_555_251_845, 0).saturating_mul(a.into())) + // Minimum execution time: 1_523_714 nanoseconds. + Weight::from_parts(864_759_111, 133335) + // Standard Error: 18_632_603 + .saturating_add(Weight::from_parts(3_449_129_449, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(24)) .saturating_add(T::DbWeight::get().reads((60_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(19)) @@ -361,14 +361,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 62]`. fn on_dispute(j: u32, r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `6039 + j * (80 ±0) + r * (16 ±0)` + // Measured: `6073 + j * (80 ±0) + r * (16 ±0)` // Estimated: `153295 + j * (11 ±0) + r * (29 ±1)` - // Minimum execution time: 292_071 nanoseconds. - Weight::from_parts(332_110_603, 153295) - // Standard Error: 1_279 - .saturating_add(Weight::from_parts(252_628, 0).saturating_mul(j.into())) - // Standard Error: 19_792 - .saturating_add(Weight::from_parts(446_988, 0).saturating_mul(r.into())) + // Minimum execution time: 298_061 nanoseconds. + Weight::from_parts(348_386_954, 153295) + // Standard Error: 1_339 + .saturating_add(Weight::from_parts(259_856, 0).saturating_mul(j.into())) + // Standard Error: 20_722 + .saturating_add(Weight::from_parts(277_477, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(33)) .saturating_add(T::DbWeight::get().writes(35)) .saturating_add(Weight::from_parts(0, 11).saturating_mul(j.into())) @@ -383,18 +383,18 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court CourtIdToMarketId (r:1 w:0) /// Proof: Court CourtIdToMarketId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Court Participants (r:510 w:510) /// Proof: Court Participants (max_values: None, max_size: Some(251), added: 2726, mode: MaxEncodedLen) /// The range of component `d` is `[1, 510]`. fn on_resolution(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `931 + d * (256 ±0)` - // Estimated: `163320 + d * (2726 ±0)` - // Minimum execution time: 45_140 nanoseconds. - Weight::from_parts(46_400_000, 163320) - // Standard Error: 5_453 - .saturating_add(Weight::from_parts(7_158_282, 0).saturating_mul(d.into())) + // Measured: `968 + d * (256 ±0)` + // Estimated: `163456 + d * (2726 ±0)` + // Minimum execution time: 45_351 nanoseconds. + Weight::from_parts(46_740_000, 163456) + // Standard Error: 5_341 + .saturating_add(Weight::from_parts(7_102_104, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -412,12 +412,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[0, 4]`. fn exchange(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `386 + a * (352 ±0)` + // Measured: `420 + a * (352 ±0)` // Estimated: `5339 + a * (6331 ±0)` - // Minimum execution time: 15_770 nanoseconds. - Weight::from_parts(19_251_587, 5339) - // Standard Error: 61_943 - .saturating_add(Weight::from_parts(31_306_570, 0).saturating_mul(a.into())) + // Minimum execution time: 14_230 nanoseconds. + Weight::from_parts(17_978_307, 5339) + // Standard Error: 68_789 + .saturating_add(Weight::from_parts(31_714_281, 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()))) @@ -429,10 +429,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Court Courts (max_values: None, max_size: Some(349), added: 2824, mode: MaxEncodedLen) fn get_auto_resolve() -> Weight { // Proof Size summary in bytes: - // Measured: `389` + // Measured: `423` // Estimated: `5339` - // Minimum execution time: 13_450 nanoseconds. - Weight::from_parts(15_930_000, 5339).saturating_add(T::DbWeight::get().reads(2)) + // Minimum execution time: 11_790 nanoseconds. + Weight::from_parts(12_930_000, 5339).saturating_add(T::DbWeight::get().reads(2)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -443,13 +443,13 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court CourtIdToMarketId (r:1 w:0) /// Proof: Court CourtIdToMarketId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) fn has_failed() -> Weight { // Proof Size summary in bytes: - // Measured: `3152` - // Estimated: `83368` - // Minimum execution time: 34_630 nanoseconds. - Weight::from_parts(36_671_000, 83368).saturating_add(T::DbWeight::get().reads(5)) + // Measured: `3189` + // Estimated: `83504` + // Minimum execution time: 36_060 nanoseconds. + Weight::from_parts(43_690_000, 83504).saturating_add(T::DbWeight::get().reads(5)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -463,12 +463,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn on_global_dispute(_a: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `448 + a * (66 ±0) + d * (256 ±0)` + // Measured: `482 + a * (66 ±0) + d * (256 ±0)` // Estimated: `157788 + d * (2726 ±0)` - // Minimum execution time: 30_740 nanoseconds. - Weight::from_parts(2_270_886, 157788) - // Standard Error: 9_278 - .saturating_add(Weight::from_parts(7_459_796, 0).saturating_mul(d.into())) + // Minimum execution time: 30_490 nanoseconds. + Weight::from_parts(12_392_980, 157788) + // Standard Error: 8_466 + .saturating_add(Weight::from_parts(7_354_052, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -486,12 +486,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn clear(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `363 + d * (256 ±0)` + // Measured: `397 + d * (256 ±0)` // Estimated: `154964 + d * (2726 ±0)` - // Minimum execution time: 25_190 nanoseconds. - Weight::from_parts(25_760_000, 154964) - // Standard Error: 6_733 - .saturating_add(Weight::from_parts(7_143_466, 0).saturating_mul(d.into())) + // Minimum execution time: 24_940 nanoseconds. + Weight::from_parts(25_650_000, 154964) + // Standard Error: 5_781 + .saturating_add(Weight::from_parts(6_956_726, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/zrml/global-disputes/Cargo.toml b/zrml/global-disputes/Cargo.toml index 0d089b5d7..450dc0fd0 100644 --- a/zrml/global-disputes/Cargo.toml +++ b/zrml/global-disputes/Cargo.toml @@ -45,4 +45,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-global-disputes" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/global-disputes/src/weights.rs b/zrml/global-disputes/src/weights.rs index e336c3eeb..cd74c28c5 100644 --- a/zrml/global-disputes/src/weights.rs +++ b/zrml/global-disputes/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_global_disputes //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -76,12 +76,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `556 + o * (26 ±0) + v * (32 ±0)` // Estimated: `13631` - // Minimum execution time: 57_410 nanoseconds. - Weight::from_parts(66_467_418, 13631) - // Standard Error: 20_588 - .saturating_add(Weight::from_parts(87_789, 0).saturating_mul(o.into())) - // Standard Error: 3_612 - .saturating_add(Weight::from_parts(45_378, 0).saturating_mul(v.into())) + // Minimum execution time: 56_570 nanoseconds. + Weight::from_parts(62_319_654, 13631) + // Standard Error: 20_343 + .saturating_add(Weight::from_parts(133_505, 0).saturating_mul(o.into())) + // Standard Error: 3_569 + .saturating_add(Weight::from_parts(91_527, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -99,12 +99,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + l * (467 ±0) + o * (1600 ±0)` // Estimated: `10497 + l * (2871 ±0)` - // Minimum execution time: 31_690 nanoseconds. - Weight::from_parts(31_232_820, 10497) - // Standard Error: 11_210 - .saturating_add(Weight::from_parts(4_094_584, 0).saturating_mul(l.into())) - // Standard Error: 62_757 - .saturating_add(Weight::from_parts(1_297_075, 0).saturating_mul(o.into())) + // Minimum execution time: 31_550 nanoseconds. + Weight::from_parts(35_536_478, 10497) + // Standard Error: 11_671 + .saturating_add(Weight::from_parts(3_910_915, 0).saturating_mul(l.into())) + // Standard Error: 65_337 + .saturating_add(Weight::from_parts(1_067_736, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(l.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -120,23 +120,21 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 50]`. /// The range of component `o` is `[1, 10]`. - fn unlock_vote_balance_remove(l: u32, o: u32) -> Weight { + fn unlock_vote_balance_remove(l: u32, _o: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0 + l * (451 ±0) + o * (1600 ±0)` // Estimated: `10497 + l * (2871 ±0)` - // Minimum execution time: 31_720 nanoseconds. - Weight::from_parts(18_548_656, 10497) - // Standard Error: 11_063 - .saturating_add(Weight::from_parts(4_057_618, 0).saturating_mul(l.into())) - // Standard Error: 61_931 - .saturating_add(Weight::from_parts(2_028_551, 0).saturating_mul(o.into())) + // Minimum execution time: 31_640 nanoseconds. + Weight::from_parts(38_456_566, 10497) + // Standard Error: 10_816 + .saturating_add(Weight::from_parts(3_883_534, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(l.into()))) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 2871).saturating_mul(l.into())) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: GlobalDisputes GlobalDisputesInfo (r:1 w:1) /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: GlobalDisputes Outcomes (r:1 w:1) @@ -146,12 +144,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `w` is `[1, 10]`. fn add_vote_outcome(w: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `687 + w * (32 ±0)` - // Estimated: `11365` - // Minimum execution time: 67_460 nanoseconds. - Weight::from_parts(77_049_200, 11365) - // Standard Error: 25_025 - .saturating_add(Weight::from_parts(93_445, 0).saturating_mul(w.into())) + // Measured: `690 + w * (32 ±0)` + // Estimated: `11501` + // Minimum execution time: 67_621 nanoseconds. + Weight::from_parts(77_496_340, 11501) + // Standard Error: 25_163 + .saturating_add(Weight::from_parts(48_228, 0).saturating_mul(w.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -166,10 +164,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `490 + o * (41 ±0)` // Estimated: `8869 + o * (2702 ±6)` - // Minimum execution time: 66_290 nanoseconds. - Weight::from_parts(51_210_493, 8869) - // Standard Error: 97_119 - .saturating_add(Weight::from_parts(29_851_278, 0).saturating_mul(o.into())) + // Minimum execution time: 66_650 nanoseconds. + Weight::from_parts(53_409_131, 8869) + // Standard Error: 97_889 + .saturating_add(Weight::from_parts(30_309_941, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(o.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -186,8 +184,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `537` // Estimated: `10955` - // Minimum execution time: 65_360 nanoseconds. - Weight::from_parts(75_881_000, 10955) + // Minimum execution time: 66_220 nanoseconds. + Weight::from_parts(75_191_000, 10955) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -201,10 +199,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `407 + k * (122 ±0) + o * (32 ±0)` // Estimated: `8611 + k * (2870 ±0)` - // Minimum execution time: 75_521 nanoseconds. - Weight::from_parts(19_988_587, 8611) - // Standard Error: 18_595 - .saturating_add(Weight::from_parts(18_053_259, 0).saturating_mul(k.into())) + // Minimum execution time: 76_340 nanoseconds. + Weight::from_parts(8_305_894, 8611) + // Standard Error: 21_012 + .saturating_add(Weight::from_parts(18_168_686, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -221,10 +219,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `407 + k * (122 ±0) + o * (32 ±0)` // Estimated: `8611 + k * (2870 ±0)` - // Minimum execution time: 72_970 nanoseconds. - Weight::from_parts(119_070_747, 8611) - // Standard Error: 15_542 - .saturating_add(Weight::from_parts(17_596_560, 0).saturating_mul(k.into())) + // Minimum execution time: 73_140 nanoseconds. + Weight::from_parts(232_428_016, 8611) + // Standard Error: 18_813 + .saturating_add(Weight::from_parts(17_740_443, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/zrml/liquidity-mining/Cargo.toml b/zrml/liquidity-mining/Cargo.toml index 67a143ddf..e394c4dd4 100644 --- a/zrml/liquidity-mining/Cargo.toml +++ b/zrml/liquidity-mining/Cargo.toml @@ -40,4 +40,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-liquidity-mining" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/liquidity-mining/src/weights.rs b/zrml/liquidity-mining/src/weights.rs index d70c2da17..69e9ef043 100644 --- a/zrml/liquidity-mining/src/weights.rs +++ b/zrml/liquidity-mining/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_liquidity_mining //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -61,7 +61,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_080 nanoseconds. - Weight::from_parts(4_510_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 3_980 nanoseconds. + Weight::from_parts(4_750_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/market-commons/Cargo.toml b/zrml/market-commons/Cargo.toml index ce8462107..d26c4e86b 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -31,4 +31,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-market-commons" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 828247a0a..d0b87d52f 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -106,4 +106,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-neo-swaps" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 6d50d69f1..59057af5a 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -61,7 +61,7 @@ pub trait WeightInfoZeitgeist { pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: System Account (r:2 w:2) @@ -72,15 +72,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn buy() -> Weight { // Proof Size summary in bytes: - // Measured: `2868` - // Estimated: `28188` - // Minimum execution time: 383_942 nanoseconds. - Weight::from_parts(452_972_000, 28188) + // Measured: `2905` + // Estimated: `28324` + // Minimum execution time: 389_381 nanoseconds. + Weight::from_parts(456_541_000, 28324) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:3 w:3) @@ -91,30 +91,30 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn sell() -> Weight { // Proof Size summary in bytes: - // Measured: `3034` - // Estimated: `28188` - // Minimum execution time: 401_532 nanoseconds. - Weight::from_parts(491_103_000, 28188) + // Measured: `3071` + // Estimated: `28324` + // Minimum execution time: 404_471 nanoseconds. + Weight::from_parts(473_241_000, 28324) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) fn join() -> Weight { // Proof Size summary in bytes: - // Measured: `2756` - // Estimated: `20536` - // Minimum execution time: 122_280 nanoseconds. - Weight::from_parts(149_371_000, 20536) + // Measured: `2793` + // Estimated: `20672` + // Minimum execution time: 122_321 nanoseconds. + Weight::from_parts(140_940_000, 20672) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) @@ -123,10 +123,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn exit() -> Weight { // Proof Size summary in bytes: - // Measured: `2524` - // Estimated: `23143` - // Minimum execution time: 124_590 nanoseconds. - Weight::from_parts(151_600_000, 23143) + // Measured: `2561` + // Estimated: `23279` + // Minimum execution time: 124_770 nanoseconds. + Weight::from_parts(151_910_000, 23279) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -138,13 +138,13 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `1819` // Estimated: `9734` - // Minimum execution time: 77_180 nanoseconds. - Weight::from_parts(93_340_000, 9734) + // Minimum execution time: 76_910 nanoseconds. + Weight::from_parts(94_141_000, 9734) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) @@ -153,10 +153,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn deploy_pool() -> Weight { // Proof Size summary in bytes: - // Measured: `2241` - // Estimated: `23143` - // Minimum execution time: 166_011 nanoseconds. - Weight::from_parts(201_540_000, 23143) + // Measured: `2278` + // Estimated: `23279` + // Minimum execution time: 167_210 nanoseconds. + Weight::from_parts(189_090_000, 23279) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(6)) } diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index 8eba77144..f914b3d84 100644 --- a/zrml/orderbook/Cargo.toml +++ b/zrml/orderbook/Cargo.toml @@ -54,4 +54,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-orderbook" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/orderbook/src/weights.rs b/zrml/orderbook/src/weights.rs index 1855bba8b..78de0818a 100644 --- a/zrml/orderbook/src/weights.rs +++ b/zrml/orderbook/src/weights.rs @@ -19,18 +19,18 @@ //! Autogenerated weights for zrml_orderbook //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `sea212-PC`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/debug/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev // --steps=10 -// --repeat=10 +// --repeat=1000 // --pallet=zrml_orderbook // --extrinsic=* // --execution=wasm @@ -68,10 +68,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) fn remove_order_ask() -> Weight { // Proof Size summary in bytes: - // Measured: `637` + // Measured: `675` // Estimated: `8967` - // Minimum execution time: 362_440 nanoseconds. - Weight::from_parts(363_640_000, 8967) + // Minimum execution time: 41_210 nanoseconds. + Weight::from_parts(51_230_000, 8967) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -81,10 +81,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) fn remove_order_bid() -> Weight { // Proof Size summary in bytes: - // Measured: `284` + // Measured: `323` // Estimated: `6342` - // Minimum execution time: 326_778 nanoseconds. - Weight::from_parts(330_518_000, 6342) + // Minimum execution time: 37_110 nanoseconds. + Weight::from_parts(45_930_000, 6342) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -100,10 +100,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn fill_order_ask() -> Weight { // Proof Size summary in bytes: - // Measured: `1310` + // Measured: `1348` // Estimated: `17325` - // Minimum execution time: 872_283 nanoseconds. - Weight::from_parts(873_872_000, 17325) + // Minimum execution time: 97_511 nanoseconds. + Weight::from_parts(120_261_000, 17325) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -119,10 +119,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn fill_order_bid() -> Weight { // Proof Size summary in bytes: - // Measured: `1249` + // Measured: `1288` // Estimated: `17298` - // Minimum execution time: 766_910 nanoseconds. - Weight::from_parts(769_050_000, 17298) + // Minimum execution time: 86_600 nanoseconds. + Weight::from_parts(106_891_000, 17298) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -138,10 +138,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) fn place_order_ask() -> Weight { // Proof Size summary in bytes: - // Measured: `626` + // Measured: `664` // Estimated: `10013` - // Minimum execution time: 441_322 nanoseconds. - Weight::from_parts(443_332_000, 10013) + // Minimum execution time: 55_910 nanoseconds. + Weight::from_parts(69_621_000, 10013) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -155,10 +155,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) fn place_order_bid() -> Weight { // Proof Size summary in bytes: - // Measured: `334` + // Measured: `372` // Estimated: `7388` - // Minimum execution time: 369_460 nanoseconds. - Weight::from_parts(370_870_000, 7388) + // Minimum execution time: 44_780 nanoseconds. + Weight::from_parts(55_840_000, 7388) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index abcb8e765..dae19e657 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -44,4 +44,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-parimutuel" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/parimutuel/src/weights.rs b/zrml/parimutuel/src/weights.rs index bf646c150..b44a0f130 100644 --- a/zrml/parimutuel/src/weights.rs +++ b/zrml/parimutuel/src/weights.rs @@ -14,17 +14,16 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - //! Autogenerated weights for zrml_parimutuel //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-13`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Chralt-3.local`, CPU: `` +//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev @@ -56,55 +55,53 @@ pub trait WeightInfoZeitgeist { pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(543), added: 3018, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(124), added: 2599, mode: MaxEncodedLen) + /// 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(44), added: 2519, mode: MaxEncodedLen) - /// Storage: Parimutuel TotalRewards (r:1 w:1) - /// Proof: Parimutuel TotalRewards (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn buy() -> Weight { // Proof Size summary in bytes: - // Measured: `1815` - // Estimated: `13258` - // Minimum execution time: 79_000 nanoseconds. - Weight::from_parts(84_000_000, 13258) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `1814` + // Estimated: `10876` + // Minimum execution time: 107_021 nanoseconds. + Weight::from_parts(131_400_000, 10876) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(543), added: 3018, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(124), added: 2599, mode: MaxEncodedLen) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn claim_rewards() -> Weight { // Proof Size summary in bytes: // Measured: `2311` - // Estimated: `10743` - // Minimum execution time: 75_000 nanoseconds. - Weight::from_parts(81_000_000, 10743) + // Estimated: `10876` + // Minimum execution time: 106_420 nanoseconds. + Weight::from_parts(146_081_000, 10876) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(543), added: 3018, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:2 w:1) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(124), added: 2599, mode: MaxEncodedLen) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn claim_refunds() -> Weight { // Proof Size summary in bytes: // Measured: `2311` - // Estimated: `13262` - // Minimum execution time: 75_000 nanoseconds. - Weight::from_parts(77_000_000, 13262) + // Estimated: `13394` + // Minimum execution time: 100_691 nanoseconds. + Weight::from_parts(123_451_000, 13394) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index d88f30b77..3ce92c2f8 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -95,4 +95,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-prediction-markets" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/prediction-markets/runtime-api/Cargo.toml b/zrml/prediction-markets/runtime-api/Cargo.toml index 0d98717a6..d4873b2a0 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.1" +version = "0.4.2" diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 9eb2ab2fd..1d095de18 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_prediction_markets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -80,20 +80,20 @@ pub trait WeightInfoZeitgeist { fn market_status_manager(b: u32, f: u32) -> Weight; fn market_resolution_manager(r: u32, d: u32) -> Weight; fn process_subsidy_collecting_markets_dummy() -> Weight; - fn create_market_and_deploy_pool(m: u32) -> Weight; fn schedule_early_close_as_authority(o: u32, n: u32) -> Weight; fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight; fn schedule_early_close_as_market_creator(o: u32, n: u32) -> Weight; 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 create_market_and_deploy_pool(m: u32) -> Weight; } /// Weight functions for zrml_prediction_markets (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) @@ -104,19 +104,21 @@ 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 { - // Proof Size summary in bytes: - // Measured: `789 + o * (16 ±0) + c * (16 ±0)` - // Estimated: `13093` - // Minimum execution time: 54_440 nanoseconds. - Weight::from_parts(65_791_830, 13093) - // Standard Error: 2_584 - .saturating_add(Weight::from_parts(32_267, 0).saturating_mul(c.into())) + fn admin_move_market_to_closed(o: u32, 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)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) @@ -126,17 +128,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 63]`. fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `786 + r * (16 ±0)` - // Estimated: `12781` - // Minimum execution time: 81_390 nanoseconds. - Weight::from_parts(93_537_719, 12781) - // Standard Error: 4_374 - .saturating_add(Weight::from_parts(88_348, 0).saturating_mul(r.into())) + // Measured: `789 + r * (16 ±0)` + // Estimated: `12917` + // Minimum execution time: 81_630 nanoseconds. + Weight::from_parts(90_598_731, 12917) + // Standard Error: 4_875 + .saturating_add(Weight::from_parts(61_887, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) @@ -148,17 +150,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 63]`. fn admin_move_market_to_resolved_categorical_reported(r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `4479 + r * (16 ±0)` - // Estimated: `18907` - // Minimum execution time: 131_361 nanoseconds. - Weight::from_parts(148_371_434, 18907) - // Standard Error: 8_178 - .saturating_add(Weight::from_parts(17_697, 0).saturating_mul(r.into())) + // Measured: `4482 + r * (16 ±0)` + // Estimated: `19043` + // Minimum execution time: 132_880 nanoseconds. + Weight::from_parts(148_466_242, 19043) + // Standard Error: 8_108 + .saturating_add(Weight::from_parts(4_693, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) @@ -174,17 +176,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 63]`. fn admin_move_market_to_resolved_scalar_disputed(r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1312 + r * (16 ±0)` - // Estimated: `24507` - // Minimum execution time: 136_470 nanoseconds. - Weight::from_parts(157_704_563, 24507) - // Standard Error: 7_258 - .saturating_add(Weight::from_parts(106_232, 0).saturating_mul(r.into())) + // Measured: `1315 + r * (16 ±0)` + // Estimated: `24643` + // Minimum execution time: 136_411 nanoseconds. + Weight::from_parts(149_906_505, 24643) + // Standard Error: 7_982 + .saturating_add(Weight::from_parts(82_359, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) @@ -200,50 +202,48 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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 `r` is `[0, 63]`. - fn admin_move_market_to_resolved_categorical_disputed(r: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `4990 + r * (16 ±0)` - // Estimated: `30633` - // Minimum execution time: 186_660 nanoseconds. - Weight::from_parts(214_938_616, 30633) - // Standard Error: 10_966 - .saturating_add(Weight::from_parts(21_013, 0).saturating_mul(r.into())) + fn admin_move_market_to_resolved_categorical_disputed(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `4993 + r * (16 ±0)` + // Estimated: `30769` + // Minimum execution time: 188_101 nanoseconds. + Weight::from_parts(213_433_833, 30769) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsForEdit (r:1 w:0) /// Proof: PredictionMarkets MarketIdsForEdit (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) fn approve_market() -> Weight { // Proof Size summary in bytes: - // Measured: `577` - // Estimated: `10266` - // Minimum execution time: 50_411 nanoseconds. - Weight::from_parts(56_920_000, 10266) + // Measured: `580` + // Estimated: `10402` + // Minimum execution time: 50_640 nanoseconds. + Weight::from_parts(56_810_000, 10402) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsForEdit (r:1 w:1) /// Proof: PredictionMarkets MarketIdsForEdit (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// The range of component `r` is `[0, 1024]`. fn request_edit(r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `516` - // Estimated: `6542` - // Minimum execution time: 25_380 nanoseconds. - Weight::from_parts(29_805_022, 6542) - // Standard Error: 89 - .saturating_add(Weight::from_parts(2_565, 0).saturating_mul(r.into())) + // Measured: `519` + // Estimated: `6678` + // Minimum execution time: 25_330 nanoseconds. + Weight::from_parts(29_109_736, 6678) + // Standard Error: 94 + .saturating_add(Weight::from_parts(2_012, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:64 w:64) @@ -253,12 +253,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 64]`. fn buy_complete_set(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `517` - // Estimated: `5624 + a * (5116 ±0)` - // Minimum execution time: 95_871 nanoseconds. - Weight::from_parts(71_923_870, 5624) - // Standard Error: 32_395 - .saturating_add(Weight::from_parts(20_437_528, 0).saturating_mul(a.into())) + // Measured: `554` + // Estimated: `5760 + a * (5116 ±0)` + // Minimum execution time: 97_011 nanoseconds. + Weight::from_parts(68_920_978, 5760) + // Standard Error: 34_434 + .saturating_add(Weight::from_parts(20_262_853, 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(1)) @@ -274,23 +274,21 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:0 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// The range of component `m` is `[0, 63]`. - fn create_market(m: u32) -> Weight { + fn create_market(_m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `240 + m * (16 ±0)` // Estimated: `8263` - // Minimum execution time: 51_380 nanoseconds. - Weight::from_parts(62_038_312, 8263) - // Standard Error: 2_844 - .saturating_add(Weight::from_parts(30_077, 0).saturating_mul(m.into())) + // Minimum execution time: 51_080 nanoseconds. + Weight::from_parts(64_414_026, 8263) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: PredictionMarkets MarketIdsForEdit (r:1 w:1) /// Proof: PredictionMarkets MarketIdsForEdit (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: Timestamp Now (r:1 w:0) @@ -298,17 +296,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[0, 63]`. fn edit_market(m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `732 + m * (16 ±0)` - // Estimated: `10570` - // Minimum execution time: 51_090 nanoseconds. - Weight::from_parts(60_075_585, 10570) - // Standard Error: 2_917 - .saturating_add(Weight::from_parts(19_784, 0).saturating_mul(m.into())) + // Measured: `735 + m * (16 ±0)` + // Estimated: `10706` + // Minimum execution time: 50_710 nanoseconds. + Weight::from_parts(60_055_013, 10706) + // Standard Error: 3_263 + .saturating_add(Weight::from_parts(43_939, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// 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) @@ -329,12 +327,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `1209 + a * (118 ±0) + o * (16 ±0)` - // Estimated: `17802 + a * (5196 ±0)` - // Minimum execution time: 182_280 nanoseconds. - Weight::from_parts(151_158_106, 17802) - // Standard Error: 44_597 - .saturating_add(Weight::from_parts(34_641_935, 0).saturating_mul(a.into())) + // 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)) @@ -342,7 +340,7 @@ impl WeightInfoZeitgeist for WeightInfo { .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(542), added: 3017, mode: MaxEncodedLen) + /// 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) @@ -360,12 +358,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 64]`. fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1075 + a * (119 ±0)` - // Estimated: `14277 + a * (5196 ±0)` - // Minimum execution time: 184_150 nanoseconds. - Weight::from_parts(112_872_816, 14277) - // Standard Error: 51_522 - .saturating_add(Weight::from_parts(35_723_201, 0).saturating_mul(a.into())) + // Measured: `1112 + a * (119 ±0)` + // Estimated: `14413 + a * (5196 ±0)` + // Minimum execution time: 185_011 nanoseconds. + Weight::from_parts(149_265_363, 14413) + // Standard Error: 44_213 + .saturating_add(Weight::from_parts(33_798_818, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -373,7 +371,7 @@ impl WeightInfoZeitgeist for WeightInfo { .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(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: GlobalDisputes GlobalDisputesInfo (r:1 w:1) /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: Court MarketIdToCourtId (r:1 w:0) @@ -394,45 +392,49 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: PredictionMarkets MarketIdsPerDisputeBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// The range of component `m` is `[1, 64]`. /// The range of component `n` is `[1, 64]`. - fn start_global_dispute(_m: u32, _n: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `9154 + m * (16 ±0)` - // Estimated: `329581` - // Minimum execution time: 316_651 nanoseconds. - Weight::from_parts(378_091_905, 329581) + fn start_global_dispute(m: u32, n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `9191 + m * (16 ±0)` + // Estimated: `329717` + // Minimum execution time: 319_451 nanoseconds. + Weight::from_parts(355_402_117, 329717) + // Standard Error: 15_398 + .saturating_add(Weight::from_parts(188_238, 0).saturating_mul(m.into())) + // Standard Error: 15_398 + .saturating_add(Weight::from_parts(21_335, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(40)) .saturating_add(T::DbWeight::get().writes(36)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) fn dispute_authorized() -> Weight { // Proof Size summary in bytes: - // Measured: `623` - // Estimated: `6741` - // Minimum execution time: 42_290 nanoseconds. - Weight::from_parts(48_340_000, 6741) + // Measured: `626` + // Estimated: `6877` + // Minimum execution time: 42_770 nanoseconds. + Weight::from_parts(51_110_000, 6877) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsForEdit (r:0 w:1) /// Proof: PredictionMarkets MarketIdsForEdit (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) fn handle_expired_advised_market() -> Weight { // Proof Size summary in bytes: - // Measured: `536` - // Estimated: `6741` - // Minimum execution time: 55_630 nanoseconds. - Weight::from_parts(67_860_000, 6741) + // Measured: `539` + // Estimated: `6877` + // Minimum execution time: 56_180 nanoseconds. + Weight::from_parts(62_890_000, 6877) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: MarketCommons MarketPool (r:1 w:0) @@ -441,15 +443,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) fn internal_resolve_categorical_reported() -> Weight { // Proof Size summary in bytes: - // Measured: `4316` - // Estimated: `15390` - // Minimum execution time: 107_881 nanoseconds. - Weight::from_parts(121_701_000, 15390) + // Measured: `4319` + // Estimated: `15526` + // Minimum execution time: 108_570 nanoseconds. + Weight::from_parts(126_090_000, 15526) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: GlobalDisputes GlobalDisputesInfo (r:1 w:0) @@ -462,30 +464,30 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Swaps Pools (max_values: None, max_size: Some(3651), added: 6126, mode: MaxEncodedLen) fn internal_resolve_categorical_disputed() -> Weight { // Proof Size summary in bytes: - // Measured: `4571` - // Estimated: `20785` - // Minimum execution time: 154_130 nanoseconds. - Weight::from_parts(180_720_000, 20785) + // Measured: `4574` + // Estimated: `20921` + // Minimum execution time: 156_021 nanoseconds. + Weight::from_parts(163_240_000, 20921) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: MarketCommons MarketPool (r:1 w:0) /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn internal_resolve_scalar_reported() -> Weight { // Proof Size summary in bytes: - // Measured: `623` - // Estimated: `9264` - // Minimum execution time: 58_790 nanoseconds. - Weight::from_parts(72_680_000, 9264) + // Measured: `626` + // Estimated: `9400` + // Minimum execution time: 59_240 nanoseconds. + Weight::from_parts(66_770_000, 9400) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: GlobalDisputes GlobalDisputesInfo (r:1 w:0) @@ -496,10 +498,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn internal_resolve_scalar_disputed() -> Weight { // Proof Size summary in bytes: - // Measured: `893` - // Estimated: `14659` - // Minimum execution time: 101_510 nanoseconds. - Weight::from_parts(124_291_000, 14659) + // Measured: `896` + // Estimated: `14795` + // Minimum execution time: 101_160 nanoseconds. + Weight::from_parts(114_551_000, 14795) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -525,8 +527,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `79` // Estimated: `23164` - // Minimum execution time: 33_631 nanoseconds. - Weight::from_parts(41_000_000, 23164) + // Minimum execution time: 32_670 nanoseconds. + Weight::from_parts(37_650_000, 23164) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -537,15 +539,15 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `63 + a * (33 ±0)` // Estimated: `1024` - // Minimum execution time: 5_300 nanoseconds. - Weight::from_parts(8_533_292, 1024) - // Standard Error: 5_298 - .saturating_add(Weight::from_parts(350_621, 0).saturating_mul(a.into())) + // Minimum execution time: 4_950 nanoseconds. + Weight::from_parts(7_737_240, 1024) + // Standard Error: 4_953 + .saturating_add(Weight::from_parts(354_749, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:1 w:1) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) @@ -554,15 +556,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn redeem_shares_categorical() -> Weight { // Proof Size summary in bytes: - // Measured: `2025` - // Estimated: `10740` - // Minimum execution time: 92_021 nanoseconds. - Weight::from_parts(107_000_000, 10740) + // Measured: `2062` + // Estimated: `10876` + // Minimum execution time: 92_011 nanoseconds. + Weight::from_parts(97_061_000, 10876) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:2 w:2) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) @@ -571,15 +573,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn redeem_shares_scalar() -> Weight { // Proof Size summary in bytes: - // Measured: `1172` - // Estimated: `15856` - // Minimum execution time: 115_351 nanoseconds. - Weight::from_parts(133_451_000, 15856) + // Measured: `1209` + // Estimated: `15992` + // Minimum execution time: 116_260 nanoseconds. + Weight::from_parts(130_120_000, 15992) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) @@ -593,21 +595,21 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 1024]`. fn reject_market(c: u32, o: u32, r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `720 + c * (16 ±0) + o * (16 ±0)` - // Estimated: `13791` - // Minimum execution time: 93_510 nanoseconds. - Weight::from_parts(103_530_583, 13791) - // Standard Error: 3_892 - .saturating_add(Weight::from_parts(48_739, 0).saturating_mul(c.into())) - // Standard Error: 3_892 - .saturating_add(Weight::from_parts(37_200, 0).saturating_mul(o.into())) - // Standard Error: 239 - .saturating_add(Weight::from_parts(1_651, 0).saturating_mul(r.into())) + // Measured: `723 + c * (16 ±0) + o * (16 ±0)` + // Estimated: `13927` + // Minimum execution time: 94_580 nanoseconds. + 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)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Timestamp Now (r:1 w:0) /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) @@ -615,32 +617,32 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[0, 63]`. fn report_market_with_dispute_mechanism(m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `746` - // Estimated: `7037` - // Minimum execution time: 40_680 nanoseconds. - Weight::from_parts(48_449_855, 7037) - // Standard Error: 2_426 - .saturating_add(Weight::from_parts(791, 0).saturating_mul(m.into())) + // Measured: `749` + // Estimated: `7173` + // Minimum execution time: 40_560 nanoseconds. + Weight::from_parts(47_301_834, 7173) + // Standard Error: 2_470 + .saturating_add(Weight::from_parts(18_180, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: MarketCommons MarketPool (r:1 w:0) /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn report_trusted_market() -> Weight { // Proof Size summary in bytes: - // Measured: `535` - // Estimated: `9264` - // Minimum execution time: 79_570 nanoseconds. - Weight::from_parts(90_940_000, 9264) + // Measured: `538` + // Estimated: `9400` + // Minimum execution time: 80_430 nanoseconds. + Weight::from_parts(91_210_000, 9400) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:64 w:64) @@ -650,12 +652,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 64]`. fn sell_complete_set(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `757 + a * (161 ±0)` - // Estimated: `5624 + a * (5116 ±0)` - // Minimum execution time: 110_910 nanoseconds. - Weight::from_parts(79_060_998, 5624) - // Standard Error: 41_575 - .saturating_add(Weight::from_parts(28_109_907, 0).saturating_mul(a.into())) + // Measured: `794 + a * (161 ±0)` + // Estimated: `5760 + a * (5116 ±0)` + // Minimum execution time: 111_700 nanoseconds. + Weight::from_parts(63_689_088, 5760) + // Standard Error: 37_295 + .saturating_add(Weight::from_parts(28_230_283, 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(1)) @@ -665,7 +667,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Swaps NextPoolId (r:1 w:1) /// Proof: Swaps NextPoolId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:1) /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) /// Storage: MarketCommons MarketPool (r:1 w:1) @@ -677,64 +679,64 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 64]`. fn start_subsidy(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `526` - // Estimated: `9870` - // Minimum execution time: 43_640 nanoseconds. - Weight::from_parts(50_437_413, 9870) - // Standard Error: 2_457 - .saturating_add(Weight::from_parts(72_183, 0).saturating_mul(a.into())) + // Measured: `529` + // Estimated: `10006` + // Minimum execution time: 43_680 nanoseconds. + Weight::from_parts(50_565_118, 10006) + // Standard Error: 2_953 + .saturating_add(Weight::from_parts(83_396, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: PredictionMarkets MarketIdsPerOpenBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerOpenBlock (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:62 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// The range of component `b` is `[1, 31]`. /// The range of component `f` is `[1, 31]`. fn market_status_manager(b: u32, f: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2536 + b * (326 ±0) + f * (328 ±0)` - // Estimated: `7050 + b * (3017 ±0) + f * (3017 ±0)` - // Minimum execution time: 161_031 nanoseconds. - Weight::from_parts(57_887_830, 7050) - // Standard Error: 23_561 - .saturating_add(Weight::from_parts(3_969_574, 0).saturating_mul(b.into())) - // Standard Error: 23_561 - .saturating_add(Weight::from_parts(3_900_962, 0).saturating_mul(f.into())) + // Measured: `2536 + b * (329 ±0) + f * (331 ±0)` + // Estimated: `7050 + b * (3153 ±0) + f * (3153 ±0)` + // Minimum execution time: 159_650 nanoseconds. + Weight::from_parts(51_830_020, 7050) + // Standard Error: 23_348 + .saturating_add(Weight::from_parts(3_911_908, 0).saturating_mul(b.into())) + // Standard Error: 23_348 + .saturating_add(Weight::from_parts(4_074_663, 0).saturating_mul(f.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(f.into()))) .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 3017).saturating_mul(b.into())) - .saturating_add(Weight::from_parts(0, 3017).saturating_mul(f.into())) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(f.into())) } /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:62 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerDisputeBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerDisputeBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// The range of component `r` is `[1, 31]`. /// The range of component `d` is `[1, 31]`. fn market_resolution_manager(r: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2487 + r * (326 ±0) + d * (328 ±0)` - // Estimated: `7034 + r * (3017 ±0) + d * (3017 ±0)` - // Minimum execution time: 159_230 nanoseconds. - Weight::from_parts(52_321_709, 7034) - // Standard Error: 23_193 - .saturating_add(Weight::from_parts(4_099_230, 0).saturating_mul(r.into())) - // Standard Error: 23_193 - .saturating_add(Weight::from_parts(4_073_704, 0).saturating_mul(d.into())) + // Measured: `2487 + r * (329 ±0) + d * (331 ±0)` + // Estimated: `7034 + r * (3153 ±0) + d * (3153 ±0)` + // Minimum execution time: 158_350 nanoseconds. + Weight::from_parts(46_712_053, 7034) + // Standard Error: 23_276 + .saturating_add(Weight::from_parts(4_006_693, 0).saturating_mul(r.into())) + // Standard Error: 23_276 + .saturating_add(Weight::from_parts(4_167_508, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 3017).saturating_mul(r.into())) - .saturating_add(Weight::from_parts(0, 3017).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(d.into())) } /// Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) /// Proof: PredictionMarkets MarketsCollectingSubsidy (max_values: Some(1), max_size: Some(529), added: 1024, mode: MaxEncodedLen) @@ -742,149 +744,157 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `27` // Estimated: `1024` - // Minimum execution time: 5_050 nanoseconds. - Weight::from_parts(5_970_000, 1024) + // Minimum execution time: 4_990 nanoseconds. + Weight::from_parts(5_360_000, 1024) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Balances Reserves (r:1 w:1) - /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketCounter (r:1 w:1) - /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:4) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:2 w:2) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:0 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) - /// The range of component `m` is `[0, 63]`. - fn create_market_and_deploy_pool(m: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `376 + m * (16 ±0)` - // Estimated: `36032` - // Minimum execution time: 236_421 nanoseconds. - Weight::from_parts(276_946_079, 36032) - // Standard Error: 13_117 - .saturating_add(Weight::from_parts(6_946, 0).saturating_mul(m.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(13)) - } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// The range of component `o` is `[0, 63]`. + /// The range of component `n` is `[0, 63]`. fn schedule_early_close_as_authority(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `747 + o * (16 ±0)` - // Estimated: `10670` - // Minimum execution time: 33_000 nanoseconds. - Weight::from_parts(34_212_254, 10670) - // Standard Error: 250 - .saturating_add(Weight::from_ref_time(11_624).saturating_mul(o.into())) - // Standard Error: 250 - .saturating_add(Weight::from_ref_time(2_400).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + // Estimated: `10706` + // Minimum execution time: 48_910 nanoseconds. + Weight::from_parts(55_628_507, 10706) + // Standard Error: 2_340 + .saturating_add(Weight::from_parts(63_826, 0).saturating_mul(o.into())) + // Standard Error: 2_340 + .saturating_add(Weight::from_parts(10_393, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `972 + o * (16 ±0)` - // Estimated: `14394` - // Minimum execution time: 69_000 nanoseconds. - Weight::from_parts(70_799_236, 14394) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(14_923).saturating_mul(o.into())) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(15_232).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + /// The range of component `o` is `[0, 63]`. + /// The range of component `n` is `[0, 63]`. + fn schedule_early_close_after_dispute(o: u32, _n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `970 + o * (16 ±0)` + // Estimated: `14430` + // Minimum execution time: 92_361 nanoseconds. + Weight::from_parts(106_196_533, 14430) + // Standard Error: 4_746 + .saturating_add(Weight::from_parts(35_448, 0).saturating_mul(o.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn schedule_early_close_as_market_creator(o: u32, n: u32) -> Weight { + /// The range of component `o` is `[0, 63]`. + /// The range of component `n` is `[0, 63]`. + fn schedule_early_close_as_market_creator(_o: u32, _n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `808 + o * (16 ±0)` - // Estimated: `14394` - // Minimum execution time: 50_000 nanoseconds. - Weight::from_parts(50_314_327, 14394) - // Standard Error: 380 - .saturating_add(Weight::from_ref_time(25_189).saturating_mul(o.into())) - // Standard Error: 380 - .saturating_add(Weight::from_ref_time(19_089).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Estimated: `14430` + // Minimum execution time: 70_741 nanoseconds. + Weight::from_parts(86_536_582, 14430) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// The range of component `o` is `[0, 63]`. + /// The range of component `n` is `[0, 63]`. fn dispute_early_close(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `794 + o * (16 ±0) + n * (16 ±0)` - // Estimated: `13891` - // Minimum execution time: 43_000 nanoseconds. - Weight::from_parts(45_632_400, 13891) - // Standard Error: 439 - .saturating_add(Weight::from_ref_time(13_851).saturating_mul(o.into())) - // Standard Error: 439 - .saturating_add(Weight::from_ref_time(14_491).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Measured: `924 + o * (16 ±0) + n * (16 ±0)` + // Estimated: `14430` + // Minimum execution time: 68_421 nanoseconds. + Weight::from_parts(74_036_489, 14430) + // Standard Error: 3_573 + .saturating_add(Weight::from_parts(69_851, 0).saturating_mul(o.into())) + // Standard Error: 3_573 + .saturating_add(Weight::from_parts(43_246, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// The range of component `o` is `[0, 63]`. + /// The range of component `n` is `[0, 63]`. fn reject_early_close_after_authority(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `686 + o * (16 ±0) + n * (16 ±0)` - // Estimated: `10167` - // Minimum execution time: 33_000 nanoseconds. - Weight::from_parts(33_100_036, 10167) - // Standard Error: 329 - .saturating_add(Weight::from_ref_time(21_279).saturating_mul(o.into())) - // Standard Error: 329 - .saturating_add(Weight::from_ref_time(26_133).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + // Measured: `814 + o * (16 ±0) + n * (16 ±0)` + // Estimated: `10706` + // Minimum execution time: 53_440 nanoseconds. + Weight::from_parts(57_491_752, 10706) + // Standard Error: 2_714 + .saturating_add(Weight::from_parts(50_284, 0).saturating_mul(o.into())) + // Standard Error: 2_714 + .saturating_add(Weight::from_parts(53_840, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(642), added: 3117, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) fn reject_early_close_after_dispute() -> Weight { // Proof Size summary in bytes: // Measured: `672` - // Estimated: `6841` - // Minimum execution time: 50_000 nanoseconds. - Weight::from_parts(53_000_000, 6841) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + // Estimated: `6877` + // Minimum execution time: 66_780 nanoseconds. + Weight::from_parts(80_781_000, 6877) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + /// Storage: MarketCommons MarketCounter (r:1 w:1) + /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:4 w:4) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: Tokens TotalIssuance (r:2 w:2) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) + /// Storage: NeoSwaps Pools (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) + /// Storage: MarketCommons Markets (r:0 w:1) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// The range of component `m` is `[0, 63]`. + fn create_market_and_deploy_pool(m: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `410 + m * (16 ±0)` + // Estimated: `36032` + // Minimum execution time: 238_861 nanoseconds. + Weight::from_parts(280_682_707, 36032) + // Standard Error: 11_735 + .saturating_add(Weight::from_parts(24_164, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(13)) } } diff --git a/zrml/rikiddo/Cargo.toml b/zrml/rikiddo/Cargo.toml index 1f1d0c5e1..8e010ffd8 100644 --- a/zrml/rikiddo/Cargo.toml +++ b/zrml/rikiddo/Cargo.toml @@ -42,4 +42,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-rikiddo" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/simple-disputes/Cargo.toml b/zrml/simple-disputes/Cargo.toml index d50e3ca88..aaef49233 100644 --- a/zrml/simple-disputes/Cargo.toml +++ b/zrml/simple-disputes/Cargo.toml @@ -41,4 +41,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-simple-disputes" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/styx/Cargo.toml b/zrml/styx/Cargo.toml index 3454d3e80..d43ee274d 100644 --- a/zrml/styx/Cargo.toml +++ b/zrml/styx/Cargo.toml @@ -36,4 +36,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-styx" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/styx/src/weights.rs b/zrml/styx/src/weights.rs index 16629bd19..e789d22d1 100644 --- a/zrml/styx/src/weights.rs +++ b/zrml/styx/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_styx //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -64,8 +64,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `76` // Estimated: `3034` - // Minimum execution time: 32_200 nanoseconds. - Weight::from_parts(39_480_000, 3034) + // Minimum execution time: 31_250 nanoseconds. + Weight::from_parts(32_820_000, 3034) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -75,7 +75,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_620 nanoseconds. - Weight::from_parts(12_290_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 10_430 nanoseconds. + Weight::from_parts(11_110_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 84cdca827..9eb5df1f7 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -66,4 +66,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps" -version = "0.4.1" +version = "0.4.2" diff --git a/zrml/swaps/rpc/Cargo.toml b/zrml/swaps/rpc/Cargo.toml index b290453ef..eeb1d9331 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.1" +version = "0.4.2" diff --git a/zrml/swaps/runtime-api/Cargo.toml b/zrml/swaps/runtime-api/Cargo.toml index 31130f1a7..63d545d46 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.1" +version = "0.4.2" diff --git a/zrml/swaps/src/weights.rs b/zrml/swaps/src/weights.rs index b3d827065..84850809d 100644 --- a/zrml/swaps/src/weights.rs +++ b/zrml/swaps/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2023-10-26`, STEPS: `10`, REPEAT: `1000`, 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` @@ -81,7 +81,7 @@ pub trait WeightInfoZeitgeist { pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// 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) @@ -89,27 +89,27 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[3, 65]`. fn admin_clean_up_pool_cpmm_categorical(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `694 + a * (54 ±0)` - // Estimated: `11666` - // Minimum execution time: 42_641 nanoseconds. - Weight::from_parts(47_869_882, 11666) - // Standard Error: 3_432 - .saturating_add(Weight::from_parts(431_932, 0).saturating_mul(a.into())) + // 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(542), added: 3017, mode: MaxEncodedLen) + /// 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: `886` - // Estimated: `11666` - // Minimum execution time: 38_730 nanoseconds. - Weight::from_parts(41_340_000, 11666) + // 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)) } @@ -126,12 +126,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[0, 63]`. fn apply_to_cached_pools_execute_arbitrage(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `3255 + a * (11514 ±0)` + // Measured: `3289 + a * (11514 ±0)` // Estimated: `163651 + a * (182700 ±0)` - // Minimum execution time: 1_020 nanoseconds. - Weight::from_parts(1_170_000, 163651) - // Standard Error: 1_165_603 - .saturating_add(Weight::from_parts(2_439_995_297, 0).saturating_mul(a.into())) + // 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)) @@ -145,10 +145,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `27 + a * (27 ±0)` // Estimated: `2499 + a * (2499 ±0)` - // Minimum execution time: 1_020 nanoseconds. - Weight::from_parts(1_260_000, 2499) - // Standard Error: 2_431 - .saturating_add(Weight::from_parts(10_034_511, 0).saturating_mul(a.into())) + // 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()))) @@ -165,12 +165,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[0, 10]`. fn destroy_pool_in_subsidy_phase(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `805 + a * (297 ±0)` + // Measured: `839 + a * (297 ±0)` // Estimated: `11476 + a * (5153 ±0)` - // Minimum execution time: 31_120 nanoseconds. - Weight::from_parts(49_313_427, 11476) - // Standard Error: 38_686 - .saturating_add(Weight::from_parts(22_850_290, 0).saturating_mul(a.into())) + // 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)) @@ -187,14 +187,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `b` is `[0, 10]`. fn distribute_pool_share_rewards(a: u32, b: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `662 + a * (414 ±0) + b * (161 ±0)` + // Measured: `696 + a * (414 ±0) + b * (161 ±0)` // Estimated: `19084 + a * (7887 ±5) + b * (5500 ±5)` - // Minimum execution time: 510_031 nanoseconds. - Weight::from_parts(101_354_611, 19084) - // Standard Error: 164_834 - .saturating_add(Weight::from_parts(28_284_866, 0).saturating_mul(a.into())) - // Standard Error: 164_834 - .saturating_add(Weight::from_parts(43_566_707, 0).saturating_mul(b.into())) + // 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()))) @@ -221,12 +221,12 @@ impl WeightInfoZeitgeist for WeightInfo { // 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_210 nanoseconds. - Weight::from_parts(17_640_000, 14083) - // Standard Error: 95_168 - .saturating_add(Weight::from_parts(21_645_458, 0).saturating_mul(a.into())) - // Standard Error: 632_038 - .saturating_add(Weight::from_parts(92_562_032, 0).saturating_mul(b.into())) + // 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()))) @@ -246,12 +246,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn execute_arbitrage_buy_burn(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `765 + a * (215 ±0)` + // Measured: `799 + a * (215 ±0)` // Estimated: `13849 + a * (5005 ±0)` - // Minimum execution time: 103_460 nanoseconds. - Weight::from_parts(76_441_913, 13849) - // Standard Error: 30_115 - .saturating_add(Weight::from_parts(41_965_359, 0).saturating_mul(a.into())) + // 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()))) @@ -268,12 +268,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn execute_arbitrage_mint_sell(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `563 + a * (215 ±0)` + // Measured: `597 + a * (215 ±0)` // Estimated: `16456 + a * (5005 ±0)` - // Minimum execution time: 108_620 nanoseconds. - Weight::from_parts(52_955_739, 16456) - // Standard Error: 38_786 - .saturating_add(Weight::from_parts(38_657_536, 0).saturating_mul(a.into())) + // 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)) @@ -287,12 +287,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn execute_arbitrage_skipped(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `406 + a * (167 ±0)` + // Measured: `440 + a * (167 ±0)` // Estimated: `6126 + a * (2598 ±0)` - // Minimum execution time: 30_730 nanoseconds. - Weight::from_parts(30_428_786, 6126) - // Standard Error: 3_743 - .saturating_add(Weight::from_parts(5_642_095, 0).saturating_mul(a.into())) + // 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())) @@ -308,12 +308,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_exit(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1015 + a * (286 ±0)` + // Measured: `1049 + a * (286 ±0)` // Estimated: `13849 + a * (5196 ±0)` - // Minimum execution time: 113_581 nanoseconds. - Weight::from_parts(65_994_621, 13849) - // Standard Error: 16_079 - .saturating_add(Weight::from_parts(30_544_652, 0).saturating_mul(a.into())) + // 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())) .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)) @@ -328,10 +328,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `2459` + // Measured: `2493` // Estimated: `11279` - // Minimum execution time: 52_520 nanoseconds. - Weight::from_parts(65_610_000, 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)) } @@ -347,10 +347,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `5392` + // Measured: `5426` // Estimated: `19045` - // Minimum execution time: 116_341 nanoseconds. - Weight::from_parts(144_710_000, 19045) + // Minimum execution time: 115_681 nanoseconds. + Weight::from_parts(135_621_000, 19045) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -366,10 +366,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `5392` + // Measured: `5426` // Estimated: `19045` - // Minimum execution time: 116_160 nanoseconds. - Weight::from_parts(145_430_000, 19045) + // Minimum execution time: 115_811 nanoseconds. + Weight::from_parts(135_051_000, 19045) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -382,12 +382,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_join(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `876 + a * (286 ±0)` + // Measured: `910 + a * (286 ±0)` // Estimated: `11242 + a * (5196 ±0)` - // Minimum execution time: 102_271 nanoseconds. - Weight::from_parts(63_625_000, 11242) - // Standard Error: 14_357 - .saturating_add(Weight::from_parts(29_899_158, 0).saturating_mul(a.into())) + // 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())) .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)) @@ -402,10 +402,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `2357` + // Measured: `2391` // Estimated: `11279` - // Minimum execution time: 53_771 nanoseconds. - Weight::from_parts(67_210_000, 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)) } @@ -419,10 +419,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `5947` + // Measured: `5981` // Estimated: `16438` - // Minimum execution time: 98_810 nanoseconds. - Weight::from_parts(122_971_000, 16438) + // Minimum execution time: 97_960 nanoseconds. + Weight::from_parts(114_620_000, 16438) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -436,10 +436,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `5947` + // Measured: `5981` // Estimated: `16438` - // Minimum execution time: 99_091 nanoseconds. - Weight::from_parts(124_120_000, 16438) + // Minimum execution time: 98_781 nanoseconds. + Weight::from_parts(108_660_000, 16438) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -450,17 +450,17 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `209 + a * (54 ±0)` // Estimated: `6126` - // Minimum execution time: 11_050 nanoseconds. - Weight::from_parts(13_881_024, 6126) - // Standard Error: 734 - .saturating_add(Weight::from_parts(216_221, 0).saturating_mul(a.into())) + // 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)) } /// 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(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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) @@ -469,17 +469,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `5489` - // Estimated: `22142` - // Minimum execution time: 180_701 nanoseconds. - Weight::from_parts(224_470_000, 22142) + // 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(542), added: 3017, mode: MaxEncodedLen) + /// 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) @@ -493,12 +493,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[3, 65]`. fn swap_exact_amount_in_rikiddo(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2123 + a * (83 ±0)` - // Estimated: `27878 + a * (2352 ±1)` - // Minimum execution time: 208_141 nanoseconds. - Weight::from_parts(193_459_057, 27878) - // Standard Error: 10_676 - .saturating_add(Weight::from_parts(22_649_984, 0).saturating_mul(a.into())) + // 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)) @@ -509,17 +509,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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(542), added: 3017, mode: MaxEncodedLen) + /// 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: `5489` - // Estimated: `22142` - // Minimum execution time: 176_021 nanoseconds. - Weight::from_parts(217_441_000, 22142) + // 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)) } @@ -528,7 +528,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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(542), added: 3017, mode: MaxEncodedLen) + /// 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) @@ -538,12 +538,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[3, 65]`. fn swap_exact_amount_out_rikiddo(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2037 + a * (85 ±0)` - // Estimated: `27869 + a * (2352 ±1)` - // Minimum execution time: 189_271 nanoseconds. - Weight::from_parts(133_210_306, 27869) - // Standard Error: 17_287 - .saturating_add(Weight::from_parts(37_283_083, 0).saturating_mul(a.into())) + // 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)) @@ -556,10 +556,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `209 + a * (54 ±0)` // Estimated: `6126` - // Minimum execution time: 17_880 nanoseconds. - Weight::from_parts(20_404_387, 6126) - // Standard Error: 1_449 - .saturating_add(Weight::from_parts(414_442, 0).saturating_mul(a.into())) + // 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())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -570,10 +570,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `209 + a * (54 ±0)` // Estimated: `6126` - // Minimum execution time: 15_710 nanoseconds. - Weight::from_parts(19_818_585, 6126) - // Standard Error: 694 - .saturating_add(Weight::from_parts(246_286, 0).saturating_mul(a.into())) + // 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())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -588,12 +588,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: `576 + a * (215 ±0)` + // Measured: `610 + a * (215 ±0)` // Estimated: `8733 + a * (5116 ±0)` - // Minimum execution time: 80_470 nanoseconds. - Weight::from_parts(42_172_169, 8733) - // Standard Error: 20_425 - .saturating_add(Weight::from_parts(28_834_240, 0).saturating_mul(a.into())) + // 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())) .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)) From 9eeae56f73ba4692a5307a607b837c19e0d91b14 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 6 Nov 2023 09:04:58 +0100 Subject: [PATCH 002/104] Remove migrations (#1180) --- runtime/common/src/lib.rs | 4 +- zrml/prediction-markets/src/migrations.rs | 343 +--------------------- 2 files changed, 4 insertions(+), 343 deletions(-) diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 0e29d2223..a0e38f37c 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -61,10 +61,10 @@ macro_rules! decl_common_types { type Address = sp_runtime::MultiAddress; #[cfg(feature = "parachain")] - type Migrations = (zrml_prediction_markets::migrations::AddEarlyCloseBonds,); + type Migrations = (); #[cfg(not(feature = "parachain"))] - type Migrations = (zrml_prediction_markets::migrations::AddEarlyCloseBonds,); + type Migrations = (); pub type Executive = frame_executive::Executive< Runtime, diff --git a/zrml/prediction-markets/src/migrations.rs b/zrml/prediction-markets/src/migrations.rs index 6c20312f8..72c952afc 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -16,349 +16,11 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -#[cfg(feature = "try-runtime")] -use crate::MarketIdOf; -use crate::{BalanceOf, Config, MomentOf}; -#[cfg(feature = "try-runtime")] -use alloc::collections::BTreeMap; -#[cfg(feature = "try-runtime")] -use alloc::format; -use alloc::vec::Vec; -#[cfg(feature = "try-runtime")] -use frame_support::migration::storage_key_iter; -use frame_support::{ - dispatch::Weight, - log, - pallet_prelude::PhantomData, - traits::{Get, OnRuntimeUpgrade, StorageVersion}, - RuntimeDebug, -}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::{traits::Saturating, Perbill}; -use zeitgeist_primitives::types::{ - Asset, Bond, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, - MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, -}; -#[cfg(feature = "try-runtime")] -use zrml_market_commons::MarketCommonsPalletApi; -use zrml_market_commons::Pallet as MarketCommonsPallet; - -#[cfg(any(feature = "try-runtime", test))] -const MARKET_COMMONS: &[u8] = b"MarketCommons"; -#[cfg(any(feature = "try-runtime", test))] -const MARKETS: &[u8] = b"Markets"; - -const MARKET_COMMONS_REQUIRED_STORAGE_VERSION: u16 = 8; -const MARKET_COMMONS_NEXT_STORAGE_VERSION: u16 = 9; - -#[derive(Clone, Decode, Encode, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct OldMarketBonds { - pub creation: Option>, - pub oracle: Option>, - pub outsider: Option>, - pub dispute: Option>, -} - -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct OldMarket { - /// Base asset of the market. - pub base_asset: A, - /// Creator of this market. - pub creator: AI, - /// Creation type. - pub creation: MarketCreation, - /// A fee that is charged each trade and given to the market creator. - pub creator_fee: Perbill, - /// Oracle that reports the outcome of this market. - pub oracle: AI, - /// Metadata for the market, usually a content address of IPFS - /// hosted JSON. Currently limited to 66 bytes (see `MaxEncodedLen` implementation) - pub metadata: Vec, - /// The type of the market. - pub market_type: MarketType, - /// Market start and end - pub period: MarketPeriod, - /// Market deadlines. - pub deadlines: Deadlines, - /// The scoring rule used for the market. - pub scoring_rule: ScoringRule, - /// The current status of the market. - pub status: MarketStatus, - /// The report of the market. Only `Some` if it has been reported. - pub report: Option>, - /// The resolved outcome. - pub resolved_outcome: Option, - /// See [`MarketDisputeMechanism`]. - pub dispute_mechanism: Option, - pub bonds: OldMarketBonds, -} - -type OldMarketOf = OldMarket< - ::AccountId, - BalanceOf, - ::BlockNumber, - MomentOf, - Asset<::MarketId>, ->; - -#[frame_support::storage_alias] -pub(crate) type Markets = StorageMap< - MarketCommonsPallet, - frame_support::Blake2_128Concat, - ::MarketId, - OldMarketOf, ->; - -pub struct AddEarlyCloseBonds(PhantomData); - -impl OnRuntimeUpgrade for AddEarlyCloseBonds { - 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!( - "AddEarlyCloseBonds: market-commons version is {:?}, but {:?} is required", - market_commons_version, - MARKET_COMMONS_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("AddEarlyCloseBonds: Starting..."); - let mut translated = 0u64; - zrml_market_commons::Markets::::translate::, _>(|_, old_market| { - translated.saturating_inc(); - - 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, - scoring_rule: old_market.scoring_rule, - status: old_market.status, - report: old_market.report, - resolved_outcome: old_market.resolved_outcome, - dispute_mechanism: old_market.dispute_mechanism, - deadlines: old_market.deadlines, - bonds: MarketBonds { - creation: old_market.bonds.creation, - oracle: old_market.bonds.oracle, - outsider: old_market.bonds.outsider, - dispute: old_market.bonds.dispute, - close_dispute: None, - close_request: None, - }, - early_close: None, - }; - - Some(new_market) - }); - log::info!("AddEarlyCloseBonds: 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!("AddEarlyCloseBonds: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - use frame_support::pallet_prelude::Blake2_128Concat; - let old_markets = storage_key_iter::, OldMarketOf, Blake2_128Concat>( - MARKET_COMMONS, - MARKETS, - ) - .collect::>(); - let markets = Markets::::iter_keys().count() as u32; - let decodable_markets = Markets::::iter_values().count() as u32; - if markets != decodable_markets { - log::error!( - "Can only decode {} of {} markets - others will be dropped", - decodable_markets, - markets - ); - } else { - log::info!("Markets: {}", markets); - } - Ok(old_markets.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - let old_markets: BTreeMap, OldMarketOf> = - Decode::decode(&mut &previous_state[..]) - .map_err(|_| "Failed to decode state: Invalid state")?; - let new_market_count = >::market_iter().count(); - assert_eq!(old_markets.len(), new_market_count); - for (market_id, new_market) in >::market_iter() { - let old_market = old_markets - .get(&market_id) - .expect(&format!("Market {:?} not found", market_id)[..]); - assert_eq!(new_market.base_asset, old_market.base_asset); - assert_eq!(new_market.creator, old_market.creator); - assert_eq!(new_market.creation, old_market.creation); - assert_eq!(new_market.creator_fee, old_market.creator_fee); - assert_eq!(new_market.oracle, old_market.oracle); - assert_eq!(new_market.metadata, old_market.metadata); - assert_eq!(new_market.market_type, old_market.market_type); - assert_eq!(new_market.period, old_market.period); - assert_eq!(new_market.deadlines, old_market.deadlines); - assert_eq!(new_market.scoring_rule, old_market.scoring_rule); - assert_eq!(new_market.status, old_market.status); - assert_eq!(new_market.report, old_market.report); - assert_eq!(new_market.resolved_outcome, old_market.resolved_outcome); - assert_eq!(new_market.dispute_mechanism, old_market.dispute_mechanism); - assert_eq!(new_market.bonds.oracle, old_market.bonds.oracle); - assert_eq!(new_market.bonds.creation, old_market.bonds.creation); - assert_eq!(new_market.bonds.outsider, old_market.bonds.outsider); - assert_eq!(new_market.creator_fee, old_market.creator_fee); - assert_eq!(new_market.bonds.dispute, old_market.bonds.dispute); - // new fields - assert_eq!(new_market.bonds.close_request, None); - assert_eq!(new_market.bonds.close_dispute, None); - assert_eq!(new_market.early_close, None); - } - - log::info!("AddEarlyCloseBonds: Market Counter post-upgrade is {}!", new_market_count); - assert!(new_market_count > 0); - Ok(()) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{ - mock::{ExtBuilder, Runtime}, - MarketIdOf, MarketOf, - }; - use frame_support::{ - dispatch::fmt::Debug, migration::put_storage_value, storage_root, Blake2_128Concat, - StateVersion, StorageHasher, - }; - use zrml_market_commons::MarketCommonsPalletApi; - - #[test] - fn on_runtime_upgrade_increments_the_storage_version() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - AddEarlyCloseBonds::::on_runtime_upgrade(); - assert_eq!( - StorageVersion::get::>(), - MARKET_COMMONS_NEXT_STORAGE_VERSION - ); - }); - } - - #[test] - fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - // Don't set up chain to signal that storage is already up to date. - let (_, new_markets) = construct_old_new_tuple(); - populate_test_data::, MarketOf>( - MARKET_COMMONS, - MARKETS, - new_markets.clone(), - ); - let tmp = storage_root(StateVersion::V1); - AddEarlyCloseBonds::::on_runtime_upgrade(); - assert_eq!(tmp, storage_root(StateVersion::V1)); - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_markets() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let (old_markets, new_markets) = construct_old_new_tuple(); - populate_test_data::, OldMarketOf>( - MARKET_COMMONS, - MARKETS, - old_markets, - ); - AddEarlyCloseBonds::::on_runtime_upgrade(); - let actual = >::market(&0u128).unwrap(); - assert_eq!(actual, new_markets[0]); - }); - } - - fn set_up_version() { - StorageVersion::new(MARKET_COMMONS_REQUIRED_STORAGE_VERSION) - .put::>(); - } - - fn construct_old_new_tuple() -> (Vec>, Vec>) { - let base_asset = Asset::Ztg; - let creator = 999; - let creator_fee = Perbill::from_parts(1); - let oracle = 2; - let metadata = vec![3, 4, 5]; - let market_type = MarketType::Categorical(6); - let period = MarketPeriod::Block(7..8); - let scoring_rule = ScoringRule::CPMM; - let status = MarketStatus::Disputed; - let creation = MarketCreation::Permissionless; - let report = None; - let resolved_outcome = None; - let deadlines = Deadlines::default(); - let dispute_mechanism = Some(MarketDisputeMechanism::Court); - let old_bonds = OldMarketBonds { - creation: Some(Bond::new(creator, ::ValidityBond::get())), - oracle: Some(Bond::new(creator, ::OracleBond::get())), - outsider: Some(Bond::new(creator, ::OutsiderBond::get())), - dispute: None, - }; - let new_bonds = MarketBonds { - creation: Some(Bond::new(creator, ::ValidityBond::get())), - oracle: Some(Bond::new(creator, ::OracleBond::get())), - outsider: Some(Bond::new(creator, ::OutsiderBond::get())), - dispute: None, - close_dispute: None, - close_request: None, - }; - - let old_market = OldMarket { - base_asset, - creator, - creation: creation.clone(), - creator_fee, - oracle, - metadata: metadata.clone(), - market_type: market_type.clone(), - period: period.clone(), - scoring_rule, - status, - report: report.clone(), - resolved_outcome: resolved_outcome.clone(), - dispute_mechanism: dispute_mechanism.clone(), - deadlines, - bonds: old_bonds, - }; - let new_market = Market { - base_asset, - creator, - creation, - creator_fee, - oracle, - metadata, - market_type, - period, - scoring_rule, - status, - report, - resolved_outcome, - dispute_mechanism, - deadlines, - bonds: new_bonds, - early_close: None, - }; - (vec![old_market], vec![new_market]) - } + use frame_support::{dispatch::fmt::Debug, migration::put_storage_value, StorageHasher}; + use parity_scale_codec::Encode; #[allow(unused)] fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) @@ -379,7 +41,6 @@ mod tests { // 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; From 186a8ab9b531ce9eb11b76288d22260107eb39f9 Mon Sep 17 00:00:00 2001 From: Chralt Date: Mon, 13 Nov 2023 10:26:03 +0100 Subject: [PATCH 003/104] Filter admin functions for main-net (#1190) filter admin functions for main-net --- runtime/zeitgeist/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 5d7bf6ae8..3a5a36e71 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -125,6 +125,7 @@ impl Contains for IsCallable { ScoringRule::RikiddoSigmoidFeeMarketEma, }; 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, }; @@ -177,6 +178,8 @@ impl Contains for IsCallable { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, + admin_move_market_to_closed { .. } => false, + admin_move_market_to_resolved { .. } => false, _ => true, } } From 9333393923b1ebacef0da6177f722aba7b83fb61 Mon Sep 17 00:00:00 2001 From: Chralt Date: Mon, 13 Nov 2023 10:26:45 +0100 Subject: [PATCH 004/104] Add debug assertions for slashes and reserves (#1188) * add debug assertions for missing slashes * place debug_assert for unreserves --- zrml/court/src/lib.rs | 11 +++++++- zrml/orderbook/src/lib.rs | 24 +++++++++++++++-- zrml/prediction-markets/src/lib.rs | 43 +++++++++++++++++++++++++++--- zrml/simple-disputes/src/lib.rs | 34 ++++++++++++++++++++--- zrml/styx/src/lib.rs | 10 +++++-- zrml/swaps/src/lib.rs | 42 ++++++++++++++++++++++++++--- 6 files changed, 147 insertions(+), 17 deletions(-) diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 0a5f9a77d..ed7e9a13b 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -2218,7 +2218,16 @@ mod pallet { debug_assert!(missing.is_zero()); overall_imbalance.subsume(imb); } else { - T::Currency::unreserve_named(&Self::reserve_id(), backer, *bond); + let missing = T::Currency::unreserve_named(&Self::reserve_id(), backer, *bond); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, who: {:?}, \ + amount: {:?}, missing: {:?}", + Self::reserve_id(), + backer, + bond, + missing, + ); } } diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index b416a6817..efa7340db 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -174,20 +174,40 @@ mod pallet { match order_data.side { OrderSide::Bid => { - T::AssetManager::unreserve_named( + let missing = T::AssetManager::unreserve_named( &Self::reserve_id(), order_data.base_asset, maker, order_data.base_asset_amount, ); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} \ + who: {:?}, amount: {:?}, missing: {:?}", + Self::reserve_id(), + order_data.base_asset, + maker, + order_data.base_asset_amount, + missing, + ); } OrderSide::Ask => { - T::AssetManager::unreserve_named( + let missing = T::AssetManager::unreserve_named( &Self::reserve_id(), order_data.outcome_asset, maker, order_data.outcome_asset_amount, ); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} \ + who: {:?}, amount: {:?}, missing: {:?}", + Self::reserve_id(), + order_data.outcome_asset, + maker, + order_data.outcome_asset_amount, + missing, + ); } } diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index e85cbb49e..5f20099fa 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -122,7 +122,14 @@ mod pallet { debug_assert!(false, "{}", warning); return Ok(()); } - T::Currency::unreserve_named(&Self::reserve_id(), &bond.who, bond.value); + let missing = T::Currency::unreserve_named(&Self::reserve_id(), &bond.who, bond.value); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, who: {:?}, value: {:?}.", + &Self::reserve_id(), + &bond.who, + bond.value, + ); >::mutate_market(market_id, |m| { m.bonds.$bond_type = Some(Bond { is_settled: true, ..bond.clone() }); Ok(()) @@ -182,7 +189,20 @@ mod pallet { debug_assert!(false, "{}", warning); } if unreserve_amount != BalanceOf::::zero() { - T::Currency::unreserve_named(&Self::reserve_id(), &bond.who, unreserve_amount); + let missing = T::Currency::unreserve_named( + &Self::reserve_id(), + &bond.who, + unreserve_amount, + ); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, \ + who: {:?}, amount: {:?}, missing: {:?}", + Self::reserve_id(), + &bond.who, + unreserve_amount, + missing, + ); } >::mutate_market(market_id, |m| { m.bonds.$bond_type = Some(Bond { is_settled: true, ..bond.clone() }); @@ -1063,7 +1083,15 @@ mod pallet { for (currency_id, payout, balance) in winning_assets { // Destroy the shares. - T::AssetManager::slash(currency_id, &sender, balance); + let missing = T::AssetManager::slash(currency_id, &sender, balance); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. currency_id {:?}, sender: {:?}, balance: \ + {:?}.", + currency_id, + &sender, + balance, + ); // Pay out the winner. let remaining_bal = @@ -2591,7 +2619,14 @@ mod pallet { // write last. for asset in assets.iter() { - T::AssetManager::slash(*asset, &who, amount); + let missing = T::AssetManager::slash(*asset, &who, amount); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. asset {:?}, who: {:?}, amount: {:?}.", + asset, + &who, + amount, + ); } T::AssetManager::transfer(market.base_asset, &market_account, &who, amount)?; diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 859a0f0ad..a29ce31ab 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -55,7 +55,7 @@ mod pallet { }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{CheckedDiv, Saturating}, + traits::{CheckedDiv, Saturating, Zero}, DispatchError, SaturatedConversion, }; @@ -375,19 +375,36 @@ mod pallet { for dispute in disputes.iter() { if &dispute.outcome == resolved_outcome { - T::Currency::unreserve_named( + let missing = T::Currency::unreserve_named( &Self::reserve_id(), &dispute.by, dispute.bond.saturated_into::().saturated_into(), ); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, who: {:?}, \ + amount: {:?}, missing: {:?}", + Self::reserve_id(), + &dispute.by, + dispute.bond.saturated_into::(), + missing, + ); correct_reporters.push(dispute.by.clone()); } else { - let (imbalance, _) = T::Currency::slash_reserved_named( + let (imbalance, missing) = T::Currency::slash_reserved_named( &Self::reserve_id(), &dispute.by, dispute.bond.saturated_into::().saturated_into(), ); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. reserve_id {:?}, who: {:?}, amount: \ + {:?}.", + &Self::reserve_id(), + &dispute.by, + dispute.bond.saturated_into::(), + ); overall_imbalance.subsume(imbalance); } } @@ -490,11 +507,20 @@ mod pallet { if market.status == MarketStatus::Disputed { disputes_len = Disputes::::decode_len(market_id).unwrap_or(0) as u32; for dispute in Disputes::::take(market_id).iter() { - T::Currency::unreserve_named( + let missing = T::Currency::unreserve_named( &Self::reserve_id(), &dispute.by, dispute.bond.saturated_into::().saturated_into(), ); + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, who: {:?}, \ + amount: {:?}, missing: {:?}", + Self::reserve_id(), + &dispute.by, + dispute.bond.saturated_into::(), + missing, + ); } } diff --git a/zrml/styx/src/lib.rs b/zrml/styx/src/lib.rs index ed96c5dca..ab86b6baf 100644 --- a/zrml/styx/src/lib.rs +++ b/zrml/styx/src/lib.rs @@ -31,7 +31,7 @@ pub use pallet::*; pub mod pallet { use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; - use sp_runtime::SaturatedConversion; + use sp_runtime::{traits::Zero, SaturatedConversion}; use zeitgeist_primitives::types::Balance; use crate::weights::WeightInfoZeitgeist; @@ -101,7 +101,13 @@ pub mod pallet { Err(Error::::FundDoesNotHaveEnoughFreeBalance)?; } - T::Currency::slash(&who, amount); + let (_imb, missing) = T::Currency::slash(&who, amount); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. who: {:?}, amount: {:?}.", + &who, + amount, + ); Crossings::::insert(&who, ()); Self::deposit_event(Event::AccountCrossed(who, amount.saturated_into())); diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index f63f281ee..95e7a4f0d 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -1677,7 +1677,14 @@ mod pallet { // Check that the account has at least as many free shares as we wish to burn! T::AssetManager::ensure_can_withdraw(shares_id, from, amount) .map_err(|_| Error::::InsufficientBalance)?; - T::AssetManager::slash(shares_id, from, amount); + let missing = T::AssetManager::slash(shares_id, from, amount); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. shares_id {:?}, who: {:?}, amount: {:?}.", + shares_id, + &from, + amount, + ); Ok(()) } @@ -2022,7 +2029,15 @@ mod pallet { let asset_len = pool.assets.len() as u32; for asset in pool.assets.into_iter() { let amount = T::AssetManager::free_balance(asset, &pool_account); - T::AssetManager::slash(asset, &pool_account, amount); + let missing = T::AssetManager::slash(asset, &pool_account, amount); + debug_assert!( + missing.is_zero(), + "Could not slash all of the amount. asset {:?}, pool_account: {:?}, amount: \ + {:?}.", + asset, + &pool_account, + amount, + ); } // NOTE: Currently we don't clean up accounts with pool_share_id. // TODO(#792): Remove pool_share_id asset for accounts! It may require storage migration. @@ -2049,7 +2064,16 @@ mod pallet { let mut providers_and_pool_shares = vec![]; for provider in >::drain_prefix(pool_id) { - T::AssetManager::unreserve(base_asset, &provider.0, provider.1); + 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); } @@ -2106,7 +2130,17 @@ mod pallet { let subsidy = provider.1; if !account_created { - T::AssetManager::unreserve(base_asset, &provider_address, subsidy); + 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, From 30beab914e0ea3525480d38709bc9842c4fe1baf Mon Sep 17 00:00:00 2001 From: Chralt Date: Mon, 13 Nov 2023 10:28:20 +0100 Subject: [PATCH 005/104] Add some verify checks to court (#1187) add some verify checks to court --- zrml/court/src/benchmarks.rs | 121 +++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 14 deletions(-) diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 9885c7b8e..bd9e79577 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -247,9 +247,30 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); join_with_min_stake::(&caller)?; + // to check that we execute the worst case benchmark path + let joined_at_before = >::block_number(); + let pool = CourtPool::::get(); + assert_eq!( + pool.into_inner().iter().find(|i| i.court_participant == caller).unwrap().joined_at, + joined_at_before, + ); + >::set_block_number( + joined_at_before + 1u64.saturated_into::(), + ); + let new_stake = T::MinJurorStake::get() .saturating_add(1u128.saturated_into::>()); - }: _(RawOrigin::Signed(caller), new_stake) + }: _(RawOrigin::Signed(caller.clone()), new_stake) + verify { + let now = >::block_number(); + let pool = CourtPool::::get(); + // joined_at did not change, because it was rewritten to the newly created pool item + assert_ne!(now, joined_at_before); + assert_eq!( + pool.into_inner().iter().find(|i| i.court_participant == caller).unwrap().joined_at, + joined_at_before, + ); + } delegate { // jurors greater or equal to MaxDelegations, @@ -262,6 +283,17 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); join_with_min_stake::(&caller)?; + // to check that we execute the worst case benchmark path + let joined_at_before = >::block_number(); + let pool = CourtPool::::get(); + assert_eq!( + pool.into_inner().iter().find(|i| i.court_participant == caller).unwrap().joined_at, + joined_at_before, + ); + >::set_block_number( + joined_at_before + 1u64.saturated_into::(), + ); + let juror_pool = >::get(); let mut delegations = Vec::::new(); juror_pool.iter() @@ -270,7 +302,17 @@ benchmarks! { let new_stake = T::MinJurorStake::get() .saturating_add(1u128.saturated_into::>()); - }: _(RawOrigin::Signed(caller), new_stake, delegations) + }: _(RawOrigin::Signed(caller.clone()), new_stake, delegations) + verify { + let now = >::block_number(); + let pool = CourtPool::::get(); + // joined_at did not change, because it was rewritten to the newly created pool item + assert_ne!(now, joined_at_before); + assert_eq!( + pool.into_inner().iter().find(|i| i.court_participant == caller).unwrap().joined_at, + joined_at_before, + ); + } prepare_exit_court { let j in 0..(T::MaxCourtParticipants::get() - 1); @@ -279,7 +321,23 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); join_with_min_stake::(&caller)?; + // query pool participant to check that we execute the worst case benchmark path + let pool_participant = >::get() + .into_inner() + .iter() + .find(|pool_item| pool_item.court_participant == caller) + .map(|pool_item| pool_item.court_participant.clone()) + .unwrap(); }: _(RawOrigin::Signed(caller)) + verify { + // worst case: pool participant was actually removed + assert!( + !>::get() + .into_inner() + .iter() + .any(|pool_item| pool_item.court_participant == pool_participant) + ); + } exit_court_remove { let caller: T::AccountId = whitelisted_caller(); @@ -294,7 +352,10 @@ benchmarks! { }); let juror = T::Lookup::unlookup(caller.clone()); - }: exit_court(RawOrigin::Signed(caller), juror) + }: exit_court(RawOrigin::Signed(caller.clone()), juror) + verify { + assert!(!>::contains_key(caller)); + } exit_court_set { let caller: T::AccountId = whitelisted_caller(); @@ -309,7 +370,10 @@ benchmarks! { }); let juror = T::Lookup::unlookup(caller.clone()); - }: exit_court(RawOrigin::Signed(caller), juror) + }: exit_court(RawOrigin::Signed(caller.clone()), juror) + verify { + assert_eq!(>::get(caller).unwrap().active_lock, T::MinJurorStake::get()); + } vote { let d in 1..T::MaxSelectedDraws::get(); @@ -333,14 +397,23 @@ benchmarks! { weight: 1u32, slashable: BalanceOf::::zero(), }; - let index = draws.binary_search_by_key(&caller, |draw| draw.court_participant.clone()).unwrap_or_else(|j| j); + let index = draws + .binary_search_by_key(&caller, |draw| draw.court_participant.clone()) + .unwrap_or_else(|j| j); draws.try_insert(index, draw).unwrap(); >::insert(court_id, draws); - >::set_block_number(pre_vote + 1u64.saturated_into::()); + >::set_block_number( + pre_vote + 1u64.saturated_into::(), + ); let commitment_vote = Default::default(); - }: _(RawOrigin::Signed(caller), court_id, commitment_vote) + }: _(RawOrigin::Signed(caller.clone()), court_id, commitment_vote) + verify { + let draws = >::get(court_id); + let draw = draws.iter().find(|draw| draw.court_participant == caller).unwrap(); + assert_eq!(draw.vote, Vote::Secret { commitment: commitment_vote }); + } denounce_vote { let d in 1..T::MaxSelectedDraws::get(); @@ -373,9 +446,11 @@ benchmarks! { let mut draws = >::get(court_id); draws.remove(0); let draws_len = draws.len(); - let index = draws.binary_search_by_key(&denounced_juror, |draw| draw.court_participant.clone()).unwrap_or_else(|j| j); + let index = draws + .binary_search_by_key(&denounced_juror, |draw| draw.court_participant.clone()) + .unwrap_or_else(|j| j); let draw = Draw { - court_participant: denounced_juror, + court_participant: denounced_juror.clone(), vote: Vote::Secret { commitment }, weight: 1u32, slashable: T::MinJurorStake::get(), @@ -383,8 +458,15 @@ benchmarks! { draws.try_insert(index, draw).unwrap(); >::insert(court_id, draws); - >::set_block_number(pre_vote + 1u64.saturated_into::()); - }: _(RawOrigin::Signed(caller), court_id, denounced_juror_unlookup, vote_item, salt) + >::set_block_number( + pre_vote + 1u64.saturated_into::(), + ); + }: _(RawOrigin::Signed(caller), court_id, denounced_juror_unlookup, vote_item.clone(), salt) + verify { + let draws = >::get(court_id); + let draw = draws.iter().find(|draw| draw.court_participant == denounced_juror).unwrap(); + assert_eq!(draw.vote, Vote::Denounced { commitment, vote_item, salt }); + } reveal_vote { let d in 1..T::MaxSelectedDraws::get(); @@ -414,7 +496,9 @@ benchmarks! { let mut draws = >::get(court_id); let draws_len = draws.len(); draws.remove(0); - let index = draws.binary_search_by_key(&caller, |draw| draw.court_participant.clone()).unwrap_or_else(|j| j); + let index = draws + .binary_search_by_key(&caller, |draw| draw.court_participant.clone()) + .unwrap_or_else(|j| j); draws.try_insert(index, Draw { court_participant: caller.clone(), vote: Vote::Secret { commitment }, @@ -423,8 +507,15 @@ benchmarks! { }).unwrap(); >::insert(court_id, draws); - >::set_block_number(vote_end + 1u64.saturated_into::()); - }: _(RawOrigin::Signed(caller), court_id, vote_item, salt) + >::set_block_number( + vote_end + 1u64.saturated_into::() + ); + }: _(RawOrigin::Signed(caller.clone()), court_id, vote_item.clone(), salt) + verify { + let draws = >::get(court_id); + let draw = draws.iter().find(|draw| draw.court_participant == caller).unwrap(); + assert_eq!(draw.vote, Vote::Revealed { commitment, vote_item, salt }); + } appeal { // from 255 because in the last appeal round we need at least 255 jurors @@ -505,6 +596,8 @@ benchmarks! { verify { let court = >::get(court_id).unwrap(); assert_eq!(court.round_ends.appeal, new_resolve_at); + assert!(T::DisputeResolution::auto_resolve_exists(&market_id, new_resolve_at)); + assert!(!T::DisputeResolution::auto_resolve_exists(&market_id, appeal_end)); } reassign_court_stakes { From 258d163295d30d0453d3abf29370fef942409292 Mon Sep 17 00:00:00 2001 From: Chralt Date: Mon, 13 Nov 2023 11:27:08 +0100 Subject: [PATCH 006/104] Bypass battery stations contracts call filter for court, parimutuel, order book markets (#1185) update contracts call filter --- runtime/battery-station/src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index e793cad60..f6a3792e9 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -128,18 +128,24 @@ impl Contains for ContractsCallfilter { deploy_swap_pool_and_additional_liquidity { .. } => true, deploy_swap_pool_for_market { .. } => true, dispute { .. } => true, - // Only allow CPMM markets using Authorized or SimpleDisputes dispute mechanism + // Only allow CPMM markets using Authorized or Court dispute mechanism create_market { - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + dispute_mechanism: + Some(MarketDisputeMechanism::Authorized) + | Some(MarketDisputeMechanism::Court), scoring_rule: ScoringRule::CPMM, .. } => true, create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + dispute_mechanism: + Some(MarketDisputeMechanism::Authorized) + | Some(MarketDisputeMechanism::Court), .. } => true, edit_market { - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + dispute_mechanism: + Some(MarketDisputeMechanism::Authorized) + | Some(MarketDisputeMechanism::Court), scoring_rule: ScoringRule::CPMM, .. } => true, @@ -160,6 +166,8 @@ impl Contains for ContractsCallfilter { swap_exact_amount_out { .. } => true, _ => false, }, + RuntimeCall::Orderbook(_) => true, + RuntimeCall::Parimutuel(_) => true, _ => false, } } From d570b4491b57164e3b5a7190b1aea2c1e3cb01a1 Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 21 Nov 2023 11:28:57 +0100 Subject: [PATCH 007/104] Fix failing court benchmark (#1191) * fix court assertion for resolve_at * remove unnecessary variable * mirror mock and actual impl for DisputeResolution --- zrml/court/src/benchmarks.rs | 2 +- zrml/court/src/mock.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index bd9e79577..0abbc954e 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -533,6 +533,7 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); deposit::(&caller); + // adds resolve_at for the created market let (market_id, court_id) = setup_court::()?; let mut court = >::get(court_id).unwrap(); @@ -541,7 +542,6 @@ benchmarks! { let market_id_i = (i + 100).saturated_into::>(); T::DisputeResolution::add_auto_resolve(&market_id_i, appeal_end).unwrap(); } - T::DisputeResolution::add_auto_resolve(&market_id, appeal_end).unwrap(); let aggregation = court.round_ends.aggregation; for i in 0..a { diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 9f3b14f5a..aa4292303 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -127,8 +127,11 @@ impl DisputeResolutionApi for MockResolution { fn remove_auto_resolve(market_id: &Self::MarketId, resolve_at: Self::BlockNumber) -> u32 { >::mutate(resolve_at, |ids| -> u32 { - ids.retain(|id| id != market_id); - ids.len() as u32 + let ids_len = ids.len() as u32; + if let Some(pos) = ids.iter().position(|i| i == market_id) { + ids.swap_remove(pos); + } + ids_len }) } } From fd3e547d6d9150305391cb7f9dae74fde63fca7e Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 21 Nov 2023 11:38:57 +0100 Subject: [PATCH 008/104] Implement trusted market close (#1184) * implement trusted market close * remove unnecessary benchmark helper function * Update zrml/prediction-markets/src/lib.rs Co-authored-by: Malte Kliemann * remove unnecessary function * check market end * check auto close * add invalid market status test --------- Co-authored-by: Malte Kliemann --- zrml/prediction-markets/src/benchmarks.rs | 87 +++++++++++-- zrml/prediction-markets/src/lib.rs | 32 +++++ zrml/prediction-markets/src/tests.rs | 143 ++++++++++++++++++++++ zrml/prediction-markets/src/weights.rs | 14 +++ 4 files changed, 263 insertions(+), 13 deletions(-) diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 3987abf00..3e8474bb1 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -64,15 +64,16 @@ const LIQUIDITY: u128 = 100 * BASE; // Get default values for market creation. Also spawns an account with maximum // amount of native currency -fn create_market_common_parameters() --> Result<(T::AccountId, T::AccountId, Deadlines, MultiHash), &'static str> { +fn create_market_common_parameters( + is_disputable: bool, +) -> Result<(T::AccountId, T::AccountId, Deadlines, MultiHash), &'static str> { let caller: T::AccountId = whitelisted_caller(); T::AssetManager::deposit(Asset::Ztg, &caller, (100u128 * LIQUIDITY).saturated_into()).unwrap(); let oracle = caller.clone(); let deadlines = Deadlines:: { grace_period: 1_u32.into(), oracle_duration: T::MinOracleDuration::get(), - dispute_duration: T::MinDisputeDuration::get(), + dispute_duration: if is_disputable { T::MinDisputeDuration::get() } else { Zero::zero() }, }; let mut metadata = [0u8; 50]; metadata[0] = 0x15; @@ -86,13 +87,15 @@ fn create_market_common( options: MarketType, scoring_rule: ScoringRule, period: Option>>, + dispute_mechanism: Option, ) -> Result<(T::AccountId, MarketIdOf), &'static str> { pallet_timestamp::Pallet::::set_timestamp(0u32.into()); let range_start: MomentOf = 100_000u64.saturated_into(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let creator_fee: Perbill = Perbill::zero(); let period = period.unwrap_or(MarketPeriod::Timestamp(range_start..range_end)); - let (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; + let (caller, oracle, deadlines, metadata) = + create_market_common_parameters::(dispute_mechanism.is_some())?; Call::::create_market { base_asset: Asset::Ztg, creator_fee, @@ -102,7 +105,7 @@ fn create_market_common( metadata, creation, market_type: options, - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), + dispute_mechanism, scoring_rule, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; @@ -118,8 +121,13 @@ fn create_close_and_report_market( let range_start: MomentOf = 100_000u64.saturated_into(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let period = MarketPeriod::Timestamp(range_start..range_end); - let (caller, market_id) = - create_market_common::(permission, options, ScoringRule::CPMM, Some(period))?; + let (caller, market_id) = create_market_common::( + permission, + options, + ScoringRule::CPMM, + 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)?; @@ -146,6 +154,7 @@ fn setup_redeem_shares_common( market_type.clone(), ScoringRule::CPMM, None, + Some(MarketDisputeMechanism::Court), )?; let outcome: OutcomeReport; @@ -188,6 +197,7 @@ fn setup_reported_categorical_market_with_pool = MaxSwapFee::get().saturated_into(); @@ -241,6 +251,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; for i in 0..o { @@ -440,6 +451,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, None, + Some(MarketDisputeMechanism::Court), )?; let approve_origin = T::ApproveOrigin::try_successful_origin().unwrap(); @@ -453,6 +465,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, None, + Some(MarketDisputeMechanism::Court), )?; let approve_origin = T::ApproveOrigin::try_successful_origin().unwrap(); @@ -467,6 +480,7 @@ benchmarks! { MarketType::Categorical(a.saturated_into()), ScoringRule::CPMM, None, + Some(MarketDisputeMechanism::Court), )?; let amount = BASE * 1_000; }: _(RawOrigin::Signed(caller), market_id, amount.saturated_into()) @@ -476,7 +490,7 @@ benchmarks! { create_market { let m in 0..63; - let (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; + 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); @@ -497,7 +511,7 @@ benchmarks! { metadata, MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), + Some(MarketDisputeMechanism::Court), ScoringRule::CPMM ) @@ -505,13 +519,13 @@ benchmarks! { let m in 0..63; let market_type = MarketType::Categorical(T::MaxCategories::get()); - let dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); + let dispute_mechanism = Some(MarketDisputeMechanism::Court); let scoring_rule = ScoringRule::CPMM; let range_start: MomentOf = 100_000u64.saturated_into(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let period = MarketPeriod::Timestamp(range_start..range_end); let (caller, oracle, deadlines, metadata) = - create_market_common_parameters::()?; + create_market_common_parameters::(true)?; Call::::create_market { base_asset: Asset::Ztg, creator_fee: Perbill::zero(), @@ -567,6 +581,7 @@ benchmarks! { MarketType::Categorical(a.saturated_into()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; assert!( @@ -623,6 +638,7 @@ benchmarks! { MarketType::Categorical(a.saturated_into()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; let market = >::market(&market_id.saturated_into())?; @@ -757,6 +773,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..T::MaxSubsidyPeriod::get())), + Some(MarketDisputeMechanism::Court), )?; let market = >::market(&market_id.saturated_into())?; }: { Pallet::::handle_expired_advised_market(&market_id, market)? } @@ -900,6 +917,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; for i in 0..o { @@ -932,6 +950,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; >::mutate_market(&market_id, |market| { @@ -973,7 +992,7 @@ benchmarks! { pallet_timestamp::Pallet::::set_timestamp(0u32.into()); let start: MomentOf = >::now(); let end: MomentOf = 1_000_000u64.saturated_into(); - let (caller, oracle, _, metadata) = create_market_common_parameters::()?; + let (caller, oracle, _, metadata) = create_market_common_parameters::(false)?; Call::::create_market { base_asset: Asset::Ztg, creator_fee: Perbill::zero(), @@ -1007,6 +1026,7 @@ benchmarks! { MarketType::Categorical(a.saturated_into()), ScoringRule::CPMM, None, + Some(MarketDisputeMechanism::Court), )?; let amount: BalanceOf = LIQUIDITY.saturated_into(); Pallet::::buy_complete_set( @@ -1026,6 +1046,7 @@ benchmarks! { 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| { @@ -1048,6 +1069,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Block(start_block..end_block)), + Some(MarketDisputeMechanism::Court), ).unwrap(); } @@ -1059,6 +1081,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), ).unwrap(); } @@ -1111,6 +1134,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; // ensure market is reported >::mutate_market(&market_id, |market| { @@ -1164,6 +1188,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; for i in 0..o { @@ -1198,6 +1223,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; for i in 0..o { @@ -1242,6 +1268,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market = >::market(&market_id)?; @@ -1279,6 +1306,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1329,6 +1357,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1376,6 +1405,7 @@ benchmarks! { MarketType::Categorical(T::MaxCategories::get()), ScoringRule::CPMM, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1395,6 +1425,37 @@ benchmarks! { let call = Call::::reject_early_close { market_id }; }: { 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(); + 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, + 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), + |ids| ids.try_push(i.into()), + ).unwrap(); + } + + let call = Call::::close_trusted_market { market_id }; + }: { call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())? } + create_market_and_deploy_pool { let m in 0..63; // Number of markets closing on the same block. @@ -1403,7 +1464,7 @@ benchmarks! { let range_end = (100 * MILLISECS_PER_BLOCK) as u64; let period = MarketPeriod::Timestamp(range_start..range_end); let market_type = MarketType::Categorical(2); - let (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; + let (caller, oracle, deadlines, metadata) = create_market_common_parameters::(true)?; let price = (BASE / 2).saturated_into(); let amount = (10u128 * BASE).saturated_into(); diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 5f20099fa..2e896d1d1 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -1715,6 +1715,34 @@ mod pallet { Ok(Some(weight).into()) } + + /// Allows the market creator of a trusted market + /// to immediately move an open market to closed. + /// + /// # Weight + /// + /// Complexity: `O(n + m)`, where `n` is the number of market ids, + /// which open at the same time as the specified market, + /// 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()))] + #[transactional] + pub fn close_trusted_market( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let market = >::market(&market_id)?; + 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()) + } } #[pallet::config] @@ -2066,6 +2094,10 @@ mod pallet { /// After there was an early close already scheduled, /// only the `CloseMarketsEarlyOrigin` can schedule another one. OnlyAuthorizedCanScheduleEarlyClose, + /// The caller is not the market creator. + CallerNotMarketCreator, + /// The market is not trusted. + MarketIsNotTrusted, } #[pallet::event] diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 0fbe22b84..177c2eca6 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -6342,6 +6342,149 @@ fn trusted_market_complete_lifecycle() { }); } +#[test] +fn close_trusted_market_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::CPMM, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, None); + + let new_end = end / 2; + assert_ne!(new_end, end); + run_to_block(new_end); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.first().cloned().unwrap(), market_id); + + assert_noop!( + PredictionMarkets::close_trusted_market(RuntimeOrigin::signed(BOB), market_id), + Error::::CallerNotMarketCreator + ); + + assert_ok!(PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, MarketPeriod::Block(0..new_end)); + assert_eq!(market.status, MarketStatus::Closed); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.len(), 0); + }); +} + +#[test] +fn close_trusted_market_fails_if_not_trusted() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Court), + ScoringRule::CPMM, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, Some(MarketDisputeMechanism::Court)); + + run_to_block(end / 2); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotTrusted + ); + }); +} + +#[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; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::CPMM, + )); + + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotActive + ); + }); +} + fn deploy_swap_pool( market: Market>, market_id: u128, diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 1d095de18..9a18cfdf9 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -87,6 +87,7 @@ pub trait WeightInfoZeitgeist { fn reject_early_close_after_authority(o: u32, n: u32) -> Weight; fn reject_early_close_after_dispute() -> Weight; fn create_market_and_deploy_pool(m: u32) -> Weight; + fn close_trusted_market(o: u32, c: u32) -> Weight; } /// Weight functions for zrml_prediction_markets (automatically generated) @@ -897,4 +898,17 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(13)) } + fn close_trusted_market(o: u32, 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)) + .saturating_add(T::DbWeight::get().writes(3)) + } } From 1cc3803fe5c59f54149c6336903a6a75dff78aa7 Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 21 Nov 2023 12:03:11 +0100 Subject: [PATCH 009/104] Modify court events for indexer (#1182) * modify events for indexer * gracefully handle panicers * handle binary search by key error * improve style --- zrml/court/src/lib.rs | 224 +++++++++++++++++++++++++++++----------- zrml/court/src/tests.rs | 19 +++- zrml/court/src/types.rs | 1 + 3 files changed, 181 insertions(+), 63 deletions(-) diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index ed7e9a13b..dd19681b3 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -36,15 +36,15 @@ use frame_support::{ dispatch::DispatchResult, ensure, log, pallet_prelude::{ - ConstU32, DispatchResultWithPostInfo, EnsureOrigin, Hooks, OptionQuery, StorageMap, - StorageValue, ValueQuery, Weight, + ConstU32, Decode, DispatchResultWithPostInfo, Encode, EnsureOrigin, Hooks, OptionQuery, + StorageMap, StorageValue, TypeInfo, ValueQuery, Weight, }, traits::{ Currency, Get, Imbalance, IsType, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced, Randomness, ReservableCurrency, StorageVersion, WithdrawReasons, }, - transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, + transactional, Blake2_128Concat, BoundedVec, PalletId, RuntimeDebug, Twox64Concat, }; use frame_system::{ ensure_signed, @@ -235,6 +235,7 @@ mod pallet { BoundedVec, ::MaxCourtParticipants>; pub(crate) type DrawOf = Draw, BalanceOf, HashOf, DelegatedStakesOf>; pub(crate) type SelectedDrawsOf = BoundedVec, ::MaxSelectedDraws>; + pub(crate) type RoundTimingOf = RoundTiming>; pub(crate) type AppealOf = AppealInfo, BalanceOf>; pub(crate) type AppealsOf = BoundedVec, ::MaxAppeals>; pub(crate) type RawCommitmentOf = RawCommitment, HashOf>; @@ -323,6 +324,8 @@ mod pallet { court_id: CourtId, vote_item: VoteItem, salt: T::Hash, + slashable_amount: BalanceOf, + draw_weight: u32, }, /// A juror vote has been denounced. DenouncedJurorVote { @@ -339,7 +342,11 @@ mod pallet { delegated_jurors: Vec, }, /// A market has been appealed. - CourtAppealed { court_id: CourtId, appeal_number: u32 }, + CourtAppealed { + court_id: CourtId, + appeal_info: AppealOf, + new_round_ends: Option>, + }, /// A new token amount was minted for a court participant. MintedInCourt { court_participant: T::AccountId, amount: BalanceOf }, /// The juror and delegator stakes have been reassigned. The losing jurors have been slashed. @@ -455,6 +462,16 @@ mod pallet { OutcomeMismatch, /// The vote item was expected to be an outcome, but is actually not an outcome. VoteItemIsNoOutcome, + /// Action cannot be completed because an unexpected error has occurred. This should be + /// reported to protocol maintainers. + Unexpected(UnexpectedError), + } + + // NOTE: these errors should never happen. + #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, frame_support::PalletError, RuntimeDebug)] + pub enum UnexpectedError { + /// The binary search by key functionality failed to find an element, although expected. + BinarySearchByKeyFailed, } #[pallet::hooks] @@ -554,9 +571,10 @@ mod pallet { let pool = CourtPool::::get(); let is_valid_set = sorted_delegations.iter().all(|pretended_juror| { >::get(pretended_juror).map_or(false, |pretended_juror_info| { - Self::get_pool_item(&pool, pretended_juror_info.stake, pretended_juror) - .is_some() - && pretended_juror_info.delegations.is_none() + match Self::get_pool_item(&pool, pretended_juror_info.stake, pretended_juror) { + Ok(Some(_)) => pretended_juror_info.delegations.is_none(), + _ => false, + } }) }); ensure!(is_valid_set, Error::::DelegatedToInvalidJuror); @@ -603,7 +621,7 @@ mod pallet { // do not error in the else case // because the juror might have been already removed from the pool - if let Some((index, _)) = Self::get_pool_item(&pool, prev_p_info.stake, &who) { + if let Some((index, _)) = Self::get_pool_item(&pool, prev_p_info.stake, &who)? { pool.remove(index); >::put(pool); } @@ -708,16 +726,17 @@ mod pallet { match draws.binary_search_by_key(&who, |draw| draw.court_participant.clone()) { Ok(index) => { - let draw = draws[index].clone(); - + let draw = draws + .get_mut(index) + .ok_or(Error::::Unexpected(UnexpectedError::BinarySearchByKeyFailed))?; // allow to override last vote ensure!( - matches!(draws[index].vote, Vote::Drawn | Vote::Secret { commitment: _ }), + matches!(draw.vote, Vote::Drawn | Vote::Secret { commitment: _ }), Error::::InvalidVoteState ); let vote = Vote::Secret { commitment: commitment_vote }; - draws[index] = Draw { vote, ..draw }; + draw.vote = vote; } Err(_) => return Err(Error::::CallerNotInSelectedDraws.into()), } @@ -791,17 +810,19 @@ mod pallet { let mut draws = >::get(court_id); match draws.binary_search_by_key(&juror, |draw| draw.court_participant.clone()) { Ok(index) => { - let draw = draws[index].clone(); - + let draw = draws + .get_mut(index) + .ok_or(Error::::Unexpected(UnexpectedError::BinarySearchByKeyFailed))?; let raw_commmitment = RawCommitment { juror: juror.clone(), vote_item: vote_item.clone(), salt }; - let commitment = Self::get_hashed_commitment(draw.vote, raw_commmitment)?; + let commitment = + Self::get_hashed_commitment(draw.vote.clone(), raw_commmitment)?; // slash for the misbehaviour happens in reassign_court_stakes let raw_vote = Vote::Denounced { commitment, vote_item: vote_item.clone(), salt }; - draws[index] = Draw { vote: raw_vote, ..draw }; + draw.vote = raw_vote; } Err(_) => return Err(Error::::JurorNotDrawn.into()), } @@ -867,27 +888,40 @@ mod pallet { ); let mut draws = >::get(court_id); - match draws.binary_search_by_key(&who, |draw| draw.court_participant.clone()) { + let (slashable_amount, draw_weight) = match draws + .binary_search_by_key(&who, |draw| draw.court_participant.clone()) + { Ok(index) => { - let draw = draws[index].clone(); - + let draw = draws + .get_mut(index) + .ok_or(Error::::Unexpected(UnexpectedError::BinarySearchByKeyFailed))?; let raw_commitment = RawCommitment { juror: who.clone(), vote_item: vote_item.clone(), salt }; - let commitment = Self::get_hashed_commitment(draw.vote, raw_commitment)?; + let commitment = + Self::get_hashed_commitment(draw.vote.clone(), raw_commitment)?; let raw_vote = Vote::Revealed { commitment, vote_item: vote_item.clone(), salt }; - draws[index] = Draw { court_participant: who.clone(), vote: raw_vote, ..draw }; + draw.vote = raw_vote; + + (draw.slashable, draw.weight) } Err(_) => return Err(Error::::CallerNotInSelectedDraws.into()), - } + }; let draws_len = draws.len() as u32; >::insert(court_id, draws); - Self::deposit_event(Event::JurorRevealedVote { juror: who, court_id, vote_item, salt }); + Self::deposit_event(Event::JurorRevealedVote { + juror: who, + court_id, + vote_item, + salt, + slashable_amount, + draw_weight, + }); Ok(Some(T::WeightInfo::reveal_vote(draws_len)).into()) } @@ -933,7 +967,7 @@ mod pallet { let appealed_vote_item = Self::get_latest_winner_vote_item(court_id, old_draws.as_slice())?; let appeal_info = AppealInfo { backer: who.clone(), bond, appealed_vote_item }; - court.appeals.try_push(appeal_info).map_err(|_| { + court.appeals.try_push(appeal_info.clone()).map_err(|_| { debug_assert!(false, "Appeal bound is checked above."); Error::::MaxAppealsReached })?; @@ -943,9 +977,8 @@ mod pallet { // used for benchmarking, juror pool is queried inside `select_participants` let pool_len = >::decode_len().unwrap_or(0) as u32; - let mut ids_len_1 = 0u32; // if appeal_number == MaxAppeals, then don't start a new appeal round - if appeal_number < T::MaxAppeals::get() as usize { + let (new_round_ends, ids_len_1) = if appeal_number < T::MaxAppeals::get() as usize { let new_draws = Self::select_participants(appeal_number)?; let request_block = >::get(); debug_assert!(request_block >= now, "Request block must be greater than now."); @@ -957,14 +990,20 @@ mod pallet { }; // sets round ends one after the other from now court.update_round(round_timing); + let new_round_ends = Some(court.round_ends.clone()); let new_resolve_at = court.round_ends.appeal; debug_assert!(new_resolve_at != last_resolve_at); - if let Some(market_id) = >::get(court_id) { - ids_len_1 = T::DisputeResolution::add_auto_resolve(&market_id, new_resolve_at)?; - } + let ids_len_1 = if let Some(market_id) = >::get(court_id) { + T::DisputeResolution::add_auto_resolve(&market_id, new_resolve_at)? + } else { + 0u32 + }; >::insert(court_id, new_draws); Self::unlock_participants_from_last_draw(court_id, old_draws); - } + (new_round_ends, ids_len_1) + } else { + (None, 0u32) + }; let mut ids_len_0 = 0u32; if let Some(market_id) = >::get(court_id) { @@ -976,7 +1015,7 @@ mod pallet { >::insert(court_id, court); let appeal_number = appeal_number as u32; - Self::deposit_event(Event::CourtAppealed { court_id, appeal_number }); + Self::deposit_event(Event::CourtAppealed { court_id, appeal_info, new_round_ends }); Ok(Some(T::WeightInfo::appeal(pool_len, appeal_number, ids_len_0, ids_len_1)).into()) } @@ -1028,7 +1067,7 @@ mod pallet { // map delegated jurors to own_slashable, vote item and Vec<(delegator, delegator_stake)> let mut jurors_to_stakes = BTreeMap::>::new(); - let mut handle_vote = |draw: DrawOf| { + let mut handle_vote = |draw: DrawOf| -> DispatchResult { match draw.vote { Vote::Drawn | Vote::Secret { commitment: _ } @@ -1056,10 +1095,13 @@ mod pallet { .binary_search_by_key(&delegator, |(d, _)| d.clone()) { Ok(i) => { - juror_vote_with_stakes.delegations[i].1 = - juror_vote_with_stakes.delegations[i] - .1 - .saturating_add(delegated_stake); + let delegations = juror_vote_with_stakes + .delegations + .get_mut(i) + .ok_or(Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed, + ))?; + delegations.1 = delegations.1.saturating_add(delegated_stake); } Err(i) => { juror_vote_with_stakes @@ -1070,6 +1112,7 @@ mod pallet { } } } + Ok(()) }; for draw in draws { @@ -1087,7 +1130,7 @@ mod pallet { debug_assert!(false); } - handle_vote(draw); + handle_vote(draw)?; } Self::slash_losers_to_award_winners(court_id, jurors_to_stakes, &winner); @@ -1175,7 +1218,8 @@ mod pallet { if let Some(prev_p_info) = >::get(who) { ensure!(amount >= prev_p_info.stake, Error::::AmountBelowLastJoin); - if let Some((index, pool_item)) = Self::get_pool_item(&pool, prev_p_info.stake, who) + if let Some((index, pool_item)) = + Self::get_pool_item(&pool, prev_p_info.stake, who)? { active_lock = prev_p_info.active_lock; consumed_stake = pool_item.consumed_stake; @@ -1397,10 +1441,13 @@ mod pallet { mut delegated_stakes: DelegatedStakesOf, delegated_juror: &T::AccountId, amount: BalanceOf, - ) -> DelegatedStakesOf { + ) -> Result, DispatchError> { match delegated_stakes.binary_search_by_key(&delegated_juror, |(j, _)| j) { Ok(index) => { - delegated_stakes[index].1 = delegated_stakes[index].1.saturating_add(amount); + let delegated_stake = delegated_stakes + .get_mut(index) + .ok_or(Error::::Unexpected(UnexpectedError::BinarySearchByKeyFailed))?; + delegated_stake.1 = delegated_stake.1.saturating_add(amount); } Err(index) => { let _ = delegated_stakes @@ -1415,7 +1462,7 @@ mod pallet { } } - delegated_stakes + Ok(delegated_stakes) } // Updates the `selections` map for the juror and the lock amount. @@ -1427,7 +1474,7 @@ mod pallet { selections: &mut BTreeMap>, court_participant: &T::AccountId, sel_add: SelectionAdd, BalanceOf>, - ) { + ) -> DispatchResult { if let Some(SelectionValue { weight, slashable, delegated_stakes }) = selections.get_mut(court_participant) { @@ -1442,7 +1489,7 @@ mod pallet { delegated_stakes.clone(), &delegated_juror, lock, - ); + )?; } SelectionAdd::DelegationWeight => { *weight = weight.saturating_add(1); @@ -1465,7 +1512,7 @@ mod pallet { DelegatedStakesOf::::default(), &delegated_juror, lock, - ); + )?; selections.insert( court_participant.clone(), SelectionValue { weight: 0, slashable: lock, delegated_stakes }, @@ -1483,10 +1530,14 @@ mod pallet { } }; } + + Ok(()) } /// Return one delegated juror out of the delegations randomly. - fn get_valid_delegated_juror(delegations: &[T::AccountId]) -> Option { + fn get_valid_delegated_juror( + delegations: &[T::AccountId], + ) -> Result, SelectionError> { let mut rng = Self::rng(); let pool: CourtPoolOf = CourtPool::::get(); let mut valid_delegated_jurors = Vec::new(); @@ -1497,15 +1548,27 @@ mod pallet { // skip if delegated juror is delegator herself continue; } - if Self::get_pool_item(&pool, delegated_juror_info.stake, delegated_juror) - .is_some() - { + let pool_item = + Self::get_pool_item(&pool, delegated_juror_info.stake, delegated_juror) + .map_err(|err| { + debug_assert!( + err == Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed + ) + .into(), + "At the point of writing this, this was the only expected \ + error." + ); + SelectionError::BinarySearchByKeyFailed + })?; + + if pool_item.is_some() { valid_delegated_jurors.push(delegated_juror.clone()); } } } - valid_delegated_jurors.choose(&mut rng).cloned() + Ok(valid_delegated_jurors.choose(&mut rng).cloned()) } /// Add a juror or delegator with the provided `lock_added` to the `selections` map. @@ -1518,23 +1581,55 @@ mod pallet { .and_then(|p_info| p_info.delegations); match delegations_opt { Some(delegations) => { - let delegated_juror = Self::get_valid_delegated_juror(delegations.as_slice()) + let delegated_juror = Self::get_valid_delegated_juror(delegations.as_slice())? .ok_or(SelectionError::NoValidDelegatedJuror)?; - // delegated juror gets the vote weight let sel_add = SelectionAdd::DelegationWeight; - Self::update_selections(selections, &delegated_juror, sel_add); + Self::update_selections(selections, &delegated_juror, sel_add).map_err( + |err| { + debug_assert!( + err == Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed + ) + .into(), + "At the point of writing this, this was the only expected error." + ); + SelectionError::BinarySearchByKeyFailed + }, + )?; let sel_add = SelectionAdd::DelegationStake { delegated_juror: delegated_juror.clone(), lock: lock_added, }; // delegator risks his stake (to delegated juror), but gets no vote weight - Self::update_selections(selections, court_participant, sel_add); + Self::update_selections(selections, court_participant, sel_add).map_err( + |err| { + debug_assert!( + err == Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed + ) + .into(), + "At the point of writing this, this was the only expected error." + ); + SelectionError::BinarySearchByKeyFailed + }, + )? } None => { let sel_add = SelectionAdd::SelfStake { lock: lock_added }; - Self::update_selections(selections, court_participant, sel_add); + Self::update_selections(selections, court_participant, sel_add).map_err( + |err| { + debug_assert!( + err == Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed + ) + .into(), + "At the point of writing this, this was the only expected error." + ); + SelectionError::BinarySearchByKeyFailed + }, + )?; } } @@ -1549,7 +1644,7 @@ mod pallet { pool: &mut CourtPoolOf, random_section_ends: BTreeSet, cumulative_section_ends: Vec<(u128, bool)>, - ) -> BTreeMap> { + ) -> Result>, DispatchError> { debug_assert!(pool.len() == cumulative_section_ends.len()); debug_assert!({ let prev = cumulative_section_ends.clone(); @@ -1589,6 +1684,12 @@ mod pallet { // if the juror fails to vote or reveal or gets denounced invalid_juror_indices.push(range_index); } + Err(SelectionError::BinarySearchByKeyFailed) => { + return Err(Error::::Unexpected( + UnexpectedError::BinarySearchByKeyFailed, + ) + .into()); + } } Self::add_active_lock(&pool_item.court_participant, lock_added); @@ -1602,7 +1703,7 @@ mod pallet { pool.remove(i); } - selections + Ok(selections) } // Converts the `selections` map into a vector of `Draw` structs. @@ -1685,7 +1786,7 @@ mod pallet { let random_section_ends = Self::get_n_random_section_ends(draw_weight, total_unconsumed); let selections = - Self::get_selections(&mut pool, random_section_ends, cumulative_section_ends); + Self::get_selections(&mut pool, random_section_ends, cumulative_section_ends)?; >::put(pool); Ok(Self::convert_selections_to_draws(selections)) @@ -1760,15 +1861,18 @@ mod pallet { pool: &'a [CourtPoolItemOf], stake: BalanceOf, court_participant: &T::AccountId, - ) -> Option<(usize, &'a CourtPoolItemOf)> { + ) -> Result)>, DispatchError> { if let Ok(i) = pool.binary_search_by_key(&(stake, court_participant), |pool_item| { (pool_item.stake, &pool_item.court_participant) }) { - return Some((i, &pool[i])); + let pool_item = pool + .get(i) + .ok_or(Error::::Unexpected(UnexpectedError::BinarySearchByKeyFailed))?; + return Ok(Some((i, pool_item))); } // this None case can happen whenever the court participant decided to leave the court // or was kicked out of the court pool because of the lowest stake - None + Ok(None) } // Returns OK if the market is in a valid state to be appealed. diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 2fd612fec..a117b6c82 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -1002,8 +1002,15 @@ fn reveal_vote_works() { salt, )); System::assert_last_event( - Event::JurorRevealedVote { juror: ALICE, court_id, vote_item: vote_item.clone(), salt } - .into(), + Event::JurorRevealedVote { + juror: ALICE, + court_id, + vote_item: vote_item.clone(), + salt, + slashable_amount: MinJurorStake::get(), + draw_weight: 1u32, + } + .into(), ); let new_draws = >::get(court_id); @@ -1504,7 +1511,13 @@ fn appeal_emits_event() { assert_ok!(Court::appeal(RuntimeOrigin::signed(CHARLIE), court_id)); - System::assert_last_event(Event::CourtAppealed { court_id, appeal_number: 1u32 }.into()); + let court = Courts::::get(court_id).unwrap(); + let new_round_ends = Some(court.round_ends); + let appeal_info = court.appeals.get(0).unwrap().clone(); + + System::assert_last_event( + Event::CourtAppealed { court_id, appeal_info, new_round_ends }.into(), + ); }); } diff --git a/zrml/court/src/types.rs b/zrml/court/src/types.rs index e9c8535ff..5c297ef36 100644 --- a/zrml/court/src/types.rs +++ b/zrml/court/src/types.rs @@ -357,4 +357,5 @@ impl Default for JurorVoteWithStakes { /// An internal error type to determine how the selection of draws fails. pub(crate) enum SelectionError { NoValidDelegatedJuror, + BinarySearchByKeyFailed, } From fb53170b99c57f5147ea7462247885ab6e36940e Mon Sep 17 00:00:00 2001 From: Chralt Date: Thu, 30 Nov 2023 13:45:14 +0100 Subject: [PATCH 010/104] Ensure MinBetSize after fee (#1193) * handle own existential deposit errors * use require_transactional * correct benchmark and test min bet size amounts --- zrml/parimutuel/src/benchmarking.rs | 15 ++++++---- zrml/parimutuel/src/lib.rs | 13 +++++++-- zrml/parimutuel/src/tests/buy.rs | 14 +++++----- zrml/parimutuel/src/tests/claim.rs | 43 ++++++++++++----------------- zrml/parimutuel/src/tests/refund.rs | 6 ++-- 5 files changed, 48 insertions(+), 43 deletions(-) diff --git a/zrml/parimutuel/src/benchmarking.rs b/zrml/parimutuel/src/benchmarking.rs index 26e9625ed..2aec79bbb 100644 --- a/zrml/parimutuel/src/benchmarking.rs +++ b/zrml/parimutuel/src/benchmarking.rs @@ -26,6 +26,7 @@ use frame_benchmarking::v2::*; use frame_support::traits::Get; use frame_system::RawOrigin; use orml_traits::MultiCurrency; +use sp_runtime::{SaturatedConversion, Saturating}; use zeitgeist_primitives::types::{Asset, MarketStatus, MarketType, OutcomeReport}; use zrml_market_commons::MarketCommonsPalletApi; @@ -60,7 +61,7 @@ mod benchmarks { let market_id = setup_market::(MarketType::Categorical(64u16)); - let amount = T::MinBetSize::get(); + let amount = T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); let asset = Asset::ParimutuelShare(market_id, 0u16); let market = T::MarketCommons::market(&market_id).unwrap(); @@ -77,12 +78,14 @@ mod benchmarks { let winner = whitelisted_caller(); let winner_asset = Asset::ParimutuelShare(market_id, 0u16); - let winner_amount = T::MinBetSize::get() + T::MinBetSize::get(); + let winner_amount = + T::MinBetSize::get().saturating_mul(20u128.saturated_into::>()); buy_asset::(market_id, winner_asset, &winner, winner_amount); let loser = whitelisted_caller(); let loser_asset = Asset::ParimutuelShare(market_id, 1u16); - let loser_amount = T::MinBetSize::get(); + let loser_amount = + T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); buy_asset::(market_id, loser_asset, &loser, loser_amount); T::MarketCommons::mutate_market(&market_id, |market| { @@ -103,13 +106,15 @@ mod benchmarks { let loser_0 = whitelisted_caller(); let loser_0_index = 0u16; let loser_0_asset = Asset::ParimutuelShare(market_id, loser_0_index); - let loser_0_amount = T::MinBetSize::get() + T::MinBetSize::get(); + let loser_0_amount = + T::MinBetSize::get().saturating_mul(20u128.saturated_into::>()); buy_asset::(market_id, loser_0_asset, &loser_0, loser_0_amount); let loser_1 = whitelisted_caller(); let loser_1_index = 1u16; let loser_1_asset = Asset::ParimutuelShare(market_id, loser_1_index); - let loser_1_amount = T::MinBetSize::get(); + let loser_1_amount = + T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); buy_asset::(market_id, loser_1_asset, &loser_1, loser_1_amount); T::MarketCommons::mutate_market(&market_id, |market| { diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 5029336c6..ab10ca693 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -33,6 +33,7 @@ mod pallet { use frame_support::{ ensure, log, pallet_prelude::{Decode, DispatchError, Encode, TypeInfo}, + require_transactional, traits::{Get, IsType, StorageVersion}, PalletId, RuntimeDebug, }; @@ -144,7 +145,7 @@ mod pallet { /// The market is not active. MarketIsNotActive, /// The specified amount is below the minimum bet size. - AmountTooSmall, + AmountBelowMinimumBetSize, /// The specified asset is not a parimutuel share. NotParimutuelOutcome, /// The specified asset was not found in the market assets. @@ -312,9 +313,8 @@ mod pallet { Err(Error::::NotParimutuelOutcome.into()) } + #[require_transactional] fn do_buy(who: T::AccountId, asset: AssetOf, amount: BalanceOf) -> DispatchResult { - ensure!(amount >= T::MinBetSize::get(), Error::::AmountTooSmall); - let market_id = match asset { Asset::ParimutuelShare(market_id, _) => market_id, _ => return Err(Error::::NotParimutuelOutcome.into()), @@ -336,6 +336,11 @@ mod pallet { let external_fees = T::ExternalFees::distribute(market_id, base_asset, &who, amount); let amount_minus_fees = amount.checked_sub(&external_fees).ok_or(Error::::Unexpected)?; + ensure!( + amount_minus_fees >= T::MinBetSize::get(), + Error::::AmountBelowMinimumBetSize + ); + let pot_account = Self::pot_account(market_id); T::AssetManager::transfer(market.base_asset, &who, &pot_account, amount_minus_fees)?; @@ -377,6 +382,7 @@ mod pallet { Ok(winning_asset) } + #[require_transactional] fn do_claim_rewards(who: T::AccountId, market_id: MarketIdOf) -> DispatchResult { let market = T::MarketCommons::market(&market_id)?; Self::ensure_parimutuel_market_resolved(&market)?; @@ -437,6 +443,7 @@ mod pallet { Ok(()) } + #[require_transactional] fn do_claim_refunds(who: T::AccountId, refund_asset: AssetOf) -> DispatchResult { let market_id = match refund_asset { Asset::ParimutuelShare(market_id, _) => market_id, diff --git a/zrml/parimutuel/src/tests/buy.rs b/zrml/parimutuel/src/tests/buy.rs index c14345582..0df053bb8 100644 --- a/zrml/parimutuel/src/tests/buy.rs +++ b/zrml/parimutuel/src/tests/buy.rs @@ -34,11 +34,11 @@ fn buy_emits_event() { Markets::::insert(market_id, market); let asset = Asset::ParimutuelShare(market_id, 0u16); - let amount = ::MinBetSize::get(); + let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); - let amount_minus_fees = 9900000000; - let fees = 100000000; + let amount_minus_fees = 99000000000; + let fees = 1000000000; assert_eq!(amount, amount_minus_fees + fees); System::assert_last_event( @@ -63,11 +63,11 @@ fn buy_balances_change_correctly() { AssetManager::free_balance(base_asset, &Parimutuel::pot_account(market_id)); let asset = Asset::ParimutuelShare(market_id, 0u16); - let amount = ::MinBetSize::get(); + let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); - let amount_minus_fees = 9900000000; - let fees = 100000000; + let amount_minus_fees = 99000000000; + let fees = 1000000000; assert_eq!(amount, amount_minus_fees + fees); assert_eq!(AssetManager::free_balance(base_asset, &ALICE), free_alice_before - amount); @@ -200,7 +200,7 @@ fn buy_fails_if_below_minimum_bet_size() { let amount = ::MinBetSize::get() - 1; assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), - Error::::AmountTooSmall + Error::::AmountBelowMinimumBetSize ); }); } diff --git a/zrml/parimutuel/src/tests/claim.rs b/zrml/parimutuel/src/tests/claim.rs index 2fb8f9efd..7c704a121 100644 --- a/zrml/parimutuel/src/tests/claim.rs +++ b/zrml/parimutuel/src/tests/claim.rs @@ -35,12 +35,11 @@ fn claim_rewards_emits_event() { Markets::::insert(market_id, market); let winner_asset = Asset::ParimutuelShare(market_id, 0u16); - let winner_amount = - ::MinBetSize::get() + ::MinBetSize::get(); + let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); let loser_asset = Asset::ParimutuelShare(market_id, 1u16); - let loser_amount = ::MinBetSize::get(); + let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -50,8 +49,8 @@ fn claim_rewards_emits_event() { assert_ok!(Parimutuel::claim_rewards(RuntimeOrigin::signed(ALICE), market_id)); - let withdrawn_asset_balance = 19800000000; - let actual_payoff = 29700000000; + let withdrawn_asset_balance = 198000000000; + let actual_payoff = 297000000000; System::assert_last_event( Event::RewardsClaimed { @@ -75,17 +74,14 @@ fn claim_rewards_categorical_changes_balances_correctly() { Markets::::insert(market_id, market); let winner_asset = Asset::ParimutuelShare(market_id, 0u16); - let winner_amount_0 = - ::MinBetSize::get() + ::MinBetSize::get(); + let winner_amount_0 = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount_0)); - let winner_amount_1 = ::MinBetSize::get() - + ::MinBetSize::get() - + ::MinBetSize::get(); + let winner_amount_1 = 30 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(CHARLIE), winner_asset, winner_amount_1)); let loser_asset = Asset::ParimutuelShare(market_id, 1u16); - let loser_amount = ::MinBetSize::get(); + let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -93,17 +89,17 @@ fn claim_rewards_categorical_changes_balances_correctly() { market.resolved_outcome = Some(OutcomeReport::Categorical(0u16)); Markets::::insert(market_id, market.clone()); - let actual_payoff = 59400000000; + let actual_payoff = 594000000000; let winner_amount = winner_amount_0 + winner_amount_1; let total_pot_amount = loser_amount + winner_amount; let total_fees = Percent::from_percent(1) * total_pot_amount; assert_eq!(actual_payoff, total_pot_amount - total_fees); - // 2/5 from 59400000000 = 23760000000 - let actual_payoff_alice = 23760000000; + // 2/5 from 594000000000 = 237600000000 + let actual_payoff_alice = 237600000000; assert_eq!(Percent::from_percent(40) * actual_payoff, actual_payoff_alice); - // 3/5 from 59400000000 = 35640000000 - let actual_payoff_charlie = 35640000000; + // 3/5 from 594000000000 = 356400000000 + let actual_payoff_charlie = 356400000000; assert_eq!(Percent::from_percent(60) * actual_payoff, actual_payoff_charlie); assert_eq!(actual_payoff_alice + actual_payoff_charlie, actual_payoff); @@ -255,12 +251,11 @@ fn claim_rewards_categorical_fails_if_no_winner() { Markets::::insert(market_id, market); let winner_asset = Asset::ParimutuelShare(market_id, 0u16); - let winner_amount = - ::MinBetSize::get() + ::MinBetSize::get(); + let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); let loser_asset = Asset::ParimutuelShare(market_id, 1u16); - let loser_amount = ::MinBetSize::get(); + let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -286,12 +281,11 @@ fn claim_rewards_categorical_fails_if_no_winning_shares() { Markets::::insert(market_id, market); let winner_asset = Asset::ParimutuelShare(market_id, 0u16); - let winner_amount = - ::MinBetSize::get() + ::MinBetSize::get(); + let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); let loser_asset = Asset::ParimutuelShare(market_id, 1u16); - let loser_amount = ::MinBetSize::get(); + let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -317,12 +311,11 @@ fn claim_refunds_works() { Markets::::insert(market_id, market); let alice_asset = Asset::ParimutuelShare(market_id, 0u16); - let alice_amount = - ::MinBetSize::get() + ::MinBetSize::get(); + let alice_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), alice_asset, alice_amount)); let bob_asset = Asset::ParimutuelShare(market_id, 1u16); - let bob_amount = ::MinBetSize::get(); + let bob_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), bob_asset, bob_amount)); let mut market = Markets::::get(market_id).unwrap(); diff --git a/zrml/parimutuel/src/tests/refund.rs b/zrml/parimutuel/src/tests/refund.rs index 6f3c95015..f346551c3 100644 --- a/zrml/parimutuel/src/tests/refund.rs +++ b/zrml/parimutuel/src/tests/refund.rs @@ -137,7 +137,7 @@ fn refund_fails_if_refund_not_allowed() { Markets::::insert(market_id, market); let asset = Asset::ParimutuelShare(market_id, 0u16); - let amount = ::MinBetSize::get(); + let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -163,7 +163,7 @@ fn refund_fails_if_refundable_balance_is_zero() { Markets::::insert(market_id, market); let asset = Asset::ParimutuelShare(market_id, 0u16); - let amount = ::MinBetSize::get(); + let amount = 2 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); let mut market = Markets::::get(market_id).unwrap(); @@ -192,7 +192,7 @@ fn refund_emits_event() { Markets::::insert(market_id, market); let asset = Asset::ParimutuelShare(market_id, 0u16); - let amount = ::MinBetSize::get(); + let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); let mut market = Markets::::get(market_id).unwrap(); From 53b0f641c0493783389f793df8bd29da615af1b4 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 12 Dec 2023 09:27:56 +0100 Subject: [PATCH 011/104] Replace fixed math operations with traited versions (#1149) * Replace `bmul` and `bdiv` with traited versions * Restructure directories * Replace `saturating_*` from neo-swaps * Fix formatting * Restructure zrml-swaps math * Implement and test `b*` * Fix formatting * Use new math in orderbook-v1 * Replace checked multiplication with new math * Use correct rounding in neo-swaps * Add docs * Update licenses * Remove `fixed` module from `primitives` * Fix formatting * Update primitives/src/math/fixed.rs Co-authored-by: Harald Heckmann --------- Co-authored-by: Harald Heckmann --- primitives/src/math/check_arithm_rslt.rs | 81 --- primitives/src/math/checked_ops_res.rs | 90 +++ primitives/src/math/consts.rs | 37 - primitives/src/math/fixed.rs | 664 ++++++++---------- primitives/src/math/mod.rs | 3 +- .../battery-station/src/xcm_config/fees.rs | 7 +- runtime/common/src/fees.rs | 5 +- runtime/zeitgeist/src/xcm_config/fees.rs | 7 +- zrml/market-commons/src/lib.rs | 13 +- zrml/neo-swaps/src/lib.rs | 49 +- zrml/neo-swaps/src/mock.rs | 8 +- zrml/neo-swaps/src/tests/buy.rs | 2 +- zrml/neo-swaps/src/tests/exit.rs | 8 +- zrml/neo-swaps/src/tests/join.rs | 8 +- zrml/neo-swaps/src/tests/mod.rs | 2 +- zrml/neo-swaps/src/tests/sell.rs | 2 +- zrml/orderbook/src/lib.rs | 52 +- zrml/parimutuel/src/lib.rs | 12 +- zrml/swaps/src/benchmarks.rs | 22 +- zrml/swaps/src/check_arithm_rslt.rs | 80 --- zrml/swaps/src/consts.rs | 36 - zrml/swaps/src/fixed.rs | 141 +--- zrml/swaps/src/lib.rs | 153 ++-- zrml/swaps/src/math.rs | 119 ++-- zrml/swaps/src/root.rs | 6 +- zrml/swaps/src/utils.rs | 24 +- 26 files changed, 631 insertions(+), 1000 deletions(-) delete mode 100644 primitives/src/math/check_arithm_rslt.rs create mode 100644 primitives/src/math/checked_ops_res.rs delete mode 100644 primitives/src/math/consts.rs delete mode 100644 zrml/swaps/src/check_arithm_rslt.rs delete mode 100644 zrml/swaps/src/consts.rs diff --git a/primitives/src/math/check_arithm_rslt.rs b/primitives/src/math/check_arithm_rslt.rs deleted file mode 100644 index 1bd010f06..000000000 --- a/primitives/src/math/check_arithm_rslt.rs +++ /dev/null @@ -1,81 +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 . -// -// This file incorporates work covered by the license above but -// published without copyright notice by Balancer Labs -// (, contact@balancer.finance) in the -// balancer-core repository -// . - -use crate::math::consts::ARITHM_OF; -use frame_support::dispatch::DispatchError; -use sp_runtime::traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; - -/// Check Arithmetic - Result -/// -/// Checked arithmetic operations returning `Result<_, DispatchError>`. -pub trait CheckArithmRslt: CheckedAdd + CheckedDiv + CheckedMul + CheckedSub { - /// Check Addition - Result - /// - /// Same as `sp_runtime::traits::CheckedAdd::checked_add` but returns a - /// `Result` instead of `Option`. - fn check_add_rslt(&self, n: &Self) -> Result; - - /// Check Division - Result - /// - /// Same as `sp_runtime::traits::CheckedDiv::checked_div` but returns a - /// `Result` instead of `Option`. - fn check_div_rslt(&self, n: &Self) -> Result; - - /// Check Multiplication - Result - /// - /// Same as `sp_runtime::traits::CheckedMul::checked_mul` but returns a - /// `Result` instead of `Option`. - fn check_mul_rslt(&self, n: &Self) -> Result; - - /// Check Subtraction - Result - /// - /// Same as `sp_runtime::traits::CheckedSub::checked_sub` but returns a - /// `Result` instead of `Option`. - fn check_sub_rslt(&self, n: &Self) -> Result; -} - -impl CheckArithmRslt for T -where - T: CheckedAdd + CheckedDiv + CheckedMul + CheckedSub, -{ - #[inline] - fn check_add_rslt(&self, n: &Self) -> Result { - self.checked_add(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_div_rslt(&self, n: &Self) -> Result { - self.checked_div(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_mul_rslt(&self, n: &Self) -> Result { - self.checked_mul(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_sub_rslt(&self, n: &Self) -> Result { - self.checked_sub(n).ok_or(ARITHM_OF) - } -} diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs new file mode 100644 index 000000000..107979fff --- /dev/null +++ b/primitives/src/math/checked_ops_res.rs @@ -0,0 +1,90 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use frame_support::dispatch::DispatchError; +use sp_arithmetic::{ + traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}, + ArithmeticError, +}; + +pub trait CheckedAddRes +where + Self: Sized, +{ + fn checked_add_res(&self, other: &Self) -> Result; +} + +pub trait CheckedSubRes +where + Self: Sized, +{ + fn checked_sub_res(&self, other: &Self) -> Result; +} + +pub trait CheckedMulRes +where + Self: Sized, +{ + fn checked_mul_res(&self, other: &Self) -> Result; +} + +pub trait CheckedDivRes +where + Self: Sized, +{ + fn checked_div_res(&self, other: &Self) -> Result; +} + +impl CheckedAddRes for T +where + T: CheckedAdd, +{ + #[inline] + fn checked_add_res(&self, other: &Self) -> Result { + self.checked_add(other).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } +} + +impl CheckedSubRes for T +where + T: CheckedSub, +{ + #[inline] + fn checked_sub_res(&self, other: &Self) -> Result { + self.checked_sub(other).ok_or(DispatchError::Arithmetic(ArithmeticError::Underflow)) + } +} + +impl CheckedMulRes for T +where + T: CheckedMul, +{ + #[inline] + fn checked_mul_res(&self, other: &Self) -> Result { + self.checked_mul(other).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } +} + +impl CheckedDivRes for T +where + T: CheckedDiv, +{ + #[inline] + fn checked_div_res(&self, other: &Self) -> Result { + self.checked_div(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) + } +} diff --git a/primitives/src/math/consts.rs b/primitives/src/math/consts.rs deleted file mode 100644 index 08e8f2aad..000000000 --- a/primitives/src/math/consts.rs +++ /dev/null @@ -1,37 +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 . -// -// This file incorporates work covered by the license above but -// published without copyright notice by Balancer Labs -// (, contact@balancer.finance) in the -// balancer-core repository -// . - -use crate::constants::BASE; -use frame_support::dispatch::DispatchError; - -pub const ARITHM_OF: DispatchError = DispatchError::Other("Arithmetic overflow"); - -/// The amount of precision to use in exponentiation. -pub const BPOW_PRECISION: u128 = 10; -/// The minimum value of the base parameter in bpow_approx. -pub const BPOW_APPROX_BASE_MIN: u128 = BASE / 4; -/// The maximum value of the base parameter in bpow_approx. -pub const BPOW_APPROX_BASE_MAX: u128 = 7 * BASE / 4; -/// The maximum number of terms from the binomial series used to calculate bpow_approx. -pub const BPOW_APPROX_MAX_ITERATIONS: u128 = 100; diff --git a/primitives/src/math/fixed.rs b/primitives/src/math/fixed.rs index 429bfc27d..f02d9ceca 100644 --- a/primitives/src/math/fixed.rs +++ b/primitives/src/math/fixed.rs @@ -22,176 +22,119 @@ // balancer-core repository // . -use crate::{ - constants::BASE, - math::{ - check_arithm_rslt::CheckArithmRslt, - consts::{ - BPOW_APPROX_BASE_MAX, BPOW_APPROX_BASE_MIN, BPOW_APPROX_MAX_ITERATIONS, BPOW_PRECISION, - }, - }, -}; +use super::checked_ops_res::{CheckedAddRes, CheckedDivRes, CheckedMulRes, CheckedSubRes}; +use crate::constants::BASE; use alloc::{ borrow::ToOwned, format, string::{String, ToString}, }; -use core::{cmp::Ordering, convert::TryFrom}; +use core::{cmp::Ordering, convert::TryFrom, marker::PhantomData}; use fixed::{traits::Fixed, ParseFixedError}; -use frame_support::dispatch::DispatchError; +use frame_support::{dispatch::DispatchError, ensure}; +use sp_arithmetic::{ + traits::{AtLeast32BitUnsigned, Zero}, + ArithmeticError, +}; -pub fn btoi(a: u128) -> Result { - a.check_div_rslt(&BASE) +/// Trait for safely obtaining constants converted to generic types in a Substrate context. +pub trait BaseProvider { + /// Returns a constant converted to type `T` and errors if the conversion failed. + fn get() -> Result; } -pub fn bfloor(a: u128) -> Result { - btoi(a)?.check_mul_rslt(&BASE) -} +/// Used to avoid saturating operations when converting `BASE` to `Balance`. +pub struct ZeitgeistBase(PhantomData); -pub fn bsub_sign(a: u128, b: u128) -> Result<(u128, bool), DispatchError> { - Ok(if a >= b { (a.check_sub_rslt(&b)?, false) } else { (b.check_sub_rslt(&a)?, true) }) -} - -pub fn bmul(a: u128, b: u128) -> Result { - let c0 = a.check_mul_rslt(&b)?; - let c1 = c0.check_add_rslt(&BASE.check_div_rslt(&2)?)?; - c1.check_div_rslt(&BASE) +impl BaseProvider for ZeitgeistBase +where + T: AtLeast32BitUnsigned, +{ + fn get() -> Result { + BASE.try_into() + .map_err(|_| DispatchError::Other("ZeitgeistBase failed to convert BASE to Balance")) + } } -pub fn bmul_floor(a: u128, b: u128) -> Result { - // checked_mul already rounds down - let c0 = a.check_mul_rslt(&b)?; - c0.check_div_rslt(&BASE) -} +/// Performs fixed point multiplication and errors with `DispatchError` in case of over- or +/// underflows. +pub trait FixedMul +where + Self: Sized, +{ + /// Calculates the product of `self` and `other` and rounds to the nearest representable fixed + /// point number. + fn bmul(&self, other: Self) -> Result; -pub fn bdiv(a: u128, b: u128) -> Result { - let c0 = a.check_mul_rslt(&BASE)?; - let c1 = c0.check_add_rslt(&b.check_div_rslt(&2)?)?; - c1.check_div_rslt(&b) -} + /// Calculates the product of `self` and `other` and rounds down. + fn bmul_floor(&self, other: Self) -> Result; -pub fn bdiv_floor(a: u128, b: u128) -> Result { - let c0 = a.check_mul_rslt(&BASE)?; - // checked_div already rounds down - c0.check_div_rslt(&b) + /// Calculates the product of `self` and `other` and rounds up. + fn bmul_ceil(&self, other: Self) -> Result; } -pub fn bpowi(a: u128, n: u128) -> Result { - let mut z = if n % 2 != 0 { a } else { BASE }; - - let mut b = a; - let mut m = n.check_div_rslt(&2)?; +/// Performs fixed point division and errors with `DispatchError` in case of over- or +/// underflows and division by zero. +pub trait FixedDiv +where + Self: Sized, +{ + /// Calculates the fixed point division of `self` by `other` and rounds to the nearest + /// representable fixed point number. + fn bdiv(&self, other: Self) -> Result; - while m != 0 { - b = bmul(b, b)?; + /// Calculates the fixed point division of `self` by `other` and rounds down. + fn bdiv_floor(&self, other: Self) -> Result; - if m % 2 != 0 { - z = bmul(z, b)?; - } + /// Calculates the fixed point division of `self` by `other` and rounds up. + fn bdiv_ceil(&self, other: Self) -> Result; +} - m = m.check_div_rslt(&2)?; +impl FixedMul for T +where + T: AtLeast32BitUnsigned, +{ + fn bmul(&self, other: Self) -> Result { + let prod = self.checked_mul_res(&other)?; + let adjustment = ZeitgeistBase::::get()?.checked_div_res(&2u8.into())?; + let prod_adjusted = prod.checked_add_res(&adjustment)?; + prod_adjusted.checked_div_res(&ZeitgeistBase::get()?) } - Ok(z) -} - -/// Compute the power `base ** exp`. -/// -/// # Arguments -/// -/// * `base`: The base, a number between `BASE / 4` and `7 * BASE / 4` -/// * `exp`: The exponent -/// -/// # Errors -/// -/// If this function encounters an arithmetic over/underflow, or if the numerical limits -/// for `base` (specified above) are violated, a `DispatchError::Other` is returned. -pub fn bpow(base: u128, exp: u128) -> Result { - let whole = bfloor(exp)?; - let remain = exp.check_sub_rslt(&whole)?; - - let whole_pow = bpowi(base, btoi(whole)?)?; - - if remain == 0 { - return Ok(whole_pow); + fn bmul_floor(&self, other: Self) -> Result { + self.checked_mul_res(&other)?.checked_div_res(&ZeitgeistBase::get()?) } - let partial_result = bpow_approx(base, remain)?; - bmul(whole_pow, partial_result) + fn bmul_ceil(&self, other: Self) -> Result { + let prod = self.checked_mul_res(&other)?; + let adjustment = ZeitgeistBase::::get()?.checked_sub_res(&1u8.into())?; + let prod_adjusted = prod.checked_add_res(&adjustment)?; + prod_adjusted.checked_div_res(&ZeitgeistBase::get()?) + } } -/// Compute an estimate of the power `base ** exp`. -/// -/// # Arguments -/// -/// * `base`: The base, an element of `[BASE / 4, 7 * BASE / 4]` -/// * `exp`: The exponent, an element of `[0, BASE]` -/// -/// # Errors -/// -/// If this function encounters an arithmetic over/underflow, or if the numerical limits -/// for `base` or `exp` (specified above) are violated, a `DispatchError::Other` is -/// returned. -pub fn bpow_approx(base: u128, exp: u128) -> Result { - // We use the binomial power series for this calculation. We stop adding terms to - // the result as soon as one term is smaller than `BPOW_PRECISION`. (Thanks to the - // limits on `base` and `exp`, this means that the total error should not exceed - // 4*BPOW_PRECISION`.) - if exp > BASE { - return Err(DispatchError::Other("[bpow_approx]: expected exp <= BASE")); - } - if base < BPOW_APPROX_BASE_MIN { - return Err(DispatchError::Other("[bpow_approx]: expected base >= BASE / 4")); - } - if base > BPOW_APPROX_BASE_MAX { - return Err(DispatchError::Other("[bpow_approx]: expected base <= 7 * BASE / 4")); +impl FixedDiv for T +where + T: AtLeast32BitUnsigned, +{ + fn bdiv(&self, other: Self) -> Result { + let prod = self.checked_mul_res(&ZeitgeistBase::get()?)?; + let adjustment = other.checked_div_res(&2u8.into())?; + let prod_adjusted = prod.checked_add_res(&adjustment)?; + prod_adjusted.checked_div_res(&other) } - let a = exp; - let (x, xneg) = bsub_sign(base, BASE)?; - let mut term = BASE; - let mut sum = term; - let mut negative = false; - - // term(k) = numer / denom - // = (product(a - i - 1, i=1-->k) * x^k) / (k!) - // each iteration, multiply previous term by (a-(k-1)) * x / k - // continue until term is less than precision - for i in 1..=BPOW_APPROX_MAX_ITERATIONS { - if term < BPOW_PRECISION { - break; - } - - let big_k = i.check_mul_rslt(&BASE)?; - let (c, cneg) = bsub_sign(a, big_k.check_sub_rslt(&BASE)?)?; - term = bmul(term, bmul(c, x)?)?; - term = bdiv(term, big_k)?; - if term == 0 { - break; - } - - if xneg { - negative = !negative; - } - if cneg { - negative = !negative; - } - if negative { - // Never underflows. In fact, the absolute value of the terms is strictly - // decreasing thanks to the numerical limits. - sum = sum.check_sub_rslt(&term)?; - } else { - sum = sum.check_add_rslt(&term)?; - } + fn bdiv_floor(&self, other: Self) -> Result { + self.checked_mul_res(&ZeitgeistBase::get()?)?.checked_div_res(&other) } - // If term is still large, then MAX_ITERATIONS was violated (can't happen with the current - // limits). - if term >= BPOW_PRECISION { - return Err(DispatchError::Other("[bpow_approx] Maximum number of iterations exceeded")); + fn bdiv_ceil(&self, other: Self) -> Result { + ensure!(other != Zero::zero(), DispatchError::Arithmetic(ArithmeticError::DivisionByZero)); + let prod = self.checked_mul_res(&ZeitgeistBase::get()?)?; + let adjustment = other.checked_sub_res(&1u8.into())?; + let prod_adjusted = prod.checked_add_res(&adjustment)?; + prod_adjusted.checked_div_res(&other) } - - Ok(sum) } /// Converts a fixed point decimal number into another type. @@ -318,262 +261,247 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{ - assert_approx, - constants::BASE, - math::{ - consts::{ARITHM_OF, BPOW_PRECISION}, - fixed::{bdiv, bmul, bpow, bpow_approx}, - }, - }; + use crate::assert_approx; use fixed::{traits::ToFixed, FixedU128}; - use frame_support::{assert_err, dispatch::DispatchError}; - use more_asserts::assert_le; + use sp_arithmetic::ArithmeticError; use test_case::test_case; use typenum::U80; - pub const ERR: Result = Err(ARITHM_OF); - - macro_rules! create_tests { - ( - $op:ident; - - 0 => $_0_0:expr, $_0_1:expr, $_0_2:expr, $_0_3:expr; - 1 => $_1_0:expr, $_1_1:expr, $_1_2:expr, $_1_3:expr; - 2 => $_2_0:expr, $_2_1:expr, $_2_2:expr, $_2_3:expr; - 3 => $_3_0:expr, $_3_1:expr, $_3_2:expr, $_3_3:expr; - max_n => $max_n_0:expr, $max_n_1:expr, $max_n_2:expr, $max_n_3:expr; - n_max => $n_max_0:expr, $n_max_1:expr, $n_max_2:expr, $n_max_3:expr; - ) => { - assert_eq!($op(0, 0 * BASE), $_0_0); - assert_eq!($op(0, 1 * BASE), $_0_1); - assert_eq!($op(0, 2 * BASE), $_0_2); - assert_eq!($op(0, 3 * BASE), $_0_3); - - assert_eq!($op(1 * BASE, 0 * BASE), $_1_0); - assert_eq!($op(1 * BASE, 1 * BASE), $_1_1); - assert_eq!($op(1 * BASE, 2 * BASE), $_1_2); - assert_eq!($op(1 * BASE, 3 * BASE), $_1_3); - - assert_eq!($op(2 * BASE, 0 * BASE), $_2_0); - assert_eq!($op(2 * BASE, 1 * BASE), $_2_1); - assert_eq!($op(2 * BASE, 2 * BASE), $_2_2); - assert_eq!($op(2 * BASE, 3 * BASE), $_2_3); - - assert_eq!($op(3 * BASE, 0 * BASE), $_3_0); - assert_eq!($op(3 * BASE, 1 * BASE), $_3_1); - assert_eq!($op(3 * BASE, 2 * BASE), $_3_2); - assert_eq!($op(3 * BASE, 3 * BASE), $_3_3); - - assert_eq!($op(u128::MAX, 0 * BASE), $max_n_0); - assert_eq!($op(u128::MAX, 1 * BASE), $max_n_1); - assert_eq!($op(u128::MAX, 2 * BASE), $max_n_2); - assert_eq!($op(u128::MAX, 3 * BASE), $max_n_3); - - assert_eq!($op(0, u128::MAX), $n_max_0); - assert_eq!($op(1, u128::MAX), $n_max_1); - assert_eq!($op(2, u128::MAX), $n_max_2); - assert_eq!($op(3, u128::MAX), $n_max_3); - }; + pub(crate) const _1: u128 = BASE; + pub(crate) const _2: u128 = 2 * _1; + pub(crate) const _3: u128 = 3 * _1; + pub(crate) const _4: u128 = 4 * _1; + pub(crate) const _5: u128 = 5 * _1; + pub(crate) const _6: u128 = 6 * _1; + pub(crate) const _7: u128 = 7 * _1; + pub(crate) const _9: u128 = 9 * _1; + pub(crate) const _10: u128 = 10 * _1; + pub(crate) const _20: u128 = 20 * _1; + pub(crate) const _70: u128 = 70 * _1; + pub(crate) const _80: u128 = 80 * _1; + pub(crate) const _100: u128 = 100 * _1; + pub(crate) const _101: u128 = 101 * _1; + + pub(crate) const _1_2: u128 = _1 / 2; + + pub(crate) const _1_3: u128 = _1 / 3; + pub(crate) const _2_3: u128 = _2 / 3; + + pub(crate) const _1_4: u128 = _1 / 4; + pub(crate) const _3_4: u128 = _3 / 4; + + pub(crate) const _1_5: u128 = _1 / 5; + + pub(crate) const _1_6: u128 = _1 / 6; + pub(crate) const _5_6: u128 = _5 / 6; + + pub(crate) const _1_10: u128 = _1 / 10; + + #[test_case(0, 0, 0)] + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, 0, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _2)] + #[test_case(_1, _3, _3)] + #[test_case(_2, 0, 0)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _4)] + #[test_case(_2, _3, _6)] + #[test_case(_3, 0, 0)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _6)] + #[test_case(_3, _3, _9)] + #[test_case(_4, _1_2, _2)] + #[test_case(_5, _1 + _1_2, _7 + _1_2)] + #[test_case(_1 + 1, _2, _2 + 2)] + #[test_case(9_999_999_999, _2, 19_999_999_998)] + #[test_case(9_999_999_999, _10, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, _1_2)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, 2_499_999_999)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, 3_333_333_332)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1_10)] + #[test_case(10_000_000_005, _1_10, _1_10 + 1)] + fn bmul_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bmul(rhs).unwrap(), expected); } - #[test] - fn bmul_rounding_behaviours() { - assert_eq!(bmul(3u128, 33_333_333_333u128).unwrap(), 10u128); - assert_eq!(bmul_floor(3u128, 33_333_333_333u128).unwrap(), 9u128); + #[test_case(0, 0, 0)] + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, 0, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _2)] + #[test_case(_1, _3, _3)] + #[test_case(_2, 0, 0)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _4)] + #[test_case(_2, _3, _6)] + #[test_case(_3, 0, 0)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _6)] + #[test_case(_3, _3, _9)] + #[test_case(_4, _1_2, _2)] + #[test_case(_5, _1 + _1_2, _7 + _1_2)] + #[test_case(_1 + 1, _2, _2 + 2)] + #[test_case(9_999_999_999, _2, 19_999_999_998)] + #[test_case(9_999_999_999, _10, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, 4_999_999_999)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, 2_499_999_999)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, 3_333_333_331)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1_10)] + #[test_case(10_000_000_005, _1_10, _1_10)] + fn bfloor_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bmul_floor(rhs).unwrap(), expected); + } + + #[test_case(0, 0, 0)] + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, 0, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _2)] + #[test_case(_1, _3, _3)] + #[test_case(_2, 0, 0)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _4)] + #[test_case(_2, _3, _6)] + #[test_case(_3, 0, 0)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _6)] + #[test_case(_3, _3, _9)] + #[test_case(_4, _1_2, _2)] + #[test_case(_5, _1 + _1_2, _7 + _1_2)] + #[test_case(_1 + 1, _2, _2 + 2)] + #[test_case(9_999_999_999, _2, 19_999_999_998)] + #[test_case(9_999_999_999, _10, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, _1_2)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, _1_4)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, 3_333_333_332)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1_10 + 1)] + #[test_case(10_000_000_005, _1_10, _1_10 + 1)] + fn bceil_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bmul_ceil(rhs).unwrap(), expected); } #[test] - fn bdiv_rounding_behaviors() { - assert_eq!(bdiv(14u128, 3u128).unwrap(), 46_666_666_667u128); - assert_eq!(bdiv_floor(14u128, 3u128).unwrap(), 46_666_666_666u128); + fn bmul_fails() { + assert_eq!(u128::MAX.bmul(_2), Err(DispatchError::Arithmetic(ArithmeticError::Overflow))); } #[test] - fn bdiv_has_minimum_set_of_correct_values() { - create_tests!( - bdiv; - 0 => ERR, Ok(0), Ok(0), Ok(0); - 1 => ERR, Ok(BASE), Ok(BASE / 2), Ok(BASE / 3); - 2 => ERR, Ok(2 * BASE), Ok(BASE), Ok(6666666667); - 3 => ERR, Ok(3 * BASE), Ok(3 * BASE / 2), Ok(BASE); - max_n => ERR, ERR, ERR, ERR; - n_max => Ok(0), Ok(1 / BASE), Ok(2 / BASE), Ok(3 / BASE); + fn bmul_floor_fails() { + assert_eq!( + u128::MAX.bmul_floor(_2), + Err(DispatchError::Arithmetic(ArithmeticError::Overflow)) ); } #[test] - fn bmul_has_minimum_set_of_correct_values() { - create_tests!( - bmul; - 0 => Ok(0), Ok(0), Ok(0), Ok(0); - 1 => Ok(0), Ok(BASE), Ok(2 * BASE), Ok(3 * BASE); - 2 => Ok(0), Ok(2 * BASE), Ok(4 * BASE), Ok(6 * BASE); - 3 => Ok(0), Ok(3 * BASE), Ok(6 * BASE), Ok(9 * BASE); - max_n => Ok(0), ERR, ERR, ERR; - n_max => Ok(0), ERR, ERR, ERR; + fn bmul_ceil_fails() { + assert_eq!( + u128::MAX.bmul_ceil(_2), + Err(DispatchError::Arithmetic(ArithmeticError::Overflow)) ); } - #[test] - fn bpow_has_minimum_set_of_correct_values() { - let test_vector: Vec<(u128, u128, u128)> = vec![ - (2500000000, 0, 10000000000), - (2500000000, 10000000000, 2500000000), - (2500000000, 33333333333, 98431332), - (2500000000, 200000000, 9726549474), - (2500000000, 500000000000, 0), - (5000000000, 0, 10000000000), - (5000000000, 10000000000, 5000000000), - (5000000000, 33333333333, 992125657), - (5000000000, 200000000, 9862327044), - (5000000000, 500000000000, 0), - (7500000000, 0, 10000000000), - (7500000000, 10000000000, 7500000000), - (7500000000, 33333333333, 3832988750), - (7500000000, 200000000, 9942628790), - (7500000000, 500000000000, 5663), - (10000000000, 0, 10000000000), - (10000000000, 10000000000, 10000000000), - (10000000000, 33333333333, 10000000000), - (10000000000, 200000000, 10000000000), - (10000000000, 500000000000, 10000000000), - (12500000000, 0, 10000000000), - (12500000000, 10000000000, 12500000000), - (12500000000, 33333333333, 21039401269), - (12500000000, 200000000, 10044728444), - (12500000000, 500000000000, 700649232162408), - (15000000000, 0, 10000000000), - (15000000000, 10000000000, 15000000000), - (15000000000, 33333333333, 38634105686), - (15000000000, 200000000, 10081422716), - (15000000000, 500000000000, 6376215002140495869), - (17500000000, 0, 10000000000), - (17500000000, 10000000000, 17500000000), - (17500000000, 33333333333, 64584280985), - (17500000000, 200000000, 10112551840), - (17500000000, 500000000000, 14187387615511831479362), - ]; - for (base, exp, expected) in test_vector.iter() { - let result = bpow(*base, *exp).unwrap(); - let precision = *expected / BASE + 4 * BPOW_PRECISION; // relative + absolute error - let diff = if result > *expected { result - *expected } else { *expected - result }; - assert_le!(diff, precision); - } + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _1_2)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _1)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _1 + _1_2)] + #[test_case(_3, _3, _1)] + #[test_case(_3 + _1_2, _1_2, _7)] + #[test_case(99_999_999_999, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_1, _3, _1_3)] + #[test_case(_2, _3, _2_3 + 1)] + #[test_case(99_999_999_999, _10, _1)] + #[test_case(99_999_999_994, _10, 9_999_999_999)] + #[test_case(5, _10, 1)] + #[test_case(4, _10, 0)] + fn bdiv_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bdiv(rhs).unwrap(), expected); + } + + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _1_2)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _1)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _1 + _1_2)] + #[test_case(_3, _3, _1)] + #[test_case(_3 + _1_2, _1_2, _7)] + #[test_case(99_999_999_999, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_1, _3, _1_3)] + #[test_case(_2, _3, _2_3)] + #[test_case(99_999_999_999, _10, 9_999_999_999)] + #[test_case(99_999_999_994, _10, 9_999_999_999)] + #[test_case(5, _10, 0)] + #[test_case(4, _10, 0)] + fn bdiv_floor_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bdiv_floor(rhs).unwrap(), expected); + } + + #[test_case(0, _1, 0)] + #[test_case(0, _2, 0)] + #[test_case(0, _3, 0)] + #[test_case(_1, _1, _1)] + #[test_case(_1, _2, _1_2)] + #[test_case(_2, _1, _2)] + #[test_case(_2, _2, _1)] + #[test_case(_3, _1, _3)] + #[test_case(_3, _2, _1 + _1_2)] + #[test_case(_3, _3, _1)] + #[test_case(_3 + _1_2, _1_2, _7)] + #[test_case(99_999_999_999, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_1, _3, _1_3 + 1)] + #[test_case(_2, _3, _2_3 + 1)] + #[test_case(99_999_999_999, _10, _1)] + #[test_case(99_999_999_994, _10, _1)] + #[test_case(5, _10, 1)] + #[test_case(4, _10, 1)] + fn bdiv_ceil_works(lhs: u128, rhs: u128, expected: u128) { + assert_eq!(lhs.bdiv_ceil(rhs).unwrap(), expected); } #[test] - fn bpow_returns_error_when_parameters_are_outside_of_specified_limits() { - let test_vector: Vec<(u128, u128)> = - vec![(BASE / 10, 3 * BASE / 2), (2 * BASE - BASE / 10, 3 * BASE / 2)]; - for (base, exp) in test_vector.iter() { - assert!(bpow(*base, *exp).is_err()); - } + fn bdiv_fails() { + assert_eq!( + 123456789u128.bdiv(0), + Err(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) + ); } #[test] - fn bpow_approx_has_minimum_set_of_correct_values() { - let precision = 4 * BPOW_PRECISION; - let test_vector: Vec<(u128, u128, u128)> = vec![ - (2500000000, 0, 10000000000), - (2500000000, 1000000000, 8705505632), - (2500000000, 2000000000, 7578582832), - (2500000000, 3000000000, 6597539553), - (2500000000, 4000000000, 5743491774), - (2500000000, 5000000000, 5000000000), - (2500000000, 6000000000, 4352752816), - (2500000000, 7000000000, 3789291416), - (2500000000, 8000000000, 3298769776), - (2500000000, 9000000000, 2871745887), - (2500000000, 10000000000, 2500000000), - (5000000000, 0, 10000000000), - (5000000000, 1000000000, 9330329915), - (5000000000, 2000000000, 8705505632), - (5000000000, 3000000000, 8122523963), - (5000000000, 4000000000, 7578582832), - (5000000000, 5000000000, 7071067811), - (5000000000, 6000000000, 6597539553), - (5000000000, 7000000000, 6155722066), - (5000000000, 8000000000, 5743491774), - (5000000000, 9000000000, 5358867312), - (5000000000, 10000000000, 5000000000), - (7500000000, 0, 10000000000), - (7500000000, 1000000000, 9716416578), - (7500000000, 2000000000, 9440875112), - (7500000000, 3000000000, 9173147546), - (7500000000, 4000000000, 8913012289), - (7500000000, 5000000000, 8660254037), - (7500000000, 6000000000, 8414663590), - (7500000000, 7000000000, 8176037681), - (7500000000, 8000000000, 7944178807), - (7500000000, 9000000000, 7718895067), - (7500000000, 10000000000, 7500000000), - (10000000000, 0, 10000000000), - (10000000000, 1000000000, 10000000000), - (10000000000, 2000000000, 10000000000), - (10000000000, 3000000000, 10000000000), - (10000000000, 4000000000, 10000000000), - (10000000000, 5000000000, 10000000000), - (10000000000, 6000000000, 10000000000), - (10000000000, 7000000000, 10000000000), - (10000000000, 8000000000, 10000000000), - (10000000000, 9000000000, 10000000000), - (10000000000, 10000000000, 10000000000), - (12500000000, 0, 10000000000), - (12500000000, 1000000000, 10225651825), - (12500000000, 2000000000, 10456395525), - (12500000000, 3000000000, 10692345999), - (12500000000, 4000000000, 10933620739), - (12500000000, 5000000000, 11180339887), - (12500000000, 6000000000, 11432626298), - (12500000000, 7000000000, 11690605597), - (12500000000, 8000000000, 11954406247), - (12500000000, 9000000000, 12224159606), - (12500000000, 10000000000, 12500000000), - (15000000000, 0, 10000000000), - (15000000000, 1000000000, 10413797439), - (15000000000, 2000000000, 10844717711), - (15000000000, 3000000000, 11293469354), - (15000000000, 4000000000, 11760790225), - (15000000000, 5000000000, 12247448713), - (15000000000, 6000000000, 12754245006), - (15000000000, 7000000000, 13282012399), - (15000000000, 8000000000, 13831618672), - (15000000000, 9000000000, 14403967511), - (15000000000, 10000000000, 15000000000), - (17500000000, 0, 10000000000), - (17500000000, 1000000000, 10575570503), - (17500000000, 2000000000, 11184269147), - (17500000000, 3000000000, 11828002689), - (17500000000, 4000000000, 12508787635), - (17500000000, 5000000000, 13228756555), - (17500000000, 6000000000, 13990164762), - (17500000000, 7000000000, 14795397379), - (17500000000, 8000000000, 15646976811), - (17500000000, 9000000000, 16547570643), - (17500000000, 10000000000, 17500000000), - ]; - for (base, exp, expected) in test_vector.iter() { - let result = bpow_approx(*base, *exp).unwrap(); - let diff = if result > *expected { result - *expected } else { *expected - result }; - assert_le!(diff, precision); - } + fn bdiv_floor_fails() { + assert_eq!( + 123456789u128.bdiv_floor(0), + Err(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) + ); } #[test] - fn bpow_approx_returns_error_when_parameters_are_outside_of_specified_limits() { - let test_vector: Vec<(u128, u128, DispatchError)> = vec![ - (BASE, BASE + 1, DispatchError::Other("[bpow_approx]: expected exp <= BASE")), - (BASE / 10, BASE / 2, DispatchError::Other("[bpow_approx]: expected base >= BASE / 4")), - ( - 2 * BASE - BASE / 10, - BASE / 2, - DispatchError::Other("[bpow_approx]: expected base <= 7 * BASE / 4"), - ), - ]; - for (base, exp, err) in test_vector.iter() { - assert_err!(bpow_approx(*base, *exp), *err); - } + fn bdiv_ceil_fails() { + assert_eq!( + 123456789u128.bdiv_ceil(0), + Err(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) + ); } #[test_case(0, 10, 0.0)] diff --git a/primitives/src/math/mod.rs b/primitives/src/math/mod.rs index 42a27f245..e89419a3a 100644 --- a/primitives/src/math/mod.rs +++ b/primitives/src/math/mod.rs @@ -15,6 +15,5 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -pub mod check_arithm_rslt; -mod consts; +pub mod checked_ops_res; pub mod fixed; diff --git a/runtime/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs index 38562ca0f..fd9dced37 100644 --- a/runtime/battery-station/src/xcm_config/fees.rs +++ b/runtime/battery-station/src/xcm_config/fees.rs @@ -20,8 +20,9 @@ use crate::{Balance, CurrencyId}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::CustomMetadata}; -use zrml_swaps::fixed::bmul; +use zeitgeist_primitives::{ + constants::BalanceFractionalDecimals, math::fixed::FixedMul, types::CustomMetadata, +}; /// The fee cost per second for transferring the native token in cents. pub fn native_per_second() -> Balance { @@ -68,7 +69,7 @@ impl< let foreign_decimals = metadata.decimals; let fee_unadjusted = if let Some(fee_factor) = metadata.additional.xcm.fee_factor { - bmul(default_per_second, fee_factor).ok()? + default_per_second.bmul(fee_factor).ok()? } else { default_per_second }; diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index 85ddc7543..daf99543a 100644 --- a/runtime/common/src/fees.rs +++ b/runtime/common/src/fees.rs @@ -81,8 +81,7 @@ macro_rules! impl_foreign_fees { }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::{Convert, DispatchInfoOf, PostDispatchInfoOf}; - use zeitgeist_primitives::types::TxPaymentAssetId; - use zrml_swaps::check_arithm_rslt::CheckArithmRslt; + use zeitgeist_primitives::{math::fixed::FixedMul, types::TxPaymentAssetId}; #[repr(u8)] pub enum CustomTxError { @@ -108,7 +107,7 @@ macro_rules! impl_foreign_fees { // less DOT than ZTG is paid for fees. // Assume a fee_factor of 20_000_000_000, then the fee would result in // 20_000_000_000 / 10_000_000_000 = 2 units per ZTG - let converted_fee = zrml_swaps::fixed::bmul(native_fee, fee_factor).map_err(|_| { + let converted_fee = native_fee.bmul(fee_factor).map_err(|_| { TransactionValidityError::Invalid(InvalidTransaction::Custom( CustomTxError::FeeConversionArith as u8, )) diff --git a/runtime/zeitgeist/src/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs index 5b186610b..a40ce9177 100644 --- a/runtime/zeitgeist/src/xcm_config/fees.rs +++ b/runtime/zeitgeist/src/xcm_config/fees.rs @@ -20,8 +20,9 @@ use crate::{Balance, CurrencyId}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::CustomMetadata}; -use zrml_swaps::fixed::bmul; +use zeitgeist_primitives::{ + constants::BalanceFractionalDecimals, math::fixed::FixedMul, types::CustomMetadata, +}; /// The fee cost per second for transferring the native token in cents. pub fn native_per_second() -> Balance { @@ -68,7 +69,7 @@ impl< let foreign_decimals = metadata.decimals; let fee_unadjusted = if let Some(fee_factor) = metadata.additional.xcm.fee_factor { - bmul(default_per_second, fee_factor).ok()? + default_per_second.bmul(fee_factor).ok()? } else { default_per_second }; diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index af6d59b77..acd8391b4 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -44,12 +44,15 @@ mod pallet { use parity_scale_codec::{FullCodec, MaxEncodedLen}; use sp_runtime::{ traits::{ - AccountIdConversion, AtLeast32Bit, AtLeast32BitUnsigned, CheckedAdd, - MaybeSerializeDeserialize, Member, Saturating, + AccountIdConversion, AtLeast32Bit, AtLeast32BitUnsigned, MaybeSerializeDeserialize, + Member, Saturating, }, - ArithmeticError, DispatchError, SaturatedConversion, + DispatchError, SaturatedConversion, + }; + use zeitgeist_primitives::{ + math::checked_ops_res::CheckedAddRes, + types::{Asset, Market, PoolId}, }; - use zeitgeist_primitives::types::{Asset, Market, PoolId}; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); @@ -131,7 +134,7 @@ mod pallet { // Returns `Err` if `MarketId` addition overflows. pub fn next_market_id() -> Result { let id = MarketCounter::::get(); - let new_counter = id.checked_add(&1u8.into()).ok_or(ArithmeticError::Overflow)?; + let new_counter = id.checked_add_res(&1u8.into())?; >::put(new_counter); Ok(id) } diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 509c361bc..016190399 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -58,7 +58,10 @@ mod pallet { }; use zeitgeist_primitives::{ constants::{BASE, CENT}, - math::fixed::{bdiv, bmul}, + math::{ + checked_ops_res::{CheckedAddRes, CheckedSubRes}, + fixed::{FixedDiv, FixedMul}, + }, traits::{CompleteSetOperationsApi, DeployPoolApi, DistributeFees}, types::{Asset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; @@ -491,7 +494,7 @@ mod pallet { } = Self::distribute_fees(market_id, pool, amount_in)?; let swap_amount_out = pool.calculate_swap_amount_out_for_buy(asset_out, amount_in_minus_fees)?; - let amount_out = swap_amount_out.saturating_add(amount_in_minus_fees); + let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); // Instead of letting `who` buy the complete sets and then transfer almost all of // the outcomes to the pool account, we prevent `(n-1)` storage reads by using the @@ -606,28 +609,23 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { - // FIXME Round up to avoid exploits. - let ratio = bdiv( - pool_shares_amount.saturated_into(), - pool.liquidity_shares_manager.total_shares()?.saturated_into(), - )?; + let ratio = + pool_shares_amount.bdiv_ceil(pool.liquidity_shares_manager.total_shares()?)?; let mut amounts_in = vec![]; for (&asset, &max_amount_in) in pool.assets().iter().zip(max_amounts_in.iter()) { let balance_in_pool = pool.reserve_of(&asset)?; - // FIXME Round up to avoid exploits. - let amount_in = bmul(ratio, balance_in_pool.saturated_into())?.saturated_into(); + let amount_in = ratio.bmul_ceil(balance_in_pool)?; amounts_in.push(amount_in); ensure!(amount_in <= max_amount_in, Error::::AmountInAboveMax); T::MultiCurrency::transfer(asset, &who, &pool.account_id, amount_in)?; } - for ((_, balance), &amount_in) in pool.reserves.iter_mut().zip(amounts_in.iter()) { - *balance = balance.saturating_add(amount_in); + for ((_, balance), amount_in) in pool.reserves.iter_mut().zip(amounts_in.iter()) { + *balance = balance.checked_add_res(amount_in)?; } pool.liquidity_shares_manager.join(&who, pool_shares_amount)?; - let new_liquidity_parameter = pool.liquidity_parameter.saturating_add( - bmul(ratio.saturated_into(), pool.liquidity_parameter.saturated_into())? - .saturated_into(), - ); + let new_liquidity_parameter = pool + .liquidity_parameter + .checked_add_res(&ratio.bmul(pool.liquidity_parameter)?)?; pool.liquidity_parameter = new_liquidity_parameter; Self::deposit_event(Event::::JoinExecuted { who: who.clone(), @@ -656,22 +654,18 @@ mod pallet { pool.liquidity_shares_manager.fees == Zero::zero(), Error::::OutstandingFees ); - let ratio = bdiv( - pool_shares_amount.saturated_into(), - pool.liquidity_shares_manager.total_shares()?.saturated_into(), - )?; + let ratio = + pool_shares_amount.bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; let mut amounts_out = vec![]; for (&asset, &min_amount_out) in pool.assets().iter().zip(min_amounts_out.iter()) { let balance_in_pool = pool.reserve_of(&asset)?; - let amount_out: BalanceOf = - bmul(ratio, balance_in_pool.saturated_into())?.saturated_into(); + let amount_out = ratio.bmul_floor(balance_in_pool)?; amounts_out.push(amount_out); ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); T::MultiCurrency::transfer(asset, &pool.account_id, &who, amount_out)?; } - for ((_, balance), &amount_out) in pool.reserves.iter_mut().zip(amounts_out.iter()) - { - *balance = balance.saturating_sub(amount_out); + for ((_, balance), amount_out) in pool.reserves.iter_mut().zip(amounts_out.iter()) { + *balance = balance.checked_sub_res(amount_out)?; } pool.liquidity_shares_manager.exit(&who, pool_shares_amount)?; if pool.liquidity_shares_manager.total_shares()? == Zero::zero() { @@ -688,9 +682,7 @@ mod pallet { }); } else { let liq = pool.liquidity_parameter; - let new_liquidity_parameter = liq.saturating_sub( - bmul(ratio.saturated_into(), liq.saturated_into())?.saturated_into(), - ); + let new_liquidity_parameter = liq.checked_sub_res(&ratio.bmul(liq)?)?; ensure!( new_liquidity_parameter >= MIN_LIQUIDITY.saturated_into(), Error::::LiquidityTooLow @@ -823,8 +815,7 @@ mod pallet { pool: &mut PoolOf, amount: BalanceOf, ) -> Result, DispatchError> { - let swap_fees_u128 = bmul(pool.swap_fee.saturated_into(), amount.saturated_into())?; - let swap_fees = swap_fees_u128.saturated_into(); + let swap_fees = pool.swap_fee.bmul(amount)?; pool.liquidity_shares_manager.deposit_fees(swap_fees)?; // Should only error unexpectedly! let external_fees = T::ExternalFees::distribute(market_id, pool.collateral, &pool.account_id, amount); diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 1a2e876c9..9a443e5b5 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -61,6 +61,7 @@ use zeitgeist_primitives::{ RemoveKeysLimit, RequestInterval, SimpleDisputesPalletId, SwapsPalletId, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, }, + math::fixed::FixedMul, traits::{DeployPoolApi, DistributeFees}, types::{ AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, @@ -140,12 +141,7 @@ where account: &Self::AccountId, amount: Self::Balance, ) -> Self::Balance { - let fees = zeitgeist_primitives::math::fixed::bmul( - amount.saturated_into(), - EXTERNAL_FEES.saturated_into(), - ) - .unwrap() - .saturated_into(); + let fees = amount.bmul(EXTERNAL_FEES.saturated_into()).unwrap(); let _ = T::MultiCurrency::transfer(asset, account, &F::get(), fees); fees } diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index d93aa5ce8..755a08a20 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -38,7 +38,7 @@ fn buy_works() { let pool = Pools::::get(market_id).unwrap(); let total_fee_percentage = swap_fee + EXTERNAL_FEES; let amount_in_minus_fees = _10; - let amount_in = bdiv(amount_in_minus_fees, _1 - total_fee_percentage).unwrap(); // This is exactly _10 after deducting fees. + let amount_in = amount_in_minus_fees.bdiv(_1 - total_fee_percentage).unwrap(); // This is exactly _10 after deducting fees. let expected_fees = amount_in - amount_in_minus_fees; let expected_swap_fee_amount = expected_fees / 2; let expected_external_fee_amount = expected_fees / 2; diff --git a/zrml/neo-swaps/src/tests/exit.rs b/zrml/neo-swaps/src/tests/exit.rs index 260b1754c..f528a7b54 100644 --- a/zrml/neo-swaps/src/tests/exit.rs +++ b/zrml/neo-swaps/src/tests/exit.rs @@ -47,12 +47,12 @@ fn exit_works() { vec![0, 0], )); let pool_after = Pools::::get(market_id).unwrap(); - let ratio = bdiv(pool_shares_amount, liquidity).unwrap(); + let ratio = pool_shares_amount.bdiv(liquidity).unwrap(); let pool_outcomes_after: Vec<_> = pool_after.assets().iter().map(|a| pool_after.reserve_of(a).unwrap()).collect(); let expected_pool_diff = vec![ - bmul(ratio, pool_outcomes_before[0]).unwrap(), - bmul(ratio, pool_outcomes_before[1]).unwrap(), + ratio.bmul(pool_outcomes_before[0]).unwrap(), + ratio.bmul(pool_outcomes_before[1]).unwrap(), ]; let alice_outcomes_after = [ AssetManager::free_balance(pool_after.assets()[0], &ALICE), @@ -64,7 +64,7 @@ fn exit_works() { assert_eq!(alice_outcomes_after[1], alice_outcomes_before[1] + expected_pool_diff[1]); assert_eq!( pool_after.liquidity_parameter, - bmul(_1 - ratio, pool_before.liquidity_parameter).unwrap() + (_1 - ratio).bmul(pool_before.liquidity_parameter).unwrap() ); assert_eq!( pool_after.liquidity_shares_manager.shares_of(&ALICE).unwrap(), diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index ff07328af..d3d0daeb0 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -51,11 +51,11 @@ fn join_works() { vec![u128::MAX, u128::MAX], )); let pool_after = Pools::::get(market_id).unwrap(); - let ratio = bdiv(liquidity + pool_shares_amount, liquidity).unwrap(); + let ratio = (liquidity + pool_shares_amount).bdiv(liquidity).unwrap(); let pool_outcomes_after: Vec<_> = pool_after.assets().iter().map(|a| pool_after.reserve_of(a).unwrap()).collect(); - assert_eq!(pool_outcomes_after[0], bmul(ratio, pool_outcomes_before[0]).unwrap()); - assert_eq!(pool_outcomes_after[1], bmul(ratio, pool_outcomes_before[1]).unwrap()); + assert_eq!(pool_outcomes_after[0], ratio.bmul(pool_outcomes_before[0]).unwrap()); + assert_eq!(pool_outcomes_after[1], 14_245_783_753); let long_diff = pool_outcomes_after[1] - pool_outcomes_before[1]; assert_eq!(AssetManager::free_balance(pool_after.assets()[0], &ALICE), 0); assert_eq!( @@ -64,7 +64,7 @@ fn join_works() { ); assert_eq!( pool_after.liquidity_parameter, - bmul(ratio, pool_before.liquidity_parameter).unwrap() + ratio.bmul(pool_before.liquidity_parameter).unwrap() ); assert_eq!( pool_after.liquidity_shares_manager.shares_of(&ALICE).unwrap(), diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 0e2633f84..f637e08e3 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -30,7 +30,7 @@ use orml_traits::MultiCurrency; use sp_runtime::Perbill; use zeitgeist_primitives::{ constants::CENT, - math::fixed::{bdiv, bmul}, + math::fixed::{FixedDiv, FixedMul}, types::{ AccountIdTest, Asset, Deadlines, MarketCreation, MarketId, MarketPeriod, MarketStatus, MarketType, MultiHash, ScalarPosition, ScoringRule, diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index 58cdd4ba5..b3095078c 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -55,7 +55,7 @@ fn sell_works() { )); let total_fee_percentage = swap_fee + EXTERNAL_FEES; let expected_amount_out = 59632253897u128; - let expected_fees = bmul(total_fee_percentage, expected_amount_out).unwrap(); + let expected_fees = total_fee_percentage.bmul(expected_amount_out).unwrap(); let expected_swap_fee_amount = expected_fees / 2; let expected_external_fee_amount = expected_fees - expected_swap_fee_amount; let expected_amount_out_minus_fees = expected_amount_out - expected_fees; diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index efa7340db..7eaf29b81 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -35,10 +35,14 @@ use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use orml_traits::{BalanceStatus, MultiCurrency, NamedMultiReservableCurrency}; pub use pallet::*; use sp_runtime::{ - traits::{CheckedSub, Get, Zero}, - ArithmeticError, Perquintill, SaturatedConversion, Saturating, + traits::{Get, Zero}, + SaturatedConversion, Saturating, }; use zeitgeist_primitives::{ + math::{ + checked_ops_res::{CheckedAddRes, CheckedSubRes}, + fixed::{FixedDiv, FixedMul}, + }, traits::MarketCommonsPalletApi, types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; @@ -269,13 +273,8 @@ mod pallet { // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. // This ensures that the reserve of the maker is always enough to repatriate successfully! - let ratio = Perquintill::from_rational( - maker_fill.saturated_into::(), - order_data.outcome_asset_amount.saturated_into::(), - ); - let taker_fill = ratio - .mul_floor(order_data.base_asset_amount.saturated_into::()) - .saturated_into::>(); + let ratio = maker_fill.bdiv_floor(order_data.outcome_asset_amount)?; + let taker_fill = ratio.bmul_floor(order_data.base_asset_amount)?; T::AssetManager::repatriate_reserved_named( &Self::reserve_id(), @@ -293,14 +292,10 @@ mod pallet { maker_fill, )?; - order_data.base_asset_amount = order_data - .base_asset_amount - .checked_sub(&taker_fill) - .ok_or(ArithmeticError::Underflow)?; - order_data.outcome_asset_amount = order_data - .outcome_asset_amount - .checked_sub(&maker_fill) - .ok_or(ArithmeticError::Underflow)?; + order_data.base_asset_amount = + order_data.base_asset_amount.checked_sub_res(&taker_fill)?; + order_data.outcome_asset_amount = + order_data.outcome_asset_amount.checked_sub_res(&maker_fill)?; // this ensures that partial fills, which fill nearly the whole order, are not executed // this protects the last fill happening without a division by zero for `Perquintill::from_rational` let is_ratio_quotient_valid = order_data.outcome_asset_amount.is_zero() @@ -312,13 +307,8 @@ mod pallet { // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. // This ensures that the reserve of the maker is always enough to repatriate successfully! - let ratio = Perquintill::from_rational( - maker_fill.saturated_into::(), - order_data.base_asset_amount.saturated_into::(), - ); - let taker_fill = ratio - .mul_floor(order_data.outcome_asset_amount.saturated_into::()) - .saturated_into::>(); + let ratio = maker_fill.bdiv_floor(order_data.base_asset_amount)?; + let taker_fill = ratio.bmul_floor(order_data.outcome_asset_amount)?; T::AssetManager::repatriate_reserved_named( &Self::reserve_id(), @@ -331,14 +321,10 @@ mod pallet { T::AssetManager::transfer(base_asset, &taker, &maker, maker_fill)?; - order_data.outcome_asset_amount = order_data - .outcome_asset_amount - .checked_sub(&taker_fill) - .ok_or(ArithmeticError::Underflow)?; - order_data.base_asset_amount = order_data - .base_asset_amount - .checked_sub(&maker_fill) - .ok_or(ArithmeticError::Underflow)?; + order_data.outcome_asset_amount = + order_data.outcome_asset_amount.checked_sub_res(&taker_fill)?; + order_data.base_asset_amount = + order_data.base_asset_amount.checked_sub_res(&maker_fill)?; // this ensures that partial fills, which fill nearly the whole order, are not executed // this protects the last fill happening without a division by zero for `Perquintill::from_rational` let is_ratio_quotient_valid = order_data.base_asset_amount.is_zero() @@ -404,7 +390,7 @@ mod pallet { let base_asset = market.base_asset; let order_id = >::get(); - let next_order_id = order_id.checked_add(1).ok_or(ArithmeticError::Overflow)?; + let next_order_id = order_id.checked_add_res(&1)?; let order = Order { market_id, diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index ab10ca693..bdef82a0c 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -48,7 +48,7 @@ mod pallet { }; use zeitgeist_primitives::{ constants::BASE, - math::fixed::*, + math::fixed::{FixedDiv, FixedMul}, traits::DistributeFees, types::{Asset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, }; @@ -406,14 +406,8 @@ mod pallet { let pot_account = Self::pot_account(market_id); let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account); - let payoff_ratio_mul_base: BalanceOf = - bdiv_floor(pot_total.saturated_into(), outcome_total.saturated_into())? - .saturated_into(); - let payoff: BalanceOf = bmul_floor( - payoff_ratio_mul_base.saturated_into(), - winning_balance.saturated_into(), - )? - .saturated_into(); + let payoff_ratio_mul_base = pot_total.bdiv_floor(outcome_total)?; + let payoff = payoff_ratio_mul_base.bmul_floor(winning_balance)?; Self::check_values( winning_balance, diff --git a/zrml/swaps/src/benchmarks.rs b/zrml/swaps/src/benchmarks.rs index 9201ae8d3..78b542014 100644 --- a/zrml/swaps/src/benchmarks.rs +++ b/zrml/swaps/src/benchmarks.rs @@ -29,7 +29,7 @@ use super::*; #[cfg(test)] use crate::Pallet as Swaps; -use crate::{fixed::bmul, pallet::ARBITRAGE_MAX_ITERATIONS, Config, Event, MarketIdOf}; +use crate::{pallet::ARBITRAGE_MAX_ITERATIONS, Config, Event, MarketIdOf}; use frame_benchmarking::{account, benchmarks, vec, whitelisted_caller, Vec}; use frame_support::{ dispatch::{DispatchResult, UnfilteredDispatchable}, @@ -44,6 +44,7 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::{BASE, CENT}, + math::fixed::FixedMul, traits::{MarketCommonsPalletApi, Swaps as _}, types::{ Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, @@ -772,12 +773,7 @@ benchmarks! { // amount_in = 1/3 * balance_in, weight_in = 1, weight_out = 2. let asset_count = T::MaxAssets::get(); let balance: BalanceOf = LIQUIDITY.saturated_into(); - let asset_amount_in: BalanceOf = bmul( - balance.saturated_into(), - T::MaxInRatio::get().saturated_into(), - ) - .unwrap() - .saturated_into(); + let asset_amount_in: BalanceOf = balance.bmul(T::MaxInRatio::get()).unwrap(); let weight_in = T::MinWeight::get(); let weight_out = 2 * weight_in; let mut weights = vec![weight_in; asset_count as usize]; @@ -839,13 +835,11 @@ 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 = bmul( - balance.saturated_into(), - T::MaxOutRatio::get().saturated_into(), - ) - .unwrap() - .saturated_into(); - asset_amount_out = Perbill::one().checked_sub(&DEFAULT_CREATOR_FEE).unwrap().mul_floor(asset_amount_out); + 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 weight_out = T::MinWeight::get(); let weight_in = 4 * weight_out; let mut weights = vec![weight_out; asset_count as usize]; diff --git a/zrml/swaps/src/check_arithm_rslt.rs b/zrml/swaps/src/check_arithm_rslt.rs deleted file mode 100644 index ec5c9cb41..000000000 --- a/zrml/swaps/src/check_arithm_rslt.rs +++ /dev/null @@ -1,80 +0,0 @@ -// 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 . -// -// This file incorporates work covered by the license above but -// published without copyright notice by Balancer Labs -// (, contact@balancer.finance) in the -// balancer-core repository -// . - -use crate::consts::ARITHM_OF; -use frame_support::dispatch::DispatchError; -use sp_runtime::traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; - -/// Check Arithmetic - Result -/// -/// Checked arithmetic operations returning `Result<_, DispatchError>`. -pub trait CheckArithmRslt: CheckedAdd + CheckedDiv + CheckedMul + CheckedSub { - /// Check Addition - Result - /// - /// Same as `sp_runtime::traits::CheckedAdd::checked_add` but returns a - /// `Result` instead of `Option`. - fn check_add_rslt(&self, n: &Self) -> Result; - - /// Check Division - Result - /// - /// Same as `sp_runtime::traits::CheckedDiv::checked_div` but returns a - /// `Result` instead of `Option`. - fn check_div_rslt(&self, n: &Self) -> Result; - - /// Check Multiplication - Result - /// - /// Same as `sp_runtime::traits::CheckedMul::checked_mul` but returns a - /// `Result` instead of `Option`. - fn check_mul_rslt(&self, n: &Self) -> Result; - - /// Check Subtraction - Result - /// - /// Same as `sp_runtime::traits::CheckedSub::checked_sub` but returns a - /// `Result` instead of `Option`. - fn check_sub_rslt(&self, n: &Self) -> Result; -} - -impl CheckArithmRslt for T -where - T: CheckedAdd + CheckedDiv + CheckedMul + CheckedSub, -{ - #[inline] - fn check_add_rslt(&self, n: &Self) -> Result { - self.checked_add(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_div_rslt(&self, n: &Self) -> Result { - self.checked_div(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_mul_rslt(&self, n: &Self) -> Result { - self.checked_mul(n).ok_or(ARITHM_OF) - } - - #[inline] - fn check_sub_rslt(&self, n: &Self) -> Result { - self.checked_sub(n).ok_or(ARITHM_OF) - } -} diff --git a/zrml/swaps/src/consts.rs b/zrml/swaps/src/consts.rs deleted file mode 100644 index 5d6f0ca1a..000000000 --- a/zrml/swaps/src/consts.rs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 . -// -// This file incorporates work covered by the license above but -// published without copyright notice by Balancer Labs -// (, contact@balancer.finance) in the -// balancer-core repository -// . - -use frame_support::dispatch::DispatchError; -use zeitgeist_primitives::constants::BASE; - -pub const ARITHM_OF: DispatchError = DispatchError::Other("Arithmetic overflow"); - -/// The amount of precision to use in exponentiation. -pub const BPOW_PRECISION: u128 = 10; -/// The minimum value of the base parameter in bpow_approx. -pub const BPOW_APPROX_BASE_MIN: u128 = BASE / 4; -/// The maximum value of the base parameter in bpow_approx. -pub const BPOW_APPROX_BASE_MAX: u128 = 7 * BASE / 4; -/// The maximum number of terms from the binomial series used to calculate bpow_approx. -pub const BPOW_APPROX_MAX_ITERATIONS: u128 = 100; diff --git a/zrml/swaps/src/fixed.rs b/zrml/swaps/src/fixed.rs index 355ab1702..ee74c799b 100644 --- a/zrml/swaps/src/fixed.rs +++ b/zrml/swaps/src/fixed.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -21,53 +22,50 @@ // balancer-core repository // . -use crate::{ - check_arithm_rslt::CheckArithmRslt, - consts::{ - BPOW_APPROX_BASE_MAX, BPOW_APPROX_BASE_MIN, BPOW_APPROX_MAX_ITERATIONS, BPOW_PRECISION, +use frame_support::dispatch::DispatchError; +use zeitgeist_primitives::{ + constants::BASE, + math::{ + checked_ops_res::{CheckedAddRes, CheckedDivRes, CheckedMulRes, CheckedSubRes}, + fixed::{FixedDiv, FixedMul}, }, }; -use frame_support::dispatch::DispatchError; -use zeitgeist_primitives::constants::BASE; + +/// The amount of precision to use in exponentiation. +pub const BPOW_PRECISION: u128 = 10; +/// The minimum value of the base parameter in bpow_approx. +pub const BPOW_APPROX_BASE_MIN: u128 = BASE / 4; +/// The maximum value of the base parameter in bpow_approx. +pub const BPOW_APPROX_BASE_MAX: u128 = 7 * BASE / 4; +/// The maximum number of terms from the binomial series used to calculate bpow_approx. +pub const BPOW_APPROX_MAX_ITERATIONS: u128 = 100; pub fn btoi(a: u128) -> Result { - a.check_div_rslt(&BASE) + a.checked_div_res(&BASE) } pub fn bfloor(a: u128) -> Result { - btoi(a)?.check_mul_rslt(&BASE) + btoi(a)?.checked_mul_res(&BASE) } pub fn bsub_sign(a: u128, b: u128) -> Result<(u128, bool), DispatchError> { - Ok(if a >= b { (a.check_sub_rslt(&b)?, false) } else { (b.check_sub_rslt(&a)?, true) }) -} - -pub fn bmul(a: u128, b: u128) -> Result { - let c0 = a.check_mul_rslt(&b)?; - let c1 = c0.check_add_rslt(&BASE.check_div_rslt(&2)?)?; - c1.check_div_rslt(&BASE) -} - -pub fn bdiv(a: u128, b: u128) -> Result { - let c0 = a.check_mul_rslt(&BASE)?; - let c1 = c0.check_add_rslt(&b.check_div_rslt(&2)?)?; - c1.check_div_rslt(&b) + Ok(if a >= b { (a.checked_sub_res(&b)?, false) } else { (b.checked_sub_res(&a)?, true) }) } pub fn bpowi(a: u128, n: u128) -> Result { let mut z = if n % 2 != 0 { a } else { BASE }; let mut b = a; - let mut m = n.check_div_rslt(&2)?; + let mut m = n.checked_div_res(&2)?; while m != 0 { - b = bmul(b, b)?; + b = b.bmul(b)?; if m % 2 != 0 { - z = bmul(z, b)?; + z = z.bmul(b)?; } - m = m.check_div_rslt(&2)?; + m = m.checked_div_res(&2)?; } Ok(z) @@ -86,7 +84,7 @@ pub fn bpowi(a: u128, n: u128) -> Result { /// for `base` (specified above) are violated, a `DispatchError::Other` is returned. pub fn bpow(base: u128, exp: u128) -> Result { let whole = bfloor(exp)?; - let remain = exp.check_sub_rslt(&whole)?; + let remain = exp.checked_sub_res(&whole)?; let whole_pow = bpowi(base, btoi(whole)?)?; @@ -95,7 +93,7 @@ pub fn bpow(base: u128, exp: u128) -> Result { } let partial_result = bpow_approx(base, remain)?; - bmul(whole_pow, partial_result) + whole_pow.bmul(partial_result) } /// Compute an estimate of the power `base ** exp`. @@ -140,10 +138,10 @@ pub fn bpow_approx(base: u128, exp: u128) -> Result { break; } - let big_k = i.check_mul_rslt(&BASE)?; - let (c, cneg) = bsub_sign(a, big_k.check_sub_rslt(&BASE)?)?; - term = bmul(term, bmul(c, x)?)?; - term = bdiv(term, big_k)?; + let big_k = i.checked_mul_res(&BASE)?; + let (c, cneg) = bsub_sign(a, big_k.checked_sub_res(&BASE)?)?; + term = term.bmul(c.bmul(x)?)?; + term = term.bdiv(big_k)?; if term == 0 { break; } @@ -157,9 +155,9 @@ pub fn bpow_approx(base: u128, exp: u128) -> Result { if negative { // Never underflows. In fact, the absolute value of the terms is strictly // decreasing thanks to the numerical limits. - sum = sum.check_sub_rslt(&term)?; + sum = sum.checked_sub_res(&term)?; } else { - sum = sum.check_add_rslt(&term)?; + sum = sum.checked_add_res(&term)?; } } @@ -174,84 +172,9 @@ pub fn bpow_approx(base: u128, exp: u128) -> Result { #[cfg(test)] mod tests { - use crate::{ - consts::{ARITHM_OF, BPOW_PRECISION}, - fixed::{bdiv, bmul, bpow, bpow_approx}, - }; + use super::*; use frame_support::{assert_err, dispatch::DispatchError}; use more_asserts::assert_le; - use zeitgeist_primitives::constants::BASE; - - pub const ERR: Result = Err(ARITHM_OF); - - macro_rules! create_tests { - ( - $op:ident; - - 0 => $_0_0:expr, $_0_1:expr, $_0_2:expr, $_0_3:expr; - 1 => $_1_0:expr, $_1_1:expr, $_1_2:expr, $_1_3:expr; - 2 => $_2_0:expr, $_2_1:expr, $_2_2:expr, $_2_3:expr; - 3 => $_3_0:expr, $_3_1:expr, $_3_2:expr, $_3_3:expr; - max_n => $max_n_0:expr, $max_n_1:expr, $max_n_2:expr, $max_n_3:expr; - n_max => $n_max_0:expr, $n_max_1:expr, $n_max_2:expr, $n_max_3:expr; - ) => { - assert_eq!($op(0, 0 * BASE), $_0_0); - assert_eq!($op(0, 1 * BASE), $_0_1); - assert_eq!($op(0, 2 * BASE), $_0_2); - assert_eq!($op(0, 3 * BASE), $_0_3); - - assert_eq!($op(1 * BASE, 0 * BASE), $_1_0); - assert_eq!($op(1 * BASE, 1 * BASE), $_1_1); - assert_eq!($op(1 * BASE, 2 * BASE), $_1_2); - assert_eq!($op(1 * BASE, 3 * BASE), $_1_3); - - assert_eq!($op(2 * BASE, 0 * BASE), $_2_0); - assert_eq!($op(2 * BASE, 1 * BASE), $_2_1); - assert_eq!($op(2 * BASE, 2 * BASE), $_2_2); - assert_eq!($op(2 * BASE, 3 * BASE), $_2_3); - - assert_eq!($op(3 * BASE, 0 * BASE), $_3_0); - assert_eq!($op(3 * BASE, 1 * BASE), $_3_1); - assert_eq!($op(3 * BASE, 2 * BASE), $_3_2); - assert_eq!($op(3 * BASE, 3 * BASE), $_3_3); - - assert_eq!($op(u128::MAX, 0 * BASE), $max_n_0); - assert_eq!($op(u128::MAX, 1 * BASE), $max_n_1); - assert_eq!($op(u128::MAX, 2 * BASE), $max_n_2); - assert_eq!($op(u128::MAX, 3 * BASE), $max_n_3); - - assert_eq!($op(0, u128::MAX), $n_max_0); - assert_eq!($op(1, u128::MAX), $n_max_1); - assert_eq!($op(2, u128::MAX), $n_max_2); - assert_eq!($op(3, u128::MAX), $n_max_3); - }; - } - - #[test] - fn bdiv_has_minimum_set_of_correct_values() { - create_tests!( - bdiv; - 0 => ERR, Ok(0), Ok(0), Ok(0); - 1 => ERR, Ok(BASE), Ok(BASE / 2), Ok(BASE / 3); - 2 => ERR, Ok(2 * BASE), Ok(BASE), Ok(6666666667); - 3 => ERR, Ok(3 * BASE), Ok(3 * BASE / 2), Ok(BASE); - max_n => ERR, ERR, ERR, ERR; - n_max => Ok(0), Ok(1 / BASE), Ok(2 / BASE), Ok(3 / BASE); - ); - } - - #[test] - fn bmul_has_minimum_set_of_correct_values() { - create_tests!( - bmul; - 0 => Ok(0), Ok(0), Ok(0), Ok(0); - 1 => Ok(0), Ok(BASE), Ok(2 * BASE), Ok(3 * BASE); - 2 => Ok(0), Ok(2 * BASE), Ok(4 * BASE), Ok(6 * BASE); - 3 => Ok(0), Ok(3 * BASE), Ok(6 * BASE), Ok(9 * BASE); - max_n => Ok(0), ERR, ERR, ERR; - n_max => Ok(0), ERR, ERR, ERR; - ); - } #[test] fn bpow_has_minimum_set_of_correct_values() { diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 95e7a4f0d..677e080f1 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -33,8 +33,6 @@ mod utils; mod arbitrage; mod benchmarks; -pub mod check_arithm_rslt; -mod consts; mod events; pub mod fixed; pub mod math; @@ -50,9 +48,7 @@ pub use pallet::*; mod pallet { use crate::{ arbitrage::ArbitrageForCpmm, - check_arithm_rslt::CheckArithmRslt, events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - fixed::{bdiv, bmul}, utils::{ pool_exit_with_exact_amount, pool_join_with_exact_amount, swap_exact_amount, PoolExitWithExactAmountParams, PoolJoinWithExactAmountParams, PoolParams, @@ -78,8 +74,7 @@ mod pallet { Perbill, }; use sp_runtime::{ - traits::AccountIdConversion, ArithmeticError, DispatchError, DispatchResult, - SaturatedConversion, + traits::AccountIdConversion, DispatchError, DispatchResult, SaturatedConversion, }; use substrate_fixed::{ traits::{FixedSigned, FixedUnsigned, LossyFrom}, @@ -90,7 +85,11 @@ mod pallet { FixedI128, FixedI32, FixedU128, FixedU32, }; use zeitgeist_primitives::{ - constants::{BASE, CENT}, + 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, @@ -226,9 +225,7 @@ mod pallet { Ok(()) }, fee: |amount: BalanceOf| { - let exit_fee_amount = - bmul(amount.saturated_into(), Self::calc_exit_fee(&pool).saturated_into())? - .saturated_into(); + let exit_fee_amount = amount.bmul(Self::calc_exit_fee(&pool))?; Ok(exit_fee_amount) }, who: who_clone, @@ -305,16 +302,10 @@ mod pallet { if new_amount > zero_balance && missing == zero_balance { >::insert(pool_id, &who, new_amount); - pool.total_subsidy = Some( - total_subsidy - .checked_sub(&transferred) - .ok_or(ArithmeticError::Overflow)?, - ); + pool.total_subsidy = Some(total_subsidy.checked_sub_res(&transferred)?); } else { >::remove(pool_id, &who); - pool.total_subsidy = Some( - total_subsidy.checked_sub(&subsidy).ok_or(ArithmeticError::Overflow)?, - ); + pool.total_subsidy = Some(total_subsidy.checked_sub_res(&subsidy)?); } } else { return Err(Error::::NoSubsidyProvided.into()); @@ -406,9 +397,7 @@ mod pallet { let params = PoolExitWithExactAmountParams { asset, asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = - bmul(total_supply.saturated_into(), T::MaxInRatio::get().saturated_into())? - .saturated_into(); + let mul: BalanceOf = total_supply.bmul(T::MaxInRatio::get())?; ensure!(pool_amount <= mul, Error::::MaxInRatio); let asset_amount: BalanceOf = crate::math::calc_single_out_given_pool_in( asset_balance.saturated_into(), @@ -423,12 +412,7 @@ mod pallet { ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); ensure!(asset_amount >= min_asset_amount, Error::::LimitOut); ensure!( - asset_amount - <= bmul( - asset_balance.saturated_into(), - T::MaxOutRatio::get().saturated_into() - )? - .saturated_into(), + asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, Error::::MaxOutRatio ); Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; @@ -468,7 +452,7 @@ mod pallet { // though. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::pool_join( - max_assets_in.len().min(T::MaxAssets::get().into()) as u32 + max_assets_in.len().min(T::MaxAssets::get().into()) as u32, ))] #[transactional] pub fn pool_join( @@ -646,11 +630,7 @@ mod pallet { let params = PoolJoinWithExactAmountParams { asset, asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = bmul( - total_supply.saturated_into(), - T::MaxOutRatio::get().saturated_into(), - )? - .saturated_into(); + let mul: BalanceOf = total_supply.bmul(T::MaxOutRatio::get())?; ensure!(pool_amount <= mul, Error::::MaxOutRatio); let asset_amount: BalanceOf = crate::math::calc_single_in_given_pool_out( asset_balance.saturated_into(), @@ -664,7 +644,7 @@ mod pallet { ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); ensure!(asset_amount <= max_asset_amount, Error::::LimitIn); ensure!( - asset_amount <= asset_balance.check_mul_rslt(&T::MaxInRatio::get())?, + asset_amount <= asset_balance.checked_mul_res(&T::MaxInRatio::get())?, Error::::MaxInRatio ); T::LiquidityMining::add_shares(who.clone(), pool.market_id, asset_amount); @@ -1191,17 +1171,15 @@ mod pallet { let share_holder_account = share_holder.0; let share_holder_balance = share_holder.1.free; let reward_pct_unadjusted = - bdiv(share_holder_balance.saturated_into(), total_pool_shares.saturated_into()) - .unwrap_or(0); + 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(1); - let holder_reward_unadjusted = - bmul(amm_profit.saturated_into(), reward_pct).unwrap_or(0); + 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(1); + let holder_reward = holder_reward_unadjusted.saturating_sub(1u8.into()); let transfer_result = T::AssetManager::transfer( base_asset, @@ -1375,7 +1353,9 @@ mod pallet { 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 > BASE.saturating_add(ARBITRAGE_THRESHOLD) { + 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 @@ -1398,7 +1378,9 @@ mod pallet { } Self::deposit_event(Event::ArbitrageMintSell(pool_id, amount)); Ok(T::WeightInfo::execute_arbitrage_mint_sell(asset_count)) - } else if total_spot_price < BASE.saturating_sub(ARBITRAGE_THRESHOLD) { + } 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. @@ -1449,7 +1431,7 @@ mod pallet { let market = T::MarketCommons::market(&pool.market_id)?; market .creator_fee - .mul_floor(BASE) + .mul_floor(ZeitgeistBase::::get()?) .checked_add(swap_fee.try_into().map_err(|_| Error::::SwapFeeTooHigh)?) .ok_or(Error::::SwapFeeTooHigh)? } else { @@ -1475,8 +1457,8 @@ mod pallet { // 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 Ok(T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)? - .saturating_add(BASE.saturated_into())); + return T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)? + .checked_add_res(&ZeitgeistBase::get()?); } let mut balance_in = BalanceOf::::zero(); @@ -1497,29 +1479,24 @@ mod pallet { if *asset_in == base_asset { T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_out, &balances) } else if *asset_out == base_asset { - let price_with_inverse_fee = bdiv( - BASE, - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_in, &balances)? - .saturated_into(), - )? - .saturated_into(); - let fee_pct = T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)?.saturated_into(); - let fee_plus_one = BASE.saturating_add(fee_pct); - let price_with_fee: u128 = - bmul(fee_plus_one, bmul(price_with_inverse_fee, fee_plus_one)?)?; - Ok(price_with_fee.saturated_into()) + 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) } else { - let price_without_fee = bdiv( - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_out, &balances)? - .saturated_into(), - T::RikiddoSigmoidFeeMarketEma::price(*pool_id, balance_in, &balances)? - .saturated_into(), - )? - .saturated_into(); - let fee_pct = T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)?.saturated_into(); - let fee_plus_one = BASE.saturating_add(fee_pct); - let price_with_fee: u128 = bmul(fee_plus_one, price_without_fee)?; - Ok(price_with_fee.saturated_into()) + 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) } } @@ -1736,7 +1713,7 @@ mod pallet { fn inc_next_pool_id() -> Result { let id = >::get(); >::try_mutate(|n| { - *n = n.checked_add(1).ok_or(ArithmeticError::Overflow)?; + *n = n.checked_add_res(&1)?; Ok::<_, DispatchError>(()) })?; Ok(id) @@ -1903,7 +1880,7 @@ mod pallet { let swap_fee_unwrapped = swap_fee.ok_or(Error::::InvalidFeeArgument)?; let total_fee = market .creator_fee - .mul_floor(BASE) + .mul_floor(ZeitgeistBase::::get()?) .checked_add( swap_fee_unwrapped .try_into() @@ -1918,7 +1895,10 @@ mod pallet { total_fee_as_balance <= T::MaxSwapFee::get(), Error::::SwapFeeTooHigh ); - ensure!(total_fee <= BASE, 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( @@ -1935,7 +1915,7 @@ mod pallet { ensure!(weight >= T::MinWeight::get(), Error::::BelowMinimumWeight); ensure!(weight <= T::MaxWeight::get(), Error::::AboveMaximumWeight); map.insert(asset, weight); - total_weight = total_weight.check_add_rslt(&weight)?; + total_weight = total_weight.checked_add_res(&weight)?; T::AssetManager::transfer( asset, &who, @@ -2295,12 +2275,7 @@ mod pallet { cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), ensure_balance: |asset_balance: BalanceOf| { ensure!( - asset_amount - <= bmul( - asset_balance.saturated_into(), - T::MaxOutRatio::get().saturated_into() - )? - .saturated_into(), + asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, Error::::MaxOutRatio ); Ok(()) @@ -2367,11 +2342,7 @@ mod pallet { bound: min_pool_amount, cache_for_arbitrage: || PoolsCachedForArbitrage::::insert(pool_id, ()), pool_amount: move |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = bmul( - asset_balance.saturated_into(), - T::MaxInRatio::get().saturated_into(), - )? - .saturated_into(); + let mul: BalanceOf = asset_balance.bmul(T::MaxInRatio::get())?; ensure!(asset_amount <= mul, Error::::MaxInRatio); let pool_amount: BalanceOf = crate::math::calc_pool_out_given_single_in( asset_balance.saturated_into(), @@ -2513,12 +2484,7 @@ mod pallet { let balance_in = T::AssetManager::free_balance(asset_in, &pool_account_id); ensure!( - asset_amount_in - <= bmul( - balance_in.saturated_into(), - T::MaxInRatio::get().saturated_into() - )? - .saturated_into(), + asset_amount_in <= balance_in.bmul(T::MaxInRatio::get())?, Error::::MaxInRatio ); let swap_fee = if handle_fees { @@ -2564,7 +2530,7 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_before)?; let cost_after = T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; - cost_before.checked_sub(&cost_after).ok_or(ArithmeticError::Overflow)? + cost_before.checked_sub_res(&cost_after)? } ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { return Err(Error::::InvalidScoringRule.into()); @@ -2673,12 +2639,7 @@ mod pallet { let asset_amount_in = match pool.scoring_rule { ScoringRule::CPMM => { ensure!( - asset_amount_out - <= bmul( - balance_out.saturated_into(), - T::MaxOutRatio::get().saturated_into() - )? - .saturated_into(), + asset_amount_out <= balance_out.bmul(T::MaxOutRatio::get(),)?, Error::::MaxOutRatio, ); @@ -2727,7 +2688,7 @@ mod pallet { T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_before)?; let cost_after = T::RikiddoSigmoidFeeMarketEma::cost(pool_id, &outstanding_after)?; - cost_after.checked_sub(&cost_before).ok_or(ArithmeticError::Overflow)? + cost_after.checked_sub_res(&cost_before)? } ScoringRule::Lmsr | ScoringRule::Parimutuel | ScoringRule::Orderbook => { return Err(Error::::InvalidScoringRule.into()); diff --git a/zrml/swaps/src/math.rs b/zrml/swaps/src/math.rs index 57b1e9b0f..c76ed5227 100644 --- a/zrml/swaps/src/math.rs +++ b/zrml/swaps/src/math.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -23,12 +24,15 @@ #![allow(clippy::let_and_return)] -use crate::{ - check_arithm_rslt::CheckArithmRslt, - fixed::{bdiv, bmul, bpow}, -}; +use crate::fixed::bpow; use frame_support::dispatch::DispatchError; -use zeitgeist_primitives::constants::BASE; +use zeitgeist_primitives::{ + constants::BASE, + math::{ + checked_ops_res::{CheckedAddRes, CheckedSubRes}, + fixed::{FixedDiv, FixedMul}, + }, +}; /// Calculate the spot price of one asset in terms of another, including trading fees. /// @@ -50,12 +54,11 @@ pub fn calc_spot_price( asset_weight_out: u128, swap_fee: u128, ) -> Result { - let numer = bdiv(asset_balance_in, asset_weight_in)?; - let denom = bdiv(asset_balance_out, asset_weight_out)?; - let ratio = bdiv(numer, denom)?; - let scale = bdiv(BASE, BASE.check_sub_rslt(&swap_fee)?)?; - let spot_price = bmul(ratio, scale); - spot_price + let numer = asset_balance_in.bdiv(asset_weight_in)?; + let denom = asset_balance_out.bdiv(asset_weight_out)?; + let ratio = numer.bdiv(denom)?; + let scale = BASE.bdiv(BASE.checked_sub_res(&swap_fee)?)?; + ratio.bmul(scale) } /// Calculate the amount of tokens received from the pool for swapping the specified amount of tokens in, including @@ -81,14 +84,13 @@ pub fn calc_out_given_in( asset_amount_in: u128, swap_fee: u128, ) -> Result { - let weight_ratio = bdiv(asset_weight_in, asset_weight_out)?; - let mut adjusted_in = BASE.check_sub_rslt(&swap_fee)?; - adjusted_in = bmul(adjusted_in, asset_amount_in)?; - let y = bdiv(asset_balance_in, asset_balance_in.check_add_rslt(&adjusted_in)?)?; + let weight_ratio = asset_weight_in.bdiv(asset_weight_out)?; + let mut adjusted_in = BASE.checked_sub_res(&swap_fee)?; + adjusted_in = adjusted_in.bmul(asset_amount_in)?; + let y = asset_balance_in.bdiv(asset_balance_in.checked_add_res(&adjusted_in)?)?; let pow = bpow(y, weight_ratio)?; - let bar = BASE.check_sub_rslt(&pow)?; - let asset_amount_out = bmul(asset_balance_out, bar); - asset_amount_out + let bar = BASE.checked_sub_res(&pow)?; + asset_balance_out.bmul(bar) } /// Calculate the required amount of tokens to swap in to receive a specified amount of tokens @@ -114,12 +116,11 @@ pub fn calc_in_given_out( asset_amount_out: u128, swap_fee: u128, ) -> Result { - let weight_ratio = bdiv(asset_weight_out, asset_weight_in)?; - let diff = asset_balance_out.check_sub_rslt(&asset_amount_out)?; - let y = bdiv(asset_balance_out, diff)?; - let pow = bpow(y, weight_ratio)?.check_sub_rslt(&BASE)?; - let asset_amount_in = bdiv(bmul(asset_balance_in, pow)?, BASE.check_sub_rslt(&swap_fee)?); - asset_amount_in + let weight_ratio = asset_weight_out.bdiv(asset_weight_in)?; + let diff = asset_balance_out.checked_sub_res(&asset_amount_out)?; + let y = asset_balance_out.bdiv(diff)?; + let pow = bpow(y, weight_ratio)?.checked_sub_res(&BASE)?; + asset_balance_in.bmul(pow)?.bdiv(BASE.checked_sub_res(&swap_fee)?) } /// Calculate the amount of pool tokens received when joining the pool with a specified amount of @@ -150,16 +151,15 @@ pub fn calc_pool_out_given_single_in( // which is implicitly traded to the other pool tokens. // That proportion is (1 - weightTokenIn) // tokenAiAfterFee = tAi * (1 - (1 - weighTi) * pool_fee) - let normalized_weight = bdiv(asset_weight_in, total_weight)?; - let zaz = bmul(BASE.check_sub_rslt(&normalized_weight)?, swap_fee)?; - let asset_amount_in_after_fee = bmul(asset_amount_in, BASE.check_sub_rslt(&zaz)?)?; - let new_asset_balance_in = asset_balance_in.check_add_rslt(&asset_amount_in_after_fee)?; - let asset_in_ratio = bdiv(new_asset_balance_in, asset_balance_in)?; + let normalized_weight = asset_weight_in.bdiv(total_weight)?; + let zaz = BASE.checked_sub_res(&normalized_weight)?.bmul(swap_fee)?; + let asset_amount_in_after_fee = asset_amount_in.bmul(BASE.checked_sub_res(&zaz)?)?; + let new_asset_balance_in = asset_balance_in.checked_add_res(&asset_amount_in_after_fee)?; + let asset_in_ratio = new_asset_balance_in.bdiv(asset_balance_in)?; let pool_ratio = bpow(asset_in_ratio, normalized_weight)?; - let new_pool_supply = bmul(pool_ratio, pool_supply)?; - let pool_amount_out = new_pool_supply.check_sub_rslt(&pool_supply); - pool_amount_out + let new_pool_supply = pool_ratio.bmul(pool_supply)?; + new_pool_supply.checked_sub_res(&pool_supply) } /// Calculate the required amount of tokens of a single asset to join the pool with to receive the @@ -186,18 +186,17 @@ pub fn calc_single_in_given_pool_out( pool_amount_out: u128, swap_fee: u128, ) -> Result { - let normalized_weight = bdiv(asset_weight_in, total_weight)?; - let new_pool_supply = pool_supply.check_add_rslt(&pool_amount_out)?; - let pool_ratio = bdiv(new_pool_supply, pool_supply)?; + let normalized_weight = asset_weight_in.bdiv(total_weight)?; + let new_pool_supply = pool_supply.checked_add_res(&pool_amount_out)?; + let pool_ratio = new_pool_supply.bdiv(pool_supply)?; - let boo = bdiv(BASE, normalized_weight)?; + let boo = BASE.bdiv(normalized_weight)?; let asset_in_ratio = bpow(pool_ratio, boo)?; - let new_asset_balance_in = bmul(asset_in_ratio, asset_balance_in)?; - let asset_amount_in_after_fee = new_asset_balance_in.check_sub_rslt(&asset_balance_in)?; + let new_asset_balance_in = asset_in_ratio.bmul(asset_balance_in)?; + let asset_amount_in_after_fee = new_asset_balance_in.checked_sub_res(&asset_balance_in)?; - let zar = bmul(BASE.check_sub_rslt(&normalized_weight)?, swap_fee)?; - let asset_amount_in = bdiv(asset_amount_in_after_fee, BASE.check_sub_rslt(&zar)?); - asset_amount_in + let zar = BASE.checked_sub_res(&normalized_weight)?.bmul(swap_fee)?; + asset_amount_in_after_fee.bdiv(BASE.checked_sub_res(&zar)?) } /// Calculate the amount of tokens of a single asset received when exiting the pool with a specified amount of @@ -225,21 +224,20 @@ pub fn calc_single_out_given_pool_in( swap_fee: u128, exit_fee: u128, ) -> Result { - let normalized_weight = bdiv(asset_weight_out, total_weight)?; + let normalized_weight = asset_weight_out.bdiv(total_weight)?; - let pool_amount_in_after_exit_fee = bmul(pool_amount_in, BASE.check_sub_rslt(&exit_fee)?)?; - let new_pool_supply = pool_supply.check_sub_rslt(&pool_amount_in_after_exit_fee)?; - let pool_ratio = bdiv(new_pool_supply, pool_supply)?; + let pool_amount_in_after_exit_fee = pool_amount_in.bmul(BASE.checked_sub_res(&exit_fee)?)?; + let new_pool_supply = pool_supply.checked_sub_res(&pool_amount_in_after_exit_fee)?; + let pool_ratio = new_pool_supply.bdiv(pool_supply)?; - let exp = bdiv(BASE, normalized_weight)?; + let exp = BASE.bdiv(normalized_weight)?; let asset_out_ratio = bpow(pool_ratio, exp)?; - let new_asset_balance_out = bmul(asset_out_ratio, asset_balance_out)?; + let new_asset_balance_out = asset_out_ratio.bmul(asset_balance_out)?; - let asset_amount_before_swap_fee = asset_balance_out.check_sub_rslt(&new_asset_balance_out)?; + let asset_amount_before_swap_fee = asset_balance_out.checked_sub_res(&new_asset_balance_out)?; - let zaz = bmul(BASE.check_sub_rslt(&normalized_weight)?, swap_fee)?; - let asset_amount_out = bmul(asset_amount_before_swap_fee, BASE.check_sub_rslt(&zaz)?); - asset_amount_out + let zaz = BASE.checked_sub_res(&normalized_weight)?.bmul(swap_fee)?; + asset_amount_before_swap_fee.bmul(BASE.checked_sub_res(&zaz)?) } /// Calculate the required amount of pool tokens to exit the pool with to receive the specified number of tokens of a single asset. @@ -266,19 +264,18 @@ pub fn calc_pool_in_given_single_out( swap_fee: u128, exit_fee: u128, ) -> Result { - let normalized_weight = bdiv(asset_weight_out, total_weight)?; - let zoo = BASE.check_sub_rslt(&normalized_weight)?; - let zar = bmul(zoo, swap_fee)?; - let asset_amount_out_before_swap_fee = bdiv(asset_amount_out, BASE.check_sub_rslt(&zar)?)?; + let normalized_weight = asset_weight_out.bdiv(total_weight)?; + let zoo = BASE.checked_sub_res(&normalized_weight)?; + let zar = zoo.bmul(swap_fee)?; + let asset_amount_out_before_swap_fee = asset_amount_out.bdiv(BASE.checked_sub_res(&zar)?)?; let new_asset_balance_out = - asset_balance_out.check_sub_rslt(&asset_amount_out_before_swap_fee)?; - let asset_out_ratio = bdiv(new_asset_balance_out, asset_balance_out)?; + asset_balance_out.checked_sub_res(&asset_amount_out_before_swap_fee)?; + let asset_out_ratio = new_asset_balance_out.bdiv(asset_balance_out)?; let pool_ratio = bpow(asset_out_ratio, normalized_weight)?; - let new_pool_supply = bmul(pool_ratio, pool_supply)?; + let new_pool_supply = pool_ratio.bmul(pool_supply)?; - let pool_amount_in_after_exit_fee = pool_supply.check_sub_rslt(&new_pool_supply)?; - let pool_amount_in = bdiv(pool_amount_in_after_exit_fee, BASE.check_sub_rslt(&exit_fee)?); - pool_amount_in + let pool_amount_in_after_exit_fee = pool_supply.checked_sub_res(&new_pool_supply)?; + pool_amount_in_after_exit_fee.bdiv(BASE.checked_sub_res(&exit_fee)?) } diff --git a/zrml/swaps/src/root.rs b/zrml/swaps/src/root.rs index 4e1222e4f..9814fdde6 100644 --- a/zrml/swaps/src/root.rs +++ b/zrml/swaps/src/root.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Forecasting Technologies LTD. +// Copyright 2022-2023 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -158,7 +158,7 @@ fn dist(x: T, y: T) -> T { #[cfg(test)] mod tests { use super::*; - use crate::fixed::{bmul, bpowi}; + use crate::fixed::bpowi; use test_case::test_case; use zeitgeist_primitives::constants::BASE; @@ -207,7 +207,7 @@ 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 = bmul(_2, bpowi(x, 3)?)?; + let third_order = 2 * bpowi(x, 3)?; let second_order = bpowi(x, 2)?; // Add positive terms first to prevent underflow. Ok(third_order + _1 - second_order - x) diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index afe06ca3c..875d9a33c 100644 --- a/zrml/swaps/src/utils.rs +++ b/zrml/swaps/src/utils.rs @@ -23,9 +23,7 @@ // . use crate::{ - check_arithm_rslt::CheckArithmRslt, events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - fixed::{bdiv, bmul}, BalanceOf, Config, Error, MarketIdOf, Pallet, }; use alloc::vec::Vec; @@ -33,9 +31,15 @@ use frame_support::{dispatch::DispatchResult, ensure}; use orml_traits::MultiCurrency; use sp_runtime::{ traits::{Saturating, Zero}, - DispatchError, SaturatedConversion, + DispatchError, +}; +use zeitgeist_primitives::{ + math::{ + checked_ops_res::CheckedSubRes, + fixed::{FixedDiv, FixedMul}, + }, + types::{Asset, Pool, PoolId, ScoringRule}, }; -use zeitgeist_primitives::types::{Asset, Pool, PoolId, ScoringRule}; use zrml_rikiddo::traits::RikiddoMVPallet; // Common code for `pool_exit_with_exact_pool_amount` and `pool_exit_with_exact_asset_amount` methods. @@ -130,17 +134,17 @@ where let pool_shares_id = Pallet::::pool_shares_id(p.pool_id); let total_issuance = T::AssetManager::total_issuance(pool_shares_id); - let ratio = bdiv(p.pool_amount.saturated_into(), total_issuance.saturated_into())?; + let ratio = p.pool_amount.bdiv(total_issuance)?; Pallet::::check_provided_values_len_must_equal_assets_len(&p.pool.assets, &p.asset_bounds)?; - ensure!(ratio != 0, Error::::MathApproximation); + ensure!(ratio != Zero::zero(), Error::::MathApproximation); let mut transferred = Vec::with_capacity(p.asset_bounds.len()); for (asset, amount_bound) in p.pool.assets.iter().cloned().zip(p.asset_bounds.iter().cloned()) { let balance = T::AssetManager::free_balance(asset, p.pool_account_id); - let amount = bmul(ratio, balance.saturated_into())?.saturated_into(); + let amount = ratio.bmul(balance)?; let fee = (p.fee)(amount)?; - let amount_minus_fee = amount.check_sub_rslt(&fee)?; + let amount_minus_fee = amount.checked_sub_res(&fee)?; transferred.push(amount_minus_fee); ensure!(amount_minus_fee != Zero::zero(), Error::::MathApproximation); (p.transfer_asset)(amount_minus_fee, amount_bound, asset)?; @@ -244,9 +248,7 @@ where match p.pool.scoring_rule { ScoringRule::CPMM => ensure!( - spot_price_before_without_fees - <= bdiv(asset_amount_in.saturated_into(), asset_amount_out.saturated_into())? - .saturated_into(), + spot_price_before_without_fees <= asset_amount_in.bdiv(asset_amount_out)?, Error::::MathApproximation ), ScoringRule::RikiddoSigmoidFeeMarketEma => { From 8790095c7be3a65c93aef46ef875d54d7133b4fb Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 12 Dec 2023 10:53:03 +0100 Subject: [PATCH 012/104] Add env_logger and add force-debug feature (#1205) * add env_logger and add force-debug feature * taplo fmt, fix copyrights * Update zrml/styx/src/mock.rs Co-authored-by: Malte Kliemann * Update zrml/rikiddo/src/mock.rs Co-authored-by: Malte Kliemann * update comment to clarify logging --------- Co-authored-by: Malte Kliemann --- Cargo.lock | 21 ++++++++++++++++++--- Cargo.toml | 2 ++ runtime/battery-station/Cargo.toml | 3 +++ runtime/zeitgeist/Cargo.toml | 3 +++ scripts/benchmarks/quick_check.sh | 3 ++- zrml/authorized/Cargo.toml | 1 + zrml/authorized/src/mock.rs | 3 +++ zrml/court/Cargo.toml | 1 + zrml/court/src/mock.rs | 3 +++ zrml/global-disputes/Cargo.toml | 1 + zrml/global-disputes/src/mock.rs | 3 +++ zrml/liquidity-mining/Cargo.toml | 1 + zrml/liquidity-mining/src/mock.rs | 3 +++ zrml/market-commons/Cargo.toml | 1 + zrml/market-commons/src/mock.rs | 3 +++ zrml/neo-swaps/Cargo.toml | 3 ++- zrml/neo-swaps/src/mock.rs | 2 ++ zrml/orderbook/Cargo.toml | 2 ++ zrml/orderbook/src/mock.rs | 3 +++ zrml/parimutuel/Cargo.toml | 1 + zrml/parimutuel/src/mock.rs | 3 +++ zrml/prediction-markets/Cargo.toml | 2 ++ zrml/prediction-markets/src/mock.rs | 3 +++ zrml/rikiddo/Cargo.toml | 2 ++ zrml/rikiddo/src/mock.rs | 4 ++++ zrml/simple-disputes/Cargo.toml | 1 + zrml/simple-disputes/src/mock.rs | 3 +++ zrml/styx/Cargo.toml | 1 + zrml/styx/src/mock.rs | 4 ++++ zrml/swaps/Cargo.toml | 2 ++ zrml/swaps/src/mock.rs | 3 +++ 31 files changed, 86 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 258481776..85fde4a3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-debug-derive", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -2550,9 +2551,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -2728,7 +2729,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger 0.10.0", + "env_logger 0.10.1", "log", ] @@ -14460,6 +14461,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-debug-derive", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -14517,6 +14519,7 @@ dependencies = [ name = "zrml-authorized" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14535,6 +14538,7 @@ name = "zrml-court" version = "0.4.2" dependencies = [ "arrayvec 0.7.4", + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14558,6 +14562,7 @@ dependencies = [ name = "zrml-global-disputes" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14578,6 +14583,7 @@ dependencies = [ name = "zrml-liquidity-mining" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14596,6 +14602,7 @@ dependencies = [ name = "zrml-market-commons" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-support", "frame-system", "pallet-balances", @@ -14612,6 +14619,7 @@ dependencies = [ name = "zrml-neo-swaps" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "fixed", "frame-benchmarking", "frame-support", @@ -14656,6 +14664,7 @@ dependencies = [ name = "zrml-orderbook" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14689,6 +14698,7 @@ dependencies = [ name = "zrml-parimutuel" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14710,6 +14720,7 @@ dependencies = [ name = "zrml-prediction-markets" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14773,6 +14784,7 @@ version = "0.4.2" dependencies = [ "arbitrary", "cfg-if", + "env_logger 0.10.1", "frame-support", "frame-system", "hashbrown 0.12.3", @@ -14804,6 +14816,7 @@ dependencies = [ name = "zrml-simple-disputes" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14824,6 +14837,7 @@ dependencies = [ name = "zrml-styx" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14840,6 +14854,7 @@ dependencies = [ name = "zrml-swaps" version = "0.4.2" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", diff --git a/Cargo.toml b/Cargo.toml index e4718530d..65b8416fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,6 +190,7 @@ sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "pol sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } +sp-debug-derive = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } @@ -246,6 +247,7 @@ zrml-swaps-runtime-api = { path = "zrml/swaps/runtime-api", default-features = f # Other (client) clap = "4.0.32" +env_logger = "0.10.1" jsonrpsee = "0.16.2" libfuzzer-sys = "0.4.7" more-asserts = "0.3.1" diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 0d3543a38..1339f7a0e 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -37,6 +37,7 @@ scale-info = { workspace = true, features = ["derive"] } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-core = { workspace = true } +sp-debug-derive = { workspace = true } sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } @@ -412,6 +413,8 @@ try-runtime = [ "cumulus-pallet-xcmp-queue?/try-runtime", "parachain-info?/try-runtime", ] +# Allow to print logs details (no wasm:stripped) +force-debug = ["sp-debug-derive/force-debug"] [package] authors = ["Zeitgeist PM "] diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index a51c91cbf..e651cb961 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -36,6 +36,7 @@ scale-info = { workspace = true, features = ["derive"] } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-core = { workspace = true } +sp-debug-derive = { workspace = true } sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } @@ -401,6 +402,8 @@ try-runtime = [ "cumulus-pallet-xcmp-queue?/try-runtime", "parachain-info?/try-runtime", ] +# Allow to print logs details (no wasm:stripped) +force-debug = ["sp-debug-derive/force-debug"] [package] authors = ["Zeitgeist PM "] diff --git a/scripts/benchmarks/quick_check.sh b/scripts/benchmarks/quick_check.sh index 9ae754b68..c7d376aba 100755 --- a/scripts/benchmarks/quick_check.sh +++ b/scripts/benchmarks/quick_check.sh @@ -25,6 +25,7 @@ export PROFILE=release export PROFILE_DIR=release export ADDITIONAL_PARAMS=--detailed-log-output export EXECUTION=native -export ADDITIONAL_FEATURES="" +# force-debug for no output +export ADDITIONAL_FEATURES="force-debug" source ./scripts/benchmarks/run_benchmarks.sh diff --git a/zrml/authorized/Cargo.toml b/zrml/authorized/Cargo.toml index 616618158..be6763a46 100644 --- a/zrml/authorized/Cargo.toml +++ b/zrml/authorized/Cargo.toml @@ -9,6 +9,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index d3d8daf5a..33a67f6c5 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -201,6 +201,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index f88784959..4095f712c 100644 --- a/zrml/court/Cargo.toml +++ b/zrml/court/Cargo.toml @@ -13,6 +13,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-randomness-collective-flip = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index aa4292303..faf63d33f 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -257,6 +257,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/global-disputes/Cargo.toml b/zrml/global-disputes/Cargo.toml index 450dc0fd0..b513c0707 100644 --- a/zrml/global-disputes/Cargo.toml +++ b/zrml/global-disputes/Cargo.toml @@ -14,6 +14,7 @@ num-traits = { workspace = true, optional = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } diff --git a/zrml/global-disputes/src/mock.rs b/zrml/global-disputes/src/mock.rs index f8734b2f1..d678cd362 100644 --- a/zrml/global-disputes/src/mock.rs +++ b/zrml/global-disputes/src/mock.rs @@ -199,6 +199,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/liquidity-mining/Cargo.toml b/zrml/liquidity-mining/Cargo.toml index e394c4dd4..3d50e0075 100644 --- a/zrml/liquidity-mining/Cargo.toml +++ b/zrml/liquidity-mining/Cargo.toml @@ -10,6 +10,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } diff --git a/zrml/liquidity-mining/src/mock.rs b/zrml/liquidity-mining/src/mock.rs index 22a76d3e3..a85e574ce 100644 --- a/zrml/liquidity-mining/src/mock.rs +++ b/zrml/liquidity-mining/src/mock.rs @@ -138,6 +138,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + crate::GenesisConfig:: { initial_balance: self.initial_balance, per_block_distribution: self.per_block_incentives, diff --git a/zrml/market-commons/Cargo.toml b/zrml/market-commons/Cargo.toml index d26c4e86b..8321bc159 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -8,6 +8,7 @@ sp-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } diff --git a/zrml/market-commons/src/mock.rs b/zrml/market-commons/src/mock.rs index 89eebf8b1..e0a89f972 100644 --- a/zrml/market-commons/src/mock.rs +++ b/zrml/market-commons/src/mock.rs @@ -106,6 +106,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: vec![] } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index d0b87d52f..6d41495cd 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -14,6 +14,7 @@ zrml-market-commons = { workspace = true } # Mock +env_logger = { workspace = true, optional = true } orml-asset-registry = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } @@ -38,7 +39,6 @@ zrml-rikiddo = { workspace = true, optional = true } zrml-simple-disputes = { workspace = true, optional = true } zrml-swaps = { workspace = true, optional = true } - [dev-dependencies] more-asserts = { workspace = true } test-case = { workspace = true } @@ -77,6 +77,7 @@ mock = [ "zrml-prediction-markets/mock", "zrml-prediction-markets/default", "serde/default", + "env_logger/default", ] parachain = ["zrml-prediction-markets/parachain"] runtime-benchmarks = [ diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 9a443e5b5..96f0c5b2d 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -480,6 +480,8 @@ impl Default for ExtBuilder { impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index f914b3d84..8a821e922 100644 --- a/zrml/orderbook/Cargo.toml +++ b/zrml/orderbook/Cargo.toml @@ -10,6 +10,7 @@ sp-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } # Mock +env_logger = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } @@ -31,6 +32,7 @@ mock = [ "orml-currencies/default", "sp-io/default", "zeitgeist-primitives/mock", + "env_logger/default", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/zrml/orderbook/src/mock.rs b/zrml/orderbook/src/mock.rs index 2384c3ab9..a5af9c621 100644 --- a/zrml/orderbook/src/mock.rs +++ b/zrml/orderbook/src/mock.rs @@ -150,6 +150,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index dae19e657..422361dbf 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -10,6 +10,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } orml-currencies = { workspace = true, features = ["default"] } orml-tokens = { workspace = true, features = ["default"] } pallet-balances = { workspace = true, features = ["default"] } diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index c04387509..4cb7da5d1 100644 --- a/zrml/parimutuel/src/mock.rs +++ b/zrml/parimutuel/src/mock.rs @@ -199,6 +199,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 3ce92c2f8..8cbb47232 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -18,6 +18,7 @@ zrml-simple-disputes = { workspace = true } # Mock +env_logger = { workspace = true, optional = true } orml-asset-registry = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } @@ -57,6 +58,7 @@ mock = [ "zrml-swaps/default", "xcm/default", "orml-asset-registry/default", + "env_logger/default", ] parachain = [] runtime-benchmarks = [ diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 8a448a1ec..c9eea71b3 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -480,6 +480,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/rikiddo/Cargo.toml b/zrml/rikiddo/Cargo.toml index 8e010ffd8..e3158e353 100644 --- a/zrml/rikiddo/Cargo.toml +++ b/zrml/rikiddo/Cargo.toml @@ -1,6 +1,7 @@ [dependencies] arbitrary = { workspace = true, features = ["derive"], optional = true } cfg-if = { workspace = true } +env_logger = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } hashbrown = { workspace = true } @@ -24,6 +25,7 @@ mock = [ "pallet-timestamp/default", "sp-io/default", "zeitgeist-primitives/mock", + "env_logger/default", ] std = [ "frame-support/std", diff --git a/zrml/rikiddo/src/mock.rs b/zrml/rikiddo/src/mock.rs index 07d089867..970a89d40 100644 --- a/zrml/rikiddo/src/mock.rs +++ b/zrml/rikiddo/src/mock.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -148,6 +149,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/simple-disputes/Cargo.toml b/zrml/simple-disputes/Cargo.toml index aaef49233..1054899b7 100644 --- a/zrml/simple-disputes/Cargo.toml +++ b/zrml/simple-disputes/Cargo.toml @@ -10,6 +10,7 @@ zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } orml-currencies = { workspace = true, features = ["default"] } orml-tokens = { workspace = true, features = ["default"] } pallet-balances = { workspace = true, features = ["default"] } diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index 3f3e3dee7..ef8643982 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -220,6 +220,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/styx/Cargo.toml b/zrml/styx/Cargo.toml index d43ee274d..44edbd03f 100644 --- a/zrml/styx/Cargo.toml +++ b/zrml/styx/Cargo.toml @@ -8,6 +8,7 @@ sp-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } [dev-dependencies] +env_logger = { workspace = true } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } diff --git a/zrml/styx/src/mock.rs b/zrml/styx/src/mock.rs index 80dd2a2f8..263c25712 100644 --- a/zrml/styx/src/mock.rs +++ b/zrml/styx/src/mock.rs @@ -1,3 +1,4 @@ +// Copyright 2023 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -111,6 +112,9 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 9eb5df1f7..298a9fe34 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -14,6 +14,7 @@ zrml-rikiddo = { workspace = true } # Mock +env_logger = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } @@ -40,6 +41,7 @@ mock = [ "zeitgeist-primitives/mock", "zrml-market-commons/default", "zrml-swaps-runtime-api/default", + "env_logger/default", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index 948a8ce5f..85f8ba551 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -283,6 +283,9 @@ impl ExtBuilder { let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` + let _ = env_logger::builder().is_test(true).try_init(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut storage) .unwrap(); From 6c2e9f768a9fb5795bf33b14f89f890e2115be12 Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 12 Dec 2023 11:45:04 +0100 Subject: [PATCH 013/104] Inflate defensively (#1195) * inflate defensively * add tests * Update zrml/court/src/tests.rs Co-authored-by: Malte Kliemann * fix test * fix test name --------- Co-authored-by: Malte Kliemann --- primitives/src/constants/mock.rs | 1 + runtime/battery-station/src/parameters.rs | 2 ++ runtime/common/src/lib.rs | 1 + runtime/zeitgeist/src/parameters.rs | 2 ++ zrml/court/src/lib.rs | 16 +++++++++- zrml/court/src/mock.rs | 5 +-- zrml/court/src/tests.rs | 37 +++++++++++++++++++++-- zrml/neo-swaps/src/mock.rs | 11 ++++--- zrml/prediction-markets/src/mock.rs | 11 ++++--- 9 files changed, 70 insertions(+), 16 deletions(-) diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 299cf27b6..8235e6eee 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -48,6 +48,7 @@ parameter_types! { pub const MaxDelegations: u32 = 5; pub const MaxSelectedDraws: u32 = 510; pub const MaxCourtParticipants: u32 = 1_000; + pub const MaxYearlyInflation: Perbill = Perbill::from_percent(10u32); pub const MinJurorStake: Balance = 50 * CENT; pub const InflationPeriod: BlockNumber = 20; } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 40cce1de7..bf19acc07 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -129,6 +129,8 @@ parameter_types! { pub const MaxSelectedDraws: u32 = 510; /// The maximum number of jurors / delegators that can be registered. pub const MaxCourtParticipants: u32 = 1_000; + /// The maximum yearly inflation for court incentivisation. + pub const MaxYearlyInflation: Perbill = Perbill::from_percent(10); /// The minimum stake a user needs to reserve to become a juror. pub const MinJurorStake: Balance = 500 * BASE; /// The interval for requesting multiple court votes at once. diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index a0e38f37c..a63a51be6 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1077,6 +1077,7 @@ macro_rules! impl_config_traits { type MaxDelegations = MaxDelegations; type MaxSelectedDraws = MaxSelectedDraws; type MaxCourtParticipants = MaxCourtParticipants; + type MaxYearlyInflation = MaxYearlyInflation; type MinJurorStake = MinJurorStake; type MonetaryGovernanceOrigin = EnsureRoot; type Random = RandomnessCollectiveFlip; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index c9e718f13..f5bf84b40 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -129,6 +129,8 @@ parameter_types! { pub const MaxSelectedDraws: u32 = 510; /// The maximum number of jurors / delegators that can be registered. pub const MaxCourtParticipants: u32 = 1_000; + /// The maximum yearly inflation for court incentivisation. + pub const MaxYearlyInflation: Perbill = Perbill::from_percent(10); /// The minimum stake a user needs to reserve to become a juror. pub const MinJurorStake: Balance = 500 * BASE; /// The interval for requesting multiple court votes at once. diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index dd19681b3..10cf08bcb 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -167,6 +167,10 @@ mod pallet { #[pallet::constant] type MaxCourtParticipants: Get; + /// The maximum yearly inflation rate. + #[pallet::constant] + type MaxYearlyInflation: Get; + /// The minimum stake a user needs to lock to become a juror. #[pallet::constant] type MinJurorStake: Get>; @@ -290,7 +294,7 @@ mod pallet { #[pallet::type_value] pub fn DefaultYearlyInflation() -> Perbill { - Perbill::from_perthousand(20u32) + Perbill::from_perthousand(0u32) } /// The current inflation rate. @@ -465,6 +469,8 @@ mod pallet { /// Action cannot be completed because an unexpected error has occurred. This should be /// reported to protocol maintainers. Unexpected(UnexpectedError), + /// The inflation rate is too high. + InflationExceedsMaxYearlyInflation, } // NOTE: these errors should never happen. @@ -1161,6 +1167,11 @@ mod pallet { pub fn set_inflation(origin: OriginFor, inflation: Perbill) -> DispatchResult { T::MonetaryGovernanceOrigin::ensure_origin(origin)?; + ensure!( + inflation <= T::MaxYearlyInflation::get(), + Error::::InflationExceedsMaxYearlyInflation + ); + >::put(inflation); Self::deposit_event(Event::InflationSet { inflation }); @@ -1293,6 +1304,9 @@ mod pallet { } let yearly_inflation_rate = >::get(); + if yearly_inflation_rate.is_zero() { + return T::WeightInfo::handle_inflation(0u32); + } // example: 1049272791644671442 let total_supply = T::Currency::total_issuance(); // example: 0.02 * 1049272791644671442 = 20985455832893428 diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index faf63d33f..1f51ae1ce 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -35,8 +35,8 @@ use zeitgeist_primitives::{ constants::mock::{ AggregationPeriod, AppealBond, AppealPeriod, BlockHashCount, BlocksPerYear, CourtPalletId, InflationPeriod, LockId, MaxAppeals, MaxApprovals, MaxCourtParticipants, MaxDelegations, - MaxReserves, MaxSelectedDraws, MinJurorStake, MinimumPeriod, PmPalletId, RequestInterval, - VotePeriod, BASE, + MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinJurorStake, MinimumPeriod, + PmPalletId, RequestInterval, VotePeriod, BASE, }, traits::DisputeResolutionApi, types::{ @@ -152,6 +152,7 @@ impl crate::Config for Runtime { type MaxDelegations = MaxDelegations; type MaxSelectedDraws = MaxSelectedDraws; type MaxCourtParticipants = MaxCourtParticipants; + type MaxYearlyInflation = MaxYearlyInflation; type MinJurorStake = MinJurorStake; type MonetaryGovernanceOrigin = EnsureRoot; type PalletId = CourtPalletId; diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index a117b6c82..b32cb3f41 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -29,24 +29,26 @@ use crate::{ AppealInfo, BalanceOf, CourtId, CourtIdToMarketId, CourtParticipantInfo, CourtParticipantInfoOf, CourtPool, CourtPoolItem, CourtPoolOf, Courts, Error, Event, MarketIdToCourtId, MarketOf, NegativeImbalanceOf, Participants, RequestBlock, SelectedDraws, + YearlyInflation, }; use alloc::collections::BTreeMap; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, storage_root, traits::{fungible::Balanced, tokens::imbalance::Imbalance, Currency, NamedReservableCurrency}, + StateVersion, }; use pallet_balances::{BalanceLock, NegativeImbalance}; use rand::seq::SliceRandom; use sp_runtime::{ traits::{BlakeTwo256, Hash, Zero}, - Perquintill, + Perbill, Perquintill, }; use test_case::test_case; use zeitgeist_primitives::{ constants::{ mock::{ AggregationPeriod, AppealBond, AppealPeriod, InflationPeriod, LockId, MaxAppeals, - MaxCourtParticipants, MinJurorStake, RequestInterval, VotePeriod, + MaxCourtParticipants, MaxYearlyInflation, MinJurorStake, RequestInterval, VotePeriod, }, BASE, }, @@ -3053,9 +3055,24 @@ fn random_jurors_returns_a_subset_of_jurors() { }); } +#[test] +fn set_inflation_fails_if_inflation_exceeds_max_yearly_inflation() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Court::set_inflation( + RuntimeOrigin::root(), + MaxYearlyInflation::get() + Perbill::from_percent(1) + ), + Error::::InflationExceedsMaxYearlyInflation + ); + }); +} + #[test] fn handle_inflation_works() { ExtBuilder::default().build().execute_with(|| { + assert_ok!(Court::set_inflation(RuntimeOrigin::root(), Perbill::from_percent(2u32))); + let mut jurors = >::get(); let mut free_balances_before = BTreeMap::new(); let jurors_list = [1000, 10_000, 100_000, 1_000_000, 10_000_000]; @@ -3099,6 +3116,20 @@ fn handle_inflation_works() { }); } +#[test] +fn handle_inflation_is_noop_if_yearly_inflation_is_zero() { + ExtBuilder::default().build().execute_with(|| { + YearlyInflation::::kill(); + + let inflation_period_block = InflationPeriod::get(); + // save current storage root + let tmp = storage_root(StateVersion::V1); + // handle_inflation is a noop for the storage + Court::handle_inflation(inflation_period_block); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); +} + #[test] fn handle_inflation_without_waiting_one_inflation_period() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 96f0c5b2d..27e41b289 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -55,11 +55,11 @@ use zeitgeist_primitives::{ MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxInRatio, MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxOwners, MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, - 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, + 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, }, math::fixed::FixedMul, traits::{DeployPoolApi, DistributeFees}, @@ -291,6 +291,7 @@ impl zrml_court::Config for Runtime { type MaxDelegations = MaxDelegations; type MaxSelectedDraws = MaxSelectedDraws; type MaxCourtParticipants = MaxCourtParticipants; + type MaxYearlyInflation = MaxYearlyInflation; type MinJurorStake = MinJurorStake; type MonetaryGovernanceOrigin = EnsureRoot; type PalletId = CourtPalletId; diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index c9eea71b3..d1017c081 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -51,11 +51,11 @@ use zeitgeist_primitives::{ MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxInRatio, MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxOwners, MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxSubsidyPeriod, MaxSwapFee, MaxTotalWeight, MaxWeight, - 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, + 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, }, traits::DeployPoolApi, types::{ @@ -343,6 +343,7 @@ impl zrml_court::Config for Runtime { type MaxDelegations = MaxDelegations; type MaxSelectedDraws = MaxSelectedDraws; type MaxCourtParticipants = MaxCourtParticipants; + type MaxYearlyInflation = MaxYearlyInflation; type MinJurorStake = MinJurorStake; type MonetaryGovernanceOrigin = EnsureRoot; type PalletId = CourtPalletId; From 6ee0368e4afcadcc4867dd6ca487a69019dd35c5 Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 12 Dec 2023 18:25:05 +0100 Subject: [PATCH 014/104] Maintain order book (#1183) * integrate market creator fees into orderbook * edit tests * update tests * modify tests * avoid order side * Update primitives/src/traits/distribute_fees.rs Co-authored-by: Malte Kliemann * remove asset and account from get_fee api call * take base asset fees from fill_order * adjust orderbook for taking fees in fill_order * adjust without order side for fill_order * adapt to avoid concept of order side * adapt benchmarks, tests and weights, restructure * use DispatchResult * remove unused import * do not adjust maker amount for place_order * correct fuzz of orderbook * check if maker_amount is zero * fix order book name in benchmarks * use remove instead of cancel order book * correct order book test names * update READMEs * fmt * add tests * use minimum balance as min order size * Update zrml/orderbook/README.md Co-authored-by: Malte Kliemann * Update zrml/orderbook/README.md Co-authored-by: Malte Kliemann * Apply suggestions from code review Co-authored-by: Malte Kliemann * prettier orderbook md * remove comments from benchmarks * fix tests and benchmarks readibility * use order book instead of orderbook * clarify error message * clarify comments * rename is to ensure * error for repatriate * fix unnecessary clone * correct mocks behaviour * improve test * improve test of near full fill error * use turbofish syntax * add filled_maker_amount to event * split tests * combine two functions, add docs * abandon get_fees * fix order book tests and mock * remove check for impossible behaviour * fix toml and benchs * prepare migration * add migration for order structure * return zero fees if transfer fails * delete unnecessary assert * fix naming * fix naming the second * fix maker fill description * add scoring rule check to remove order * fix post_upgrade * fix terminology * Update zrml/orderbook/src/migrations.rs Co-authored-by: Malte Kliemann * use storage root check in migration test * Update zrml/orderbook/src/lib.rs Co-authored-by: Harald Heckmann * Update zrml/orderbook/src/lib.rs Co-authored-by: Harald Heckmann * Update zrml/orderbook/src/lib.rs Co-authored-by: Harald Heckmann * delete debug_assert --------- Co-authored-by: Malte Kliemann Co-authored-by: Harald Heckmann --- Cargo.lock | 1 + README.md | 4 +- runtime/common/src/lib.rs | 5 +- zrml/neo-swaps/src/mock.rs | 8 +- zrml/orderbook/Cargo.toml | 1 + zrml/orderbook/README.md | 47 +- .../fuzz/orderbook_v1_full_workflow.rs | 56 +- zrml/orderbook/src/benchmarks.rs | 100 +- zrml/orderbook/src/lib.rs | 548 +++++----- zrml/orderbook/src/migrations.rs | 331 ++++++ zrml/orderbook/src/mock.rs | 49 +- zrml/orderbook/src/tests.rs | 947 ++++++++++++++---- zrml/orderbook/src/types.rs | 15 +- zrml/orderbook/src/weights.rs | 104 +- 14 files changed, 1567 insertions(+), 649 deletions(-) create mode 100644 zrml/orderbook/src/migrations.rs diff --git a/Cargo.lock b/Cargo.lock index 85fde4a3c..9722c255b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14668,6 +14668,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "orml-currencies", "orml-tokens", "orml-traits", diff --git a/README.md b/README.md index 0f98b4ee0..ac77d1939 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,7 @@ _anything_. - [neo-swaps](./zrml/neo-swaps) - An implementation of the Logarithmic Market Scoring Rule as constant function market maker, tailor-made for decentralized combinatorial markets and Futarchy. -- [orderbook](./zrml/orderbook) - A naive orderbook implementation that's - only part of Zeitgeist's PoC. Will be replaced by a v2 orderbook that uses - 0x-style hybrid on-chain and off-chain trading. +- [orderbook](./zrml/orderbook) - An order book implementation. - [parimutuel](./zrml/parimutuel) - A straightforward parimutuel market maker for categorical markets. - [prediction-markets](./zrml/prediction-markets) - The core implementation of diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index a63a51be6..17b57de5a 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -61,10 +61,10 @@ macro_rules! decl_common_types { type Address = sp_runtime::MultiAddress; #[cfg(feature = "parachain")] - type Migrations = (); + type Migrations = zrml_orderbook::migrations::TranslateOrderStructure; #[cfg(not(feature = "parachain"))] - type Migrations = (); + type Migrations = zrml_orderbook::migrations::TranslateOrderStructure; pub type Executive = frame_executive::Executive< Runtime, @@ -1270,6 +1270,7 @@ macro_rules! impl_config_traits { impl zrml_orderbook::Config for Runtime { type AssetManager = AssetManager; + type ExternalFees = MarketCreatorFee; type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; type PalletId = OrderbookPalletId; diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 27e41b289..c505c0fe3 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -36,7 +36,7 @@ use orml_asset_registry::AssetMetadata; use orml_traits::MultiCurrency; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Get, IdentityLookup}, + traits::{BlakeTwo256, Get, IdentityLookup, Zero}, DispatchResult, Percent, SaturatedConversion, }; use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; @@ -142,8 +142,10 @@ where amount: Self::Balance, ) -> Self::Balance { let fees = amount.bmul(EXTERNAL_FEES.saturated_into()).unwrap(); - let _ = T::MultiCurrency::transfer(asset, account, &F::get(), fees); - fees + match T::MultiCurrency::transfer(asset, account, &F::get(), fees) { + Ok(_) => fees, + Err(_) => Zero::zero(), + } } } diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index 8a821e922..508240347 100644 --- a/zrml/orderbook/Cargo.toml +++ b/zrml/orderbook/Cargo.toml @@ -3,6 +3,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } diff --git a/zrml/orderbook/README.md b/zrml/orderbook/README.md index 46f4a170c..0f9842dbf 100644 --- a/zrml/orderbook/README.md +++ b/zrml/orderbook/README.md @@ -1,3 +1,46 @@ -# Orderbook Module +# Orderbook Pallet -A module to trade shares using a naive on-chain orderbook. +A pallet of an on-chain order book, which allows to exchange the market's base +asset for outcome assets and vice versa. + +## Overview + +The order book can be set as a market's scoring rule. It allows to place, +partially or fully fill and remove orders. + +## Terminology + +- `maker_partial_fill`: The partial amount of what the maker wants to get filled. +- `maker_fill`: The amount of what the maker wants to get filled. +- `taker_fill`: The amount of what the taker wants to fill. +- `maker_asset`: The asset that the maker wants to sell. +- `maker_amount`: The amount of the asset that the maker wants to sell. +- `taker_asset`: The asset that the taker needs to have to buy the maker's + asset. +- `taker_amount`: The amount of the asset that the taker needs to have to buy + the maker's asset. + +## Notes + +- Orders must always bid or ask for the corresponding market's base asset. +- External fees are always paid in the market's base asset after the order is + filled. In particular, the recipient of the collateral pays the fee. The + implementation, however, arranges the transfers slightly differently for + convenience: + - If the order is an ask (maker sells outcome tokens), then the external fees + are taken (not charged!) from the taker before the order is executed. The + taker still receives the full amount of outcome tokens, but the maker + receives only an adjusted amount. + - If the order is a bid (maker buys outcome tokens), then the external fees + are charged from the taker after the transaction is executed. In particular, + the maker still receives the full amount of outcome tokens. + +## Interface + +### Dispatches + +#### Public Dispatches + +- `remove_order`: Allows a user to remove their order from the order book. +- `fill_order`: Used to fill an order either partially or completely. +- `place_order`: Places a new order into the order book. diff --git a/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs b/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs index 4212d80bf..807eabd2f 100644 --- a/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs +++ b/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs @@ -20,10 +20,7 @@ use libfuzzer_sys::fuzz_target; use zeitgeist_primitives::types::{Asset, ScalarPosition, SerdeWrapper}; -use zrml_orderbook::{ - mock::{ExtBuilder, Orderbook, RuntimeOrigin}, - types::OrderSide, -}; +use zrml_orderbook::mock::{ExtBuilder, Orderbook, RuntimeOrigin}; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; @@ -32,35 +29,36 @@ fuzz_target!(|data: Data| { let mut ext = ExtBuilder::default().build(); ext.execute_with(|| { // Make arbitrary order and attempt to fill - let outcome_asset = asset(data.fill_order_outcome_asset); + let maker_asset = asset(data.maker_asset); + let taker_asset = asset(data.taker_asset); let _ = Orderbook::place_order( RuntimeOrigin::signed(data.fill_order_origin.into()), data.market_id, - outcome_asset, - orderside(data.fill_order_side), - data.fill_order_amount, - data.fill_order_price, + maker_asset, + data.maker_amount, + taker_asset, + data.taker_amount, ); let _ = Orderbook::fill_order( RuntimeOrigin::signed(data.fill_order_origin.into()), data.order_id, - None, + maker_partial_fill(data.maker_partial_fill), ); - // Make arbitrary order and attempt to cancel + // Make arbitrary order and attempt to remove let _ = Orderbook::place_order( - RuntimeOrigin::signed(data.cancel_order_origin.into()), + RuntimeOrigin::signed(data.place_order_origin.into()), data.market_id, - outcome_asset, - orderside(data.cancel_order_side), - data.cancel_order_amount, - data.cancel_order_price, + maker_asset, + data.maker_amount, + taker_asset, + data.taker_amount, ); let _ = Orderbook::remove_order( - RuntimeOrigin::signed(data.cancel_order_origin.into()), + RuntimeOrigin::signed(data.remove_order_origin.into()), data.order_id, ); }); @@ -72,16 +70,16 @@ struct Data { market_id: u128, order_id: u128, - fill_order_amount: u128, - fill_order_outcome_asset: (u128, u16), - fill_order_price: u128, + place_order_origin: u8, + maker_asset: (u128, u16), + maker_amount: u128, + taker_asset: (u128, u16), + taker_amount: u128, + fill_order_origin: u8, - fill_order_side: u8, + maker_partial_fill: u128, - cancel_order_amount: u128, - cancel_order_price: u128, - cancel_order_origin: u8, - cancel_order_side: u8, + remove_order_origin: u8, } fn asset(seed: (u128, u16)) -> Asset { @@ -99,10 +97,6 @@ fn asset(seed: (u128, u16)) -> Asset { } } -fn orderside(seed: u8) -> OrderSide { - let module = seed % 2; - match module { - 0 => OrderSide::Bid, - _ => OrderSide::Ask, - } +fn maker_partial_fill(s: u128) -> Option { + if s % 2 == 0 { Some(s) } else { None } } diff --git a/zrml/orderbook/src/benchmarks.rs b/zrml/orderbook/src/benchmarks.rs index 8f51b5d23..b3075b678 100644 --- a/zrml/orderbook/src/benchmarks.rs +++ b/zrml/orderbook/src/benchmarks.rs @@ -24,9 +24,7 @@ #![allow(clippy::type_complexity)] use super::*; -use crate::utils::market_mock; -#[cfg(test)] -use crate::Pallet as OrderBook; +use crate::{utils::market_mock, Pallet as Orderbook}; use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_support::dispatch::UnfilteredDispatchable; use frame_system::RawOrigin; @@ -34,87 +32,79 @@ use orml_traits::MultiCurrency; use sp_runtime::SaturatedConversion; use zeitgeist_primitives::{constants::BASE, types::Asset}; -// Takes a `seed` and returns an account. Use None to generate a whitelisted caller -fn generate_funded_account(seed: Option) -> Result { +fn generate_funded_account( + seed: Option, + asset: AssetOf, +) -> Result { let acc = if let Some(s) = seed { account("AssetHolder", 0, s) } else { whitelisted_caller() }; - - let outcome_asset = Asset::CategoricalOutcome::>(0u32.into(), 0); - T::AssetManager::deposit(outcome_asset, &acc, BASE.saturating_mul(1_000).saturated_into())?; - let _ = T::AssetManager::deposit(Asset::Ztg, &acc, BASE.saturating_mul(1_000).saturated_into()); + T::AssetManager::deposit(asset, &acc, BASE.saturating_mul(1_000).saturated_into())?; Ok(acc) } -// Creates an account and gives it asset and currency. `seed` specifies the account seed, -// None will return a whitelisted account -// Returns `account`, `asset`, `outcome_asset_amount` and `base_asset_amount` fn order_common_parameters( seed: Option, ) -> Result< - (T::AccountId, Asset>, BalanceOf, BalanceOf, MarketIdOf), + (MarketIdOf, T::AccountId, Asset>, BalanceOf, BalanceOf), &'static str, > { - let acc = generate_funded_account::(seed)?; - let outcome_asset = Asset::CategoricalOutcome::>(0u32.into(), 0); - let outcome_asset_amount: BalanceOf = BASE.saturated_into(); - let base_asset_amount: BalanceOf = 100u32.into(); - let market_id: MarketIdOf = 0u32.into(); let market = market_mock::(); + let maker_asset = market.base_asset; + let acc = generate_funded_account::(seed, maker_asset)?; + let maker_amount: BalanceOf = BASE.saturating_mul(1_000).saturated_into(); + let taker_amount: BalanceOf = BASE.saturating_mul(1_000).saturated_into(); T::MarketCommons::push_market(market.clone()).unwrap(); - Ok((acc, outcome_asset, outcome_asset_amount, base_asset_amount, market_id)) + let market_id: MarketIdOf = 0u32.into(); + Ok((market_id, acc, maker_asset, maker_amount, taker_amount)) } -// Creates an order of type `order_type`. `seed` specifies the account seed, -// None will return a whitelisted account -// Returns `account`, `asset`, `order_id` -fn place_order( - order_type: OrderSide, +fn place_default_order( seed: Option, + taker_asset: AssetOf, ) -> Result<(T::AccountId, MarketIdOf, OrderId), &'static str> { - let (acc, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = + let (market_id, acc, maker_asset, maker_amount, taker_amount) = order_common_parameters::(seed)?; let order_id = >::get(); - let _ = Call::::place_order { - market_id, - outcome_asset, - side: order_type.clone(), - outcome_asset_amount, - base_asset_amount, - } - .dispatch_bypass_filter(RawOrigin::Signed(acc.clone()).into())?; + let _ = + Call::::place_order { market_id, maker_asset, maker_amount, taker_asset, taker_amount } + .dispatch_bypass_filter(RawOrigin::Signed(acc.clone()).into())?; Ok((acc, market_id, order_id)) } benchmarks! { - remove_order_ask { - let (caller, _, order_id) = place_order::(OrderSide::Ask, None)?; + remove_order { + let market_id = 0u32.into(); + let taker_asset = Asset::CategoricalOutcome::>(market_id, 0); + let (caller, _, order_id) = place_default_order::(None, taker_asset)?; }: remove_order(RawOrigin::Signed(caller), order_id) - remove_order_bid { - let (caller, _, order_id) = place_order::(OrderSide::Bid, None)?; - }: remove_order(RawOrigin::Signed(caller), order_id) - - fill_order_ask { - let caller = generate_funded_account::(None)?; - let (_, _, order_id) = place_order::(OrderSide::Ask, Some(0))?; - }: fill_order(RawOrigin::Signed(caller), order_id, None) - - fill_order_bid { - let caller = generate_funded_account::(None)?; - let (_, _, order_id) = place_order::(OrderSide::Bid, Some(0))?; + fill_order { + let market_id = 0u32.into(); + let taker_asset = Asset::CategoricalOutcome::>(market_id, 0); + let (_, _, order_id) = place_default_order::(Some(0), taker_asset)?; + let caller = generate_funded_account::(None, taker_asset)?; + let maker_asset = T::MarketCommons::market(&market_id).unwrap().base_asset; + let caller = generate_funded_account::(None, maker_asset)?; }: fill_order(RawOrigin::Signed(caller), order_id, None) - place_order_ask { - let (caller, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = order_common_parameters::(None)?; - }: place_order(RawOrigin::Signed(caller), market_id, outcome_asset, OrderSide::Ask, outcome_asset_amount, base_asset_amount) - - place_order_bid { - let (caller, outcome_asset, outcome_asset_amount, base_asset_amount, market_id) = order_common_parameters::(None)?; - }: place_order(RawOrigin::Signed(caller), market_id, outcome_asset, OrderSide::Bid, outcome_asset_amount, base_asset_amount) + place_order { + let (market_id, caller, maker_asset, maker_amount, taker_amount) = + order_common_parameters::(None)?; + let taker_asset = Asset::CategoricalOutcome::>(market_id, 0); + }: { + Orderbook::::place_order( + RawOrigin::Signed(caller).into(), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )?; + } impl_benchmark_test_suite!( - OrderBook, + Orderbook, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime, ); diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index 7eaf29b81..450bd6c9c 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -25,30 +25,29 @@ use crate::{types::*, weights::*}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ - dispatch::DispatchResultWithPostInfo, ensure, - pallet_prelude::{OptionQuery, StorageMap, StorageValue, ValueQuery}, + pallet_prelude::{ + DispatchError, DispatchResult, OptionQuery, StorageMap, StorageValue, ValueQuery, + }, traits::{IsType, StorageVersion}, transactional, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use orml_traits::{BalanceStatus, MultiCurrency, NamedMultiReservableCurrency}; pub use pallet::*; -use sp_runtime::{ - traits::{Get, Zero}, - SaturatedConversion, Saturating, -}; +use sp_runtime::traits::{Get, Zero}; use zeitgeist_primitives::{ math::{ checked_ops_res::{CheckedAddRes, CheckedSubRes}, fixed::{FixedDiv, FixedMul}, }, - traits::MarketCommonsPalletApi, + traits::{DistributeFees, MarketCommonsPalletApi}, types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; #[cfg(feature = "runtime-benchmarks")] mod benchmarks; +pub mod migrations; pub mod mock; #[cfg(test)] mod tests; @@ -60,15 +59,26 @@ pub mod weights; mod pallet { use super::*; + #[allow(dead_code)] + const LOG_TARGET: &str = "runtime::zrml-orderbook"; + #[pallet::config] pub trait Config: frame_system::Config { /// Shares of outcome assets and native currency type AssetManager: NamedMultiReservableCurrency< Self::AccountId, - CurrencyId = Asset>, + CurrencyId = AssetOf, ReserveIdentifier = [u8; 8], >; + /// The way how fees are taken from the market base asset. + type ExternalFees: DistributeFees< + Asset = AssetOf, + AccountId = AccountIdOf, + Balance = BalanceOf, + MarketId = MarketIdOf, + >; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; type MarketCommons: MarketCommonsPalletApi< @@ -84,7 +94,7 @@ mod pallet { } /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); pub(crate) type BalanceOf = <::AssetManager as MultiCurrency< ::AccountId, @@ -94,12 +104,13 @@ mod pallet { pub(crate) type AccountIdOf = ::AccountId; pub(crate) type OrderOf = Order, BalanceOf, MarketIdOf>; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; + pub(crate) type AssetOf = Asset>; pub(crate) type MarketOf = Market< AccountIdOf, BalanceOf, ::BlockNumber, MomentOf, - Asset>, + AssetOf, >; #[pallet::pallet] @@ -122,9 +133,10 @@ mod pallet { order_id: OrderId, maker: AccountIdOf, taker: AccountIdOf, - filled: BalanceOf, - unfilled_outcome_asset_amount: BalanceOf, - unfilled_base_asset_amount: BalanceOf, + filled_maker_amount: BalanceOf, + filled_taker_amount: BalanceOf, + unfilled_maker_amount: BalanceOf, + unfilled_taker_amount: BalanceOf, }, OrderPlaced { order_id: OrderId, @@ -144,16 +156,18 @@ mod pallet { OrderDoesNotExist, /// The market is not active. MarketIsNotActive, - /// The scoring rule is not orderbook. + /// The scoring rule is not order book. InvalidScoringRule, /// The specified amount parameter is too high for the order. AmountTooHighForOrder, - /// The specified amount parameter is zero. - AmountIsZero, /// The specified outcome asset is not part of the market. InvalidOutcomeAsset, /// The maker partial fill leads to a too low quotient for the next order execution. - MakerPartialFillTooLow, + PartialFillNearFullFillNotAllowed, + /// The market base asset is not present. + MarketBaseAssetNotPresent, + /// The specified amount is below the minimum balance. + BelowMinimumBalance, } #[pallet::call] @@ -164,181 +178,266 @@ mod pallet { /// /// Complexity: `O(1)` #[pallet::call_index(0)] - #[pallet::weight( - T::WeightInfo::remove_order_ask().max(T::WeightInfo::remove_order_bid()) - )] + #[pallet::weight(T::WeightInfo::remove_order())] #[transactional] - pub fn remove_order(origin: OriginFor, order_id: OrderId) -> DispatchResultWithPostInfo { - let sender = ensure_signed(origin)?; - - let order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; - - let maker = &order_data.maker; - ensure!(sender == *maker, Error::::NotOrderCreator); - - match order_data.side { - OrderSide::Bid => { - let missing = T::AssetManager::unreserve_named( - &Self::reserve_id(), - order_data.base_asset, - maker, - order_data.base_asset_amount, - ); - debug_assert!( - missing.is_zero(), - "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} \ - who: {:?}, amount: {:?}, missing: {:?}", - Self::reserve_id(), - order_data.base_asset, - maker, - order_data.base_asset_amount, - missing, - ); - } - OrderSide::Ask => { - let missing = T::AssetManager::unreserve_named( - &Self::reserve_id(), - order_data.outcome_asset, - maker, - order_data.outcome_asset_amount, - ); - debug_assert!( - missing.is_zero(), - "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} \ - who: {:?}, amount: {:?}, missing: {:?}", - Self::reserve_id(), - order_data.outcome_asset, - maker, - order_data.outcome_asset_amount, - missing, - ); - } - } - - >::remove(order_id); + pub fn remove_order(origin: OriginFor, order_id: OrderId) -> DispatchResult { + let who = ensure_signed(origin)?; - Self::deposit_event(Event::OrderRemoved { order_id, maker: maker.clone() }); + Self::do_remove_order(order_id, who)?; - match order_data.side { - OrderSide::Bid => Ok(Some(T::WeightInfo::remove_order_bid()).into()), - OrderSide::Ask => Ok(Some(T::WeightInfo::remove_order_ask()).into()), - } + Ok(()) } /// Fill an existing order entirely (`maker_partial_fill` = None) /// or partially (`maker_partial_fill` = Some(partial_amount)). /// - /// NOTE: The `maker_partial_fill` is the partial amount of what the maker wants to fill. + /// External fees are paid in the base asset. + /// + /// NOTE: The `maker_partial_fill` is the partial amount + /// of what the maker wants to get filled. /// /// # Weight /// /// Complexity: `O(1)` #[pallet::call_index(1)] - #[pallet::weight( - T::WeightInfo::fill_order_ask().max(T::WeightInfo::fill_order_bid()) - )] + #[pallet::weight(T::WeightInfo::fill_order())] #[transactional] pub fn fill_order( origin: OriginFor, order_id: OrderId, maker_partial_fill: Option>, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let taker = ensure_signed(origin)?; + Self::do_fill_order(order_id, taker, maker_partial_fill)?; + + Ok(()) + } + + /// Place a new order. + /// + /// # Weight + /// + /// Complexity: `O(1)` + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::place_order())] + #[transactional] + pub fn place_order( + origin: OriginFor, + #[pallet::compact] market_id: MarketIdOf, + maker_asset: AssetOf, + #[pallet::compact] maker_amount: BalanceOf, + taker_asset: AssetOf, + #[pallet::compact] taker_amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + Self::do_place_order( + who, + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )?; + + Ok(()) + } + } + + impl Pallet { + /// The reserve ID of the order book pallet. + #[inline] + pub fn reserve_id() -> [u8; 8] { + T::PalletId::get().0 + } + + pub fn outcome_assets(market_id: MarketIdOf, market: &MarketOf) -> Vec> { + match market.market_type { + MarketType::Categorical(categories) => { + let mut assets = Vec::new(); + for i in 0..categories { + assets.push(Asset::CategoricalOutcome(market_id, i)); + } + assets + } + MarketType::Scalar(_) => { + vec![ + Asset::ScalarOutcome(market_id, ScalarPosition::Long), + Asset::ScalarOutcome(market_id, ScalarPosition::Short), + ] + } + } + } + + /// Reduces the reserved maker and requested taker amount + /// by the amount the maker and taker actually filled. + fn decrease_order_amounts( + order_data: &mut OrderOf, + maker_fill: BalanceOf, + taker_fill: BalanceOf, + ) -> DispatchResult { + order_data.maker_amount = order_data.maker_amount.checked_sub_res(&taker_fill)?; + order_data.taker_amount = order_data.taker_amount.checked_sub_res(&maker_fill)?; + Ok(()) + } + + /// Calculates the amount that the taker is going to get from the maker's amount. + fn get_taker_fill( + order_data: &OrderOf, + maker_fill: BalanceOf, + ) -> Result, DispatchError> { + // the maker_full_fill is the maximum amount of what the maker wants to have + let maker_full_fill = order_data.taker_amount; + // the taker_full_fill is the maximum amount of what the taker wants to have + let taker_full_fill = order_data.maker_amount; + // rounding down: the taker will always get a little bit less than what they asked for. + // This ensures that the reserve of the maker + // is always enough to repatriate successfully! + // `maker_full_fill` is ensured to be never zero in `ensure_ratio_quotient_valid` + let ratio = maker_fill.bdiv_floor(maker_full_fill)?; + // returns the (partial) amount of what the taker gets from the maker's amount + // respected the partial fill from the taker of what the maker wants to get filled + ratio.bmul_floor(taker_full_fill) + } + + fn ensure_ratio_quotient_valid(order_data: &OrderOf) -> DispatchResult { + let maker_full_fill = order_data.taker_amount; + // this ensures that partial fills, which fill nearly the whole order, are not executed + // this protects the last fill happening + // without a division by zero for `Perquintill::from_rational` + let is_ratio_quotient_valid = maker_full_fill.is_zero() + || maker_full_fill >= T::AssetManager::minimum_balance(order_data.taker_asset); + ensure!(is_ratio_quotient_valid, Error::::PartialFillNearFullFillNotAllowed); + Ok(()) + } + + fn do_remove_order(order_id: OrderId, who: AccountIdOf) -> DispatchResult { + let order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; + + let maker = &order_data.maker; + ensure!(who == *maker, Error::::NotOrderCreator); + + let missing = T::AssetManager::unreserve_named( + &Self::reserve_id(), + order_data.maker_asset, + maker, + order_data.maker_amount, + ); + + debug_assert!( + missing.is_zero(), + "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} who: {:?}, \ + amount: {:?}, missing: {:?}", + Self::reserve_id(), + order_data.maker_asset, + maker, + order_data.maker_amount, + missing, + ); + + >::remove(order_id); + + Self::deposit_event(Event::OrderRemoved { order_id, maker: maker.clone() }); + + Ok(()) + } + + /// Charge the external fees from `taker` and return the adjusted maker fill. + fn charge_external_fees( + order_data: &OrderOf, + base_asset: AssetOf, + maker_fill: BalanceOf, + taker: &AccountIdOf, + taker_fill: BalanceOf, + ) -> Result, DispatchError> { + let maker_asset_is_base = order_data.maker_asset == base_asset; + let base_asset_fill = if maker_asset_is_base { + taker_fill + } else { + debug_assert!(order_data.taker_asset == base_asset); + maker_fill + }; + let fee_amount = T::ExternalFees::distribute( + order_data.market_id, + base_asset, + taker, + base_asset_fill, + ); + if maker_asset_is_base { + // maker_fill is the amount that the maker wants to have (outcome asset from taker) + // do not charge fees from outcome assets, but rather from the base asset + Ok(maker_fill) + } else { + // accounting fees from the taker, + // who is responsible to pay the base asset minus fees to the maker + Ok(maker_fill.checked_sub_res(&fee_amount)?) + } + } + + fn do_fill_order( + order_id: OrderId, + taker: AccountIdOf, + maker_partial_fill: Option>, + ) -> DispatchResult { let mut order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; let market = T::MarketCommons::market(&order_data.market_id)?; - ensure!(market.scoring_rule == ScoringRule::Orderbook, Error::::InvalidScoringRule); + debug_assert!( + market.scoring_rule == ScoringRule::Orderbook, + "The call to place_order already ensured the scoring rule order book." + ); ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); let base_asset = market.base_asset; - let makers_requested_total = match order_data.side { - OrderSide::Bid => order_data.outcome_asset_amount, - OrderSide::Ask => order_data.base_asset_amount, - }; - let maker_fill = maker_partial_fill.unwrap_or(makers_requested_total); - ensure!(!maker_fill.is_zero(), Error::::AmountIsZero); - ensure!(maker_fill <= makers_requested_total, Error::::AmountTooHighForOrder); + let maker_fill = maker_partial_fill.unwrap_or(order_data.taker_amount); + ensure!( + maker_fill >= T::AssetManager::minimum_balance(order_data.taker_asset), + Error::::BelowMinimumBalance + ); + ensure!(maker_fill <= order_data.taker_amount, Error::::AmountTooHighForOrder); let maker = order_data.maker.clone(); + let maker_asset = order_data.maker_asset; + let taker_asset = order_data.taker_asset; - // the reserve of the maker should always be enough to repatriate successfully, e.g. taker gets a little bit less + // the reserve of the maker should always be enough + // to repatriate successfully, e.g. taker gets a little bit less // it should always ensure that the maker's request (maker_fill) is fully filled - match order_data.side { - OrderSide::Bid => { - T::AssetManager::ensure_can_withdraw( - order_data.outcome_asset, - &taker, - maker_fill, - )?; - - // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. - // This ensures that the reserve of the maker is always enough to repatriate successfully! - let ratio = maker_fill.bdiv_floor(order_data.outcome_asset_amount)?; - let taker_fill = ratio.bmul_floor(order_data.base_asset_amount)?; - - T::AssetManager::repatriate_reserved_named( - &Self::reserve_id(), - base_asset, - &maker, - &taker, - taker_fill, - BalanceStatus::Free, - )?; - - T::AssetManager::transfer( - order_data.outcome_asset, - &taker, - &maker, - maker_fill, - )?; - - order_data.base_asset_amount = - order_data.base_asset_amount.checked_sub_res(&taker_fill)?; - order_data.outcome_asset_amount = - order_data.outcome_asset_amount.checked_sub_res(&maker_fill)?; - // this ensures that partial fills, which fill nearly the whole order, are not executed - // this protects the last fill happening without a division by zero for `Perquintill::from_rational` - let is_ratio_quotient_valid = order_data.outcome_asset_amount.is_zero() - || order_data.outcome_asset_amount.saturated_into::() >= 100u128; - ensure!(is_ratio_quotient_valid, Error::::MakerPartialFillTooLow); - } - OrderSide::Ask => { - T::AssetManager::ensure_can_withdraw(base_asset, &taker, maker_fill)?; - - // Note that this always rounds down, i.e. the taker will always get a little bit less than what they asked for. - // This ensures that the reserve of the maker is always enough to repatriate successfully! - let ratio = maker_fill.bdiv_floor(order_data.base_asset_amount)?; - let taker_fill = ratio.bmul_floor(order_data.outcome_asset_amount)?; - - T::AssetManager::repatriate_reserved_named( - &Self::reserve_id(), - order_data.outcome_asset, - &maker, - &taker, - taker_fill, - BalanceStatus::Free, - )?; - - T::AssetManager::transfer(base_asset, &taker, &maker, maker_fill)?; - - order_data.outcome_asset_amount = - order_data.outcome_asset_amount.checked_sub_res(&taker_fill)?; - order_data.base_asset_amount = - order_data.base_asset_amount.checked_sub_res(&maker_fill)?; - // this ensures that partial fills, which fill nearly the whole order, are not executed - // this protects the last fill happening without a division by zero for `Perquintill::from_rational` - let is_ratio_quotient_valid = order_data.base_asset_amount.is_zero() - || order_data.base_asset_amount.saturated_into::() >= 100u128; - ensure!(is_ratio_quotient_valid, Error::::MakerPartialFillTooLow); - } - }; - - let unfilled_outcome_asset_amount = order_data.outcome_asset_amount; - let unfilled_base_asset_amount = order_data.base_asset_amount; - let total_unfilled = - unfilled_outcome_asset_amount.saturating_add(unfilled_base_asset_amount); - - if total_unfilled.is_zero() { + let taker_fill = Self::get_taker_fill(&order_data, maker_fill)?; + + // if base asset: fund the full amount, but charge base asset fees from taker later + T::AssetManager::repatriate_reserved_named( + &Self::reserve_id(), + maker_asset, + &maker, + &taker, + taker_fill, + BalanceStatus::Free, + )?; + + // always charge fees from the base asset and not the outcome asset + let maybe_adjusted_maker_fill = Self::charge_external_fees( + &order_data, + base_asset, + maker_fill, + &taker, + taker_fill, + )?; + + T::AssetManager::transfer( + taker_asset, + &taker, + &maker, + // fee was only charged if the taker spends base asset to the maker + maybe_adjusted_maker_fill, + )?; + + // the accounting system does not care about, whether fees were charged, + // it just substracts the total maker_fill and taker_fill (including fees) + Self::decrease_order_amounts(&mut order_data, maker_fill, taker_fill)?; + Self::ensure_ratio_quotient_valid(&order_data)?; + + if order_data.maker_amount.is_zero() { >::remove(order_id); } else { >::insert(order_id, order_data.clone()); @@ -348,116 +447,67 @@ mod pallet { order_id, maker, taker: taker.clone(), - filled: maker_fill, - unfilled_outcome_asset_amount, - unfilled_base_asset_amount, + filled_maker_amount: taker_fill, + filled_taker_amount: maker_fill, + unfilled_maker_amount: order_data.maker_amount, + unfilled_taker_amount: order_data.taker_amount, }); - match order_data.side { - OrderSide::Bid => Ok(Some(T::WeightInfo::fill_order_bid()).into()), - OrderSide::Ask => Ok(Some(T::WeightInfo::fill_order_ask()).into()), - } + Ok(()) } - /// Place a new order. - /// - /// # Weight - /// - /// Complexity: `O(1)` - #[pallet::call_index(2)] - #[pallet::weight( - T::WeightInfo::place_order_ask().max(T::WeightInfo::place_order_bid()) - )] - #[transactional] - pub fn place_order( - origin: OriginFor, - #[pallet::compact] market_id: MarketIdOf, - outcome_asset: Asset>, - side: OrderSide, - #[pallet::compact] outcome_asset_amount: BalanceOf, - #[pallet::compact] base_asset_amount: BalanceOf, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - + fn do_place_order( + who: AccountIdOf, + market_id: MarketIdOf, + maker_asset: AssetOf, + maker_amount: BalanceOf, + taker_asset: AssetOf, + taker_amount: BalanceOf, + ) -> DispatchResult { let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); ensure!(market.scoring_rule == ScoringRule::Orderbook, Error::::InvalidScoringRule); let market_assets = Self::outcome_assets(market_id, &market); + let base_asset = market.base_asset; + let outcome_asset = if maker_asset == base_asset { + taker_asset + } else { + ensure!(taker_asset == base_asset, Error::::MarketBaseAssetNotPresent); + maker_asset + }; ensure!( market_assets.binary_search(&outcome_asset).is_ok(), Error::::InvalidOutcomeAsset ); - let base_asset = market.base_asset; + ensure!( + maker_amount >= T::AssetManager::minimum_balance(maker_asset), + Error::::BelowMinimumBalance + ); + ensure!( + taker_amount >= T::AssetManager::minimum_balance(taker_asset), + Error::::BelowMinimumBalance + ); let order_id = >::get(); let next_order_id = order_id.checked_add_res(&1)?; + // fees are always only charged in the base asset in fill_order + T::AssetManager::reserve_named(&Self::reserve_id(), maker_asset, &who, maker_amount)?; + let order = Order { market_id, - side: side.clone(), - maker: who.clone(), - outcome_asset, - base_asset, - outcome_asset_amount, - base_asset_amount, + maker: who, + maker_asset, + maker_amount, + taker_asset, + taker_amount, }; - match side { - OrderSide::Bid => { - T::AssetManager::reserve_named( - &Self::reserve_id(), - base_asset, - &who, - base_asset_amount, - )?; - } - OrderSide::Ask => { - T::AssetManager::reserve_named( - &Self::reserve_id(), - outcome_asset, - &who, - outcome_asset_amount, - )?; - } - } - >::insert(order_id, order.clone()); >::put(next_order_id); Self::deposit_event(Event::OrderPlaced { order_id, order }); - match side { - OrderSide::Bid => Ok(Some(T::WeightInfo::place_order_bid()).into()), - OrderSide::Ask => Ok(Some(T::WeightInfo::place_order_ask()).into()), - } - } - } - - impl Pallet { - /// The reserve ID of the orderbook pallet. - #[inline] - pub fn reserve_id() -> [u8; 8] { - T::PalletId::get().0 - } - - pub fn outcome_assets( - market_id: MarketIdOf, - market: &MarketOf, - ) -> Vec>> { - match market.market_type { - MarketType::Categorical(categories) => { - let mut assets = Vec::new(); - for i in 0..categories { - assets.push(Asset::CategoricalOutcome(market_id, i)); - } - assets - } - MarketType::Scalar(_) => { - vec![ - Asset::ScalarOutcome(market_id, ScalarPosition::Long), - Asset::ScalarOutcome(market_id, ScalarPosition::Short), - ] - } - } + Ok(()) } } } diff --git a/zrml/orderbook/src/migrations.rs b/zrml/orderbook/src/migrations.rs new file mode 100644 index 000000000..aa7be090a --- /dev/null +++ b/zrml/orderbook/src/migrations.rs @@ -0,0 +1,331 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + types::{Order, OrderId}, + AccountIdOf, BalanceOf, Config, MarketIdOf, Pallet as OrderbookPallet, +}; +#[cfg(feature = "try-runtime")] +use alloc::collections::BTreeMap; +#[cfg(feature = "try-runtime")] +use alloc::format; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +#[cfg(feature = "try-runtime")] +use frame_support::migration::storage_key_iter; +use frame_support::{ + dispatch::Weight, + log, + pallet_prelude::PhantomData, + traits::{Get, OnRuntimeUpgrade, StorageVersion}, + RuntimeDebug, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::traits::Saturating; +use zeitgeist_primitives::types::Asset; + +#[cfg(any(feature = "try-runtime", test))] +const ORDER_BOOK: &[u8] = b"Orderbook"; +#[cfg(any(feature = "try-runtime", test))] +const ORDERS: &[u8] = b"Orders"; + +const ORDER_BOOK_REQUIRED_STORAGE_VERSION: u16 = 0; +const ORDER_BOOK_NEXT_STORAGE_VERSION: u16 = 1; + +#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub enum OldOrderSide { + Bid, + Ask, +} + +#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] +pub struct OldOrder { + pub market_id: MarketId, + pub side: OldOrderSide, + pub maker: AccountId, + pub outcome_asset: Asset, + pub base_asset: Asset, + pub outcome_asset_amount: Balance, + pub base_asset_amount: Balance, +} + +type OldOrderOf = OldOrder, BalanceOf, MarketIdOf>; + +#[frame_support::storage_alias] +pub(crate) type Orders = + StorageMap, frame_support::Twox64Concat, OrderId, OldOrderOf>; + +pub struct TranslateOrderStructure(PhantomData); + +impl OnRuntimeUpgrade for TranslateOrderStructure { + fn on_runtime_upgrade() -> Weight { + let mut total_weight = T::DbWeight::get().reads(1); + let order_book_pallet_version = StorageVersion::get::>(); + if order_book_pallet_version != ORDER_BOOK_REQUIRED_STORAGE_VERSION { + log::info!( + "TranslateOrderStructure: order book pallet version is {:?}, but {:?} is required", + order_book_pallet_version, + ORDER_BOOK_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("TranslateOrderStructure: Starting..."); + + let mut translated = 0u64; + crate::Orders::::translate::, _>(|_order_id, old_order| { + translated.saturating_inc(); + + let (maker_asset, maker_amount, taker_asset, taker_amount) = match old_order.side { + // the maker reserved the base asset for bids + OldOrderSide::Bid => ( + old_order.base_asset, + old_order.base_asset_amount, + old_order.outcome_asset, + old_order.outcome_asset_amount, + ), + // the maker reserved the outcome asset for asks + OldOrderSide::Ask => ( + old_order.outcome_asset, + old_order.outcome_asset_amount, + old_order.base_asset, + old_order.base_asset_amount, + ), + }; + + let new_order = Order { + market_id: old_order.market_id, + maker: old_order.maker, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + }; + + Some(new_order) + }); + log::info!("TranslateOrderStructure: Upgraded {} orders.", translated); + total_weight = + total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); + + StorageVersion::new(ORDER_BOOK_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("TranslateOrderStructure: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + use frame_support::pallet_prelude::Twox64Concat; + + let old_orders = + storage_key_iter::, Twox64Concat>(ORDER_BOOK, ORDERS) + .collect::>(); + + let orders = Orders::::iter_keys().count() as u32; + let decodable_orders = Orders::::iter_values().count() as u32; + if orders == decodable_orders { + log::info!("All orders could successfully be decoded, order_count: {}.", orders); + } else { + log::error!( + "Can only decode {} of {} orders - others will be dropped", + decodable_orders, + orders + ); + } + + Ok(old_orders.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { + use orml_traits::NamedMultiReservableCurrency; + use zeitgeist_primitives::traits::MarketCommonsPalletApi; + + let old_orders: BTreeMap> = Decode::decode(&mut &previous_state[..]) + .expect("Failed to decode state: Invalid state"); + let mut new_order_count = 0usize; + for (order_id, new_order) in crate::Orders::::iter() { + let old_order = + old_orders.get(&order_id).expect(&format!("Order {:?} not found", order_id)[..]); + // assert old fields + assert_eq!(old_order.market_id, new_order.market_id); + assert_eq!(old_order.maker, new_order.maker); + // assert new fields + let reserved = T::AssetManager::reserved_balance_named( + &OrderbookPallet::::reserve_id(), + new_order.maker_asset, + &new_order.maker, + ); + // one reserve_id is for all orders for this maker_asset + assert!(reserved >= new_order.maker_amount); + + if let Ok(market) = T::MarketCommons::market(&new_order.market_id) { + let base_asset = market.base_asset; + if new_order.maker_asset == base_asset { + assert_eq!(new_order.maker_asset, old_order.base_asset); + assert_eq!(new_order.maker_amount, old_order.base_asset_amount); + assert_eq!(new_order.taker_amount, old_order.outcome_asset_amount); + assert_eq!(new_order.taker_asset, old_order.outcome_asset); + } else { + assert_eq!(new_order.taker_asset, base_asset); + assert_eq!(new_order.taker_asset, old_order.base_asset); + assert_eq!(new_order.taker_amount, old_order.base_asset_amount); + assert_eq!(new_order.maker_amount, old_order.outcome_asset_amount); + assert_eq!(new_order.maker_asset, old_order.outcome_asset); + } + } else { + log::error!( + "The market should be present for the order market id {:?}!", + new_order.market_id + ); + } + + new_order_count.saturating_inc(); + } + assert_eq!(old_orders.len(), new_order_count); + log::info!("TranslateOrderStructure: Order Counter post-upgrade is {}!", new_order_count); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{ExtBuilder, Runtime}, + OrderOf, + }; + use alloc::vec::Vec; + use frame_support::{ + dispatch::fmt::Debug, migration::put_storage_value, storage_root, StorageHasher, + Twox64Concat, + }; + use sp_runtime::StateVersion; + use zeitgeist_primitives::types::ScalarPosition; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + TranslateOrderStructure::::on_runtime_upgrade(); + assert_eq!( + StorageVersion::get::>(), + ORDER_BOOK_NEXT_STORAGE_VERSION + ); + }); + } + + #[test] + fn on_runtime_upgrade_works_as_expected() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let (old_orders, new_orders) = construct_old_new_tuple(); + populate_test_data::>( + ORDER_BOOK, + ORDERS, + old_orders.clone(), + ); + TranslateOrderStructure::::on_runtime_upgrade(); + + let actual = crate::Orders::::get(0).unwrap(); + assert_eq!(actual, new_orders[0]); + + let actual = crate::Orders::::get(1).unwrap(); + assert_eq!(actual, new_orders[1]); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + // ensure we migrated already + StorageVersion::new(ORDER_BOOK_NEXT_STORAGE_VERSION).put::>(); + + // save current storage root + let tmp = storage_root(StateVersion::V1); + TranslateOrderStructure::::on_runtime_upgrade(); + // ensure we did not change any storage with the migration + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + fn set_up_version() { + StorageVersion::new(ORDER_BOOK_REQUIRED_STORAGE_VERSION).put::>(); + } + + fn construct_old_new_tuple() -> (Vec>, Vec>) { + let market_id_0 = 0; + let outcome_asset_amount_0 = 42000; + let base_asset_amount_0 = 69000; + let old_order_0 = OldOrder { + market_id: market_id_0, + side: OldOrderSide::Bid, + maker: 1, + outcome_asset: Asset::CategoricalOutcome(market_id_0, 0u16), + base_asset: Asset::Ztg, + outcome_asset_amount: outcome_asset_amount_0, + base_asset_amount: base_asset_amount_0, + }; + let new_order_0 = Order { + market_id: market_id_0, + maker: 1, + // the maker reserved the base asset for order side bid + maker_asset: Asset::Ztg, + maker_amount: base_asset_amount_0, + taker_asset: Asset::CategoricalOutcome(market_id_0, 0u16), + taker_amount: outcome_asset_amount_0, + }; + + let market_id_1 = 1; + let outcome_asset_amount_1 = 42000; + let base_asset_amount_1 = 69000; + let old_order_1 = OldOrder { + market_id: market_id_1, + side: OldOrderSide::Ask, + maker: 1, + outcome_asset: Asset::ScalarOutcome(market_id_1, ScalarPosition::Long), + base_asset: Asset::Ztg, + outcome_asset_amount: outcome_asset_amount_1, + base_asset_amount: base_asset_amount_1, + }; + let new_order_1 = Order { + market_id: market_id_1, + maker: 1, + // the maker reserved the outcome asset for order side ask + maker_asset: Asset::ScalarOutcome(market_id_1, ScalarPosition::Long), + maker_amount: outcome_asset_amount_1, + taker_asset: Asset::Ztg, + taker_amount: base_asset_amount_1, + }; + (vec![old_order_0, old_order_1], vec![new_order_0, new_order_1]) + } + + #[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/orderbook/src/mock.rs b/zrml/orderbook/src/mock.rs index a5af9c621..321b73584 100644 --- a/zrml/orderbook/src/mock.rs +++ b/zrml/orderbook/src/mock.rs @@ -19,16 +19,21 @@ #![cfg(feature = "mock")] use crate as orderbook_v1; -use frame_support::{construct_runtime, traits::Everything}; +use crate::{AssetOf, BalanceOf, MarketIdOf}; +use core::marker::PhantomData; +use frame_support::{construct_runtime, pallet_prelude::Get, parameter_types, traits::Everything}; +use orml_traits::MultiCurrency; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup, Zero}, + Perbill, SaturatedConversion, }; use zeitgeist_primitives::{ constants::mock::{ BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, MaxReserves, MinimumPeriod, OrderbookPalletId, PmPalletId, BASE, }, + traits::DistributeFees, types::{ AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, @@ -38,6 +43,43 @@ use zeitgeist_primitives::{ pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; +pub const MARKET_CREATOR: AccountIdTest = 42; + +pub const INITIAL_BALANCE: Balance = 100 * BASE; + +parameter_types! { + pub const FeeAccount: AccountIdTest = MARKET_CREATOR; +} + +pub fn calculate_fee(amount: BalanceOf) -> BalanceOf { + Perbill::from_rational(1u64, 100u64).mul_floor(amount.saturated_into::>()) +} + +pub struct ExternalFees(PhantomData, PhantomData); + +impl DistributeFees for ExternalFees +where + F: Get, +{ + type Asset = AssetOf; + type AccountId = T::AccountId; + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + fn distribute( + _market_id: Self::MarketId, + asset: Self::Asset, + account: &Self::AccountId, + amount: Self::Balance, + ) -> Self::Balance { + let fees = calculate_fee::(amount); + match T::AssetManager::transfer(asset, account, &F::get(), fees) { + Ok(_) => fees, + Err(_) => Zero::zero(), + } + } +} + construct_runtime!( pub enum Runtime where @@ -57,6 +99,7 @@ construct_runtime!( impl crate::Config for Runtime { type AssetManager = AssetManager; + type ExternalFees = ExternalFees; type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; type PalletId = OrderbookPalletId; @@ -143,7 +186,7 @@ pub struct ExtBuilder { impl Default for ExtBuilder { fn default() -> Self { - Self { balances: vec![(ALICE, BASE), (BOB, BASE)] } + Self { balances: vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)] } } } impl ExtBuilder { diff --git a/zrml/orderbook/src/tests.rs b/zrml/orderbook/src/tests.rs index 3c5ae098d..b40db571e 100644 --- a/zrml/orderbook/src/tests.rs +++ b/zrml/orderbook/src/tests.rs @@ -16,26 +16,28 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{mock::*, utils::market_mock, Error, Event, Order, OrderSide, Orders}; -use frame_support::{ - assert_noop, assert_ok, - traits::{Currency, ReservableCurrency}, -}; +use crate::{mock::*, utils::market_mock, Error, Event, Order, Orders}; +use frame_support::{assert_noop, assert_ok}; +use orml_tokens::Error as AError; use orml_traits::{MultiCurrency, MultiReservableCurrency}; +use pallet_balances::Error as BError; +use sp_runtime::{Perbill, Perquintill}; use test_case::test_case; use zeitgeist_primitives::{ constants::BASE, - types::{AccountIdTest, Asset, ScoringRule}, + types::{Asset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; -use zrml_market_commons::{MarketCommonsPalletApi, Markets}; +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; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); assert_ok!(MarketCommons::mutate_market(&market_id, |market| { market.scoring_rule = scoring_rule; @@ -45,43 +47,432 @@ fn place_order_fails_with_wrong_scoring_rule(scoring_rule: ScoringRule) { Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - Asset::CategoricalOutcome(0, 2), - OrderSide::Bid, - 100, - 250, + market.base_asset, + 10 * BASE, + Asset::CategoricalOutcome(market_id, 2), + 25 * BASE, ), Error::::InvalidScoringRule ); }); } -#[test_case(ScoringRule::CPMM; "CPMM")] -#[test_case(ScoringRule::RikiddoSigmoidFeeMarketEma; "Rikiddo")] -fn fill_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")] +fn place_order_fails_if_market_status_not_active(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + market.base_asset, + 10 * BASE, + Asset::CategoricalOutcome(0, 2), + 25 * BASE, + ), + Error::::MarketIsNotActive + ); + }); +} + +#[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")] +fn fill_order_fails_if_market_status_not_active(status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - Asset::CategoricalOutcome(0, 2), - OrderSide::Bid, - 10, - 250, + maker_asset, + 10 * BASE, + taker_asset, + 25 * BASE, )); assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.scoring_rule = scoring_rule; + market.status = status; Ok(()) })); assert_noop!( - Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None), - Error::::InvalidScoringRule + Orderbook::fill_order(RuntimeOrigin::signed(BOB), order_id, None), + Error::::MarketIsNotActive + ); + }); +} + +#[test] +fn fill_order_fails_if_amount_too_high_for_order() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + taker_amount, + )); + + assert_noop!( + Orderbook::fill_order(RuntimeOrigin::signed(BOB), order_id, Some(taker_amount + 1)), + Error::::AmountTooHighForOrder + ); + }); +} + +#[test] +fn fill_order_fails_if_amount_is_below_minimum_balance() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + taker_amount, + )); + + assert_noop!( + Orderbook::fill_order( + RuntimeOrigin::signed(BOB), + order_id, + Some(AssetManager::minimum_balance(taker_asset) - 1) + ), + Error::::BelowMinimumBalance + ); + }); +} + +#[test] +fn place_order_fails_if_amount_is_below_minimum_balance() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + AssetManager::minimum_balance(maker_asset) - 1, + taker_asset, + AssetManager::minimum_balance(taker_asset), + ), + Error::::BelowMinimumBalance + ); + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + AssetManager::minimum_balance(maker_asset), + taker_asset, + AssetManager::minimum_balance(taker_asset) - 1, + ), + Error::::BelowMinimumBalance + ); + }); +} + +#[test] +fn fill_order_fails_if_balance_too_low() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + taker_amount, + )); + + AssetManager::deposit(taker_asset, &BOB, taker_amount - 1).unwrap(); + let bob_free_taker_asset = AssetManager::free_balance(taker_asset, &BOB); + assert_eq!(bob_free_taker_asset, taker_amount - 1); + + assert_noop!( + Orderbook::fill_order(RuntimeOrigin::signed(BOB), order_id, None), + AError::::BalanceTooLow + ); + }); +} + +#[test] +fn fill_order_fails_if_partial_fill_near_full_fill_not_allowed() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + taker_amount, + )); + + AssetManager::deposit(taker_asset, &BOB, taker_amount).unwrap(); + + assert_noop!( + Orderbook::fill_order( + RuntimeOrigin::signed(BOB), + order_id, + Some(taker_amount - AssetManager::minimum_balance(taker_asset) + 1) + ), + Error::::PartialFillNearFullFillNotAllowed + ); + }); +} + +#[test] +fn fill_order_removes_order() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + taker_amount, + )); + + AssetManager::deposit(taker_asset, &BOB, taker_amount).unwrap(); + + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(BOB), order_id, None)); + + assert!(Orders::::get(order_id).is_none()); + }); +} + +#[test] +fn fill_order_partially_fills_order() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let order_id = 0u128; + let taker_amount = 25 * BASE; + let maker_amount = 10 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + AssetManager::deposit(taker_asset, &BOB, taker_amount).unwrap(); + + let order = Orders::::get(order_id).unwrap(); + assert_eq!( + order, + Order { market_id, maker: ALICE, maker_asset, maker_amount, taker_asset, taker_amount } + ); + + assert_ok!(Orderbook::fill_order( + RuntimeOrigin::signed(BOB), + order_id, + Some(taker_amount / 2) + )); + + let order = Orders::::get(order_id).unwrap(); + + assert_eq!( + order, + Order { + market_id, + maker: ALICE, + maker_asset, + maker_amount: 5 * BASE, + taker_asset, + taker_amount: 125_000_000_000, + } + ); + }); +} + +#[test] +fn place_order_fails_if_market_base_asset_not_present() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = Asset::CategoricalOutcome(0, 1); + let taker_asset = Asset::CategoricalOutcome(0, 2); + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + 25 * BASE, + ), + Error::::MarketBaseAssetNotPresent + ); + }); +} + +#[test] +fn place_order_fails_if_invalid_outcome_asset() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + assert_eq!(market.market_type, MarketType::Categorical(64u16)); + let maker_asset = Asset::ScalarOutcome(0, ScalarPosition::Long); + let taker_asset = market.base_asset; + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + 25 * BASE, + ), + Error::::InvalidOutcomeAsset + ); + }); +} + +#[test] +fn place_order_fails_if_market_not_found() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + + let maker_asset = Asset::Ztg; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + 25 * BASE, + ), + MError::::MarketDoesNotExist + ); + }); +} + +#[test] +fn place_order_fails_if_maker_has_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker = ALICE; + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + let alice_free_maker_amount = AssetManager::free_balance(maker_asset, &maker); + + AssetManager::withdraw(maker_asset, &ALICE, alice_free_maker_amount).unwrap(); + + assert_noop!( + Orderbook::place_order( + RuntimeOrigin::signed(maker), + market_id, + maker_asset, + 10 * BASE, + taker_asset, + 25 * BASE, + ), + BError::::InsufficientBalance, ); }); } @@ -107,68 +498,85 @@ fn it_places_orders() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); + + let taker_asset_0 = Asset::CategoricalOutcome(0, 2); + + let taker_amount = 10 * BASE; + let maker_amount = 250 * BASE; - // Give some shares for Bob. - assert_ok!(AssetManager::deposit(Asset::CategoricalOutcome(0, 1), &BOB, 10)); + assert_ok!(AssetManager::deposit(market.base_asset, &ALICE, maker_amount)); - // Make an order from Alice to buy shares. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - Asset::CategoricalOutcome(0, 2), - OrderSide::Bid, - 10, - 250, + market.base_asset, + maker_amount, + taker_asset_0, + taker_amount, )); - let reserved_funds = - >::reserved_balance(&ALICE); - assert_eq!(reserved_funds, 250); + let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); + assert_eq!(reserved_funds, maker_amount); + + let maker_asset = Asset::CategoricalOutcome(0, 1); + + let maker_amount = 10 * BASE; + let taker_amount = 5 * BASE; + assert_ok!(AssetManager::deposit(maker_asset, &BOB, maker_amount)); - // Make an order from Bob to sell shares. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, - Asset::CategoricalOutcome(0, 1), - OrderSide::Ask, - 10, - 5, + maker_asset, + maker_amount, + market.base_asset, + taker_amount, )); - let shares_reserved = Tokens::reserved_balance(Asset::CategoricalOutcome(0, 1), &BOB); - assert_eq!(shares_reserved, 10); + let shares_reserved = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(shares_reserved, maker_amount); }); } #[test] -fn it_fills_ask_orders_fully() { +fn it_fills_order_fully_maker_outcome_asset() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); - let outcome_asset = Asset::CategoricalOutcome(0, 1); - // Give some shares for Bob. - assert_ok!(Tokens::deposit(outcome_asset, &BOB, 100)); + let maker_asset = Asset::CategoricalOutcome(0, 1); + let taker_asset = market.base_asset; + + let maker_amount = 100 * BASE; + let taker_amount = 500 * BASE; + assert_ok!(AssetManager::deposit(maker_asset, &BOB, maker_amount)); - // Make an order from Bob to sell shares. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, - outcome_asset, - OrderSide::Ask, - 10, - 50, + maker_asset, + maker_amount, + taker_asset, + taker_amount, )); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); - assert_eq!(reserved_bob, 10); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, maker_amount); + + let market_creator_balance_before = + AssetManager::free_balance(taker_asset, &MARKET_CREATOR); let order_id = 0u128; + assert_ok!(AssetManager::deposit(taker_asset, &ALICE, taker_amount)); assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None)); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + let market_creator_balance_after = AssetManager::free_balance(taker_asset, &MARKET_CREATOR); + let taker_fees = calculate_fee::(taker_amount); + assert_eq!(market_creator_balance_after - market_creator_balance_before, taker_fees); + + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); assert_eq!(reserved_bob, 0); System::assert_last_event( @@ -176,52 +584,56 @@ fn it_fills_ask_orders_fully() { order_id, maker: BOB, taker: ALICE, - filled: 50, - unfilled_outcome_asset_amount: 0, - unfilled_base_asset_amount: 0, + filled_maker_amount: maker_amount, + filled_taker_amount: taker_amount, + unfilled_maker_amount: 0, + unfilled_taker_amount: 0, } .into(), ); - let alice_bal = >::free_balance(&ALICE); - let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); - assert_eq!(alice_bal, BASE - 50); - assert_eq!(alice_shares, 10); + let alice_maker_asset_free = AssetManager::free_balance(taker_asset, &ALICE); + let alice_taker_asset_free = AssetManager::free_balance(maker_asset, &ALICE); + assert_eq!(alice_maker_asset_free, INITIAL_BALANCE); + assert_eq!(alice_taker_asset_free, maker_amount); - let bob_bal = >::free_balance(&BOB); - let bob_shares = Tokens::free_balance(outcome_asset, &BOB); - assert_eq!(bob_bal, BASE + 50); - assert_eq!(bob_shares, 90); + let bob_taker_asset_free = AssetManager::free_balance(market.base_asset, &BOB); + let bob_maker_asset_free = AssetManager::free_balance(maker_asset, &BOB); + assert_eq!(bob_taker_asset_free, INITIAL_BALANCE + taker_amount - taker_fees); + assert_eq!(bob_maker_asset_free, 0); }); } #[test] -fn it_fills_bid_orders_fully() { +fn it_fills_order_fully_maker_base_asset() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 1); - let outcome_asset = Asset::CategoricalOutcome(0, 1); + let taker_amount = 10 * BASE; + let maker_amount = 50 * BASE; - // Make an order from Bob to sell shares. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, - outcome_asset, - OrderSide::Bid, - 10, - 50, + maker_asset, + maker_amount, + taker_asset, + taker_amount, )); - let reserved_bob = Balances::reserved_balance(BOB); - assert_eq!(reserved_bob, 50); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, maker_amount); let order_id = 0u128; - assert_ok!(Tokens::deposit(outcome_asset, &ALICE, 10)); + assert_ok!(AssetManager::deposit(taker_asset, &ALICE, taker_amount)); assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, None)); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); + let reserved_bob = AssetManager::reserved_balance(taker_asset, &BOB); assert_eq!(reserved_bob, 0); System::assert_last_event( @@ -229,245 +641,360 @@ fn it_fills_bid_orders_fully() { order_id, maker: BOB, taker: ALICE, - filled: 10, - unfilled_outcome_asset_amount: 0, - unfilled_base_asset_amount: 0, + filled_maker_amount: maker_amount, + filled_taker_amount: taker_amount, + unfilled_taker_amount: 0, + unfilled_maker_amount: 0, } .into(), ); - let alice_bal = >::free_balance(&ALICE); - let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); - assert_eq!(alice_bal, BASE + 50); - assert_eq!(alice_shares, 0); - - let bob_bal = >::free_balance(&BOB); - let bob_shares = Tokens::free_balance(outcome_asset, &BOB); - assert_eq!(bob_bal, BASE - 50); - assert_eq!(bob_shares, 10); + let alice_maker_asset_free = AssetManager::free_balance(maker_asset, &ALICE); + let alice_taker_asset_free = AssetManager::free_balance(taker_asset, &ALICE); + let maker_fees = calculate_fee::(maker_amount); + let maker_amount_minus_fees = maker_amount - maker_fees; + assert_eq!(alice_maker_asset_free, INITIAL_BALANCE + maker_amount_minus_fees); + assert_eq!(alice_taker_asset_free, 0); + + let bob_bal = AssetManager::free_balance(maker_asset, &BOB); + let bob_shares = AssetManager::free_balance(taker_asset, &BOB); + assert_eq!(bob_bal, INITIAL_BALANCE - maker_amount); + assert_eq!(bob_shares, taker_amount); }); } #[test] -fn it_fills_bid_orders_partially() { +fn it_fills_order_partially_maker_base_asset() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 1); - let outcome_asset = Asset::CategoricalOutcome(0, 1); + let maker_amount = 500 * BASE; + let taker_amount = 100 * BASE; + + assert_ok!(AssetManager::deposit(maker_asset, &BOB, maker_amount)); - // Make an order from Bob to buy outcome tokens. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, - outcome_asset, - OrderSide::Bid, - 1000, - 5000, + maker_asset, + maker_amount, + taker_asset, + taker_amount, )); - let reserved_bob = Balances::reserved_balance(BOB); - assert_eq!(reserved_bob, 5000); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, maker_amount); let order_id = 0u128; - assert_ok!(Tokens::deposit(outcome_asset, &ALICE, 1000)); + assert_ok!(AssetManager::deposit(taker_asset, &ALICE, taker_amount)); + + let market_creator_free_before = AssetManager::free_balance(maker_asset, &MARKET_CREATOR); - // instead of selling 1000 shares, Alice sells 700 shares - let portion = Some(700); + // instead of selling 100 shares, Alice sells 70 shares + let alice_portion = 70 * BASE; + assert!(alice_portion < taker_amount); + let alice_taker_asset_free_left = taker_amount - alice_portion; + let portion = Some(alice_portion); assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, portion,)); - let order = >::get(order_id).unwrap(); + let order = Orders::::get(order_id).unwrap(); + let unfilled_taker_amount = taker_amount - alice_portion; + + let filled_maker_amount = + Perquintill::from_rational(alice_portion, taker_amount).mul_floor(maker_amount); + let unfilled_maker_amount = maker_amount - filled_maker_amount; + assert_eq!( order, Order { market_id, - side: OrderSide::Bid, maker: BOB, - outcome_asset, - base_asset: Asset::Ztg, - // from 1000 to 300 changed (partially filled) - outcome_asset_amount: 300, - base_asset_amount: 1500, + maker_asset, + maker_amount: unfilled_maker_amount, + taker_asset, + taker_amount: unfilled_taker_amount, } ); - let reserved_bob = Balances::reserved_balance(BOB); - // 5000 - (700 shares * 500 price) = 1500 - assert_eq!(reserved_bob, 1500); - System::assert_last_event( Event::::OrderFilled { order_id, maker: BOB, taker: ALICE, - filled: 700, - unfilled_outcome_asset_amount: 300, - unfilled_base_asset_amount: 1500, + filled_maker_amount, + filled_taker_amount: alice_portion, + unfilled_maker_amount, + unfilled_taker_amount, } .into(), ); - let alice_bal = >::free_balance(&ALICE); - let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); - assert_eq!(alice_bal, BASE + 3500); - assert_eq!(alice_shares, 300); - - let bob_bal = >::free_balance(&BOB); - let bob_shares = Tokens::free_balance(outcome_asset, &BOB); - // 3500 of base_asset lost, 1500 of base_asset reserved - assert_eq!(bob_bal, BASE - 5000); - assert_eq!(bob_shares, 700); - - let reserved_bob = Balances::reserved_balance(BOB); - assert_eq!(reserved_bob, 1500); + let market_creator_free_after = AssetManager::free_balance(maker_asset, &MARKET_CREATOR); + let maker_fees = calculate_fee::(filled_maker_amount); + assert_eq!(market_creator_free_after - market_creator_free_before, maker_fees); + + let alice_maker_asset_free = AssetManager::free_balance(maker_asset, &ALICE); + let alice_taker_asset_free = AssetManager::free_balance(taker_asset, &ALICE); + let filled_maker_amount = + Perquintill::from_rational(alice_portion, taker_amount).mul_floor(maker_amount); + let filled_maker_amount_minus_fees = + filled_maker_amount - calculate_fee::(filled_maker_amount); + assert_eq!(alice_maker_asset_free, INITIAL_BALANCE + filled_maker_amount_minus_fees); + assert_eq!(alice_taker_asset_free, alice_taker_asset_free_left); + + let bob_maker_asset_free = AssetManager::free_balance(maker_asset, &BOB); + let bob_taker_asset_free = AssetManager::free_balance(taker_asset, &BOB); + assert_eq!(bob_maker_asset_free, INITIAL_BALANCE); + assert_eq!(bob_taker_asset_free, alice_portion); + + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, unfilled_maker_amount); }); } #[test] -fn it_fills_ask_orders_partially() { +fn it_fills_order_partially_maker_outcome_asset() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let outcome_asset = Asset::CategoricalOutcome(0, 1); + let maker_asset = Asset::CategoricalOutcome(0, 1); + let taker_asset = market.base_asset; + + let maker_amount = 100 * BASE; + let taker_amount = 500 * BASE; - assert_ok!(Tokens::deposit(outcome_asset, &BOB, 2000)); + assert_ok!(AssetManager::deposit(maker_asset, &BOB, maker_amount)); // Make an order from Bob to sell outcome tokens. assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, - outcome_asset, - OrderSide::Ask, - 1000, - 5000, + maker_asset, + maker_amount, + taker_asset, + taker_amount, )); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); - assert_eq!(reserved_bob, 1000); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, maker_amount); let order_id = 0u128; - assert_ok!(Tokens::deposit(market.base_asset, &ALICE, 5000)); + let market_creator_free_balance_before = + AssetManager::free_balance(market.base_asset, &MARKET_CREATOR); - // instead of buying 5000 of the base asset, Alice buys 700 shares - let portion = Some(700); + // instead of buying 500 of the base asset, Alice buys 70 shares + let alice_portion = 70 * BASE; + let portion = Some(alice_portion); assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, portion,)); - let order = >::get(order_id).unwrap(); + let market_creator_free_balance_after = + AssetManager::free_balance(market.base_asset, &MARKET_CREATOR); + assert_eq!( + market_creator_free_balance_after - market_creator_free_balance_before, + calculate_fee::(70 * BASE) + ); + + let order = Orders::::get(order_id).unwrap(); + let filled_maker_amount = 860_000_000_000; assert_eq!( order, Order { market_id, - side: OrderSide::Ask, maker: BOB, - outcome_asset, - base_asset: Asset::Ztg, - // from 1000 to 860 changed (partially filled) - outcome_asset_amount: 860, - // from 5000 to 4300 changed (partially filled) - base_asset_amount: 4300, + maker_asset, + // from 100 to 86 changed (partially filled) minus fees + maker_amount: filled_maker_amount, + taker_asset, + // from 500 to 430 changed (partially filled) + taker_amount: taker_amount - alice_portion, } ); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); - assert_eq!(reserved_bob, 860); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, filled_maker_amount); System::assert_last_event( Event::::OrderFilled { order_id, maker: BOB, taker: ALICE, - filled: 700, - unfilled_outcome_asset_amount: 860, - unfilled_base_asset_amount: 4300, + // this is confusing, it's 140_000_000_000, so the invert of 860_000_000_000, which got filled + filled_maker_amount: maker_amount - filled_maker_amount, + filled_taker_amount: alice_portion, + unfilled_maker_amount: filled_maker_amount, + unfilled_taker_amount: taker_amount - alice_portion, } .into(), ); - let alice_bal = >::free_balance(&ALICE); - let alice_shares = Tokens::free_balance(outcome_asset, &ALICE); - assert_eq!(alice_bal, BASE - 700); - assert_eq!(alice_shares, 140); + let alice_taker_asset_free = AssetManager::free_balance(taker_asset, &ALICE); + let alice_maker_asset_free = AssetManager::free_balance(maker_asset, &ALICE); + assert_eq!(alice_taker_asset_free, INITIAL_BALANCE - alice_portion); + assert_eq!( + alice_maker_asset_free, + Perbill::from_rational(alice_portion, taker_amount).mul_floor(maker_amount) + ); + assert_eq!(alice_maker_asset_free, 140_000_000_000); - let bob_bal = >::free_balance(&BOB); - let bob_shares = Tokens::free_balance(outcome_asset, &BOB); - assert_eq!(bob_bal, BASE + 700); - // ask order was adjusted from 1000 to 860, and bob had 2000 shares at start - assert_eq!(bob_shares, 1000); + let bob_taker_asset_free = AssetManager::free_balance(taker_asset, &BOB); + let bob_maker_asset_free = AssetManager::free_balance(maker_asset, &BOB); + let filled_minus_fees = alice_portion - calculate_fee::(alice_portion); + assert_eq!(bob_taker_asset_free, INITIAL_BALANCE + filled_minus_fees); + assert_eq!(bob_maker_asset_free, 0); - let reserved_bob = Tokens::reserved_balance(outcome_asset, &BOB); - assert_eq!(reserved_bob, 860); + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, filled_maker_amount); }); } #[test] -fn it_removes_orders() { +fn it_removes_order() { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; let market = market_mock::(); - Markets::::insert(market_id, market); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let taker_amount = 25 * BASE; + let maker_amount = 10 * BASE; - // Make an order from Alice to buy shares. - let share_id = Asset::CategoricalOutcome(0, 2); assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - share_id, - OrderSide::Bid, - 25, - 10 + maker_asset, + maker_amount, + taker_asset, + taker_amount, )); - let order_id = 0u128; - System::assert_last_event( - Event::::OrderPlaced { - order_id: 0, - order: Order { - market_id, - side: OrderSide::Bid, - maker: ALICE, - outcome_asset: share_id, - base_asset: Asset::Ztg, - outcome_asset_amount: 25, - base_asset_amount: 10, - }, - } - .into(), - ); + let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); + assert_eq!(reserved_funds, maker_amount); - let order = >::get(order_id).unwrap(); + let order_id = 0u128; + let order = Orders::::get(order_id).unwrap(); assert_eq!( order, - Order { - market_id, - side: OrderSide::Bid, - maker: ALICE, - outcome_asset: share_id, - base_asset: Asset::Ztg, - outcome_asset_amount: 25, - base_asset_amount: 10, - } - ); - - assert_noop!( - Orderbook::remove_order(RuntimeOrigin::signed(BOB), order_id), - Error::::NotOrderCreator, + Order { market_id, maker: ALICE, maker_asset, maker_amount, taker_asset, taker_amount } ); - let reserved_funds = - >::reserved_balance(&ALICE); - assert_eq!(reserved_funds, 10); + let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); + assert_eq!(reserved_funds, maker_amount); assert_ok!(Orderbook::remove_order(RuntimeOrigin::signed(ALICE), order_id)); - let reserved_funds = - >::reserved_balance(&ALICE); + let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); assert_eq!(reserved_funds, 0); - assert!(>::get(order_id).is_none()); + assert!(Orders::::get(order_id).is_none()); + }); +} + +#[test] +fn remove_order_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let taker_amount = 25 * BASE; + let maker_amount = 10 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + let order_id = 0u128; + + assert_ok!(Orderbook::remove_order(RuntimeOrigin::signed(ALICE), order_id)); System::assert_last_event(Event::::OrderRemoved { order_id, maker: ALICE }.into()); }); } + +#[test] +fn remove_order_fails_if_not_order_creator() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let taker_amount = 25 * BASE; + let maker_amount = 10 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(BOB), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + let order_id = 0u128; + + assert_noop!( + Orderbook::remove_order(RuntimeOrigin::signed(ALICE), order_id), + Error::::NotOrderCreator + ); + }); +} + +#[test] +fn place_order_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let market = market_mock::(); + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset; + let taker_asset = Asset::CategoricalOutcome(0, 2); + + let taker_amount = 25 * BASE; + let maker_amount = 10 * BASE; + + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + let order_id = 0u128; + System::assert_last_event( + Event::::OrderPlaced { + order_id, + order: Order { + market_id, + maker: ALICE, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + }, + } + .into(), + ); + }); +} diff --git a/zrml/orderbook/src/types.rs b/zrml/orderbook/src/types.rs index b0e1d2b73..b47673c64 100644 --- a/zrml/orderbook/src/types.rs +++ b/zrml/orderbook/src/types.rs @@ -22,19 +22,12 @@ use zeitgeist_primitives::types::Asset; pub type OrderId = u128; -#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub enum OrderSide { - Bid, - Ask, -} - #[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] pub struct Order { pub market_id: MarketId, - pub side: OrderSide, pub maker: AccountId, - pub outcome_asset: Asset, - pub base_asset: Asset, - pub outcome_asset_amount: Balance, - pub base_asset_amount: Balance, + pub maker_asset: Asset, + pub maker_amount: Balance, + pub taker_asset: Asset, + pub taker_amount: Balance, } diff --git a/zrml/orderbook/src/weights.rs b/zrml/orderbook/src/weights.rs index 78de0818a..5ebda41a3 100644 --- a/zrml/orderbook/src/weights.rs +++ b/zrml/orderbook/src/weights.rs @@ -19,13 +19,13 @@ //! Autogenerated weights for zrml_orderbook //! //! 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: `2023-11-03`, STEPS: `10`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `chralt`, CPU: `` //! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev @@ -49,80 +49,43 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_orderbook (automatically generated) pub trait WeightInfoZeitgeist { - fn remove_order_ask() -> Weight; - fn remove_order_bid() -> Weight; - fn fill_order_ask() -> Weight; - fn fill_order_bid() -> Weight; - fn place_order_ask() -> Weight; - fn place_order_bid() -> Weight; + fn remove_order() -> Weight; + fn fill_order() -> Weight; + fn place_order() -> Weight; } /// Weight functions for zrml_orderbook (automatically generated) pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: Orderbook Orders (r:1 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) - /// Storage: Tokens Reserves (r:1 w:1) - /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - fn remove_order_ask() -> Weight { - // Proof Size summary in bytes: - // Measured: `675` - // Estimated: `8967` - // Minimum execution time: 41_210 nanoseconds. - Weight::from_parts(51_230_000, 8967) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: Orderbook Orders (r:1 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(142), added: 2617, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - fn remove_order_bid() -> Weight { + fn remove_order() -> Weight { // Proof Size summary in bytes: - // Measured: `323` - // Estimated: `6342` - // Minimum execution time: 37_110 nanoseconds. - Weight::from_parts(45_930_000, 6342) + // Measured: `283` + // Estimated: `6341` + // Minimum execution time: 26_000 nanoseconds. + Weight::from_parts(27_000_000, 6341) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: Orderbook Orders (r:1 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) + /// Proof: Orderbook Orders (max_values: None, max_size: Some(142), added: 2617, 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 Reserves (r:1 w:1) - /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:2 w:2) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - fn fill_order_ask() -> Weight { - // Proof Size summary in bytes: - // Measured: `1348` - // Estimated: `17325` - // Minimum execution time: 97_511 nanoseconds. - Weight::from_parts(120_261_000, 17325) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: Orderbook Orders (r:1 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, 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:2 w:2) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - fn fill_order_bid() -> Weight { + /// Storage: Tokens Accounts (r:2 w:2) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + fn fill_order() -> Weight { // Proof Size summary in bytes: - // Measured: `1288` - // Estimated: `17298` - // Minimum execution time: 86_600 nanoseconds. - Weight::from_parts(106_891_000, 17298) + // Measured: `1081` + // Estimated: `17297` + // Minimum execution time: 67_000 nanoseconds. + Weight::from_parts(68_000_000, 17297) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -130,35 +93,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Orderbook NextOrderId (r:1 w:1) /// Proof: Orderbook NextOrderId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: Tokens Reserves (r:1 w:1) - /// Proof: Tokens Reserves (max_values: None, max_size: Some(1276), added: 3751, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:1 w:1) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Orderbook Orders (r:0 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) - fn place_order_ask() -> Weight { - // Proof Size summary in bytes: - // Measured: `664` - // Estimated: `10013` - // Minimum execution time: 55_910 nanoseconds. - Weight::from_parts(69_621_000, 10013) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: Orderbook NextOrderId (r:1 w:1) - /// Proof: Orderbook NextOrderId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: Orderbook Orders (r:0 w:1) - /// Proof: Orderbook Orders (max_values: None, max_size: Some(143), added: 2618, mode: MaxEncodedLen) - fn place_order_bid() -> Weight { + /// Proof: Orderbook Orders (max_values: None, max_size: Some(142), added: 2617, mode: MaxEncodedLen) + fn place_order() -> Weight { // Proof Size summary in bytes: // Measured: `372` // Estimated: `7388` - // Minimum execution time: 44_780 nanoseconds. - Weight::from_parts(55_840_000, 7388) + // Minimum execution time: 33_000 nanoseconds. + Weight::from_parts(34_000_000, 7388) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } From db4a48500b84450fe8ece61defe8a4ebfea04ca9 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Wed, 20 Dec 2023 15:45:33 +0100 Subject: [PATCH 015/104] Implement AMM 2.0 (#1173) * Replace `bmul` and `bdiv` with traited versions * Restructure directories * Replace `saturating_*` from neo-swaps * Fix formatting * Restructure zrml-swaps math * Implement and test `b*` * Fix formatting * Use new math in orderbook-v1 * Replace checked multiplication with new math * Use correct rounding in neo-swaps * Add docs * Update licenses * Remove `fixed` module from `primitives` * Fix formatting * . * Rewrite math functions * Remove training wheels * Fix docs.pdf * Fix quotes * Add tests for buying * Add tests for selling and improve error names * Update docs * Check adjusted amount in for numerical bounds * Remove unused implementations * Adjust docs * Add stress test exploring various scenarios * Add swap fees to stress test * Add underscore separators * Clean up * Benchmark `buy` as function of `asset_count` * Update benchmarks * Clippy fix * Fix benchmark tests * Update benchmarks of zrml-prediction-markets * Fix broken comment * Add comment explaining benchmark spot prices * Use clearer constants for `amount_in` in tests * Update zrml/neo-swaps/src/traits/pool_operations.rs Co-authored-by: Chralt * Fix botched merge * Fix merge * Update benchmarks * Update zrml/neo-swaps/docs/docs.tex Co-authored-by: Harald Heckmann * Apply suggestions from code review Co-authored-by: Harald Heckmann * Rename `Fixed` and clean up * Hotfix exponential function and extend tests * Complete math test suite * Fix broken sentence * Remove unused imports * Extend `ln` test cases * Merge & fix formatting --------- Co-authored-by: Chralt Co-authored-by: Harald Heckmann --- zrml/neo-swaps/docs/docs.pdf | Bin 204370 -> 204580 bytes zrml/neo-swaps/docs/docs.tex | 52 ++- zrml/neo-swaps/src/benchmarking.rs | 55 ++- zrml/neo-swaps/src/consts.rs | 20 +- zrml/neo-swaps/src/lib.rs | 110 +++-- zrml/neo-swaps/src/math.rs | 399 +++++++++++++++---- zrml/neo-swaps/src/tests/buy.rs | 71 ++-- zrml/neo-swaps/src/tests/buy_and_sell.rs | 182 +++++++++ zrml/neo-swaps/src/tests/deploy_pool.rs | 31 +- zrml/neo-swaps/src/tests/mod.rs | 1 + zrml/neo-swaps/src/tests/sell.rs | 122 ++++-- zrml/neo-swaps/src/traits/pool_operations.rs | 20 +- zrml/neo-swaps/src/types/pool.rs | 11 +- zrml/neo-swaps/src/weights.rs | 20 +- zrml/orderbook/src/lib.rs | 2 +- zrml/prediction-markets/src/benchmarks.rs | 23 +- zrml/prediction-markets/src/lib.rs | 8 +- zrml/prediction-markets/src/weights.rs | 68 ++-- 18 files changed, 900 insertions(+), 295 deletions(-) create mode 100644 zrml/neo-swaps/src/tests/buy_and_sell.rs diff --git a/zrml/neo-swaps/docs/docs.pdf b/zrml/neo-swaps/docs/docs.pdf index f98e4c2bfa37a5691c36ecc673d53b7cb94130c1..28af3262da1c74589395e5d53448e5024b3ced25 100644 GIT binary patch delta 100080 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

_xh!8U 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 U1(zuv10M=EIWY<)B}Gq03Pe_=u>b%7 delta 99938 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 XdP{8{m-`+A9||=%GYTaoMNdWwjv}&x diff --git a/zrml/neo-swaps/docs/docs.tex b/zrml/neo-swaps/docs/docs.tex index a3712d394..560e8639a 100644 --- a/zrml/neo-swaps/docs/docs.tex +++ b/zrml/neo-swaps/docs/docs.tex @@ -28,7 +28,7 @@ } \title{zrml-neo-swaps Documentation} -\date{0.4.1 (\today)} +\date{0.4.3 (\today)} \begin{document} @@ -40,8 +40,6 @@ \section{Introduction} Unlike the typical implementation using a cost function (see \cite{chen_vaughan_2010}), this implementation of LMSR is a \emph{constant-function market maker} (CFMM), similar to the classical constant product market maker, which allows us to implement \emph{dynamic liquidity}. In other words, liquidity providers (LPs) can come and go as they please, allowing the market to self-regulate how much price resistance the AMM should provide. -As of v0.4.1, the AMM is only available for markets with two outcomes. This will be mitigated in a future publication. - \section{The Trading Function} We consider a prediction market with $n$ outcomes, denoted by $1, \ldots, n$ for simplicity. Every complete set of outcome tokens is backed a unit of collateral, denotes by \$. The AMM operates on a \emph{liquidity pool} (or just \emph{pool}), which consists of a \emph{reserve} $(r_1, \ldots, r_n)$ of outcome tokens and a \emph{liquidity parameter} $b$. The trading function is defined as @@ -96,7 +94,7 @@ \subsection{Buying} Trading fees are specified as fractional (a fee of $f = .01$ means that $1\%$ are charged) and deducted from the amount of collateral before the complete set operations are executed. In other words, the liquidity providers receive $fx$ dollars (fees are distributed pro rata amongst the liquidity providers) and Alice goes through the entire process described above with $\tilde x = (1-f)x$ in place of $x$. The spot price taking the fees into account is (as expected) \[ - \psi(b, r, f) = (1 - f)^{-1}e^{-r_i/b}. + p_i(b, r, f) = (1 - f)^{-1}e^{-r_i/b}. \] \subsection{Selling} @@ -136,7 +134,7 @@ \subsection{Adding Liquidity} Now let $i$ be so that $r_i = \max_k r_k$. Let $\lambda = x / r_i$ and $\mu = 1 + \lambda$. For each $k$, Alice moves $\lambda r_k$ units of $k$ into the pool and receives $\lambda q$ pool shares. The liquidity parameter changes from $b$ to $b' = \mu b$. Alice's transfers change the reserve from $r$ to $r' = \mu r$. -The new total issuance of pool shares is $\mu q$ and Alice's share of the pool now is $\lambda / \mu$. Note that Alice retains the balance $(x)^n - \lambda r$ of "left-over tokens". +The new total issuance of pool shares is $\mu q$ and Alice's share of the pool now is $\lambda / \mu$. Note that Alice retains the balance $(x)^n - \lambda r$ of ``left-over tokens". \subsection{Withdrawing Liquidity} @@ -152,7 +150,7 @@ \subsection{Fee Distribution} \section{Creating Pools} -Creating a pool is straightforward. The initial odds are defined by adding different amounts of each outcome to the pool. If Alice wants to deposit liquidity worth $x$ units of collateral with initial probability $p$, then she starts off by buying $x$ complete sets. The following algorithm is used to calculate how many units of each outcome go into the pool. Alice retains the other tokens as "left-overs". +Creating a pool is straightforward. The initial odds are defined by adding different amounts of each outcome to the pool. If Alice wants to deposit liquidity worth $x$ units of collateral with initial probability $p$, then she starts off by buying $x$ complete sets. The following algorithm is used to calculate how many units of each outcome go into the pool. Alice retains the other tokens as ``left-overs". Let $b = 1$, and let $r_i = - b \ln p_i$ for all $i$. Now let $y = x / \max_i r_i$. Then $y r_i \leq x$ for all $i$ and there exists $i_0$ so that $y r_{i_0} = x$. Set $\tilde r_i = y r_i$ and $\tilde b = yb$. Then \[ @@ -208,36 +206,46 @@ \section{Numerical Issues} \end{align*} The magnitude of $y(x)$ is the same as $x$, but the exponentials $e^{x/b}$ and $e^{-r_i/b}$ over- or underflow easily. -Let $A = 20$. Python calculates $e^A = 485165195.4097903$ and $e^{-A} = 2.061153622438558 \cdot 10^{-9}$. The fixed crate (see \url{https://crates.io/crates/fixed}) can represent these using \texttt{FixedU128} without considerable loss of precision or risk of over- or underflow. Let $M = e^A$. +What makes matters worse is that the logarithm's derivative approaches infinity near zero, so any rounding errors when calculating the argument of $\ln$ in that area will amplify. -Note that for any number $a$, the following are equivalent: 1) $M^{-1} \leq e^a \leq M$, 2) $M^{-1} \leq e^{-a} \leq M$. Thus, the following restrictions prevent over- and underflows when calculating the exponential expressions: +Let $A = 10$. Python calculates $e^A = 22026.465794806718$ and $e^{-A} = 4.5399929762484854 \cdot 10^{-5}$. The fixed crate (see \url{https://crates.io/crates/fixed}) can represent these using \texttt{FixedU128} without considerable loss of precision or risk of over- or underflow. Let $M = Ab$, $\varepsilon = e^{-A}$ and $c = \frac{1}{10}$. The following restrictions will prevent numerical issues. \begin{itemize} - \item The amount $x$ must satisfy $x \leq Ab$. - \item The price of $i$ must satisfy $p_i(b, r) = e^{-r_i/b} \geq e^{-A}$. + \item If $p_i(b, r) < \varepsilon$ (or, equivalently, $r_i > M$), then selling is not allowed. + \item If $p_i(b, r) \geq \varepsilon$ (or, equivalently, $r_i \leq M$), then the amount $x$ must satisfy $x \leq M$ when calculating $v(x)$ (to avoid overflows). + \item Sells that push the price below $\varepsilon$ are not allowed. + \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} -How "bad" are these restrictions? The first restriction is completely irrelevant: Suppose Alice executes a trade of $y(x)$ units of outcome $i$ for $x = Ab$ dollars, the maximum allowed value. Let $q = 1 - e^{-r_i/b} \in (0, 1)$. Then +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$. + +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*} - \ln(e^A) - \ln(e^A - q) &= \ln\left(\frac{e^A}{e^A - q}\right) \\ - &\leq \ln\left(\frac{e^A}{e^A - 1}\right) \\ - &\approx 2.0611536900435727 \cdot 10^{-9} \\ - &\leq 10^{-10}. + \ln(M) - \ln(M - q) &= \ln\left(\frac{M}{M - q}\right) \\ + &\leq \ln\left(\frac{M}{M - 1}\right) \\ + &\approx 4.540096037046513 \cdot 10^{-5} \\ + &\leq 10^{-4} = \varepsilon. \end{align*} -Let $\varepsilon = 10^{-10}$. Then we have +Then we have \begin{align*} - y(x) &= b\ln(e^A - 1 + e^{-r_i/b}) + r_i - x \\ - &\geq b(\ln(e^A) - \varepsilon) + r_i - x \\ - &= bA - b\varepsilon + r_i - x \\ + y(x) &= b\ln(M - q) + r_i - x \\ + &\geq b(\ln(M) - \varepsilon) + r_i - x \\ + &= M - b\varepsilon + r_i - x \\ &= r_i - b\varepsilon. \end{align*} Thus, Alice receives all funds from the pool except $b \varepsilon$, which is very small unless the pool contains an inordinate amount of liquidity. -The second restriction means that no trades of outcome $i$ can be executed if the price of $i$ drops below the threshold $\varepsilon = e^{-A}$. On markets with two outcomes (binary or scalar), this is equivalent to the price of the other outcome rising above $1 - \varepsilon$. Due to risk considerations, these are generally scenarios that won't occur. +The restriction that sells can't push prices below $\varepsilon$ is liable to cause some confusion for the users. Nevertheless, it's unclear if this causes them any real inconvenience. + +Despite the rule that sells can't push prices below $\varepsilon$, prices can be moved below $\varepsilon$. The issue in a market with three or more outcomes $A, B, C, \ldots$ is that if $C$ is a clear underdog and most of the trading happens between the favorites $A$ and $B$, then the price of $C$ might drop below the allowed threshold. This is what the last rule handles. -For markets with two outcomes (binary or scalar), we therefore make the following restriction: \emph{Any trade that moves the price of an outcome below $\varepsilon = .005$ (or equivalently, moves the price of an outcome above $1 - \varepsilon$) is not allowed.} This will ensure that the pool is always in a valid state where it can execute trades. Note that in the presence of a swap fee of 1\%, this isn't even a restriction. +The last rule is made to ensure that even if the price is very small (below $\varepsilon$) or even underflows (both of which \emph{shouldn't} ever happen thanks to trading fees), buying the outcome is still possible, allowing users to push the price back up again. The restriction is that there's essentially a minimum amount. In fact, in the pessimistic case (price underflows to zero), the restriction becomes + +$$ +x \geq b \ln(c + 1) \approx bc = \frac{b}{10}. +$$ -Markets with more than two outcomes are currently not allowed to use AMM 2.0 pools. The issue in a market with three or more outcomes $A, B, C, \ldots$ is that if $C$ is a clear underdog and most of the trading happens between the favorites $A$ and $B$, then the price of $C$ might drop below the allowed threshold and \emph{brick} the market of $C$ (all trades involving $C$ must be rejected due to numerical issues). While this is most likely to happen if the market is $C$-weakly trivialized (it is common knowledge that $C$ will almost certainly not materialize), which should never happen on a live market, this is unfortunate. A solution for this issue is provided in the near future. +That's certainly a tall order if the market is very liquid. If this continues to be a problem, we can test how much smaller $c$ can be made without causing trouble. \newpage diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index b6e8c8582..f4612de55 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -20,7 +20,7 @@ use super::*; use crate::{ consts::*, traits::liquidity_shares_manager::LiquiditySharesManager, AssetOf, BalanceOf, - MarketIdOf, Pallet as NeoSwaps, Pools, + MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; use frame_benchmarking::v2::*; use frame_support::{ @@ -32,6 +32,7 @@ use orml_traits::MultiCurrency; use sp_runtime::{Perbill, SaturatedConversion}; use zeitgeist_primitives::{ constants::CENT, + math::fixed::{BaseProvider, ZeitgeistBase}, traits::CompleteSetOperationsApi, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; @@ -73,6 +74,15 @@ fn create_market( maybe_market_id.unwrap() } +fn create_spot_prices(asset_count: u16) -> Vec> { + let mut result = vec![MIN_SPOT_PRICE.saturated_into(); (asset_count - 1) as usize]; + // Price distribution has no bearing on the benchmarks. + let remaining_u128 = + ZeitgeistBase::::get().unwrap() - (asset_count - 1) as u128 * MIN_SPOT_PRICE; + result.push(remaining_u128.saturated_into()); + result +} + fn create_market_and_deploy_pool( caller: T::AccountId, base_asset: AssetOf, @@ -91,7 +101,7 @@ fn create_market_and_deploy_pool( RawOrigin::Signed(caller).into(), market_id, amount, - vec![_1_2.saturated_into(), _1_2.saturated_into()], + create_spot_prices::(asset_count), CENT.saturated_into(), )); market_id @@ -101,11 +111,12 @@ fn create_market_and_deploy_pool( mod benchmarks { use super::*; + /// FIXME Replace hardcoded variant with `{ MAX_ASSETS as u32 }` as soon as possible. #[benchmark] - fn buy() { + fn buy(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; - let asset_count = 2u16; + let asset_count = n.try_into().unwrap(); let market_id = create_market_and_deploy_pool::( alice, base_asset, @@ -124,12 +135,17 @@ mod benchmarks { } #[benchmark] - fn sell() { + fn sell(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; - let market_id = - create_market_and_deploy_pool::(alice, base_asset, 2u16, _10.saturated_into()); - let asset_in = Asset::CategoricalOutcome(market_id, 0); + let asset_count = n.try_into().unwrap(); + let market_id = create_market_and_deploy_pool::( + alice, + base_asset, + asset_count, + _10.saturated_into(), + ); + let asset_in = Asset::CategoricalOutcome(market_id, asset_count - 1); let amount_in = _1.saturated_into(); let min_amount_out = 0u8.saturated_into(); @@ -137,21 +153,22 @@ mod benchmarks { assert_ok!(T::MultiCurrency::deposit(asset_in, &bob, amount_in)); #[extrinsic_call] - _(RawOrigin::Signed(bob), market_id, 2, asset_in, amount_in, min_amount_out); + _(RawOrigin::Signed(bob), market_id, asset_count, asset_in, amount_in, min_amount_out); } #[benchmark] - fn join() { + fn join(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; + let asset_count = n.try_into().unwrap(); let market_id = create_market_and_deploy_pool::( alice.clone(), base_asset, - 2u16, + asset_count, _10.saturated_into(), ); let pool_shares_amount = _1.saturated_into(); - let max_amounts_in = vec![u128::MAX.saturated_into(), u128::MAX.saturated_into()]; + let max_amounts_in = vec![u128::MAX.saturated_into(); asset_count as usize]; assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, pool_shares_amount)); assert_ok_with_transaction!(T::CompleteSetOperations::buy_complete_set( @@ -167,17 +184,18 @@ mod benchmarks { // There are two execution paths in `exit`: 1) Keep pool alive or 2) destroy it. Clearly 1) is // heavier. #[benchmark] - fn exit() { + fn exit(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; + let asset_count = n.try_into().unwrap(); let market_id = create_market_and_deploy_pool::( alice.clone(), base_asset, - 2u16, + asset_count, _10.saturated_into(), ); let pool_shares_amount = _1.saturated_into(); - let min_amounts_out = vec![0u8.saturated_into(), 0u8.saturated_into()]; + let min_amounts_out = vec![0u8.saturated_into(); asset_count as usize]; #[extrinsic_call] _(RawOrigin::Signed(alice), market_id, pool_shares_amount, min_amounts_out); @@ -208,10 +226,11 @@ mod benchmarks { } #[benchmark] - fn deploy_pool() { + fn deploy_pool(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; - let market_id = create_market::(alice.clone(), base_asset, 2); + let asset_count = n.try_into().unwrap(); + let market_id = create_market::(alice.clone(), base_asset, asset_count); let amount = _10.saturated_into(); let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); @@ -227,7 +246,7 @@ mod benchmarks { RawOrigin::Signed(alice), market_id, amount, - vec![_1_2.saturated_into(), _1_2.saturated_into()], + create_spot_prices::(asset_count), CENT.saturated_into(), ); } diff --git a/zrml/neo-swaps/src/consts.rs b/zrml/neo-swaps/src/consts.rs index cab971b4b..0ba4c0cc9 100644 --- a/zrml/neo-swaps/src/consts.rs +++ b/zrml/neo-swaps/src/consts.rs @@ -17,7 +17,11 @@ use zeitgeist_primitives::constants::BASE; -pub(crate) const EXP_NUMERICAL_LIMIT: u128 = 20; // Numerical limit for exp arguments. +/// Numerical limit for absolute value of exp arguments (not a fixed point number). +pub(crate) const EXP_NUMERICAL_LIMIT: u128 = 10; +/// Numerical lower limit for ln arguments (fixed point number). +pub(crate) const LN_NUMERICAL_LIMIT: u128 = BASE / 10; +/// The maximum number of assets allowed in a pool. pub(crate) const MAX_ASSETS: u16 = 128; pub(crate) const _1: u128 = BASE; @@ -25,13 +29,22 @@ pub(crate) const _2: u128 = 2 * _1; pub(crate) const _3: u128 = 3 * _1; pub(crate) const _4: u128 = 4 * _1; pub(crate) const _5: u128 = 5 * _1; +pub(crate) const _7: u128 = 7 * _1; +pub(crate) const _8: u128 = 8 * _1; pub(crate) const _9: u128 = 9 * _1; pub(crate) const _10: u128 = 10 * _1; +pub(crate) const _11: u128 = 11 * _1; +pub(crate) const _17: u128 = 17 * _1; pub(crate) const _20: u128 = 20 * _1; +pub(crate) const _30: u128 = 30 * _1; pub(crate) const _70: u128 = 70 * _1; pub(crate) const _80: u128 = 80 * _1; pub(crate) const _100: u128 = 100 * _1; pub(crate) const _101: u128 = 101 * _1; +pub(crate) const _444: u128 = 444 * _1; +pub(crate) const _500: u128 = 500 * _1; +pub(crate) const _777: u128 = 777 * _1; +pub(crate) const _1000: u128 = 1_000 * _1; pub(crate) const _1_2: u128 = _1 / 2; @@ -45,3 +58,8 @@ pub(crate) const _1_5: u128 = _1 / 5; pub(crate) const _1_6: u128 = _1 / 6; pub(crate) const _5_6: u128 = _5 / 6; + +pub(crate) const _1_10: u128 = _1 / 10; +pub(crate) const _2_10: u128 = _2 / 10; +pub(crate) const _3_10: u128 = _3 / 10; +pub(crate) const _4_10: u128 = _4 / 10; diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 016190399..af316fb33 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -34,7 +34,7 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{ - consts::MAX_ASSETS, + consts::{LN_NUMERICAL_LIMIT, MAX_ASSETS}, math::{Math, MathOps}, traits::{pool_operations::PoolOperations, LiquiditySharesManager}, types::{FeeDistribution, Pool, SoloLp}, @@ -48,10 +48,12 @@ mod pallet { pallet_prelude::StorageMap, require_transactional, traits::{Get, IsType, StorageVersion}, - transactional, PalletId, Twox64Concat, + transactional, PalletError, PalletId, RuntimeDebug, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use orml_traits::MultiCurrency; + use parity_scale_codec::{Decode, Encode}; + use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, CheckedSub, Saturating, Zero}, DispatchError, DispatchResult, SaturatedConversion, @@ -68,10 +70,15 @@ mod pallet { use zrml_market_commons::MarketCommonsPalletApi; // These should not be config parameters to avoid misconfigurations. - pub(crate) const MIN_SWAP_FEE: u128 = BASE / 1_000; // 0.1%. pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + /// The minimum allowed swap fee. Hardcoded to avoid misconfigurations which may lead to + /// exploits. + pub(crate) const MIN_SWAP_FEE: u128 = BASE / 1_000; // 0.1%. + /// The maximum allowed spot price when creating a pool. pub(crate) const MAX_SPOT_PRICE: u128 = BASE - CENT / 2; + /// The minimum allowed spot price when creating a pool. pub(crate) const MIN_SPOT_PRICE: u128 = CENT / 2; + /// The minimum vallowed value of a pool's liquidity parameter. pub(crate) const MIN_LIQUIDITY: u128 = BASE; pub(crate) type AccountIdOf = ::AccountId; @@ -216,8 +223,6 @@ mod pallet { InvalidTradingMechanism, /// Pool can only be traded on if the market is active. MarketNotActive, - /// Deploying pools is only supported for scalar or binary markets. - MarketNotBinaryOrScalar, /// Some calculation failed. This shouldn't happen. MathError, /// The user is not allowed to execute this command. @@ -225,7 +230,7 @@ mod pallet { /// This feature is not yet implemented. NotImplemented, /// Some value in the operation is too large or small. - NumericalLimits, + NumericalLimits(NumericalLimitsError), /// Outstanding fees prevent liquidity withdrawal. OutstandingFees, /// Specified market does not have a pool. @@ -244,6 +249,18 @@ mod pallet { ZeroAmount, } + #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] + pub enum NumericalLimitsError { + /// Selling is not allowed at prices this low. + SpotPriceTooLow, + /// Sells which move the price below this threshold are not allowed. + SpotPriceSlippedTooLow, + /// The maximum buy or sell amount was exceeded. + MaxAmountExceeded, + /// The minimum buy or sell amount was exceeded. + MinAmountNotMet, + } + #[pallet::call] impl Pallet { /// Buy outcome tokens from the specified market. @@ -252,6 +269,14 @@ mod pallet { /// tokens received is smaller than `min_amount_out`. The user must correctly specify the /// number of outcomes for benchmarking reasons. /// + /// The `amount_in` parameter must also satisfy lower and upper limits due to numerical + /// constraints. In fact, after `amount_in` has been adjusted for fees, the following must + /// hold: + /// + /// - `amount_in_minus_fees <= EXP_NUMERICAL_LIMIT * pool.liquidity_parameter`. + /// - `exp(amount_in_minus_fees/pool.liquidity_parameter) - 1 + p <= LN_NUMERICAL_LIMIT`, + /// where `p` is the spot price of `asset_out`. + /// /// # Parameters /// /// - `origin`: The origin account making the purchase. @@ -266,7 +291,7 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::buy())] + #[pallet::weight(T::WeightInfo::buy(*asset_count as u32))] #[transactional] pub fn buy( origin: OriginFor, @@ -280,7 +305,7 @@ mod pallet { let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); Self::do_buy(who, market_id, asset_out, amount_in, min_amount_out)?; - Ok(Some(T::WeightInfo::buy()).into()) + Ok(Some(T::WeightInfo::buy(asset_count as u32)).into()) } /// Sell outcome tokens to the specified market. @@ -289,6 +314,13 @@ mod pallet { /// tokens received is smaller than `min_amount_out`. The user must correctly specify the /// number of outcomes for benchmarking reasons. /// + /// The `amount_in` parameter must also satisfy lower and upper limits due to numerical + /// constraints. In fact, the following must hold: + /// + /// - `amount_in <= EXP_NUMERICAL_LIMIT * pool.liquidity_parameter`. + /// - The spot price of `asset_in` is greater than `exp(-EXP_NUMERICAL_LIMIT)` before and + /// after execution + /// /// # Parameters /// /// - `origin`: The origin account making the sale. @@ -303,7 +335,7 @@ mod pallet { /// Depends on the implementation of `CompleteSetOperationsApi` and `ExternalFees`; when /// using the canonical implementations, the runtime complexity is `O(asset_count)`. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::sell())] + #[pallet::weight(T::WeightInfo::sell(*asset_count as u32))] #[transactional] pub fn sell( origin: OriginFor, @@ -317,7 +349,7 @@ mod pallet { let asset_count_real = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(asset_count == asset_count_real, Error::::IncorrectAssetCount); Self::do_sell(who, market_id, asset_in, amount_in, min_amount_out)?; - Ok(Some(T::WeightInfo::sell()).into()) + Ok(Some(T::WeightInfo::sell(asset_count as u32)).into()) } /// Join the liquidity pool for the specified market. @@ -341,7 +373,7 @@ mod pallet { /// /// `O(n)` where `n` is the number of assets in the pool. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::join())] + #[pallet::weight(T::WeightInfo::join(max_amounts_in.len() as u32))] #[transactional] pub fn join( origin: OriginFor, @@ -353,7 +385,7 @@ mod pallet { let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(max_amounts_in.len() == asset_count as usize, Error::::IncorrectVecLen); Self::do_join(who, market_id, pool_shares_amount, max_amounts_in)?; - Ok(Some(T::WeightInfo::join()).into()) + Ok(Some(T::WeightInfo::join(asset_count as u32)).into()) } /// Exit the liquidity pool for the specified market. @@ -385,7 +417,7 @@ mod pallet { /// /// `O(n)` where `n` is the number of assets in the pool. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::exit())] + #[pallet::weight(T::WeightInfo::exit(min_amounts_out.len() as u32))] #[transactional] pub fn exit( origin: OriginFor, @@ -397,7 +429,7 @@ mod pallet { let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(min_amounts_out.len() == asset_count as usize, Error::::IncorrectVecLen); Self::do_exit(who, market_id, pool_shares_amount_out, min_amounts_out)?; - Ok(Some(T::WeightInfo::exit()).into()) + Ok(Some(T::WeightInfo::exit(asset_count as u32)).into()) } /// Withdraw swap fees from the specified market. @@ -449,7 +481,7 @@ mod pallet { /// /// `O(n)` where `n` is the number of outcomes in the specified market. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::deploy_pool())] + #[pallet::weight(T::WeightInfo::deploy_pool(spot_prices.len() as u32))] #[transactional] pub fn deploy_pool( origin: OriginFor, @@ -459,10 +491,10 @@ mod pallet { #[pallet::compact] swap_fee: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let asset_count = T::MarketCommons::market(&market_id)?.outcomes() as u32; + let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(spot_prices.len() == asset_count as usize, Error::::IncorrectVecLen); Self::do_deploy_pool(who, market_id, amount, spot_prices, swap_fee)?; - Ok(Some(T::WeightInfo::deploy_pool()).into()) + Ok(Some(T::WeightInfo::deploy_pool(asset_count as u32)).into()) } } @@ -480,18 +512,21 @@ mod pallet { ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { ensure!(pool.contains(&asset_out), Error::::AssetNotFound); - // Defensive check (shouldn't ever happen)! - ensure!( - pool.calculate_spot_price(asset_out)? <= MAX_SPOT_PRICE.saturated_into(), - Error::::Unexpected - ); - ensure!(amount_in <= pool.calculate_max_amount_in(), Error::::NumericalLimits); T::MultiCurrency::transfer(pool.collateral, &who, &pool.account_id, amount_in)?; let FeeDistribution { remaining: amount_in_minus_fees, swap_fees: swap_fee_amount, external_fees: external_fee_amount, } = Self::distribute_fees(market_id, pool, amount_in)?; + ensure!( + amount_in_minus_fees <= pool.calculate_numerical_threshold(), + Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), + ); + ensure!( + pool.calculate_buy_ln_argument(asset_out, amount_in_minus_fees)? + >= LN_NUMERICAL_LIMIT.saturated_into(), + Error::::NumericalLimits(NumericalLimitsError::MinAmountNotMet), + ); let swap_amount_out = pool.calculate_swap_amount_out_for_buy(asset_out, amount_in_minus_fees)?; let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; @@ -511,11 +546,6 @@ mod pallet { pool.decrease_reserve(asset, &amount_out)?; } } - let new_price = pool.calculate_spot_price(asset_out)?; - ensure!( - new_price <= MAX_SPOT_PRICE.saturated_into(), - Error::::SpotPriceAboveMax - ); Self::deposit_event(Event::::BuyExecuted { who: who.clone(), market_id, @@ -542,12 +572,16 @@ mod pallet { ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); Self::try_mutate_pool(&market_id, |pool| { ensure!(pool.contains(&asset_in), Error::::AssetNotFound); - // Defensive check (shouldn't ever happen)! + // Ensure that the price of `asset_in` is at least `exp(-EXP_NUMERICAL_LIMITS) = + // 4.5399...e-05`. ensure!( - pool.calculate_spot_price(asset_in)? >= MIN_SPOT_PRICE.saturated_into(), - Error::::Unexpected + pool.reserve_of(&asset_in)? <= pool.calculate_numerical_threshold(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceTooLow), + ); + ensure!( + amount_in <= pool.calculate_numerical_threshold(), + Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), ); - ensure!(amount_in <= pool.calculate_max_amount_in(), Error::::NumericalLimits); // Instead of first executing a swap with `(n-1)` transfers from the pool account to // `who` and then selling complete sets, we prevent `(n-1)` storage reads: 1) // Transfer `amount_in` units of `asset_in` to the pool account, 2) sell @@ -555,7 +589,7 @@ mod pallet { // `amount_out_minus_fees` units of collateral to `who`. The fees automatically end // up in the pool. let amount_out = pool.calculate_swap_amount_out_for_sell(asset_in, amount_in)?; - // Beware! This transfer happen _after_ calculating `amount_out`: + // Beware! This transfer **must** happen _after_ calculating `amount_out`: T::MultiCurrency::transfer(asset_in, &who, &pool.account_id, amount_in)?; T::CompleteSetOperations::sell_complete_set( pool.account_id.clone(), @@ -580,10 +614,11 @@ mod pallet { } pool.decrease_reserve(asset, &amount_out)?; } - let new_price = pool.calculate_spot_price(asset_in)?; + // Ensure that the sell doesn't move the price below the minimum defined by + // `EXP_NUMERICAL_LIMITS` (see comment above). ensure!( - new_price >= MIN_SPOT_PRICE.saturated_into(), - Error::::SpotPriceBelowMin + pool.reserve_of(&asset_in)? <= pool.calculate_numerical_threshold(), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), ); Self::deposit_event(Event::::SellExecuted { who: who.clone(), @@ -729,7 +764,6 @@ mod pallet { ensure!(market.scoring_rule == ScoringRule::Lmsr, Error::::InvalidTradingMechanism); let asset_count = spot_prices.len(); ensure!(asset_count as u16 == market.outcomes(), Error::::IncorrectVecLen); - ensure!(market.outcomes() == 2, Error::::MarketNotBinaryOrScalar); ensure!(market.outcomes() <= MAX_ASSETS, Error::::AssetCountAboveMax); ensure!(swap_fee >= MIN_SWAP_FEE.saturated_into(), Error::::SwapFeeBelowMin); ensure!(swap_fee <= T::MaxSwapFee::get(), Error::::SwapFeeAboveMax); @@ -844,7 +878,7 @@ mod pallet { }) } - fn try_mutate_pool(market_id: &MarketIdOf, mutator: F) -> DispatchResult + pub(crate) fn try_mutate_pool(market_id: &MarketIdOf, mutator: F) -> DispatchResult where F: FnMut(&mut PoolOf) -> DispatchResult, { diff --git a/zrml/neo-swaps/src/math.rs b/zrml/neo-swaps/src/math.rs index f0978deb6..7e6a7d2db 100644 --- a/zrml/neo-swaps/src/math.rs +++ b/zrml/neo-swaps/src/math.rs @@ -14,17 +14,45 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +// +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright (c) 2019 Alain Brenzikofer, modified by GalacticCouncil(2021) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Original source: https://github.com/encointer/substrate-fixed +// +// The changes applied are: 1) Used same design for definition of `exp` +// as in the source. 2) Re-used and extended tests for `exp` and other +// functions. -use crate::{BalanceOf, Config, Error}; +use crate::{ + math::transcendental::{exp, ln}, + BalanceOf, Config, Error, +}; use alloc::vec::Vec; use core::marker::PhantomData; use fixed::FixedU128; -use hydra_dx_math::transcendental::{exp, ln}; use sp_runtime::{DispatchError, SaturatedConversion}; use typenum::U80; type Fractional = U80; -type Fixed = FixedU128; +type FixedType = FixedU128; + +// 32.44892769177272 +const EXP_OVERFLOW_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) trait MathOps { fn calculate_swap_amount_out_for_buy( @@ -32,19 +60,28 @@ pub(crate) trait MathOps { amount_in: BalanceOf, liquidity: BalanceOf, ) -> Result, DispatchError>; + fn calculate_swap_amount_out_for_sell( reserve: BalanceOf, amount_in: BalanceOf, liquidity: BalanceOf, ) -> Result, DispatchError>; + fn calculate_spot_price( reserve: BalanceOf, liquidity: BalanceOf, ) -> Result, DispatchError>; + fn calculate_reserves_from_spot_prices( amount: BalanceOf, spot_prices: Vec>, ) -> Result<(BalanceOf, Vec>), DispatchError>; + + fn calculate_buy_ln_argument( + reserve: BalanceOf, + amount: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError>; } pub(crate) struct Math(PhantomData); @@ -100,6 +137,19 @@ impl MathOps for Math { let spot_prices = spot_prices.into_iter().map(|p| p.saturated_into()).collect(); Ok((liquidity, spot_prices)) } + + fn calculate_buy_ln_argument( + reserve: BalanceOf, + amount_in: BalanceOf, + liquidity: BalanceOf, + ) -> Result, DispatchError> { + let reserve = reserve.saturated_into(); + let amount_in = amount_in.saturated_into(); + let liquidity = liquidity.saturated_into(); + detail::calculate_buy_ln_argument(reserve, amount_in, liquidity) + .map(|result| result.saturated_into()) + .ok_or_else(|| Error::::MathError.into()) + } } mod detail { @@ -109,7 +159,7 @@ mod detail { math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, }; - /// Calculate b * ln( e^(x/b) − 1 + e^(−r_i/b) ) + r_i − x + /// Calculate b * ln( e^(x/b) − 1 + e^(−r_i/b) ) + r_i − x. pub(super) fn calculate_swap_amount_out_for_buy( reserve: u128, amount_in: u128, @@ -123,7 +173,7 @@ mod detail { from_fixed(result_fixed) } - /// Calculate –1 * b * ln( e^(-x/b) − 1 + e^(r_i/b) ) + r_i + /// Calculate –1 * b * ln( e^(-x/b) − 1 + e^(r_i/b) ) + r_i. pub(super) fn calculate_swap_amount_out_for_sell( reserve: u128, amount_in: u128, @@ -156,14 +206,28 @@ mod detail { Some((liquidity, reserve)) } - fn to_fixed(value: B) -> Option + /// Calculate e^(x/b) − 1 + e^(−r_i/b). + pub(super) fn calculate_buy_ln_argument( + reserve: u128, + amount_in: u128, + liquidity: u128, + ) -> Option { + let result_fixed = calculate_buy_ln_argument_fixed( + to_fixed(reserve)?, + to_fixed(amount_in)?, + to_fixed(liquidity)?, + )?; + from_fixed(result_fixed) + } + + fn to_fixed(value: B) -> Option where B: Into + From, { value.to_fixed_from_fixed_decimal(DECIMALS).ok() } - fn from_fixed(value: Fixed) -> Option + fn from_fixed(value: FixedType) -> Option where B: Into + From, { @@ -171,16 +235,11 @@ mod detail { } fn calculate_swap_amount_out_for_buy_fixed( - reserve: Fixed, - amount_in: Fixed, - liquidity: Fixed, - ) -> Option { - // FIXME Defensive programming: Check for underflow in x/b and r_i/b. - let exp_x_over_b: Fixed = exp(amount_in.checked_div(liquidity)?, false).ok()?; - let exp_neg_r_over_b = exp(reserve.checked_div(liquidity)?, true).ok()?; - // FIXME Defensive programming: Check for underflow in the exponential expressions. - let inside_ln = - exp_x_over_b.checked_add(exp_neg_r_over_b)?.checked_sub(Fixed::checked_from_num(1)?)?; + reserve: FixedType, + amount_in: FixedType, + liquidity: FixedType, + ) -> Option { + let inside_ln = calculate_buy_ln_argument_fixed(reserve, amount_in, liquidity)?; let (ln_result, ln_neg) = ln(inside_ln).ok()?; let blob = liquidity.checked_mul(ln_result)?; let reserve_plus_blob = @@ -189,30 +248,35 @@ mod detail { } fn calculate_swap_amount_out_for_sell_fixed( - reserve: Fixed, - amount_in: Fixed, - liquidity: Fixed, - ) -> Option { - // FIXME Defensive programming: Check for underflow in x/b and r_i/b. - let exp_neg_x_over_b: Fixed = exp(amount_in.checked_div(liquidity)?, true).ok()?; + reserve: FixedType, + amount_in: FixedType, + liquidity: FixedType, + ) -> Option { + if reserve.is_zero() { + // Ensure that if the reserve is zero, we don't accidentally return a non-zero value. + return None; + } + let exp_neg_x_over_b: FixedType = exp(amount_in.checked_div(liquidity)?, true).ok()?; let exp_r_over_b = exp(reserve.checked_div(liquidity)?, false).ok()?; - // FIXME Defensive programming: Check for underflow in the exponential expressions. - let inside_ln = - exp_neg_x_over_b.checked_add(exp_r_over_b)?.checked_sub(Fixed::checked_from_num(1)?)?; + let inside_ln = exp_neg_x_over_b + .checked_add(exp_r_over_b)? + .checked_sub(FixedType::checked_from_num(1)?)?; let (ln_result, ln_neg) = ln(inside_ln).ok()?; let blob = liquidity.checked_mul(ln_result)?; if ln_neg { reserve.checked_add(blob) } else { reserve.checked_sub(blob) } } - pub(crate) fn calculate_spot_price_fixed(reserve: Fixed, liquidity: Fixed) -> Option { + pub(crate) fn calculate_spot_price_fixed( + reserve: FixedType, + liquidity: FixedType, + ) -> Option { exp(reserve.checked_div(liquidity)?, true).ok() } fn calculate_reserve_from_spot_prices_fixed( - amount: Fixed, - spot_prices: Vec, - ) -> Option<(Fixed, Vec)> { - // FIXME Defensive programming - ensure against underflows + amount: FixedType, + spot_prices: Vec, + ) -> Option<(FixedType, Vec)> { let tmp_reserves = spot_prices .iter() // Drop the bool (second tuple component) as ln(p) is always negative. @@ -221,62 +285,251 @@ mod detail { .ok()?; let max_value = *tmp_reserves.iter().max()?; let liquidity = amount.checked_div(max_value)?; - let reserves: Vec = + let reserves: Vec = tmp_reserves.iter().map(|&r| r.checked_mul(liquidity)).collect::>>()?; Some((liquidity, reserves)) } + /// Calculate e^(x/b) − 1 + e^(−r_i/b). + pub(super) fn calculate_buy_ln_argument_fixed( + reserve: FixedType, + amount_in: FixedType, + liquidity: FixedType, + ) -> Option { + let exp_x_over_b: FixedType = exp(amount_in.checked_div(liquidity)?, false).ok()?; + let r_over_b = reserve.checked_div(liquidity)?; + let exp_neg_r_over_b = if r_over_b < EXP_OVERFLOW_THRESHOLD { + exp(reserve.checked_div(liquidity)?, true).ok()? + } else { + FixedType::checked_from_num(0)? // Underflow to zero. + }; + exp_x_over_b.checked_add(exp_neg_r_over_b)?.checked_sub(FixedType::checked_from_num(1)?) + } +} + +mod transcendental { + use fixed::traits::FixedUnsigned; + pub(crate) use hydra_dx_math::transcendental::{exp as inner_exp, ln}; + use sp_runtime::traits::One; + + pub(crate) fn exp(operand: S, neg: bool) -> Result + where + S: FixedUnsigned + PartialOrd + One, + D: FixedUnsigned + PartialOrd + From + One, + { + if operand == S::one() && neg { + let e_inverse = + S::from_str("0.367879441171442321595523770161460867445").map_err(|_| ())?; + return Ok(D::from(e_inverse)); + } + inner_exp(operand, neg) + } + #[cfg(test)] mod tests { use super::*; - use crate::{assert_approx, consts::*}; - use std::str::FromStr; + use alloc::str::FromStr; + use fixed::types::U64F64; use test_case::test_case; - // Example taken from - // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr - #[test] - fn calculate_swap_amount_out_for_buy_works() { - let liquidity = 144269504088; - assert_eq!( - calculate_swap_amount_out_for_buy(_10, _10, liquidity).unwrap(), - 58496250072 - ); - } + type S = U64F64; + type D = U64F64; - #[test] - fn calculate_swap_amount_out_for_sell_works() { - let liquidity = 144269504088; - assert_eq!( - calculate_swap_amount_out_for_sell(_10, _10, liquidity).unwrap(), - 41503749928 - ); + #[test_case("0", false, "1")] + #[test_case("0", true, "1")] + #[test_case("1", false, "2.718281828459045235360287471352662497757")] + #[test_case("1", true, "0.367879441171442321595523770161460867445")] + #[test_case("2", false, "7.3890560989306502265")] + #[test_case("2", true, "0.13533528323661269186")] + #[test_case("0.1", false, "1.1051709180756476246")] + #[test_case("0.1", true, "0.9048374180359595733")] + #[test_case("0.9", false, "2.4596031111569496633")] + #[test_case("0.9", true, "0.40656965974059911195")] + #[test_case("1.5", false, "4.481689070338064822")] + #[test_case("1.5", true, "0.22313016014842982894")] + #[test_case("3.3", false, "27.1126389206578874259")] + #[test_case("3.3", true, "0.03688316740124000543")] + #[test_case("7.3456", false, "1549.3643050275008503592")] + #[test_case("7.3456", true, "0.00064542599616831253")] + #[test_case("12.3456789", false, "229964.194569082134542849")] + #[test_case("12.3456789", true, "0.00000434850304358833")] + #[test_case("13", false, "442413.39200892050332603603")] + #[test_case("13", true, "0.0000022603294069810542")] + fn exp_works(operand: &str, neg: bool, expected: &str) { + let o = U64F64::from_str(operand).unwrap(); + let e = U64F64::from_str(expected).unwrap(); + assert_eq!(exp::(o, neg).unwrap(), e); } - #[test] - fn calcuate_spot_price_works() { - let liquidity = 144269504088; - assert_eq!(calculate_spot_price(_10, liquidity).unwrap(), _1_2); - assert_eq!(calculate_spot_price(_10 - 58496250072, liquidity).unwrap(), _3_4); - assert_eq!(calculate_spot_price(_20, liquidity).unwrap(), _1_4); + #[test_case("1", "0", false)] + #[test_case("2", "0.69314718055994530943", false)] + #[test_case("3", "1.09861228866810969136", false)] + #[test_case("2.718281828459045235360287471352662497757", "1", false)] + #[test_case("1.1051709180756476246", "0.09999999999999999975", false)] + #[test_case("2.4596031111569496633", "0.89999999999999999976", false)] + #[test_case("4.481689070338064822", "1.49999999999999999984", false)] + #[test_case("27.1126389206578874261", "3.3", false)] + #[test_case("1549.3643050275008503592", "7.34560000000000000003", false)] + #[test_case("229964.194569082134542849", "12.3456789000000000002", false)] + #[test_case("442413.39200892050332603603", "13.0000000000000000002", false)] + #[test_case("0.9048374180359595733", "0.09999999999999999975", true)] + #[test_case("0.40656965974059911195", "0.8999999999999999998", true)] + #[test_case("0.22313016014842982894", "1.4999999999999999999", true)] + #[test_case("0.03688316740124000543", "3.3000000000000000005", true)] + #[test_case("0.00064542599616831253", "7.34560000000000002453", true)] + #[test_case("0.00000434850304358833", "12.34567890000000711117", true)] + #[test_case("0.0000022603294069810542", "13.0000000000000045352", true)] + #[test_case("1.0001", "0.00009999500033330827", false)] + #[test_case("1.00000001", "0.0000000099999999499", false)] + #[test_case("0.9999", "0.00010000500033335825", true)] + #[test_case("0.99999999", "0.00000001000000004987", true)] + // Powers of 2 (since we're using squares when calculating the fractional part of log2. + #[test_case("3.999999999", "1.38629436086989061877", false)] + #[test_case("4", "1.38629436111989061886", false)] + #[test_case("4.000000001", "1.3862943613698906188", false)] + #[test_case("7.999999999", "2.07944154155483592824", false)] + #[test_case("8", "2.0794415416798359283", false)] + #[test_case("8.000000001", "2.0794415418048359282", false)] + #[test_case("0.499999999", "0.69314718255994531136", true)] + #[test_case("0.5", "0.69314718055994530943", true)] + #[test_case("0.500000001", "0.69314717855994531135", true)] + #[test_case("0.249999999", "1.38629436511989062684", true)] + #[test_case("0.25", "1.38629436111989061886", true)] + #[test_case("0.250000001", "1.38629435711989062676", true)] + fn ln_works(operand: &str, expected_abs: &str, expected_neg: bool) { + let o = U64F64::from_str(operand).unwrap(); + let e = U64F64::from_str(expected_abs).unwrap(); + let (a, n) = ln::(o).unwrap(); + assert_eq!(a, e); + assert_eq!(n, expected_neg); } + } +} - #[test] - fn calculate_reserves_from_spot_prices_works() { - let expected_liquidity = 144269504088; - let (liquidity, reserves) = - calculate_reserves_from_spot_prices(_10, vec![_1_2, _1_2]).unwrap(); - assert_approx!(liquidity, expected_liquidity, 1); - assert_eq!(reserves, vec![_10, _10]); - } +#[cfg(test)] +mod tests { + use super::*; + use crate::{consts::*, mock::Runtime as MockRuntime}; + use alloc::str::FromStr; + use test_case::test_case; - // This test ensures that we don't mess anything up when we change precision. - #[test_case(false, Fixed::from_str("10686474581524.462146990468650739308072").unwrap())] - #[test_case(true, Fixed::from_str("0.000000000000093576229688").unwrap())] - fn exp_does_not_overflow_or_underflow(neg: bool, expected: Fixed) { - let value = 30; - let result: Fixed = exp(Fixed::checked_from_num(value).unwrap(), neg).unwrap(); - assert_eq!(result, expected); - } + type MockBalance = BalanceOf; + type MockMath = Math; + + // Example taken from + // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr + #[test_case(_10, _10, 144_269_504_088, 58_496_250_072)] + #[test_case(_1, _1, _1, 7_353_256_641)] + #[test_case(_2, _2, _2, 14_706_513_281; "positive ln")] + #[test_case(_1, _1_10, _3, 386_589_943; "negative ln")] + #[test_case(_100, _10, _3, 998_910_224_189; "underflow to zero, positive ln")] + #[test_case(_100, _1_10, _3, 897_465_467_426; "underflow to zero, negative ln")] + // Limit value tests; functions shouldn't be called with these values, but these tests + // demonstrate they can be called without risk. + #[test_case(0, _1, _1, 0)] + #[test_case(_1, 0, _1, 0)] + #[test_case(_30, _30, _1 - 100_000, _30)] + #[test_case(_1_10, _30, _1 - 100_000, _1_10)] + #[test_case(_30, _1_10, _1 - 100_000, 276_478_645_689)] + fn calculate_swap_amount_out_for_buy_works( + reserve: MockBalance, + amount_in: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_swap_amount_out_for_buy(reserve, amount_in, liquidity).unwrap(), + expected + ); + } + + #[test_case(_10, _10, 144_269_504_088, 41_503_749_928)] + #[test_case(_1, _1, _1, 2_646_743_359)] + #[test_case(_2, _2, _2, 5_293_486_719)] + #[test_case(_17, _8, _7, 4_334_780_553; "positive ln")] + #[test_case(_1, _11, 33_000_000_000, 41_104_447_891; "negative ln")] + // Limit value tests; functions shouldn't be called with these values, but these tests + // demonstrate they can be called without risk. + #[test_case(_1, 0, _1, 0)] + #[test_case(_30, _30, _1 - 100_000, 0)] + #[test_case(_1_10, _30, _1 - 100_000, 23_521_354_311)] + #[test_case(_30, _1_10, _1 - 100_000, 0)] + fn calculate_swap_amount_out_for_sell_works( + reserve: MockBalance, + amount_in: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_swap_amount_out_for_sell(reserve, amount_in, liquidity).unwrap(), + expected + ); + } + + #[test] + fn calculate_swap_amount_out_for_sell_fails_if_reserve_is_zero() { + assert!(MockMath::calculate_swap_amount_out_for_sell(0, _1, _1).is_err()); + } + + #[test_case(_10, 144_269_504_088, _1_2)] + #[test_case(_10 - 58_496_250_072, 144_269_504_088, _3_4)] + #[test_case(_20, 144_269_504_088, _1_4)] + fn calcuate_spot_price_works( + reserve: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!(MockMath::calculate_spot_price(reserve, liquidity).unwrap(), expected); + } + + #[test_case(_10, vec![_1_2, _1_2], vec![_10, _10], 144_269_504_089)] + #[test_case(_20, vec![_3_4, _1_4], vec![_10 - 58_496_250_072, _20], 144_269_504_089)] + #[test_case( + _444, + vec![_1_10, _2_10, _3_10, _4_10], + vec![_444, 3_103_426_819_252, 2_321_581_629_045, 1_766_853_638_504], + 1_928_267_499_650 + )] + #[test_case( + _100, + vec![50_000_000, 50_000_000, 50_000_000, 8_500_000_000], + vec![_100, _100, _100, 30_673_687_183], + 188_739_165_818 + )] + fn calculate_reserves_from_spot_prices_works( + amount: MockBalance, + spot_prices: Vec, + expected_reserves: Vec, + expected_liquidity: MockBalance, + ) { + let (liquidity, reserves) = + MockMath::calculate_reserves_from_spot_prices(amount, spot_prices).unwrap(); + assert_eq!(liquidity, expected_liquidity); + assert_eq!(reserves, expected_reserves); + } + + #[test_case(_10, _10, 144_269_504_088, _1 + _1_2)] + #[test_case(_10, _1, 144_269_504_088, 5_717_734_625)] + #[test_case(_1, _1, _1, 20_861_612_696)] + #[test_case(_444, _1, _11, 951_694_399; "underflow_to_zero")] + fn calculate_buy_ln_argument_fixed_works( + reserve: MockBalance, + amount_in: MockBalance, + liquidity: MockBalance, + expected: MockBalance, + ) { + assert_eq!( + MockMath::calculate_buy_ln_argument(reserve, amount_in, liquidity).unwrap(), + expected + ); + } + + // This test ensures that we don't mess anything up when we change precision. + #[test_case(false, FixedType::from_str("123705850708694.521074740553659523785099").unwrap())] + #[test_case(true, FixedType::from_str("0.000000000000008083692034").unwrap())] + fn exp_does_not_overflow_or_underflow(neg: bool, expected: FixedType) { + let result: FixedType = + exp(FixedType::checked_from_num(EXP_OVERFLOW_THRESHOLD).unwrap(), neg).unwrap(); + assert_eq!(result, expected); } } diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 755a08a20..9b72ddba3 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -243,66 +243,67 @@ fn buy_fails_on_asset_not_found(market_type: MarketType) { } #[test] -fn buy_fails_on_numerical_limits() { +fn buy_fails_if_amount_in_is_greater_than_numerical_threshold() { ExtBuilder::default().build().execute_with(|| { + let asset_count = 4; let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + MarketType::Categorical(asset_count), _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); let pool = Pools::::get(market_id).unwrap(); - let amount_in = 100 * pool.liquidity_parameter; + // Using twice the threshold here to account for the removal of swap fees. + let amount_in = 2 * pool.calculate_numerical_threshold(); assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); assert_noop!( NeoSwaps::buy( RuntimeOrigin::signed(BOB), market_id, - 2, - Asset::ScalarOutcome(market_id, ScalarPosition::Long), + asset_count, + Asset::CategoricalOutcome(market_id, asset_count - 1), amount_in, 0, ), - Error::::NumericalLimits, + Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), ); }); } #[test] -fn buy_fails_on_insufficient_funds() { +fn buy_fails_if_ln_arg_is_less_than_numerical_limit() { ExtBuilder::default().build().execute_with(|| { + let asset_count = 4; + let price = CENT; let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + MarketType::Categorical(asset_count), _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_2 - price, price], CENT, ); - let amount_in = _10; - #[cfg(not(feature = "parachain"))] - let expected_error = pallet_balances::Error::::InsufficientBalance; - #[cfg(feature = "parachain")] - let expected_error = orml_tokens::Error::::BalanceTooLow; - assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in - 1)); + let pool = Pools::::get(market_id).unwrap(); + let amount_in = 5 * CENT.bmul(pool.liquidity_parameter).unwrap(); + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); assert_noop!( NeoSwaps::buy( RuntimeOrigin::signed(BOB), market_id, - 2, - Asset::ScalarOutcome(market_id, ScalarPosition::Long), + asset_count, + Asset::CategoricalOutcome(market_id, asset_count - 1), amount_in, 0, ), - expected_error, + Error::::NumericalLimits(NumericalLimitsError::MinAmountNotMet), ); }); } #[test] -fn buy_fails_on_amount_out_below_min() { +fn buy_fails_on_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, @@ -312,9 +313,12 @@ fn buy_fails_on_amount_out_below_min() { vec![_1_2, _1_2], CENT, ); - let amount_in = _1; - assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. + let amount_in = _10; + #[cfg(not(feature = "parachain"))] + let expected_error = pallet_balances::Error::::InsufficientBalance; + #[cfg(feature = "parachain")] + let expected_error = orml_tokens::Error::::BalanceTooLow; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in - 1)); assert_noop!( NeoSwaps::buy( RuntimeOrigin::signed(BOB), @@ -322,34 +326,37 @@ fn buy_fails_on_amount_out_below_min() { 2, Asset::ScalarOutcome(market_id, ScalarPosition::Long), amount_in, - _2, + 0, ), - Error::::AmountOutBelowMin, + expected_error, ); }); } #[test] -fn buy_fails_on_spot_price_above_max() { +fn buy_fails_on_amount_out_below_min() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Categorical(2), + MarketType::Scalar(0..=1), _10, vec![_1_2, _1_2], CENT, ); + let amount_in = _1; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); + // Buying 1 at price of .5 will return less than 2 outcomes due to slippage. assert_noop!( NeoSwaps::buy( - RuntimeOrigin::signed(ALICE), + RuntimeOrigin::signed(BOB), market_id, 2, - Asset::CategoricalOutcome(market_id, 0), - _70, - 0, + Asset::ScalarOutcome(market_id, ScalarPosition::Long), + amount_in, + _2, ), - Error::::SpotPriceAboveMax + Error::::AmountOutBelowMin, ); }); } diff --git a/zrml/neo-swaps/src/tests/buy_and_sell.rs b/zrml/neo-swaps/src/tests/buy_and_sell.rs new file mode 100644 index 000000000..fbb545171 --- /dev/null +++ b/zrml/neo-swaps/src/tests/buy_and_sell.rs @@ -0,0 +1,182 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use zeitgeist_primitives::constants::BASE; + +macro_rules! assert_pool_status { + ($market_id:expr, $reserves:expr, $spot_prices:expr, $fees:expr $(,)?) => { + let pool = Pools::::get($market_id).unwrap(); + assert_eq!(pool.reserves.values().cloned().collect::>(), $reserves); + assert_eq!( + pool.assets() + .iter() + .map(|&a| pool.calculate_spot_price(a).unwrap()) + .collect::>(), + $spot_prices, + ); + let invariant = $spot_prices.iter().sum::(); + assert_approx!(invariant, _1, 1); + assert_eq!(pool.liquidity_shares_manager.fees, $fees); + }; +} + +#[test] +fn buy_and_sell() { + ExtBuilder::default().build().execute_with(|| { + let asset_count = 3; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + _100, + vec![_1_2, _1_4, _1_4], + CENT, + ); + assert_ok!(AssetManager::deposit(BASE_ASSET, &ALICE, _1000)); + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, _1000)); + assert_ok!(AssetManager::deposit(BASE_ASSET, &CHARLIE, _1000)); + + assert_ok!(NeoSwaps::buy( + RuntimeOrigin::signed(ALICE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 2), + _10, + 0, + )); + assert_pool_status!( + market_id, + vec![598_000_000_000, 1_098_000_000_000, 767_092_556_931], + [4_364_837_956, 2_182_418_978, 3_452_743_066], + 1_000_000_000, + ); + + assert_ok!(NeoSwaps::buy( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 1), + 1_234_567_898_765, + 0, + )); + assert_pool_status!( + market_id, + vec![1_807_876_540_789, 113_931_597_104, 1_976_969_097_720], + [815_736_444, 8_538_986_828, 645_276_728], + 13_345_678_988, + ); + + assert_ok!(NeoSwaps::buy( + RuntimeOrigin::signed(CHARLIE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 0), + 667 * BASE, + 0, + )); + assert_pool_status!( + market_id, + vec![76_875_275, 6_650_531_597_104, 8_513_569_097_720], + [9_998_934_339, 990_789, 74_872], + 80_045_678_988, + ); + + // Selling asset 2 is illegal due to low spot price. + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(ALICE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 2), + 123_456, + 0, + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceTooLow), + ); + + assert_ok!(NeoSwaps::sell( + RuntimeOrigin::signed(CHARLIE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 0), + _1, + 0, + )); + assert_pool_status!( + market_id, + vec![77_948_356, 6_640_532_670_185, 8_503_570_170_801], + [9_998_919_465, 1_004_618, 75_917], + 80_145_668_257, + ); + + // Selling asset 1 is allowed, but selling too much will raise an error. + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 1), + _100, + 0, + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), + ); + + // Try to sell more than the maximum amount. + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 1), + _1000, + 0, + ), + Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), + ); + + // Buying a small amount from an asset with a low price fails... + assert_noop!( + NeoSwaps::buy( + RuntimeOrigin::signed(CHARLIE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 2), + _1, + 0, + ), + Error::::NumericalLimits(NumericalLimitsError::MinAmountNotMet), + ); + + // ...but buying a large amount is fine. + assert_ok!(NeoSwaps::buy( + RuntimeOrigin::signed(CHARLIE), + market_id, + asset_count, + Asset::CategoricalOutcome(market_id, 2), + _100, + 0, + )); + assert_pool_status!( + market_id, + vec![980_077_948_356, 7_620_532_670_185, 214_308_675_476], + [2_570_006_838, 258_215, 7_429_734_946], + 90_145_668_257, + ); + }); +} diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index 639f1f839..d5764f846 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -263,33 +263,6 @@ fn deploy_pool_fails_on_invalid_trading_mechanism() { } #[test] -fn deploy_pool_fails_on_market_is_not_binary_or_scalar() { - ExtBuilder::default().build().execute_with(|| { - let market_id = - create_market(ALICE, BASE_ASSET, MarketType::Categorical(3), ScoringRule::Lmsr); - let liquidity = _10; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(ALICE), - market_id, - liquidity, - )); - assert_noop!( - NeoSwaps::deploy_pool( - RuntimeOrigin::signed(ALICE), - market_id, - liquidity, - vec![_1_3, _1_3, _1_3], - CENT - ), - Error::::MarketNotBinaryOrScalar - ); - }); -} - -// FIXME This test currently fails because the `ensure!` throwing `AssetCountAboveMax` is -// currently unreachable if the market is not binary/scalar. -#[test] -#[should_panic] fn deploy_pool_fails_on_asset_count_above_max() { ExtBuilder::default().build().execute_with(|| { let category_count = MAX_ASSETS + 1; @@ -305,8 +278,8 @@ fn deploy_pool_fails_on_asset_count_above_max() { market_id, liquidity, )); - // Depending on the value of MAX_ASSETS and PRICE_BARRIER_*, this `spot_prices` vector - // might violate some other rules for deploying pools. + // Beware! Depending on the value of MAX_ASSETS and price barriers, this `spot_prices` + // vector might violate some other rules for deploying pools. let mut spot_prices = vec![_1 / category_count as u128; category_count as usize - 1]; spot_prices.push(_1 - spot_prices.iter().sum::()); assert_noop!( diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index f637e08e3..32d320610 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -18,6 +18,7 @@ #![cfg(all(feature = "mock", test))] mod buy; +mod buy_and_sell; mod deploy_pool; mod exit; mod join; diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index b3095078c..43ea93549 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -243,23 +243,110 @@ fn sell_fails_on_asset_not_found(market_type: MarketType) { } #[test] -fn sell_fails_on_numerical_limits() { +fn sell_fails_if_amount_in_is_greater_than_numerical_threshold() { ExtBuilder::default().build().execute_with(|| { + let asset_count = 4; let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), + MarketType::Categorical(asset_count), _10, - vec![_1_2, _1_2], + vec![_1_4, _1_4, _1_4, _1_4], CENT, ); let pool = Pools::::get(market_id).unwrap(); - let asset_in = Asset::ScalarOutcome(market_id, ScalarPosition::Long); - let amount_in = 100 * pool.liquidity_parameter; + let asset_in = Asset::CategoricalOutcome(market_id, asset_count - 1); + let amount_in = pool.calculate_numerical_threshold() + 1; + assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + asset_in, + amount_in, + 0 + ), + Error::::NumericalLimits(NumericalLimitsError::MaxAmountExceeded), + ); + }); +} + +#[test] +fn sell_fails_if_price_is_too_low() { + ExtBuilder::default().build().execute_with(|| { + let asset_count = 4; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + _10, + vec![_1_4, _1_4, _1_4, _1_4], + CENT, + ); + let asset_in = Asset::CategoricalOutcome(market_id, asset_count - 1); + // Force the price below the threshold by changing the reserve of the pool. Strictly + // speaking this leaves the pool in an inconsistent state (reserve recorded in the `Pool` + // struct is smaller than actual reserve), but this doesn't matter in this test. + NeoSwaps::try_mutate_pool(&market_id, |pool| { + pool.reserves.insert(asset_in, 11 * pool.liquidity_parameter); + Ok(()) + }) + .unwrap(); + let amount_in = _1; + assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); + assert_noop!( + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + asset_in, + amount_in, + 0 + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceTooLow), + ); + }); +} + +#[test] +fn sell_fails_if_price_is_pushed_below_threshold() { + ExtBuilder::default().build().execute_with(|| { + let asset_count = 4; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(asset_count), + _10, + vec![_1_4, _1_4, _1_4, _1_4], + CENT, + ); + let asset_in = Asset::CategoricalOutcome(market_id, asset_count - 1); + // Force the price below the threshold by changing the reserve of the pool. Strictly + // speaking this leaves the pool in an inconsistent state (reserve recorded in the `Pool` + // struct is smaller than actual reserve), but this doesn't matter in this test. + NeoSwaps::try_mutate_pool(&market_id, |pool| { + // The price is right at the brink here. Any further shift and sells won't be accepted + // anymore. + pool.reserves.insert(asset_in, 10 * pool.liquidity_parameter); + Ok(()) + }) + .unwrap(); + let amount_in = _10; assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); + // The received amount is so small that it triggers an ED error if we don't "pad out" Bob's + // account with some funds. + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, _1)); assert_noop!( - NeoSwaps::buy(RuntimeOrigin::signed(BOB), market_id, 2, asset_in, amount_in, 0), - Error::::NumericalLimits, + NeoSwaps::sell( + RuntimeOrigin::signed(BOB), + market_id, + asset_count, + asset_in, + amount_in, + 0 + ), + Error::::NumericalLimits(NumericalLimitsError::SpotPriceSlippedTooLow), ); }); } @@ -313,24 +400,3 @@ fn sell_fails_on_amount_out_below_min() { ); }); } - -#[test] -fn sell_fails_on_spot_price_below_min() { - ExtBuilder::default().build().execute_with(|| { - let market_id = create_market_and_deploy_pool( - ALICE, - BASE_ASSET, - MarketType::Categorical(2), - _10, - vec![_1_2, _1_2], - CENT, - ); - let asset_in = Asset::CategoricalOutcome(market_id, 0); - let amount_in = _80; - assert_ok!(AssetManager::deposit(asset_in, &BOB, amount_in)); - assert_noop!( - NeoSwaps::sell(RuntimeOrigin::signed(BOB), market_id, 2, asset_in, amount_in, 0), - Error::::SpotPriceBelowMin - ); - }); -} diff --git a/zrml/neo-swaps/src/traits/pool_operations.rs b/zrml/neo-swaps/src/traits/pool_operations.rs index e187361c8..8f1a58edb 100644 --- a/zrml/neo-swaps/src/traits/pool_operations.rs +++ b/zrml/neo-swaps/src/traits/pool_operations.rs @@ -77,7 +77,21 @@ pub(crate) trait PoolOperations { /// Calculate the spot price of `asset`. fn calculate_spot_price(&self, asset: AssetOf) -> Result, DispatchError>; - /// Calculate the maximum number of units of outcomes anyone is allowed to swap in or out of the - /// pool. - fn calculate_max_amount_in(&self) -> BalanceOf; + /// Calculate a numerical threshold, which determines the maximum number of units of outcomes + /// anyone is allowed to swap in or out of the pool, and the minimum prices required for selling + /// to the pool. + fn calculate_numerical_threshold(&self) -> BalanceOf; + + /// Calculate the ln argument used when calculating amounts out for buys. Underflows do not + /// raise an error and are rounded down to zero instead. + /// + /// # Parameters + /// + /// - `asset_out`: The outcome being bought. + /// - `amount_in`: The amount of collateral paid. + fn calculate_buy_ln_argument( + &self, + asset: AssetOf, + amount_in: BalanceOf, + ) -> Result, DispatchError>; } diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index c9cec18d8..d76264cae 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -103,10 +103,19 @@ where Math::::calculate_spot_price(reserve, self.liquidity_parameter) } - fn calculate_max_amount_in(&self) -> BalanceOf { + fn calculate_numerical_threshold(&self) -> BalanceOf { // Saturation is OK. If this saturates, the maximum amount in is just the numerical limit. self.liquidity_parameter.saturating_mul(EXP_NUMERICAL_LIMIT.saturated_into()) } + + fn calculate_buy_ln_argument( + &self, + asset: AssetOf, + amount_in: BalanceOf, + ) -> Result, DispatchError> { + let reserve = self.reserve_of(&asset)?; + Math::::calculate_buy_ln_argument(reserve, amount_in, self.liquidity_parameter) + } } impl> MaxEncodedLen for Pool diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 59057af5a..8ee41b44a 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -49,12 +49,12 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_neo_swaps (automatically generated) pub trait WeightInfoZeitgeist { - fn buy() -> Weight; - fn sell() -> Weight; - fn join() -> Weight; - fn exit() -> Weight; + fn buy(n: u32) -> Weight; + fn sell(n: u32) -> Weight; + fn join(n: u32) -> Weight; + fn exit(n: u32) -> Weight; fn withdraw_fees() -> Weight; - fn deploy_pool() -> Weight; + fn deploy_pool(n: u32) -> Weight; } /// Weight functions for zrml_neo_swaps (automatically generated) @@ -70,7 +70,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:2 w:2) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - fn buy() -> Weight { + fn buy(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `2905` // Estimated: `28324` @@ -89,7 +89,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:2 w:2) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - fn sell() -> Weight { + fn sell(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `3071` // Estimated: `28324` @@ -104,7 +104,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:4 w:4) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - fn join() -> Weight { + fn join(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `2793` // Estimated: `20672` @@ -121,7 +121,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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) - fn exit() -> Weight { + fn exit(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `2561` // Estimated: `23279` @@ -151,7 +151,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - fn deploy_pool() -> Weight { + fn deploy_pool(_n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `2278` // Estimated: `23279` diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index 450bd6c9c..6a877c38f 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -314,7 +314,7 @@ mod pallet { fn do_remove_order(order_id: OrderId, who: AccountIdOf) -> DispatchResult { let order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; - + let maker = &order_data.maker; ensure!(who == *maker, Error::::NotOrderCreator); diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 3e8474bb1..9b94d1314 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -41,8 +41,9 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::mock::{ CloseEarlyProtectionTimeFramePeriod, CloseEarlyTimeFramePeriod, MaxSwapFee, MinWeight, - BASE, MILLISECS_PER_BLOCK, + BASE, CENT, MILLISECS_PER_BLOCK, }, + math::fixed::{BaseProvider, ZeitgeistBase}, traits::{DisputeApi, Swaps}, types::{ Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, @@ -232,6 +233,13 @@ fn setup_reported_categorical_market_with_pool(asset_count: u16) -> Vec> { + let mut result = vec![CENT.saturated_into(); (asset_count - 1) as usize]; + let remaining_u128 = ZeitgeistBase::::get().unwrap() - (asset_count - 1) as u128 * CENT; + result.push(remaining_u128.saturated_into()); + result +} + benchmarks! { where_clause { where @@ -1457,15 +1465,18 @@ benchmarks! { }: { call.dispatch_bypass_filter(RawOrigin::Signed(caller).into())? } create_market_and_deploy_pool { + // Beware! This benchmark expects the `DeployPool` implementation to accept spot prices as + // low as `BASE / MaxCategories::get()`! let m in 0..63; // Number of markets closing on the same block. + let n in 2..T::MaxCategories::get() as u32; // Number of assets in the market. let base_asset = Asset::Ztg; let range_start = (5 * MILLISECS_PER_BLOCK) as u64; let range_end = (100 * MILLISECS_PER_BLOCK) as u64; let period = MarketPeriod::Timestamp(range_start..range_end); - let market_type = MarketType::Categorical(2); + let asset_count = n.try_into().unwrap(); + let market_type = MarketType::Categorical(asset_count); let (caller, oracle, deadlines, metadata) = create_market_common_parameters::(true)?; - let price = (BASE / 2).saturated_into(); let amount = (10u128 * BASE).saturated_into(); ::AssetManager::deposit( @@ -1487,11 +1498,11 @@ benchmarks! { period, deadlines, metadata, - MarketType::Categorical(2), + MarketType::Categorical(asset_count), Some(MarketDisputeMechanism::Court), amount, - vec![price, price], - (BASE / 100).saturated_into() + create_spot_prices::(asset_count), + CENT.saturated_into() ) impl_benchmark_test_suite!( diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 2e896d1d1..d45196e49 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -1348,7 +1348,10 @@ mod pallet { /// `O(n)` where `n` is the number of markets which close on the same block, plus the /// resources consumed by `DeployPool::create_pool`. In the standard implementation using /// neo-swaps, this is `O(m)` where `m` is the number of assets in the market. - #[pallet::weight(T::WeightInfo::create_market_and_deploy_pool(CacheSize::get()))] + #[pallet::weight(T::WeightInfo::create_market_and_deploy_pool( + CacheSize::get(), + spot_prices.len() as u32, + ))] #[transactional] #[pallet::call_index(17)] pub fn create_market_and_deploy_pool( @@ -1380,8 +1383,9 @@ mod pallet { ScoringRule::Lmsr, )?; Self::do_buy_complete_set(who.clone(), market_id, amount)?; + let spot_prices_len = spot_prices.len() as u32; T::DeployPool::deploy_pool(who, market_id, amount, spot_prices, swap_fee)?; - Ok(Some(T::WeightInfo::create_market_and_deploy_pool(ids_len)).into()) + Ok(Some(T::WeightInfo::create_market_and_deploy_pool(ids_len, spot_prices_len)).into()) } /// Allows the `CloseMarketsEarlyOrigin` or the market creator to schedule an early close. diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 9a18cfdf9..f9f8c7872 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -80,13 +80,13 @@ pub trait WeightInfoZeitgeist { fn market_status_manager(b: u32, f: u32) -> Weight; fn market_resolution_manager(r: u32, d: u32) -> Weight; fn process_subsidy_collecting_markets_dummy() -> Weight; + fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight; fn schedule_early_close_as_authority(o: u32, n: u32) -> Weight; fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight; fn schedule_early_close_as_market_creator(o: u32, n: u32) -> Weight; 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 create_market_and_deploy_pool(m: u32) -> Weight; fn close_trusted_market(o: u32, c: u32) -> Weight; } @@ -750,6 +750,42 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + /// Storage: MarketCommons MarketCounter (r:1 w:1) + /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:126 w:126) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: Tokens TotalIssuance (r:63 w:63) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) + /// Storage: NeoSwaps Pools (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) + /// Storage: MarketCommons Markets (r:0 w:1) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// The range of component `m` is `[0, 63]`. + /// The range of component `n` is `[2, 64]`. + fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `410 + m * (16 ±0)` + // Estimated: `20604 + n * (7714 ±0)` + // Minimum execution time: 372_000 nanoseconds. + Weight::from_parts(215_092_617, 20604) + // Standard Error: 998_171 + .saturating_add(Weight::from_parts(565_260, 0).saturating_mul(m.into())) + // Standard Error: 1_014_199 + .saturating_add(Weight::from_parts(65_201_886, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 7714).saturating_mul(n.into())) + } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Timestamp Now (r:1 w:0) @@ -868,36 +904,6 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Balances Reserves (r:1 w:1) - /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketCounter (r:1 w:1) - /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:4) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:2 w:2) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:0 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// The range of component `m` is `[0, 63]`. - fn create_market_and_deploy_pool(m: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `410 + m * (16 ±0)` - // Estimated: `36032` - // Minimum execution time: 238_861 nanoseconds. - Weight::from_parts(280_682_707, 36032) - // Standard Error: 11_735 - .saturating_add(Weight::from_parts(24_164, 0).saturating_mul(m.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(13)) - } fn close_trusted_market(o: u32, c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `792 + o * (16 ±0) + c * (16 ±0)` From d26dc8c85e0b942241379f15c0d3add4b456dda1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 21 Dec 2023 13:28:30 +0100 Subject: [PATCH 016/104] Implement Liquidity Tree (#1178) * Replace `bmul` and `bdiv` with traited versions * Restructure directories * Replace `saturating_*` from neo-swaps * Fix formatting * Restructure zrml-swaps math * Implement and test `b*` * Fix formatting * Use new math in orderbook-v1 * Replace checked multiplication with new math * Use correct rounding in neo-swaps * Add docs * Update licenses * Remove `fixed` module from `primitives` * Fix formatting * Add liquidity tree draft * Fix compiler error in draft * Clean up `propagate_fees` * Add docs * Reorganize * Clean up, add max iterations * Remove migrations * Prepare switch to bounded vector * Use `BoundedVec` * Use bounded `BTreeMap` and insert `LiquidityTree` into neo-swaps * Resolve TODO * Clean up tests * Add migration and fix clippy errors * Make tree depth a generic * Make tree depth a config parameter * Update runtime complexities * Add benchmarking utilities * Fix runtime complexity for `deploy_pool` * Add missing lazy propagation * Fix comment * Fix error type * Add `join` benchmarking and fix a bug in `LiquidityTree` * Clean up benchmarks * Fix clippy issues * Remove unnecessary type hint * Fix bugs in liquidity tree * Fix comments * Some fixes in benchmarks * Implement `BenchmarkHelper` * Update benchmarks to use the LT * Clean up and format * Add testing framework for liquidity trees * Add first extensive test and fix a bug * Add more tests * Clean up test * Add news tests and use better numerics for ratio calculations * Make docs more explicit * Add tests for `exit` * Add tests for deposit fees * Add tests for getters * Clean up tests some more * Finalize liquidity tree tests * Clean up comments * Introduce exit fees * Rewrite `exit_works` * Fix liquidity parameter calculation * Make test a bit more complex * Test liquidity shares * Clean up tests * Update test for destruction * Enhance `exit_destroys_pool` test * More cleanup * Add test for full tree * Add tests for `join` * Improve test * Test withdrawal * Clean up the test * Add test for noop * Add minimum relative liquidity * Verify that buys deduct the correct amount of fees * Add last tests * Add notes about the liquidity tree * Fix benchmarks * Fix clippy errors * Fix benchmarks * Do more work on benchmarks * Fix benchmarks, deduce max tree depth * Remove already solved TODO * Document macros * Remove TODO (not a good idea to edit LaTeX docs now) * Fix `bmul_bdiv` * Make `bmul_bdiv_*` not implemented * Double-check that there's a non-zero check for `ratio` * Fix formatting * Fix taplo formatting * Remove TODO * Remove TODOs and fix documents * Update primitives/src/math/fixed.rs Co-authored-by: Chralt * Mark `SoloLp` as deprecated * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Make `bmul_bdiv` a little more idiomatic * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Update zrml/neo-swaps/README.md Co-authored-by: Chralt * Rewrite and format `README.md` * Fix comment about existential deposit * Remove FIXME * Add a check against misconfiguration of the pallet * Fix field docstrings * Add comment about `assert_ok_with_transaction` * Update zrml/neo-swaps/src/mock.rs Co-authored-by: Chralt * Format code * Fix comment * Prettify extrinsic calls in benchmarks * Improve code quality of `assert_pool_state!` * Extend comment about order of abandoned nodes * Clarify the meaning of "leaf" in `is_leaf` * Rename `index_maybe` to `opt_index` * Add unexpected storage overflow error for bounded objects * Rename `UnclaimedFees` to `UnwithdrawnFees` * Fix documentation * Use enum to signal update direction in `update_descendant_stake` * Add verification to `join_*` benchmarks * Improve documentation * Use builtin log * Remove illegal token from `README.md` * Update zrml/neo-swaps/src/types/liquidity_tree.rs Co-authored-by: Chralt * Fix unintentional doctest * Improve `mutate_children` * Add helpful comment * Update zrml/neo-swaps/src/types/liquidity_tree.rs Co-authored-by: Chralt * Fix migration * Fix balances in parachain mock * use PartialEqNoBound and CloneNoBound for tree * Fix formatting * Make `debug_assert!` more clear * Redesign node assignment * Clean up comments * Add some storage overflow errors * Introduce `LiquidityTreeChildIndices` * Remove outdated docs * Rename `update_descendant_stake` * Remove `Default` usage * Make liquidity tree types `pub(crate)` where possible * Make all fields of `Node` only `pub(crate)` * Make `Node` an associated type of `LiquidityTreeHelper` * Update zrml/neo-swaps/src/lib.rs Co-authored-by: Harald Heckmann * Fix comment * Update zrml/neo-swaps/src/types/liquidity_tree.rs Co-authored-by: Harald Heckmann * Fix tests * Prettify `path_to_node` * Add max iterations to `path_to_node` * Add test for complex liquidity tree interactions * Improve documentation of `LiquidityTreeChildIndices` * Reorganize crate structure * Update weights and fix formatting --------- Co-authored-by: Chralt Co-authored-by: Chralt98 Co-authored-by: Harald Heckmann --- Cargo.lock | 2 + primitives/Cargo.toml | 1 + primitives/src/constants/mock.rs | 1 + primitives/src/math/checked_ops_res.rs | 18 + primitives/src/math/fixed.rs | 157 ++++++- runtime/battery-station/src/parameters.rs | 1 + runtime/common/src/lib.rs | 9 +- runtime/zeitgeist/src/parameters.rs | 1 + zrml/neo-swaps/Cargo.toml | 1 + zrml/neo-swaps/README.md | 115 +++-- zrml/neo-swaps/src/benchmarking.rs | 321 +++++++++++-- zrml/neo-swaps/src/consts.rs | 7 + zrml/neo-swaps/src/helpers.rs | 34 ++ zrml/neo-swaps/src/lib.rs | 150 ++++-- zrml/neo-swaps/src/liquidity_tree/macros.rs | 70 +++ zrml/neo-swaps/src/liquidity_tree/mod.rs | 21 + .../src/liquidity_tree/tests/deposit_fees.rs | 45 ++ .../src/liquidity_tree/tests/exit.rs | 173 +++++++ .../src/liquidity_tree/tests/join.rs | 196 ++++++++ .../neo-swaps/src/liquidity_tree/tests/mod.rs | 175 +++++++ .../src/liquidity_tree/tests/shares_of.rs | 29 ++ .../src/liquidity_tree/tests/total_shares.rs | 24 + .../src/liquidity_tree/tests/withdraw_fees.rs | 73 +++ .../traits/liquidity_tree_helper.rs | 114 +++++ .../src/liquidity_tree/traits/mod.rs | 20 + .../liquidity_tree/types/liquidity_tree.rs | 430 ++++++++++++++++++ .../types/liquidity_tree_child_indices.rs | 51 +++ .../types/liquidity_tree_error.rs | 81 ++++ .../types/liquidity_tree_max_nodes.rs | 37 ++ .../neo-swaps/src/liquidity_tree/types/mod.rs | 30 ++ .../src/liquidity_tree/types/node.rs | 72 +++ .../update_descendant_stake_operation.rs | 22 + zrml/neo-swaps/src/macros.rs | 156 +++++++ zrml/neo-swaps/src/migration.rs | 254 +++++++++++ zrml/neo-swaps/src/mock.rs | 58 +-- zrml/neo-swaps/src/tests/buy.rs | 53 +-- zrml/neo-swaps/src/tests/buy_and_sell.rs | 37 +- zrml/neo-swaps/src/tests/deploy_pool.rs | 76 ++-- zrml/neo-swaps/src/tests/exit.rs | 251 +++++----- zrml/neo-swaps/src/tests/join.rs | 149 ++++-- .../src/tests/liquidity_tree_interactions.rs | 58 +++ zrml/neo-swaps/src/tests/mod.rs | 36 +- zrml/neo-swaps/src/tests/sell.rs | 53 +-- zrml/neo-swaps/src/tests/withdraw_fees.rs | 112 ++++- .../src/traits/liquidity_shares_manager.rs | 8 +- zrml/neo-swaps/src/types/pool.rs | 2 +- zrml/neo-swaps/src/types/solo_lp.rs | 6 +- zrml/neo-swaps/src/weights.rs | 204 ++++++--- 48 files changed, 3466 insertions(+), 528 deletions(-) create mode 100644 zrml/neo-swaps/src/helpers.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/macros.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/mod.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/deposit_fees.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/exit.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/join.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/mod.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/shares_of.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/total_shares.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/tests/withdraw_fees.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/traits/liquidity_tree_helper.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/traits/mod.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_child_indices.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_error.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_max_nodes.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/mod.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/node.rs create mode 100644 zrml/neo-swaps/src/liquidity_tree/types/update_descendant_stake_operation.rs create mode 100644 zrml/neo-swaps/src/macros.rs create mode 100644 zrml/neo-swaps/src/migration.rs create mode 100644 zrml/neo-swaps/src/tests/liquidity_tree_interactions.rs diff --git a/Cargo.lock b/Cargo.lock index 9722c255b..f59d33291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14378,6 +14378,7 @@ dependencies = [ "frame-support", "frame-system", "more-asserts", + "num-traits", "orml-currencies", "orml-tokens", "orml-traits", @@ -14619,6 +14620,7 @@ dependencies = [ name = "zrml-neo-swaps" version = "0.4.2" dependencies = [ + "cfg-if", "env_logger 0.10.1", "fixed", "frame-benchmarking", diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 903a91f70..53d3b282e 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -3,6 +3,7 @@ arbitrary = { workspace = true, optional = true } fixed = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +num-traits = { workspace = true } orml-currencies = { workspace = true } orml-tokens = { workspace = true } orml-traits = { workspace = true } diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 8235e6eee..0e1fee734 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -75,6 +75,7 @@ parameter_types! { parameter_types! { pub storage NeoExitFee: Balance = CENT; pub const NeoMaxSwapFee: Balance = 10 * CENT; + pub const MaxLiquidityTreeDepth: u32 = 3u32; pub const NeoSwapsPalletId: PalletId = PalletId(*b"zge/neos"); } diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs index 107979fff..02e9ec3de 100644 --- a/primitives/src/math/checked_ops_res.rs +++ b/primitives/src/math/checked_ops_res.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . use frame_support::dispatch::DispatchError; +use num_traits::{checked_pow, One}; use sp_arithmetic::{ traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}, ArithmeticError, @@ -49,6 +50,13 @@ where fn checked_div_res(&self, other: &Self) -> Result; } +pub trait CheckedPowRes +where + Self: Sized, +{ + fn checked_pow_res(&self, exp: usize) -> Result; +} + impl CheckedAddRes for T where T: CheckedAdd, @@ -88,3 +96,13 @@ where self.checked_div(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) } } + +impl CheckedPowRes for T +where + T: Copy + One + CheckedMul, +{ + #[inline] + fn checked_pow_res(&self, exp: usize) -> Result { + checked_pow(*self, exp).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } +} diff --git a/primitives/src/math/fixed.rs b/primitives/src/math/fixed.rs index f02d9ceca..0af4fe0b5 100644 --- a/primitives/src/math/fixed.rs +++ b/primitives/src/math/fixed.rs @@ -73,8 +73,8 @@ where fn bmul_ceil(&self, other: Self) -> Result; } -/// Performs fixed point division and errors with `DispatchError` in case of over- or -/// underflows and division by zero. +/// Performs fixed point division and errors with `DispatchError` in case of over- or underflows and +/// division by zero. pub trait FixedDiv where Self: Sized, @@ -90,6 +90,22 @@ where fn bdiv_ceil(&self, other: Self) -> Result; } +/// Performs fixed point multiplication and division, calculating `self * multiplier / divisor`. +pub trait FixedMulDiv +where + Self: Sized, +{ + /// Calculates the fixed point product `self * multiplier / divisor` and rounds to the nearest + /// representable fixed point number. + fn bmul_bdiv(&self, multiplier: Self, divisor: Self) -> Result; + + /// Calculates the fixed point product `self * multiplier / divisor` and rounds down. + fn bmul_bdiv_floor(&self, multiplier: Self, divisor: Self) -> Result; + + /// Calculates the fixed point product `self * multiplier / divisor` and rounds up. + fn bmul_bdiv_ceil(&self, multiplier: Self, divisor: Self) -> Result; +} + impl FixedMul for T where T: AtLeast32BitUnsigned, @@ -137,6 +153,63 @@ where } } +/// Helper function for implementing `FixedMulDiv` in a numerically clean way. +/// +/// The main idea is to keep the fixed point number scaled up between the multiplication and +/// division operation, so as to not lose any precision. Multiplication-first is preferred as it +/// grants better precision, but may suffer from overflows. If an overflow occurs, division-first is +/// used instead. +fn bmul_bdiv_common(x: &T, multiplier: T, divisor: T, adjustment: T) -> Result +where + T: AtLeast32BitUnsigned + Copy, +{ + // Try to multiply first, then divide. This overflows if the (mathematical) product of `x` and + // `multiplier` is around 3M. Use divide-first if this is the case. + let maybe_prod = x.checked_mul_res(&multiplier); + let maybe_scaled_prod = maybe_prod.and_then(|r| r.checked_mul_res(&ZeitgeistBase::get()?)); + if let Ok(scaled_prod) = maybe_scaled_prod { + // Multiply first, then divide. + let quot = scaled_prod.checked_div_res(&divisor)?; + let adjusted_quot = quot.checked_add_res(&adjustment)?; + adjusted_quot.checked_div_res(&ZeitgeistBase::get()?) + } else { + // Divide first, multiply later. It's cleaner to use the maximum of (x, multiplier) as + // divident. + let smallest = x.min(&multiplier); + let largest = x.max(&multiplier); + let scaled_divident = largest.checked_mul_res(&ZeitgeistBase::get()?)?; + let quot = scaled_divident.checked_div_res(&divisor)?; + let prod = quot.checked_mul_res(smallest)?; + let adjusted_prod = prod.checked_add_res(&adjustment)?; + adjusted_prod.checked_div_res(&ZeitgeistBase::get()?) + } +} + +/// Numerically clean implementation of `FixedMulDiv` which ensures higher precision than naive +/// multiplication and division for extreme values. +impl FixedMulDiv for T +where + T: AtLeast32BitUnsigned + Copy, +{ + fn bmul_bdiv(&self, multiplier: Self, divisor: Self) -> Result { + let adjustment = ZeitgeistBase::::get()?.checked_div_res(&2u8.into())?; + bmul_bdiv_common(self, multiplier, divisor, adjustment) + } + + fn bmul_bdiv_floor(&self, _multiplier: Self, _divisor: Self) -> Result { + // FIXME Untested! + // bmul_bdiv_common(self, multiplier, divisor, Zero::zero()) + Err(DispatchError::Other("not implemented")) + } + + fn bmul_bdiv_ceil(&self, _multiplier: Self, _divisor: Self) -> Result { + // FIXME Untested! + // let adjustment = ZeitgeistBase::::get()?.checked_sub_res(&1u8.into())?; + // bmul_bdiv_common(self, multiplier, divisor, adjustment) + Err(DispatchError::Other("not implemented")) + } +} + /// Converts a fixed point decimal number into another type. pub trait FromFixedDecimal> where @@ -504,6 +577,86 @@ mod tests { ); } + // bmul tests + #[test_case(0, 0, _1, 0)] + #[test_case(0, _1, _1, 0)] + #[test_case(0, _2, _1, 0)] + #[test_case(0, _3, _1, 0)] + #[test_case(_1, 0, _1, 0)] + #[test_case(_1, _1, _1, _1)] + #[test_case(_1, _2, _1, _2)] + #[test_case(_1, _3, _1, _3)] + #[test_case(_2, 0, _1, 0)] + #[test_case(_2, _1, _1, _2)] + #[test_case(_2, _2, _1, _4)] + #[test_case(_2, _3, _1, _6)] + #[test_case(_3, 0, _1, 0)] + #[test_case(_3, _1, _1, _3)] + #[test_case(_3, _2, _1, _6)] + #[test_case(_3, _3, _1, _9)] + #[test_case(_4, _1_2, _1, _2)] + #[test_case(_5, _1 + _1_2, _1, _7 + _1_2)] + #[test_case(_1 + 1, _2, _1, _2 + 2)] + #[test_case(9_999_999_999, _2, _1, 19_999_999_998)] + #[test_case(9_999_999_999, _10, _1, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, _1, _1_2)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, _1, 2_499_999_999)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, _1, 3_333_333_332)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1, _1_10)] + #[test_case(10_000_000_005, _1_10, _1, _1_10 + 1)] // + + // bdiv tests + #[test_case(0, _1, _3, 0)] + #[test_case(_1, _1, _2, _1_2)] + #[test_case(_2, _1, _2, _1)] + #[test_case(_3,_1, _2, _1 + _1_2)] + #[test_case(_3, _1, _3, _1)] + #[test_case(_3 + _1_2, _1, _1_2, _7)] + #[test_case(99_999_999_999, _1, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_2, _1, _3, _2_3 + 1)] + #[test_case(99_999_999_999, _1, _10, _1)] + #[test_case(99_999_999_994, _1, _10, 9_999_999_999)] + #[test_case(5, _1, _10, 1)] + #[test_case(4, _1, _10, 0)] // + + // Normal Cases + #[test_case(_2, _2, _2, _2)] + #[test_case(_1, _2, _3, _2_3 + 1)] + #[test_case(_2, _3, _4, _1 + _1_2)] + #[test_case(_1 + 1, _2, _3, (_2 + 2) / 3)] + #[test_case(_5, _6, _7, _5 * _6 / _7)] + #[test_case(_100, _101, _20, _100 * _101 / _20)] // + + // Boundary cases + #[test_case(u128::MAX / _1, _1, _2, u128::MAX / _2)] + #[test_case(0, _1, _2, 0)] + #[test_case(_1, u128::MAX / _1, u128::MAX / _1, _1)] // + + // Special rounding cases + #[test_case(_1, _1_2, _1, _1_2)] + #[test_case(_1, _1_3, _1, _1_3)] + #[test_case(_1, _2_3, _1, _2_3)] + #[test_case(_9, _1_2, _1, _9 / 2)] + #[test_case(_9, _1_3, _1, 29_999_999_997)] + #[test_case(_9, _2_3, _1, 59_999_999_994)] // + + // Divide-first value + #[test_case(1_000_000 * _1, 1_000_000 * _1, _10, 100000000000 * _1)] + #[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)] + #[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)] + + fn fixed_mul_div_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) { + assert_eq!(lhs.bmul_bdiv(multiplier, divisor).unwrap(), expected); + } + + #[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))] + #[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))] + fn fixed_mul_div_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) { + assert_eq!(lhs.bmul_bdiv(multiplier, divisor), Err(expected)); + } + #[test_case(0, 10, 0.0)] #[test_case(1, 10, 0.0000000001)] #[test_case(9, 10, 0.0000000009)] diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index bf19acc07..945dabebe 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -195,6 +195,7 @@ parameter_types! { // NeoSwaps pub const NeoSwapsMaxSwapFee: Balance = 10 * CENT; pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; + pub const MaxLiquidityTreeDepth: u32 = 9u32; // ORML pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 17b57de5a..a0a96428c 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -55,16 +55,14 @@ 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_neo_swaps::migration::MigrateToLiquidityTree; + use zrml_orderbook::migrations::TranslateOrderStructure; pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; - #[cfg(feature = "parachain")] - type Migrations = zrml_orderbook::migrations::TranslateOrderStructure; - - #[cfg(not(feature = "parachain"))] - type Migrations = zrml_orderbook::migrations::TranslateOrderStructure; + type Migrations = (MigrateToLiquidityTree, TranslateOrderStructure); pub type Executive = frame_executive::Executive< Runtime, @@ -1264,6 +1262,7 @@ macro_rules! impl_config_traits { type MultiCurrency = AssetManager; type RuntimeEvent = RuntimeEvent; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; + type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; type MaxSwapFee = NeoSwapsMaxSwapFee; type PalletId = NeoSwapsPalletId; } diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index f5bf84b40..569449b09 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -195,6 +195,7 @@ parameter_types! { // NeoSwaps pub const NeoSwapsMaxSwapFee: Balance = 10 * CENT; pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; + pub const MaxLiquidityTreeDepth: u32 = 9u32; // ORML pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 6d41495cd..6ecd9e15a 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -1,4 +1,5 @@ [dependencies] +cfg-if = { workspace = true } fixed = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } diff --git a/zrml/neo-swaps/README.md b/zrml/neo-swaps/README.md index e2b057c39..fa3e2f7e0 100644 --- a/zrml/neo-swaps/README.md +++ b/zrml/neo-swaps/README.md @@ -10,38 +10,93 @@ For a detailed description of the underlying mathematics see [here][docslink]. ### Terminology -- _Collateral_: The currency type that backs the outcomes in the pool. This is - also called the _base asset_ in other contexts. -- _Exit_: Refers to removing (part of) the liquidity from a liquidity pool in - exchange for burning pool shares. -- _External fees_: After taking swap fees, additional fees can be withdrawn - from an informant's collateral. They might go to the chain's treasury or the - market creator. -- _Join_: Refers to adding more liquidity to a pool and receiving pool shares - in return. -- _Liquidity provider_: A user who owns pool shares indicating their stake in - the liquidity pool. -- _Pool Shares_: A token indicating the owner's per rate share of the - liquidity pool. -- _Reserve_: The balances in the liquidity pool used for trading. -- _Swap fees_: Part of the collateral paid or received by informants that is - moved to a separate account owned by the liquidity providers. They need to - be withdrawn using the `withdraw_fees` extrinsic. +- _Collateral_: The currency type that backs the outcomes in the pool. This is + also called the _base asset_ in other contexts. +- _Exit_: Refers to removing (part of) the liquidity from a liquidity pool in + exchange for burning pool shares. +- _External fees_: After taking swap fees, additional fees can be withdrawn from + an informant's collateral. They might go to the chain's treasury or the market + creator. +- _Join_: Refers to adding more liquidity to a pool and receiving pool shares in + return. +- _Liquidity provider_: A user who owns pool shares indicating their stake in + the liquidity pool. +- _Pool Shares_: A token indicating the owner's per rate share of the liquidity + pool. +- _Reserve_: The balances in the liquidity pool used for trading. +- _Swap fees_: Part of the collateral paid or received by informants that is + moved to a separate account owned by the liquidity providers. They need to be + withdrawn using the `withdraw_fees` extrinsic. +- _Liquidity Tree_: A data structure used to store a pool's liquidity providers' + positions. + +### Liquidity Tree + +The _liquidity tree_ is one implementation of the `LiquiditySharesManager` trait +which the `Pool` struct uses to manage liquidity provider's positions. Liquidity +shares managers in general handles how many pool shares\_ each LP owns (similar +to pallet-balances), as well as the distribution of fees. + +The liquidity tree is a binary segment tree. Each node represents one liquidity +provider and stores their stake in the pool and how much fees they're owed. As +opposed to a naked list, the liquidity tree solves one particular problem: +Naively distributing fees every time a trade is executed requires +`O(liquidity_providers)` operations, which is unacceptable. The problem is +solved by lazily distributing fees using lazy propagation. Whenever changes are +made to a node in the tree, e.g. an LP joins, leaves or withdraws fees, fees are +then lazily propagated to the corresponding node of the tree before any other +changes are enacted. This brings the complexity of distributing fees to constant +time, while lazy propagation only requires `O(log(liquidity_providers))` +operations. + +The design of the liquidity tree is based on +[Azuro-protocol/LiquidityTree](https://github.com/Azuro-protocol/LiquidityTree). + +#### Lazy Propagation + +Fees are propagated up the tree in a "lazy" manner, i.e., the propagation +happens when liquidity providers deposit liquidity, or withdraw liquidity or +fees. The process of lazy propagation at a node `node` is as follows: + +```ignore +If node.descendant_stake == 0 then + node.fees ← node.fees + node.lazy_fees +Else + total_stake ← node.stake + node.descendant_stake + fees ← (node.descendant_stake / total_stake) * node.lazy_fees + node.fees ← node.fees + fees + remaining ← node.lazy_fees - fees + For each child in node.children() do + child.lazy_fees ← child.lazy_fees + (child.descendant_stake / total_stake) * remaining + End For +End If +node.lazy_fees ← 0 +``` + +This means that at every node, the remaining lazy fees are distributed pro rata +between the current node and its two children. With the total stake defined as +the sum of the current node's stake and the stake of its descendants, the +process is as follows: + +- The current node's fees are increased by `node.stake / total_stake` of the + remaining lazy fees. +- Each child's lazy fees are increased by `child.descendant_stake / total_stake` + of the remaining lazy fees. ### Notes -- The `Pool` struct tracks the reserve held in the pool account. The reserve - changes when trades are executed or the liquidity changes, but the reserve - does not take into account funds that are sent to the pool account - unsolicitedly. This fixes a griefing vector which allows an attacker to - change prices by sending funds to the pool account. -- Pool shares are not recorded using the `ZeitgeistAssetManager` trait. - Instead, they are part of the `Pool` object and can be tracked using events. -- When the native currency is used as collateral, the pallet deposits the - existential deposit to the pool account (which holds the swap fees). This is - done to ensure that small amounts of fees don't cause the entire transaction - to error with `ExistentialDeposit`. This "buffer" is removed when the pool - is destroyed. The pool account is expected to be whitelisted from dusting - for all other assets. +- The `Pool` struct tracks the reserve held in the pool account. The reserve + changes when trades are executed or the liquidity changes, but the reserve + does not take into account funds that are sent to the pool account + unsolicitedly. This fixes a griefing vector which allows an attacker to + manipulate prices by sending funds to the pool account. +- Pool shares are not recorded using the `ZeitgeistAssetManager` trait. Instead, + they are part of the `Pool` object and can be tracked using events. +- When a pool is deployed, the pallet charges the signer an extra fee to the + tune of the collateral's existential deposit. This fee is moved into the pool + account (which holds the swap fees). This is done to ensure that small amounts + of fees don't cause the entire transaction to fail with `ExistentialDeposit`. + This "buffer" is burned when the pool is destroyed. The pool account is + expected to be whitelisted from dusting for all other assets. [docslink]: ./docs/docs.pdf diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index f4612de55..dfde6ec4d 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -19,9 +19,12 @@ use super::*; use crate::{ - consts::*, traits::liquidity_shares_manager::LiquiditySharesManager, AssetOf, BalanceOf, - MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, + consts::*, + liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, + traits::{liquidity_shares_manager::LiquiditySharesManager, pool_operations::PoolOperations}, + AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; +use core::{cell::Cell, iter, marker::PhantomData}; use frame_benchmarking::v2::*; use frame_support::{ assert_ok, @@ -29,15 +32,18 @@ use frame_support::{ }; use frame_system::RawOrigin; use orml_traits::MultiCurrency; -use sp_runtime::{Perbill, SaturatedConversion}; +use sp_runtime::{traits::Get, Perbill, SaturatedConversion}; use zeitgeist_primitives::{ constants::CENT, - math::fixed::{BaseProvider, ZeitgeistBase}, + math::fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, traits::CompleteSetOperationsApi, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; +// Same behavior as `assert_ok!`, except that it wraps the call inside a transaction layer. Required +// when calling into functions marked `require_transactional` to avoid a `Transactional(NoLayer)` +// error. macro_rules! assert_ok_with_transaction { ($expr:expr) => {{ assert_ok!(with_transaction(|| match $expr { @@ -47,11 +53,133 @@ macro_rules! assert_ok_with_transaction { }}; } -fn create_market( +trait LiquidityTreeBenchmarkHelper +where + T: Config, +{ + fn calculate_min_pool_shares_amount(&self) -> BalanceOf; +} + +impl LiquidityTreeBenchmarkHelper for LiquidityTree +where + T: Config, + U: Get, +{ + /// Calculate the minimum amount required to join a liquidity tree without erroring. + fn calculate_min_pool_shares_amount(&self) -> BalanceOf { + self.total_shares() + .unwrap() + .bmul_ceil(MIN_RELATIVE_LP_POSITION_VALUE.saturated_into()) + .unwrap() + } +} + +/// Utilities for setting up benchmarks. +struct BenchmarkHelper { + current_id: Cell, + _marker: PhantomData, +} + +impl BenchmarkHelper +where + T: Config, +{ + fn new() -> Self { + BenchmarkHelper { current_id: Cell::new(0), _marker: PhantomData } + } + + /// Return an iterator which ranges over _unused_ accounts. + fn accounts(&self) -> impl Iterator + '_ { + iter::from_fn(move || { + let id = self.current_id.get(); + self.current_id.set(id + 1); + Some(account("", id, 0)) + }) + } + + /// Populates the market's liquidity tree until almost full with one free leaf remaining. + /// Ensures that the tree has the expected configuration of nodes. + fn populate_liquidity_tree_with_free_leaf(&self, market_id: MarketIdOf) { + let max_node_count = LiquidityTreeOf::::max_node_count(); + let last = (max_node_count - 1) as usize; + for caller in self.accounts().take(last - 1) { + add_liquidity_provider_to_market::(market_id, caller); + } + // Verify that we've got the right number of nodes. + let pool = Pools::::get(market_id).unwrap(); + assert_eq!(pool.liquidity_shares_manager.nodes.len(), last); + } + + /// Populates the market's liquidity tree until full. The `caller` is the owner of the last + /// leaf. Ensures that the tree has the expected configuration of nodes. + fn populate_liquidity_tree_until_full(&self, market_id: MarketIdOf, caller: T::AccountId) { + // Start by populating the entire tree except for one node. `caller` will then join and + // occupy the last node. + self.populate_liquidity_tree_with_free_leaf(market_id); + add_liquidity_provider_to_market::(market_id, caller); + // Verify that we've got the right number of nodes. + let pool = Pools::::get(market_id).unwrap(); + let max_node_count = LiquidityTreeOf::::max_node_count(); + assert_eq!(pool.liquidity_shares_manager.nodes.len(), max_node_count as usize); + } + + /// Populates the market's liquidity tree until almost full with one abandoned node remaining. + fn populate_liquidity_tree_with_abandoned_node(&self, market_id: MarketIdOf) { + // Start by populating the entire tree. `caller` will own one of the leaves, withdraw their + // stake, leaving an abandoned node at a leaf. + let caller = self.accounts().next().unwrap(); + self.populate_liquidity_tree_until_full(market_id, caller.clone()); + let pool = Pools::::get(market_id).unwrap(); + let pool_shares_amount = pool.liquidity_shares_manager.shares_of(&caller).unwrap(); + assert_ok!(NeoSwaps::::exit( + RawOrigin::Signed(caller).into(), + market_id, + pool_shares_amount, + vec![Zero::zero(); pool.assets().len()] + )); + // Verify that we've got the right number of nodes. + let pool = Pools::::get(market_id).unwrap(); + let max_node_count = LiquidityTreeOf::::max_node_count(); + assert_eq!(pool.liquidity_shares_manager.nodes.len(), max_node_count as usize); + let last = max_node_count - 1; + assert_eq!(pool.liquidity_shares_manager.abandoned_nodes, vec![last]); + } + + /// Run the common setup of `join` benchmarks and return the target market's ID and Bob's + /// address (who will execute the call). + /// + /// Parameters: + /// + /// - `market_id`: The ID to set the benchmark up for. + /// - `complete_set_amount`: The amount of complete sets to buy for Bob. + fn set_up_liquidity_benchmark( + &self, + market_id: MarketIdOf, + account: AccountIdOf, + complete_set_amount: Option>, + ) { + let pool = Pools::::get(market_id).unwrap(); + let multiplier = MIN_RELATIVE_LP_POSITION_VALUE + 1_000; + let complete_set_amount = complete_set_amount.unwrap_or_else(|| { + pool.reserves.values().max().unwrap().bmul_ceil(multiplier.saturated_into()).unwrap() + }); + assert_ok!(T::MultiCurrency::deposit(pool.collateral, &account, complete_set_amount)); + assert_ok_with_transaction!(T::CompleteSetOperations::buy_complete_set( + account, + market_id, + complete_set_amount, + )); + } +} + +fn create_market( caller: T::AccountId, base_asset: AssetOf, asset_count: AssetIndexType, -) -> MarketIdOf { +) -> MarketIdOf +where + T: Config, +{ let market = Market { base_asset, creation: MarketCreation::Permissionless, @@ -88,7 +216,10 @@ fn create_market_and_deploy_pool( base_asset: AssetOf, asset_count: AssetIndexType, amount: BalanceOf, -) -> MarketIdOf { +) -> MarketIdOf +where + T: Config, +{ let market_id = create_market::(caller.clone(), base_asset, asset_count); let total_cost = amount + T::MultiCurrency::minimum_balance(base_asset); assert_ok!(T::MultiCurrency::deposit(base_asset, &caller, total_cost)); @@ -107,6 +238,43 @@ fn create_market_and_deploy_pool( market_id } +fn deposit_fees(market_id: MarketIdOf, amount: BalanceOf) +where + T: Config, +{ + let mut pool = Pools::::get(market_id).unwrap(); + assert_ok!(T::MultiCurrency::deposit(pool.collateral, &pool.account_id, amount)); + assert_ok!(pool.liquidity_shares_manager.deposit_fees(amount)); + Pools::::insert(market_id, pool); +} + +// Let `caller` join the pool of `market_id` after adding the required funds to their account. +fn add_liquidity_provider_to_market(market_id: MarketIdOf, caller: AccountIdOf) +where + T: Config, +{ + let pool = Pools::::get(market_id).unwrap(); + // Buy a little more to account for rounding. + let pool_shares_amount = + pool.liquidity_shares_manager.calculate_min_pool_shares_amount() + _1.saturated_into(); + let ratio = + pool_shares_amount.bdiv(pool.liquidity_shares_manager.total_shares().unwrap()).unwrap(); + let complete_set_amount = + pool.reserves.values().max().unwrap().bmul_ceil(ratio).unwrap() * 2u8.into(); + assert_ok!(T::MultiCurrency::deposit(pool.collateral, &caller, complete_set_amount)); + assert_ok_with_transaction!(T::CompleteSetOperations::buy_complete_set( + caller.clone(), + market_id, + complete_set_amount, + )); + assert_ok!(NeoSwaps::::join( + RawOrigin::Signed(caller.clone()).into(), + market_id, + pool_shares_amount, + vec![u128::MAX.saturated_into(); pool.assets().len()] + )); +} + #[benchmarks] mod benchmarks { use super::*; @@ -114,7 +282,7 @@ mod benchmarks { /// FIXME Replace hardcoded variant with `{ MAX_ASSETS as u32 }` as soon as possible. #[benchmark] fn buy(n: Linear<2, 128>) { - let alice: T::AccountId = whitelisted_caller(); + let alice = whitelisted_caller(); let base_asset = Asset::Ztg; let asset_count = n.try_into().unwrap(); let market_id = create_market_and_deploy_pool::( @@ -127,7 +295,8 @@ mod benchmarks { let amount_in = _1.saturated_into(); let min_amount_out = 0u8.saturated_into(); - let bob: T::AccountId = whitelisted_caller(); + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); assert_ok!(T::MultiCurrency::deposit(base_asset, &bob, amount_in)); #[extrinsic_call] @@ -136,7 +305,7 @@ mod benchmarks { #[benchmark] fn sell(n: Linear<2, 128>) { - let alice: T::AccountId = whitelisted_caller(); + let alice = whitelisted_caller(); let base_asset = Asset::Ztg; let asset_count = n.try_into().unwrap(); let market_id = create_market_and_deploy_pool::( @@ -149,15 +318,18 @@ mod benchmarks { let amount_in = _1.saturated_into(); let min_amount_out = 0u8.saturated_into(); - let bob: T::AccountId = whitelisted_caller(); + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); assert_ok!(T::MultiCurrency::deposit(asset_in, &bob, amount_in)); #[extrinsic_call] _(RawOrigin::Signed(bob), market_id, asset_count, asset_in, amount_in, min_amount_out); } + // Bob already owns a leaf at maximum depth in the tree but decides to increase his stake. + // Maximum propagation steps thanks to maximum depth. #[benchmark] - fn join(n: Linear<2, 128>) { + fn join_in_place(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; let asset_count = n.try_into().unwrap(); @@ -167,22 +339,97 @@ mod benchmarks { asset_count, _10.saturated_into(), ); + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); + helper.populate_liquidity_tree_until_full(market_id, bob.clone()); let pool_shares_amount = _1.saturated_into(); + // Due to rounding, we need to buy a little more than the pool share amount. + let complete_set_amount = _100.saturated_into(); + helper.set_up_liquidity_benchmark(market_id, bob.clone(), Some(complete_set_amount)); let max_amounts_in = vec![u128::MAX.saturated_into(); asset_count as usize]; - assert_ok!(T::MultiCurrency::deposit(base_asset, &alice, pool_shares_amount)); - assert_ok_with_transaction!(T::CompleteSetOperations::buy_complete_set( + // Double check that there's no abandoned node or free leaf. + let pool = Pools::::get(market_id).unwrap(); + assert_eq!(pool.liquidity_shares_manager.abandoned_nodes.len(), 0); + let max_node_count = LiquidityTreeOf::::max_node_count(); + assert_eq!(pool.liquidity_shares_manager.node_count(), max_node_count); + + #[extrinsic_call] + join(RawOrigin::Signed(bob), market_id, pool_shares_amount, max_amounts_in); + } + + // Bob joins the pool and is assigned an abandoned node at maximum depth in the tree. Maximum + // propagation steps thanks to maximum depth. + #[benchmark] + fn join_reassigned(n: Linear<2, 128>) { + let alice: T::AccountId = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = n.try_into().unwrap(); + let market_id = create_market_and_deploy_pool::( alice.clone(), - market_id, - pool_shares_amount - )); + base_asset, + asset_count, + _10.saturated_into(), + ); + let helper = BenchmarkHelper::::new(); + helper.populate_liquidity_tree_with_abandoned_node(market_id); + let pool = Pools::::get(market_id).unwrap(); + let pool_shares_amount = pool.liquidity_shares_manager.calculate_min_pool_shares_amount(); + // Due to rounding, we need to buy a little more than the pool share amount. + let bob = helper.accounts().next().unwrap(); + helper.set_up_liquidity_benchmark(market_id, bob.clone(), None); + let max_amounts_in = vec![u128::MAX.saturated_into(); asset_count as usize]; + + // Double check that there's an abandoned node. + assert_eq!(pool.liquidity_shares_manager.abandoned_nodes.len(), 1); + + #[extrinsic_call] + join(RawOrigin::Signed(bob), market_id, pool_shares_amount, max_amounts_in); + + let pool = Pools::::get(market_id).unwrap(); + assert_eq!(pool.liquidity_shares_manager.abandoned_nodes.len(), 0); + } + + // Bob joins the pool and is assigned a leaf at maximum depth in the tree. Maximum propagation + // steps thanks to maximum depth. + #[benchmark] + fn join_leaf(n: Linear<2, 128>) { + let alice: T::AccountId = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = n.try_into().unwrap(); + let market_id = create_market_and_deploy_pool::( + alice.clone(), + base_asset, + asset_count, + _10.saturated_into(), + ); + let helper = BenchmarkHelper::::new(); + helper.populate_liquidity_tree_with_free_leaf(market_id); + let pool = Pools::::get(market_id).unwrap(); + let pool_shares_amount = pool.liquidity_shares_manager.calculate_min_pool_shares_amount(); + // Due to rounding, we need to buy a little more than the pool share amount. + let bob = helper.accounts().next().unwrap(); + helper.set_up_liquidity_benchmark(market_id, bob.clone(), None); + let max_amounts_in = vec![u128::MAX.saturated_into(); asset_count as usize]; + + // Double-check that there's a free leaf. + let max_node_count = LiquidityTreeOf::::max_node_count(); + assert_eq!(pool.liquidity_shares_manager.node_count(), max_node_count - 1); #[extrinsic_call] - _(RawOrigin::Signed(alice), market_id, pool_shares_amount, max_amounts_in); + join(RawOrigin::Signed(bob), market_id, pool_shares_amount, max_amounts_in); + + // Ensure that the leaf is taken. + let pool = Pools::::get(market_id).unwrap(); + assert_eq!(pool.liquidity_shares_manager.node_count(), max_node_count); } - // There are two execution paths in `exit`: 1) Keep pool alive or 2) destroy it. Clearly 1) is - // heavier. + // Worst-case benchmark of `exit`. A couple of conditions must be met to get the worst-case: + // + // - Caller withdraws their total share (the node is then abandoned, resulting in extra writes). + // - The pool is kept alive (changing the pool struct instead of destroying it is heavier). + // - The caller owns a leaf of maximum depth (equivalent to the second condition unless the tree + // has max depth zero). #[benchmark] fn exit(n: Linear<2, 128>) { let alice: T::AccountId = whitelisted_caller(); @@ -194,35 +441,45 @@ mod benchmarks { asset_count, _10.saturated_into(), ); - let pool_shares_amount = _1.saturated_into(); - let min_amounts_out = vec![0u8.saturated_into(); asset_count as usize]; + let min_amounts_out = vec![0u8.into(); asset_count as usize]; + + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); + helper.populate_liquidity_tree_until_full(market_id, bob.clone()); + let pool = Pools::::get(market_id).unwrap(); + let pool_shares_amount = pool.liquidity_shares_manager.shares_of(&bob).unwrap(); #[extrinsic_call] - _(RawOrigin::Signed(alice), market_id, pool_shares_amount, min_amounts_out); + _(RawOrigin::Signed(bob), market_id, pool_shares_amount, min_amounts_out); assert!(Pools::::contains_key(market_id)); // Ensure we took the right turn. } + // Worst-case benchmark of `withdraw_fees`: Bob, who owns a leaf of maximum depth, withdraws his + // stake. #[benchmark] fn withdraw_fees() { let alice: T::AccountId = whitelisted_caller(); - let base_asset = Asset::Ztg; let market_id = create_market_and_deploy_pool::( alice.clone(), - base_asset, + Asset::Ztg, 2u16, _10.saturated_into(), ); - let fee_amount = _1.saturated_into(); + let helper = BenchmarkHelper::::new(); + let bob = helper.accounts().next().unwrap(); + helper.populate_liquidity_tree_until_full(market_id, bob.clone()); + helper.set_up_liquidity_benchmark(market_id, bob.clone(), None); - // Mock up some fees. - let mut pool = Pools::::get(market_id).unwrap(); - assert_ok!(T::MultiCurrency::deposit(base_asset, &pool.account_id, fee_amount)); - assert_ok!(pool.liquidity_shares_manager.deposit_fees(fee_amount)); - Pools::::insert(market_id, pool); + // Mock up some fees. Needs to be large enough to ensure that Bob's share is not smaller + // than the existential deposit. + let pool = Pools::::get(market_id).unwrap(); + let max_node_count = LiquidityTreeOf::::max_node_count() as u128; + let fee_amount = (max_node_count * _10).saturated_into(); + deposit_fees::(market_id, fee_amount); #[extrinsic_call] - _(RawOrigin::Signed(alice), market_id); + _(RawOrigin::Signed(bob), market_id); } #[benchmark] diff --git a/zrml/neo-swaps/src/consts.rs b/zrml/neo-swaps/src/consts.rs index 0ba4c0cc9..0a12edf64 100644 --- a/zrml/neo-swaps/src/consts.rs +++ b/zrml/neo-swaps/src/consts.rs @@ -29,14 +29,21 @@ pub(crate) const _2: u128 = 2 * _1; pub(crate) const _3: u128 = 3 * _1; pub(crate) const _4: u128 = 4 * _1; pub(crate) const _5: u128 = 5 * _1; +pub(crate) const _6: u128 = 6 * _1; pub(crate) const _7: u128 = 7 * _1; pub(crate) const _8: u128 = 8 * _1; pub(crate) const _9: u128 = 9 * _1; pub(crate) const _10: u128 = 10 * _1; pub(crate) const _11: u128 = 11 * _1; +pub(crate) const _12: u128 = 12 * _1; +pub(crate) const _14: u128 = 14 * _1; pub(crate) const _17: u128 = 17 * _1; pub(crate) const _20: u128 = 20 * _1; +pub(crate) const _23: u128 = 23 * _1; +pub(crate) const _24: u128 = 24 * _1; pub(crate) const _30: u128 = 30 * _1; +pub(crate) const _36: u128 = 36 * _1; +pub(crate) const _40: u128 = 40 * _1; pub(crate) const _70: u128 = 70 * _1; pub(crate) const _80: u128 = 80 * _1; pub(crate) const _100: u128 = 100 * _1; diff --git a/zrml/neo-swaps/src/helpers.rs b/zrml/neo-swaps/src/helpers.rs new file mode 100644 index 000000000..06381ac8a --- /dev/null +++ b/zrml/neo-swaps/src/helpers.rs @@ -0,0 +1,34 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] + +use crate::{BalanceOf, Config, MIN_SPOT_PRICE}; +use sp_runtime::SaturatedConversion; +use zeitgeist_primitives::math::fixed::{BaseProvider, ZeitgeistBase}; + +pub(crate) fn create_spot_prices(asset_count: u16) -> Vec> +where + T: Config, +{ + let mut result = vec![MIN_SPOT_PRICE.saturated_into(); (asset_count - 1) as usize]; + // Price distribution has no bearing on the benchmarks. + let remaining_u128 = + ZeitgeistBase::::get().unwrap() - (asset_count - 1) as u128 * MIN_SPOT_PRICE; + result.push(remaining_u128.saturated_into()); + result +} diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index af316fb33..a87329ae5 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -22,7 +22,11 @@ extern crate alloc; mod benchmarking; mod consts; +mod helpers; +mod liquidity_tree; +mod macros; mod math; +pub mod migration; mod mock; mod tests; pub mod traits; @@ -35,9 +39,10 @@ pub use pallet::*; mod pallet { use crate::{ consts::{LN_NUMERICAL_LIMIT, MAX_ASSETS}, + liquidity_tree::types::{BenchmarkInfo, LiquidityTree, LiquidityTreeError}, math::{Math, MathOps}, traits::{pool_operations::PoolOperations, LiquiditySharesManager}, - types::{FeeDistribution, Pool, SoloLp}, + types::{FeeDistribution, Pool}, weights::*, }; use alloc::{collections::BTreeMap, vec, vec::Vec}; @@ -62,15 +67,17 @@ mod pallet { constants::{BASE, CENT}, math::{ checked_ops_res::{CheckedAddRes, CheckedSubRes}, - fixed::{FixedDiv, FixedMul}, + fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, }, traits::{CompleteSetOperationsApi, DeployPoolApi, DistributeFees}, types::{Asset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + // These should not be config parameters to avoid misconfigurations. - pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + pub(crate) const EXIT_FEE: u128 = CENT / 10; /// The minimum allowed swap fee. Hardcoded to avoid misconfigurations which may lead to /// exploits. pub(crate) const MIN_SWAP_FEE: u128 = BASE / 1_000; // 0.1%. @@ -80,6 +87,9 @@ mod pallet { pub(crate) const MIN_SPOT_PRICE: u128 = CENT / 2; /// The minimum vallowed value of a pool's liquidity parameter. pub(crate) const MIN_LIQUIDITY: u128 = BASE; + /// The minimum percentage each new LP position must increase the liquidity by, represented as + /// fractional (0.0139098411 represents 1.39098411%). + pub(crate) const MIN_RELATIVE_LP_POSITION_VALUE: u128 = 139098411; // 1.39098411% pub(crate) type AccountIdOf = ::AccountId; pub(crate) type AssetOf = Asset>; @@ -88,7 +98,8 @@ mod pallet { pub(crate) type AssetIndexType = u16; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; - pub(crate) type PoolOf = Pool>; + pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; + pub(crate) type PoolOf = Pool>; #[pallet::config] pub trait Config: frame_system::Config { @@ -115,6 +126,11 @@ mod pallet { type WeightInfo: WeightInfoZeitgeist; + /// The maximum allowed liquidity tree depth per pool. Each pool can support `2^(depth + 1) + /// - 1` liquidity providers. **Must** be less than 16. + #[pallet::constant] + type MaxLiquidityTreeDepth: Get; + #[pallet::constant] type MaxSwapFee: Get>; @@ -128,8 +144,7 @@ mod pallet { pub struct Pallet(PhantomData); #[pallet::storage] - #[pallet::getter(fn pools)] - pub type Pools = StorageMap<_, Twox64Concat, MarketIdOf, PoolOf>; + pub(crate) type Pools = StorageMap<_, Twox64Concat, MarketIdOf, PoolOf>; #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] @@ -247,6 +262,10 @@ mod pallet { Unexpected, /// Specified monetary amount is zero. ZeroAmount, + /// An error occurred when handling the liquidty tree. + LiquidityTreeError(LiquidityTreeError), + /// The relative value of a new LP position is too low. + MinRelativeLiquidityThresholdViolated, } #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] @@ -371,9 +390,15 @@ mod pallet { /// /// # Complexity /// - /// `O(n)` where `n` is the number of assets in the pool. + /// `O(n + d)` where `n` is the number of assets in the pool and `d` is the depth of the + /// pool's liquidity tree, or, equivalently, `log_2(m)` where `m` is the number of liquidity + /// providers in the pool. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::join(max_amounts_in.len() as u32))] + #[pallet::weight( + T::WeightInfo::join_in_place(max_amounts_in.len() as u32) + .max(T::WeightInfo::join_reassigned(max_amounts_in.len() as u32)) + .max(T::WeightInfo::join_leaf(max_amounts_in.len() as u32)) + )] #[transactional] pub fn join( origin: OriginFor, @@ -384,8 +409,7 @@ mod pallet { let who = ensure_signed(origin)?; let asset_count = T::MarketCommons::market(&market_id)?.outcomes(); ensure!(max_amounts_in.len() == asset_count as usize, Error::::IncorrectVecLen); - Self::do_join(who, market_id, pool_shares_amount, max_amounts_in)?; - Ok(Some(T::WeightInfo::join(asset_count as u32)).into()) + Self::do_join(who, market_id, pool_shares_amount, max_amounts_in) } /// Exit the liquidity pool for the specified market. @@ -400,7 +424,9 @@ mod pallet { /// batch transaction is very useful here. /// /// If the LP withdraws all pool shares that exist, then the pool is afterwards destroyed. A - /// new pool can be deployed at any time, provided that the market is still open. + /// new pool can be deployed at any time, provided that the market is still open. If there + /// are funds left in the pool account (this can happen due to exit fees), the remaining + /// funds are destroyed. /// /// The LP is not allowed to leave a positive but small amount liquidity in the pool. If the /// liquidity parameter drops below a certain threshold, the transaction will fail. The only @@ -415,7 +441,9 @@ mod pallet { /// /// # Complexity /// - /// `O(n)` where `n` is the number of assets in the pool. + /// `O(n + d)` where `n` is the number of assets in the pool and `d` is the depth of the + /// pool's liquidity tree, or, equivalently, `log_2(m)` where `m` is the number of liquidity + /// providers in the pool. #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::exit(min_amounts_out.len() as u32))] #[transactional] @@ -479,7 +507,7 @@ mod pallet { /// /// # Complexity /// - /// `O(n)` where `n` is the number of outcomes in the specified market. + /// `O(n)` where `n` is the number of assets in the pool. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::deploy_pool(spot_prices.len() as u32))] #[transactional] @@ -639,13 +667,23 @@ mod pallet { market_id: MarketIdOf, pool_shares_amount: BalanceOf, max_amounts_in: Vec>, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); let market = T::MarketCommons::market(&market_id)?; ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); - Self::try_mutate_pool(&market_id, |pool| { + let asset_count = max_amounts_in.len() as u32; + ensure!(asset_count == market.outcomes() as u32, Error::::IncorrectAssetCount); + let benchmark_info = Self::try_mutate_pool(&market_id, |pool| { let ratio = pool_shares_amount.bdiv_ceil(pool.liquidity_shares_manager.total_shares()?)?; + // Ensure that new LPs contribute at least MIN_RELATIVE_LP_POSITION_VALUE. Note that + // this ensures that the ratio can never be zero. + if pool.liquidity_shares_manager.shares_of(&who).is_err() { + ensure!( + ratio >= MIN_RELATIVE_LP_POSITION_VALUE.saturated_into(), + Error::::MinRelativeLiquidityThresholdViolated, + ); + } let mut amounts_in = vec![]; for (&asset, &max_amount_in) in pool.assets().iter().zip(max_amounts_in.iter()) { let balance_in_pool = pool.reserve_of(&asset)?; @@ -657,7 +695,8 @@ mod pallet { for ((_, balance), amount_in) in pool.reserves.iter_mut().zip(amounts_in.iter()) { *balance = balance.checked_add_res(amount_in)?; } - pool.liquidity_shares_manager.join(&who, pool_shares_amount)?; + let benchmark_info = + pool.liquidity_shares_manager.join(&who, pool_shares_amount)?; let new_liquidity_parameter = pool .liquidity_parameter .checked_add_res(&ratio.bmul(pool.liquidity_parameter)?)?; @@ -669,8 +708,14 @@ mod pallet { amounts_in, new_liquidity_parameter, }); - Ok(()) - }) + Ok(benchmark_info) + })?; + let weight = match benchmark_info { + BenchmarkInfo::InPlace => T::WeightInfo::join_in_place(asset_count), + BenchmarkInfo::Reassigned => T::WeightInfo::join_reassigned(asset_count), + BenchmarkInfo::Leaf => T::WeightInfo::join_leaf(asset_count), + }; + Ok((Some(weight)).into()) } #[require_transactional] @@ -681,16 +726,20 @@ mod pallet { min_amounts_out: Vec>, ) -> DispatchResult { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); - let _ = T::MarketCommons::market(&market_id)?; + let market = T::MarketCommons::market(&market_id)?; Pools::::try_mutate_exists(market_id, |maybe_pool| { let pool = maybe_pool.as_mut().ok_or::(Error::::PoolNotFound.into())?; - ensure!( - pool.liquidity_shares_manager.fees == Zero::zero(), - Error::::OutstandingFees - ); - let ratio = - pool_shares_amount.bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; + let ratio = { + let mut ratio = pool_shares_amount + .bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; + if market.status == MarketStatus::Active { + let multiplier = ZeitgeistBase::>::get()? + .checked_sub_res(&EXIT_FEE.saturated_into())?; + ratio = ratio.bmul_floor(multiplier)?; + } + ratio + }; let mut amounts_out = vec![]; for (&asset, &min_amount_out) in pool.assets().iter().zip(min_amounts_out.iter()) { let balance_in_pool = pool.reserve_of(&asset)?; @@ -704,11 +753,18 @@ mod pallet { } pool.liquidity_shares_manager.exit(&who, pool_shares_amount)?; if pool.liquidity_shares_manager.total_shares()? == Zero::zero() { + let withdraw_remaining = |&asset| -> DispatchResult { + let remaining = T::MultiCurrency::free_balance(asset, &pool.account_id); + T::MultiCurrency::withdraw(asset, &pool.account_id, remaining)?; + Ok(()) + }; // FIXME We will withdraw all remaining funds (the "buffer"). This is an ugly - // hack and system should offer the option to whitelist accounts. - let remaining = - T::MultiCurrency::free_balance(pool.collateral, &pool.account_id); - T::MultiCurrency::withdraw(pool.collateral, &pool.account_id, remaining)?; + // 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() { + withdraw_remaining(asset)?; + } *maybe_pool = None; // Delete the storage map entry. Self::deposit_event(Event::::PoolDestroyed { who: who.clone(), @@ -716,12 +772,26 @@ mod pallet { amounts_out, }); } else { - let liq = pool.liquidity_parameter; - let new_liquidity_parameter = liq.checked_sub_res(&ratio.bmul(liq)?)?; - ensure!( - new_liquidity_parameter >= MIN_LIQUIDITY.saturated_into(), - Error::::LiquidityTooLow - ); + let old_liquidity_parameter = pool.liquidity_parameter; + let new_liquidity_parameter = old_liquidity_parameter + .checked_sub_res(&ratio.bmul(old_liquidity_parameter)?)?; + // If `who` still holds pool shares, check that their position has at least + // minimum size. + if let Ok(remaining_pool_shares_amount) = + pool.liquidity_shares_manager.shares_of(&who) + { + ensure!( + new_liquidity_parameter >= MIN_LIQUIDITY.saturated_into(), + Error::::LiquidityTooLow + ); + let remaining_pool_shares_ratio = remaining_pool_shares_amount + .bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; + ensure!( + remaining_pool_shares_ratio + >= MIN_RELATIVE_LP_POSITION_VALUE.saturated_into(), + Error::::MinRelativeLiquidityThresholdViolated + ); + } pool.liquidity_parameter = new_liquidity_parameter; Self::deposit_event(Event::::ExitExecuted { who: who.clone(), @@ -759,7 +829,6 @@ mod pallet { ) -> DispatchResult { ensure!(!Pools::::contains_key(market_id), Error::::DuplicatePool); let market = T::MarketCommons::market(&market_id)?; - ensure!(market.creator == who, Error::::NotAllowed); ensure!(market.status == MarketStatus::Active, Error::::MarketNotActive); ensure!(market.scoring_rule == ScoringRule::Lmsr, Error::::InvalidTradingMechanism); let asset_count = spot_prices.len(); @@ -803,7 +872,7 @@ mod pallet { reserves: reserves.clone(), collateral, liquidity_parameter, - liquidity_shares_manager: SoloLp::new(who.clone(), amount), + 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 @@ -878,9 +947,12 @@ mod pallet { }) } - pub(crate) fn try_mutate_pool(market_id: &MarketIdOf, mutator: F) -> DispatchResult + pub(crate) fn try_mutate_pool( + market_id: &MarketIdOf, + mutator: F, + ) -> Result where - F: FnMut(&mut PoolOf) -> DispatchResult, + F: FnMut(&mut PoolOf) -> Result, { Pools::::try_mutate(market_id, |maybe_pool| { maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator) diff --git a/zrml/neo-swaps/src/liquidity_tree/macros.rs b/zrml/neo-swaps/src/liquidity_tree/macros.rs new file mode 100644 index 000000000..6db3979d1 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/macros.rs @@ -0,0 +1,70 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +/// Asserts that a liquidity tree has the specified state. +/// +/// Parameters: +/// +/// - `tree`: The `LiquidityTree` to check. +/// - `expected_nodes`: The expected `tree.nodes`. +/// - `expected_accounts_to_index`: The expected `tree.accounts_to_index`. +/// - `expected_abandoned_nodes`: The expected `tree.abandoned_nodes`. +#[macro_export] +macro_rules! assert_liquidity_tree_state { + ( + $tree:expr, + $expected_nodes:expr, + $expected_account_to_index:expr, + $expected_abandoned_nodes:expr + $(,)? + ) => { + let actual_nodes = $tree.nodes.clone().into_inner(); + let max_len = std::cmp::max($expected_nodes.len(), actual_nodes.len()); + let mut error = false; + for index in 0..max_len { + match ($expected_nodes.get(index), actual_nodes.get(index)) { + (Some(exp), Some(act)) => { + if exp != act { + error = true; + eprintln!( + "assert_liquidity_tree_state: Mismatched node at index {}", + index, + ); + eprintln!(" Expected node: {:?}", exp); + eprintln!(" Actual node: {:?}", act); + } + } + (None, Some(act)) => { + error = true; + eprintln!("assert_liquidity_tree_state: Extra node at index {}", index); + eprintln!(" {:?}", act); + } + (Some(exp), None) => { + error = true; + eprintln!("assert_liquidity_tree_state: Missing node at index {}", index); + eprintln!(" {:?}", exp); + } + (None, None) => break, + } + } + if error { + panic!(); + } + assert_eq!($expected_account_to_index, $tree.account_to_index.clone().into_inner()); + assert_eq!($expected_abandoned_nodes, $tree.abandoned_nodes.clone().into_inner()); + }; +} diff --git a/zrml/neo-swaps/src/liquidity_tree/mod.rs b/zrml/neo-swaps/src/liquidity_tree/mod.rs new file mode 100644 index 000000000..05d14b703 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +mod macros; +mod tests; +pub(crate) mod traits; +pub(crate) mod types; diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/deposit_fees.rs b/zrml/neo-swaps/src/liquidity_tree/tests/deposit_fees.rs new file mode 100644 index 000000000..e1218c9ce --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/deposit_fees.rs @@ -0,0 +1,45 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn deposit_fees_works_root() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + let amount = _12; + nodes[0].lazy_fees += amount; + tree.deposit_fees(amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn deposit_fees_works_no_root() { + let mut tree = utility::create_test_tree(); + tree.nodes[0].account = None; + tree.nodes[1].stake = Zero::zero(); + tree.nodes[2].fees = Zero::zero(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + let amount = _12; + nodes[0].lazy_fees += amount; + tree.deposit_fees(amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/exit.rs b/zrml/neo-swaps/src/liquidity_tree/tests/exit.rs new file mode 100644 index 000000000..fc34abbda --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/exit.rs @@ -0,0 +1,173 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; +use crate::{AccountIdOf, BalanceOf}; +use test_case::test_case; + +#[test_case(false)] +#[test_case(true)] +fn exit_root_works(withdraw_all: bool) { + let mut tree = utility::create_test_tree(); + // Remove lazy fees on the path to the node (and actual fees from the node). + tree.nodes[0].lazy_fees = Zero::zero(); + tree.nodes[0].fees = Zero::zero(); + + let mut nodes = tree.nodes.clone().into_inner(); + let amount = if withdraw_all { _1 } else { _1_2 }; + let account = 3; + nodes[0].stake -= amount; + let mut account_to_index = tree.account_to_index.clone().into_inner(); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + if withdraw_all { + nodes[0].account = None; + account_to_index.remove(&account); + abandoned_nodes.push(0); + } + + tree.exit(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test_case(false)] +#[test_case(true)] +fn exit_middle_works(withdraw_all: bool) { + let mut tree = utility::create_test_tree(); + // Remove lazy fees on the path to the node (and actual fees from the node). + tree.nodes[0].lazy_fees = Zero::zero(); + tree.nodes[1].lazy_fees = Zero::zero(); + tree.nodes[3].lazy_fees = Zero::zero(); + tree.nodes[3].fees = Zero::zero(); + + let mut nodes = tree.nodes.clone().into_inner(); + let amount = if withdraw_all { _3 } else { _1 }; + let account = 5; + nodes[0].descendant_stake -= amount; + nodes[1].descendant_stake -= amount; + nodes[3].stake -= amount; + let mut account_to_index = tree.account_to_index.clone().into_inner(); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + if withdraw_all { + nodes[3].account = None; + account_to_index.remove(&account); + abandoned_nodes.push(3); + } + + tree.exit(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test_case(false)] +#[test_case(true)] +fn exit_leaf_works(withdraw_all: bool) { + let mut tree = utility::create_test_tree(); + // Remove lazy fees on the path to the node (and actual fees from the node). + tree.nodes[0].lazy_fees = Zero::zero(); + tree.nodes[1].lazy_fees = Zero::zero(); + tree.nodes[3].lazy_fees = Zero::zero(); + tree.nodes[7].lazy_fees = Zero::zero(); + tree.nodes[7].fees = Zero::zero(); + + let mut nodes = tree.nodes.clone().into_inner(); + let amount = if withdraw_all { _12 } else { _1 }; + let account = 6; + nodes[0].descendant_stake -= amount; + nodes[1].descendant_stake -= amount; + nodes[3].descendant_stake -= amount; + nodes[7].stake -= amount; + let mut account_to_index = tree.account_to_index.clone().into_inner(); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + if withdraw_all { + nodes[7].account = None; + account_to_index.remove(&account); + abandoned_nodes.push(7); + } + + tree.exit(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test_case(3, _1 + 1)] +#[test_case(9, _3 + 1)] +#[test_case(5, _3 + 1)] +#[test_case(7, _1 + 1)] +#[test_case(6, _12 + 1)] +#[test_case(8, _4 + 1)] +fn exit_fails_on_insufficient_stake(account: AccountIdOf, amount: BalanceOf) { + let mut tree = utility::create_test_tree(); + // Clear unclaimed fees. + for node in tree.nodes.iter_mut() { + node.fees = Zero::zero(); + node.lazy_fees = Zero::zero(); + } + assert_err!( + tree.exit(&account, amount), + LiquidityTreeError::InsufficientStake.into_dispatch_error::(), + ); +} + +#[test] +fn exit_fails_on_unclaimed_fees_at_root() { + let mut tree = utility::create_test_tree(); + // Clear unclaimed fees except for root. + tree.nodes[0].lazy_fees = _1; + tree.nodes[1].lazy_fees = Zero::zero(); + tree.nodes[3].fees = Zero::zero(); + tree.nodes[3].lazy_fees = Zero::zero(); + assert_err!( + tree.exit(&5, 1), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::() + ); +} + +#[test] +fn exit_fails_on_unclaimed_fees_on_middle_of_path() { + let mut tree = utility::create_test_tree(); + // Clear unclaimed fees except for the middle node. + tree.nodes[3].fees = Zero::zero(); + tree.nodes[3].lazy_fees = Zero::zero(); + assert_err!( + tree.exit(&5, 1), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::() + ); +} + +#[test] +fn exit_fails_on_unclaimed_fees_at_last_node_due_to_lazy_fees() { + let mut tree = utility::create_test_tree(); + // Clear unclaimed fees except for the last node. + tree.nodes[1].lazy_fees = Zero::zero(); + // This ensures that the error is caused by propagated lazy fees sitting in the node. + tree.nodes[3].fees = Zero::zero(); + assert_err!( + tree.exit(&5, 1), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::() + ); +} + +#[test] +fn exit_fails_on_unclaimed_fees_at_last_node_due_to_fees() { + let mut tree = utility::create_test_tree(); + // Clear unclaimed fees except for the last node. + tree.nodes[1].lazy_fees = Zero::zero(); + // This ensures that the error is caused by normal fees. + tree.nodes[3].lazy_fees = Zero::zero(); + assert_err!( + tree.exit(&5, 1), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::() + ); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/join.rs b/zrml/neo-swaps/src/liquidity_tree/tests/join.rs new file mode 100644 index 000000000..973b0241d --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/join.rs @@ -0,0 +1,196 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn join_in_place_works_root() { + let mut tree = utility::create_test_tree(); + tree.nodes[0].lazy_fees = _36; + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + let amount = _2; + nodes[0].stake += amount; + // Distribute lazy fees of node at index 0. + nodes[0].fees += 15_000_000_000; // 1.5 + nodes[0].lazy_fees = Zero::zero(); + nodes[1].lazy_fees += 300_000_000_000; // 30 + nodes[2].lazy_fees += 45_000_000_000; // 4.5 + tree.join(&3, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_in_place_works_leaf() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + let amount = _2; + nodes[0].descendant_stake += amount; + nodes[1].descendant_stake += amount; + nodes[3].descendant_stake += amount; + nodes[7].stake += amount; + // Distribute lazy fees of node at index 1 and 3. + nodes[1].lazy_fees = Zero::zero(); + nodes[3].fees += 12_000_000_000; // 1.2 + nodes[3].lazy_fees = Zero::zero(); + nodes[4].lazy_fees += _1; + nodes[7].fees += 78_000_000_000; // 7.8 (4.8 propagated and 3 lazy fees in place) + nodes[7].lazy_fees = Zero::zero(); + tree.join(&6, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_in_place_works_middle() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + let amount = _2; + nodes[0].descendant_stake += amount; + nodes[1].descendant_stake += amount; + nodes[3].stake += amount; + // Distribute lazy fees of node at index 1 and 3. + nodes[1].lazy_fees = Zero::zero(); + nodes[3].fees += 12_000_000_000; // 1.2 + nodes[3].lazy_fees = 0; + nodes[4].lazy_fees += _1; + nodes[7].lazy_fees += 48_000_000_000; // 4.8 + tree.join(&5, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_reassigned_works_middle() { + let mut tree = utility::create_test_tree(); + // Manipulate which node is joined by changing the order of abandoned nodes. + tree.abandoned_nodes[0] = 8; + tree.abandoned_nodes[3] = 1; + let mut nodes = tree.nodes.clone().into_inner(); + let account = 99; + let amount = _2; + + // Add new account. + nodes[0].descendant_stake += amount; + nodes[1].account = Some(account); + nodes[1].stake = amount; + nodes[1].lazy_fees = Zero::zero(); + // Propagate fees of node at index 1. + nodes[3].lazy_fees += _3; + nodes[4].lazy_fees += _1; + let mut account_to_index = tree.account_to_index.clone().into_inner(); + account_to_index.insert(account, 1); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + abandoned_nodes.pop(); + + tree.join(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_reassigned_works_root() { + let mut tree = utility::create_test_tree(); + // Store original test tree. + let mut nodes = tree.nodes.clone().into_inner(); + // Manipulate test tree so that it looks like root was abandoned. + tree.nodes[0].account = None; + tree.nodes[0].stake = Zero::zero(); + tree.nodes[0].fees = Zero::zero(); + tree.nodes[0].lazy_fees = 345_000_000_000; // 34.5 + tree.abandoned_nodes.try_push(0).unwrap(); + tree.account_to_index.remove(&3); + + // Prepare expected data. The only things that have changed are that the 34.5 units of + // collateral are propagated to the nodes of depth 1; and the root. + let account = 99; + let amount = _3; + nodes[0].account = Some(account); + nodes[0].stake = amount; + nodes[0].fees = Zero::zero(); + nodes[0].lazy_fees = Zero::zero(); + nodes[1].lazy_fees += _30; + nodes[2].lazy_fees += 45_000_000_000; // 4.5 + let mut account_to_index = tree.account_to_index.clone().into_inner(); + account_to_index.insert(account, 0); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + abandoned_nodes.pop(); + + tree.join(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_reassigned_works_leaf() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account = 99; + let amount = _3; + nodes[0].descendant_stake += amount; + nodes[1].descendant_stake += amount; + nodes[3].descendant_stake += amount; + nodes[8].account = Some(account); + nodes[8].stake = amount; + // Distribute lazy fees of node at index 1, 3 and 7 (same as join_reassigned_works_middle). + nodes[1].lazy_fees = Zero::zero(); + nodes[3].fees += 12_000_000_000; // 1.2 + nodes[3].lazy_fees = 0; + nodes[4].lazy_fees += _1; + nodes[7].lazy_fees += 48_000_000_000; // 4.8 + + let mut account_to_index = tree.account_to_index.clone().into_inner(); + account_to_index.insert(account, 8); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + abandoned_nodes.pop(); + + tree.join(&account, amount).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_in_place_works_if_tree_is_full() { + let mut tree = utility::create_full_tree(); + // Remove one node. + tree.nodes[0].descendant_stake -= tree.nodes[2].stake; + tree.nodes[2].account = None; + tree.nodes[2].stake = Zero::zero(); + tree.account_to_index.remove(&2); + tree.abandoned_nodes.try_push(2).unwrap(); + let mut nodes = tree.nodes.clone().into_inner(); + let account = 99; + let stake = 2; + nodes[2].account = Some(account); + nodes[2].stake = stake; + nodes[0].descendant_stake += stake; + let mut account_to_index = tree.account_to_index.clone().into_inner(); + let mut abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + account_to_index.insert(account, 2); + abandoned_nodes.pop(); + tree.join(&account, stake).unwrap(); + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn join_new_fails_if_tree_is_full() { + let mut tree = utility::create_full_tree(); + assert_err!( + tree.join(&99, _1), + LiquidityTreeError::TreeIsFull.into_dispatch_error::() + ); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs new file mode 100644 index 000000000..9b23c8e38 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/mod.rs @@ -0,0 +1,175 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] + +use crate::{ + assert_liquidity_tree_state, + consts::*, + create_b_tree_map, + liquidity_tree::{ + traits::liquidity_tree_helper::LiquidityTreeHelper, + types::{LiquidityTreeError, Node}, + }, + mock::Runtime, + traits::liquidity_shares_manager::LiquiditySharesManager, + LiquidityTreeOf, +}; +use alloc::collections::BTreeMap; +use frame_support::assert_err; +use sp_runtime::traits::Zero; + +mod deposit_fees; +mod exit; +mod join; +mod shares_of; +mod total_shares; +mod withdraw_fees; + +/// Most tests use the same pattern: +/// +/// - Create a test tree. In some cases the test tree needs to be modified as part of the test +/// setup. +/// - Clone the contents of the tests tree and modify them to obtain the expected state of the tree +/// after executing the test. +/// - Run the test. +/// - Verify state. +mod utility { + use super::*; + + /// Create the following liquidity tree: + /// + /// (3, _1, _2, _23, 0) + /// / \ + /// (None, 0, 0, _20, _4) (9, _3, _5, 0, 0) + /// / \ / \ + /// (5, _3, _1, _12, _3) (7, _1, _1, _4, _3) (None, 0, 0, 0, 0) (None, 0, 0, 0, 0) + /// / \ / + /// (6, _12, _1, 0, _3) (None, 0, 0, 0, 0) (8, _4, _1, 0, 0) + /// + /// This tree is used in most tests, but will sometime have to be modified. + pub(super) fn create_test_tree() -> LiquidityTreeOf { + LiquidityTreeOf:: { + nodes: vec![ + // Root + Node:: { + account: Some(3), + stake: _1, + fees: _2, + descendant_stake: _23, + lazy_fees: Zero::zero(), + }, + // Depth 1 + Node:: { + account: None, + stake: Zero::zero(), + fees: Zero::zero(), + descendant_stake: _20, + lazy_fees: _4, + }, + Node:: { + account: Some(9), + stake: _3, + fees: _5, + descendant_stake: Zero::zero(), + lazy_fees: Zero::zero(), + }, + // Depth 2 + Node:: { + account: Some(5), + stake: _3, + fees: _1, + descendant_stake: _12, + lazy_fees: _3, + }, + Node:: { + account: Some(7), + stake: _1, + fees: _1, + descendant_stake: _4, + lazy_fees: _3, + }, + Node:: { + account: None, + stake: Zero::zero(), + fees: Zero::zero(), + descendant_stake: Zero::zero(), + lazy_fees: Zero::zero(), + }, + Node:: { + account: None, + stake: Zero::zero(), + fees: Zero::zero(), + descendant_stake: Zero::zero(), + lazy_fees: Zero::zero(), + }, + // Depth 3 + Node:: { + account: Some(6), + stake: _12, + fees: _1, + descendant_stake: Zero::zero(), + lazy_fees: _3, + }, + Node:: { + account: None, + stake: Zero::zero(), + fees: Zero::zero(), + descendant_stake: Zero::zero(), + lazy_fees: Zero::zero(), + }, + Node:: { + account: Some(8), + stake: _4, + fees: _1, + descendant_stake: Zero::zero(), + lazy_fees: Zero::zero(), + }, + ] + .try_into() + .unwrap(), + account_to_index: create_b_tree_map!({3 => 0, 9 => 2, 5 => 3, 7 => 4, 6 => 7, 8 => 9}) + .try_into() + .unwrap(), + abandoned_nodes: vec![1, 5, 6, 8].try_into().unwrap(), + } + } + + /// Create a full tree. All nodes have the same stake of 1. + pub(super) fn create_full_tree() -> LiquidityTreeOf { + let max_depth = LiquidityTreeOf::::max_depth(); + let node_count = LiquidityTreeOf::::max_node_count(); + let nodes = (0..node_count) + .map(|a| Node::::new(a as u128, 1)) + .collect::>() + .try_into() + .unwrap(); + let account_to_index = + (0..node_count).map(|a| (a as u128, a)).collect::>().try_into().unwrap(); + let mut tree = LiquidityTreeOf:: { + nodes, + account_to_index, + abandoned_nodes: vec![].try_into().unwrap(), + }; + // Nodes have the wrong descendant stake at this point, so let's fix that. + for (index, node) in tree.nodes.iter_mut().enumerate() { + let exp = max_depth + 1 - (index + 1).checked_ilog2().unwrap(); + node.descendant_stake = 2u128.pow(exp) - 2; + } + tree + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/shares_of.rs b/zrml/neo-swaps/src/liquidity_tree/tests/shares_of.rs new file mode 100644 index 000000000..816ea02ef --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/shares_of.rs @@ -0,0 +1,29 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn shares_of_works() { + let tree = utility::create_test_tree(); + assert_eq!(tree.shares_of(&3).unwrap(), _1); + assert_eq!(tree.shares_of(&9).unwrap(), _3); + assert_eq!(tree.shares_of(&5).unwrap(), _3); + assert_eq!(tree.shares_of(&7).unwrap(), _1); + assert_eq!(tree.shares_of(&6).unwrap(), _12); + assert_eq!(tree.shares_of(&8).unwrap(), _4); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/total_shares.rs b/zrml/neo-swaps/src/liquidity_tree/tests/total_shares.rs new file mode 100644 index 000000000..94dd61b22 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/total_shares.rs @@ -0,0 +1,24 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn total_shares_works() { + let tree = utility::create_test_tree(); + assert_eq!(tree.total_shares().unwrap(), _24); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/tests/withdraw_fees.rs b/zrml/neo-swaps/src/liquidity_tree/tests/withdraw_fees.rs new file mode 100644 index 000000000..ff4a0a77e --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/tests/withdraw_fees.rs @@ -0,0 +1,73 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn withdraw_fees_works_root() { + let mut tree = utility::create_test_tree(); + tree.nodes[0].lazy_fees = _36; + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + + // Distribute lazy fees of node at index 0. + nodes[0].fees = Zero::zero(); + nodes[0].lazy_fees = Zero::zero(); + nodes[1].lazy_fees += 300_000_000_000; // 30 + nodes[2].lazy_fees += 45_000_000_000; // 4.5 + + assert_eq!(tree.withdraw_fees(&3).unwrap(), 35_000_000_000); // 2 (fees) + 1.5 (lazy) + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn withdraw_fees_works_middle() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + + // Distribute lazy fees of node at index 1, 3 and 7 (same as join_reassigned_works_middle). + nodes[1].lazy_fees = Zero::zero(); + nodes[3].fees = Zero::zero(); + nodes[3].lazy_fees = Zero::zero(); + nodes[4].lazy_fees += _1; + nodes[7].lazy_fees += 48_000_000_000; // 4.8 + + assert_eq!(tree.withdraw_fees(&5).unwrap(), 22_000_000_000); // 1 (fees) + 1.2 (lazy) + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} + +#[test] +fn withdraw_fees_works_leaf() { + let mut tree = utility::create_test_tree(); + let mut nodes = tree.nodes.clone().into_inner(); + let account_to_index = tree.account_to_index.clone().into_inner(); + let abandoned_nodes = tree.abandoned_nodes.clone().into_inner(); + + // Distribute lazy fees of node at index 1, 3 and 7 (same as join_reassigned_works_middle). + nodes[1].lazy_fees = Zero::zero(); + nodes[3].fees += 12_000_000_000; // 1.2 + nodes[3].lazy_fees = Zero::zero(); + nodes[4].lazy_fees += _1; + nodes[7].fees = Zero::zero(); + nodes[7].lazy_fees = Zero::zero(); + + assert_eq!(tree.withdraw_fees(&6).unwrap(), 88_000_000_000); // 1 (fees) + 7.8 (lazy) + assert_liquidity_tree_state!(tree, nodes, account_to_index, abandoned_nodes); +} diff --git a/zrml/neo-swaps/src/liquidity_tree/traits/liquidity_tree_helper.rs b/zrml/neo-swaps/src/liquidity_tree/traits/liquidity_tree_helper.rs new file mode 100644 index 000000000..e73073d08 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/traits/liquidity_tree_helper.rs @@ -0,0 +1,114 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + liquidity_tree::types::{LiquidityTreeChildIndices, UpdateDescendantStakeOperation}, + BalanceOf, Config, +}; +use alloc::vec::Vec; +use sp_runtime::{DispatchError, DispatchResult}; + +/// A collection of member functions used in the implementation of `LiquiditySharesManager` for +/// `LiquidityTree`. +pub(crate) trait LiquidityTreeHelper +where + T: Config, +{ + type Node; + + /// Propagate lazy fees from the tree's root to the node at `index`. + /// + /// Propagation includes moving the part of the lazy fees of each node on the path to the node + /// at `index` to the node's fees. + /// + /// Assuming correct state this function can only fail if there is no node at `index`. + fn propagate_fees_to_node(&mut self, index: u32) -> DispatchResult; + + /// Propagate lazy fees from the node at `index` to its children. + /// + /// Propagation includes moving the node's share of the lazy fees to the node's fees. + /// + /// Assuming correct state this function can only fail if there is no node at `index`. + fn propagate_fees(&mut self, index: u32) -> DispatchResult; + + /// Return the indices of the children of the node at `index`. + fn children(&self, index: u32) -> Result; + + /// Return the index of a node's parent; `None` if `index` is `0u32`, i.e. the node is root. + fn parent_index(&self, index: u32) -> Option; + + /// Return a path from the tree's root to the node at `index`. + /// + /// The return value is a vector of the indices of the nodes of the path, starting with the + /// root and including `index`. The parameter `opt_iterations` specifies how many iterations the + /// operation is allowed to take and can be used to terminate if the number of iterations + /// exceeds the expected amount by setting it to `None`. + fn path_to_node( + &self, + index: u32, + opt_iterations: Option, + ) -> Result, DispatchError>; + + /// Pops the most recently abandoned node's index from the stack. Returns `None` if there's no + /// abandoned node. + fn take_last_abandoned_node_index(&mut self) -> Option; + + /// Returns the index of the next free leaf; `None` if the tree is full. + fn peek_next_free_leaf(&self) -> Option; + + /// Mutate a node's ancestor's `descendant_stake` field. + /// + /// # Parameters + /// + /// - `index`: The index of the node. + /// - `delta`: The (absolute) amount by which to modfiy the descendant stake. + /// - `op`: The sign of the delta. + fn update_descendant_stake_of_ancestors( + &mut self, + index: u32, + delta: BalanceOf, + op: UpdateDescendantStakeOperation, + ) -> DispatchResult; + + /// Mutate each child of the node at `index` using `mutator`. + fn mutate_each_child(&mut self, index: u32, mutator: F) -> DispatchResult + where + F: FnMut(&mut Self::Node) -> DispatchResult; + + /// Return the number of nodes in the tree. Note that abandoned nodes are counted. + fn node_count(&self) -> u32; + + /// Get a reference to the node at `index`. + fn get_node(&self, index: u32) -> Result<&Self::Node, DispatchError>; + + /// Get a mutable reference to the node at `index`. + fn get_node_mut(&mut self, index: u32) -> Result<&mut Self::Node, DispatchError>; + + /// Get the node which belongs to `account`. + fn map_account_to_index(&self, account: &T::AccountId) -> Result; + + /// Mutate the node at `index` using `mutator`. + fn mutate_node(&mut self, index: u32, mutator: F) -> DispatchResult + where + F: FnOnce(&mut Self::Node) -> DispatchResult; + + /// Return the maximum allowed depth of the tree. + fn max_depth() -> u32; + + /// Return the maximum allowed amount of nodes in the tree. + fn max_node_count() -> u32; +} diff --git a/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs new file mode 100644 index 000000000..08dcddd9d --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/traits/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +pub(crate) mod liquidity_tree_helper; + +pub(crate) use liquidity_tree_helper::*; diff --git a/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree.rs b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree.rs new file mode 100644 index 000000000..5986b85f2 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree.rs @@ -0,0 +1,430 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + liquidity_tree::{ + traits::LiquidityTreeHelper, + types::{ + LiquidityTreeChildIndices, LiquidityTreeError, LiquidityTreeMaxNodes, Node, + StorageOverflowError, UpdateDescendantStakeOperation, + }, + }, + traits::LiquiditySharesManager, + BalanceOf, Config, Error, +}; +use alloc::{vec, vec::Vec}; +use frame_support::{ + ensure, + pallet_prelude::RuntimeDebugNoBound, + storage::{bounded_btree_map::BoundedBTreeMap, bounded_vec::BoundedVec}, + traits::Get, + CloneNoBound, PartialEqNoBound, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, CheckedSub, Zero}, + DispatchError, DispatchResult, +}; +use zeitgeist_primitives::math::{ + checked_ops_res::{CheckedAddRes, CheckedMulRes, CheckedSubRes}, + fixed::FixedMulDiv, +}; + +/// A segment tree used to track balances of liquidity shares which allows `O(log(n))` distribution +/// of fees. +/// +/// Each liquidity provider owns exactly one node of the tree which records their stake and fees. +/// When a liquidity provider leaves the tree, the node is not removed from the tree, but marked as +/// _abandoned_ instead. Abandoned nodes are reassigned when new LPs enter the tree. Nodes are added +/// to the leaves of the tree only if there are no abandoned nodes to reassign. +/// +/// Fees are lazily propagated down the tree. This allows fees to be deposited to the tree in `O(1)` +/// (fees deposited at the root and later propagated down). If a particular node requires to know +/// what its fees are, propagating fees to this node takes `O(depth)` operations (or, equivalently, +/// `O(log_2(node_count))`). +/// +/// # Generics +/// +/// - `T`: The pallet configuration. +/// - `U`: A getter for the maximum depth of the tree. Using a depth larger than `31` will result in +/// undefined behavior. +#[derive( + CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[scale_info(skip_type_params(T, U))] +pub(crate) struct LiquidityTree +where + T: Config, + U: Get, +{ + /// A vector which holds the nodes of the tree. The nodes are ordered by depth (the root is the + /// first element of `nodes`) and from left to right. For example, the right-most grandchild of + /// the root is at index `6`. + pub(crate) nodes: BoundedVec, LiquidityTreeMaxNodes>, + /// Maps an account to the node that belongs to it. + pub(crate) account_to_index: BoundedBTreeMap>, + /// A vector that contains the indices of abandoned nodes. Sorted in the order in which the + /// nodes were abandoned, with the last element of the vector being the most recently abandoned + /// node. + pub(crate) abandoned_nodes: BoundedVec>, +} + +impl LiquidityTree +where + T: Config, + U: Get, +{ + /// Create a new liquidity tree. + /// + /// # Parameters + /// + /// - `account`: The account to which the tree's root belongs. + /// - `stake`: The stake of the tree's root. + pub(crate) fn new( + account: T::AccountId, + stake: BalanceOf, + ) -> Result, DispatchError> { + let root = Node::new(account.clone(), stake); + let nodes = vec![root] + .try_into() + .map_err(|_| StorageOverflowError::Nodes.into_dispatch_error::())?; + let mut account_to_index = BoundedBTreeMap::<_, _, _>::new(); + account_to_index + .try_insert(account, 0u32) + .map_err(|_| StorageOverflowError::AccountToIndex.into_dispatch_error::())?; + let abandoned_nodes = vec![] + .try_into() + .map_err(|_| StorageOverflowError::AbandonedNodes.into_dispatch_error::())?; + Ok(LiquidityTree { nodes, account_to_index, abandoned_nodes }) + } +} + +/// Execution path info for `join` calls. +#[derive(Debug, PartialEq)] +pub(crate) enum BenchmarkInfo { + /// The LP already owns a node in the tree. + InPlace, + /// The LP is reassigned an abandoned node. + Reassigned, + /// The LP is assigned a leaf of the tree. + Leaf, +} + +impl LiquiditySharesManager for LiquidityTree +where + T: Config + frame_system::Config, + T::AccountId: PartialEq, + BalanceOf: AtLeast32BitUnsigned + Copy + Zero, + U: Get, +{ + type JoinBenchmarkInfo = BenchmarkInfo; + + fn join( + &mut self, + who: &T::AccountId, + stake: BalanceOf, + ) -> Result { + let opt_index = self.account_to_index.get(who); + let (index, benchmark_info) = if let Some(&index) = opt_index { + // Pile onto existing account. + self.propagate_fees_to_node(index)?; + let node = self.get_node_mut(index)?; + node.stake = node.stake.checked_add_res(&stake)?; + (index, BenchmarkInfo::InPlace) + } else { + // Push onto new node. + let (index, benchmark_info) = if let Some(index) = self.take_last_abandoned_node_index() + { + self.propagate_fees_to_node(index)?; + let node = self.get_node_mut(index)?; + node.account = Some(who.clone()); + node.stake = stake; + node.fees = Zero::zero(); // Not necessary, but better safe than sorry. + // Don't change `descendant_stake`; we're still maintaining it for abandoned + // nodes. + node.lazy_fees = Zero::zero(); + (index, BenchmarkInfo::Reassigned) + } else if let Some(index) = self.peek_next_free_leaf() { + // Add new leaf. Propagate first so we don't propagate fees to the new leaf. + if let Some(parent_index) = self.parent_index(index) { + self.propagate_fees_to_node(parent_index)?; + } + self.nodes + .try_push(Node::new(who.clone(), stake)) + .map_err(|_| StorageOverflowError::Nodes.into_dispatch_error::())?; + (index, BenchmarkInfo::Leaf) + } else { + return Err(LiquidityTreeError::TreeIsFull.into_dispatch_error::()); + }; + self.account_to_index + .try_insert(who.clone(), index) + .map_err(|_| StorageOverflowError::AccountToIndex.into_dispatch_error::())?; + (index, benchmark_info) + }; + if let Some(parent_index) = self.parent_index(index) { + self.update_descendant_stake_of_ancestors( + parent_index, + stake, + UpdateDescendantStakeOperation::Add, + )?; + } + Ok(benchmark_info) + } + + fn exit(&mut self, who: &T::AccountId, stake: BalanceOf) -> DispatchResult { + let index = self.map_account_to_index(who)?; + self.propagate_fees_to_node(index)?; + let node = self.get_node_mut(index)?; + ensure!( + node.fees == Zero::zero(), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::() + ); + node.stake = node + .stake + .checked_sub(&stake) + .ok_or(LiquidityTreeError::InsufficientStake.into_dispatch_error::())?; + if node.stake == Zero::zero() { + node.account = None; + self.abandoned_nodes + .try_push(index) + .map_err(|_| StorageOverflowError::AbandonedNodes.into_dispatch_error::())?; + let _ = self.account_to_index.remove(who); + } + if let Some(parent_index) = self.parent_index(index) { + self.update_descendant_stake_of_ancestors( + parent_index, + stake, + UpdateDescendantStakeOperation::Sub, + )?; + } + Ok(()) + } + + fn split( + &mut self, + _sender: &T::AccountId, + _receiver: &T::AccountId, + _amount: BalanceOf, + ) -> DispatchResult { + Err(Error::::NotImplemented.into()) + } + + fn deposit_fees(&mut self, amount: BalanceOf) -> DispatchResult { + let root = self.get_node_mut(0u32)?; + root.lazy_fees = root.lazy_fees.checked_add_res(&amount)?; + Ok(()) + } + + fn withdraw_fees(&mut self, who: &T::AccountId) -> Result, DispatchError> { + let index = self.map_account_to_index(who)?; + self.propagate_fees_to_node(index)?; + let node = self.get_node_mut(index)?; + let fees = node.fees; + node.fees = Zero::zero(); + Ok(fees) + } + + fn shares_of(&self, who: &T::AccountId) -> Result, DispatchError> { + let index = self.map_account_to_index(who)?; + let node = self.get_node(index)?; + Ok(node.stake) + } + + fn total_shares(&self) -> Result, DispatchError> { + let root = self.get_node(0u32)?; + root.total_stake() + } +} + +impl LiquidityTreeHelper for LiquidityTree +where + T: Config, + U: Get, +{ + type Node = Node; + + fn propagate_fees_to_node(&mut self, index: u32) -> DispatchResult { + let path = self.path_to_node(index, None)?; + for i in path { + self.propagate_fees(i)?; + } + Ok(()) + } + + fn propagate_fees(&mut self, index: u32) -> DispatchResult { + let node = self.get_node(index)?; + if node.total_stake()? == Zero::zero() { + return Ok(()); // Don't propagate if there are no LPs under this node. + } + if node.is_weak_leaf() { + self.mutate_node(index, |node| { + node.fees = node.fees.checked_add_res(&node.lazy_fees)?; + Ok(()) + })?; + } else { + // Temporary storage to ensure that the borrow checker doesn't get upset. + let node_descendant_stake = node.descendant_stake; + // The lazy fees that will be propagated down the tree. + let mut remaining_lazy_fees = + node.descendant_stake.bmul_bdiv(node.lazy_fees, node.total_stake()?)?; + // The fees that stay at this node. + let fees = node.lazy_fees.checked_sub_res(&remaining_lazy_fees)?; + self.mutate_node(index, |node| { + node.fees = node.fees.checked_add_res(&fees)?; + Ok(()) + })?; + let (opt_lhs_index, opt_rhs_index) = self.children(index)?.into(); + if let Some(lhs_index) = opt_lhs_index { + self.mutate_node(lhs_index, |lhs_node| { + // The descendant's share of the stake: + let child_lazy_fees = lhs_node + .total_stake()? + .bmul_bdiv(remaining_lazy_fees, node_descendant_stake)?; + lhs_node.lazy_fees = lhs_node.lazy_fees.checked_add_res(&child_lazy_fees)?; + remaining_lazy_fees = remaining_lazy_fees.checked_sub_res(&child_lazy_fees)?; + Ok(()) + })?; + } + if let Some(rhs_index) = opt_rhs_index { + self.mutate_node(rhs_index, |rhs_node| { + rhs_node.lazy_fees = + rhs_node.lazy_fees.checked_add_res(&remaining_lazy_fees)?; + Ok(()) + })?; + } + } + self.mutate_node(index, |node| { + node.lazy_fees = Zero::zero(); + Ok(()) + })?; + Ok(()) + } + + fn children(&self, index: u32) -> Result { + let calculate_child = + |child_index: u32| Some(child_index).filter(|&i| i < self.node_count()); + let left_child_index = index.checked_mul_res(&2)?.checked_add_res(&1)?; + let lhs = calculate_child(left_child_index); + let right_child_index = left_child_index.checked_add_res(&1)?; + let rhs = calculate_child(right_child_index); + Ok(LiquidityTreeChildIndices { lhs, rhs }) + } + + fn parent_index(&self, index: u32) -> Option { + if index == 0 { None } else { index.checked_sub(1)?.checked_div(2) } + } + + fn path_to_node( + &self, + index: u32, + opt_iterations: Option, + ) -> Result, DispatchError> { + let remaining_iterations = + opt_iterations.unwrap_or(Self::max_depth().checked_add_res(&1)? as usize); + let remaining_iterations = remaining_iterations + .checked_sub(1) + .ok_or(LiquidityTreeError::MaxIterationsReached.into_dispatch_error::())?; + if let Some(parent_index) = self.parent_index(index) { + let mut path = self.path_to_node(parent_index, Some(remaining_iterations))?; + path.push(index); + Ok(path) + } else { + Ok(vec![0]) + } + } + + fn take_last_abandoned_node_index(&mut self) -> Option { + self.abandoned_nodes.pop() + } + + fn peek_next_free_leaf(&self) -> Option { + let node_count = self.node_count(); + if node_count < Self::max_node_count() { Some(node_count) } else { None } + } + + fn update_descendant_stake_of_ancestors( + &mut self, + index: u32, + delta: BalanceOf, + op: UpdateDescendantStakeOperation, + ) -> DispatchResult { + for &i in self.path_to_node(index, None)?.iter() { + let node = self.get_node_mut(i)?; + match op { + UpdateDescendantStakeOperation::Add => { + node.descendant_stake = node.descendant_stake.checked_add_res(&delta)? + } + UpdateDescendantStakeOperation::Sub => { + node.descendant_stake = node.descendant_stake.checked_sub_res(&delta)? + } + } + } + Ok(()) + } + + fn mutate_each_child(&mut self, index: u32, mut mutator: F) -> DispatchResult + where + F: FnMut(&mut Self::Node) -> DispatchResult, + { + let child_indices = self.children(index)?; + child_indices.apply(|index| { + self.mutate_node(index, |node| mutator(node))?; + Ok(()) + })?; + Ok(()) + } + + fn node_count(&self) -> u32 { + self.nodes.len() as u32 + } + + fn get_node(&self, index: u32) -> Result<&Self::Node, DispatchError> { + self.nodes + .get(index as usize) + .ok_or(LiquidityTreeError::NodeNotFound.into_dispatch_error::()) + } + + fn get_node_mut(&mut self, index: u32) -> Result<&mut Self::Node, DispatchError> { + self.nodes + .get_mut(index as usize) + .ok_or(LiquidityTreeError::NodeNotFound.into_dispatch_error::()) + } + + fn map_account_to_index(&self, who: &T::AccountId) -> Result { + self.account_to_index + .get(who) + .ok_or(LiquidityTreeError::AccountNotFound.into_dispatch_error::()) + .copied() + } + + fn mutate_node(&mut self, index: u32, mutator: F) -> DispatchResult + where + F: FnOnce(&mut Self::Node) -> DispatchResult, + { + let node = self.get_node_mut(index)?; + mutator(node) + } + + fn max_depth() -> u32 { + U::get() + } + + fn max_node_count() -> u32 { + LiquidityTreeMaxNodes::::get() + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_child_indices.rs b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_child_indices.rs new file mode 100644 index 000000000..4a4d47cad --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_child_indices.rs @@ -0,0 +1,51 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use sp_runtime::DispatchError; + +/// Structure for managing children in a liquidity tree. +pub(crate) struct LiquidityTreeChildIndices { + /// Left-hand side child; `None` if there's no left-hand side child (the node is either empty or + /// the parent is a leaf). + pub(crate) lhs: Option, + /// Right-hand side child; `None` if there's no right-hand side child (the node is either empty + /// of the parent is a leaf). + pub(crate) rhs: Option, +} + +impl LiquidityTreeChildIndices { + /// Applies a `mutator` function to each child if it exists. + pub fn apply(&self, mut mutator: F) -> Result<(), DispatchError> + where + F: FnMut(u32) -> Result<(), DispatchError>, + { + if let Some(lhs) = self.lhs { + mutator(lhs)?; + } + if let Some(rhs) = self.rhs { + mutator(rhs)?; + } + Ok(()) + } +} + +// Implement `From` for destructuring +impl From for (Option, Option) { + fn from(child_indices: LiquidityTreeChildIndices) -> (Option, Option) { + (child_indices.lhs, child_indices.rhs) + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_error.rs b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_error.rs new file mode 100644 index 000000000..7d25a25f4 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_error.rs @@ -0,0 +1,81 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{Config, Error}; +use frame_support::{PalletError, RuntimeDebugNoBound}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::DispatchError; + +#[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebugNoBound, TypeInfo)] +pub enum LiquidityTreeError { + /// There is no node which belongs to this account. + AccountNotFound, + /// There is no node with this index. + NodeNotFound, + /// Operation can't be executed while there are unclaimed fees. + UnwithdrawnFees, + /// The liquidity tree is full and can't accept any new nodes. + TreeIsFull, + /// This node doesn't hold enough stake. + InsufficientStake, + /// A while loop exceeded the expected number of iterations. This is unexpected behavior. + MaxIterationsReached, + /// Unexpected storage overflow. + StorageOverflow(StorageOverflowError), +} + +#[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebugNoBound, TypeInfo)] +pub enum StorageOverflowError { + /// Encountered a storage overflow when trying to push onto the `nodes` vector. + Nodes, + /// Encountered a storage overflow when trying to push onto the `account_to_index` map. + AccountToIndex, + /// Encountered a storage overflow when trying to push onto the `abandoned_nodes` vector. + AbandonedNodes, +} + +impl From for LiquidityTreeError { + fn from(error: StorageOverflowError) -> LiquidityTreeError { + LiquidityTreeError::StorageOverflow(error) + } +} + +impl StorageOverflowError { + pub(crate) fn into_dispatch_error(self) -> DispatchError + where + T: Config, + { + let liquidity_tree_error: LiquidityTreeError = self.into(); + liquidity_tree_error.into_dispatch_error::() + } +} + +impl From for Error { + fn from(error: LiquidityTreeError) -> Error { + Error::::LiquidityTreeError(error) + } +} + +impl LiquidityTreeError { + pub(crate) fn into_dispatch_error(self) -> DispatchError + where + T: Config, + { + Error::::LiquidityTreeError(self).into() + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_max_nodes.rs b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_max_nodes.rs new file mode 100644 index 000000000..fe1252dcc --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/liquidity_tree_max_nodes.rs @@ -0,0 +1,37 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use core::marker::PhantomData; +use sp_runtime::traits::Get; + +/// Gets the maximum number of nodes allowed in the liquidity tree as a function of its depth. +/// Saturates at `u32::MAX`, but will warn about this in DEBUG. +/// +/// # Generics +/// +/// - `D`: A getter for the depth of the tree. +pub(crate) struct LiquidityTreeMaxNodes(PhantomData); + +impl Get for LiquidityTreeMaxNodes +where + D: Get, +{ + fn get() -> u32 { + debug_assert!(D::get() < 31, "LiquidityTreeMaxNodes::get(): Integer overflow"); + 2u32.saturating_pow(D::get() + 1).saturating_sub(1) + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/types/mod.rs b/zrml/neo-swaps/src/liquidity_tree/types/mod.rs new file mode 100644 index 000000000..a6bd45681 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/mod.rs @@ -0,0 +1,30 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +pub(crate) mod liquidity_tree; +pub(crate) mod liquidity_tree_child_indices; +pub(crate) mod liquidity_tree_error; +pub(crate) mod liquidity_tree_max_nodes; +pub(crate) mod node; +pub(crate) mod update_descendant_stake_operation; + +pub(crate) use liquidity_tree::*; +pub(crate) use liquidity_tree_child_indices::*; +pub(crate) use liquidity_tree_error::*; +pub(crate) use liquidity_tree_max_nodes::*; +pub(crate) use node::*; +pub(crate) use update_descendant_stake_operation::*; diff --git a/zrml/neo-swaps/src/liquidity_tree/types/node.rs b/zrml/neo-swaps/src/liquidity_tree/types/node.rs new file mode 100644 index 000000000..a96db72aa --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/node.rs @@ -0,0 +1,72 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{BalanceOf, Config}; +use frame_support::RuntimeDebugNoBound; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, DispatchError}; +use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes; + +/// Type for nodes of a liquidity tree. +/// +/// # Notes +/// +/// - `descendant_stake` does not contain the stake of `self`. +/// - `lazy_fees`, when propagated, is distributed not only to the descendants of `self`, but also to +/// `self`. +#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebugNoBound, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub(crate) struct Node { + /// The account that the node belongs to. `None` signifies an abandoned node. + pub(crate) account: Option, + /// The stake belonging to the owner. + pub(crate) stake: BalanceOf, + /// The fees owed to the owner. + pub(crate) fees: BalanceOf, + /// The sum of the stake of all descendants of this node. + pub(crate) descendant_stake: BalanceOf, + /// The amount of fees to be lazily propagated down the tree. + pub(crate) lazy_fees: BalanceOf, +} + +impl Node +where + T: Config, +{ + /// Create a new node with `stake` belonging to `account`. + pub(crate) fn new(account: T::AccountId, stake: BalanceOf) -> Node { + Node { + account: Some(account), + stake, + fees: 0u8.into(), + descendant_stake: 0u8.into(), + lazy_fees: 0u8.into(), + } + } + + /// Return the total stake of the node (the node's stake plus the sum of descendant's stakes). + pub(crate) fn total_stake(&self) -> Result, DispatchError> { + self.stake.checked_add_res(&self.descendant_stake) + } + + /// Return `true` is the node is a leaf in the sense that none of its descendants hold any + /// stake. (Strictly speaking, it's not always a leaf, as there might be abandoned nodes!) + pub(crate) fn is_weak_leaf(&self) -> bool { + self.descendant_stake == Zero::zero() + } +} diff --git a/zrml/neo-swaps/src/liquidity_tree/types/update_descendant_stake_operation.rs b/zrml/neo-swaps/src/liquidity_tree/types/update_descendant_stake_operation.rs new file mode 100644 index 000000000..be4785e61 --- /dev/null +++ b/zrml/neo-swaps/src/liquidity_tree/types/update_descendant_stake_operation.rs @@ -0,0 +1,22 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +/// Type for specifying a sign for `update_descendant_stake_of_ancestors`. +pub(crate) enum UpdateDescendantStakeOperation { + Add, + Sub, +} diff --git a/zrml/neo-swaps/src/macros.rs b/zrml/neo-swaps/src/macros.rs new file mode 100644 index 000000000..e14242861 --- /dev/null +++ b/zrml/neo-swaps/src/macros.rs @@ -0,0 +1,156 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] + +/// Creates an `alloc::collections::BTreeMap` from the pattern `{ key => value, ... }`. +/// +/// ```rust +/// // 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::>() + } +} + +/// Asserts that a market's LMSR liquidity pool has the specified state. +/// +/// In addition to verifying the specified state, the macro also ensures that the pool's trading +/// function is (approximately) equal to `1`. +/// +/// Parameters: +/// +/// - `market_id`: The ID of the market that the pool belongs to. +/// - `reserves`: The expected reserves of the pool. +/// - `spot_prices`: The expected spot prices of outcomes in the pool. +/// - `liquidity_parameter`: The expected liquidity parameter of the pool. +/// - `liquidity_shares`: An `alloc::collections::BTreeMap` which maps each liquidity provider to +/// their expected stake. +/// - `total_fees`: The sum of all fees (both lazy and distributed) in the pool's liquidity tree. +#[macro_export] +macro_rules! assert_pool_state { + ( + $market_id:expr, + $reserves:expr, + $spot_prices:expr, + $liquidity_parameter:expr, + $liquidity_shares:expr, + $total_fees:expr + $(,)? + ) => { + let pool = Pools::::get($market_id).unwrap(); + assert_eq!( + pool.reserves.values().cloned().collect::>(), + $reserves, + "assert_pool_state: Reserves mismatch" + ); + let actual_spot_prices = pool + .assets() + .iter() + .map(|&a| pool.calculate_spot_price(a).unwrap()) + .collect::>(); + assert_eq!(actual_spot_prices, $spot_prices, "assert_pool_state: Spot price mismatch"); + assert_eq!( + pool.liquidity_parameter, $liquidity_parameter, + "assert_pool_state: Liquidity parameter mismatch" + ); + let actual_liquidity_shares = pool + .liquidity_shares_manager + .account_to_index + .keys() + .map(|&account| { + ( + account, + pool.liquidity_shares_manager.shares_of(&account).expect( + format!("assert_pool_state: No shares found for {:?}", account).as_str(), + ), + ) + }) + .collect::>(); + assert_eq!( + actual_liquidity_shares, $liquidity_shares, + "assert_pool_state: Liquidity shares mismatch" + ); + let actual_total_fees = pool + .liquidity_shares_manager + .nodes + .iter() + .fold(0u128, |acc, node| acc + node.fees + node.lazy_fees); + assert_eq!(actual_total_fees, $total_fees); + let invariant = actual_spot_prices.iter().sum::(); + assert_approx!(invariant, _1, 1); + }; +} + +/// Asserts that `account` has the specified `balances` of `assets`. +#[macro_export] +macro_rules! assert_balances { + ($account:expr, $assets:expr, $balances:expr $(,)?) => { + assert_eq!( + $assets.len(), + $balances.len(), + "assert_balances: Assets and balances length mismatch" + ); + for (&asset, &expected_balance) in $assets.iter().zip($balances.iter()) { + let actual_balance = AssetManager::free_balance(asset, &$account); + assert_eq!( + actual_balance, expected_balance, + "assert_balances: Balance mismatch for asset {:?}", + asset, + ); + } + }; +} + +/// Asserts that `account` has the specified `balance` of `asset`. +#[macro_export] +macro_rules! assert_balance { + ($account:expr, $asset:expr, $balance:expr $(,)?) => { + assert_balances!($account, [$asset], [$balance]); + }; +} + +/// Asserts that `abs(left - right) < precision`. +#[macro_export] +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_val { + panic!( + "assertion `left approx== right` failed\n left: {}\n right: {}\n \ + precision: {}\ndifference: {}", + *left_val, *right_val, *precision_val, diff + ); + } + } + } + }; +} diff --git a/zrml/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs new file mode 100644 index 000000000..51e605c87 --- /dev/null +++ b/zrml/neo-swaps/src/migration.rs @@ -0,0 +1,254 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + liquidity_tree::types::LiquidityTree, + types::{Pool, SoloLp}, + Config, Pallet, +}; +use frame_support::{ + dispatch::Weight, + log, + pallet_prelude::PhantomData, + traits::{Get, OnRuntimeUpgrade, StorageVersion}, +}; +use sp_runtime::traits::Saturating; + +cfg_if::cfg_if! { + if #[cfg(feature = "try-runtime")] { + use crate::{MarketIdOf, Pools}; + use alloc::{collections::BTreeMap, format, vec::Vec}; + use frame_support::{migration::storage_key_iter, pallet_prelude::Twox64Concat}; + use parity_scale_codec::{Decode, Encode}; + use sp_runtime::traits::Zero; + } +} + +cfg_if::cfg_if! { + if #[cfg(any(feature = "try-runtime", test))] { + const NEO_SWAPS: &[u8] = b"NeoSwaps"; + const POOLS: &[u8] = b"Pools"; + } +} + +const NEO_SWAPS_REQUIRED_STORAGE_VERSION: u16 = 0; +const NEO_SWAPS_NEXT_STORAGE_VERSION: u16 = NEO_SWAPS_REQUIRED_STORAGE_VERSION + 1; + +type OldPoolOf = Pool>; + +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 { + log::info!( + "MigrateToLiquidityTree: market-commons version is {:?}, but {:?} is required", + market_commons_version, + NEO_SWAPS_REQUIRED_STORAGE_VERSION, + ); + return total_weight; + } + log::info!("MigrateToLiquidityTree: Starting..."); + let mut translated = 0u64; + crate::Pools::::translate::, _>(|_, pool| { + let solo = pool.liquidity_shares_manager; + // This should never fail; if it does, then we just delete the entry. + let mut liquidity_tree = + LiquidityTree::new(solo.owner.clone(), solo.total_shares).ok()?; + liquidity_tree.nodes.get_mut(0)?.fees = solo.fees; // Can't fail. + translated.saturating_inc(); + Some(Pool { + account_id: pool.account_id, + reserves: pool.reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + liquidity_shares_manager: liquidity_tree, + swap_fee: pool.swap_fee, + }) + }); + log::info!("MigrateToLiquidityTree: Upgraded {} pools.", translated); + total_weight = + total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); + StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + log::info!("MigrateToLiquidityTree: Done!"); + total_weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + let old_pools = + storage_key_iter::, OldPoolOf, Twox64Concat>(NEO_SWAPS, POOLS) + .collect::>(); + Ok(old_pools.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { + let old_pools: BTreeMap, OldPoolOf> = + Decode::decode(&mut &previous_state[..]) + .map_err(|_| "Failed to decode state: Invalid state")?; + let new_pool_count = Pools::::iter().count(); + assert_eq!(old_pools.len(), new_pool_count); + for (market_id, new_pool) in Pools::::iter() { + let old_pool = + old_pools.get(&market_id).expect(&format!("Pool {:?} not found", market_id)[..]); + assert_eq!(new_pool.account_id, old_pool.account_id); + assert_eq!(new_pool.reserves, old_pool.reserves); + assert_eq!(new_pool.collateral, old_pool.collateral); + assert_eq!(new_pool.liquidity_parameter, old_pool.liquidity_parameter); + assert_eq!(new_pool.swap_fee, old_pool.swap_fee); + let tree = new_pool.liquidity_shares_manager; + let solo = &old_pool.liquidity_shares_manager; + assert_eq!(tree.nodes.len(), 1); + assert_eq!(tree.abandoned_nodes.len(), 0); + assert_eq!(tree.account_to_index.len(), 1); + let root = tree.nodes[0].clone(); + let account = root.account.clone(); + assert_eq!(root.account, Some(solo.owner.clone())); + assert_eq!(root.stake, solo.total_shares); + assert_eq!(root.fees, solo.fees); + assert_eq!(root.descendant_stake, Zero::zero()); + assert_eq!(root.lazy_fees, Zero::zero()); + let address = account.unwrap(); + assert_eq!(tree.account_to_index.get(&address), Some(&0)); + } + log::info!("MigrateToLiquidityTree: Post-upgrade pool count is {}!", new_pool_count); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{ExtBuilder, Runtime}, + MarketIdOf, PoolOf, Pools, + }; + use alloc::collections::BTreeMap; + use frame_support::{ + dispatch::fmt::Debug, migration::put_storage_value, storage_root, StateVersion, + StorageHasher, Twox64Concat, + }; + use parity_scale_codec::Encode; + use zeitgeist_primitives::types::Asset; + + #[test] + fn on_runtime_upgrade_increments_the_storage_version() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + MigrateToLiquidityTree::::on_runtime_upgrade(); + assert_eq!(StorageVersion::get::>(), NEO_SWAPS_NEXT_STORAGE_VERSION); + }); + } + + #[test] + fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { + ExtBuilder::default().build().execute_with(|| { + StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); + let (_, new_pools) = construct_old_new_tuple(); + populate_test_data::, PoolOf>( + NEO_SWAPS, POOLS, new_pools, + ); + let tmp = storage_root(StateVersion::V1); + MigrateToLiquidityTree::::on_runtime_upgrade(); + assert_eq!(tmp, storage_root(StateVersion::V1)); + }); + } + + #[test] + fn on_runtime_upgrade_correctly_updates_markets() { + ExtBuilder::default().build().execute_with(|| { + set_up_version(); + let (old_pools, new_pools) = construct_old_new_tuple(); + populate_test_data::, OldPoolOf>( + NEO_SWAPS, POOLS, old_pools, + ); + MigrateToLiquidityTree::::on_runtime_upgrade(); + let actual = Pools::get(0u128).unwrap(); + assert_eq!(actual, new_pools[0]); + }); + } + + fn set_up_version() { + StorageVersion::new(NEO_SWAPS_REQUIRED_STORAGE_VERSION).put::>(); + } + + fn construct_old_new_tuple() -> (Vec>, Vec>) { + let account_id = 1; + let mut reserves = BTreeMap::new(); + reserves.insert(Asset::CategoricalOutcome(2, 3), 4); + let collateral = Asset::Ztg; + let liquidity_parameter = 5; + let swap_fee = 6; + let total_shares = 7; + let fees = 8; + + let solo = SoloLp { owner: account_id, total_shares, fees }; + let mut liquidity_tree = LiquidityTree::new(account_id, total_shares).unwrap(); + liquidity_tree.nodes.get_mut(0).unwrap().fees = fees; + + let old_pool = OldPoolOf { + account_id, + reserves: reserves.clone(), + collateral, + liquidity_parameter, + liquidity_shares_manager: solo, + swap_fee, + }; + let new_pool = Pool { + account_id, + reserves, + collateral, + liquidity_parameter, + liquidity_shares_manager: liquidity_tree, + swap_fee, + }; + (vec![old_pool], vec![new_pool]) + } + + #[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 = utility::key_to_hash::(K::try_from(key).unwrap()); + put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); + } + } +} + +mod utility { + use alloc::vec::Vec; + use frame_support::StorageHasher; + use parity_scale_codec::Encode; + + #[allow(unused)] + pub fn key_to_hash(key: K) -> Vec + where + H: StorageHasher, + K: Encode, + { + key.using_encoded(H::hash).as_ref().to_vec() + } +} diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index c505c0fe3..b361d473a 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -52,8 +52,8 @@ use zeitgeist_primitives::{ GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, MaxApprovals, MaxAssets, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, MaxDisputes, - MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxInRatio, MaxLocks, - MaxMarketLifetime, MaxOracleDuration, MaxOutRatio, MaxOwners, MaxRejectReasonLen, + 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, @@ -192,6 +192,7 @@ impl crate::Config for Runtime { type ExternalFees = ExternalFees; type MarketCommons = MarketCommons; type RuntimeEvent = RuntimeEvent; + type MaxLiquidityTreeDepth = MaxLiquidityTreeDepth; type MaxSwapFee = NeoMaxSwapFee; type PalletId = NeoSwapsPalletId; type WeightInfo = zrml_neo_swaps::weights::WeightInfo; @@ -475,7 +476,7 @@ pub struct ExtBuilder { #[allow(unused)] impl Default for ExtBuilder { fn default() -> Self { - Self { balances: vec![(ALICE, _101), (CHARLIE, _1), (DAVE, _1), (EVE, _1)] } + Self { balances: vec![(ALICE, 100_000_000_001 * _1), (CHARLIE, _1), (DAVE, _1), (EVE, _1)] } } } @@ -489,32 +490,35 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); #[cfg(feature = "parachain")] - use frame_support::traits::GenesisBuild; - #[cfg(feature = "parachain")] - orml_tokens::GenesisConfig:: { balances: vec![(ALICE, FOREIGN_ASSET, _101)] } + { + use frame_support::traits::GenesisBuild; + orml_tokens::GenesisConfig:: { + balances: vec![(ALICE, FOREIGN_ASSET, 100_000_000_001 * _1)], + } + .assimilate_storage(&mut t) + .unwrap(); + let custom_metadata = zeitgeist_primitives::types::CustomMetadata { + allow_as_base_asset: true, + ..Default::default() + }; + orml_asset_registry_mock::GenesisConfig { + metadata: vec![( + FOREIGN_ASSET, + AssetMetadata { + decimals: 18, + name: "MKL".as_bytes().to_vec(), + symbol: "MKL".as_bytes().to_vec(), + existential_deposit: 0, + location: None, + additional: custom_metadata, + }, + )], + } .assimilate_storage(&mut t) .unwrap(); - #[cfg(feature = "parachain")] - let custom_metadata = zeitgeist_primitives::types::CustomMetadata { - allow_as_base_asset: true, - ..Default::default() - }; - #[cfg(feature = "parachain")] - orml_asset_registry_mock::GenesisConfig { - metadata: vec![( - FOREIGN_ASSET, - AssetMetadata { - decimals: 18, - name: "MKL".as_bytes().to_vec(), - symbol: "MKL".as_bytes().to_vec(), - existential_deposit: 0, - location: None, - additional: custom_metadata, - }, - )], } - .assimilate_storage(&mut t) - .unwrap(); - t.into() + let mut test_ext: sp_io::TestExternalities = t.into(); + test_ext.execute_with(|| System::set_block_number(1)); + test_ext } } diff --git a/zrml/neo-swaps/src/tests/buy.rs b/zrml/neo-swaps/src/tests/buy.rs index 9b72ddba3..a613b5fae 100644 --- a/zrml/neo-swaps/src/tests/buy.rs +++ b/zrml/neo-swaps/src/tests/buy.rs @@ -23,7 +23,6 @@ use test_case::test_case; #[test] fn buy_works() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); let liquidity = _10; let spot_prices = vec![_1_2, _1_2]; let swap_fee = CENT; @@ -44,7 +43,7 @@ fn buy_works() { let expected_external_fee_amount = expected_fees / 2; let pool_outcomes_before: Vec<_> = pool.assets().iter().map(|a| pool.reserve_of(a).unwrap()).collect(); - let pool_liquidity_before = pool.liquidity_parameter; + let liquidity_parameter_before = pool.liquidity_parameter; let asset_out = pool.assets()[0]; assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); // Deposit some stuff in the pool account to check that the pools `reserves` fields tracks @@ -59,41 +58,29 @@ fn buy_works() { 0, )); let pool = Pools::::get(market_id).unwrap(); - assert_eq!(pool.liquidity_parameter, pool_liquidity_before); - assert_eq!(pool.liquidity_shares_manager.owner, ALICE); - assert_eq!(pool.liquidity_shares_manager.total_shares, liquidity); - assert_eq!(pool.liquidity_shares_manager.fees, expected_swap_fee_amount); - let pool_outcomes_after: Vec<_> = pool - .assets() - .iter() - .map(|a| pool.reserve_of(a).unwrap()) - .collect(); let expected_swap_amount_out = 58496250072; - let expected_amount_in_minus_fees = _10 + 1; // Note: This is 1 Pennock of the correct result. - let expected_amount_out = expected_swap_amount_out + expected_amount_in_minus_fees; - assert_eq!(AssetManager::free_balance(BASE_ASSET, &BOB), 0); - assert_eq!(AssetManager::free_balance(asset_out, &BOB), expected_amount_out); - assert_eq!(pool_outcomes_after[0], pool_outcomes_before[0] - expected_swap_amount_out); - assert_eq!( - pool_outcomes_after[1], + let expected_amount_in_minus_fees = _10 + 1; // Note: This is 1 Pennock off of the correct result. + let expected_reserves = vec![ + pool_outcomes_before[0] - expected_swap_amount_out, pool_outcomes_before[0] + expected_amount_in_minus_fees, + ]; + assert_pool_state!( + market_id, + expected_reserves, + vec![_3_4, _1_4], + liquidity_parameter_before, + create_b_tree_map!({ ALICE => liquidity }), + expected_swap_fee_amount, ); - let expected_pool_account_balance = - expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral); - assert_eq!( - AssetManager::free_balance(BASE_ASSET, &pool.account_id), - expected_pool_account_balance - ); - assert_eq!( - AssetManager::free_balance(BASE_ASSET, &FEE_ACCOUNT), - expected_external_fee_amount + let expected_amount_out = expected_swap_amount_out + expected_amount_in_minus_fees; + assert_balance!(BOB, BASE_ASSET, 0); + assert_balance!(BOB, asset_out, expected_amount_out); + assert_balance!( + pool.account_id, + BASE_ASSET, + expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral) ); - let price_sum = pool - .assets() - .iter() - .map(|&a| pool.calculate_spot_price(a).unwrap()) - .sum::(); - assert_eq!(price_sum, _1); + assert_balance!(FEE_ACCOUNT, BASE_ASSET, expected_external_fee_amount); System::assert_last_event( Event::BuyExecuted { who: BOB, diff --git a/zrml/neo-swaps/src/tests/buy_and_sell.rs b/zrml/neo-swaps/src/tests/buy_and_sell.rs index fbb545171..cce3d02a7 100644 --- a/zrml/neo-swaps/src/tests/buy_and_sell.rs +++ b/zrml/neo-swaps/src/tests/buy_and_sell.rs @@ -18,23 +18,6 @@ use super::*; use zeitgeist_primitives::constants::BASE; -macro_rules! assert_pool_status { - ($market_id:expr, $reserves:expr, $spot_prices:expr, $fees:expr $(,)?) => { - let pool = Pools::::get($market_id).unwrap(); - assert_eq!(pool.reserves.values().cloned().collect::>(), $reserves); - assert_eq!( - pool.assets() - .iter() - .map(|&a| pool.calculate_spot_price(a).unwrap()) - .collect::>(), - $spot_prices, - ); - let invariant = $spot_prices.iter().sum::(); - assert_approx!(invariant, _1, 1); - assert_eq!(pool.liquidity_shares_manager.fees, $fees); - }; -} - #[test] fn buy_and_sell() { ExtBuilder::default().build().execute_with(|| { @@ -59,10 +42,12 @@ fn buy_and_sell() { _10, 0, )); - assert_pool_status!( + assert_pool_state!( market_id, vec![598_000_000_000, 1_098_000_000_000, 767_092_556_931], [4_364_837_956, 2_182_418_978, 3_452_743_066], + 721_347_520_444, + create_b_tree_map!({ ALICE => _100 }), 1_000_000_000, ); @@ -74,10 +59,12 @@ fn buy_and_sell() { 1_234_567_898_765, 0, )); - assert_pool_status!( + assert_pool_state!( market_id, vec![1_807_876_540_789, 113_931_597_104, 1_976_969_097_720], [815_736_444, 8_538_986_828, 645_276_728], + 721_347_520_444, + create_b_tree_map!({ ALICE => _100 }), 13_345_678_988, ); @@ -89,10 +76,12 @@ fn buy_and_sell() { 667 * BASE, 0, )); - assert_pool_status!( + assert_pool_state!( market_id, vec![76_875_275, 6_650_531_597_104, 8_513_569_097_720], [9_998_934_339, 990_789, 74_872], + 721_347_520_444, + create_b_tree_map!({ ALICE => _100 }), 80_045_678_988, ); @@ -117,10 +106,12 @@ fn buy_and_sell() { _1, 0, )); - assert_pool_status!( + assert_pool_state!( market_id, vec![77_948_356, 6_640_532_670_185, 8_503_570_170_801], [9_998_919_465, 1_004_618, 75_917], + 721_347_520_444, + create_b_tree_map!({ ALICE => _100 }), 80_145_668_257, ); @@ -172,10 +163,12 @@ fn buy_and_sell() { _100, 0, )); - assert_pool_status!( + assert_pool_state!( market_id, vec![980_077_948_356, 7_620_532_670_185, 214_308_675_476], [2_570_006_838, 258_215, 7_429_734_946], + 721_347_520_444, + create_b_tree_map!({ ALICE => _100 }), 90_145_668_257, ); }); diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index d5764f846..17283f349 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -16,13 +16,13 @@ // along with Zeitgeist. If not, see . use super::*; +use crate::liquidity_tree::types::Node; use alloc::collections::BTreeMap; use test_case::test_case; #[test] fn deploy_pool_works_with_binary_markets() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); let amount = _10; let spot_prices = vec![_1_2, _1_2]; @@ -43,20 +43,29 @@ fn deploy_pool_works_with_binary_markets() { assert_eq!(pool.assets(), assets); assert_approx!(pool.liquidity_parameter, expected_liquidity, 1); assert_eq!(pool.collateral, BASE_ASSET); - assert_eq!(pool.liquidity_shares_manager.owner, ALICE); - assert_eq!(pool.liquidity_shares_manager.total_shares, amount); - assert_eq!(pool.liquidity_shares_manager.fees, 0); + assert_liquidity_tree_state!( + pool.liquidity_shares_manager, + [Node:: { + account: Some(ALICE), + stake: amount, + fees: 0u128, + descendant_stake: 0u128, + lazy_fees: 0u128, + }], + create_b_tree_map!({ ALICE => 0 }), + Vec::::new(), + ); assert_eq!(pool.swap_fee, swap_fee); - assert_eq!(AssetManager::free_balance(pool.collateral, &pool.account_id), buffer); - assert_eq!(AssetManager::free_balance(assets[0], &pool.account_id), amount); - assert_eq!(AssetManager::free_balance(assets[1], &pool.account_id), amount); + assert_balance!(pool.account_id, pool.collateral, buffer); + assert_balance!(pool.account_id, assets[0], amount); + assert_balance!(pool.account_id, assets[1], amount); assert_eq!(pool.reserve_of(&assets[0]).unwrap(), amount); assert_eq!(pool.reserve_of(&assets[1]).unwrap(), amount); assert_eq!(pool.calculate_spot_price(assets[0]).unwrap(), spot_prices[0]); assert_eq!(pool.calculate_spot_price(assets[1]).unwrap(), spot_prices[1]); - assert_eq!(AssetManager::free_balance(BASE_ASSET, &ALICE), alice_before - amount - buffer); - assert_eq!(AssetManager::free_balance(assets[0], &ALICE), 0); - assert_eq!(AssetManager::free_balance(assets[1], &ALICE), 0); + assert_balance!(ALICE, BASE_ASSET, alice_before - amount - buffer); + assert_balance!(ALICE, assets[0], 0); + assert_balance!(ALICE, assets[1], 0); let mut reserves = BTreeMap::new(); reserves.insert(assets[0], amount); reserves.insert(assets[1], amount); @@ -79,7 +88,6 @@ fn deploy_pool_works_with_binary_markets() { #[test] fn deploy_pool_works_with_scalar_marktes() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); let amount = _100; let spot_prices = vec![_1_6, _5_6 + 1]; @@ -112,22 +120,28 @@ fn deploy_pool_works_with_scalar_marktes() { assert_eq!(pool.assets(), assets); assert_approx!(pool.liquidity_parameter, expected_liquidity, 1_000); assert_eq!(pool.collateral, BASE_ASSET); - assert_eq!(pool.liquidity_shares_manager.owner, ALICE); - assert_eq!(pool.liquidity_shares_manager.total_shares, amount); - assert_eq!(pool.liquidity_shares_manager.fees, 0); - assert_eq!(pool.swap_fee, swap_fee); - assert_eq!( - AssetManager::free_balance(assets[0], &pool.account_id), - expected_amounts[0] + rogue_funds + assert_liquidity_tree_state!( + pool.liquidity_shares_manager, + [Node:: { + account: Some(ALICE), + stake: amount, + fees: 0u128, + descendant_stake: 0u128, + lazy_fees: 0u128, + }], + create_b_tree_map!({ ALICE => 0 }), + Vec::::new(), ); - assert_eq!(AssetManager::free_balance(assets[1], &pool.account_id), expected_amounts[1]); + assert_eq!(pool.swap_fee, swap_fee); + assert_balance!(pool.account_id, assets[0], expected_amounts[0] + rogue_funds); + assert_balance!(pool.account_id, assets[1], expected_amounts[1]); assert_eq!(pool.reserve_of(&assets[0]).unwrap(), expected_amounts[0]); assert_eq!(pool.reserve_of(&assets[1]).unwrap(), expected_amounts[1]); assert_eq!(pool.calculate_spot_price(assets[0]).unwrap(), spot_prices[0]); assert_eq!(pool.calculate_spot_price(assets[1]).unwrap(), spot_prices[1]); - assert_eq!(AssetManager::free_balance(BASE_ASSET, &ALICE), alice_before - amount - buffer); - assert_eq!(AssetManager::free_balance(assets[0], &ALICE), 0); - assert_eq!(AssetManager::free_balance(assets[1], &ALICE), amount - expected_amounts[1]); + assert_balance!(ALICE, BASE_ASSET, alice_before - amount - buffer); + assert_balance!(ALICE, assets[0], 0); + assert_balance!(ALICE, assets[1], amount - expected_amounts[1]); let price_sum = pool.assets().iter().map(|&a| pool.calculate_spot_price(a).unwrap()).sum::(); assert_eq!(price_sum, _1); @@ -226,24 +240,6 @@ fn deploy_pool_fails_on_duplicate_pool() { }); } -#[test] -fn deploy_pool_fails_on_not_allowed() { - ExtBuilder::default().build().execute_with(|| { - let market_id = - create_market(ALICE, BASE_ASSET, MarketType::Scalar(0..=1), ScoringRule::Lmsr); - assert_noop!( - NeoSwaps::deploy_pool( - RuntimeOrigin::signed(BOB), - market_id, - _10, - vec![_1_4, _3_4], - CENT - ), - Error::::NotAllowed - ); - }); -} - #[test] fn deploy_pool_fails_on_invalid_trading_mechanism() { ExtBuilder::default().build().execute_with(|| { diff --git a/zrml/neo-swaps/src/tests/exit.rs b/zrml/neo-swaps/src/tests/exit.rs index f528a7b54..1d8162a49 100644 --- a/zrml/neo-swaps/src/tests/exit.rs +++ b/zrml/neo-swaps/src/tests/exit.rs @@ -16,12 +16,18 @@ // along with Zeitgeist. If not, see . use super::*; +use crate::liquidity_tree::types::LiquidityTreeError; +use test_case::test_case; -#[test] -fn exit_works() { +#[test_case(MarketStatus::Active, vec![39_960_000_000, 4_066_153_704], 33_508_962_010)] +#[test_case(MarketStatus::Resolved, vec![40_000_000_000, 4_070_223_928], 33_486_637_585)] +fn exit_works( + market_status: MarketStatus, + amounts_out: Vec>, + new_liquidity_parameter: BalanceOf, +) { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let liquidity = _10; + let liquidity = _5; let spot_prices = vec![_1_6, _5_6 + 1]; let swap_fee = CENT; let market_id = create_market_and_deploy_pool( @@ -32,98 +38,153 @@ fn exit_works() { spot_prices.clone(), swap_fee, ); + // Add a second LP to create a more generic situation, bringing the total of shares to _10. + deposit_complete_set(market_id, BOB, liquidity); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(BOB), + market_id, + liquidity, + vec![u128::MAX, u128::MAX], + )); + MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + }) + .unwrap(); + let pool = Pools::::get(market_id).unwrap(); + let outcomes = pool.assets(); + let alice_balances = [0, 44_912_220_089]; + assert_balances!(ALICE, outcomes, alice_balances); + let pool_balances = vec![100_000_000_000, 10_175_559_822]; + assert_pool_state!( + market_id, + pool_balances, + spot_prices, + 55_811_062_642, + create_b_tree_map!({ ALICE => _5, BOB => _5 }), + 0, + ); let pool_shares_amount = _4; // Remove 40% to the pool. - let pool_before = Pools::::get(market_id).unwrap(); - let alice_outcomes_before = [ - AssetManager::free_balance(pool_before.assets()[0], &ALICE), - AssetManager::free_balance(pool_before.assets()[1], &ALICE), - ]; - let pool_outcomes_before: Vec<_> = - pool_before.assets().iter().map(|a| pool_before.reserve_of(a).unwrap()).collect(); assert_ok!(NeoSwaps::exit( RuntimeOrigin::signed(ALICE), market_id, pool_shares_amount, vec![0, 0], )); - let pool_after = Pools::::get(market_id).unwrap(); - let ratio = pool_shares_amount.bdiv(liquidity).unwrap(); - let pool_outcomes_after: Vec<_> = - pool_after.assets().iter().map(|a| pool_after.reserve_of(a).unwrap()).collect(); - let expected_pool_diff = vec![ - ratio.bmul(pool_outcomes_before[0]).unwrap(), - ratio.bmul(pool_outcomes_before[1]).unwrap(), - ]; - let alice_outcomes_after = [ - AssetManager::free_balance(pool_after.assets()[0], &ALICE), - AssetManager::free_balance(pool_after.assets()[1], &ALICE), - ]; - assert_eq!(pool_outcomes_after[0], pool_outcomes_before[0] - expected_pool_diff[0]); - assert_eq!(pool_outcomes_after[1], pool_outcomes_before[1] - expected_pool_diff[1]); - assert_eq!(alice_outcomes_after[0], alice_outcomes_before[0] + expected_pool_diff[0]); - assert_eq!(alice_outcomes_after[1], alice_outcomes_before[1] + expected_pool_diff[1]); - assert_eq!( - pool_after.liquidity_parameter, - (_1 - ratio).bmul(pool_before.liquidity_parameter).unwrap() - ); - assert_eq!( - pool_after.liquidity_shares_manager.shares_of(&ALICE).unwrap(), - liquidity - pool_shares_amount + let new_pool_balances = + pool_balances.iter().zip(amounts_out.iter()).map(|(b, a)| b - a).collect::>(); + let new_alice_balances = + alice_balances.iter().zip(amounts_out.iter()).map(|(b, a)| b + a).collect::>(); + assert_balances!(ALICE, outcomes, new_alice_balances); + assert_pool_state!( + market_id, + new_pool_balances, + spot_prices, + new_liquidity_parameter, + create_b_tree_map!({ ALICE => _1, BOB => _5 }), + 0, ); System::assert_last_event( Event::ExitExecuted { who: ALICE, market_id, pool_shares_amount, - amounts_out: expected_pool_diff, - new_liquidity_parameter: pool_after.liquidity_parameter, + amounts_out, + new_liquidity_parameter, } .into(), ); }); } -#[test] -fn exit_destroys_pool() { +#[test_case(MarketStatus::Active, vec![39_960_000_000, 4_066_153_705])] +#[test_case(MarketStatus::Resolved, vec![40_000_000_000, 4_070_223_929])] +fn last_exit_destroys_pool(market_status: MarketStatus, amounts_out: Vec>) { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let liquidity = _10; + let liquidity = _4; + let spot_prices = vec![_1_6, _5_6 + 1]; let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, MarketType::Scalar(0..=1), liquidity, - vec![_1_6, _5_6 + 1], + spot_prices.clone(), CENT, ); + MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + }) + .unwrap(); let pool = Pools::::get(market_id).unwrap(); - let amounts_out = vec![ - pool.reserve_of(&pool.assets()[0]).unwrap(), - pool.reserve_of(&pool.assets()[1]).unwrap(), - ]; - let alice_before = [ - AssetManager::free_balance(pool.assets()[0], &ALICE), - AssetManager::free_balance(pool.assets()[1], &ALICE), - ]; + let pool_account = pool.account_id; + let outcomes = pool.assets(); + let alice_balances = [0, 35_929_776_071]; + assert_balances!(ALICE, outcomes, alice_balances); + let pool_balances = vec![40_000_000_000, 4_070_223_929]; + assert_pool_state!( + market_id, + pool_balances, + spot_prices, + 22_324_425_057, + create_b_tree_map!({ ALICE => _4 }), + 0, + ); assert_ok!(NeoSwaps::exit(RuntimeOrigin::signed(ALICE), market_id, liquidity, vec![0, 0])); + let new_alice_balances = + alice_balances.iter().zip(amounts_out.iter()).map(|(b, a)| b + a).collect::>(); + assert_balances!(ALICE, outcomes, new_alice_balances); + // Pool doesn't exist anymore and exit fees are cleared. assert!(!Pools::::contains_key(market_id)); - assert_eq!(AssetManager::free_balance(pool.collateral, &pool.account_id), 0); - assert_eq!(AssetManager::free_balance(pool.assets()[0], &pool.account_id), 0); - assert_eq!(AssetManager::free_balance(pool.assets()[1], &pool.account_id), 0); - assert_eq!( - AssetManager::free_balance(pool.assets()[0], &ALICE), - alice_before[0] + amounts_out[0] - ); - assert_eq!( - AssetManager::free_balance(pool.assets()[1], &ALICE), - alice_before[1] + amounts_out[1] - ); + assert_balances!(pool_account, outcomes, [0, 0]); System::assert_last_event( Event::PoolDestroyed { who: ALICE, market_id, amounts_out }.into(), ); }); } +#[test] +fn removing_second_to_last_lp_does_not_destroy_pool_and_removes_node_from_liquidity_tree() { + ExtBuilder::default().build().execute_with(|| { + let liquidity = _5; + let spot_prices = vec![_1_6, _5_6 + 1]; + let swap_fee = CENT; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + liquidity, + spot_prices.clone(), + swap_fee, + ); + // Add a second LP, bringing the total of shares to _10. + deposit_complete_set(market_id, BOB, liquidity); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(BOB), + market_id, + liquidity, + vec![u128::MAX, u128::MAX], + )); + assert_pool_state!( + market_id, + [100_000_000_000, 10_175_559_822], + spot_prices, + 55_811_062_642, + create_b_tree_map!({ ALICE => _5, BOB => _5 }), + 0, + ); + assert_ok!(NeoSwaps::exit(RuntimeOrigin::signed(BOB), market_id, liquidity, vec![0, 0])); + assert_pool_state!( + market_id, + [50_050_000_000, 5_092_867_691], + spot_prices, + 27_933_436_852, + create_b_tree_map!({ ALICE => _5 }), + 0, + ); + }); +} + #[test] fn exit_fails_on_incorrect_vec_len() { ExtBuilder::default().build().execute_with(|| { @@ -192,7 +253,7 @@ fn exit_fails_on_insufficient_funds() { liquidity + 1, // One more than Alice has. vec![0, 0] ), - Error::::InsufficientPoolShares, + LiquidityTreeError::InsufficientStake.into_dispatch_error::(), ); }); } @@ -227,7 +288,7 @@ fn exit_fails_on_amount_out_below_min() { } #[test] -fn exit_fails_if_not_allowed() { +fn exit_fails_on_outstanding_fees() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, @@ -237,75 +298,61 @@ fn exit_fails_if_not_allowed() { vec![_1_2, _1_2], CENT, ); - let pool_shares_amount = _5; - assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, pool_shares_amount)); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(BOB), - market_id, - pool_shares_amount, - )); + assert_ok!(Pools::::try_mutate(market_id, |pool| pool + .as_mut() + .unwrap() + .liquidity_shares_manager + .deposit_fees(_10))); assert_noop!( - NeoSwaps::exit( - RuntimeOrigin::signed(BOB), - market_id, - pool_shares_amount, - vec![pool_shares_amount, pool_shares_amount] - ), - Error::::NotAllowed + NeoSwaps::exit(RuntimeOrigin::signed(ALICE), market_id, _1, vec![0, 0]), + LiquidityTreeError::UnwithdrawnFees.into_dispatch_error::(), ); }); } #[test] -fn exit_fails_on_outstanding_fees() { +fn exit_pool_fails_on_liquidity_too_low() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, MarketType::Scalar(0..=1), - _20, + _10, vec![_1_2, _1_2], CENT, ); - let pool_shares_amount = _20; - assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, pool_shares_amount)); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(BOB), - market_id, - pool_shares_amount, - )); - assert_ok!(Pools::::try_mutate(market_id, |pool| pool - .as_mut() - .unwrap() - .liquidity_shares_manager - .deposit_fees(1))); + // Will result in liquidity of about 0.7213475204444817. assert_noop!( - NeoSwaps::exit( - RuntimeOrigin::signed(BOB), - market_id, - pool_shares_amount, - vec![pool_shares_amount, pool_shares_amount] - ), - Error::::OutstandingFees + NeoSwaps::exit(RuntimeOrigin::signed(ALICE), market_id, _10 - _1_2, vec![0, 0]), + Error::::LiquidityTooLow ); }); } #[test] -fn exit_pool_fails_on_liquidity_too_low() { +fn exit_pool_fails_on_relative_liquidity_threshold_violated() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, MarketType::Scalar(0..=1), - _10, + _100, vec![_1_2, _1_2], CENT, ); - // Will result in liquidity of about 0.7213475204444817. + // Bob contributes only 1.390...% of liquidity. Any removal (no matter how small the amount) + // should fail. + let amount = 13_910_041_100; + deposit_complete_set(market_id, BOB, amount); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(BOB), + market_id, + amount, + vec![u128::MAX, u128::MAX], + )); assert_noop!( - NeoSwaps::exit(RuntimeOrigin::signed(ALICE), market_id, _10 - _1_2, vec![0, 0]), - Error::::LiquidityTooLow + NeoSwaps::exit(RuntimeOrigin::signed(BOB), market_id, CENT, vec![0, 0]), + Error::::MinRelativeLiquidityThresholdViolated ); }); } diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index d3d0daeb0..cc0912264 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -16,12 +16,22 @@ // along with Zeitgeist. If not, see . use super::*; +use crate::{ + helpers::create_spot_prices, + liquidity_tree::{ + traits::liquidity_tree_helper::LiquidityTreeHelper, types::LiquidityTreeError, + }, +}; +use alloc::collections::BTreeMap; use test_case::test_case; -#[test] -fn join_works() { +#[test_case(ALICE, create_b_tree_map!({ ALICE => _14 }))] +#[test_case(BOB, create_b_tree_map!({ ALICE => _10, BOB => _4 }))] +fn join_works( + who: AccountIdOf, + expected_pool_shares: BTreeMap, BalanceOf>, +) { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); let liquidity = _10; let spot_prices = vec![_1_6, _5_6 + 1]; let swap_fee = CENT; @@ -34,55 +44,79 @@ fn join_works() { swap_fee, ); let pool_shares_amount = _4; // Add 40% to the pool. - assert_ok!(AssetManager::deposit(BASE_ASSET, &ALICE, pool_shares_amount)); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(ALICE), - market_id, - pool_shares_amount, - )); - let pool_before = Pools::::get(market_id).unwrap(); - let alice_long_before = AssetManager::free_balance(pool_before.assets()[1], &ALICE); - let pool_outcomes_before: Vec<_> = - pool_before.assets().iter().map(|a| pool_before.reserve_of(a).unwrap()).collect(); + deposit_complete_set(market_id, who, pool_shares_amount); assert_ok!(NeoSwaps::join( - RuntimeOrigin::signed(ALICE), + RuntimeOrigin::signed(who), market_id, pool_shares_amount, - vec![u128::MAX, u128::MAX], + vec![u128::MAX; 2], )); - let pool_after = Pools::::get(market_id).unwrap(); - let ratio = (liquidity + pool_shares_amount).bdiv(liquidity).unwrap(); - let pool_outcomes_after: Vec<_> = - pool_after.assets().iter().map(|a| pool_after.reserve_of(a).unwrap()).collect(); - assert_eq!(pool_outcomes_after[0], ratio.bmul(pool_outcomes_before[0]).unwrap()); - assert_eq!(pool_outcomes_after[1], 14_245_783_753); - let long_diff = pool_outcomes_after[1] - pool_outcomes_before[1]; - assert_eq!(AssetManager::free_balance(pool_after.assets()[0], &ALICE), 0); - assert_eq!( - AssetManager::free_balance(pool_after.assets()[1], &ALICE), - alice_long_before - long_diff - ); - assert_eq!( - pool_after.liquidity_parameter, - ratio.bmul(pool_before.liquidity_parameter).unwrap() - ); - assert_eq!( - pool_after.liquidity_shares_manager.shares_of(&ALICE).unwrap(), - liquidity + pool_shares_amount + let expected_pool_balances = vec![140_000_000_000, 14_245_783_753]; + let new_liquidity_parameter = 78_135_487_700; + assert_pool_state!( + market_id, + expected_pool_balances, + spot_prices, + new_liquidity_parameter, + expected_pool_shares, + 0, ); + let amounts_in = vec![40_000_000_000, 4_070_223_930]; System::assert_last_event( Event::JoinExecuted { - who: ALICE, + who, market_id, pool_shares_amount, - amounts_in: vec![pool_shares_amount, long_diff], - new_liquidity_parameter: pool_after.liquidity_parameter, + amounts_in, + new_liquidity_parameter, } .into(), ); }); } +#[test] +fn join_fails_on_max_liquidity_providers() { + ExtBuilder::default().build().execute_with(|| { + let category_count = 2; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(category_count), + _100, + create_spot_prices::(category_count), + CENT, + ); + // Populate the tree with the maximum allowed number of LPs. + let offset = 100; + let max_node_count = LiquidityTreeOf::::max_node_count() as u128; + let amount = _10; + for index in 1..max_node_count { + let account = offset + index; + // Adding a little more because ceil rounding may cause slightly higher prices for + // joining. + deposit_complete_set(market_id, account, amount + CENT); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(account), + market_id, + amount, + vec![u128::MAX; category_count as usize], + )); + } + let account = offset + max_node_count; + deposit_complete_set(market_id, account, amount + CENT); + assert_noop!( + NeoSwaps::join( + RuntimeOrigin::signed(account), + market_id, + amount, + vec![u128::MAX; category_count as usize] + ), + LiquidityTreeError::TreeIsFull.into_dispatch_error::(), + ); + }); +} + #[test] fn join_fails_on_incorrect_vec_len() { ExtBuilder::default().build().execute_with(|| { @@ -219,31 +253,48 @@ fn join_fails_on_amount_in_above_max() { } #[test] -fn join_fails_if_not_allowed() { +fn join_pool_fails_on_relative_liquidity_threshold_violated() { ExtBuilder::default().build().execute_with(|| { let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, MarketType::Scalar(0..=1), - _20, + _100, vec![_1_2, _1_2], CENT, ); - let pool_shares_amount = _5; - assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, pool_shares_amount)); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(BOB), - market_id, - pool_shares_amount, - )); + // Bob contributes slightly less than 1.39098411% additional liquidity; this should fail. + let amount = 139098411 - 100; + deposit_complete_set(market_id, BOB, amount + CENT); assert_noop!( NeoSwaps::join( RuntimeOrigin::signed(BOB), market_id, - pool_shares_amount, - vec![pool_shares_amount, pool_shares_amount] + amount, + vec![u128::MAX, u128::MAX], ), - Error::::NotAllowed + Error::::MinRelativeLiquidityThresholdViolated + ); + }); +} + +#[test] +fn join_pool_fails_on_small_amounts() { + // This tests verifies that joining with miniscule amounts of pool shares can't be exploited to + // funnel money from the pool. + ExtBuilder::default().build().execute_with(|| { + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Scalar(0..=1), + 100_000_000_000 * _1, + vec![_1_2, _1_2], + CENT, + ); + deposit_complete_set(market_id, BOB, CENT); + assert_noop!( + NeoSwaps::join(RuntimeOrigin::signed(BOB), market_id, 1, vec![u128::MAX, u128::MAX],), + Error::::MinRelativeLiquidityThresholdViolated ); }); } diff --git a/zrml/neo-swaps/src/tests/liquidity_tree_interactions.rs b/zrml/neo-swaps/src/tests/liquidity_tree_interactions.rs new file mode 100644 index 000000000..b2a0c6920 --- /dev/null +++ b/zrml/neo-swaps/src/tests/liquidity_tree_interactions.rs @@ -0,0 +1,58 @@ +// Copyright 2023 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::*; + +#[test] +fn withdraw_fees_interacts_correctly_with_join() { + ExtBuilder::default().build().execute_with(|| { + let category_count = 2; + let spot_prices = vec![_3_4, _1_4]; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(category_count), + _10, + spot_prices.clone(), + CENT, + ); + + // Mock up some fees. + let mut pool = Pools::::get(market_id).unwrap(); + let fee_amount = _1; + assert_ok!(AssetManager::deposit(pool.collateral, &pool.account_id, fee_amount)); + assert_ok!(pool.liquidity_shares_manager.deposit_fees(fee_amount)); + Pools::::insert(market_id, pool.clone()); + + // Bob joins the pool after fees are distributed. + let join_amount = _10; + deposit_complete_set(market_id, BOB, join_amount + CENT); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(BOB), + market_id, + join_amount, + vec![u128::MAX; category_count as usize], + )); + + // Alice withdraws and should receive all fees. + let old_balance = ::MultiCurrency::free_balance(BASE_ASSET, &ALICE); + assert_ok!(NeoSwaps::withdraw_fees(RuntimeOrigin::signed(ALICE), market_id)); + assert_balance!(ALICE, BASE_ASSET, old_balance + fee_amount); + assert_ok!(NeoSwaps::withdraw_fees(RuntimeOrigin::signed(BOB), market_id)); + assert_balance!(BOB, BASE_ASSET, 0); + }); +} diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 32d320610..efbf95964 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -22,6 +22,7 @@ mod buy_and_sell; mod deploy_pool; mod exit; mod join; +mod liquidity_tree_interactions; mod sell; mod withdraw_fees; @@ -74,7 +75,7 @@ fn create_market( } fn create_market_and_deploy_pool( - creator: AccountIdTest, + creator: AccountIdOf, base_asset: Asset, market_type: MarketType, amount: BalanceOf, @@ -87,7 +88,6 @@ fn create_market_and_deploy_pool( market_id, amount, )); - println!("{:?}", AssetManager::free_balance(base_asset, &ALICE)); assert_ok!(NeoSwaps::deploy_pool( RuntimeOrigin::signed(ALICE), market_id, @@ -98,24 +98,16 @@ fn create_market_and_deploy_pool( market_id } -#[macro_export] -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_val { - panic!( - "assertion `left approx== right` failed\n left: {}\n right: {}\n \ - precision: {}\ndifference: {}", - *left_val, *right_val, *precision_val, diff - ); - } - } - } - }; +fn deposit_complete_set( + market_id: MarketId, + account: AccountIdOf, + amount: BalanceOf, +) { + let market = MarketCommons::market(&market_id).unwrap(); + assert_ok!(AssetManager::deposit(market.base_asset, &account, amount)); + assert_ok!(::CompleteSetOperations::buy_complete_set( + RuntimeOrigin::signed(account), + market_id, + amount, + )); } diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index 43ea93549..6c0bd9fc7 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -21,7 +21,6 @@ use test_case::test_case; #[test] fn sell_works() { ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); let liquidity = _10; let spot_prices = vec![_1_4, _3_4]; let swap_fee = CENT; @@ -35,15 +34,8 @@ fn sell_works() { ); let pool = Pools::::get(market_id).unwrap(); let amount_in = _10; - let pool_outcomes_before: Vec<_> = - pool.assets().iter().map(|a| pool.reserve_of(a).unwrap()).collect(); - let pool_liquidity_before = pool.liquidity_parameter; - AssetManager::deposit(BASE_ASSET, &BOB, amount_in).unwrap(); - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(BOB), - market_id, - amount_in, - )); + let liquidity_parameter_before = pool.liquidity_parameter; + deposit_complete_set(market_id, BOB, amount_in); let asset_in = pool.assets()[1]; assert_ok!(NeoSwaps::sell( RuntimeOrigin::signed(BOB), @@ -54,35 +46,27 @@ fn sell_works() { 0, )); let total_fee_percentage = swap_fee + EXTERNAL_FEES; - let expected_amount_out = 59632253897u128; + let expected_amount_out = 59632253897; let expected_fees = total_fee_percentage.bmul(expected_amount_out).unwrap(); let expected_swap_fee_amount = expected_fees / 2; let expected_external_fee_amount = expected_fees - expected_swap_fee_amount; let expected_amount_out_minus_fees = expected_amount_out - expected_fees; - assert_eq!(AssetManager::free_balance(BASE_ASSET, &BOB), expected_amount_out_minus_fees); - assert_eq!(AssetManager::free_balance(asset_in, &BOB), 0); - let pool = Pools::::get(market_id).unwrap(); - assert_eq!(pool.liquidity_parameter, pool_liquidity_before); - assert_eq!(pool.liquidity_shares_manager.owner, ALICE); - assert_eq!(pool.liquidity_shares_manager.total_shares, liquidity); - assert_eq!(pool.liquidity_shares_manager.fees, expected_swap_fee_amount); - let pool_outcomes_after: Vec<_> = - pool.assets().iter().map(|a| pool.reserve_of(a).unwrap()).collect(); - assert_eq!(pool_outcomes_after[0], pool_outcomes_before[0] - expected_amount_out); - assert_eq!( - pool_outcomes_after[1], - pool_outcomes_before[1] + (amount_in - expected_amount_out) - ); - let expected_pool_account_balance = - expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral); - assert_eq!( - AssetManager::free_balance(BASE_ASSET, &pool.account_id), - expected_pool_account_balance + assert_balance!(BOB, BASE_ASSET, expected_amount_out_minus_fees); + assert_balance!(BOB, asset_in, 0); + assert_pool_state!( + market_id, + vec![40367746103, 61119621067], + [5_714_285_714, 4_285_714_286], + liquidity_parameter_before, + create_b_tree_map!({ ALICE => liquidity }), + expected_swap_fee_amount, ); - assert_eq!( - AssetManager::free_balance(BASE_ASSET, &FEE_ACCOUNT), - expected_external_fee_amount + assert_balance!( + pool.account_id, + BASE_ASSET, + expected_swap_fee_amount + AssetManager::minimum_balance(pool.collateral) ); + assert_balance!(FEE_ACCOUNT, BASE_ASSET, expected_external_fee_amount); assert_eq!( AssetManager::total_issuance(pool.assets()[0]), liquidity + amount_in - expected_amount_out @@ -91,9 +75,6 @@ fn sell_works() { AssetManager::total_issuance(pool.assets()[1]), liquidity + amount_in - expected_amount_out ); - let price_sum = - pool.assets().iter().map(|&a| pool.calculate_spot_price(a).unwrap()).sum::(); - assert_eq!(price_sum, _1); System::assert_last_event( Event::SellExecuted { who: BOB, diff --git a/zrml/neo-swaps/src/tests/withdraw_fees.rs b/zrml/neo-swaps/src/tests/withdraw_fees.rs index 3fc71d6d2..64aea9079 100644 --- a/zrml/neo-swaps/src/tests/withdraw_fees.rs +++ b/zrml/neo-swaps/src/tests/withdraw_fees.rs @@ -19,38 +19,67 @@ use super::*; #[test] fn withdraw_fees_works() { + // Verify that fees are correctly distributed among LPs. ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let liquidity = _10; - let spot_prices = vec![_1_6, _5_6 + 1]; - let swap_fee = CENT; + let category_count = 2; + let spot_prices = vec![_3_4, _1_4]; let market_id = create_market_and_deploy_pool( ALICE, BASE_ASSET, - MarketType::Scalar(0..=1), - liquidity, + MarketType::Categorical(category_count), + _10, spot_prices.clone(), - swap_fee, + CENT, ); - // Mock up some fees for Alice to withdraw. + let join = |who: AccountIdOf, amount: BalanceOf| { + // Adding a little more to ensure that rounding doesn't cause issues. + deposit_complete_set(market_id, who, amount + CENT); + assert_ok!(NeoSwaps::join( + RuntimeOrigin::signed(who), + market_id, + amount, + vec![u128::MAX; category_count as usize], + )); + }; + join(BOB, _10); + join(CHARLIE, _20); + + // Mock up some fees. let mut pool = Pools::::get(market_id).unwrap(); - let fees = 123456789; - assert_ok!(AssetManager::deposit(pool.collateral, &pool.account_id, fees)); - pool.liquidity_shares_manager.fees = fees; + let fee_amount = _1; + assert_ok!(AssetManager::deposit(pool.collateral, &pool.account_id, fee_amount)); + assert_ok!(pool.liquidity_shares_manager.deposit_fees(fee_amount)); Pools::::insert(market_id, pool.clone()); - let alice_before = AssetManager::free_balance(pool.collateral, &ALICE); - assert_ok!(NeoSwaps::withdraw_fees(RuntimeOrigin::signed(ALICE), market_id)); - let expected_pool_account_balance = AssetManager::minimum_balance(pool.collateral); - assert_eq!( - AssetManager::free_balance(pool.collateral, &pool.account_id), - expected_pool_account_balance - ); - assert_eq!(AssetManager::free_balance(pool.collateral, &ALICE), alice_before + fees); - let pool_after = Pools::::get(market_id).unwrap(); - assert_eq!(pool_after.liquidity_shares_manager.fees, 0); - System::assert_last_event( - Event::FeesWithdrawn { who: ALICE, market_id, amount: fees }.into(), - ); + let liquidity_parameter = 288_539_008_176; + let pool_balances = [83_007_499_856, 400_000_000_000]; + + let test_withdraw = |who: AccountIdOf, + fees_withdrawn: BalanceOf, + fees_remaining: BalanceOf| { + // Make sure everybody's got at least the minimum deposit. + assert_ok!(::MultiCurrency::deposit( + BASE_ASSET, + &who, + ::MultiCurrency::minimum_balance(BASE_ASSET) + )); + let old_balance = ::MultiCurrency::free_balance(BASE_ASSET, &who); + assert_ok!(NeoSwaps::withdraw_fees(RuntimeOrigin::signed(who), market_id)); + assert_balance!(who, BASE_ASSET, old_balance + fees_withdrawn); + assert_pool_state!( + market_id, + pool_balances, + spot_prices, + liquidity_parameter, + create_b_tree_map!({ ALICE => _10, BOB => _10, CHARLIE => _20 }), + fees_remaining, + ); + System::assert_last_event( + Event::FeesWithdrawn { who, market_id, amount: fees_withdrawn }.into(), + ); + }; + test_withdraw(ALICE, _1_4, _3_4); + test_withdraw(BOB, _1_4, _1_2); + test_withdraw(CHARLIE, _1_2, 0); }); } @@ -65,3 +94,38 @@ fn withdraw_fees_fails_on_pool_not_found() { ); }); } + +#[test] +fn withdraw_fees_is_noop_if_there_are_no_fees() { + ExtBuilder::default().build().execute_with(|| { + let spot_prices = vec![_3_4, _1_4]; + let amount = _40; + let market_id = create_market_and_deploy_pool( + ALICE, + BASE_ASSET, + MarketType::Categorical(2), + amount, + spot_prices.clone(), + CENT, + ); + let pool_balances = [83_007_499_856, 400_000_000_000]; + let liquidity_parameter = 288_539_008_178; + assert_pool_state!( + market_id, + pool_balances, + spot_prices, + liquidity_parameter, + create_b_tree_map!({ ALICE => amount }), + 0, + ); + assert_ok!(NeoSwaps::withdraw_fees(RuntimeOrigin::signed(ALICE), market_id)); + assert_pool_state!( + market_id, + pool_balances, + spot_prices, + liquidity_parameter, + create_b_tree_map!({ ALICE => amount }), + 0, + ); + }); +} diff --git a/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs b/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs index 97ad3f333..b99302fb1 100644 --- a/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs +++ b/zrml/neo-swaps/src/traits/liquidity_shares_manager.rs @@ -21,8 +21,14 @@ use sp_runtime::{DispatchError, DispatchResult}; /// Trait for managing pool share tokens and distributing fees to LPs according to their share of /// the total issuance of pool share tokens. pub trait LiquiditySharesManager { + type JoinBenchmarkInfo; + /// Add `amount` units of pool shares to the account of `who`. - fn join(&mut self, who: &T::AccountId, amount: BalanceOf) -> DispatchResult; + fn join( + &mut self, + who: &T::AccountId, + amount: BalanceOf, + ) -> Result; /// Remove `amount` units of pool shares from the account of `who`. fn exit(&mut self, who: &T::AccountId, amount: BalanceOf) -> DispatchResult; diff --git a/zrml/neo-swaps/src/types/pool.rs b/zrml/neo-swaps/src/types/pool.rs index d76264cae..598edc1b4 100644 --- a/zrml/neo-swaps/src/types/pool.rs +++ b/zrml/neo-swaps/src/types/pool.rs @@ -30,7 +30,7 @@ use sp_runtime::{ DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, Saturating, }; -#[derive(TypeInfo, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Pool where diff --git a/zrml/neo-swaps/src/types/solo_lp.rs b/zrml/neo-swaps/src/types/solo_lp.rs index 02cbed618..13f9c4cde 100644 --- a/zrml/neo-swaps/src/types/solo_lp.rs +++ b/zrml/neo-swaps/src/types/solo_lp.rs @@ -24,6 +24,7 @@ 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 { @@ -32,6 +33,7 @@ pub struct SoloLp { pub fees: BalanceOf, } +#[allow(dead_code)] impl SoloLp { pub(crate) fn new(owner: T::AccountId, total_shares: BalanceOf) -> SoloLp { SoloLp { owner, total_shares, fees: Zero::zero() } @@ -43,7 +45,9 @@ where T::AccountId: PartialEq, BalanceOf: AtLeast32BitUnsigned + Copy + Zero, { - fn join(&mut self, who: &T::AccountId, shares: BalanceOf) -> DispatchResult { + type JoinBenchmarkInfo = (); + + fn join(&mut self, who: &T::AccountId, shares: BalanceOf) -> Result<(), DispatchError> { ensure!(*who == self.owner, Error::::NotAllowed); self.total_shares = self.total_shares.checked_add(&shares).ok_or(Error::::MathError)?; Ok(()) diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 8ee41b44a..c7114792f 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,18 +19,18 @@ //! Autogenerated weights for zrml_neo_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: `2023-12-20`, STEPS: `10`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `mkl-mac`, CPU: `` //! EXECUTION: `Some(Wasm)`, 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 +// --repeat=2 // --pallet=zrml_neo_swaps // --extrinsic=* // --execution=wasm @@ -51,7 +51,9 @@ use frame_support::{traits::Get, weights::Weight}; pub trait WeightInfoZeitgeist { fn buy(n: u32) -> Weight; fn sell(n: u32) -> Weight; - fn join(n: u32) -> Weight; + fn join_in_place(n: u32) -> Weight; + fn join_reassigned(n: u32) -> Weight; + fn join_leaf(n: u32) -> Weight; fn exit(n: u32) -> Weight; fn withdraw_fees() -> Weight; fn deploy_pool(n: u32) -> Weight; @@ -63,101 +65,179 @@ 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: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:2) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:3 w:3) + /// Storage: Tokens Accounts (r:129 w:129) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:2 w:2) + /// Storage: Tokens TotalIssuance (r:128 w:128) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - fn buy(_n: u32) -> Weight { + /// The range of component `n` is `[2, 128]`. + fn buy(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2905` - // Estimated: `28324` - // Minimum execution time: 389_381 nanoseconds. - Weight::from_parts(456_541_000, 28324) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `2790 + n * (195 ±0)` + // Estimated: `160792 + n * (5116 ±0)` + // Minimum execution time: 510_000 nanoseconds. + Weight::from_parts(480_304_329, 160792) + // Standard Error: 593_647 + .saturating_add(Weight::from_parts(23_811_471, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5116).saturating_mul(n.into())) } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:3 w:3) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, 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:2 w:2) + /// Storage: System Account (r:3 w:3) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:2 w:2) + /// Storage: Tokens TotalIssuance (r:128 w:128) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - fn sell(_n: u32) -> Weight { + /// The range of component `n` is `[2, 128]`. + fn sell(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `3071` - // Estimated: `28324` - // Minimum execution time: 404_471 nanoseconds. - Weight::from_parts(473_241_000, 28324) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `2952 + n * (195 ±0)` + // Estimated: `160792 + n * (5116 ±0)` + // Minimum execution time: 380_000 nanoseconds. + Weight::from_parts(404_645_021, 160792) + // Standard Error: 514_733 + .saturating_add(Weight::from_parts(33_359_307, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5116).saturating_mul(n.into())) } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:4) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - fn join(_n: u32) -> Weight { + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// The range of component `n` is `[2, 128]`. + fn join_in_place(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2793` - // Estimated: `20672` - // Minimum execution time: 122_321 nanoseconds. - Weight::from_parts(140_940_000, 20672) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `140919 + n * (261 ±0)` + // Estimated: `152980 + n * (5196 ±0)` + // Minimum execution time: 507_000 nanoseconds. + Weight::from_parts(381_461_038, 152980) + // Standard Error: 6_222_921 + .saturating_add(Weight::from_parts(43_846_753, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:4) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:256 w:256) /// 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) - fn exit(_n: u32) -> Weight { + /// The range of component `n` is `[2, 128]`. + fn join_reassigned(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2561` - // Estimated: `23279` - // Minimum execution time: 124_770 nanoseconds. - Weight::from_parts(151_910_000, 23279) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `140715 + n * (261 ±0)` + // Estimated: `152980 + n * (5196 ±0)` + // Minimum execution time: 445_000 nanoseconds. + Weight::from_parts(295_293_073, 152980) + // Standard Error: 2_105_968 + .saturating_add(Weight::from_parts(41_721_645, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) } + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:256 w:256) + /// 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) + /// The range of component `n` is `[2, 128]`. + fn join_leaf(n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `140720 + n * (261 ±0)` + // Estimated: `152980 + n * (5196 ±0)` + // Minimum execution time: 489_000 nanoseconds. + Weight::from_parts(426_883_549, 152980) + // Standard Error: 1_583_212 + .saturating_add(Weight::from_parts(41_776_406, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + } + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: NeoSwaps Pools (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:256 w:256) + /// 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) + /// The range of component `n` is `[2, 128]`. + fn exit(n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `140816 + n * (261 ±0)` + // Estimated: `152980 + n * (5196 ±0)` + // Minimum execution time: 449_000 nanoseconds. + Weight::from_parts(555_190_909, 152980) + // Standard Error: 2_997_117 + .saturating_add(Weight::from_parts(39_540_909, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) + } + /// Storage: NeoSwaps Pools (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn withdraw_fees() -> Weight { // Proof Size summary in bytes: - // Measured: `1819` - // Estimated: `9734` - // Minimum execution time: 76_910 nanoseconds. - Weight::from_parts(94_141_000, 9734) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `139382` + // Estimated: `152434` + // Minimum execution time: 431_000 nanoseconds. + Weight::from_parts(452_000_000, 152434) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:4 w:4) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - fn deploy_pool(_n: u32) -> Weight { + /// The range of component `n` is `[2, 128]`. + fn deploy_pool(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2278` - // Estimated: `23279` - // Minimum execution time: 167_210 nanoseconds. - Weight::from_parts(189_090_000, 23279) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(6)) + // Measured: `2167 + n * (113 ±0)` + // Estimated: `152980 + n * (5196 ±0)` + // Minimum execution time: 233_000 nanoseconds. + Weight::from_parts(553_764_069, 152980) + // Standard Error: 3_614_297 + .saturating_add(Weight::from_parts(46_549_783, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5196).saturating_mul(n.into())) } } From 71c846491ac538250b2de91614868e324187594c Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 5 Jan 2024 11:14:57 +0100 Subject: [PATCH 017/104] Improve XCM fee handling (#1225) * Fix padding of foreign fees in Zeitgeist runtime * Fix padding of foreign fees in Battery Station runtime * Format * Update copyright * Use adjusted_balance function --- .../integration_tests/xcm/tests/transfers.rs | 21 ++++++++++++++++-- .../battery-station/src/xcm_config/config.rs | 15 +++++++++---- .../integration_tests/xcm/tests/transfers.rs | 22 ++++++++++++++++--- runtime/zeitgeist/src/xcm_config/config.rs | 15 +++++++++---- 4 files changed, 60 insertions(+), 13 deletions(-) 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/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/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/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); + } } } } From 3761f96d9ff14a4b3c6c152f8b5df700dc2bf54c Mon Sep 17 00:00:00 2001 From: Chralt Date: Mon, 8 Jan 2024 14:00:19 +0100 Subject: [PATCH 018/104] Remove court and global disputes from call filter for the main-net (#1226) * remove from call filter * update copyright --- runtime/zeitgeist/src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 3a5a36e71..5193bf5e0 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. @@ -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 { @@ -121,8 +121,7 @@ impl Contains for IsCallable { use pallet_vesting::Call::force_vested_transfer; use zeitgeist_primitives::types::{ - MarketDisputeMechanism::{Court, SimpleDisputes}, - ScoringRule::RikiddoSigmoidFeeMarketEma, + MarketDisputeMechanism::SimpleDisputes, ScoringRule::RikiddoSigmoidFeeMarketEma, }; use zrml_prediction_markets::Call::{ admin_move_market_to_closed, admin_move_market_to_resolved, @@ -161,21 +160,19 @@ 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, + // Disable SimpleDisputes dispute resolution mechanism + create_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, + edit_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(Court | SimpleDisputes), + dispute_mechanism: Some(SimpleDisputes), .. } => false, admin_move_market_to_closed { .. } => false, From 9e1cc2c2fc0c1e119ca321d6e0de35d4bb355375 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 8 Jan 2024 15:43:21 +0100 Subject: [PATCH 019/104] Sunset old AMMs and their pools (#1197) * Replace `bmul` and `bdiv` with traited versions * Restructure directories * Replace `saturating_*` from neo-swaps * Fix formatting * Restructure zrml-swaps math * Implement and test `b*` * Fix formatting * Use new math in orderbook-v1 * Replace checked multiplication with new math * Use correct rounding in neo-swaps * Add docs * Update licenses * Remove `fixed` module from `primitives` * Fix formatting * . * Rewrite math functions * Remove training wheels * Fix docs.pdf * Fix quotes * Add tests for buying * Add tests for selling and improve error names * Update docs * Check adjusted amount in for numerical bounds * Remove unused implementations * Adjust docs * Add stress test exploring various scenarios * Add swap fees to stress test * Add underscore separators * Clean up * Benchmark `buy` as function of `asset_count` * Update benchmarks * Clippy fix * Fix benchmark tests * Update benchmarks of zrml-prediction-markets * Fix broken comment * Add comment explaining benchmark spot prices * Use clearer constants for `amount_in` in tests * Update zrml/neo-swaps/src/traits/pool_operations.rs Co-authored-by: Chralt * Fix botched merge * Fix merge * Update benchmarks * Remove `pool_*_subsidy` * Remove `distribute_pool_share_rewards` * Remove `end_subsidy_phase` * Remove `destroy_pool_in_subsidy_phase` * Remove `MinSubsidy*` * Remove `SubsidyProviders` * Remove `start_subsidy` * Rewrite `create_pool` * Rewrite `swap_exact_amount_in` * Rewrite `swap_exact_amount_out` * Rewrite utility functions * Remove Rikiddo from weight calculation * Remove Rikiddo from zrml-swaps * Remove unused errors * Remove `ScoringRule::Rikiddo...` * Remove `*SubsidyPeriod` * Remove Rikiddo-related storage and events * Remove automatic opening of pools * Remove `open_pool` from prediction-markets * Remove `Swaps::close_pool` from prediction-markets * Remove `clean_up_pool` from prediction-markets * Remove `clean_up_pool` from `Swaps` trait * Remove CPMM deployment from prediction-markets * Remove automatic arbitrage * Move `market_account` back to prediction-markets * Remove unused market states * Fix fuzz tests * Implement market migration * Minor changes * Fix migration behavior * Remove creator fees from swaps * Fix try-runtime * Fix clippy issues * Remove `LiquidityMining` from swaps * Fix `get_spot_prices` * Take first step to remove `MarketCommons` from swaps * Remove `MarketCommons` from swaps * Rewrite `PoolStatus` * Move `Pool*` to swaps * Use `Bounded*` types in `Pool` * Finish swaps migration * Add missing files * Fix formatting and clippy errors * Remove `pool_status.rs` * Ignore doctests * Fix fuzz tests * Add prediciton-markets migration * Test prediction-markets migration * Finish tests of the market-commons migration * Add migrations to runtime and fix various errors * Clean up * Clean up * Format code * Fix pool migration behavior * Remove `MarketId` from swaps * Fix formatting * Fix formatting * Remove `CPMM` and allow other scoring rules on Battery Station * Update macros/Cargo.toml Co-authored-by: Harald Heckmann * Update primitives/src/traits/zeitgeist_asset.rs Co-authored-by: Harald Heckmann * Update zrml/market-commons/src/migrations.rs Co-authored-by: Harald Heckmann * Update zrml/swaps/src/migrations.rs Co-authored-by: Harald Heckmann * Update zrml/swaps/src/migrations.rs Co-authored-by: Harald Heckmann * Update zrml/prediction-markets/src/migrations.rs Co-authored-by: Harald Heckmann * Update zrml/market-commons/src/migrations.rs Co-authored-by: Harald Heckmann * Clean up TODOs/FIXMEs * Update changelog * Make more changes to changelog * Clear zrml-swaps storage * Remove cfg-if dependency * Fix formatting * Trigger CI * Update copyright notices * Update docs/changelog_for_devs.md Co-authored-by: Chralt * Make benchmark helper only available if feature flags are set * Remove `ZeitgeistAsset` trait * Remove preliminary benchmarks with more steps * Format code * Fix copyright notice --------- Co-authored-by: Chralt Co-authored-by: Harald Heckmann --- Cargo.lock | 11 +- Cargo.toml | 3 + docs/changelog_for_devs.md | 73 + macros/Cargo.toml | 5 + macros/src/lib.rs | 34 + primitives/Cargo.toml | 1 + primitives/src/asset.rs | 22 +- primitives/src/constants/mock.rs | 8 +- primitives/src/lib.rs | 4 +- primitives/src/market.rs | 32 +- primitives/src/math/fixed.rs | 6 +- primitives/src/math/mod.rs | 3 +- .../swaps/src => primitives/src/math}/root.rs | 13 +- primitives/src/pool.rs | 90 - primitives/src/traits.rs | 4 +- .../src/traits/market_commons_pallet_api.rs | 5 +- primitives/src/traits/swaps.rs | 91 +- primitives/src/traits/zeitgeist_asset.rs | 37 + primitives/src/types.rs | 6 +- runtime/battery-station/src/lib.rs | 53 +- runtime/common/src/lib.rs | 86 +- runtime/zeitgeist/src/lib.rs | 15 +- scripts/update-copyright.sh | 4 +- zrml/authorized/src/lib.rs | 4 +- zrml/authorized/src/migrations.rs | 58 +- zrml/authorized/src/mock.rs | 6 +- zrml/court/src/benchmarks.rs | 4 +- zrml/court/src/mock.rs | 5 +- zrml/court/src/tests.rs | 4 +- zrml/global-disputes/src/mock.rs | 7 +- zrml/global-disputes/src/utils.rs | 4 +- zrml/liquidity-mining/src/mock.rs | 5 +- zrml/liquidity-mining/src/tests.rs | 4 +- zrml/market-commons/Cargo.toml | 1 + zrml/market-commons/src/lib.rs | 47 +- zrml/market-commons/src/migrations.rs | 505 +++- zrml/market-commons/src/mock.rs | 5 +- zrml/market-commons/src/tests.rs | 4 +- zrml/neo-swaps/Cargo.toml | 2 - zrml/neo-swaps/docs/docs.pdf | Bin 204580 -> 204581 bytes zrml/neo-swaps/docs/docs.tex | 2 +- zrml/neo-swaps/src/benchmarking.rs | 4 +- zrml/neo-swaps/src/lib.rs | 12 +- zrml/neo-swaps/src/migration.rs | 10 +- zrml/neo-swaps/src/mock.rs | 81 +- zrml/neo-swaps/src/tests/buy.rs | 5 +- zrml/neo-swaps/src/tests/deploy_pool.rs | 13 +- zrml/neo-swaps/src/tests/join.rs | 5 +- zrml/neo-swaps/src/tests/sell.rs | 5 +- zrml/neo-swaps/src/types/pool.rs | 4 +- zrml/neo-swaps/src/types/solo_lp.rs | 5 +- zrml/orderbook/src/mock.rs | 5 +- zrml/orderbook/src/tests.rs | 10 +- zrml/parimutuel/src/mock.rs | 5 +- zrml/parimutuel/src/tests/buy.rs | 7 +- zrml/parimutuel/src/tests/claim.rs | 7 +- zrml/parimutuel/src/tests/refund.rs | 7 +- zrml/prediction-markets/Cargo.toml | 2 - .../fuzz/pm_full_workflow.rs | 4 +- zrml/prediction-markets/src/benchmarks.rs | 288 +- zrml/prediction-markets/src/lib.rs | 745 +---- zrml/prediction-markets/src/migrations.rs | 174 +- zrml/prediction-markets/src/mock.rs | 79 +- zrml/prediction-markets/src/tests.rs | 1307 ++------- zrml/prediction-markets/src/weights.rs | 59 +- zrml/simple-disputes/src/lib.rs | 4 +- zrml/simple-disputes/src/mock.rs | 5 +- zrml/simple-disputes/src/tests.rs | 4 +- zrml/swaps/Cargo.toml | 7 +- zrml/swaps/fuzz/create_pool.rs | 9 +- zrml/swaps/fuzz/utils.rs | 23 +- zrml/swaps/rpc/src/lib.rs | 45 +- zrml/swaps/runtime-api/src/lib.rs | 19 +- zrml/swaps/src/arbitrage.rs | 450 --- zrml/swaps/src/benchmarks.rs | 838 +----- zrml/swaps/src/lib.rs | 1850 ++----------- zrml/swaps/src/migrations.rs | 386 +++ zrml/swaps/src/mock.rs | 99 +- zrml/swaps/src/tests.rs | 2445 ++--------------- zrml/swaps/src/types/mod.rs | 20 + .../swaps/src/types/pool.rs | 43 +- zrml/swaps/src/utils.rs | 166 +- zrml/swaps/src/weights.rs | 508 +--- 83 files changed, 2396 insertions(+), 8641 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/lib.rs rename {zrml/swaps/src => primitives/src/math}/root.rs (97%) delete mode 100644 primitives/src/pool.rs create mode 100644 primitives/src/traits/zeitgeist_asset.rs delete mode 100644 zrml/swaps/src/arbitrage.rs create mode 100644 zrml/swaps/src/types/mod.rs rename primitives/src/pool_status.rs => zrml/swaps/src/types/pool.rs (54%) diff --git a/Cargo.lock b/Cargo.lock index f59d33291..c5b66768d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14277,6 +14277,10 @@ dependencies = [ "time 0.3.24", ] +[[package]] +name = "zeitgeist-macros" +version = "0.4.3" + [[package]] name = "zeitgeist-node" version = "0.4.2" @@ -14613,6 +14617,7 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", + "test-case", "zeitgeist-primitives", ] @@ -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", @@ -14743,7 +14747,6 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", - "substrate-fixed", "test-case", "xcm", "zeitgeist-primitives", @@ -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", ] 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/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/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/primitives/Cargo.toml b/primitives/Cargo.toml index 53d3b282e..63f20fcf1 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", 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 0af4fe0b5..d41fdc0f1 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. @@ -197,13 +197,13 @@ where } fn bmul_bdiv_floor(&self, _multiplier: Self, _divisor: Self) -> Result { - // FIXME Untested! + // TODO(#1217): Commented mplementation below should work, but remains untested! // bmul_bdiv_common(self, multiplier, divisor, Zero::zero()) Err(DispatchError::Other("not implemented")) } fn bmul_bdiv_ceil(&self, _multiplier: Self, _divisor: Self) -> Result { - // FIXME Untested! + // TODO(#1217): Commented mplementation below should work, but remains untested! // let adjustment = ZeitgeistBase::::get()?.checked_sub_res(&1u8.into())?; // bmul_bdiv_common(self, multiplier, divisor, adjustment) Err(DispatchError::Other("not implemented")) 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/src/lib.rs b/runtime/battery-station/src/lib.rs index f6a3792e9..95922c090 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,9 +54,7 @@ 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::{ @@ -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, @@ -183,30 +171,17 @@ impl Contains for IsCallable { 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, + }, _ => true, } } 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/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 5193bf5e0..22ba04af2 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -119,13 +119,9 @@ impl Contains for IsCallable { set_code as set_code_contracts, }; use pallet_vesting::Call::force_vested_transfer; - - use zeitgeist_primitives::types::{ - MarketDisputeMechanism::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, }; #[allow(clippy::match_like_matches_macro)] @@ -165,16 +161,9 @@ impl Contains for IsCallable { 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 SimpleDisputes dispute resolution mechanism create_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, edit_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, - create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(SimpleDisputes), - .. - } => false, admin_move_market_to_closed { .. } => false, admin_move_market_to_resolved { .. } => false, _ => true, 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/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/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 0abbc954e..12cd68d55 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. @@ -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, 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/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/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..69cdad15e 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] 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: MarketkO0m;Xj+;X#cF9* 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 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/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/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..333c5d6a1 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", 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/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 9b94d1314..6afce4418 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; @@ -125,7 +124,7 @@ fn create_close_and_report_market( let (caller, market_id) = create_market_common::( permission, options, - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(period), Some(MarketDisputeMechanism::Court), )?; @@ -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), )?; @@ -189,32 +188,21 @@ 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)?; @@ -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), @@ -316,7 +296,7 @@ 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), )?; @@ -405,7 +385,7 @@ 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) )?; @@ -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); @@ -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(); @@ -779,8 +660,8 @@ 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())?; @@ -788,7 +669,7 @@ benchmarks! { 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), )?; @@ -807,7 +688,7 @@ benchmarks! { 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) )?; @@ -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), @@ -956,7 +810,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..range_end)), Some(MarketDisputeMechanism::Court), )?; @@ -1015,7 +869,7 @@ 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()?; @@ -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,7 +975,7 @@ 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), )?; @@ -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), )?; @@ -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), )?; @@ -1274,7 +1100,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), )?; @@ -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), )?; @@ -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), )?; @@ -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), diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index d45196e49..f20e3df11 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}; @@ -332,30 +334,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 +425,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 +434,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 +576,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 +598,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 +649,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 +688,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 +705,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 +849,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 +860,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 +867,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 +1432,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 +1443,10 @@ 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()) } } @@ -1868,10 +1569,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 +1577,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 +1663,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] @@ -2120,11 +1810,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. @@ -2173,14 +1858,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 +1880,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 +1924,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 +1947,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 +1957,7 @@ mod pallet { ValueQuery, >; + // TODO(#1212): Remove in v0.5.1. #[pallet::storage] pub type MarketIdsPerOpenTimeFrame = StorageMap< _, @@ -2364,6 +2012,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 +2029,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 +2050,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 +2107,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 +2188,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 +2247,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 +2298,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 +2462,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 +2492,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 +2734,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 +2754,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] { @@ -3434,65 +2835,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 +2872,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..942820374 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; @@ -1223,45 +1193,12 @@ fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { }); } -#[test] -fn on_market_open_successfully_auto_opens_market_pool_with_blocks() { - ExtBuilder::default().build().execute_with(|| { - let start = 33; - let end = 66; - let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(start..end), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - 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(start); - let pool_after_open = Swaps::pool(pool_id).unwrap(); - assert_eq!(pool_after_open.pool_status, PoolStatus::Active); - }); -} - #[test] fn on_market_close_successfully_auto_closes_market_with_blocks() { ExtBuilder::default().build().execute_with(|| { 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(), @@ -1269,73 +1206,31 @@ fn on_market_close_successfully_auto_closes_market_with_blocks() { 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(end - 1); 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); 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() { - ExtBuilder::default().build().execute_with(|| { - let start: Moment = (33 * MILLISECS_PER_BLOCK).into(); - let end: Moment = (66 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - 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); - - 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); - }); -} - #[test] fn on_market_close_successfully_auto_closes_market_with_timestamps() { ExtBuilder::default().build().execute_with(|| { 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(), @@ -1343,82 +1238,28 @@ 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, )); 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); 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::MarketClosed(market_id).into()); }); } -#[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 - // 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 category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(50), - 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::Timestamp(start..end), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - - // This block takes much longer than 12sec, but markets and pools still close correctly. - set_timestamp_for_on_initialize(end / 2); - 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); - }); -} - #[test] 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 @@ -1429,7 +1270,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,13 +1278,12 @@ 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( + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), Asset::Ztg, Perbill::zero(), @@ -1451,11 +1291,10 @@ 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, )); // This block takes much longer than 12sec, but markets and pools still close correctly. @@ -1464,14 +1303,10 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { 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()); }); } @@ -1483,7 +1318,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 +1326,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 +1358,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 +1373,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 +1398,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 +1422,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 +1453,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 +1477,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 +1495,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 +1509,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 +1525,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 +1540,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 +1550,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 +1588,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 +1604,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 +1653,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 +1699,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 +1723,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 +1771,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 +1805,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 +1828,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 +1851,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 +1874,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 +1930,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 +1971,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 +2006,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 +2045,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 +2079,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 +2106,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 +2135,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 +2163,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 +2199,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 +2270,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 +2339,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 +2371,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 +2400,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 +2475,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 +2543,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 +2597,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 +2638,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 +2670,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 +2708,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 +2748,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 +2802,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 +2843,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 +2912,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 +2959,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 +2992,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 +3029,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 +3088,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 +3260,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 +3528,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 +3606,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 +3616,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 +3646,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 +3682,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)); @@ -4003,261 +3746,43 @@ 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")] +fn only_creator_can_edit_market() { ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..1, + ScoringRule::Lmsr, + ); -#[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); + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); - 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)); - }); -} + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; -#[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); + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - 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(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::CPMM, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // ALICE is market creator through simple_create_categorical_market - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(BOB), - Asset::Ztg, - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM - ), - Error::::EditorNotCreator - ); + assert!(MarketIdsForEdit::::contains_key(0)); + + // ALICE is market creator through simple_create_categorical_market + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(BOB), + Asset::Ztg, + 0, + CHARLIE, + MarketPeriod::Block(0..1), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::EditorNotCreator + ); }); } @@ -4270,7 +3795,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 +3821,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 +3841,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 +3868,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 +3884,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 +3899,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 +3921,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 +3961,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 +4124,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 +4143,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 +4155,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 +4171,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 +4317,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 +4337,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 +4355,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 +4401,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 +4467,7 @@ fn outsider_reports_wrong_outcome() { MarketCreation::Permissionless, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let outsider = CHARLIE; @@ -5169,7 +4546,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 +4592,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 +4639,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 +4690,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 +4742,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 +4802,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 +4860,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 +4932,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 +4999,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,7 +5022,7 @@ fn report_fails_on_market_state_closed_for_advised_market() { MarketCreation::Advised, MarketType::Categorical(2), Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + ScoringRule::Lmsr )); assert_noop!( PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), @@ -5654,56 +5031,6 @@ fn report_fails_on_market_state_closed_for_advised_market() { }); } -#[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 - )); - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::InsufficientSubsidy; - Ok(()) - }); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - #[test] fn report_fails_on_market_state_active() { ExtBuilder::default().build().execute_with(|| { @@ -5718,7 +5045,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 +5054,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 +5068,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 +5095,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 +5136,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 +5164,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 +5192,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 +5223,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 +5232,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 +5245,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 +5379,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 +5398,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 +5424,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 +5474,7 @@ fn close_trusted_market_works() { MarketCreation::Permissionless, MarketType::Categorical(3), None, - ScoringRule::CPMM, + ScoringRule::Lmsr, )); let market_id = 0; @@ -6417,7 +5529,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 +5551,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 +5575,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 +5594,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 +5601,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..cd0bf0b6c 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,7 @@ 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; } /// Weight functions for zrml_prediction_markets (automatically generated) @@ -105,14 +104,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 +315,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 +557,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 +565,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 +865,12 @@ 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 { + 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/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/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 298a9fe34..28d2cac64 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", 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/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/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..6a644f4b8 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}, + checked_ops_res::{CheckedAddRes, CheckedMulRes}, + fixed::FixedMul, }, - traits::{MarketCommonsPalletApi, Swaps, ZeitgeistAssetManager}, - types::{ - Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, - ScoringRule, SerdeWrapper, - }, - }; - 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`. @@ -216,7 +145,6 @@ mod pallet { 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(()) }, @@ -233,95 +161,6 @@ mod pallet { 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(()) - }) - } - /// Pool - Exit with exact pool amount /// /// Takes an asset from `pool_id` and transfers to `origin`. Differently from `pool_exit`, @@ -345,7 +184,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 +222,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 +242,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 +255,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 +265,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 +301,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 +314,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 +325,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 +348,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 +386,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 +403,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 +414,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 +424,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 +445,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 +466,6 @@ mod pallet { asset_out, min_asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } @@ -725,16 +488,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,7 +509,6 @@ mod pallet { asset_out, asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } @@ -755,51 +516,27 @@ mod pallet { #[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 +559,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 +585,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 +615,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 +647,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 +669,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 +685,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 +713,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 +728,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 +744,38 @@ 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( - pool_id: PoolId, - max_iterations: usize, - ) -> Result { - 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)) - } - } - 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 +784,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 +797,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 +808,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 +824,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 +839,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 +861,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 +873,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 +889,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 +912,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 +924,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 +943,8 @@ mod pallet { where T: Config, { + type Asset = AssetOf; type Balance = BalanceOf; - type MarketId = MarketIdOf; /// Creates an initial active pool. /// @@ -1830,7 +955,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 +963,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 +983,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 +1033,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 +1066,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 +1095,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 +1108,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 +1120,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 +1137,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 +1157,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 +1171,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 +1178,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 +1193,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 +1214,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 +1227,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 +1263,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 +1270,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 +1293,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 +1332,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 +1339,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..80b8f7609 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)), @@ -1198,7 +743,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 +777,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 +802,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), @@ -1292,224 +837,61 @@ 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 +#[test_case(49_999_999_665, 12_272_234_300, 0, 0; "no_fees")] +#[test_case(45_082_061_850, 12_272_234_300, _1_10, 0; "with_exit_fees")] +#[test_case(46_403_174_924, 11_820_024_200, 0, _1_20; "with_swap_fees")] +#[test_case(41_836_235_739, 11_820_024_200, _1_10, _1_20; "with_both_fees")] +fn pool_exit_with_exact_pool_amount_exchanges_correct_values( + asset_amount_expected: BalanceOf, + pool_amount_expected: BalanceOf, + exit_fee: BalanceOf, + swap_fee: BalanceOf, +) { ExtBuilder::default().build().execute_with(|| { + let bound = _4; + 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(0), true); - assert_ok!(MarketCommons::insert_market_pool(DEFAULT_MARKET_ID, DEFAULT_POOL_ID)); - - 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(), + create_initial_pool_with_funds_for_alice(swap_fee, true); + assert_ok!(Swaps::pool_join_with_exact_asset_amount( + alice_signed(), DEFAULT_POOL_ID, - OutcomeReport::Categorical(65), + ASSET_A, + asset_amount_joined, + 0 )); - assert_ok!(Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1),)); + let pool_amount = Currencies::free_balance(Swaps::pool_shares_id(DEFAULT_POOL_ID), &ALICE); + assert_eq!(pool_amount, pool_amount_expected); // (This is just a sanity check) + assert_ok!(Swaps::pool_exit_with_exact_pool_amount( + alice_signed(), + DEFAULT_POOL_ID, + ASSET_A, + pool_amount, + bound, + )); System::assert_last_event( - Event::PoolExit(PoolAssetsEvent { - assets: vec![ASSET_A, ASSET_D], - bounds: vec![_1, _1], + Event::PoolExitWithExactPoolAmount(PoolAssetEvent { + asset: ASSET_A, + bound, cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: 0 }, - transferred: vec![_1 + 1, _1 + 1], - pool_amount: _1, + transferred: asset_amount_expected, + pool_amount, }) .into(), ); assert_all_parameters( - [_25 + 1, _24, _24, _25 + 1], + [_25 - asset_amount_joined + asset_amount_expected, _25, _25, _25], 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 + asset_amount_joined - asset_amount_expected, + DEFAULT_LIQUIDITY, + DEFAULT_LIQUIDITY, + DEFAULT_LIQUIDITY, ], DEFAULT_LIQUIDITY, - ); - }) -} - -#[test] -fn pool_exit_subsidy_unreserves_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, - ); - - // 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); - System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - _25, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - _20, - ) - .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() { - 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 - ); - }); -} - -#[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 - ); - }); -} - -#[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 - ); - }); -} - -#[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 - ); - }); -} - -#[test_case(49_999_999_665, 12_272_234_300, 0, 0; "no_fees")] -#[test_case(45_082_061_850, 12_272_234_300, _1_10, 0; "with_exit_fees")] -#[test_case(46_403_174_924, 11_820_024_200, 0, _1_20; "with_swap_fees")] -#[test_case(41_836_235_739, 11_820_024_200, _1_10, _1_20; "with_both_fees")] -fn pool_exit_with_exact_pool_amount_exchanges_correct_values( - asset_amount_expected: BalanceOf, - pool_amount_expected: BalanceOf, - exit_fee: BalanceOf, - swap_fee: BalanceOf, -) { - ExtBuilder::default().build().execute_with(|| { - let bound = _4; - 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); - assert_ok!(Swaps::pool_join_with_exact_asset_amount( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - asset_amount_joined, - 0 - )); - let pool_amount = Currencies::free_balance(Swaps::pool_shares_id(DEFAULT_POOL_ID), &ALICE); - assert_eq!(pool_amount, pool_amount_expected); // (This is just a sanity check) - - assert_ok!(Swaps::pool_exit_with_exact_pool_amount( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - pool_amount, - bound, - )); - System::assert_last_event( - Event::PoolExitWithExactPoolAmount(PoolAssetEvent { - asset: ASSET_A, - bound, - cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: 0 }, - transferred: asset_amount_expected, - pool_amount, - }) - .into(), - ); - assert_all_parameters( - [_25 - asset_amount_joined + asset_amount_expected, _25, _25, _25], - 0, - [ - DEFAULT_LIQUIDITY + asset_amount_joined - asset_amount_expected, - DEFAULT_LIQUIDITY, - DEFAULT_LIQUIDITY, - DEFAULT_LIQUIDITY, - ], - DEFAULT_LIQUIDITY, - ) - }); + ) + }); } #[test_case(49_999_999_297, 12_272_234_248, 0, 0; "no_fees")] @@ -1530,7 +912,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 +964,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!( @@ -1603,7 +985,7 @@ fn pool_exit_is_not_allowed_with_insufficient_funds() { 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 +1008,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 +1023,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 +1032,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 +1075,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 +1111,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 @@ -1894,99 +1123,13 @@ fn provided_values_len_must_equal_assets_len() { }); } -#[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), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(1337), - &Default::default() - ), - Error::::WinningAssetNotFound - ); - }); -} - #[test] fn swap_exact_amount_in_exchanges_correct_values_with_cpmm() { ExtBuilder::default().build().execute_with(|| { 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 +1176,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 +1227,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 +1246,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 +1268,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 +1286,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 +1339,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 +1388,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 +1407,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 +1429,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!( @@ -2371,66 +1448,7 @@ 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() { +fn create_pool_fails_on_too_many_assets() { ExtBuilder::default().build().execute_with(|| { let length = ::MaxAssets::get(); let assets: Vec> = @@ -2442,16 +1460,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 +1473,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,45 +1493,19 @@ 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 ); }); } -#[test] -fn create_pool_fails_if_swap_fee_is_unspecified_for_cpmm() { - ExtBuilder::default().build().execute_with(|| { - let amount = _100; - ASSETS.iter().cloned().for_each(|asset| { - let _ = Currencies::deposit(asset, &BOB, amount); - }); - 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 - ); - }); -} - #[test] fn join_pool_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); @@ -2588,17 +1549,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 +1573,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 +1595,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 +1612,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 +1633,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 +1657,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 +1684,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 +1700,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 +1715,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 +1738,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 +1752,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 +1763,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 +1782,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,7 +1801,7 @@ 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)), @@ -2885,7 +1814,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 +1841,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 +1875,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 +1895,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 +1931,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 +1952,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 +1974,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 +1996,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 +2034,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. @@ -3135,7 +2060,7 @@ fn pool_exit_fails_if_liquidity_drops_too_low() { 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 +2104,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 +2148,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 +2190,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 +2221,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 +2264,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,997 +2284,50 @@ 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)); - }); -} - -#[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)); - }); - // 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()); - }); -} - -#[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]), - )); - - // 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)); - - // 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(), - ); - }); +fn alice_signed() -> RuntimeOrigin { + RuntimeOrigin::signed(ALICE) } -#[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)); +fn create_initial_pool(swap_fee: BalanceOf, deposit: bool) { + if deposit { + ASSETS.iter().cloned().for_each(|asset| { + assert_ok!(Currencies::deposit(asset, &BOB, _100)); }); - 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()); - }); + } + 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 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, - ); - }); +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); } -#[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)); +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(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()); - }); -} + 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]); -#[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(psi, &ALICE), alice_pool_assets); + assert_eq!(Currencies::free_balance(psi, &ALICE), alice_pool_assets); assert_eq!(Currencies::free_balance(ASSET_A, &pai), pool_assets[0]); assert_eq!(Currencies::free_balance(ASSET_B, &pai), pool_assets[1]); @@ -4357,22 +2335,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)) From b96446ba85a4fcfceb782f1a1586acb4ef1d594f Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Mon, 8 Jan 2024 20:59:48 +0100 Subject: [PATCH 020/104] Merge release v0.4.3 (#1211) * Use hotfixed `exp` * Reorganize tests * Fix formatting * Bump versions to v0.4.3 * Update spec versions * Add missing version bumps * Format * Update licenses --------- Co-authored-by: Malte Kliemann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- Cargo.lock | 42 +++++++++---------- node/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- runtime/battery-station/Cargo.toml | 2 +- runtime/battery-station/src/lib.rs | 2 +- runtime/common/Cargo.toml | 2 +- runtime/zeitgeist/Cargo.toml | 2 +- runtime/zeitgeist/src/lib.rs | 2 +- zrml/authorized/Cargo.toml | 2 +- zrml/court/Cargo.toml | 2 +- zrml/global-disputes/Cargo.toml | 2 +- zrml/liquidity-mining/Cargo.toml | 2 +- zrml/market-commons/Cargo.toml | 2 +- zrml/neo-swaps/Cargo.toml | 2 +- zrml/orderbook/Cargo.toml | 2 +- zrml/parimutuel/Cargo.toml | 2 +- zrml/prediction-markets/Cargo.toml | 2 +- .../prediction-markets/runtime-api/Cargo.toml | 2 +- zrml/rikiddo/Cargo.toml | 2 +- zrml/simple-disputes/Cargo.toml | 2 +- zrml/styx/Cargo.toml | 2 +- zrml/swaps/Cargo.toml | 2 +- zrml/swaps/rpc/Cargo.toml | 2 +- zrml/swaps/runtime-api/Cargo.toml | 2 +- 24 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5b66768d..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", @@ -14283,7 +14283,7 @@ version = "0.4.3" [[package]] name = "zeitgeist-node" -version = "0.4.2" +version = "0.4.3" dependencies = [ "battery-station-runtime", "cfg-if", @@ -14375,7 +14375,7 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arbitrary", "fixed", @@ -14398,7 +14398,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "common-runtime", @@ -14522,7 +14522,7 @@ dependencies = [ [[package]] name = "zrml-authorized" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14540,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", @@ -14565,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", @@ -14586,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", @@ -14605,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", @@ -14623,7 +14623,7 @@ dependencies = [ [[package]] name = "zrml-neo-swaps" -version = "0.4.2" +version = "0.4.3" dependencies = [ "cfg-if", "env_logger 0.10.1", @@ -14668,7 +14668,7 @@ dependencies = [ [[package]] name = "zrml-orderbook" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14703,7 +14703,7 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14725,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", @@ -14777,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", @@ -14786,7 +14786,7 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.2" +version = "0.4.3" dependencies = [ "arbitrary", "cfg-if", @@ -14820,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", @@ -14841,7 +14841,7 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14858,7 +14858,7 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.2" +version = "0.4.3" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14902,7 +14902,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.2" +version = "0.4.3" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14915,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/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 63f20fcf1..4a4bca7b9 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -39,4 +39,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-primitives" -version = "0.4.2" +version = "0.4.3" 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/lib.rs b/runtime/battery-station/src/lib.rs index 95922c090..b633cb4df 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -100,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, 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/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/lib.rs b/runtime/zeitgeist/src/lib.rs index 22ba04af2..1ae68efe3 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -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, 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/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/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/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/market-commons/Cargo.toml b/zrml/market-commons/Cargo.toml index 69cdad15e..215042a49 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -33,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/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 3e1589630..2ac64b8e1 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -106,4 +106,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-neo-swaps" -version = "0.4.2" +version = "0.4.3" 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/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/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 333c5d6a1..3d1af3594 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -95,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/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/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/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 28d2cac64..80abf4639 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -67,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/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/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" From cb6a0531d8be6844472b69cbfe5093ec4c899143 Mon Sep 17 00:00:00 2001 From: Chralt Date: Thu, 11 Jan 2024 13:59:37 +0100 Subject: [PATCH 021/104] Reduce `market_status_manager` aka `on_initialize` iterations (#1160) * remove dangerous loop * limit iterations of dangerous loop * reintegrate last time frame storage item * wip * benchmark manual close and open * fix stall test * emit event and log for recovery time frame * add tests * add trailing comma * Update zrml/prediction-markets/src/benchmarks.rs Co-authored-by: Malte Kliemann * change should_be_closed condition * Apply suggestions from code review Co-authored-by: Malte Kliemann * change recursion limit line * regard period not started yet * add error tests * correct benchmarks * fix after merge and clippy * use turbofish, add test checks * remove manually open broken market * correct errors and call index * correct wrong error name * correct position of call index * correct error position * update copyrights * fix after merge * Update zrml/prediction-markets/src/benchmarks.rs Co-authored-by: Malte Kliemann * set market end for manual close --------- Co-authored-by: Malte Kliemann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zrml/prediction-markets/src/benchmarks.rs | 131 ++++++++------ zrml/prediction-markets/src/lib.rs | 84 ++++++++- zrml/prediction-markets/src/tests.rs | 204 +++++++++++++++++++++- zrml/prediction-markets/src/weights.rs | 21 +++ 4 files changed, 389 insertions(+), 51 deletions(-) diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 6afce4418..f5cb54eb8 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -109,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)) } @@ -130,7 +130,7 @@ fn create_close_and_report_market( )?; 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::(), _ => { @@ -171,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::(), _ => { @@ -205,7 +205,7 @@ where 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::(), _ => { @@ -269,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); @@ -300,12 +300,12 @@ benchmarks! { 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); @@ -337,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); @@ -353,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(), @@ -390,7 +390,7 @@ benchmarks! { OutcomeReport::Categorical(2) )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -411,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( @@ -527,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]; @@ -570,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(()) })?; @@ -582,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(), )?; @@ -610,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], @@ -620,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(); @@ -644,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 }; }: { @@ -664,7 +664,7 @@ benchmarks! { 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 { @@ -673,15 +673,15 @@ benchmarks! { 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); } @@ -692,7 +692,7 @@ benchmarks! { 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(()) })?; @@ -707,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); } @@ -721,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); } @@ -735,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, @@ -750,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); } @@ -805,7 +805,7 @@ 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, @@ -815,7 +815,7 @@ benchmarks! { 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(()) @@ -824,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::() @@ -852,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 { @@ -872,7 +872,7 @@ benchmarks! { 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); @@ -980,7 +980,7 @@ benchmarks! { 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(()) })?; @@ -1032,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 { @@ -1067,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 { @@ -1105,7 +1105,7 @@ benchmarks! { 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 { @@ -1115,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 { @@ -1150,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 @@ -1202,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 @@ -1323,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 f20e3df11..b1144dd33 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -82,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; @@ -1448,6 +1453,57 @@ mod pallet { Self::set_market_end(&market_id)?; 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()) + } } #[pallet::config] @@ -1792,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] @@ -1850,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] @@ -2787,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); diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 942820374..0b3b6cbaf 100644 --- a/zrml/prediction-markets/src/tests.rs +++ b/zrml/prediction-markets/src/tests.rs @@ -1298,7 +1298,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { )); // 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(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(); @@ -1311,6 +1311,208 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { }); } +#[test] +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(|| { + // 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_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, + )); + 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, + )); + + 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! + + System::assert_last_event( + Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.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 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 end: Moment = (5 * MILLISECS_PER_BLOCK).into(); + let category_count = 3; + 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, + )); + 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, + )); + + // 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! + 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 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(|| { + // 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_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, + )); + + // 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::Block(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // 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::::NotAllowedForBlockBasedMarkets + ); + }); +} + #[test] fn on_initialize_skips_the_genesis_block() { // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index cd0bf0b6c..2c9ae0bc8 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -87,6 +87,7 @@ pub trait WeightInfoZeitgeist { fn reject_early_close_after_authority(o: u32, n: u32) -> Weight; fn reject_early_close_after_dispute() -> Weight; fn close_trusted_market(c: u32) -> Weight; + fn manually_close_market(o: u32) -> Weight; } /// Weight functions for zrml_prediction_markets (automatically generated) @@ -865,6 +866,26 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// 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)` From a5f4b397ba09a95bb2037255dbec99ac6844e4e1 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 12 Jan 2024 18:37:21 +0100 Subject: [PATCH 022/104] Update style guide to streamline reviews (#1228) --- docs/STYLE_GUIDE.md | 90 +++++++++++++++++++++++++++++----------- docs/review_checklist.md | 2 + 2 files changed, 68 insertions(+), 24 deletions(-) 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/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 From e10b9c93eea3d59a0c19c7a0c0b192ce505e1736 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 16 Jan 2024 13:00:13 +0100 Subject: [PATCH 023/104] Reduce benchmark runs of Zeitgeist pallets (#1233) Reduce bencharm runs of Zeitgeist pallets Running benchmarks of Zeitgeist pallets on the Zeitgeist reference machine currently takes four days. --- scripts/benchmarks/configuration.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}" From b5e0d4e979222c11b290e345395b30191f1516a5 Mon Sep 17 00:00:00 2001 From: Chralt Date: Tue, 16 Jan 2024 18:20:43 +0100 Subject: [PATCH 024/104] Set inflation to more than zero for a full benchmark of handle_inflation (#1234) Update benchmarks.rs --- zrml/court/src/benchmarks.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 12cd68d55..39a810659 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -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}; @@ -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); } From ca684aceb81011ec003968ed43503d8cf4a03618 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 18 Jan 2024 16:29:46 +0100 Subject: [PATCH 025/104] Implement `force_pool_exit` and disable other zrml-swaps functions (#1235) * Abstract `pool_exit` business logic into `do_*` function * Add `force_pool_exit` and test * Install call filters for zrml-swaps --- runtime/battery-station/src/lib.rs | 11 +- runtime/zeitgeist/src/lib.rs | 5 + zrml/swaps/src/lib.rs | 85 ++++++--- zrml/swaps/src/tests.rs | 282 +++++++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 34 deletions(-) diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index b633cb4df..de55e6b7c 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -58,9 +58,9 @@ use zrml_prediction_markets::Call::{ }; 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 { @@ -164,7 +164,6 @@ 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)] @@ -182,6 +181,10 @@ impl Contains for IsCallable { } => false, _ => true, }, + RuntimeCall::Swaps(inner_call) => match inner_call { + force_pool_exit { .. } => true, + _ => false, + }, _ => true, } } diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 1ae68efe3..c57f3f4b0 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -123,6 +123,7 @@ impl Contains for IsCallable { use zrml_prediction_markets::Call::{ 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 { @@ -170,6 +171,10 @@ impl Contains for IsCallable { } } 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/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 6a644f4b8..64fafebad 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -129,36 +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::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) + Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) } /// Pool - Exit with exact pool amount @@ -512,6 +483,22 @@ mod pallet { )?; 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] @@ -747,6 +734,44 @@ mod pallet { pub(crate) type NextPoolId = StorageValue<_, PoolId, ValueQuery>; impl Pallet { + fn do_pool_exit( + who: T::AccountId, + pool_id: PoolId, + 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)?; + // 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: &AssetOf, diff --git a/zrml/swaps/src/tests.rs b/zrml/swaps/src/tests.rs index 80b8f7609..c12fbd0c1 100644 --- a/zrml/swaps/src/tests.rs +++ b/zrml/swaps/src/tests.rs @@ -704,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 @@ -837,6 +848,113 @@ fn pool_exit_decreases_correct_pool_parameters_with_exit_fee() { }) } +#[test] +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(0, true); + + assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); + + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _1, + vec!(_1, _1, _1, _1), + )); + + 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: 0 }, + transferred: vec![_1 + 1, _1 + 1, _1 + 1, _1 + 1], + pool_amount: _1, + }) + .into(), + ); + assert_all_parameters( + [_25 + 1, _25 + 1, _25 + 1, _25 + 1], + 0, + [ + DEFAULT_LIQUIDITY - 1, + DEFAULT_LIQUIDITY - 1, + DEFAULT_LIQUIDITY - 1, + DEFAULT_LIQUIDITY - 1, + ], + DEFAULT_LIQUIDITY, + ); + }) +} + +#[test] +fn force_pool_exit_emits_correct_events() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + 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::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(), + ); + }); +} + +#[test] +fn force_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(0, true); + + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _10, + vec!(_1, _1, _1, _1), + )); + + 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); + + 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")] #[test_case(45_082_061_850, 12_272_234_300, _1_10, 0; "with_exit_fees")] #[test_case(46_403_174_924, 11_820_024_200, 0, _1_20; "with_swap_fees")] @@ -981,6 +1099,39 @@ 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(|| { @@ -1120,6 +1271,16 @@ fn provided_values_len_must_equal_assets_len() { Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _5, vec![]), Error::::ProvidedValuesLenMustEqualAssetsLen ); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _5, + vec![] + ), + Error::::ProvidedValuesLenMustEqualAssetsLen + ); }); } @@ -1539,6 +1700,44 @@ fn join_pool_exit_pool_does_not_create_extra_tokens() { }); } +#[test] +fn join_pool_force_exit_pool_does_not_create_extra_tokens() { + ExtBuilder::default().build().execute_with(|| { + create_initial_pool_with_funds_for_alice(0, true); + + ASSETS.iter().cloned().for_each(|asset| { + let _ = Currencies::deposit(asset, &CHARLIE, _100); + }); + + 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::force_pool_exit( + RuntimeOrigin::signed(DAVE), + 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 create_pool_fails_on_weight_below_minimum_weight() { ExtBuilder::default().build().execute_with(|| { @@ -1807,6 +2006,16 @@ fn pool_exit_fails_if_min_assets_out_is_violated() { 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, + ); }); } @@ -2056,6 +2265,79 @@ 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(|| { From 19184169a0e8a5e48dd270c36e661ad5bfeb2647 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 22 Jan 2024 14:47:54 +0100 Subject: [PATCH 026/104] Implement and test `bmul_bdiv_*`; use in zrml-orderbook and zrml-parimutuel (#1223) * Implement and test `bmul_bdiv_*` * Use `bmul_bdiv_*` in pallets * Update copyright --- primitives/src/math/fixed.rs | 180 ++++++++++++++++++++++++++++++++--- zrml/orderbook/src/lib.rs | 9 +- zrml/parimutuel/src/lib.rs | 28 ++---- 3 files changed, 178 insertions(+), 39 deletions(-) diff --git a/primitives/src/math/fixed.rs b/primitives/src/math/fixed.rs index d41fdc0f1..eff7434d5 100644 --- a/primitives/src/math/fixed.rs +++ b/primitives/src/math/fixed.rs @@ -196,17 +196,13 @@ where bmul_bdiv_common(self, multiplier, divisor, adjustment) } - fn bmul_bdiv_floor(&self, _multiplier: Self, _divisor: Self) -> Result { - // TODO(#1217): Commented mplementation below should work, but remains untested! - // bmul_bdiv_common(self, multiplier, divisor, Zero::zero()) - Err(DispatchError::Other("not implemented")) + fn bmul_bdiv_floor(&self, multiplier: Self, divisor: Self) -> Result { + bmul_bdiv_common(self, multiplier, divisor, Zero::zero()) } - fn bmul_bdiv_ceil(&self, _multiplier: Self, _divisor: Self) -> Result { - // TODO(#1217): Commented mplementation below should work, but remains untested! - // let adjustment = ZeitgeistBase::::get()?.checked_sub_res(&1u8.into())?; - // bmul_bdiv_common(self, multiplier, divisor, adjustment) - Err(DispatchError::Other("not implemented")) + fn bmul_bdiv_ceil(&self, multiplier: Self, divisor: Self) -> Result { + let adjustment = ZeitgeistBase::::get()?.checked_sub_res(&1u8.into())?; + bmul_bdiv_common(self, multiplier, divisor, adjustment) } } @@ -647,16 +643,178 @@ mod tests { #[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)] #[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)] - fn fixed_mul_div_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) { + fn bmul_bdiv_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) { assert_eq!(lhs.bmul_bdiv(multiplier, divisor).unwrap(), expected); } #[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))] #[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))] - fn fixed_mul_div_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) { + fn bmul_bdiv_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) { assert_eq!(lhs.bmul_bdiv(multiplier, divisor), Err(expected)); } + // bmul tests + #[test_case(0, 0, _1, 0)] + #[test_case(0, _1, _1, 0)] + #[test_case(0, _2, _1, 0)] + #[test_case(0, _3, _1, 0)] + #[test_case(_1, 0, _1, 0)] + #[test_case(_1, _1, _1, _1)] + #[test_case(_1, _2, _1, _2)] + #[test_case(_1, _3, _1, _3)] + #[test_case(_2, 0, _1, 0)] + #[test_case(_2, _1, _1, _2)] + #[test_case(_2, _2, _1, _4)] + #[test_case(_2, _3, _1, _6)] + #[test_case(_3, 0, _1, 0)] + #[test_case(_3, _1, _1, _3)] + #[test_case(_3, _2, _1, _6)] + #[test_case(_3, _3, _1, _9)] + #[test_case(_4, _1_2, _1, _2)] + #[test_case(_5, _1 + _1_2, _1, _7 + _1_2)] + #[test_case(_1 + 1, _2, _1, _2 + 2)] + #[test_case(9_999_999_999, _2, _1, 19_999_999_998)] + #[test_case(9_999_999_999, _10, _1, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, _1, _1_2 - 1)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, _1, 2_499_999_999)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, _1, 3_333_333_331)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1, _1_10)] + #[test_case(10_000_000_005, _1_10, _1, _1_10)] + #[test_case(10_000_000_009, _1_10, _1, _1_10)] // + + // bdiv tests + #[test_case(0, _1, _3, 0)] + #[test_case(_1, _1, _2, _1_2)] + #[test_case(_2, _1, _2, _1)] + #[test_case(_3,_1, _2, _1 + _1_2)] + #[test_case(_3, _1, _3, _1)] + #[test_case(_3 + _1_2, _1, _1_2, _7)] + #[test_case(99_999_999_999, _1, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_2, _1, _3, _2_3)] // 0.6666... + #[test_case(99_999_999_999, _1, _10, 9_999_999_999)] + #[test_case(99_999_999_994, _1, _10, 9_999_999_999)] + #[test_case(9, _1, _10, 0)] // 0.0...09 (less than precision) + #[test_case(4, _1, _10, 0)] // 0.0...04 (less than precision) + + // Normal Cases + #[test_case(_2, _2, _2, _2)] + #[test_case(_1, _2, _3, _2_3)] // 0.6666... + #[test_case(_2, _3, _4, _1 + _1_2)] + #[test_case(_1 + 1, _2, _3, (_2 + 2) / 3)] + #[test_case(_5, _6, _7, _5 * _6 / _7)] + #[test_case(_100, _101, _20, _100 * _101 / _20)] // + + // Boundary cases + #[test_case(u128::MAX / _1, _1, _2, u128::MAX / _2)] + #[test_case(0, _1, _2, 0)] + #[test_case(_1, u128::MAX / _1, u128::MAX / _1, _1)] // + + // Special rounding cases + #[test_case(_1, _1_2, _1, _1_2)] + #[test_case(_1, _1_3, _1, _1_3)] + #[test_case(_1, _2_3, _1, _2_3)] + #[test_case(_9, _1_2, _1, _9 / 2)] + #[test_case(_9, _1_3, _1, 29_999_999_997)] + #[test_case(_9, _2_3, _1, 59_999_999_994)] // + + // Divide-first value + #[test_case(1_000_000 * _1, 1_000_000 * _1, _10, 100000000000 * _1)] + #[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)] + #[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)] + + fn bmul_bdiv_floor_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) { + assert_eq!(lhs.bmul_bdiv_floor(multiplier, divisor).unwrap(), expected); + } + + #[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))] + #[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))] + fn bmul_bdiv_floor_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) { + assert_eq!(lhs.bmul_bdiv_floor(multiplier, divisor), Err(expected)); + } + + // bmul tests + #[test_case(0, 0, _1, 0)] + #[test_case(0, _1, _1, 0)] + #[test_case(0, _2, _1, 0)] + #[test_case(0, _3, _1, 0)] + #[test_case(_1, 0, _1, 0)] + #[test_case(_1, _1, _1, _1)] + #[test_case(_1, _2, _1, _2)] + #[test_case(_1, _3, _1, _3)] + #[test_case(_2, 0, _1, 0)] + #[test_case(_2, _1, _1, _2)] + #[test_case(_2, _2, _1, _4)] + #[test_case(_2, _3, _1, _6)] + #[test_case(_3, 0, _1, 0)] + #[test_case(_3, _1, _1, _3)] + #[test_case(_3, _2, _1, _6)] + #[test_case(_3, _3, _1, _9)] + #[test_case(_4, _1_2, _1, _2)] + #[test_case(_5, _1 + _1_2, _1, _7 + _1_2)] + #[test_case(_1 + 1, _2, _1, _2 + 2)] + #[test_case(9_999_999_999, _2, _1, 19_999_999_998)] + #[test_case(9_999_999_999, _10, _1, 99_999_999_990)] + // Rounding behavior when multiplying with small numbers + #[test_case(9_999_999_999, _1_2, _1, _1_2)] // 4999999999.5 + #[test_case(9_999_999_997, _1_4, _1, 2_500_000_000)] // 2499999999.25 + #[test_case(9_999_999_996, _1_3, _1, 3_333_333_332)] // 3333333331.666... + #[test_case(10_000_000_001, _1_10, _1, _1_10 + 1)] + #[test_case(10_000_000_005, _1_10, _1, _1_10 + 1)] + #[test_case(10_000_000_009, _1_10, _1, _1_10 + 1)] // + + // bdiv tests + #[test_case(0, _1, _3, 0)] + #[test_case(_1, _1, _2, _1_2)] + #[test_case(_2, _1, _2, _1)] + #[test_case(_3,_1, _2, _1 + _1_2)] + #[test_case(_3, _1, _3, _1)] + #[test_case(_3 + _1_2, _1, _1_2, _7)] + #[test_case(99_999_999_999, _1, 1, 99_999_999_999 * _1)] + // Rounding behavior + #[test_case(_2, _1, _3, _2_3 + 1)] // 0.6666... + #[test_case(99_999_999_999, _1, _10, _1)] + #[test_case(99_999_999_991, _1, _10, _1)] + #[test_case(9, _1, _10, 1)] // 0.0...09 (less than precision) + #[test_case(4, _1, _10, 1)] // 0.0...04 (less than precision) + + // Normal Cases + #[test_case(_2, _2, _2, _2)] + #[test_case(_1, _2, _3, _2_3 + 1)] // 0.6666... + #[test_case(_2, _3, _4, _1 + _1_2)] + #[test_case(_1 + 1, _2, _3, 6_666_666_668)] // 0.6666666667333333 + #[test_case(_5, _6, _7, 42_857_142_858)] + #[test_case(_100, _101, _20, _100 * _101 / _20)] // + + // Boundary cases + #[test_case(u128::MAX / _1, _1, _2, u128::MAX / _2)] + #[test_case(0, _1, _2, 0)] + #[test_case(_1, u128::MAX / _1, u128::MAX / _1, _1)] // + + // Special rounding cases + #[test_case(_1, _1_2, _1, _1_2)] + #[test_case(_1, _1_3, _1, _1_3)] + #[test_case(_1, _2_3, _1, _2_3)] + #[test_case(_9, _1_2, _1, _9 / 2)] + #[test_case(_9, _1_3, _1, 29_999_999_997)] + #[test_case(_9, _2_3, _1, 59_999_999_994)] // + + // Divide-first value + #[test_case(1_000_000 * _1, 1_000_000 * _1, _10, 100000000000 * _1)] + #[test_case(1_234_567 * _1, 9_876_543 * _1, 123_456, 9876599000357212286158412534)] + #[test_case(1_000_000 * _1, 9_876_543 * _1, 1_000_000 * _1, 9_876_543 * _1)] + + fn bmul_bdiv_ceil_works(lhs: u128, multiplier: u128, divisor: u128, expected: u128) { + assert_eq!(lhs.bmul_bdiv_ceil(multiplier, divisor).unwrap(), expected); + } + + #[test_case(_1, u128::MAX, u128::MAX, DispatchError::Arithmetic(ArithmeticError::Overflow))] + #[test_case(_1, _2, 0, DispatchError::Arithmetic(ArithmeticError::DivisionByZero))] + fn bmul_bdiv_ceil_fails(lhs: u128, multiplier: u128, divisor: u128, expected: DispatchError) { + assert_eq!(lhs.bmul_bdiv_ceil(multiplier, divisor), Err(expected)); + } + #[test_case(0, 10, 0.0)] #[test_case(1, 10, 0.0000000001)] #[test_case(9, 10, 0.0000000009)] diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index 6a877c38f..d6ab8b7c9 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/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. @@ -39,7 +39,7 @@ use sp_runtime::traits::{Get, Zero}; use zeitgeist_primitives::{ math::{ checked_ops_res::{CheckedAddRes, CheckedSubRes}, - fixed::{FixedDiv, FixedMul}, + fixed::FixedMulDiv, }, traits::{DistributeFees, MarketCommonsPalletApi}, types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, @@ -295,10 +295,7 @@ mod pallet { // This ensures that the reserve of the maker // is always enough to repatriate successfully! // `maker_full_fill` is ensured to be never zero in `ensure_ratio_quotient_valid` - let ratio = maker_fill.bdiv_floor(maker_full_fill)?; - // returns the (partial) amount of what the taker gets from the maker's amount - // respected the partial fill from the taker of what the maker wants to get filled - ratio.bmul_floor(taker_full_fill) + maker_fill.bmul_bdiv_floor(taker_full_fill, maker_full_fill) } fn ensure_ratio_quotient_valid(order_data: &OrderOf) -> DispatchResult { diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index bdef82a0c..3bbcbd441 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -44,11 +44,10 @@ mod pallet { use orml_traits::MultiCurrency; use sp_runtime::{ traits::{AccountIdConversion, CheckedSub, Zero}, - DispatchResult, SaturatedConversion, + DispatchResult, }; use zeitgeist_primitives::{ - constants::BASE, - math::fixed::{FixedDiv, FixedMul}, + math::fixed::FixedMulDiv, traits::DistributeFees, types::{Asset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, }; @@ -260,7 +259,6 @@ mod pallet { winning_balance: BalanceOf, pot_total: BalanceOf, outcome_total: BalanceOf, - payoff_ratio_mul_base: BalanceOf, payoff: BalanceOf, ) -> DispatchResult { ensure!( @@ -275,13 +273,6 @@ mod pallet { InconsistentStateError::OutcomeIssuanceGreaterCollateral ) ); - if payoff_ratio_mul_base < BASE.saturated_into() { - log::debug!( - target: LOG_TARGET, - "The payoff ratio should be greater than or equal to BASE!" - ); - debug_assert!(false); - } if payoff < winning_balance { log::debug!( target: LOG_TARGET, @@ -406,16 +397,9 @@ mod pallet { let pot_account = Self::pot_account(market_id); let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account); - let payoff_ratio_mul_base = pot_total.bdiv_floor(outcome_total)?; - let payoff = payoff_ratio_mul_base.bmul_floor(winning_balance)?; - - Self::check_values( - winning_balance, - pot_total, - outcome_total, - payoff_ratio_mul_base, - payoff, - )?; + let payoff = pot_total.bmul_bdiv(winning_balance, outcome_total)?; + + Self::check_values(winning_balance, pot_total, outcome_total, payoff)?; let withdrawn_asset_balance = winning_balance; From c8604f9212e959b2d1667a351ca4fc775c3a6886 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 23 Jan 2024 00:36:50 +0530 Subject: [PATCH 027/104] Utilize Merigify's Merge Queue (#1243) ci(Mergify): configuration update Signed-off-by: Harald Heckmann --- .mergify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index ece3525e5..795fe6716 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -19,8 +19,8 @@ pull_request_rules: remove: - s:in-progress - s:review-needed - merge: - method: squash + queue: + merge_method: squash - name: ask to resolve conflict conditions: - conflict From bb0eab689d6dd82a5289fc9e60eefbbe5e428ea8 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 23 Jan 2024 05:06:26 +0530 Subject: [PATCH 028/104] Set in-progress when needed and rerun CI in merge queue (#1244) * Set in-progress when need and rerun CI in merge queue --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .mergify.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index 795fe6716..cc0804e24 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -34,4 +34,29 @@ pull_request_rules: - s:accepted - s:in-progress - s:review-needed - + - name: Set in-progress label after changes are pushed + conditions: + - commits[-1].date_committer>=0 days 00:01 ago + actions: + label: + add: + - s:in-progress + remove: + - s:accepted + - s:available + - s:blocked + - s:on-hold + - s:review-needed + - s:revision-needed + - name: Trigger CI after Mergify merged the base branch (fix merge queue) + conditions: + - commits[-1].author=mergify[bot] + - label=s:in-progress + - queue-position=0 + actions: + label: + remove: + - s:in-progress + add: + - s:accepted + From 0cab1fa86a67fed3f7e60229fd49f2d719e07c05 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 23 Jan 2024 16:25:22 +0530 Subject: [PATCH 029/104] Avoid mergify dequeue (#1245) * Avoid mergify deque * Set review needed only shortly after Mergify commit --- .mergify.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index cc0804e24..07c49ea76 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -36,7 +36,8 @@ pull_request_rules: - s:review-needed - name: Set in-progress label after changes are pushed conditions: - - commits[-1].date_committer>=0 days 00:01 ago + - commits[-1].author!=mergify[bot] + - commits[-1].date_committer>=0 days 00:01 ago actions: label: add: @@ -51,12 +52,19 @@ pull_request_rules: - name: Trigger CI after Mergify merged the base branch (fix merge queue) conditions: - commits[-1].author=mergify[bot] - - label=s:in-progress + - commits[-1].date_committer>=0 days 00:01 ago - queue-position=0 actions: label: - remove: - - s:in-progress add: - - s:accepted - + - s:review-needed + - name: Remove CI trigger label + conditions: + - commits[-1].author=mergify[bot] + - label=s:review-needed + - queue-position=0 + actions: + label: + remove: + - s:review-needed + From dcbb0062d074b3dc5a41a8272794e8410ea4c5a6 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 23 Jan 2024 13:13:37 +0100 Subject: [PATCH 030/104] Extend neo-swaps tests and clean up `math.rs` (#1238) * Add tests to neo-swaps that check large buy/sell amounts at high liquidity * Use new implementation of HydraDX's `exp` * Add failure tests to neo-swaps's `math.rs` * Fix formatting * Update copyright notices --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- Cargo.lock | 4 +- Cargo.toml | 2 +- zrml/neo-swaps/src/consts.rs | 3 +- zrml/neo-swaps/src/math.rs | 136 +++++++++++++++++++++++++++-------- 4 files changed, 110 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3363f08a2..56a6c80f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3606,8 +3606,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hydra-dx-math" -version = "7.4.3" -source = "git+https://github.com/galacticcouncil/HydraDX-node?tag=v18.0.0#6173a8b0661582247eed774330aa8fa6d99d524d" +version = "7.7.0" +source = "git+https://github.com/galacticcouncil/HydraDX-node?tag=v21.1.1#564553c30caa1eccfa1b6504ec8d5130201c8765" dependencies = [ "fixed", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 1b0769fc1..ccb25afea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ arrayvec = { version = "0.7.4", default-features = false } cfg-if = { version = "1.0.0" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } # Using math code directly from the HydraDX node repository as https://github.com/galacticcouncil/hydradx-math is outdated and has been archived in May 2023. -hydra-dx-math = { git = "https://github.com/galacticcouncil/HydraDX-node", package = "hydra-dx-math", tag = "v18.0.0", default-features = false } +hydra-dx-math = { git = "https://github.com/galacticcouncil/HydraDX-node", package = "hydra-dx-math", tag = "v21.1.1", default-features = false } # Hashbrown works in no_std by default and default features are used in Rikiddo hashbrown = { version = "0.12.3", default-features = true } hex-literal = { version = "0.3.4", default-features = false } diff --git a/zrml/neo-swaps/src/consts.rs b/zrml/neo-swaps/src/consts.rs index 0a12edf64..19d0af5f7 100644 --- a/zrml/neo-swaps/src/consts.rs +++ b/zrml/neo-swaps/src/consts.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -70,3 +70,4 @@ pub(crate) const _1_10: u128 = _1 / 10; pub(crate) const _2_10: u128 = _2 / 10; pub(crate) const _3_10: u128 = _3 / 10; pub(crate) const _4_10: u128 = _4 / 10; +pub(crate) const _9_10: u128 = _9 / 10; diff --git a/zrml/neo-swaps/src/math.rs b/zrml/neo-swaps/src/math.rs index 7e6a7d2db..276b20fc8 100644 --- a/zrml/neo-swaps/src/math.rs +++ b/zrml/neo-swaps/src/math.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -34,8 +34,7 @@ // // Original source: https://github.com/encointer/substrate-fixed // -// The changes applied are: 1) Used same design for definition of `exp` -// as in the source. 2) Re-used and extended tests for `exp` and other +// The changes applied are: Re-used and extended tests for `exp` and other // functions. use crate::{ @@ -277,6 +276,10 @@ mod detail { amount: FixedType, spot_prices: Vec, ) -> Option<(FixedType, Vec)> { + if amount.is_zero() { + // Ensure that if the amount is zero, we don't accidentally return meaningless results. + return None; + } let tmp_reserves = spot_prices .iter() // Drop the bool (second tuple component) as ln(p) is always negative. @@ -308,22 +311,7 @@ mod detail { } mod transcendental { - use fixed::traits::FixedUnsigned; - pub(crate) use hydra_dx_math::transcendental::{exp as inner_exp, ln}; - use sp_runtime::traits::One; - - pub(crate) fn exp(operand: S, neg: bool) -> Result - where - S: FixedUnsigned + PartialOrd + One, - D: FixedUnsigned + PartialOrd + From + One, - { - if operand == S::one() && neg { - let e_inverse = - S::from_str("0.367879441171442321595523770161460867445").map_err(|_| ())?; - return Ok(D::from(e_inverse)); - } - inner_exp(operand, neg) - } + pub(crate) use hydra_dx_math::transcendental::{exp, ln}; #[cfg(test)] mod tests { @@ -337,7 +325,7 @@ mod transcendental { #[test_case("0", false, "1")] #[test_case("0", true, "1")] - #[test_case("1", false, "2.718281828459045235360287471352662497757")] + #[test_case("1", false, "2.7182818284590452353")] #[test_case("1", true, "0.367879441171442321595523770161460867445")] #[test_case("2", false, "7.3890560989306502265")] #[test_case("2", true, "0.13533528323661269186")] @@ -411,6 +399,7 @@ mod tests { use super::*; use crate::{consts::*, mock::Runtime as MockRuntime}; use alloc::str::FromStr; + use frame_support::assert_err; use test_case::test_case; type MockBalance = BalanceOf; @@ -431,6 +420,19 @@ mod tests { #[test_case(_30, _30, _1 - 100_000, _30)] #[test_case(_1_10, _30, _1 - 100_000, _1_10)] #[test_case(_30, _1_10, _1 - 100_000, 276_478_645_689)] + #[test_case(10_000_000 * _1, _1, 144_269_504_088_896_352, 9_999_999_307)] + #[test_case( + 100_000_000 * _1, + 100_000_000 * _1, + 434_294_481_903_251_840, + 959_041_392_321_093_596 + )] + #[test_case( + 45_757_490_560_675_120, + 100_000_000 * _1, + 434_294_481_903_251_840, + 41_392_685_158_225_036 + )] fn calculate_swap_amount_out_for_buy_works( reserve: MockBalance, amount_in: MockBalance, @@ -443,6 +445,22 @@ mod tests { ); } + #[test_case(_1, _1, 0)] // Division by zero + #[test_case(_1, 1_000 * _1, _1)] // Overflow + #[test_case(u128::MAX, _1, _1)] // to_fixed error + #[test_case(_1, u128::MAX, _1)] // to_fixed error + #[test_case(_1, _1, u128::MAX)] // to_fixed error + fn calculate_swap_amount_out_for_buy_throws_math_error( + reserve: MockBalance, + amount_in: MockBalance, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_swap_amount_out_for_buy(reserve, amount_in, liquidity), + Error::::MathError + ); + } + #[test_case(_10, _10, 144_269_504_088, 41_503_749_928)] #[test_case(_1, _1, _1, 2_646_743_359)] #[test_case(_2, _2, _2, 5_293_486_719)] @@ -454,6 +472,19 @@ mod tests { #[test_case(_30, _30, _1 - 100_000, 0)] #[test_case(_1_10, _30, _1 - 100_000, 23_521_354_311)] #[test_case(_30, _1_10, _1 - 100_000, 0)] + #[test_case(10_000_000 * _1, _1, 144_269_504_088_896_352, 4_999_999_913)] + #[test_case( + 100_000_000 * _1, + 100_000_000 * _1, + 434_294_481_903_251_840, + 40_958_607_678_906_404 + )] + #[test_case( + 45_757_490_560_675_120, + 100_000_000 * _1, + 434_294_481_903_251_840, + 721_246_399_047_171_074 + )] fn calculate_swap_amount_out_for_sell_works( reserve: MockBalance, amount_in: MockBalance, @@ -466,9 +497,21 @@ mod tests { ); } - #[test] - fn calculate_swap_amount_out_for_sell_fails_if_reserve_is_zero() { - assert!(MockMath::calculate_swap_amount_out_for_sell(0, _1, _1).is_err()); + #[test_case(0, _1, _1)] + #[test_case(_1, _1, 0)] // Division by zero + #[test_case(1000 * _1, _1, _1)] // Overflow + #[test_case(u128::MAX, _1, _1)] // to_fixed error + #[test_case(_1, u128::MAX, _1)] // to_fixed error + #[test_case(_1, _1, u128::MAX)] // to_fixed error + fn calculate_swap_amount_out_for_sell_throws_math_error( + reserve: MockBalance, + amount_in: MockBalance, + liquidity: MockBalance, + ) { + assert_err!( + MockMath::calculate_swap_amount_out_for_sell(reserve, amount_in, liquidity), + Error::::MathError + ); } #[test_case(_10, 144_269_504_088, _1_2)] @@ -482,6 +525,17 @@ mod tests { assert_eq!(MockMath::calculate_spot_price(reserve, liquidity).unwrap(), expected); } + #[test_case(_1, 0)] // Division by zero + #[test_case(1_000 * _1, _1)] // Overflow + #[test_case(u128::MAX, _1)] // to_fixed error + #[test_case(_1, u128::MAX)] // to_fixed error + fn calculate_spot_price_throws_math_error(reserve: MockBalance, liquidity: MockBalance) { + assert_err!( + MockMath::calculate_spot_price(reserve, liquidity), + Error::::MathError + ); + } + #[test_case(_10, vec![_1_2, _1_2], vec![_10, _10], 144_269_504_089)] #[test_case(_20, vec![_3_4, _1_4], vec![_10 - 58_496_250_072, _20], 144_269_504_089)] #[test_case( @@ -496,6 +550,12 @@ mod tests { vec![_100, _100, _100, 30_673_687_183], 188_739_165_818 )] + #[test_case( + 100_000_000 * _1, + vec![_1_10, _9_10], + vec![100_000_000 * _1, 45_757_490_560_675_125], + 434_294_481_903_251_828 + )] fn calculate_reserves_from_spot_prices_works( amount: MockBalance, spot_prices: Vec, @@ -508,19 +568,33 @@ mod tests { assert_eq!(reserves, expected_reserves); } - #[test_case(_10, _10, 144_269_504_088, _1 + _1_2)] - #[test_case(_10, _1, 144_269_504_088, 5_717_734_625)] - #[test_case(_1, _1, _1, 20_861_612_696)] - #[test_case(_444, _1, _11, 951_694_399; "underflow_to_zero")] + #[test_case(0, vec![_1_10, _2_10, _3_10, _4_10])] + #[test_case(u128::MAX, vec![_1_10, _2_10, _3_10, _4_10])] // to_fixed error + #[test_case(_1, vec![u128::MAX, 0, 0])] // to_fixed error + #[test_case(_1, vec![_1, 0])] // ln out of range + fn calculate_reserves_from_spot_prices_throws_math_error( + amount: MockBalance, + spot_prices: Vec, + ) { + assert_err!( + MockMath::calculate_reserves_from_spot_prices(amount, spot_prices), + Error::::MathError + ); + } + + #[test_case(_1, _1, 0)] // Division by zero + #[test_case(_1, 1_000 * _1, _1)] // Overflow + #[test_case(u128::MAX, _1, _1)] // to_fixed error + #[test_case(_1, u128::MAX, _1)] // to_fixed error + #[test_case(_1, _1, u128::MAX)] // to_fixed error fn calculate_buy_ln_argument_fixed_works( reserve: MockBalance, amount_in: MockBalance, liquidity: MockBalance, - expected: MockBalance, ) { - assert_eq!( - MockMath::calculate_buy_ln_argument(reserve, amount_in, liquidity).unwrap(), - expected + assert_err!( + MockMath::calculate_buy_ln_argument(reserve, amount_in, liquidity), + Error::::MathError ); } From e1ff1e15ee3a5f6dfd1a70e92ebe723c26701d7e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 23 Jan 2024 14:29:13 +0100 Subject: [PATCH 031/104] Remove migrations and dead code (#1241) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- primitives/src/market.rs | 13 - runtime/common/src/lib.rs | 13 +- zrml/market-commons/src/migrations.rs | 475 ---------------------- zrml/neo-swaps/src/migration.rs | 223 ---------- zrml/neo-swaps/src/types/mod.rs | 4 +- zrml/neo-swaps/src/types/solo_lp.rs | 93 ----- zrml/orderbook/src/migrations.rs | 317 +-------------- zrml/prediction-markets/src/lib.rs | 32 -- zrml/prediction-markets/src/migrations.rs | 148 ------- zrml/swaps/src/lib.rs | 15 +- zrml/swaps/src/migrations.rs | 385 ------------------ 11 files changed, 5 insertions(+), 1713 deletions(-) delete mode 100644 zrml/neo-swaps/src/types/solo_lp.rs diff --git a/primitives/src/market.rs b/primitives/src/market.rs index 02f3972ad..cae1eb2bc 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -343,19 +343,6 @@ pub enum ResolutionMechanism { Noop, } -/// Contains a market id and the market period. -/// -/// * `BN`: Block Number -/// * `MO`: Moment (Time moment) -/// * `MI`: Market Id -#[derive(TypeInfo, Clone, Eq, PartialEq, Decode, Encode, MaxEncodedLen, RuntimeDebug)] -pub struct SubsidyUntil { - /// Market id of associated market. - pub market_id: MI, - /// Market start and end. - pub period: MarketPeriod, -} - #[cfg(test)] mod tests { use crate::{market::*, types::Asset}; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 430ea89cc..12e5a57cb 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -55,23 +55,12 @@ 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 = ( - MigratePools, - DrainDeprecatedStorage, - MigrateScoringRuleAndMarketStatus, - TranslateOrderStructure, - MigrateToLiquidityTree, - ); + type Migrations = (); pub type Executive = frame_executive::Executive< Runtime, diff --git a/zrml/market-commons/src/migrations.rs b/zrml/market-commons/src/migrations.rs index 95890edb8..7bcc6e2e5 100644 --- a/zrml/market-commons/src/migrations.rs +++ b/zrml/market-commons/src/migrations.rs @@ -15,478 +15,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -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, -}; - -#[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 - } - - #[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()) - } - - #[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(()) - } -} - -#[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)] - 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/neo-swaps/src/migration.rs b/zrml/neo-swaps/src/migration.rs index ac04b7ce7..fb9ed7fb7 100644 --- a/zrml/neo-swaps/src/migration.rs +++ b/zrml/neo-swaps/src/migration.rs @@ -15,229 +15,6 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{ - liquidity_tree::types::LiquidityTree, - types::{Pool, SoloLp}, - Config, Pallet, -}; -use frame_support::{ - dispatch::Weight, - log, - pallet_prelude::PhantomData, - traits::{Get, OnRuntimeUpgrade, StorageVersion}, -}; -use sp_runtime::traits::Saturating; - -cfg_if::cfg_if! { - if #[cfg(feature = "try-runtime")] { - use crate::{MarketIdOf, Pools}; - use alloc::{collections::BTreeMap, format, vec::Vec}; - use frame_support::{migration::storage_key_iter, pallet_prelude::Twox64Concat}; - use parity_scale_codec::{Decode, Encode}; - use sp_runtime::traits::Zero; - } -} - -cfg_if::cfg_if! { - if #[cfg(any(feature = "try-runtime", test))] { - const NEO_SWAPS: &[u8] = b"NeoSwaps"; - const POOLS: &[u8] = b"Pools"; - } -} - -const NEO_SWAPS_REQUIRED_STORAGE_VERSION: u16 = 0; -const NEO_SWAPS_NEXT_STORAGE_VERSION: u16 = NEO_SWAPS_REQUIRED_STORAGE_VERSION + 1; - -type OldPoolOf = Pool>; - -pub struct MigrateToLiquidityTree(PhantomData); - -impl OnRuntimeUpgrade for MigrateToLiquidityTree { - fn on_runtime_upgrade() -> Weight { - let mut total_weight = T::DbWeight::get().reads(1); - let neo_swaps_version = StorageVersion::get::>(); - if neo_swaps_version != NEO_SWAPS_REQUIRED_STORAGE_VERSION { - log::info!( - "MigrateToLiquidityTree: neo-swaps version is {:?}, but {:?} is required", - neo_swaps_version, - NEO_SWAPS_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("MigrateToLiquidityTree: Starting..."); - let mut translated = 0u64; - crate::Pools::::translate::, _>(|_, pool| { - let solo = pool.liquidity_shares_manager; - // This should never fail; if it does, then we just delete the entry. - let mut liquidity_tree = - LiquidityTree::new(solo.owner.clone(), solo.total_shares).ok()?; - liquidity_tree.nodes.get_mut(0)?.fees = solo.fees; // Can't fail. - translated.saturating_inc(); - Some(Pool { - account_id: pool.account_id, - reserves: pool.reserves, - collateral: pool.collateral, - liquidity_parameter: pool.liquidity_parameter, - liquidity_shares_manager: liquidity_tree, - swap_fee: pool.swap_fee, - }) - }); - log::info!("MigrateToLiquidityTree: Upgraded {} pools.", translated); - total_weight = - total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); - StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("MigrateToLiquidityTree: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - let old_pools = - storage_key_iter::, OldPoolOf, Twox64Concat>(NEO_SWAPS, POOLS) - .collect::>(); - Ok(old_pools.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - let old_pools: BTreeMap, OldPoolOf> = - Decode::decode(&mut &previous_state[..]) - .map_err(|_| "Failed to decode state: Invalid state")?; - let new_pool_count = Pools::::iter().count(); - assert_eq!(old_pools.len(), new_pool_count); - for (market_id, new_pool) in Pools::::iter() { - let old_pool = - old_pools.get(&market_id).expect(&format!("Pool {:?} not found", market_id)[..]); - assert_eq!(new_pool.account_id, old_pool.account_id); - assert_eq!(new_pool.reserves, old_pool.reserves); - assert_eq!(new_pool.collateral, old_pool.collateral); - assert_eq!(new_pool.liquidity_parameter, old_pool.liquidity_parameter); - assert_eq!(new_pool.swap_fee, old_pool.swap_fee); - let tree = new_pool.liquidity_shares_manager; - let solo = &old_pool.liquidity_shares_manager; - assert_eq!(tree.nodes.len(), 1); - assert_eq!(tree.abandoned_nodes.len(), 0); - assert_eq!(tree.account_to_index.len(), 1); - let root = tree.nodes[0].clone(); - let account = root.account.clone(); - assert_eq!(root.account, Some(solo.owner.clone())); - assert_eq!(root.stake, solo.total_shares); - assert_eq!(root.fees, solo.fees); - assert_eq!(root.descendant_stake, Zero::zero()); - assert_eq!(root.lazy_fees, Zero::zero()); - let address = account.unwrap(); - assert_eq!(tree.account_to_index.get(&address), Some(&0)); - } - log::info!("MigrateToLiquidityTree: Post-upgrade pool count is {}!", new_pool_count); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{ExtBuilder, Runtime}, - MarketIdOf, PoolOf, Pools, - }; - use alloc::collections::BTreeMap; - use frame_support::{ - dispatch::fmt::Debug, migration::put_storage_value, storage_root, StateVersion, - StorageHasher, Twox64Concat, - }; - use parity_scale_codec::Encode; - use zeitgeist_primitives::types::Asset; - - #[test] - fn on_runtime_upgrade_increments_the_storage_version() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - MigrateToLiquidityTree::::on_runtime_upgrade(); - assert_eq!(StorageVersion::get::>(), NEO_SWAPS_NEXT_STORAGE_VERSION); - }); - } - - #[test] - fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - StorageVersion::new(NEO_SWAPS_NEXT_STORAGE_VERSION).put::>(); - let (_, new_pools) = construct_old_new_tuple(); - populate_test_data::, PoolOf>( - NEO_SWAPS, POOLS, new_pools, - ); - let tmp = storage_root(StateVersion::V1); - MigrateToLiquidityTree::::on_runtime_upgrade(); - assert_eq!(tmp, storage_root(StateVersion::V1)); - }); - } - - #[test] - fn on_runtime_upgrade_correctly_updates_markets() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let (old_pools, new_pools) = construct_old_new_tuple(); - populate_test_data::, OldPoolOf>( - NEO_SWAPS, POOLS, old_pools, - ); - MigrateToLiquidityTree::::on_runtime_upgrade(); - let actual = Pools::get(0u128).unwrap(); - assert_eq!(actual, new_pools[0]); - }); - } - - fn set_up_version() { - StorageVersion::new(NEO_SWAPS_REQUIRED_STORAGE_VERSION).put::>(); - } - - fn construct_old_new_tuple() -> (Vec>, Vec>) { - let account_id = 1; - let mut reserves = BTreeMap::new(); - reserves.insert(Asset::CategoricalOutcome(2, 3), 4); - let collateral = Asset::Ztg; - let liquidity_parameter = 5; - let swap_fee = 6; - let total_shares = 7; - let fees = 8; - - let solo = SoloLp { owner: account_id, total_shares, fees }; - let mut liquidity_tree = LiquidityTree::new(account_id, total_shares).unwrap(); - liquidity_tree.nodes.get_mut(0).unwrap().fees = fees; - - let old_pool = OldPoolOf { - account_id, - reserves: reserves.clone(), - collateral, - liquidity_parameter, - liquidity_shares_manager: solo, - swap_fee, - }; - let new_pool = Pool { - account_id, - reserves, - collateral, - liquidity_parameter, - liquidity_shares_manager: liquidity_tree, - swap_fee, - }; - (vec![old_pool], vec![new_pool]) - } - - #[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 = utility::key_to_hash::(K::try_from(key).unwrap()); - put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); - } - } -} - mod utility { use alloc::vec::Vec; use frame_support::StorageHasher; diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 3968b4718..30734e23e 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -17,8 +17,6 @@ mod fee_distribution; mod pool; -mod solo_lp; pub(crate) use fee_distribution::*; pub(crate) use pool::*; -pub(crate) use solo_lp::*; diff --git a/zrml/neo-swaps/src/types/solo_lp.rs b/zrml/neo-swaps/src/types/solo_lp.rs deleted file mode 100644 index 503a72bbe..000000000 --- a/zrml/neo-swaps/src/types/solo_lp.rs +++ /dev/null @@ -1,93 +0,0 @@ -// 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 . - -// 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}; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Zero}, - DispatchError, DispatchResult, RuntimeDebug, -}; - -#[derive(TypeInfo, MaxEncodedLen, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] -#[scale_info(skip_type_params(T))] -pub struct SoloLp { - pub owner: T::AccountId, - pub total_shares: BalanceOf, - pub fees: BalanceOf, -} - -#[allow(dead_code)] -impl SoloLp { - pub(crate) fn new(owner: T::AccountId, total_shares: BalanceOf) -> SoloLp { - SoloLp { owner, total_shares, fees: Zero::zero() } - } -} - -impl LiquiditySharesManager for SoloLp -where - T::AccountId: PartialEq, - BalanceOf: AtLeast32BitUnsigned + Copy + Zero, -{ - type JoinBenchmarkInfo = (); - - fn join(&mut self, who: &T::AccountId, shares: BalanceOf) -> Result<(), DispatchError> { - ensure!(*who == self.owner, Error::::NotAllowed); - self.total_shares = self.total_shares.checked_add(&shares).ok_or(Error::::MathError)?; - Ok(()) - } - - fn exit(&mut self, who: &T::AccountId, shares: BalanceOf) -> DispatchResult { - ensure!(*who == self.owner, Error::::NotAllowed); - ensure!(shares <= self.total_shares, Error::::InsufficientPoolShares); - self.total_shares = self.total_shares.checked_sub(&shares).ok_or(Error::::MathError)?; - Ok(()) - } - - fn split( - &mut self, - _sender: &T::AccountId, - _receiver: &T::AccountId, - _amount: BalanceOf, - ) -> DispatchResult { - Err(Error::::NotImplemented.into()) - } - - fn deposit_fees(&mut self, amount: BalanceOf) -> DispatchResult { - self.fees = self.fees.checked_add(&amount).ok_or(Error::::MathError)?; - Ok(()) - } - - fn withdraw_fees(&mut self, who: &T::AccountId) -> Result, DispatchError> { - ensure!(*who == self.owner, Error::::NotAllowed); - let result = self.fees; - self.fees = Zero::zero(); - Ok(result) - } - - fn shares_of(&self, who: &T::AccountId) -> Result, DispatchError> { - ensure!(*who == self.owner, Error::::NotAllowed); - Ok(self.total_shares) - } - - fn total_shares(&self) -> Result, DispatchError> { - Ok(self.total_shares) - } -} diff --git a/zrml/orderbook/src/migrations.rs b/zrml/orderbook/src/migrations.rs index aa7be090a..2e9ba478f 100644 --- a/zrml/orderbook/src/migrations.rs +++ b/zrml/orderbook/src/migrations.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -14,318 +14,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -use crate::{ - types::{Order, OrderId}, - AccountIdOf, BalanceOf, Config, MarketIdOf, Pallet as OrderbookPallet, -}; -#[cfg(feature = "try-runtime")] -use alloc::collections::BTreeMap; -#[cfg(feature = "try-runtime")] -use alloc::format; -#[cfg(feature = "try-runtime")] -use alloc::vec::Vec; -#[cfg(feature = "try-runtime")] -use frame_support::migration::storage_key_iter; -use frame_support::{ - dispatch::Weight, - log, - pallet_prelude::PhantomData, - traits::{Get, OnRuntimeUpgrade, StorageVersion}, - RuntimeDebug, -}; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::traits::Saturating; -use zeitgeist_primitives::types::Asset; - -#[cfg(any(feature = "try-runtime", test))] -const ORDER_BOOK: &[u8] = b"Orderbook"; -#[cfg(any(feature = "try-runtime", test))] -const ORDERS: &[u8] = b"Orders"; - -const ORDER_BOOK_REQUIRED_STORAGE_VERSION: u16 = 0; -const ORDER_BOOK_NEXT_STORAGE_VERSION: u16 = 1; - -#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub enum OldOrderSide { - Bid, - Ask, -} - -#[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct OldOrder { - pub market_id: MarketId, - pub side: OldOrderSide, - pub maker: AccountId, - pub outcome_asset: Asset, - pub base_asset: Asset, - pub outcome_asset_amount: Balance, - pub base_asset_amount: Balance, -} - -type OldOrderOf = OldOrder, BalanceOf, MarketIdOf>; - -#[frame_support::storage_alias] -pub(crate) type Orders = - StorageMap, frame_support::Twox64Concat, OrderId, OldOrderOf>; - -pub struct TranslateOrderStructure(PhantomData); - -impl OnRuntimeUpgrade for TranslateOrderStructure { - fn on_runtime_upgrade() -> Weight { - let mut total_weight = T::DbWeight::get().reads(1); - let order_book_pallet_version = StorageVersion::get::>(); - if order_book_pallet_version != ORDER_BOOK_REQUIRED_STORAGE_VERSION { - log::info!( - "TranslateOrderStructure: order book pallet version is {:?}, but {:?} is required", - order_book_pallet_version, - ORDER_BOOK_REQUIRED_STORAGE_VERSION, - ); - return total_weight; - } - log::info!("TranslateOrderStructure: Starting..."); - - let mut translated = 0u64; - crate::Orders::::translate::, _>(|_order_id, old_order| { - translated.saturating_inc(); - - let (maker_asset, maker_amount, taker_asset, taker_amount) = match old_order.side { - // the maker reserved the base asset for bids - OldOrderSide::Bid => ( - old_order.base_asset, - old_order.base_asset_amount, - old_order.outcome_asset, - old_order.outcome_asset_amount, - ), - // the maker reserved the outcome asset for asks - OldOrderSide::Ask => ( - old_order.outcome_asset, - old_order.outcome_asset_amount, - old_order.base_asset, - old_order.base_asset_amount, - ), - }; - - let new_order = Order { - market_id: old_order.market_id, - maker: old_order.maker, - maker_asset, - maker_amount, - taker_asset, - taker_amount, - }; - - Some(new_order) - }); - log::info!("TranslateOrderStructure: Upgraded {} orders.", translated); - total_weight = - total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); - - StorageVersion::new(ORDER_BOOK_NEXT_STORAGE_VERSION).put::>(); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("TranslateOrderStructure: Done!"); - total_weight - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - use frame_support::pallet_prelude::Twox64Concat; - - let old_orders = - storage_key_iter::, Twox64Concat>(ORDER_BOOK, ORDERS) - .collect::>(); - - let orders = Orders::::iter_keys().count() as u32; - let decodable_orders = Orders::::iter_values().count() as u32; - if orders == decodable_orders { - log::info!("All orders could successfully be decoded, order_count: {}.", orders); - } else { - log::error!( - "Can only decode {} of {} orders - others will be dropped", - decodable_orders, - orders - ); - } - - Ok(old_orders.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - use orml_traits::NamedMultiReservableCurrency; - use zeitgeist_primitives::traits::MarketCommonsPalletApi; - - let old_orders: BTreeMap> = Decode::decode(&mut &previous_state[..]) - .expect("Failed to decode state: Invalid state"); - let mut new_order_count = 0usize; - for (order_id, new_order) in crate::Orders::::iter() { - let old_order = - old_orders.get(&order_id).expect(&format!("Order {:?} not found", order_id)[..]); - // assert old fields - assert_eq!(old_order.market_id, new_order.market_id); - assert_eq!(old_order.maker, new_order.maker); - // assert new fields - let reserved = T::AssetManager::reserved_balance_named( - &OrderbookPallet::::reserve_id(), - new_order.maker_asset, - &new_order.maker, - ); - // one reserve_id is for all orders for this maker_asset - assert!(reserved >= new_order.maker_amount); - - if let Ok(market) = T::MarketCommons::market(&new_order.market_id) { - let base_asset = market.base_asset; - if new_order.maker_asset == base_asset { - assert_eq!(new_order.maker_asset, old_order.base_asset); - assert_eq!(new_order.maker_amount, old_order.base_asset_amount); - assert_eq!(new_order.taker_amount, old_order.outcome_asset_amount); - assert_eq!(new_order.taker_asset, old_order.outcome_asset); - } else { - assert_eq!(new_order.taker_asset, base_asset); - assert_eq!(new_order.taker_asset, old_order.base_asset); - assert_eq!(new_order.taker_amount, old_order.base_asset_amount); - assert_eq!(new_order.maker_amount, old_order.outcome_asset_amount); - assert_eq!(new_order.maker_asset, old_order.outcome_asset); - } - } else { - log::error!( - "The market should be present for the order market id {:?}!", - new_order.market_id - ); - } - - new_order_count.saturating_inc(); - } - assert_eq!(old_orders.len(), new_order_count); - log::info!("TranslateOrderStructure: Order Counter post-upgrade is {}!", new_order_count); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::{ExtBuilder, Runtime}, - OrderOf, - }; - use alloc::vec::Vec; - use frame_support::{ - dispatch::fmt::Debug, migration::put_storage_value, storage_root, StorageHasher, - Twox64Concat, - }; - use sp_runtime::StateVersion; - use zeitgeist_primitives::types::ScalarPosition; - - #[test] - fn on_runtime_upgrade_increments_the_storage_version() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - TranslateOrderStructure::::on_runtime_upgrade(); - assert_eq!( - StorageVersion::get::>(), - ORDER_BOOK_NEXT_STORAGE_VERSION - ); - }); - } - - #[test] - fn on_runtime_upgrade_works_as_expected() { - ExtBuilder::default().build().execute_with(|| { - set_up_version(); - let (old_orders, new_orders) = construct_old_new_tuple(); - populate_test_data::>( - ORDER_BOOK, - ORDERS, - old_orders.clone(), - ); - TranslateOrderStructure::::on_runtime_upgrade(); - - let actual = crate::Orders::::get(0).unwrap(); - assert_eq!(actual, new_orders[0]); - - let actual = crate::Orders::::get(1).unwrap(); - assert_eq!(actual, new_orders[1]); - }); - } - - #[test] - fn on_runtime_upgrade_is_noop_if_versions_are_not_correct() { - ExtBuilder::default().build().execute_with(|| { - // ensure we migrated already - StorageVersion::new(ORDER_BOOK_NEXT_STORAGE_VERSION).put::>(); - - // save current storage root - let tmp = storage_root(StateVersion::V1); - TranslateOrderStructure::::on_runtime_upgrade(); - // ensure we did not change any storage with the migration - assert_eq!(tmp, storage_root(StateVersion::V1)); - }); - } - - fn set_up_version() { - StorageVersion::new(ORDER_BOOK_REQUIRED_STORAGE_VERSION).put::>(); - } - - fn construct_old_new_tuple() -> (Vec>, Vec>) { - let market_id_0 = 0; - let outcome_asset_amount_0 = 42000; - let base_asset_amount_0 = 69000; - let old_order_0 = OldOrder { - market_id: market_id_0, - side: OldOrderSide::Bid, - maker: 1, - outcome_asset: Asset::CategoricalOutcome(market_id_0, 0u16), - base_asset: Asset::Ztg, - outcome_asset_amount: outcome_asset_amount_0, - base_asset_amount: base_asset_amount_0, - }; - let new_order_0 = Order { - market_id: market_id_0, - maker: 1, - // the maker reserved the base asset for order side bid - maker_asset: Asset::Ztg, - maker_amount: base_asset_amount_0, - taker_asset: Asset::CategoricalOutcome(market_id_0, 0u16), - taker_amount: outcome_asset_amount_0, - }; - - let market_id_1 = 1; - let outcome_asset_amount_1 = 42000; - let base_asset_amount_1 = 69000; - let old_order_1 = OldOrder { - market_id: market_id_1, - side: OldOrderSide::Ask, - maker: 1, - outcome_asset: Asset::ScalarOutcome(market_id_1, ScalarPosition::Long), - base_asset: Asset::Ztg, - outcome_asset_amount: outcome_asset_amount_1, - base_asset_amount: base_asset_amount_1, - }; - let new_order_1 = Order { - market_id: market_id_1, - maker: 1, - // the maker reserved the outcome asset for order side ask - maker_asset: Asset::ScalarOutcome(market_id_1, ScalarPosition::Long), - maker_amount: outcome_asset_amount_1, - taker_asset: Asset::Ztg, - taker_amount: base_asset_amount_1, - }; - (vec![old_order_0, old_order_1], vec![new_order_0, new_order_1]) - } - - #[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/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index b1144dd33..1334925db 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -51,7 +51,6 @@ mod pallet { }; 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}; @@ -2011,26 +2010,6 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - // TODO(#1212): Remove in v0.5.1. - #[pallet::storage] - pub type MarketIdsPerOpenBlock = StorageMap< - _, - Blake2_128Concat, - T::BlockNumber, - BoundedVec, CacheSize>, - ValueQuery, - >; - - // TODO(#1212): Remove in v0.5.1. - #[pallet::storage] - pub type MarketIdsPerOpenTimeFrame = StorageMap< - _, - Blake2_128Concat, - TimeFrame, - BoundedVec, CacheSize>, - ValueQuery, - >; - /// A mapping of market identifiers to the block their market ends on. #[pallet::storage] pub type MarketIdsPerCloseBlock = StorageMap< @@ -2082,17 +2061,6 @@ mod pallet { 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). - #[pallet::storage] - pub type MarketsCollectingSubsidy = StorageValue< - _, - BoundedVec, MarketIdOf>, ConstU32<16>>, - ValueQuery, - >; - impl Pallet { impl_unreserve_bond!(unreserve_creation_bond, creation); impl_unreserve_bond!(unreserve_oracle_bond, oracle); diff --git a/zrml/prediction-markets/src/migrations.rs b/zrml/prediction-markets/src/migrations.rs index ce968a678..2893d660c 100644 --- a/zrml/prediction-markets/src/migrations.rs +++ b/zrml/prediction-markets/src/migrations.rs @@ -16,154 +16,6 @@ // 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 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) - where - H: StorageHasher, - K: TryFrom + Encode, - V: Encode + Clone, - >::Error: Debug, - { - for (key, value) in data.iter().enumerate() { - let storage_hash = utility::key_to_hash::(K::try_from(key).unwrap()); - put_storage_value::(pallet, prefix, &storage_hash, (*value).clone()); - } - } -} - mod utility { use alloc::vec::Vec; use frame_support::StorageHasher; diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 64fafebad..4774d2ce6 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -60,9 +60,9 @@ mod pallet { use frame_support::{ dispatch::{DispatchResultWithPostInfo, Weight}, ensure, - pallet_prelude::{OptionQuery, StorageDoubleMap, StorageMap, StorageValue, ValueQuery}, + pallet_prelude::{OptionQuery, StorageMap, StorageValue, ValueQuery}, traits::{Get, IsType, StorageVersion}, - transactional, Blake2_128Concat, PalletError, PalletId, Parameter, Twox64Concat, + transactional, Blake2_128Concat, PalletError, PalletId, Parameter, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use orml_traits::MultiCurrency; @@ -718,17 +718,6 @@ mod pallet { 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 = - StorageDoubleMap<_, Twox64Concat, PoolId, Twox64Concat, T::AccountId, BalanceOf>; - #[pallet::storage] #[pallet::getter(fn next_pool_id)] pub(crate) type NextPoolId = StorageValue<_, PoolId, ValueQuery>; diff --git a/zrml/swaps/src/migrations.rs b/zrml/swaps/src/migrations.rs index 88b9fd515..8853c4110 100644 --- a/zrml/swaps/src/migrations.rs +++ b/zrml/swaps/src/migrations.rs @@ -21,388 +21,3 @@ // (, 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()); - } - } -} From 761cc598a6eb168a3377831d0addc69f36069f65 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 23 Jan 2024 15:46:20 +0100 Subject: [PATCH 032/104] Reorganize prediction-market tests (#1242) * Rename `tests.rs` to `old_tests.rs` to avoid naming conflicts * Move `buy_complete_set` tests to new tests * Clean up `buy_complete_set` tests * Extract `sell_complete_set_tests` * Clean up `sell_complete_set` tests * Extract `admin_move_market_to_closed` tests * Extract `create_market` tests * Extract `admin_move_market_to_resolved` tests * Remove superfluous test * Extract more `create_market` tests * Extract `approve_market` tests * Extract `edit_market` tests * Extract `edit_market` tests * Add `on_market_close` tests * Extract `manually_close_market` tests * Extract `on_initialize` tests * Extract `report` tests * Extract `dispute` tests * Extract `schedule_early_close` tests * Extract `dispute_early_close` tests * Extract `reject_early_close` tests * Extract more `dispute` tests * Extract `close_trusted_market` tests * Extract `start_global_dispute` tests * Extract `redeem_shares` tests and add missing tests * Extract `on_resolution` and additional `redeem_shares` tests * Move integration tests into new test module * Automatically set block to `1` at the start of test * Replace `crate::Config` with `Config` * Access constant through `Config` * Add TODOs for missing execution paths * Fix formatting --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zrml/prediction-markets/src/lib.rs | 51 +- zrml/prediction-markets/src/mock.rs | 5 +- zrml/prediction-markets/src/tests.rs | 5848 ----------------- .../src/tests/admin_move_market_to_closed.rs | 166 + .../tests/admin_move_market_to_resolved.rs | 122 + .../src/tests/approve_market.rs | 115 + .../src/tests/buy_complete_set.rs | 147 + .../src/tests/close_trusted_market.rs | 166 + .../src/tests/create_market.rs | 620 ++ .../tests/create_market_and_deploy_pool.rs | 96 + zrml/prediction-markets/src/tests/dispute.rs | 311 + .../src/tests/dispute_early_close.rs | 421 ++ .../src/tests/edit_market.rs | 183 + .../src/tests/integration.rs | 557 ++ .../src/tests/manually_close_market.rs | 172 + zrml/prediction-markets/src/tests/mod.rs | 188 + .../src/tests/on_initialize.rs | 61 + .../src/tests/on_market_close.rs | 301 + .../src/tests/on_resolution.rs | 1101 ++++ .../src/tests/redeem_shares.rs | 180 + .../src/tests/reject_early_close.rs | 209 + .../src/tests/reject_market.rs | 244 + zrml/prediction-markets/src/tests/report.rs | 419 ++ .../src/tests/request_edit.rs | 111 + .../src/tests/schedule_early_close.rs | 339 + .../src/tests/sell_complete_set.rs | 142 + .../src/tests/start_global_dispute.rs | 71 + 27 files changed, 6468 insertions(+), 5878 deletions(-) delete mode 100644 zrml/prediction-markets/src/tests.rs create mode 100644 zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs create mode 100644 zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs create mode 100644 zrml/prediction-markets/src/tests/approve_market.rs create mode 100644 zrml/prediction-markets/src/tests/buy_complete_set.rs create mode 100644 zrml/prediction-markets/src/tests/close_trusted_market.rs create mode 100644 zrml/prediction-markets/src/tests/create_market.rs create mode 100644 zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs create mode 100644 zrml/prediction-markets/src/tests/dispute.rs create mode 100644 zrml/prediction-markets/src/tests/dispute_early_close.rs create mode 100644 zrml/prediction-markets/src/tests/edit_market.rs create mode 100644 zrml/prediction-markets/src/tests/integration.rs create mode 100644 zrml/prediction-markets/src/tests/manually_close_market.rs create mode 100644 zrml/prediction-markets/src/tests/mod.rs create mode 100644 zrml/prediction-markets/src/tests/on_initialize.rs create mode 100644 zrml/prediction-markets/src/tests/on_market_close.rs create mode 100644 zrml/prediction-markets/src/tests/on_resolution.rs create mode 100644 zrml/prediction-markets/src/tests/redeem_shares.rs create mode 100644 zrml/prediction-markets/src/tests/reject_early_close.rs create mode 100644 zrml/prediction-markets/src/tests/reject_market.rs create mode 100644 zrml/prediction-markets/src/tests/report.rs create mode 100644 zrml/prediction-markets/src/tests/request_edit.rs create mode 100644 zrml/prediction-markets/src/tests/schedule_early_close.rs create mode 100644 zrml/prediction-markets/src/tests/sell_complete_set.rs create mode 100644 zrml/prediction-markets/src/tests/start_global_dispute.rs diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 1334925db..d2834b23d 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -87,26 +87,28 @@ mod pallet { /// 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; - pub(crate) type NegativeImbalanceOf = - <::Currency as Currency>>::NegativeImbalance; - pub(crate) type TimeFrame = u64; + pub(crate) type AssetOf = Asset>; + pub(crate) type BalanceOf = ::Balance; + pub(crate) type CacheSize = ConstU32<64>; + pub(crate) type EditReason = BoundedVec::MaxEditReasonLen>; + pub(crate) type InitialItemOf = InitialItem, BalanceOf>; + pub(crate) type MarketBondsOf = MarketBonds, BalanceOf>; pub(crate) type MarketIdOf = ::MarketId; - pub(crate) type MomentOf = - <::Timestamp as frame_support::traits::Time>::Moment; - pub type MarketOf = Market< + pub(crate) type MarketOf = Market< AccountIdOf, BalanceOf, ::BlockNumber, MomentOf, - Asset>, + AssetOf, >; + pub(crate) type MomentOf = + <::Timestamp as frame_support::traits::Time>::Moment; + pub(crate) type NegativeImbalanceOf = + <::Currency as Currency>>::NegativeImbalance; + pub(crate) type RejectReason = BoundedVec::MaxRejectReasonLen>; pub(crate) type ReportOf = Report, ::BlockNumber>; - pub type CacheSize = ConstU32<64>; - pub type EditReason = BoundedVec::MaxEditReasonLen>; - pub type RejectReason = BoundedVec::MaxRejectReasonLen>; - type InitialItemOf = InitialItem, BalanceOf>; + pub(crate) type TimeFrame = u64; macro_rules! impl_unreserve_bond { ($fn_name:ident, $bond_type:ident) => { @@ -591,7 +593,7 @@ mod pallet { #[transactional] pub fn create_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -643,7 +645,7 @@ mod pallet { #[transactional] pub fn edit_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, market_id: MarketIdOf, oracle: T::AccountId, period: MarketPeriod>, @@ -1062,7 +1064,7 @@ mod pallet { #[pallet::call_index(17)] pub fn create_market_and_deploy_pool( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -1902,13 +1904,7 @@ mod pallet { SoldCompleteSet(MarketIdOf, BalanceOf, AccountIdOf), /// An amount of winning outcomes have been redeemed. /// \[market_id, currency_id, amount_redeemed, payout, who\] - TokensRedeemed( - MarketIdOf, - Asset>, - BalanceOf, - BalanceOf, - AccountIdOf, - ), + TokensRedeemed(MarketIdOf, AssetOf, BalanceOf, BalanceOf, AccountIdOf), /// The global dispute was started. \[market_id\] GlobalDisputeStarted(MarketIdOf), /// The recovery limit for timestamp based markets was reached due to a prolonged chain stall. @@ -2090,7 +2086,7 @@ mod pallet { #[require_transactional] fn do_create_market( who: T::AccountId, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -2148,10 +2144,7 @@ mod pallet { Ok((ids_amount, market_id)) } - pub fn outcome_assets( - market_id: MarketIdOf, - market: &MarketOf, - ) -> Vec>> { + pub fn outcome_assets(market_id: MarketIdOf, market: &MarketOf) -> Vec> { match market.market_type { MarketType::Categorical(categories) => { let mut assets = Vec::new(); @@ -2886,7 +2879,7 @@ mod pallet { } fn construct_market( - base_asset: Asset>, + base_asset: AssetOf, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, @@ -2899,7 +2892,7 @@ mod pallet { scoring_rule: ScoringRule, report: Option>, resolved_outcome: Option, - bonds: MarketBonds>, + bonds: MarketBondsOf, ) -> Result, DispatchError> { let valid_base_asset = match base_asset { Asset::Ztg => true, diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 40cad9a65..4eec9a8f7 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -482,7 +482,10 @@ impl ExtBuilder { } .assimilate_storage(&mut t) .unwrap(); - t.into() + + let mut test_ext: sp_io::TestExternalities = t.into(); + test_ext.execute_with(|| System::set_block_number(1)); + test_ext } } diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs deleted file mode 100644 index 0b3b6cbaf..000000000 --- a/zrml/prediction-markets/src/tests.rs +++ /dev/null @@ -1,5848 +0,0 @@ -// Copyright 2022-2024 Forecasting Technologies LTD. -// Copyright 2021-2022 Zeitgeist PM LLC. -// -// This file is part of Zeitgeist. -// -// Zeitgeist is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// Zeitgeist is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Zeitgeist. If not, see . - -#![cfg(all(feature = "mock", test))] -#![allow(clippy::reversed_empty_ranges)] - -extern crate alloc; - -use crate::{ - mock::*, Config, Error, Event, LastTimeFrame, MarketIdsForEdit, MarketIdsPerCloseBlock, - MarketIdsPerCloseTimeFrame, MarketIdsPerDisputeBlock, MarketIdsPerReportBlock, TimeFrame, -}; -use alloc::collections::BTreeMap; -use core::ops::{Range, RangeInclusive}; -use frame_support::{ - assert_noop, assert_ok, - dispatch::DispatchError, - traits::{NamedReservableCurrency, OnInitialize}, -}; -use sp_runtime::{traits::BlakeTwo256, Perquintill}; -use test_case::test_case; -use zeitgeist_primitives::types::{EarlyClose, EarlyCloseState}; -use zrml_court::{types::*, Error as CError}; - -use orml_traits::{MultiCurrency, MultiReservableCurrency}; -use sp_arithmetic::Perbill; -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, - }, - types::{ - AccountIdTest, Asset, Balance, BlockNumber, Bond, Deadlines, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, - MultiHash, OutcomeReport, Report, ScalarPosition, ScoringRule, - }, -}; -use zrml_global_disputes::{ - types::{OutcomeInfo, Possession}, - GlobalDisputesPalletApi, Outcomes, PossessionOf, -}; -use zrml_market_commons::MarketCommonsPalletApi; - -const SENTINEL_AMOUNT: u128 = BASE; - -fn get_deadlines() -> Deadlines<::BlockNumber> { - Deadlines { - grace_period: 1_u32.into(), - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get(), - } -} - -fn gen_metadata(byte: u8) -> MultiHash { - let mut metadata = [byte; 50]; - metadata[0] = 0x15; - metadata[1] = 0x30; - MultiHash::Sha3_384(metadata) -} - -fn reserve_sentinel_amounts() { - // Reserve a sentinel amount to check that we don't unreserve too much. - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &ALICE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &BOB, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &CHARLIE, - SENTINEL_AMOUNT - )); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &DAVE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &EVE, SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &FRED, SENTINEL_AMOUNT)); - assert_eq!(Balances::reserved_balance(ALICE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(BOB), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(CHARLIE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(DAVE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(EVE), SENTINEL_AMOUNT); - assert_eq!(Balances::reserved_balance(FRED), SENTINEL_AMOUNT); -} - -fn check_reserve(account: &AccountIdTest, expected: Balance) { - assert_eq!(Balances::reserved_balance(account), SENTINEL_AMOUNT + expected); -} - -fn simple_create_categorical_market( - base_asset: Asset, - creation: MarketCreation, - period: Range, - scoring_rule: ScoringRule, -) { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(period), - get_deadlines(), - gen_metadata(2), - creation, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - scoring_rule - )); -} - -fn simple_create_scalar_market( - base_asset: Asset, - creation: MarketCreation, - period: Range, - scoring_rule: ScoringRule, -) { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(period), - get_deadlines(), - gen_metadata(2), - creation, - MarketType::Scalar(100..=200), - Some(MarketDisputeMechanism::SimpleDisputes), - scoring_rule - )); -} - -#[test_case(MarketStatus::Proposed)] -#[test_case(MarketStatus::Closed)] -#[test_case(MarketStatus::Reported)] -#[test_case(MarketStatus::Disputed)] -#[test_case(MarketStatus::Resolved)] -fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), - Error::::MarketIsNotActive, - ); - }); -} - -#[test_case(ScoringRule::Parimutuel)] -fn buy_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - scoring_rule, - ); - let market_id = 0; - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), - Error::::InvalidScoringRule, - ); - }); -} - -#[test] -fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumber() { - ExtBuilder::default().build().execute_with(|| { - run_blocks(7); - let now = frame_system::Pallet::::block_number(); - let end = 42; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - now..end, - ScoringRule::Lmsr, - ); - run_blocks(3); - let market_id = 0; - assert_ok!(PredictionMarkets::admin_move_market_to_closed( - RuntimeOrigin::signed(SUDO), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - let new_end = now + 3; - assert_eq!(market.period, MarketPeriod::Block(now..new_end)); - assert_ne!(new_end, end); - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, MarketPeriod::Timestamp(start..end)); - - let shift_blocks = 3; - let shift = shift_blocks * MILLISECS_PER_BLOCK as u64; - // millisecs per block is substracted inside the function - set_timestamp_for_on_initialize(start + shift + MILLISECS_PER_BLOCK as u64); - run_blocks(shift_blocks); - - assert_ok!(PredictionMarkets::admin_move_market_to_closed( - RuntimeOrigin::signed(SUDO), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - let new_end = start + shift; - assert_eq!(market.period, MarketPeriod::Timestamp(start..new_end)); - assert_ne!(new_end, end); - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn admin_move_market_to_closed_fails_if_market_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::Reported; "reported")] -#[test_case(MarketStatus::Disputed; "disputed")] -#[test_case(MarketStatus::Resolved; "resolved")] -#[test_case(MarketStatus::Proposed; "proposed")] -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::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = market_status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), market_id), - Error::::MarketIsNotActive, - ); - }); -} - -#[test] -fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { - ExtBuilder::default().build().execute_with(|| { - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(22..66), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(33..66), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0)); - - let auto_close = MarketIdsPerCloseBlock::::get(66).into_inner(); - assert_eq!(auto_close, vec![1]); - }); -} - -#[test_case(654..=321; "empty range")] -#[test_case(555..=555; "one element as range")] -fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Scalar(range), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::InvalidOutcomeRange - ); - }); -} - -#[test] -fn create_market_fails_on_min_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get() - 1, - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::DisputeDurationSmallerThanMinDisputeDuration - ); - }); -} - -#[test] -fn create_market_fails_on_min_oracle_duration() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MinOracleDuration::get() - 1, - dispute_duration: ::MinDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::OracleDurationSmallerThanMinOracleDuration - ); - }); -} - -#[test] -fn create_market_fails_on_max_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get() + 1, - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::DisputeDurationGreaterThanMaxDisputeDuration - ); - }); -} - -#[test] -fn create_market_fails_on_max_grace_period() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get() + 1, - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::GracePeriodGreaterThanMaxGracePeriod - ); - }); -} - -#[test] -fn create_market_fails_on_max_oracle_duration() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get() + 1, - dispute_duration: ::MaxDisputeDuration::get(), - }; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::OracleDurationGreaterThanMaxOracleDuration - ); - }); -} - -#[cfg(feature = "parachain")] -#[test] -fn create_market_with_foreign_assets() { - ExtBuilder::default().build().execute_with(|| { - let deadlines = Deadlines { - grace_period: ::MaxGracePeriod::get(), - oracle_duration: ::MaxOracleDuration::get(), - dispute_duration: ::MaxDisputeDuration::get(), - }; - - // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. - - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::InvalidBaseAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - ), - Error::::UnregisteredForeignAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), - Perbill::zero(), - BOB, - MarketPeriod::Block(123..456), - deadlines, - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); - }); -} - -#[test] -fn admin_move_market_to_resolved_resolves_reported_market() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market_id = 0; - - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the correct bonds are unreserved! - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before = Balances::free_balance(ALICE); - let balance_reserved_before = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - let category = 1; - let outcome_report = OutcomeReport::Categorical(category); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - outcome_report.clone() - )); - assert_ok!(PredictionMarkets::admin_move_market_to_resolved( - RuntimeOrigin::signed(SUDO), - market_id - )); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - assert_eq!(market.report.unwrap().outcome, outcome_report); - assert_eq!(market.resolved_outcome.unwrap(), outcome_report); - System::assert_last_event( - Event::MarketResolved(market_id, MarketStatus::Resolved, outcome_report).into(), - ); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before - - ::OracleBond::get() - - ::ValidityBond::get() - ); - assert_eq!( - Balances::free_balance(ALICE), - balance_free_before - + ::OracleBond::get() - + ::ValidityBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[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(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..33, - ScoringRule::Lmsr, - ); - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = market_status; - Ok(()) - })); - assert_noop!( - PredictionMarkets::admin_move_market_to_resolved( - RuntimeOrigin::signed(SUDO), - market_id, - ), - Error::::InvalidMarketStatus, - ); - }); -} - -#[test] -fn it_creates_binary_markets() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - // check the correct amount was reserved - let alice_reserved = Balances::reserved_balance(ALICE); - assert_eq!(alice_reserved, ValidityBond::get() + OracleBond::get()); - - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..2, - ScoringRule::Lmsr, - ); - - let new_alice_reserved = Balances::reserved_balance(ALICE); - assert_eq!(new_alice_reserved, AdvisoryBond::get() + OracleBond::get() + alice_reserved); - - // Make sure that the market id has been incrementing - let market_id = MarketCommons::latest_market_id().unwrap(); - assert_eq!(market_id, 1); - }); -} - -#[test] -fn create_categorical_market_deposits_the_correct_event() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 1..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = PredictionMarkets::market_account(market_id); - System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); - }); -} - -#[test] -fn create_scalar_market_deposits_the_correct_event() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - simple_create_scalar_market( - Asset::Ztg, - MarketCreation::Permissionless, - 1..2, - ScoringRule::Lmsr, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = PredictionMarkets::market_account(market_id); - System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); - }); -} - -#[test] -fn it_does_not_create_market_with_too_few_categories() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(::MinCategories::get() - 1), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::NotEnoughCategories - ); - }); -} - -#[test] -fn it_does_not_create_market_with_too_many_categories() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(::MaxCategories::get() + 1), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::TooManyCategories - ); - }); -} - -#[test] -fn it_allows_advisory_origin_to_approve_markets() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::approve_market(RuntimeOrigin::signed(BOB), 0), - DispatchError::BadOrigin - ); - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - - let after_market = MarketCommons::market(&0); - assert_eq!(after_market.unwrap().status, MarketStatus::Active); - }); -} - -#[test] -fn it_allows_request_edit_origin_to_request_edits_for_markets() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), - DispatchError::BadOrigin - ); - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit( - RuntimeOrigin::signed(SUDO), - 0, - edit_reason.clone() - )); - System::assert_last_event( - Event::MarketRequestedEdit( - 0, - edit_reason.try_into().expect("Conversion to BoundedVec failed"), - ) - .into(), - ); - - assert!(MarketIdsForEdit::::contains_key(0)); - }); -} - -#[test] -fn request_edit_fails_on_bad_origin() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - // Make sure it fails from the random joe - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn edit_request_fails_if_edit_reason_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; - - assert_noop!( - PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), - Error::::EditReasonLengthExceedsMaxEditReasonLen - ); - }); -} - -#[test] -fn market_with_edit_request_cannot_be_approved() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - assert_noop!( - PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0), - Error::::MarketEditRequestAlreadyInProgress - ); - }); -} - -#[test] -fn it_allows_the_advisory_origin_to_reject_markets() { - ExtBuilder::default().build().execute_with(|| { - run_to_block(2); - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 4..6, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - // Now it should work from SUDO - assert_ok!(PredictionMarkets::reject_market( - RuntimeOrigin::signed(SUDO), - 0, - reject_reason.clone() - )); - let reject_reason = reject_reason.try_into().expect("BoundedVec conversion failed"); - System::assert_has_event(Event::MarketRejected(0, reject_reason).into()); - - assert_noop!( - MarketCommons::market(&0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test] -fn reject_errors_if_reject_reason_is_too_long() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize + 1]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::RejectReasonLengthExceedsMaxRejectReasonLen - ); - }); -} - -#[test] -fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - let reject_reason = vec![0_u8; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - assert!(MarketIdsForEdit::::contains_key(0)); - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - assert!(!MarketIdsForEdit::::contains_key(0)); - - assert_noop!( - MarketCommons::market(&0), - zrml_market_commons::Error::::MarketDoesNotExist - ); - }); -} - -#[test] -fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond gets slashed but the OracleBond gets unreserved. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT, - )); - assert!(Balances::free_balance(Treasury::account_id()).is_zero()); - - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - - // AdvisoryBond gets slashed after reject_market - // OracleBond gets unreserved after reject_market - let balance_reserved_after_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - assert_eq!( - balance_reserved_after_alice, - balance_reserved_before_alice - - ::OracleBond::get() - - ::AdvisoryBond::get(), - ); - let balance_free_after_alice = Balances::free_balance(ALICE); - let slash_amount_advisory_bond = ::AdvisoryBondSlashPercentage::get() - .mul_floor(::AdvisoryBond::get()); - let advisory_bond_remains = - ::AdvisoryBond::get() - slash_amount_advisory_bond; - assert_eq!( - balance_free_after_alice, - balance_free_before_alice - + ::OracleBond::get() - + advisory_bond_remains, - ); - - // AdvisoryBond is transferred to the treasury - let balance_treasury_after = Balances::free_balance(Treasury::account_id()); - assert_eq!(balance_treasury_after, slash_amount_advisory_bond); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn reject_market_clears_auto_close_blocks() { - // We don't have to check that reject market clears the cache for opening pools, since Cpmm pools - // can not be deployed on pending advised pools. - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 33..66, - ScoringRule::Lmsr, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..66, - ScoringRule::Lmsr, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..33, - ScoringRule::Lmsr, - ); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); - - let auto_close = MarketIdsPerCloseBlock::::get(66); - assert_eq!(auto_close.len(), 1); - assert_eq!(auto_close[0], 1); - }); -} - -#[test] -fn on_market_close_auto_rejects_expired_advised_market() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..end, - ScoringRule::Lmsr, - ); - let market_id = 0; - - run_to_block(end); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before_alice - ); - assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); - assert_noop!( - MarketCommons::market(&market_id), - zrml_market_commons::Error::::MarketDoesNotExist, - ); - System::assert_has_event(Event::MarketExpired(market_id).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { - let test = |base_asset: Asset| { - // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check - // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. - assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); - assert_ok!(Balances::reserve_named( - &PredictionMarkets::reserve_id(), - &ALICE, - SENTINEL_AMOUNT - )); - let balance_free_before_alice = Balances::free_balance(ALICE); - let balance_reserved_before_alice = - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); - - let end = 33; - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 0..end, - ScoringRule::Lmsr, - ); - run_to_block(2); - let market_id = 0; - let market = MarketCommons::market(&market_id); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - assert_ok!(PredictionMarkets::request_edit( - RuntimeOrigin::signed(SUDO), - market_id, - edit_reason - )); - - assert!(MarketIdsForEdit::::contains_key(0)); - run_blocks(end); - assert!(!MarketIdsForEdit::::contains_key(0)); - - assert_eq!( - Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), - balance_reserved_before_alice - ); - assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); - assert_noop!( - MarketCommons::market(&market_id), - zrml_market_commons::Error::::MarketDoesNotExist, - ); - System::assert_has_event(Event::MarketExpired(market_id).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_market_close_successfully_auto_closes_market_with_blocks() { - ExtBuilder::default().build().execute_with(|| { - let end = 33; - let category_count = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market_id = 0; - - 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); - - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -fn on_market_close_successfully_auto_closes_market_with_timestamps() { - ExtBuilder::default().build().execute_with(|| { - let end: Moment = (2 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - 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; - - // (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); - - 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); - - System::assert_last_event(Event::MarketClosed(market_id).into()); - }); -} - -#[test] -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(|| { - // 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_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, - )); - 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, - )); - - // 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()); - - 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_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(|| { - // 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_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, - )); - 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, - )); - - 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! - - System::assert_last_event( - Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.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 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 end: Moment = (5 * MILLISECS_PER_BLOCK).into(); - let category_count = 3; - 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, - )); - 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, - )); - - // 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! - 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 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(|| { - // 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_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, - )); - - // 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::Block(0..end), - get_deadlines(), - gen_metadata(50), - MarketCreation::Permissionless, - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - // 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::::NotAllowedForBlockBasedMarkets - ); - }); -} - -#[test] -fn on_initialize_skips_the_genesis_block() { - // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. - let blocks = 5; - let end: Moment = (blocks * MILLISECS_PER_BLOCK).into(); - ExtBuilder::default().build().execute_with(|| { - let category_count = 3; - 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, - )); - - // Blocknumber = 0 - assert_eq!(Timestamp::get(), 0); - PredictionMarkets::on_initialize(0); - assert_eq!(LastTimeFrame::::get(), None); - - // Blocknumber = 1 - assert_eq!(Timestamp::get(), 0); - PredictionMarkets::on_initialize(1); - assert_eq!(LastTimeFrame::::get(), None); - - // Blocknumer != 0, 1 - set_timestamp_for_on_initialize(end); - PredictionMarkets::on_initialize(2); - assert_eq!(LastTimeFrame::::get(), Some(blocks.into())); - }); -} - -#[test] -fn it_allows_to_buy_a_complete_set() { - let test = |base_asset: Asset| { - frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - // Allows someone to generate a complete set - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, CENT); - } - - let market_account = PredictionMarkets::market_account(0); - let bal = AssetManager::free_balance(base_asset, &BOB); - assert_eq!(bal, 1_000 * BASE - CENT); - - let market_bal = AssetManager::free_balance(base_asset, &market_account); - assert_eq!(market_bal, CENT); - System::assert_last_event(Event::BoughtCompleteSet(0, CENT, BOB).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_does_not_allow_to_buy_a_complete_set_on_pending_advised_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), - Error::::MarketIsNotActive, - ); - }); -} - -#[test] -fn create_categorical_market_fails_if_market_begin_is_equal_to_end() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(3..3), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - -#[test_case(MarketPeriod::Block(2..1); "block start greater than end")] -#[test_case(MarketPeriod::Block(3..3); "block start equal to end")] -#[test_case(MarketPeriod::Timestamp(2..1); "timestamp start greater than end")] -#[test_case(MarketPeriod::Timestamp(3..3); "timestamp start equal to end")] -#[test_case( - MarketPeriod::Timestamp(0..(MILLISECS_PER_BLOCK - 1).into()); - "range shorter than block time" -)] -fn create_categorical_market_fails_if_market_period_is_invalid( - period: MarketPeriod, -) { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - period, - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - -#[test] -fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { - ExtBuilder::default().build().execute_with(|| { - let end_block = 33; - run_to_block(end_block); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end_block), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - - let end_time = MILLISECS_PER_BLOCK as u64 / 2; - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..end_time), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - Error::::InvalidMarketPeriod, - ); - }); -} - -#[test] -fn it_does_not_allow_zero_amounts_in_buy_complete_set() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn it_does_not_allow_buying_complete_sets_with_insufficient_balance() { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 10000 * BASE), - Error::::NotEnoughBalance - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_allows_to_sell_a_complete_set() { - let test = |base_asset: Asset| { - frame_system::Pallet::::set_block_number(1); - // Creates a permissionless market. - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..2, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - assert_ok!(PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - - // Check the outcome balances - let assets = PredictionMarkets::outcome_assets(0, &market); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); - assert_eq!(bal, 0); - } - - // also check native balance - let bal = Balances::free_balance(BOB); - assert_eq!(bal, 1_000 * BASE); - - System::assert_last_event(Event::SoldCompleteSet(0, CENT, BOB).into()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_does_not_allow_zero_amounts_in_sell_complete_set() { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn it_does_not_allow_to_sell_complete_sets_with_insufficient_balance() { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - 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); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), - Error::::InsufficientShareBalance - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(ScoringRule::Parimutuel; "parimutuel")] -fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { - let test = |base_asset: Asset| { - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..1, - scoring_rule, - ); - assert_noop!( - PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), - Error::::InvalidScoringRule - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_allows_to_report_the_outcome_of_a_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let market_after = MarketCommons::market(&0).unwrap(); - let report = market_after.report.unwrap(); - assert_eq!(market_after.status, MarketStatus::Reported); - assert_eq!(report.outcome, OutcomeReport::Categorical(1)); - assert_eq!(report.by, market_after.oracle); - - // Reset and report again as approval origin - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::Closed; - market.report = None; - Ok(()) - }); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(SUDO), - 0, - OutcomeReport::Categorical(1) - )); - }); -} - -#[test] -fn report_fails_before_grace_period_is_over() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - run_to_block(end); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::NotAllowedToReportYet - ); - }); -} - -#[test] -fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_blocks() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - - assert_noop!( - PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - ), - Error::::ReporterNotOracle - ); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let market_after = MarketCommons::market(&0).unwrap(); - let report = market_after.report.unwrap(); - assert_eq!(market_after.status, MarketStatus::Reported); - assert_eq!(report.outcome, OutcomeReport::Categorical(1)); - assert_eq!(report.by, market_after.oracle); - }); -} - -#[test] -fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_moment() { - 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::Lmsr - )); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - - // set the timestamp - let market = MarketCommons::market(&0).unwrap(); - // set the timestamp - - set_timestamp_for_on_initialize(100_000_000); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2. - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - Timestamp::set_timestamp(100_000_000 + grace_period); - - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(EVE), 0, OutcomeReport::Categorical(1)), - Error::::ReporterNotOracle - ); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - }); -} - -#[test] -fn report_fails_on_mismatched_outcome_for_categorical_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Scalar(123)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - -#[test] -fn report_fails_on_out_of_range_outcome_for_categorical_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(2)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - -#[test] -fn report_fails_on_mismatched_outcome_for_scalar_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_scalar_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(0)), - Error::::OutcomeMismatch, - ); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - assert!(market.report.is_none()); - }); -} - -#[test] -fn it_allows_to_dispute_the_outcome_of_a_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // Run to the end of the trading phase. - 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(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 1); - let dispute = &disputes[0]; - assert_eq!(dispute.at, dispute_at); - assert_eq!(dispute.by, CHARLIE); - assert_eq!(dispute.outcome, OutcomeReport::Categorical(0)); - - let dispute_ends_at = dispute_at + market.deadlines.dispute_duration; - let market_ids = MarketIdsPerDisputeBlock::::get(dispute_ends_at); - assert_eq!(market_ids.len(), 1); - assert_eq!(market_ids[0], 0); - }); -} - -#[test] -fn dispute_fails_disputed_already() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - 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(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidMarketStatus, - ); - }); -} - -#[test] -fn dispute_fails_if_market_not_reported() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - run_to_block(grace_period + 1); - - // no report happening here... - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidMarketStatus, - ); - }); -} - -#[test] -fn dispute_reserves_dispute_bond() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - 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(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - let free_charlie_before = Balances::free_balance(CHARLIE); - let reserved_charlie = Balances::reserved_balance(CHARLIE); - assert_eq!(reserved_charlie, 0); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - let free_charlie_after = Balances::free_balance(CHARLIE); - assert_eq!(free_charlie_before - free_charlie_after, DisputeBond::get()); - - let reserved_charlie = Balances::reserved_balance(CHARLIE); - assert_eq!(reserved_charlie, DisputeBond::get()); - }); -} - -#[test] -fn schedule_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id)); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - assert!(new_end < end); - - let new_period = MarketPeriod::Block(0..new_end); - System::assert_last_event( - Event::MarketEarlyCloseScheduled { - market_id, - new_period: new_period.clone(), - state: EarlyCloseState::ScheduledAsOther, - } - .into(), - ); - }); -} - -#[test] -fn dispute_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - System::assert_last_event(Event::MarketEarlyCloseDisputed { market_id }.into()); - }); -} - -#[test] -fn reject_early_close_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - System::assert_last_event(Event::MarketEarlyCloseRejected { market_id }.into()); - }); -} - -#[test] -fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - assert_noop!( - PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn reject_early_close_fails_if_state_is_rejected() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - // just to ensure events are emitted - run_blocks(2); - - let market_id = 0; - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - assert_noop!( - PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn sudo_schedule_early_close_at_block_works() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().next().unwrap(); - assert_eq!(market_ids_to_close.0, end); - assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the old one without a market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end); - assert!(first.1.clone().into_inner().is_empty()); - - // The second entry is the new one with the market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, new_end); - assert_eq!(second.1.clone().into_inner(), vec![market_id]); - - run_to_block(new_end + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - -#[test] -fn sudo_schedule_early_close_at_timeframe_works() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 1); - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert_eq!(first.1.clone().into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::now(); - let new_end = now + CloseEarlyProtectionTimeFramePeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Timestamp(start..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the new one with the market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, new_end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert_eq!(first.1.clone().into_inner(), vec![market_id]); - - // The second entry is the old one without a market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); - assert!(second.1.clone().into_inner().is_empty()); - - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64 + new_end); - run_to_block(start_block + new_end.saturating_div(MILLISECS_PER_BLOCK.into()) + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - -#[test] -fn schedule_early_close_block_fails_if_early_close_request_too_late() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - run_to_block(end - 1); - - let market_id = 0; - assert_noop!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), - Error::::EarlyCloseRequestTooLate - ); - }); -} - -#[test] -fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { - ExtBuilder::default().build().execute_with(|| { - let start_block = 7; - set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); - run_blocks(start_block); - let start = >::now(); - let end = start + 42.saturated_into::() * MILLISECS_PER_BLOCK as u64; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - run_to_block(end.saturating_div(MILLISECS_PER_BLOCK.into()) - 1); - set_timestamp_for_on_initialize(end - MILLISECS_PER_BLOCK as u64); - - let market_id = 0; - assert_noop!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), - Error::::EarlyCloseRequestTooLate - ); - }); -} - -#[test] -fn schedule_early_close_as_market_creator_works() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - assert_eq!(market.status, MarketStatus::Active); - let market_ids_to_close = >::iter().next().unwrap(); - assert_eq!(market_ids_to_close.0, end); - assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); - assert!(market.early_close.is_none()); - - let reserved_balance_alice = Balances::reserved_balance(ALICE); - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let reserved_balance_alice_after = Balances::reserved_balance(ALICE); - assert_eq!( - reserved_balance_alice_after - reserved_balance_alice, - CloseEarlyRequestBond::get() - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyBlockPeriod::get(); - assert!(new_end < end); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::ScheduledAsMarketCreator, - } - ); - - let market_ids_to_close = >::iter().collect::>(); - assert_eq!(market_ids_to_close.len(), 2); - - // The first entry is the old one without a market id inside. - let first = market_ids_to_close.first().unwrap(); - assert_eq!(first.0, end); - assert!(first.1.clone().into_inner().is_empty()); - - // The second entry is the new one with the market id inside. - let second = market_ids_to_close.last().unwrap(); - assert_eq!(second.0, new_end); - assert_eq!(second.1.clone().into_inner(), vec![market_id]); - - run_to_block(new_end + 1); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - }); -} - -#[test] -fn dispute_early_close_from_market_creator_works() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let old_market_period = market.period; - - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let now = >::block_number(); - let new_end = now + CloseEarlyBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - run_blocks(1); - - let reserved_bob = Balances::reserved_balance(BOB); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob_after = Balances::reserved_balance(BOB); - assert_eq!(reserved_bob_after - reserved_bob, CloseEarlyDisputeBond::get()); - - let market_ids_at_new_end = >::get(new_end); - assert!(market_ids_at_new_end.is_empty()); - - let market_ids_at_old_end = >::get(end); - assert_eq!(market_ids_at_old_end, vec![market_id]); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, old_market_period); - assert_eq!(market.bonds.close_dispute, Some(Bond::new(BOB, CloseEarlyDisputeBond::get()))); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_market_period, - new: new_period, - state: EarlyCloseState::Disputed, - } - ); - - run_to_block(new_end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - }); -} - -#[test] -fn settles_early_close_bonds_with_resolution_in_state_disputed() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let alice_free = Balances::free_balance(ALICE); - let alice_reserved = Balances::reserved_balance(ALICE); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let bob_free = Balances::free_balance(BOB); - let bob_reserved = Balances::reserved_balance(BOB); - - run_to_block(end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - - let alice_free_after = Balances::free_balance(ALICE); - let alice_reserved_after = Balances::reserved_balance(ALICE); - // moved CloseEarlyRequestBond from reserved to free - assert_eq!(alice_reserved - alice_reserved_after, CloseEarlyRequestBond::get()); - assert_eq!(alice_free_after - alice_free, CloseEarlyRequestBond::get()); - - let bob_free_after = Balances::free_balance(BOB); - let bob_reserved_after = Balances::reserved_balance(BOB); - // moved CloseEarlyDisputeBond from reserved to free - assert_eq!(bob_reserved - bob_reserved_after, CloseEarlyDisputeBond::get()); - assert_eq!(bob_free_after - bob_free, CloseEarlyDisputeBond::get()); - }); -} - -#[test] -fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creator() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - let alice_free = Balances::free_balance(ALICE); - let alice_reserved = Balances::reserved_balance(ALICE); - - run_to_block(end + 1); - - // verify the market doesn't close after proposed new market period end - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Closed); - - let alice_free_after = Balances::free_balance(ALICE); - let alice_reserved_after = Balances::reserved_balance(ALICE); - // moved CloseEarlyRequestBond from reserved to free - assert_eq!(alice_reserved - alice_reserved_after, CloseEarlyRequestBond::get()); - assert_eq!(alice_free_after - alice_free, CloseEarlyRequestBond::get()); - }); -} - -#[test] -fn dispute_early_close_fails_if_scheduled_as_sudo() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - run_blocks(1); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn dispute_early_close_fails_if_already_disputed() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Disputed); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn reject_early_close_resets_to_old_market_period() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - run_blocks(1); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market_ids_at_new_end = >::get(new_end); - assert!(market_ids_at_new_end.is_empty()); - - let market_ids_at_old_end = >::get(end); - assert_eq!(market_ids_at_old_end, vec![market_id]); - }); -} - -#[test] -fn reject_early_close_settles_bonds() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob = Balances::reserved_balance(BOB); - let reserved_alice = Balances::reserved_balance(ALICE); - let free_bob = Balances::free_balance(BOB); - let free_alice = Balances::free_balance(ALICE); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); - - let reserved_bob_after = Balances::reserved_balance(BOB); - let reserved_alice_after = Balances::reserved_balance(ALICE); - let free_bob_after = Balances::free_balance(BOB); - let free_alice_after = Balances::free_balance(ALICE); - - assert_eq!(reserved_alice - reserved_alice_after, CloseEarlyRequestBond::get()); - assert_eq!(reserved_bob - reserved_bob_after, CloseEarlyDisputeBond::get()); - // disputant Bob gets the bonds - assert_eq!( - free_bob_after - free_bob, - CloseEarlyRequestBond::get() + CloseEarlyDisputeBond::get() - ); - assert_eq!(free_alice_after - free_alice, 0); - }); -} - -#[test] -fn dispute_early_close_fails_if_already_rejected() { - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); - - assert_noop!( - PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), - Error::::InvalidEarlyCloseState - ); - }); -} - -#[test] -fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { - ExtBuilder::default().build().execute_with(|| { - let end = 100; - let old_period = MarketPeriod::Block(0..end); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - old_period.clone(), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr - )); - - let market_id = 0; - assert_ok!(PredictionMarkets::schedule_early_close( - RuntimeOrigin::signed(ALICE), - market_id, - )); - - run_blocks(1); - - assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); - - let reserved_bob = Balances::reserved_balance(BOB); - let reserved_alice = Balances::reserved_balance(ALICE); - let free_bob = Balances::free_balance(BOB); - let free_alice = Balances::free_balance(ALICE); - - assert_ok!( - PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) - ); - - let reserved_bob_after = Balances::reserved_balance(BOB); - let reserved_alice_after = Balances::reserved_balance(ALICE); - let free_bob_after = Balances::free_balance(BOB); - let free_alice_after = Balances::free_balance(ALICE); - - assert_eq!(reserved_alice - reserved_alice_after, CloseEarlyRequestBond::get()); - assert_eq!(reserved_bob - reserved_bob_after, CloseEarlyDisputeBond::get()); - // market creator Alice gets the bonds - assert_eq!( - free_alice_after - free_alice, - CloseEarlyRequestBond::get() + CloseEarlyDisputeBond::get() - ); - assert_eq!(free_bob_after - free_bob, 0); - - let now = >::block_number(); - let new_end = now + CloseEarlyProtectionBlockPeriod::get(); - let market_ids_at_new_end = >::get(new_end); - assert_eq!(market_ids_at_new_end, vec![market_id]); - - let market = MarketCommons::market(&market_id).unwrap(); - let new_period = MarketPeriod::Block(0..new_end); - assert_eq!( - market.early_close.unwrap(), - EarlyClose { - old: old_period, - new: new_period, - state: EarlyCloseState::ScheduledAsOther, - } - ); - }); -} - -#[test] -fn dispute_updates_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - 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(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Reported); - assert_eq!(market.bonds.dispute, None); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - assert_eq!( - market.bonds.dispute, - Some(Bond { who: CHARLIE, value: DisputeBond::get(), is_settled: false }) - ); - }); -} - -#[test] -fn dispute_emits_event() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - 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(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - - // Run to the end of the trading phase. - 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(1) - )); - - let dispute_at = grace_period + 2; - run_to_block(dispute_at); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - System::assert_last_event( - Event::MarketDisputed(0u32.into(), MarketStatus::Disputed, CHARLIE).into(), - ); - }); -} - -#[test] -fn it_allows_anyone_to_report_an_unreported_market() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - let market = MarketCommons::market(&0).unwrap(); - // Just skip to waaaay overdue. - run_to_block(end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(ALICE), // alice reports her own market now - 0, - OutcomeReport::Categorical(1), - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Reported); - assert_eq!(market.report.unwrap().by, ALICE); - // but oracle was bob - assert_eq!(market.oracle, BOB); - - // make sure it still resolves - run_to_block( - frame_system::Pallet::::block_number() + market.deadlines.dispute_duration, - ); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - }); -} - -#[test] -fn it_correctly_resolves_a_market_that_was_reported_on() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - - let market = MarketCommons::market(&0).unwrap(); - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - - let reported_ids = - MarketIdsPerReportBlock::::get(report_at + market.deadlines.dispute_duration); - assert_eq!(reported_ids.len(), 1); - let id = reported_ids[0]; - assert_eq!(id, 0); - - run_blocks(market.deadlines.dispute_duration); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - - // Check balance of winning outcome asset. - let share_b = Asset::CategoricalOutcome(0, 1); - let share_b_total = Tokens::total_issuance(share_b); - assert_eq!(share_b_total, CENT); - let share_b_bal = Tokens::free_balance(share_b, &CHARLIE); - assert_eq!(share_b_bal, CENT); - - // TODO(#792): Remove other assets. - let share_a = Asset::CategoricalOutcome(0, 0); - let share_a_total = Tokens::total_issuance(share_a); - assert_eq!(share_a_total, CENT); - let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); - assert_eq!(share_a_bal, CENT); - - let share_c = Asset::CategoricalOutcome(0, 2); - let share_c_total = Tokens::total_issuance(share_c); - assert_eq!(share_c_total, 0); - let share_c_bal = Tokens::free_balance(share_c, &CHARLIE); - assert_eq!(share_c_bal, 0); - - assert!(market.bonds.creation.unwrap().is_settled); - assert!(market.bonds.oracle.unwrap().is_settled); - }); -} - -#[test] -fn it_resolves_a_disputed_market() { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - let market = MarketCommons::market(&0).unwrap(); - - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(0) - )); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - let charlie_reserved = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get()); - - let dispute_at_0 = report_at + 1; - run_to_block(dispute_at_0); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - - let dispute_at_1 = report_at + 2; - run_to_block(dispute_at_1); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(DAVE), - 0, - OutcomeReport::Categorical(0) - )); - - let dispute_at_2 = report_at + 3; - run_to_block(dispute_at_2); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - // check everyone's deposits - let charlie_reserved = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get() + OutcomeBond::get()); - - let dave_reserved = Balances::reserved_balance(DAVE); - assert_eq!(dave_reserved, OutcomeBond::get() + OutcomeFactor::get()); - - let eve_reserved = Balances::reserved_balance(EVE); - assert_eq!(eve_reserved, OutcomeBond::get() + 2 * OutcomeFactor::get()); - - // check disputes length - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 3); - - // make sure the old mappings of market id per dispute block are erased - let market_ids_1 = MarketIdsPerDisputeBlock::::get( - dispute_at_0 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_1.len(), 0); - - let market_ids_2 = MarketIdsPerDisputeBlock::::get( - dispute_at_1 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_2.len(), 0); - - let market_ids_3 = MarketIdsPerDisputeBlock::::get( - dispute_at_2 + market.deadlines.dispute_duration, - ); - assert_eq!(market_ids_3.len(), 1); - - run_blocks(market.deadlines.dispute_duration); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - - // Make sure rewards are right: - // - // Slashed amounts: - // - Dave's reserve: OutcomeBond::get() + OutcomeFactor::get() - // - Alice's oracle bond: OracleBond::get() - // simple-disputes reward: OutcomeBond::get() + OutcomeFactor::get() - // Charlie gets OracleBond, because the dispute was justified. - // A dispute is justified if the oracle's report is different to the final outcome. - // - // Charlie and Eve each receive half of the simple-disputes reward as bounty. - let dave_reserved = OutcomeBond::get() + OutcomeFactor::get(); - let total_slashed = dave_reserved; - - let charlie_balance = Balances::free_balance(CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get() + total_slashed / 2); - let charlie_reserved_2 = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved_2, 0); - let eve_balance = Balances::free_balance(EVE); - assert_eq!(eve_balance, 1_000 * BASE + total_slashed / 2); - - let dave_balance = Balances::free_balance(DAVE); - assert_eq!(dave_balance, 1_000 * BASE - dave_reserved); - - let alice_balance = Balances::free_balance(ALICE); - assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); - - // bob kinda gets away scot-free since Alice is held responsible - // for her designated reporter - let bob_balance = Balances::free_balance(BOB); - assert_eq!(bob_balance, 1_000 * BASE); - - assert!(market_after.bonds.creation.unwrap().is_settled); - assert!(market_after.bonds.oracle.unwrap().is_settled); - assert!(market_after.bonds.dispute.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn it_resolves_a_disputed_court_market() { - let test = |base_asset: Asset| { - let juror_0 = 1000; - let juror_1 = 1001; - let juror_2 = 1002; - let juror_3 = 1003; - let juror_4 = 1004; - let juror_5 = 1005; - - for j in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { - let amount = MinJurorStake::get() + *j; - assert_ok!(AssetManager::deposit(Asset::Ztg, j, amount + SENTINEL_AMOUNT)); - assert_ok!(Court::join_court(RuntimeOrigin::signed(*j), amount)); - } - - // just to have enough jurors for the dispute - for j in 1006..(1006 + Court::necessary_draws_weight(0usize) as u32) { - let juror = j as u128; - let amount = MinJurorStake::get() + juror; - assert_ok!(AssetManager::deposit(Asset::Ztg, &juror, amount + SENTINEL_AMOUNT)); - assert_ok!(Court::join_court(RuntimeOrigin::signed(juror), amount)); - } - - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&0).unwrap(); - - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - OutcomeReport::Categorical(0) - )); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - - let court = zrml_court::Courts::::get(market_id).unwrap(); - let vote_start = court.round_ends.pre_vote + 1; - - run_to_block(vote_start); - - // overwrite draws to disregard randomness - zrml_court::SelectedDraws::::remove(market_id); - let mut draws = zrml_court::SelectedDraws::::get(market_id); - for juror in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { - let draw = Draw { - court_participant: *juror, - weight: 1, - vote: Vote::Drawn, - slashable: MinJurorStake::get(), - }; - let index = draws - .binary_search_by_key(juror, |draw| draw.court_participant) - .unwrap_or_else(|j| j); - draws.try_insert(index, draw).unwrap(); - } - let old_draws = draws.clone(); - zrml_court::SelectedDraws::::insert(market_id, draws); - - let salt = ::Hash::default(); - - // outcome_0 is the plurality decision => right outcome - let outcome_0 = OutcomeReport::Categorical(0); - let vote_item_0 = VoteItem::Outcome(outcome_0.clone()); - // outcome_1 is the wrong outcome - let outcome_1 = OutcomeReport::Categorical(1); - let vote_item_1 = VoteItem::Outcome(outcome_1); - - let commitment_0 = BlakeTwo256::hash_of(&(juror_0, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_0), market_id, commitment_0)); - - // juror_1 votes for non-plurality outcome => slashed later - let commitment_1 = BlakeTwo256::hash_of(&(juror_1, vote_item_1.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_1), market_id, commitment_1)); - - let commitment_2 = BlakeTwo256::hash_of(&(juror_2, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_2), market_id, commitment_2)); - - let commitment_3 = BlakeTwo256::hash_of(&(juror_3, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_3), market_id, commitment_3)); - - // juror_4 fails to vote in time - - let commitment_5 = BlakeTwo256::hash_of(&(juror_5, vote_item_0.clone(), salt)); - assert_ok!(Court::vote(RuntimeOrigin::signed(juror_5), market_id, commitment_5)); - - // juror_3 is denounced by juror_0 => slashed later - assert_ok!(Court::denounce_vote( - RuntimeOrigin::signed(juror_0), - market_id, - juror_3, - vote_item_0.clone(), - salt - )); - - let aggregation_start = court.round_ends.vote + 1; - run_to_block(aggregation_start); - - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_0), - market_id, - vote_item_0.clone(), - salt - )); - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_1), - market_id, - vote_item_1, - salt - )); - - let wrong_salt = BlakeTwo256::hash_of(&69); - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_2), - market_id, - vote_item_0.clone(), - wrong_salt - ), - CError::::CommitmentHashMismatch - ); - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(juror_2), - market_id, - vote_item_0.clone(), - salt - )); - - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_3), - market_id, - vote_item_0.clone(), - salt - ), - CError::::VoteAlreadyDenounced - ); - - assert_noop!( - Court::reveal_vote( - RuntimeOrigin::signed(juror_4), - market_id, - vote_item_0.clone(), - salt - ), - CError::::JurorDidNotVote - ); - - // juror_5 fails to reveal in time - - let resolve_at = court.round_ends.appeal; - let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); - assert_eq!(market_ids.len(), 1); - - run_blocks(resolve_at); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - assert_eq!(market_after.resolved_outcome, Some(outcome_0)); - let court_after = zrml_court::Courts::::get(market_id).unwrap(); - assert_eq!(court_after.status, CourtStatus::Closed { winner: vote_item_0 }); - - let free_juror_0_before = Balances::free_balance(juror_0); - let free_juror_1_before = Balances::free_balance(juror_1); - let free_juror_2_before = Balances::free_balance(juror_2); - let free_juror_3_before = Balances::free_balance(juror_3); - let free_juror_4_before = Balances::free_balance(juror_4); - let free_juror_5_before = Balances::free_balance(juror_5); - - assert_ok!(Court::reassign_court_stakes(RuntimeOrigin::signed(juror_0), market_id)); - - let free_juror_0_after = Balances::free_balance(juror_0); - let slashable_juror_0 = - old_draws.iter().find(|draw| draw.court_participant == juror_0).unwrap().slashable; - let free_juror_1_after = Balances::free_balance(juror_1); - let slashable_juror_1 = - old_draws.iter().find(|draw| draw.court_participant == juror_1).unwrap().slashable; - let free_juror_2_after = Balances::free_balance(juror_2); - let slashable_juror_2 = - old_draws.iter().find(|draw| draw.court_participant == juror_2).unwrap().slashable; - let free_juror_3_after = Balances::free_balance(juror_3); - let slashable_juror_3 = - old_draws.iter().find(|draw| draw.court_participant == juror_3).unwrap().slashable; - let free_juror_4_after = Balances::free_balance(juror_4); - let slashable_juror_4 = - old_draws.iter().find(|draw| draw.court_participant == juror_4).unwrap().slashable; - let free_juror_5_after = Balances::free_balance(juror_5); - let slashable_juror_5 = - old_draws.iter().find(|draw| draw.court_participant == juror_5).unwrap().slashable; - - let mut total_slashed = 0; - // juror_1 voted for the wrong outcome => slashed - assert_eq!(free_juror_1_before - free_juror_1_after, slashable_juror_1); - total_slashed += slashable_juror_1; - // juror_3 was denounced by juror_0 => slashed - assert_eq!(free_juror_3_before - free_juror_3_after, slashable_juror_3); - total_slashed += slashable_juror_3; - // juror_4 failed to vote => slashed - assert_eq!(free_juror_4_before - free_juror_4_after, slashable_juror_4); - total_slashed += slashable_juror_4; - // juror_5 failed to reveal => slashed - assert_eq!(free_juror_5_before - free_juror_5_after, slashable_juror_5); - total_slashed += slashable_juror_5; - // juror_0 and juror_2 voted for the right outcome => rewarded - let total_winner_stake = slashable_juror_0 + slashable_juror_2; - let juror_0_share = Perquintill::from_rational(slashable_juror_0, total_winner_stake); - assert_eq!(free_juror_0_after, free_juror_0_before + juror_0_share * total_slashed); - let juror_2_share = Perquintill::from_rational(slashable_juror_2, total_winner_stake); - assert_eq!(free_juror_2_after, free_juror_2_before + juror_2_share * total_slashed); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -fn simulate_appeal_cycle(market_id: MarketId) { - let court = zrml_court::Courts::::get(market_id).unwrap(); - let vote_start = court.round_ends.pre_vote + 1; - - run_to_block(vote_start); - - let salt = ::Hash::default(); - - let wrong_outcome = OutcomeReport::Categorical(1); - let wrong_vote_item = VoteItem::Outcome(wrong_outcome); - - let draws = zrml_court::SelectedDraws::::get(market_id); - for draw in &draws { - let commitment = - BlakeTwo256::hash_of(&(draw.court_participant, wrong_vote_item.clone(), salt)); - assert_ok!(Court::vote( - RuntimeOrigin::signed(draw.court_participant), - market_id, - commitment - )); - } - - let aggregation_start = court.round_ends.vote + 1; - run_to_block(aggregation_start); - - for draw in draws { - assert_ok!(Court::reveal_vote( - RuntimeOrigin::signed(draw.court_participant), - market_id, - wrong_vote_item.clone(), - salt, - )); - } - - let resolve_at = court.round_ends.appeal; - let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); - assert_eq!(market_ids.len(), 1); - - run_to_block(resolve_at - 1); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Disputed); -} - -#[test] -fn it_appeals_a_court_market_to_global_dispute() { - let test = |base_asset: Asset| { - let mut free_before = BTreeMap::new(); - let jurors = 1000..(1000 + MaxSelectedDraws::get() as u128); - for j in jurors { - let amount = MinJurorStake::get() + j; - assert_ok!(AssetManager::deposit(Asset::Ztg, &j, amount + SENTINEL_AMOUNT)); - assert_ok!(Court::join_court(RuntimeOrigin::signed(j), amount)); - free_before.insert(j, Balances::free_balance(j)); - } - - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&0).unwrap(); - - let report_at = end + market.deadlines.grace_period + 1; - run_to_block(report_at); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - OutcomeReport::Categorical(0) - )); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - - for _ in 0..(MaxAppeals::get() - 1) { - simulate_appeal_cycle(market_id); - assert_ok!(Court::appeal(RuntimeOrigin::signed(BOB), market_id)); - } - - let court = zrml_court::Courts::::get(market_id).unwrap(); - let appeals = court.appeals; - assert_eq!(appeals.len(), (MaxAppeals::get() - 1) as usize); - - assert_noop!( - PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(BOB), market_id), - Error::::MarketDisputeMechanismNotFailed - ); - - simulate_appeal_cycle(market_id); - assert_ok!(Court::appeal(RuntimeOrigin::signed(BOB), market_id)); - - assert_noop!( - Court::appeal(RuntimeOrigin::signed(BOB), market_id), - CError::::MaxAppealsReached - ); - - assert!(!GlobalDisputes::does_exist(&market_id)); - - assert_ok!(PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(BOB), market_id)); - - let now = >::block_number(); - - assert!(GlobalDisputes::does_exist(&market_id)); - System::assert_last_event(Event::GlobalDisputeStarted(market_id).into()); - - // report check - let possession: PossessionOf = - Possession::Shared { owners: frame_support::BoundedVec::try_from(vec![BOB]).unwrap() }; - let outcome_info = OutcomeInfo { outcome_sum: Zero::zero(), possession }; - assert_eq!( - Outcomes::::get(market_id, &OutcomeReport::Categorical(0)).unwrap(), - outcome_info - ); - - let add_outcome_end = now + GlobalDisputes::get_add_outcome_period(); - let vote_end = add_outcome_end + GlobalDisputes::get_vote_period(); - let market_ids = MarketIdsPerDisputeBlock::::get(vote_end); - assert_eq!(market_ids, vec![market_id]); - assert!(GlobalDisputes::is_active(&market_id)); - - assert_noop!( - PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), - Error::::GlobalDisputeExistsAlready - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(MarketStatus::Active; "active")] -#[test_case(MarketStatus::Closed; "closed")] -#[test_case(MarketStatus::Proposed; "proposed")] -#[test_case(MarketStatus::Resolved; "resolved")] -fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - - assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { - market_inner.status = status; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0), - Error::::InvalidMarketStatus - ); - }); -} - -#[test] -fn start_global_dispute_fails_on_wrong_mdm() { - ExtBuilder::default().build().execute_with(|| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..2), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MaxDisputes::get() + 1), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - let market_id = MarketCommons::latest_market_id().unwrap(); - - let market = MarketCommons::market(&market_id).unwrap(); - let grace_period = market.deadlines.grace_period; - run_to_block(end + grace_period + 1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - OutcomeReport::Categorical(0) - )); - let dispute_at_0 = end + grace_period + 2; - run_to_block(dispute_at_0); - - // only one dispute allowed for authorized mdm - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); - run_blocks(1); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - assert_noop!( - PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), - Error::::InvalidDisputeMechanism - ); - }); -} - -#[test] -fn it_allows_to_redeem_shares() { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - 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(1) - )); - run_blocks(market.deadlines.dispute_duration); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - let bal = Balances::free_balance(CHARLIE); - assert_eq!(bal, 1_000 * BASE); - System::assert_last_event( - Event::TokensRedeemed(0, Asset::CategoricalOutcome(0, 1), CENT, CENT, CHARLIE).into(), - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test_case(ScoringRule::Parimutuel; "parimutuel")] -fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule) { - let test = |base_asset: Asset| { - let end = 2; - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - scoring_rule, - ); - - assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { - market_inner.status = MarketStatus::Resolved; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0), - Error::::InvalidResolutionMechanism - ); - }; - 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(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // ALICE is market creator through simple_create_categorical_market - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(BOB), - Asset::Ztg, - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::EditorNotCreator - ); - }); -} - -#[test] -fn edit_cycle_for_proposed_markets() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - run_to_block(1); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 2..4, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // BOB was the oracle before through simple_create_categorical_market - // After this edit its changed to ALICE - assert_ok!(PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - 0, - CHARLIE, - MarketPeriod::Block(2..4), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let edited_market = MarketCommons::market(&0).expect("Market not found"); - System::assert_last_event(Event::MarketEdited(0, edited_market).into()); - assert!(!MarketIdsForEdit::::contains_key(0)); - // verify oracle is CHARLIE - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().oracle, CHARLIE); - }); -} - -#[cfg(feature = "parachain")] -#[test] -fn edit_market_with_foreign_asset() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - - // make sure it's in status proposed - let market = MarketCommons::market(&0); - assert_eq!(market.unwrap().status, MarketStatus::Proposed); - - let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; - - // Now it should work from SUDO - assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); - - assert!(MarketIdsForEdit::::contains_key(0)); - - // ALICE is market creator through simple_create_categorical_market - // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::UnregisteredForeignAsset - ); - // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. - assert_noop!( - PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - ), - Error::::InvalidBaseAsset, - ); - // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. - assert_ok!(PredictionMarkets::edit_market( - RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), - 0, - CHARLIE, - MarketPeriod::Block(0..1), - get_deadlines(), - gen_metadata(2), - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); - }); -} - -#[test] -fn the_entire_market_lifecycle_works_with_timestamps() { - ExtBuilder::default().build().execute_with(|| { - // Creates a permissionless market. - 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::Lmsr - )); - - // is ok - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); - let market = MarketCommons::market(&0).unwrap(); - - // set the timestamp - set_timestamp_for_on_initialize(100_000_000); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2. - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - Timestamp::set_timestamp(100_000_000 + grace_period); - - assert_noop!( - PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), - Error::::MarketIsNotActive, - ); - - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - }); -} - -#[test] -fn full_scalar_market_lifecycle() { - let test = |base_asset: Asset| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), - gen_metadata(3), - MarketCreation::Permissionless, - MarketType::Scalar(10..=30), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(CHARLIE), - 0, - 100 * BASE - )); - - // check balances - let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); - assert_eq!(assets.len(), 2); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &CHARLIE); - assert_eq!(bal, 100 * BASE); - } - let market = MarketCommons::market(&0).unwrap(); - - set_timestamp_for_on_initialize(100_000_000); - let report_at = 2; - run_to_block(report_at); // Trigger `on_initialize`; must be at least block #2. - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - Timestamp::set_timestamp(100_000_000 + grace_period); - - // report - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Scalar(100) - )); - - let market_after_report = MarketCommons::market(&0).unwrap(); - assert!(market_after_report.report.is_some()); - let report = market_after_report.report.unwrap(); - assert_eq!(report.at, report_at); - assert_eq!(report.by, BOB); - assert_eq!(report.outcome, OutcomeReport::Scalar(100)); - - // dispute - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(DAVE), 0)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(DAVE), - 0, - OutcomeReport::Scalar(25) - )); - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 1); - - run_blocks(market.deadlines.dispute_duration); - - let market_after_resolve = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_resolve.status, MarketStatus::Resolved); - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - - // give EVE some shares - assert_ok!(Tokens::transfer( - RuntimeOrigin::signed(CHARLIE), - EVE, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 50 * BASE - )); - - assert_eq!( - Tokens::free_balance(Asset::ScalarOutcome(0, ScalarPosition::Short), &CHARLIE), - 50 * BASE - ); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &CHARLIE); - assert_eq!(bal, 0); - } - - // check payouts is right for each CHARLIE and EVE - let base_asset_bal_charlie = AssetManager::free_balance(base_asset, &CHARLIE); - let base_asset_bal_eve = AssetManager::free_balance(base_asset, &EVE); - assert_eq!(base_asset_bal_charlie, 98750 * CENT); // 75 (LONG) + 12.5 (SHORT) + 900 (balance) - assert_eq!(base_asset_bal_eve, 1000 * BASE); - System::assert_has_event( - Event::TokensRedeemed( - 0, - Asset::ScalarOutcome(0, ScalarPosition::Long), - 100 * BASE, - 75 * BASE, - CHARLIE, - ) - .into(), - ); - System::assert_has_event( - Event::TokensRedeemed( - 0, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 50 * BASE, - 1250 * CENT, // 12.5 - CHARLIE, - ) - .into(), - ); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); - let base_asset_bal_eve_after = AssetManager::free_balance(base_asset, &EVE); - assert_eq!(base_asset_bal_eve_after, 101250 * CENT); // 12.5 (SHORT) + 1000 (balance) - System::assert_last_event( - Event::TokensRedeemed( - 0, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 50 * BASE, - 1250 * CENT, // 12.5 - EVE, - ) - .into(), - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn scalar_market_correctly_resolves_on_out_of_range_outcomes_below_threshold() { - let test = |base_asset: Asset| { - scalar_market_correctly_resolves_common(base_asset, 50); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1100 * BASE); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { - let test = |base_asset: Asset| { - scalar_market_correctly_resolves_common(base_asset, 250); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 1000 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn reject_market_fails_on_permissionless_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..1, - ScoringRule::Lmsr, - ); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::InvalidMarketStatus - ); - }); -} - -#[test] -fn reject_market_fails_on_approved_market() { - ExtBuilder::default().build().execute_with(|| { - // Creates an advised market. - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 0..1, - ScoringRule::Lmsr, - ); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let reject_reason: Vec = - vec![0; ::MaxRejectReasonLen::get() as usize]; - assert_noop!( - PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), - Error::::InvalidMarketStatus - ); - }); -} - -#[test] -fn authorized_correctly_resolves_disputed_market() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - let end = 2; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(::MinCategories::get()), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); - - 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(0) - )); - - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT); - - let dispute_at = grace_period + 1 + 1; - run_to_block(dispute_at); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - - if base_asset == Asset::Ztg { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); - } else { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT); - } - - // Fred authorizses an outcome, but fat-fingers it on the first try. - assert_ok!(Authorized::authorize_market_outcome( - RuntimeOrigin::signed(AuthorizedDisputeResolutionUser::get()), - 0, - OutcomeReport::Categorical(0) - )); - assert_ok!(Authorized::authorize_market_outcome( - RuntimeOrigin::signed(AuthorizedDisputeResolutionUser::get()), - 0, - OutcomeReport::Categorical(1) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.status, MarketStatus::Disputed); - - // check everyone's deposits - let charlie_reserved = Balances::reserved_balance(CHARLIE); - assert_eq!(charlie_reserved, DisputeBond::get()); - - let market_ids_1 = MarketIdsPerDisputeBlock::::get( - dispute_at + ::CorrectionPeriod::get(), - ); - assert_eq!(market_ids_1.len(), 1); - - if base_asset == Asset::Ztg { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); - } else { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT); - } - - run_blocks(::CorrectionPeriod::get() - 1); - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Disputed); - - if base_asset == Asset::Ztg { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT - DisputeBond::get()); - } else { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT); - } - - run_blocks(1); - - if base_asset == Asset::Ztg { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT + OracleBond::get()); - } else { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE - CENT); - } - - let market_after = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after.status, MarketStatus::Resolved); - let disputes = zrml_simple_disputes::Disputes::::get(0); - assert_eq!(disputes.len(), 0); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - - if base_asset == Asset::Ztg { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); - } else { - let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); - assert_eq!(charlie_balance, 1_000 * BASE); - } - let charlie_reserved_2 = AssetManager::reserved_balance(Asset::Ztg, &CHARLIE); - assert_eq!(charlie_reserved_2, 0); - - let alice_balance = AssetManager::free_balance(Asset::Ztg, &ALICE); - assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); - - // bob kinda gets away scot-free since Alice is held responsible - // for her designated reporter - let bob_balance = AssetManager::free_balance(Asset::Ztg, &BOB); - assert_eq!(bob_balance, 1_000 * BASE); - - assert!(market_after.bonds.creation.unwrap().is_settled); - assert!(market_after.bonds.oracle.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn approve_market_correctly_unreserves_advisory_bond() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let market_id = 0; - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, AdvisoryBond::get() + OracleBond::get()); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), market_id)); - check_reserve(&ALICE, OracleBond::get()); - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + AdvisoryBond::get()); - let market = MarketCommons::market(&market_id).unwrap(); - assert!(market.bonds.creation.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - 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(0) - )); - run_to_block(grace_period + market.deadlines.dispute_duration + 1); - check_reserve(&ALICE, 0); - assert_eq!( - Balances::free_balance(ALICE), - alice_balance_before + ValidityBond::get() + OracleBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_outsider_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..100), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - - let charlie_balance_before = Balances::free_balance(CHARLIE); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - - assert!(market.bonds.outsider.is_none()); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.bonds.outsider, Some(Bond::new(CHARLIE, OutsiderBond::get()))); - check_reserve(&CHARLIE, OutsiderBond::get()); - assert_eq!(Balances::free_balance(CHARLIE), charlie_balance_before - OutsiderBond::get()); - let charlie_balance_before = Balances::free_balance(CHARLIE); - - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // Check that validity bond didn't get slashed, but oracle bond did - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - - check_reserve(&CHARLIE, 0); - // Check that the outsider gets the OracleBond together with the OutsiderBond - assert_eq!( - Balances::free_balance(CHARLIE), - charlie_balance_before + OracleBond::get() + OutsiderBond::get() - ); - let market = MarketCommons::market(&0).unwrap(); - assert!(market.bonds.outsider.unwrap().is_settled); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn outsider_reports_wrong_outcome() { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - - let end = 100; - let alice_balance_before = Balances::free_balance(ALICE); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let outsider = CHARLIE; - - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(1) - )); - - let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); - - let dispute_at_0 = report_at + 1; - run_to_block(dispute_at_0); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - check_reserve(&EVE, DisputeBond::get()); - - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(DAVE), - 0, - OutcomeReport::Categorical(0) - )); - - let outcome_bond = zrml_simple_disputes::default_outcome_bond::(0); - - check_reserve(&DAVE, outcome_bond); - - let eve_balance_before = Balances::free_balance(EVE); - let dave_balance_before = Balances::free_balance(DAVE); - - // on_resolution called - run_blocks(market.deadlines.dispute_duration); - - assert_eq!(Balances::free_balance(ALICE), alice_balance_before - OracleBond::get()); - - check_reserve(&outsider, 0); - assert_eq!(Balances::free_balance(outsider), outsider_balance_before); - - // disputor EVE gets the OracleBond and OutsiderBond and DisputeBond - assert_eq!( - Balances::free_balance(EVE), - eve_balance_before + DisputeBond::get() + OutsiderBond::get() + OracleBond::get() - ); - // DAVE gets his outcome bond back - assert_eq!(Balances::free_balance(DAVE), dave_balance_before + outcome_bond); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_oracle_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // Check that nothing got slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_outsider_report() - { - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let grace_period = end + market.deadlines.grace_period; - let report_at = grace_period + market.deadlines.oracle_duration + 1; - run_to_block(report_at); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - // Check that oracle bond got slashed - check_reserve(&ALICE, 0); - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_correct_disputed_outcome_with_oracle_report() - { - // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - 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(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_with_correct_disputed_outcome_with_oracle_report() - { - // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - 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(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_wrong_disputed_outcome_with_oracle_report() - { - // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - 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(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is not slashed - assert_eq!( - Balances::free_balance(ALICE), - alice_balance_before + ValidityBond::get() + OracleBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_wrong_disputed_outcome_with_oracle_report() - { - // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - 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(0) - )); - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is not slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_disputed_outcome_with_outsider_report() - { - // Oracle does not report in time, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Permissionless, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); - - let outsider = CHARLIE; - - let market = MarketCommons::market(&0).unwrap(); - let after_oracle_duration = - end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; - run_to_block(after_oracle_duration); - // CHARLIE is not an Oracle - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(0) - )); - let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(FRED), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); - - check_reserve(&outsider, 0); - assert_eq!( - Balances::free_balance(outsider), - outsider_balance_before + OracleBond::get() + OutsiderBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_disputed_outcome_with_outsider_report() - { - // Oracle does not report in time, so OracleBond gets slashed on resolution - // NOTE: Bonds are always in ZTG - let test = |base_asset: Asset| { - reserve_sentinel_amounts(); - let end = 100; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - base_asset, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr, - )); - - let outsider = CHARLIE; - - assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); - let alice_balance_before = Balances::free_balance(ALICE); - check_reserve(&ALICE, OracleBond::get()); - let market = MarketCommons::market(&0).unwrap(); - let after_oracle_duration = - end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; - run_to_block(after_oracle_duration); - // CHARLIE is not an Oracle - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(outsider), - 0, - OutcomeReport::Categorical(0) - )); - let outsider_balance_before = Balances::free_balance(outsider); - check_reserve(&outsider, OutsiderBond::get()); - - assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); - // EVE disputes with wrong outcome - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(EVE), - 0, - OutcomeReport::Categorical(1) - )); - assert_ok!(SimpleDisputes::suggest_outcome( - RuntimeOrigin::signed(FRED), - 0, - OutcomeReport::Categorical(0) - )); - run_blocks(market.deadlines.dispute_duration); - check_reserve(&ALICE, 0); - // ValidityBond bond is returned but OracleBond is slashed - assert_eq!(Balances::free_balance(ALICE), alice_balance_before); - - check_reserve(&outsider, 0); - assert_eq!( - Balances::free_balance(outsider), - outsider_balance_before + OracleBond::get() + OutsiderBond::get() - ); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn report_fails_on_market_state_proposed() { - 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::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_closed_for_advised_market() { - 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::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_active() { - 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::Lmsr - )); - 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(|| { - 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::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::Lmsr - )); - let _ = MarketCommons::mutate_market(&0, |market| { - market.status = MarketStatus::Resolved; - Ok(()) - }); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_if_reporter_is_not_the_oracle() { - 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::Lmsr - )); - let market = MarketCommons::market(&0).unwrap(); - set_timestamp_for_on_initialize(100_000_000); - // Trigger hooks which close the market. - run_to_block(2); - let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; - set_timestamp_for_on_initialize(100_000_000 + grace_period + MILLISECS_PER_BLOCK as u64); - assert_noop!( - PredictionMarkets::report( - RuntimeOrigin::signed(CHARLIE), - 0, - OutcomeReport::Categorical(1) - ), - Error::::ReporterNotOracle, - ); - }); -} - -#[test] -fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { - ExtBuilder::default().build().execute_with(|| { - let now = 1; - frame_system::Pallet::::set_block_number(now); - let start = 5; - let end = now + ::MaxMarketLifetime::get(); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - }); -} - -#[test] -fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { - ExtBuilder::default().build().execute_with(|| { - let now = 12_001u64; - Timestamp::set_timestamp(now); - let start = 5 * MILLISECS_PER_BLOCK as u64; - let end = - now + ::MaxMarketLifetime::get() * (MILLISECS_PER_BLOCK as u64); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - )); - }); -} - -#[test] -fn create_market_fails_if_market_duration_is_too_long_in_blocks() { - ExtBuilder::default().build().execute_with(|| { - let now = 1; - frame_system::Pallet::::set_block_number(now); - let start = 5; - let end = now + ::MaxMarketLifetime::get() + 1; - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - crate::Error::::MarketDurationTooLong, - ); - }); -} - -#[test] -fn create_market_fails_if_market_duration_is_too_long_in_moments() { - ExtBuilder::default().build().execute_with(|| { - let now = 12_001u64; - Timestamp::set_timestamp(now); - let start = 5 * MILLISECS_PER_BLOCK as u64; - let end = now - + (::MaxMarketLifetime::get() + 1) * (MILLISECS_PER_BLOCK as u64); - assert!( - end > start, - "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" - ); - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(start..end), - get_deadlines(), - gen_metadata(0), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Authorized), - ScoringRule::Lmsr, - ), - crate::Error::::MarketDurationTooLong, - ); - }); -} - -#[test_case( - MarketCreation::Advised, - ScoringRule::Lmsr, - MarketStatus::Proposed, - MarketBonds { - creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), - oracle: Some(Bond::new(ALICE, ::OracleBond::get())), - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - } -)] -#[test_case( - MarketCreation::Permissionless, - ScoringRule::Lmsr, - MarketStatus::Active, - 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, - } -)] -fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amount( - creation: MarketCreation, - scoring_rule: ScoringRule, - status: MarketStatus, - bonds: MarketBonds, -) { - 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 MultiHash::Sha3_384(multihash) = metadata; - let market_type = MarketType::Categorical(7); - let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); - let creator_fee = Perbill::from_parts(1); - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(creator), - Asset::Ztg, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata, - creation.clone(), - market_type.clone(), - dispute_mechanism.clone(), - scoring_rule, - )); - let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.creator, creator); - assert_eq!(market.creation, creation); - 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, scoring_rule); - assert_eq!(market.status, status); - 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_works() { - ExtBuilder::default().build().execute_with(|| { - let creator = ALICE; - let creator_fee = Perbill::from_parts(1); - 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 MultiHash::Sha3_384(multihash) = metadata; - 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; - let market_id = 0; - assert_ok!(PredictionMarkets::create_market_and_deploy_pool( - RuntimeOrigin::signed(creator), - Asset::Ztg, - creator_fee, - oracle, - period.clone(), - deadlines, - metadata, - market_type.clone(), - dispute_mechanism.clone(), - amount, - swap_prices.clone(), - swap_fee, - )); - let market = MarketCommons::market(&0).unwrap(); - 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, - }; - 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::Lmsr); - 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); - // Check that the correct amount of full sets were bought. - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(market_id, 0), &ALICE), - amount - ); - assert!(DeployPoolMock::called_once_with( - creator, - market_id, - amount, - swap_prices, - swap_fee - )); - }); -} - -#[test] -fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(1..2), - Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - ), - Error::::NonZeroDisputePeriodOnTrustedMarket - ); - }); -} - -#[test] -fn trusted_market_complete_lifecycle() { - ExtBuilder::default().build().execute_with(|| { - let end = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - let market_id = 0; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(FRED), - market_id, - BASE - )); - run_to_block(end); - let outcome = OutcomeReport::Categorical(1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - outcome.clone() - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); - assert_eq!(market.resolved_outcome, Some(outcome)); - assert_eq!(market.dispute_mechanism, None); - assert!(market.bonds.oracle.unwrap().is_settled); - assert_eq!(market.bonds.outsider, None); - assert_eq!(market.bonds.dispute, None); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); - // Ensure that we don't accidentally leave any artifacts. - assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); - assert!(MarketIdsPerReportBlock::::iter().next().is_none()); - }); -} - -#[test] -fn close_trusted_market_works() { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.dispute_mechanism, None); - - let new_end = end / 2; - assert_ne!(new_end, end); - run_to_block(new_end); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - - let auto_closes = MarketIdsPerCloseBlock::::get(end); - assert_eq!(auto_closes.first().cloned().unwrap(), market_id); - - assert_noop!( - PredictionMarkets::close_trusted_market(RuntimeOrigin::signed(BOB), market_id), - Error::::CallerNotMarketCreator - ); - - assert_ok!(PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.period, MarketPeriod::Block(0..new_end)); - assert_eq!(market.status, MarketStatus::Closed); - - let auto_closes = MarketIdsPerCloseBlock::::get(end); - assert_eq!(auto_closes.len(), 0); - }); -} - -#[test] -fn close_trusted_market_fails_if_not_trusted() { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: ::MinDisputeDuration::get(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - Some(MarketDisputeMechanism::Court), - ScoringRule::Lmsr, - )); - - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.dispute_mechanism, Some(MarketDisputeMechanism::Court)); - - run_to_block(end / 2); - - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Active); - - assert_noop!( - PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - ), - Error::::MarketIsNotTrusted - ); - }); -} - -#[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")] -fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { - ExtBuilder::default().build().execute_with(|| { - let end = 10; - let market_creator = ALICE; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(market_creator), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::Lmsr, - )); - - let market_id = 0; - assert_ok!(MarketCommons::mutate_market(&market_id, |market| { - market.status = status; - Ok(()) - })); - - assert_noop!( - PredictionMarkets::close_trusted_market( - RuntimeOrigin::signed(market_creator), - market_id - ), - Error::::MarketIsNotActive - ); - }); -} - -// Common code of `scalar_market_correctly_resolves_*` -fn scalar_market_correctly_resolves_common(base_asset: Asset, reported_value: u128) { - let end = 100; - simple_create_scalar_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::Lmsr, - ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); - assert_ok!(Tokens::transfer( - RuntimeOrigin::signed(CHARLIE), - EVE, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 100 * BASE - )); - // (Eve now has 100 SHORT, Charlie has 100 LONG) - - 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::Scalar(reported_value) - )); - let market_after_report = MarketCommons::market(&0).unwrap(); - assert!(market_after_report.report.is_some()); - let report = market_after_report.report.unwrap(); - assert_eq!(report.at, grace_period + 1); - assert_eq!(report.by, BOB); - assert_eq!(report.outcome, OutcomeReport::Scalar(reported_value)); - - run_blocks(market.deadlines.dispute_duration); - let market_after_resolve = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_resolve.status, MarketStatus::Resolved); - - // Check balances before redeeming (just to make sure that our tests are based on correct - // assumptions)! - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); - - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); - let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); - for asset in assets.iter() { - assert_eq!(AssetManager::free_balance(*asset, &CHARLIE), 0); - assert_eq!(AssetManager::free_balance(*asset, &EVE), 0); - } -} diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs new file mode 100644 index 000000000..b28662cfb --- /dev/null +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -0,0 +1,166 @@ +// Copyright 2022-2024 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 super::*; +use crate::{MarketIdsPerCloseBlock, MomentOf}; +use test_case::test_case; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumber() { + ExtBuilder::default().build().execute_with(|| { + run_blocks(7); + let now = frame_system::Pallet::::block_number(); + let end = 42; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + now..end, + ScoringRule::Lmsr, + ); + run_blocks(3); + let market_id = 0; + assert_ok!(PredictionMarkets::admin_move_market_to_closed( + RuntimeOrigin::signed(SUDO), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + let new_end = now + 3; + assert_eq!(market.period, MarketPeriod::Block(now..new_end)); + assert_ne!(new_end, end); + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as MomentOf); + run_blocks(start_block); + let start = >::now(); + + let end = start + 42u64 * (MILLISECS_PER_BLOCK as u64); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, MarketPeriod::Timestamp(start..end)); + + let shift_blocks = 3; + let shift = shift_blocks * MILLISECS_PER_BLOCK as u64; + // millisecs per block is substracted inside the function + set_timestamp_for_on_initialize(start + shift + MILLISECS_PER_BLOCK as u64); + run_blocks(shift_blocks); + + assert_ok!(PredictionMarkets::admin_move_market_to_closed( + RuntimeOrigin::signed(SUDO), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + let new_end = start + shift; + assert_eq!(market.period, MarketPeriod::Timestamp(start..new_end)); + assert_ne!(new_end, end); + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn admin_move_market_to_closed_fails_if_market_does_not_exist() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test_case(MarketStatus::Closed; "closed")] +#[test_case(MarketStatus::Reported; "reported")] +#[test_case(MarketStatus::Disputed; "disputed")] +#[test_case(MarketStatus::Resolved; "resolved")] +#[test_case(MarketStatus::Proposed; "proposed")] +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..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), market_id), + Error::::MarketIsNotActive, + ); + }); +} + +#[test] +fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { + ExtBuilder::default().build().execute_with(|| { + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(22..66), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(33..66), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::admin_move_market_to_closed(RuntimeOrigin::signed(SUDO), 0)); + + let auto_close = MarketIdsPerCloseBlock::::get(66).into_inner(); + assert_eq!(auto_close, vec![1]); + }); +} diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs new file mode 100644 index 000000000..d4a8a8cca --- /dev/null +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs @@ -0,0 +1,122 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +use zeitgeist_primitives::types::OutcomeReport; + +#[test] +fn admin_move_market_to_resolved_resolves_reported_market() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market_id = 0; + + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the correct bonds are unreserved! + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before = Balances::free_balance(ALICE); + let balance_reserved_before = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + let category = 1; + let outcome_report = OutcomeReport::Categorical(category); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + outcome_report.clone() + )); + assert_ok!(PredictionMarkets::admin_move_market_to_resolved( + RuntimeOrigin::signed(SUDO), + market_id + )); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + assert_eq!(market.report.unwrap().outcome, outcome_report); + assert_eq!(market.resolved_outcome.unwrap(), outcome_report); + System::assert_last_event( + Event::MarketResolved(market_id, MarketStatus::Resolved, outcome_report).into(), + ); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before + - ::OracleBond::get() + - ::ValidityBond::get() + ); + assert_eq!( + Balances::free_balance(ALICE), + balance_free_before + + ::OracleBond::get() + + ::ValidityBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +// TODO(#1239) resolves disputed market + +#[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(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..33, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = market_status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::admin_move_market_to_resolved( + RuntimeOrigin::signed(SUDO), + market_id, + ), + Error::::InvalidMarketStatus, + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs new file mode 100644 index 000000000..d5a0db67c --- /dev/null +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -0,0 +1,115 @@ +// Copyright 2022-2024 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 super::*; + +use crate::MarketIdsForEdit; +use sp_runtime::DispatchError; + +// TODO(#1239) Be more granular with regards to origins +// TODO(#1239) Approve fails if market status is not proposed + +#[test] +fn it_allows_advisory_origin_to_approve_markets() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + // Make sure it fails for the random joe + assert_noop!( + PredictionMarkets::approve_market(RuntimeOrigin::signed(BOB), 0), + DispatchError::BadOrigin + ); + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + + let after_market = MarketCommons::market(&0); + assert_eq!(after_market.unwrap().status, MarketStatus::Active); + }); +} + +#[test] +fn market_with_edit_request_cannot_be_approved() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + assert_noop!( + PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0), + Error::::MarketEditRequestAlreadyInProgress + ); + }); +} + +#[test] +fn approve_market_correctly_unreserves_advisory_bond() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market_id = 0; + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, AdvisoryBond::get() + OracleBond::get()); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), market_id)); + check_reserve(&ALICE, OracleBond::get()); + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + AdvisoryBond::get()); + let market = MarketCommons::market(&market_id).unwrap(); + assert!(market.bonds.creation.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs new file mode 100644 index 000000000..c9b2f3311 --- /dev/null +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -0,0 +1,147 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +// TODO(#1239) buy_complete_set fails if market doesn't exist + +#[test] +fn buy_complete_set_works() { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + + let market_id = 0; + let who = BOB; + let amount = CENT; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + amount + )); + + let market = MarketCommons::market(&market_id).unwrap(); + + let assets = PredictionMarkets::outcome_assets(market_id, &market); + for asset in assets.iter() { + let bal = Tokens::free_balance(*asset, &who); + assert_eq!(bal, amount); + } + + let bal = AssetManager::free_balance(base_asset, &who); + assert_eq!(bal, 1_000 * BASE - amount); + + let market_account = PredictionMarkets::market_account(market_id); + let market_bal = AssetManager::free_balance(base_asset, &market_account); + assert_eq!(market_bal, amount); + System::assert_last_event(Event::BoughtCompleteSet(market_id, amount, who).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn buy_complete_fails_on_zero_amount() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 0), + Error::::ZeroAmount + ); + }); +} + +#[test] +fn buy_complete_set_fails_on_insufficient_balance() { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, 10000 * BASE), + Error::::NotEnoughBalance + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(MarketStatus::Proposed)] +#[test_case(MarketStatus::Closed)] +#[test_case(MarketStatus::Reported)] +#[test_case(MarketStatus::Disputed)] +#[test_case(MarketStatus::Resolved)] +fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), + Error::::MarketIsNotActive, + ); + }); +} + +#[test_case(ScoringRule::Parimutuel)] +fn buy_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + scoring_rule, + ); + let market_id = 0; + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(FRED), market_id, 1), + Error::::InvalidScoringRule, + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/close_trusted_market.rs b/zrml/prediction-markets/src/tests/close_trusted_market.rs new file mode 100644 index 000000000..8fcdcb2f9 --- /dev/null +++ b/zrml/prediction-markets/src/tests/close_trusted_market.rs @@ -0,0 +1,166 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +use crate::MarketIdsPerCloseBlock; +use sp_runtime::traits::Zero; + +// TODO(#1239) MarketDoesNotExist + +// TODO(#1239) Split test +#[test] +fn close_trusted_market_works() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, None); + + let new_end = end / 2; + assert_ne!(new_end, end); + run_to_block(new_end); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.first().cloned().unwrap(), market_id); + + assert_noop!( + PredictionMarkets::close_trusted_market(RuntimeOrigin::signed(BOB), market_id), + Error::::CallerNotMarketCreator + ); + + assert_ok!(PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, MarketPeriod::Block(0..new_end)); + assert_eq!(market.status, MarketStatus::Closed); + + let auto_closes = MarketIdsPerCloseBlock::::get(end); + assert_eq!(auto_closes.len(), 0); + }); +} + +#[test] +fn close_trusted_market_fails_if_not_trusted() { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.dispute_mechanism, Some(MarketDisputeMechanism::Court)); + + run_to_block(end / 2); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotTrusted + ); + }); +} + +#[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")] +fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + let end = 10; + let market_creator = ALICE; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(market_creator), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + + let market_id = 0; + assert_ok!(MarketCommons::mutate_market(&market_id, |market| { + market.status = status; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::close_trusted_market( + RuntimeOrigin::signed(market_creator), + market_id + ), + Error::::MarketIsNotActive + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs new file mode 100644 index 000000000..151ab1407 --- /dev/null +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -0,0 +1,620 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +use crate::MarketBondsOf; +use core::ops::RangeInclusive; +use zeitgeist_primitives::{ + constants::MILLISECS_PER_BLOCK, + types::{BlockNumber, Bond, MarketBonds, Moment}, +}; + +// TODO(#1239) FeeTooHigh not verified +// TODO(#1239) InvalidMultihash not verified +// TODO(#1239) Creation fails if user can't afford the bonds + +#[test_case(std::ops::RangeInclusive::new(7, 6); "empty range")] +#[test_case(555..=555; "one element as range")] +fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Scalar(range), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::InvalidOutcomeRange + ); + }); +} + +#[test] +fn create_market_fails_on_min_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get() - 1, + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::DisputeDurationSmallerThanMinDisputeDuration + ); + }); +} + +#[test] +fn create_market_fails_on_min_oracle_duration() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MinOracleDuration::get() - 1, + dispute_duration: ::MinDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::OracleDurationSmallerThanMinOracleDuration + ); + }); +} + +#[test] +fn create_market_fails_on_max_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get() + 1, + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::DisputeDurationGreaterThanMaxDisputeDuration + ); + }); +} + +#[test] +fn create_market_fails_on_max_grace_period() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get() + 1, + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::GracePeriodGreaterThanMaxGracePeriod + ); + }); +} + +#[test] +fn create_market_fails_on_max_oracle_duration() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get() + 1, + dispute_duration: ::MaxDisputeDuration::get(), + }; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::OracleDurationGreaterThanMaxOracleDuration + ); + }); +} + +// TODO(#1239) split this test +#[cfg(feature = "parachain")] +#[test] +fn create_market_with_foreign_assets() { + ExtBuilder::default().build().execute_with(|| { + let deadlines = Deadlines { + grace_period: ::MaxGracePeriod::get(), + oracle_duration: ::MaxOracleDuration::get(), + dispute_duration: ::MaxDisputeDuration::get(), + }; + + // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. + + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(420), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::InvalidBaseAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(50), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + ), + Error::::UnregisteredForeignAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(100), + Perbill::zero(), + BOB, + MarketPeriod::Block(123..456), + deadlines, + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_does_not_create_market_with_too_few_categories() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(::MinCategories::get() - 1), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::NotEnoughCategories + ); + }); +} + +#[test] +fn it_does_not_create_market_with_too_many_categories() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(::MaxCategories::get() + 1), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::TooManyCategories + ); + }); +} + +#[test_case(MarketPeriod::Block(3..3); "empty range blocks")] +#[test_case(MarketPeriod::Timestamp(3..3); "empty range timestamp")] +#[test_case( + MarketPeriod::Timestamp(0..(MILLISECS_PER_BLOCK - 1).into()); + "range shorter than block time" +)] +fn create_categorical_market_fails_if_market_period_is_invalid( + period: MarketPeriod, +) { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + period, + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + }); +} + +#[test] +fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { + ExtBuilder::default().build().execute_with(|| { + let end_block = 33; + run_to_block(end_block); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end_block), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + + let end_time = MILLISECS_PER_BLOCK as u64 / 2; + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..end_time), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + Error::::InvalidMarketPeriod, + ); + }); +} + +#[test] +fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { + ExtBuilder::default().build().execute_with(|| { + let now = 1; + frame_system::Pallet::::set_block_number(now); + let start = 5; + let end = now + ::MaxMarketLifetime::get(); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + }); +} + +#[test] +fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { + ExtBuilder::default().build().execute_with(|| { + let now = 12_001u64; + Timestamp::set_timestamp(now); + let start = 5 * MILLISECS_PER_BLOCK as u64; + let end = + now + ::MaxMarketLifetime::get() * (MILLISECS_PER_BLOCK as u64); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + }); +} + +#[test] +fn create_market_fails_if_market_duration_is_too_long_in_blocks() { + ExtBuilder::default().build().execute_with(|| { + let now = 1; + frame_system::Pallet::::set_block_number(now); + let start = 5; + let end = now + ::MaxMarketLifetime::get() + 1; + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + crate::Error::::MarketDurationTooLong, + ); + }); +} + +#[test] +fn create_market_fails_if_market_duration_is_too_long_in_moments() { + ExtBuilder::default().build().execute_with(|| { + let now = 12_001u64; + Timestamp::set_timestamp(now); + let start = 5 * MILLISECS_PER_BLOCK as u64; + let end = now + + (::MaxMarketLifetime::get() + 1) * (MILLISECS_PER_BLOCK as u64); + assert!( + end > start, + "Test failed due to misconfiguration: `MaxMarketLifetime` is too small" + ); + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(0), + MarketCreation::Permissionless, + MarketType::Categorical(3), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + ), + crate::Error::::MarketDurationTooLong, + ); + }); +} + +#[test_case( + MarketCreation::Advised, + ScoringRule::Lmsr, + MarketStatus::Proposed, + MarketBonds { + creation: Some(Bond::new(ALICE, ::AdvisoryBond::get())), + oracle: Some(Bond::new(ALICE, ::OracleBond::get())), + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + } +)] +#[test_case( + MarketCreation::Permissionless, + ScoringRule::Lmsr, + MarketStatus::Active, + 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, + } +)] +fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amount( + creation: MarketCreation, + scoring_rule: ScoringRule, + status: MarketStatus, + bonds: MarketBondsOf, +) { + 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 MultiHash::Sha3_384(multihash) = metadata; + let market_type = MarketType::Categorical(7); + let dispute_mechanism = Some(MarketDisputeMechanism::Authorized); + let creator_fee = Perbill::from_parts(1); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(creator), + Asset::Ztg, + creator_fee, + oracle, + period.clone(), + deadlines, + metadata, + creation.clone(), + market_type.clone(), + dispute_mechanism.clone(), + scoring_rule, + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.creator, creator); + assert_eq!(market.creation, creation); + 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, scoring_rule); + assert_eq!(market.status, status); + 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_fails_on_trusted_market_with_non_zero_dispute_period() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(1..2), + Deadlines { + grace_period: 1, + oracle_duration: ::MinOracleDuration::get() + 2, + dispute_duration: ::MinDisputeDuration::get() + 3, + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + ), + Error::::NonZeroDisputePeriodOnTrustedMarket + ); + }); +} + +#[test] +fn create_categorical_market_deposits_the_correct_event() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let market_account = PredictionMarkets::market_account(market_id); + System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); + }); +} + +#[test] +fn create_scalar_market_deposits_the_correct_event() { + ExtBuilder::default().build().execute_with(|| { + simple_create_scalar_market( + Asset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let market_account = PredictionMarkets::market_account(market_id); + System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); + }); +} diff --git a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs new file mode 100644 index 000000000..73264f96d --- /dev/null +++ b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs @@ -0,0 +1,96 @@ +// Copyright 2022-2024 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 super::*; +use zeitgeist_primitives::types::{Bond, MarketBonds}; + +// TODO(#1239) Issue: Separate integration tests and other; use mocks for unit testing +// TODO(#1239) do_buy_complete_set failure +// TODO(#1239) deploy_pool failure + +#[test] +fn create_market_and_deploy_pool_works() { + ExtBuilder::default().build().execute_with(|| { + let creator = ALICE; + let creator_fee = Perbill::from_parts(1); + 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 MultiHash::Sha3_384(multihash) = metadata; + 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; + let market_id = 0; + assert_ok!(PredictionMarkets::create_market_and_deploy_pool( + RuntimeOrigin::signed(creator), + Asset::Ztg, + creator_fee, + oracle, + period.clone(), + deadlines, + metadata, + market_type.clone(), + dispute_mechanism.clone(), + amount, + swap_prices.clone(), + swap_fee, + )); + let market = MarketCommons::market(&0).unwrap(); + 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, + }; + 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::Lmsr); + 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); + // Check that the correct amount of full sets were bought. + assert_eq!( + AssetManager::free_balance(Asset::CategoricalOutcome(market_id, 0), &ALICE), + amount + ); + assert!(DeployPoolMock::called_once_with( + creator, + market_id, + amount, + swap_prices, + swap_fee + )); + }); +} diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs new file mode 100644 index 000000000..947f2caee --- /dev/null +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -0,0 +1,311 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +use crate::MarketIdsPerDisputeBlock; +use zeitgeist_primitives::types::{Bond, OutcomeReport}; + +// TODO(#1239) fails if market doesn't exist +// TODO(#1239) fails if market is trusted +// TODO(#1239) fails if user can't afford the bond + +#[test] +fn it_allows_to_dispute_the_outcome_of_a_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // Run to the end of the trading phase. + 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(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 1); + let dispute = &disputes[0]; + assert_eq!(dispute.at, dispute_at); + assert_eq!(dispute.by, CHARLIE); + assert_eq!(dispute.outcome, OutcomeReport::Categorical(0)); + + let dispute_ends_at = dispute_at + market.deadlines.dispute_duration; + let market_ids = MarketIdsPerDisputeBlock::::get(dispute_ends_at); + assert_eq!(market_ids.len(), 1); + assert_eq!(market_ids[0], 0); + }); +} + +#[test] +fn dispute_fails_disputed_already() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + 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(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidMarketStatus, + ); + }); +} + +#[test] +fn dispute_fails_if_market_not_reported() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + // no report happening here... + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidMarketStatus, + ); + }); +} + +#[test] +fn dispute_reserves_dispute_bond() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + 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(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + let free_charlie_before = Balances::free_balance(CHARLIE); + let reserved_charlie = Balances::reserved_balance(CHARLIE); + assert_eq!(reserved_charlie, 0); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + let free_charlie_after = Balances::free_balance(CHARLIE); + assert_eq!(free_charlie_before - free_charlie_after, DisputeBond::get()); + + let reserved_charlie = Balances::reserved_balance(CHARLIE); + assert_eq!(reserved_charlie, DisputeBond::get()); + }); +} + +#[test] +fn dispute_updates_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + 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(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Reported); + assert_eq!(market.bonds.dispute, None); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + assert_eq!( + market.bonds.dispute, + Some(Bond { who: CHARLIE, value: DisputeBond::get(), is_settled: false }) + ); + }); +} + +#[test] +fn dispute_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + + // Run to the end of the trading phase. + 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(1) + )); + + let dispute_at = grace_period + 2; + run_to_block(dispute_at); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + System::assert_last_event( + Event::MarketDisputed(0u32.into(), MarketStatus::Disputed, CHARLIE).into(), + ); + }); +} + +#[test_case(MarketStatus::Active; "active")] +#[test_case(MarketStatus::Closed; "closed")] +#[test_case(MarketStatus::Proposed; "proposed")] +#[test_case(MarketStatus::Resolved; "resolved")] +fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { + ExtBuilder::default().build().execute_with(|| { + // Creates a permissionless market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = status; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0), + Error::::InvalidMarketStatus + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs new file mode 100644 index 000000000..8e2f7fdf1 --- /dev/null +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -0,0 +1,421 @@ +// Copyright 2022-2024 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 super::*; + +use crate::MarketIdsPerCloseBlock; +use zeitgeist_primitives::types::{Bond, EarlyClose, EarlyCloseState}; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketIsNotActive +// TODO(#1239) dispute bond failure + +#[test] +fn dispute_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + System::assert_last_event(Event::MarketEarlyCloseDisputed { market_id }.into()); + }); +} + +#[test] +fn dispute_early_close_from_market_creator_works() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + run_blocks(1); + + let reserved_bob = Balances::reserved_balance(BOB); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob_after = Balances::reserved_balance(BOB); + assert_eq!( + reserved_bob_after - reserved_bob, + ::CloseEarlyDisputeBond::get() + ); + + let market_ids_at_new_end = >::get(new_end); + assert!(market_ids_at_new_end.is_empty()); + + let market_ids_at_old_end = >::get(end); + assert_eq!(market_ids_at_old_end, vec![market_id]); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.period, old_market_period); + assert_eq!( + market.bonds.close_dispute, + Some(Bond::new(BOB, ::CloseEarlyDisputeBond::get())) + ); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::Disputed, + } + ); + + run_to_block(new_end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Active); + }); +} + +#[test] +fn dispute_early_close_fails_if_scheduled_as_sudo() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + run_blocks(1); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn dispute_early_close_fails_if_already_disputed() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Disputed); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn dispute_early_close_fails_if_already_rejected() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); + + assert_noop!( + PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn settles_early_close_bonds_with_resolution_in_state_disputed() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let alice_free = Balances::free_balance(ALICE); + let alice_reserved = Balances::reserved_balance(ALICE); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let bob_free = Balances::free_balance(BOB); + let bob_reserved = Balances::reserved_balance(BOB); + + run_to_block(end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + + let alice_free_after = Balances::free_balance(ALICE); + let alice_reserved_after = Balances::reserved_balance(ALICE); + // moved ::CloseEarlyRequestBond from reserved to free + assert_eq!( + alice_reserved - alice_reserved_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + alice_free_after - alice_free, + ::CloseEarlyRequestBond::get() + ); + + let bob_free_after = Balances::free_balance(BOB); + let bob_reserved_after = Balances::reserved_balance(BOB); + // moved ::CloseEarlyDisputeBond from reserved to free + assert_eq!( + bob_reserved - bob_reserved_after, + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(bob_free_after - bob_free, ::CloseEarlyDisputeBond::get()); + }); +} + +#[test] +fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creator() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let alice_free = Balances::free_balance(ALICE); + let alice_reserved = Balances::reserved_balance(ALICE); + + run_to_block(end + 1); + + // verify the market doesn't close after proposed new market period end + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + + let alice_free_after = Balances::free_balance(ALICE); + let alice_reserved_after = Balances::reserved_balance(ALICE); + // moved ::CloseEarlyRequestBond from reserved to free + assert_eq!( + alice_reserved - alice_reserved_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + alice_free_after - alice_free, + ::CloseEarlyRequestBond::get() + ); + }); +} + +#[test] +fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + let old_period = MarketPeriod::Block(0..end); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + old_period.clone(), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob = Balances::reserved_balance(BOB); + let reserved_alice = Balances::reserved_balance(ALICE); + let free_bob = Balances::free_balance(BOB); + let free_alice = Balances::free_balance(ALICE); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let reserved_bob_after = Balances::reserved_balance(BOB); + let reserved_alice_after = Balances::reserved_balance(ALICE); + let free_bob_after = Balances::free_balance(BOB); + let free_alice_after = Balances::free_balance(ALICE); + + assert_eq!( + reserved_alice - reserved_alice_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + reserved_bob - reserved_bob_after, + ::CloseEarlyDisputeBond::get() + ); + // market creator Alice gets the bonds + assert_eq!( + free_alice_after - free_alice, + ::CloseEarlyRequestBond::get() + + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(free_bob_after - free_bob, 0); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs new file mode 100644 index 000000000..731b93d2c --- /dev/null +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -0,0 +1,183 @@ +// Copyright 2022-2024 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 super::*; + +use crate::MarketIdsForEdit; + +// TODO(#1239) MarketEditNotRequested +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) InvalidMarketStatus +// TODO(#1239) All failures that need to be ensured for `create_market` + +#[test] +fn only_creator_can_edit_market() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // ALICE is market creator through simple_create_categorical_market + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(BOB), + Asset::Ztg, + 0, + CHARLIE, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::EditorNotCreator + ); + }); +} + +#[test] +fn edit_cycle_for_proposed_markets() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // BOB was the oracle before through simple_create_categorical_market + // After this edit its changed to ALICE + assert_ok!(PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + 0, + CHARLIE, + MarketPeriod::Block(2..4), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let edited_market = MarketCommons::market(&0).expect("Market not found"); + System::assert_last_event(Event::MarketEdited(0, edited_market).into()); + assert!(!MarketIdsForEdit::::contains_key(0)); + // verify oracle is CHARLIE + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().oracle, CHARLIE); + }); +} + +#[cfg(feature = "parachain")] +#[test] +fn edit_market_with_foreign_asset() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + + assert!(MarketIdsForEdit::::contains_key(0)); + + // ALICE is market creator through simple_create_categorical_market + // As per Mock asset_registry genesis ForeignAsset(50) is not registered in asset_registry. + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(50), + 0, + CHARLIE, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::UnregisteredForeignAsset + ); + // As per Mock asset_registry genesis ForeignAsset(420) has allow_as_base_asset set to false. + assert_noop!( + PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(420), + 0, + CHARLIE, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + ), + Error::::InvalidBaseAsset, + ); + // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. + assert_ok!(PredictionMarkets::edit_market( + RuntimeOrigin::signed(ALICE), + Asset::ForeignAsset(100), + 0, + CHARLIE, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs new file mode 100644 index 000000000..7640e4acb --- /dev/null +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -0,0 +1,557 @@ +// Copyright 2022-2024 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 super::*; + +use alloc::collections::BTreeMap; +use zeitgeist_primitives::types::OutcomeReport; + +use crate::MarketIdsPerDisputeBlock; +use orml_traits::MultiReservableCurrency; +use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::ScalarPosition}; +use zrml_global_disputes::{ + types::{OutcomeInfo, Possession}, + GlobalDisputesPalletApi, Outcomes, PossessionOf, +}; + +#[test] +fn it_appeals_a_court_market_to_global_dispute() { + let test = |base_asset: AssetOf| { + let mut free_before = BTreeMap::new(); + let jurors = + 1000..(1000 + ::MaxSelectedDraws::get() as u128); + for j in jurors { + let amount = ::MinJurorStake::get() + j; + assert_ok!(AssetManager::deposit(Asset::Ztg, &j, amount + SENTINEL_AMOUNT)); + assert_ok!(Court::join_court(RuntimeOrigin::signed(j), amount)); + free_before.insert(j, Balances::free_balance(j)); + } + + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&0).unwrap(); + + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + OutcomeReport::Categorical(0) + )); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); + + for _ in 0..(::MaxAppeals::get() - 1) { + simulate_appeal_cycle(market_id); + assert_ok!(Court::appeal(RuntimeOrigin::signed(BOB), market_id)); + } + + let court = zrml_court::Courts::::get(market_id).unwrap(); + let appeals = court.appeals; + assert_eq!( + appeals.len(), + (::MaxAppeals::get() - 1) as usize + ); + + assert_noop!( + PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(BOB), market_id), + Error::::MarketDisputeMechanismNotFailed + ); + + simulate_appeal_cycle(market_id); + assert_ok!(Court::appeal(RuntimeOrigin::signed(BOB), market_id)); + + assert_noop!( + Court::appeal(RuntimeOrigin::signed(BOB), market_id), + zrml_court::Error::::MaxAppealsReached + ); + + assert!(!GlobalDisputes::does_exist(&market_id)); + + assert_ok!(PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(BOB), market_id)); + + let now = >::block_number(); + + assert!(GlobalDisputes::does_exist(&market_id)); + System::assert_last_event(Event::GlobalDisputeStarted(market_id).into()); + + // report check + let possession: PossessionOf = + Possession::Shared { owners: frame_support::BoundedVec::try_from(vec![BOB]).unwrap() }; + let outcome_info = OutcomeInfo { outcome_sum: Zero::zero(), possession }; + assert_eq!( + Outcomes::::get(market_id, &OutcomeReport::Categorical(0)).unwrap(), + outcome_info + ); + + let add_outcome_end = now + GlobalDisputes::get_add_outcome_period(); + let vote_end = add_outcome_end + GlobalDisputes::get_vote_period(); + let market_ids = MarketIdsPerDisputeBlock::::get(vote_end); + assert_eq!(market_ids, vec![market_id]); + assert!(GlobalDisputes::is_active(&market_id)); + + assert_noop!( + PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), + Error::::GlobalDisputeExistsAlready + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn the_entire_market_lifecycle_works_with_timestamps() { + ExtBuilder::default().build().execute_with(|| { + // Creates a permissionless market. + 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::Lmsr + )); + + // is ok + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + let market = MarketCommons::market(&0).unwrap(); + + // set the timestamp + set_timestamp_for_on_initialize(100_000_000); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2. + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + Timestamp::set_timestamp(100_000_000 + grace_period); + + assert_noop!( + PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT), + Error::::MarketIsNotActive, + ); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + }); +} + +#[test] +fn full_scalar_market_lifecycle() { + let test = |base_asset: AssetOf| { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(0..100_000_000), + get_deadlines(), + gen_metadata(3), + MarketCreation::Permissionless, + MarketType::Scalar(10..=30), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(CHARLIE), + 0, + 100 * BASE + )); + + // check balances + let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); + assert_eq!(assets.len(), 2); + for asset in assets.iter() { + let bal = Tokens::free_balance(*asset, &CHARLIE); + assert_eq!(bal, 100 * BASE); + } + let market = MarketCommons::market(&0).unwrap(); + + set_timestamp_for_on_initialize(100_000_000); + let report_at = 2; + run_to_block(report_at); // Trigger `on_initialize`; must be at least block #2. + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + Timestamp::set_timestamp(100_000_000 + grace_period); + + // report + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Scalar(100) + )); + + let market_after_report = MarketCommons::market(&0).unwrap(); + assert!(market_after_report.report.is_some()); + let report = market_after_report.report.unwrap(); + assert_eq!(report.at, report_at); + assert_eq!(report.by, BOB); + assert_eq!(report.outcome, OutcomeReport::Scalar(100)); + + // dispute + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(DAVE), 0)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(DAVE), + 0, + OutcomeReport::Scalar(25) + )); + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 1); + + run_blocks(market.deadlines.dispute_duration); + + let market_after_resolve = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_resolve.status, MarketStatus::Resolved); + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 0); + + // give EVE some shares + assert_ok!(Tokens::transfer( + RuntimeOrigin::signed(CHARLIE), + EVE, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 50 * BASE + )); + + assert_eq!( + Tokens::free_balance(Asset::ScalarOutcome(0, ScalarPosition::Short), &CHARLIE), + 50 * BASE + ); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + for asset in assets.iter() { + let bal = Tokens::free_balance(*asset, &CHARLIE); + assert_eq!(bal, 0); + } + + // check payouts is right for each CHARLIE and EVE + let base_asset_bal_charlie = AssetManager::free_balance(base_asset, &CHARLIE); + let base_asset_bal_eve = AssetManager::free_balance(base_asset, &EVE); + assert_eq!(base_asset_bal_charlie, 98750 * CENT); // 75 (LONG) + 12.5 (SHORT) + 900 (balance) + assert_eq!(base_asset_bal_eve, 1000 * BASE); + System::assert_has_event( + Event::TokensRedeemed( + 0, + Asset::ScalarOutcome(0, ScalarPosition::Long), + 100 * BASE, + 75 * BASE, + CHARLIE, + ) + .into(), + ); + System::assert_has_event( + Event::TokensRedeemed( + 0, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 50 * BASE, + 1250 * CENT, // 12.5 + CHARLIE, + ) + .into(), + ); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); + let base_asset_bal_eve_after = AssetManager::free_balance(base_asset, &EVE); + assert_eq!(base_asset_bal_eve_after, 101250 * CENT); // 12.5 (SHORT) + 1000 (balance) + System::assert_last_event( + Event::TokensRedeemed( + 0, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 50 * BASE, + 1250 * CENT, // 12.5 + EVE, + ) + .into(), + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn authorized_correctly_resolves_disputed_market() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + + 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(0) + )); + + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + + let dispute_at = grace_period + 1 + 1; + run_to_block(dispute_at); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + if base_asset == Asset::Ztg { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); + } else { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + } + + // Fred authorizses an outcome, but fat-fingers it on the first try. + assert_ok!(Authorized::authorize_market_outcome( + RuntimeOrigin::signed(AuthorizedDisputeResolutionUser::get()), + 0, + OutcomeReport::Categorical(0) + )); + assert_ok!(Authorized::authorize_market_outcome( + RuntimeOrigin::signed(AuthorizedDisputeResolutionUser::get()), + 0, + OutcomeReport::Categorical(1) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + // check everyone's deposits + let charlie_reserved = Balances::reserved_balance(CHARLIE); + assert_eq!(charlie_reserved, ::DisputeBond::get()); + + let market_ids_1 = MarketIdsPerDisputeBlock::::get( + dispute_at + ::CorrectionPeriod::get(), + ); + assert_eq!(market_ids_1.len(), 1); + + if base_asset == Asset::Ztg { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); + } else { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + } + + run_blocks(::CorrectionPeriod::get() - 1); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Disputed); + + if base_asset == Asset::Ztg { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT - ::DisputeBond::get() + ); + } else { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + } + + run_blocks(1); + + if base_asset == Asset::Ztg { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!( + charlie_balance, + 1_000 * BASE - CENT + ::OracleBond::get() + ); + } else { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE - CENT); + } + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 0); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + + if base_asset == Asset::Ztg { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); + } else { + let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); + let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE); + } + let charlie_reserved_2 = AssetManager::reserved_balance(Asset::Ztg, &CHARLIE); + assert_eq!(charlie_reserved_2, 0); + + let alice_balance = AssetManager::free_balance(Asset::Ztg, &ALICE); + assert_eq!(alice_balance, 1_000 * BASE - ::OracleBond::get()); + + // bob kinda gets away scot-free since Alice is held responsible + // for her designated reporter + let bob_balance = AssetManager::free_balance(Asset::Ztg, &BOB); + assert_eq!(bob_balance, 1_000 * BASE); + + assert!(market_after.bonds.creation.unwrap().is_settled); + assert!(market_after.bonds.oracle.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn outsider_reports_wrong_outcome() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + + let end = 100; + let alice_balance_before = Balances::free_balance(ALICE); + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + let outsider = CHARLIE; + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(outsider), + 0, + OutcomeReport::Categorical(1) + )); + + let outsider_balance_before = Balances::free_balance(outsider); + check_reserve(&outsider, ::OutsiderBond::get()); + + let dispute_at_0 = report_at + 1; + run_to_block(dispute_at_0); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + check_reserve(&EVE, ::DisputeBond::get()); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(DAVE), + 0, + OutcomeReport::Categorical(0) + )); + + let outcome_bond = zrml_simple_disputes::default_outcome_bond::(0); + + check_reserve(&DAVE, outcome_bond); + + let eve_balance_before = Balances::free_balance(EVE); + let dave_balance_before = Balances::free_balance(DAVE); + + // on_resolution called + run_blocks(market.deadlines.dispute_duration); + + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before - ::OracleBond::get() + ); + + check_reserve(&outsider, 0); + assert_eq!(Balances::free_balance(outsider), outsider_balance_before); + + // disputor EVE gets the OracleBond and ::OutsiderBond and DisputeBond + assert_eq!( + Balances::free_balance(EVE), + eve_balance_before + + ::DisputeBond::get() + + ::OutsiderBond::get() + + ::OracleBond::get() + ); + // DAVE gets his outcome bond back + assert_eq!(Balances::free_balance(DAVE), dave_balance_before + outcome_bond); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/manually_close_market.rs b/zrml/prediction-markets/src/tests/manually_close_market.rs new file mode 100644 index 000000000..9c61a8d33 --- /dev/null +++ b/zrml/prediction-markets/src/tests/manually_close_market.rs @@ -0,0 +1,172 @@ +// Copyright 2022-2024 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 super::*; + +use crate::{LastTimeFrame, MarketIdsPerCloseTimeFrame}; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketPeriodEndNotAlreadyReachedYet + +#[test] +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 end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + 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, + )); + 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, + )); + + // 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! + 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 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(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + 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, + )); + + // 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::Block(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + // 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::::NotAllowedForBlockBasedMarkets + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs new file mode 100644 index 000000000..e11639a55 --- /dev/null +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -0,0 +1,188 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "mock", test))] + +mod admin_move_market_to_closed; +mod admin_move_market_to_resolved; +mod approve_market; +mod buy_complete_set; +mod close_trusted_market; +mod create_market; +mod create_market_and_deploy_pool; +mod dispute; +mod dispute_early_close; +mod edit_market; +mod integration; +mod manually_close_market; +mod on_initialize; +mod on_market_close; +mod on_resolution; +mod redeem_shares; +mod reject_early_close; +mod reject_market; +mod report; +mod request_edit; +mod schedule_early_close; +mod sell_complete_set; +mod start_global_dispute; + +use crate::{ + mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event, MarketIdsPerDisputeBlock, +}; +use core::ops::Range; +use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; +use orml_traits::MultiCurrency; +use sp_arithmetic::Perbill; +use sp_runtime::traits::{BlakeTwo256, Hash, Zero}; +use zeitgeist_primitives::{ + constants::mock::{BASE, CENT}, + types::{ + Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, + MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, + }, +}; +use zrml_court::types::VoteItem; +use zrml_market_commons::MarketCommonsPalletApi; + +const SENTINEL_AMOUNT: u128 = BASE; + +fn get_deadlines() -> Deadlines<::BlockNumber> { + Deadlines { + grace_period: 1_u32.into(), + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: ::MinDisputeDuration::get(), + } +} + +fn gen_metadata(byte: u8) -> MultiHash { + let mut metadata = [byte; 50]; + metadata[0] = 0x15; + metadata[1] = 0x30; + MultiHash::Sha3_384(metadata) +} + +fn simple_create_categorical_market( + base_asset: AssetOf, + creation: MarketCreation, + period: Range, + scoring_rule: ScoringRule, +) { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(period), + get_deadlines(), + gen_metadata(2), + creation, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::SimpleDisputes), + scoring_rule + )); +} + +fn simple_create_scalar_market( + base_asset: AssetOf, + creation: MarketCreation, + period: Range, + scoring_rule: ScoringRule, +) { + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(period), + get_deadlines(), + gen_metadata(2), + creation, + MarketType::Scalar(100..=200), + Some(MarketDisputeMechanism::SimpleDisputes), + scoring_rule + )); +} + +fn check_reserve(account: &AccountIdOf, expected: BalanceOf) { + assert_eq!(Balances::reserved_balance(account), SENTINEL_AMOUNT + expected); +} + +fn reserve_sentinel_amounts() { + // Reserve a sentinel amount to check that we don't unreserve too much. + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &ALICE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &BOB, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &CHARLIE, + SENTINEL_AMOUNT + )); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &DAVE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &EVE, SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named(&PredictionMarkets::reserve_id(), &FRED, SENTINEL_AMOUNT)); + assert_eq!(Balances::reserved_balance(ALICE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(BOB), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(CHARLIE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(DAVE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(EVE), SENTINEL_AMOUNT); + assert_eq!(Balances::reserved_balance(FRED), SENTINEL_AMOUNT); +} + +fn simulate_appeal_cycle(market_id: MarketId) { + let court = zrml_court::Courts::::get(market_id).unwrap(); + let vote_start = court.round_ends.pre_vote + 1; + + run_to_block(vote_start); + + let salt = ::Hash::default(); + + let wrong_outcome = OutcomeReport::Categorical(1); + let wrong_vote_item = VoteItem::Outcome(wrong_outcome); + + let draws = zrml_court::SelectedDraws::::get(market_id); + for draw in &draws { + let commitment = + BlakeTwo256::hash_of(&(draw.court_participant, wrong_vote_item.clone(), salt)); + assert_ok!(Court::vote( + RuntimeOrigin::signed(draw.court_participant), + market_id, + commitment + )); + } + + let aggregation_start = court.round_ends.vote + 1; + run_to_block(aggregation_start); + + for draw in draws { + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(draw.court_participant), + market_id, + wrong_vote_item.clone(), + salt, + )); + } + + let resolve_at = court.round_ends.appeal; + let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); + assert_eq!(market_ids.len(), 1); + + run_to_block(resolve_at - 1); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Disputed); +} diff --git a/zrml/prediction-markets/src/tests/on_initialize.rs b/zrml/prediction-markets/src/tests/on_initialize.rs new file mode 100644 index 000000000..0d5d74427 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_initialize.rs @@ -0,0 +1,61 @@ +// Copyright 2022-2024 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 super::*; + +use crate::LastTimeFrame; +use frame_support::traits::Hooks; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn on_initialize_skips_the_genesis_block() { + // We ensure that a timestamp of zero will not be stored at genesis into LastTimeFrame storage. + let blocks = 5; + let end = (blocks * MILLISECS_PER_BLOCK) as u64; + ExtBuilder::default().build().execute_with(|| { + let category_count = 3; + 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, + )); + + // Blocknumber = 0 + assert_eq!(Timestamp::get(), 0); + PredictionMarkets::on_initialize(0); + assert_eq!(LastTimeFrame::::get(), None); + + // Blocknumber = 1 + assert_eq!(Timestamp::get(), 0); + PredictionMarkets::on_initialize(1); + assert_eq!(LastTimeFrame::::get(), None); + + // Blocknumer != 0, 1 + set_timestamp_for_on_initialize(end); + PredictionMarkets::on_initialize(2); + assert_eq!(LastTimeFrame::::get(), Some(blocks.into())); + }); +} diff --git a/zrml/prediction-markets/src/tests/on_market_close.rs b/zrml/prediction-markets/src/tests/on_market_close.rs new file mode 100644 index 000000000..23ce18632 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_market_close.rs @@ -0,0 +1,301 @@ +// Copyright 2022-2024 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 super::*; + +use crate::{LastTimeFrame, MarketIdsForEdit}; +use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; + +#[test] +fn on_market_close_auto_rejects_expired_advised_market() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..end, + ScoringRule::Lmsr, + ); + let market_id = 0; + + run_to_block(end); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before_alice + ); + assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); + assert_noop!( + MarketCommons::market(&market_id), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + System::assert_has_event(Event::MarketExpired(market_id).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { + let test = |base_asset: AssetOf| { + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT + )); + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let end = 33; + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..end, + ScoringRule::Lmsr, + ); + run_to_block(2); + let market_id = 0; + let market = MarketCommons::market(&market_id); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + assert_ok!(PredictionMarkets::request_edit( + RuntimeOrigin::signed(SUDO), + market_id, + edit_reason + )); + + assert!(MarketIdsForEdit::::contains_key(0)); + run_blocks(end); + assert!(!MarketIdsForEdit::::contains_key(0)); + + assert_eq!( + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE), + balance_reserved_before_alice + ); + assert_eq!(Balances::free_balance(ALICE), balance_free_before_alice); + assert_noop!( + MarketCommons::market(&market_id), + zrml_market_commons::Error::::MarketDoesNotExist, + ); + System::assert_has_event(Event::MarketExpired(market_id).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_market_close_successfully_auto_closes_market_with_blocks() { + ExtBuilder::default().build().execute_with(|| { + let end = 33; + let category_count = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + ALICE, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(50), + MarketCreation::Permissionless, + MarketType::Categorical(category_count), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let market_id = 0; + + 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); + + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +fn on_market_close_successfully_auto_closes_market_with_timestamps() { + ExtBuilder::default().build().execute_with(|| { + let end = (2 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + 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; + + // (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); + + 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); + + System::assert_last_event(Event::MarketClosed(market_id).into()); + }); +} + +#[test] +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(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + 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, + )); + 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, + )); + + // 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()); + + 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_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(|| { + // Mock last time frame to prevent it from defaulting. + LastTimeFrame::::set(Some(0)); + + let end = (5 * MILLISECS_PER_BLOCK) as u64; + let category_count = 3; + 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, + )); + 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, + )); + + 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! + + System::assert_last_event( + Event::RecoveryLimitReached { last_time_frame: 0, limit_time_frame: 6 }.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); + }); +} diff --git a/zrml/prediction-markets/src/tests/on_resolution.rs b/zrml/prediction-markets/src/tests/on_resolution.rs new file mode 100644 index 000000000..c9ec16c84 --- /dev/null +++ b/zrml/prediction-markets/src/tests/on_resolution.rs @@ -0,0 +1,1101 @@ +// Copyright 2022-2024 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 super::*; + +use crate::{MarketIdsPerDisputeBlock, MarketIdsPerReportBlock}; +use sp_runtime::{ + traits::{BlakeTwo256, Hash, Zero}, + Perquintill, +}; +use zeitgeist_primitives::types::{Bond, OutcomeReport, Report}; +use zrml_court::types::{CourtStatus, Draw, Vote, VoteItem}; + +#[test] +fn it_correctly_resolves_a_market_that_was_reported_on() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + + let market = MarketCommons::market(&0).unwrap(); + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let reported_ids = + MarketIdsPerReportBlock::::get(report_at + market.deadlines.dispute_duration); + assert_eq!(reported_ids.len(), 1); + let id = reported_ids[0]; + assert_eq!(id, 0); + + run_blocks(market.deadlines.dispute_duration); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + + // Check balance of winning outcome asset. + let share_b = Asset::CategoricalOutcome(0, 1); + let share_b_total = Tokens::total_issuance(share_b); + assert_eq!(share_b_total, CENT); + let share_b_bal = Tokens::free_balance(share_b, &CHARLIE); + assert_eq!(share_b_bal, CENT); + + // TODO(#792): Remove other assets. + let share_a = Asset::CategoricalOutcome(0, 0); + let share_a_total = Tokens::total_issuance(share_a); + assert_eq!(share_a_total, CENT); + let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); + assert_eq!(share_a_bal, CENT); + + let share_c = Asset::CategoricalOutcome(0, 2); + let share_c_total = Tokens::total_issuance(share_c); + assert_eq!(share_c_total, 0); + let share_c_bal = Tokens::free_balance(share_c, &CHARLIE); + assert_eq!(share_c_bal, 0); + + assert!(market.bonds.creation.unwrap().is_settled); + assert!(market.bonds.oracle.unwrap().is_settled); + }); +} + +#[test] +fn it_resolves_a_disputed_market() { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + let market = MarketCommons::market(&0).unwrap(); + + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(0) + )); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + let charlie_reserved = Balances::reserved_balance(CHARLIE); + assert_eq!(charlie_reserved, DisputeBond::get()); + + let dispute_at_0 = report_at + 1; + run_to_block(dispute_at_0); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + + let dispute_at_1 = report_at + 2; + run_to_block(dispute_at_1); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(DAVE), + 0, + OutcomeReport::Categorical(0) + )); + + let dispute_at_2 = report_at + 3; + run_to_block(dispute_at_2); + + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + // check everyone's deposits + let charlie_reserved = Balances::reserved_balance(CHARLIE); + assert_eq!( + charlie_reserved, + DisputeBond::get() + ::OutcomeBond::get() + ); + + let dave_reserved = Balances::reserved_balance(DAVE); + assert_eq!( + dave_reserved, + ::OutcomeBond::get() + + ::OutcomeFactor::get() + ); + + let eve_reserved = Balances::reserved_balance(EVE); + assert_eq!( + eve_reserved, + ::OutcomeBond::get() + + 2 * ::OutcomeFactor::get() + ); + + // check disputes length + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 3); + + // make sure the old mappings of market id per dispute block are erased + let market_ids_1 = MarketIdsPerDisputeBlock::::get( + dispute_at_0 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_1.len(), 0); + + let market_ids_2 = MarketIdsPerDisputeBlock::::get( + dispute_at_1 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_2.len(), 0); + + let market_ids_3 = MarketIdsPerDisputeBlock::::get( + dispute_at_2 + market.deadlines.dispute_duration, + ); + assert_eq!(market_ids_3.len(), 1); + + run_blocks(market.deadlines.dispute_duration); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + let disputes = zrml_simple_disputes::Disputes::::get(0); + assert_eq!(disputes.len(), 0); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + + // Make sure rewards are right: + // + // Slashed amounts: + // - Dave's reserve: ::OutcomeBond::get() + ::OutcomeFactor::get() + // - Alice's oracle bond: OracleBond::get() + // simple-disputes reward: ::OutcomeBond::get() + ::OutcomeFactor::get() + // Charlie gets OracleBond, because the dispute was justified. + // A dispute is justified if the oracle's report is different to the final outcome. + // + // Charlie and Eve each receive half of the simple-disputes reward as bounty. + let dave_reserved = ::OutcomeBond::get() + + ::OutcomeFactor::get(); + let total_slashed = dave_reserved; + + let charlie_balance = Balances::free_balance(CHARLIE); + assert_eq!(charlie_balance, 1_000 * BASE + OracleBond::get() + total_slashed / 2); + let charlie_reserved_2 = Balances::reserved_balance(CHARLIE); + assert_eq!(charlie_reserved_2, 0); + let eve_balance = Balances::free_balance(EVE); + assert_eq!(eve_balance, 1_000 * BASE + total_slashed / 2); + + let dave_balance = Balances::free_balance(DAVE); + assert_eq!(dave_balance, 1_000 * BASE - dave_reserved); + + let alice_balance = Balances::free_balance(ALICE); + assert_eq!(alice_balance, 1_000 * BASE - OracleBond::get()); + + // bob kinda gets away scot-free since Alice is held responsible + // for her designated reporter + let bob_balance = Balances::free_balance(BOB); + assert_eq!(bob_balance, 1_000 * BASE); + + assert!(market_after.bonds.creation.unwrap().is_settled); + assert!(market_after.bonds.oracle.unwrap().is_settled); + assert!(market_after.bonds.dispute.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn it_resolves_a_disputed_court_market() { + let test = |base_asset: AssetOf| { + let juror_0 = 1000; + let juror_1 = 1001; + let juror_2 = 1002; + let juror_3 = 1003; + let juror_4 = 1004; + let juror_5 = 1005; + + for j in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { + let amount = ::MinJurorStake::get() + *j; + assert_ok!(AssetManager::deposit(Asset::Ztg, j, amount + SENTINEL_AMOUNT)); + assert_ok!(Court::join_court(RuntimeOrigin::signed(*j), amount)); + } + + // just to have enough jurors for the dispute + for j in 1006..(1006 + Court::necessary_draws_weight(0usize) as u32) { + let juror = j as u128; + let amount = ::MinJurorStake::get() + juror; + assert_ok!(AssetManager::deposit(Asset::Ztg, &juror, amount + SENTINEL_AMOUNT)); + assert_ok!(Court::join_court(RuntimeOrigin::signed(juror), amount)); + } + + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr, + )); + + let market_id = 0; + let market = MarketCommons::market(&0).unwrap(); + + let report_at = end + market.deadlines.grace_period + 1; + run_to_block(report_at); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + OutcomeReport::Categorical(0) + )); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); + + let court = zrml_court::Courts::::get(market_id).unwrap(); + let vote_start = court.round_ends.pre_vote + 1; + + run_to_block(vote_start); + + // overwrite draws to disregard randomness + zrml_court::SelectedDraws::::remove(market_id); + let mut draws = zrml_court::SelectedDraws::::get(market_id); + for juror in &[juror_0, juror_1, juror_2, juror_3, juror_4, juror_5] { + let draw = Draw { + court_participant: *juror, + weight: 1, + vote: Vote::Drawn, + slashable: ::MinJurorStake::get(), + }; + let index = draws + .binary_search_by_key(juror, |draw| draw.court_participant) + .unwrap_or_else(|j| j); + draws.try_insert(index, draw).unwrap(); + } + let old_draws = draws.clone(); + zrml_court::SelectedDraws::::insert(market_id, draws); + + let salt = ::Hash::default(); + + // outcome_0 is the plurality decision => right outcome + let outcome_0 = OutcomeReport::Categorical(0); + let vote_item_0 = VoteItem::Outcome(outcome_0.clone()); + // outcome_1 is the wrong outcome + let outcome_1 = OutcomeReport::Categorical(1); + let vote_item_1 = VoteItem::Outcome(outcome_1); + + let commitment_0 = BlakeTwo256::hash_of(&(juror_0, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_0), market_id, commitment_0)); + + // juror_1 votes for non-plurality outcome => slashed later + let commitment_1 = BlakeTwo256::hash_of(&(juror_1, vote_item_1.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_1), market_id, commitment_1)); + + let commitment_2 = BlakeTwo256::hash_of(&(juror_2, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_2), market_id, commitment_2)); + + let commitment_3 = BlakeTwo256::hash_of(&(juror_3, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_3), market_id, commitment_3)); + + // juror_4 fails to vote in time + + let commitment_5 = BlakeTwo256::hash_of(&(juror_5, vote_item_0.clone(), salt)); + assert_ok!(Court::vote(RuntimeOrigin::signed(juror_5), market_id, commitment_5)); + + // juror_3 is denounced by juror_0 => slashed later + assert_ok!(Court::denounce_vote( + RuntimeOrigin::signed(juror_0), + market_id, + juror_3, + vote_item_0.clone(), + salt + )); + + let aggregation_start = court.round_ends.vote + 1; + run_to_block(aggregation_start); + + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_0), + market_id, + vote_item_0.clone(), + salt + )); + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_1), + market_id, + vote_item_1, + salt + )); + + let wrong_salt = BlakeTwo256::hash_of(&69); + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_2), + market_id, + vote_item_0.clone(), + wrong_salt + ), + zrml_court::Error::::CommitmentHashMismatch + ); + assert_ok!(Court::reveal_vote( + RuntimeOrigin::signed(juror_2), + market_id, + vote_item_0.clone(), + salt + )); + + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_3), + market_id, + vote_item_0.clone(), + salt + ), + zrml_court::Error::::VoteAlreadyDenounced + ); + + assert_noop!( + Court::reveal_vote( + RuntimeOrigin::signed(juror_4), + market_id, + vote_item_0.clone(), + salt + ), + zrml_court::Error::::JurorDidNotVote + ); + + // juror_5 fails to reveal in time + + let resolve_at = court.round_ends.appeal; + let market_ids = MarketIdsPerDisputeBlock::::get(resolve_at); + assert_eq!(market_ids.len(), 1); + + run_blocks(resolve_at); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + assert_eq!(market_after.resolved_outcome, Some(outcome_0)); + let court_after = zrml_court::Courts::::get(market_id).unwrap(); + assert_eq!(court_after.status, CourtStatus::Closed { winner: vote_item_0 }); + + let free_juror_0_before = Balances::free_balance(juror_0); + let free_juror_1_before = Balances::free_balance(juror_1); + let free_juror_2_before = Balances::free_balance(juror_2); + let free_juror_3_before = Balances::free_balance(juror_3); + let free_juror_4_before = Balances::free_balance(juror_4); + let free_juror_5_before = Balances::free_balance(juror_5); + + assert_ok!(Court::reassign_court_stakes(RuntimeOrigin::signed(juror_0), market_id)); + + let free_juror_0_after = Balances::free_balance(juror_0); + let slashable_juror_0 = + old_draws.iter().find(|draw| draw.court_participant == juror_0).unwrap().slashable; + let free_juror_1_after = Balances::free_balance(juror_1); + let slashable_juror_1 = + old_draws.iter().find(|draw| draw.court_participant == juror_1).unwrap().slashable; + let free_juror_2_after = Balances::free_balance(juror_2); + let slashable_juror_2 = + old_draws.iter().find(|draw| draw.court_participant == juror_2).unwrap().slashable; + let free_juror_3_after = Balances::free_balance(juror_3); + let slashable_juror_3 = + old_draws.iter().find(|draw| draw.court_participant == juror_3).unwrap().slashable; + let free_juror_4_after = Balances::free_balance(juror_4); + let slashable_juror_4 = + old_draws.iter().find(|draw| draw.court_participant == juror_4).unwrap().slashable; + let free_juror_5_after = Balances::free_balance(juror_5); + let slashable_juror_5 = + old_draws.iter().find(|draw| draw.court_participant == juror_5).unwrap().slashable; + + let mut total_slashed = 0; + // juror_1 voted for the wrong outcome => slashed + assert_eq!(free_juror_1_before - free_juror_1_after, slashable_juror_1); + total_slashed += slashable_juror_1; + // juror_3 was denounced by juror_0 => slashed + assert_eq!(free_juror_3_before - free_juror_3_after, slashable_juror_3); + total_slashed += slashable_juror_3; + // juror_4 failed to vote => slashed + assert_eq!(free_juror_4_before - free_juror_4_after, slashable_juror_4); + total_slashed += slashable_juror_4; + // juror_5 failed to reveal => slashed + assert_eq!(free_juror_5_before - free_juror_5_after, slashable_juror_5); + total_slashed += slashable_juror_5; + // juror_0 and juror_2 voted for the right outcome => rewarded + let total_winner_stake = slashable_juror_0 + slashable_juror_2; + let juror_0_share = Perquintill::from_rational(slashable_juror_0, total_winner_stake); + assert_eq!(free_juror_0_after, free_juror_0_before + juror_0_share * total_slashed); + let juror_2_share = Perquintill::from_rational(slashable_juror_2, total_winner_stake); + assert_eq!(free_juror_2_after, free_juror_2_before + juror_2_share * total_slashed); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_oracle_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + 1; + run_to_block(report_at); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // Check that nothing got slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_outsider_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + // Check that oracle bond got slashed + check_reserve(&ALICE, 0); + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_correct_disputed_outcome_with_oracle_report() + { + // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + 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(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_with_correct_disputed_outcome_with_oracle_report() + { + // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + 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(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_wrong_disputed_outcome_with_oracle_report() + { + // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + 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(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is not slashed + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before + ValidityBond::get() + OracleBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_wrong_disputed_outcome_with_oracle_report() + { + // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + 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(0) + )); + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is not slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_with_disputed_outcome_with_outsider_report() + { + // Oracle does not report in time, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + + let outsider = CHARLIE; + + let market = MarketCommons::market(&0).unwrap(); + let after_oracle_duration = + end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; + run_to_block(after_oracle_duration); + // CHARLIE is not an Oracle + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(outsider), + 0, + OutcomeReport::Categorical(0) + )); + let outsider_balance_before = Balances::free_balance(outsider); + check_reserve(&outsider, ::OutsiderBond::get()); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(FRED), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + + check_reserve(&outsider, 0); + assert_eq!( + Balances::free_balance(outsider), + outsider_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_market_with_disputed_outcome_with_outsider_report() + { + // Oracle does not report in time, so OracleBond gets slashed on resolution + // NOTE: Bonds are always in ZTG + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + + let outsider = CHARLIE; + + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, OracleBond::get()); + let market = MarketCommons::market(&0).unwrap(); + let after_oracle_duration = + end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1; + run_to_block(after_oracle_duration); + // CHARLIE is not an Oracle + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(outsider), + 0, + OutcomeReport::Categorical(0) + )); + let outsider_balance_before = Balances::free_balance(outsider); + check_reserve(&outsider, ::OutsiderBond::get()); + + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(EVE), 0,)); + // EVE disputes with wrong outcome + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(EVE), + 0, + OutcomeReport::Categorical(1) + )); + assert_ok!(SimpleDisputes::suggest_outcome( + RuntimeOrigin::signed(FRED), + 0, + OutcomeReport::Categorical(0) + )); + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // ValidityBond bond is returned but OracleBond is slashed + assert_eq!(Balances::free_balance(ALICE), alice_balance_before); + + check_reserve(&outsider, 0); + assert_eq!( + Balances::free_balance(outsider), + outsider_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn trusted_market_complete_lifecycle() { + ExtBuilder::default().build().execute_with(|| { + let end = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + Deadlines { + grace_period: 0, + oracle_duration: ::MinOracleDuration::get(), + dispute_duration: Zero::zero(), + }, + gen_metadata(0x99), + MarketCreation::Permissionless, + MarketType::Categorical(3), + None, + ScoringRule::Lmsr, + )); + let market_id = 0; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(FRED), + market_id, + BASE + )); + run_to_block(end); + let outcome = OutcomeReport::Categorical(1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + outcome.clone() + )); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); + assert_eq!(market.resolved_outcome, Some(outcome)); + assert_eq!(market.dispute_mechanism, None); + assert!(market.bonds.oracle.unwrap().is_settled); + assert_eq!(market.bonds.outsider, None); + assert_eq!(market.bonds.dispute, None); + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); + // Ensure that we don't accidentally leave any artifacts. + assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); + assert!(MarketIdsPerReportBlock::::iter().next().is_none()); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + 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(0) + )); + run_to_block(grace_period + market.deadlines.dispute_duration + 1); + check_reserve(&ALICE, 0); + assert_eq!( + Balances::free_balance(ALICE), + alice_balance_before + ValidityBond::get() + OracleBond::get() + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_outsider_report() + { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + reserve_sentinel_amounts(); + let end = 100; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + base_asset, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..100), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr, + )); + let alice_balance_before = Balances::free_balance(ALICE); + check_reserve(&ALICE, ValidityBond::get() + OracleBond::get()); + + let charlie_balance_before = Balances::free_balance(CHARLIE); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + let report_at = grace_period + market.deadlines.oracle_duration + 1; + run_to_block(report_at); + + assert!(market.bonds.outsider.is_none()); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!( + market.bonds.outsider, + Some(Bond::new(CHARLIE, ::OutsiderBond::get())) + ); + check_reserve(&CHARLIE, ::OutsiderBond::get()); + assert_eq!( + Balances::free_balance(CHARLIE), + charlie_balance_before - ::OutsiderBond::get() + ); + let charlie_balance_before = Balances::free_balance(CHARLIE); + + run_blocks(market.deadlines.dispute_duration); + check_reserve(&ALICE, 0); + // Check that validity bond didn't get slashed, but oracle bond did + assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); + + check_reserve(&CHARLIE, 0); + // Check that the outsider gets the OracleBond together with the OutsiderBond + assert_eq!( + Balances::free_balance(CHARLIE), + charlie_balance_before + OracleBond::get() + ::OutsiderBond::get() + ); + let market = MarketCommons::market(&0).unwrap(); + assert!(market.bonds.outsider.unwrap().is_settled); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs new file mode 100644 index 000000000..a76618a4d --- /dev/null +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -0,0 +1,180 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +use zeitgeist_primitives::types::{OutcomeReport, ScalarPosition}; + +// TODO(#1239) MarketIsNotResolved +// TODO(#1239) NoWinningBalance +// TODO(#1239) MarketDoesNotExist + +#[test] +fn it_allows_to_redeem_shares() { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, CENT)); + 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(1) + )); + run_blocks(market.deadlines.dispute_duration); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Resolved); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + let bal = Balances::free_balance(CHARLIE); + assert_eq!(bal, 1_000 * BASE); + System::assert_last_event( + Event::TokensRedeemed(0, Asset::CategoricalOutcome(0, 1), CENT, CENT, CHARLIE).into(), + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(ScoringRule::Parimutuel; "parimutuel")] +fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule) { + let test = |base_asset: AssetOf| { + let end = 2; + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + scoring_rule, + ); + + assert_ok!(MarketCommons::mutate_market(&0, |market_inner| { + market_inner.status = MarketStatus::Resolved; + Ok(()) + })); + + assert_noop!( + PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0), + Error::::InvalidResolutionMechanism + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn scalar_market_correctly_resolves_on_out_of_range_outcomes_below_threshold() { + let test = |base_asset: AssetOf| { + scalar_market_correctly_resolves_common(base_asset, 50); + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1100 * BASE); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { + let test = |base_asset: AssetOf| { + scalar_market_correctly_resolves_common(base_asset, 250); + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 1000 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +// Common code of `scalar_market_correctly_resolves_*` +fn scalar_market_correctly_resolves_common(base_asset: AssetOf, reported_value: u128) { + let end = 100; + simple_create_scalar_market( + base_asset, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); + assert_ok!(Tokens::transfer( + RuntimeOrigin::signed(CHARLIE), + EVE, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 100 * BASE + )); + // (Eve now has 100 SHORT, Charlie has 100 LONG) + + 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::Scalar(reported_value) + )); + let market_after_report = MarketCommons::market(&0).unwrap(); + assert!(market_after_report.report.is_some()); + let report = market_after_report.report.unwrap(); + assert_eq!(report.at, grace_period + 1); + assert_eq!(report.by, BOB); + assert_eq!(report.outcome, OutcomeReport::Scalar(reported_value)); + + run_blocks(market.deadlines.dispute_duration); + let market_after_resolve = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after_resolve.status, MarketStatus::Resolved); + + // Check balances before redeeming (just to make sure that our tests are based on correct + // assumptions)! + assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); + assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); + let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); + for asset in assets.iter() { + assert_eq!(AssetManager::free_balance(*asset, &CHARLIE), 0); + assert_eq!(AssetManager::free_balance(*asset, &EVE), 0); + } +} diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs new file mode 100644 index 000000000..77cd26a85 --- /dev/null +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -0,0 +1,209 @@ +// Copyright 2022-2024 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 super::*; + +use crate::MarketIdsPerCloseBlock; +use zeitgeist_primitives::types::EarlyCloseState; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) NoEarlyCloseScheduled + +#[test] +fn reject_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + System::assert_last_event(Event::MarketEarlyCloseRejected { market_id }.into()); + }); +} + +#[test] +fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + assert_noop!( + PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn reject_early_close_fails_if_state_is_rejected() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + // just to ensure events are emitted + run_blocks(2); + + let market_id = 0; + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + assert_noop!( + PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,), + Error::::InvalidEarlyCloseState + ); + }); +} + +#[test] +fn reject_early_close_resets_to_old_market_period() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + let market_ids_at_new_end = >::get(new_end); + assert_eq!(market_ids_at_new_end, vec![market_id]); + + run_blocks(1); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market_ids_at_new_end = >::get(new_end); + assert!(market_ids_at_new_end.is_empty()); + + let market_ids_at_old_end = >::get(end); + assert_eq!(market_ids_at_old_end, vec![market_id]); + }); +} + +#[test] +fn reject_early_close_settles_bonds() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + run_blocks(1); + + assert_ok!(PredictionMarkets::dispute_early_close(RuntimeOrigin::signed(BOB), market_id,)); + + let reserved_bob = Balances::reserved_balance(BOB); + let reserved_alice = Balances::reserved_balance(ALICE); + let free_bob = Balances::free_balance(BOB); + let free_alice = Balances::free_balance(ALICE); + + assert_ok!(PredictionMarkets::reject_early_close(RuntimeOrigin::signed(SUDO), market_id,)); + + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.early_close.unwrap().state, EarlyCloseState::Rejected); + + let reserved_bob_after = Balances::reserved_balance(BOB); + let reserved_alice_after = Balances::reserved_balance(ALICE); + let free_bob_after = Balances::free_balance(BOB); + let free_alice_after = Balances::free_balance(ALICE); + + assert_eq!( + reserved_alice - reserved_alice_after, + ::CloseEarlyRequestBond::get() + ); + assert_eq!( + reserved_bob - reserved_bob_after, + ::CloseEarlyDisputeBond::get() + ); + // disputant Bob gets the bonds + assert_eq!( + free_bob_after - free_bob, + ::CloseEarlyRequestBond::get() + + ::CloseEarlyDisputeBond::get() + ); + assert_eq!(free_alice_after - free_alice, 0); + }); +} diff --git a/zrml/prediction-markets/src/tests/reject_market.rs b/zrml/prediction-markets/src/tests/reject_market.rs new file mode 100644 index 000000000..38eded944 --- /dev/null +++ b/zrml/prediction-markets/src/tests/reject_market.rs @@ -0,0 +1,244 @@ +// Copyright 2022-2024 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 super::*; + +use crate::{MarketIdsForEdit, MarketIdsPerCloseBlock}; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) Fails if market is not proposed + +#[test] +fn it_allows_the_advisory_origin_to_reject_markets() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 4..6, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::reject_market( + RuntimeOrigin::signed(SUDO), + 0, + reject_reason.clone() + )); + let reject_reason = reject_reason.try_into().expect("BoundedVec conversion failed"); + System::assert_has_event(Event::MarketRejected(0, reject_reason).into()); + + assert_noop!( + MarketCommons::market(&0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test] +fn reject_errors_if_reject_reason_is_too_long() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize + 1]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::RejectReasonLengthExceedsMaxRejectReasonLen + ); + }); +} + +#[test] +fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + + let reject_reason = vec![0_u8; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason)); + assert!(MarketIdsForEdit::::contains_key(0)); + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + assert!(!MarketIdsForEdit::::contains_key(0)); + + assert_noop!( + MarketCommons::market(&0), + zrml_market_commons::Error::::MarketDoesNotExist + ); + }); +} + +#[test] +fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { + // NOTE: Bonds are always in ZTG, irrespective of base_asset. + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check + // that the AdvisoryBond gets slashed but the OracleBond gets unreserved. + assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); + assert_ok!(Balances::reserve_named( + &PredictionMarkets::reserve_id(), + &ALICE, + SENTINEL_AMOUNT, + )); + assert_eq!(Balances::free_balance(Treasury::account_id()), 0); + + let balance_free_before_alice = Balances::free_balance(ALICE); + let balance_reserved_before_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + + // AdvisoryBond gets slashed after reject_market + // OracleBond gets unreserved after reject_market + let balance_reserved_after_alice = + Balances::reserved_balance_named(&PredictionMarkets::reserve_id(), &ALICE); + assert_eq!( + balance_reserved_after_alice, + balance_reserved_before_alice + - ::OracleBond::get() + - ::AdvisoryBond::get(), + ); + let balance_free_after_alice = Balances::free_balance(ALICE); + let slash_amount_advisory_bond = ::AdvisoryBondSlashPercentage::get() + .mul_floor(::AdvisoryBond::get()); + let advisory_bond_remains = + ::AdvisoryBond::get() - slash_amount_advisory_bond; + assert_eq!( + balance_free_after_alice, + balance_free_before_alice + + ::OracleBond::get() + + advisory_bond_remains, + ); + + // AdvisoryBond is transferred to the treasury + let balance_treasury_after = Balances::free_balance(Treasury::account_id()); + assert_eq!(balance_treasury_after, slash_amount_advisory_bond); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn reject_market_clears_auto_close_blocks() { + // We don't have to check that reject market clears the cache for opening pools, since Cpmm pools + // can not be deployed on pending advised pools. + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 33..66, + ScoringRule::Lmsr, + ); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 22..66, + ScoringRule::Lmsr, + ); + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 22..33, + ScoringRule::Lmsr, + ); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_ok!(PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason)); + + let auto_close = MarketIdsPerCloseBlock::::get(66); + assert_eq!(auto_close.len(), 1); + assert_eq!(auto_close[0], 1); + }); +} + +#[test] +fn reject_market_fails_on_permissionless_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::InvalidMarketStatus + ); + }); +} + +#[test] +fn reject_market_fails_on_approved_market() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + let reject_reason: Vec = + vec![0; ::MaxRejectReasonLen::get() as usize]; + assert_noop!( + PredictionMarkets::reject_market(RuntimeOrigin::signed(SUDO), 0, reject_reason), + Error::::InvalidMarketStatus + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/report.rs b/zrml/prediction-markets/src/tests/report.rs new file mode 100644 index 000000000..4d28e8cea --- /dev/null +++ b/zrml/prediction-markets/src/tests/report.rs @@ -0,0 +1,419 @@ +// Copyright 2022-2024 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 super::*; + +use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::OutcomeReport}; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) MarketAlreadyReported +// TODO(#1239) Trusted markets resolve immediately +// TODO(#1239) NotAllowedToReport with timestamps +// TODO(#1239) Reports are allowed after the oracle duration +// TODO(#1239) Outsider can't report if they can't pay for the bond + +#[test] +fn it_allows_to_report_the_outcome_of_a_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let market_after = MarketCommons::market(&0).unwrap(); + let report = market_after.report.unwrap(); + assert_eq!(market_after.status, MarketStatus::Reported); + assert_eq!(report.outcome, OutcomeReport::Categorical(1)); + assert_eq!(report.by, market_after.oracle); + + // Reset and report again as approval origin + let _ = MarketCommons::mutate_market(&0, |market| { + market.status = MarketStatus::Closed; + market.report = None; + Ok(()) + }); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(SUDO), + 0, + OutcomeReport::Categorical(1) + )); + }); +} + +#[test] +fn report_fails_before_grace_period_is_over() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + run_to_block(end); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::NotAllowedToReportYet + ); + }); +} + +// TODO(#1239) This test is misnamed - this does NOT ensure that reports outside of the oracle duration are +// not allowed. +#[test] +fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_blocks() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + + assert_noop!( + PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + ), + Error::::ReporterNotOracle + ); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + + let market_after = MarketCommons::market(&0).unwrap(); + let report = market_after.report.unwrap(); + assert_eq!(market_after.status, MarketStatus::Reported); + assert_eq!(report.outcome, OutcomeReport::Categorical(1)); + assert_eq!(report.by, market_after.oracle); + }); +} + +#[test] +fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duration_moment() { + 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::Lmsr + )); + + assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(BOB), 0, CENT)); + + // set the timestamp + let market = MarketCommons::market(&0).unwrap(); + // set the timestamp + + set_timestamp_for_on_initialize(100_000_000); + run_to_block(2); // Trigger `on_initialize`; must be at least block #2. + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + Timestamp::set_timestamp(100_000_000 + grace_period); + + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(EVE), 0, OutcomeReport::Categorical(1)), + Error::::ReporterNotOracle + ); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + }); +} + +// TODO(#1239) Use test_case! +#[test] +fn report_fails_on_mismatched_outcome_for_categorical_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Scalar(123)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn report_fails_on_out_of_range_outcome_for_categorical_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(2)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn report_fails_on_mismatched_outcome_for_scalar_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_scalar_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + let market = MarketCommons::market(&0).unwrap(); + let grace_period = end + market.deadlines.grace_period; + run_to_block(grace_period + 1); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(0)), + Error::::OutcomeMismatch, + ); + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + assert!(market.report.is_none()); + }); +} + +#[test] +fn it_allows_anyone_to_report_an_unreported_market() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0).unwrap(); + // Just skip to waaaay overdue. + run_to_block(end + market.deadlines.grace_period + market.deadlines.oracle_duration + 1); + + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(ALICE), // alice reports her own market now + 0, + OutcomeReport::Categorical(1), + )); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Reported); + assert_eq!(market.report.unwrap().by, ALICE); + // but oracle was bob + assert_eq!(market.oracle, BOB); + + // make sure it still resolves + run_to_block( + frame_system::Pallet::::block_number() + market.deadlines.dispute_duration, + ); + + let market_after = MarketCommons::market(&0).unwrap(); + assert_eq!(market_after.status, MarketStatus::Resolved); + }); +} + +// TODO(#1239) Use `test_case` +#[test] +fn report_fails_on_market_state_proposed() { + 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::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_on_market_state_closed_for_advised_market() { + 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::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_on_market_state_active() { + 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::Lmsr + )); + 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(|| { + 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::Advised, + MarketType::Categorical(2), + Some(MarketDisputeMechanism::SimpleDisputes), + ScoringRule::Lmsr + )); + let _ = MarketCommons::mutate_market(&0, |market| { + market.status = MarketStatus::Resolved; + Ok(()) + }); + assert_noop!( + PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), + Error::::MarketIsNotClosed, + ); + }); +} + +#[test] +fn report_fails_if_reporter_is_not_the_oracle() { + 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::Lmsr + )); + let market = MarketCommons::market(&0).unwrap(); + set_timestamp_for_on_initialize(100_000_000); + // Trigger hooks which close the market. + run_to_block(2); + let grace_period: u64 = market.deadlines.grace_period * MILLISECS_PER_BLOCK as u64; + set_timestamp_for_on_initialize(100_000_000 + grace_period + MILLISECS_PER_BLOCK as u64); + assert_noop!( + PredictionMarkets::report( + RuntimeOrigin::signed(CHARLIE), + 0, + OutcomeReport::Categorical(1) + ), + Error::::ReporterNotOracle, + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/request_edit.rs b/zrml/prediction-markets/src/tests/request_edit.rs new file mode 100644 index 000000000..8851e82de --- /dev/null +++ b/zrml/prediction-markets/src/tests/request_edit.rs @@ -0,0 +1,111 @@ +// Copyright 2022-2024 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 super::*; +use crate::MarketIdsForEdit; +use sp_runtime::DispatchError; + +// TODO(#1239) request_edit fails if market is not proposed +// TODO(#1239) request_edit fails if edit already in progress + +#[test] +fn it_allows_request_edit_origin_to_request_edits_for_markets() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason.clone()), + DispatchError::BadOrigin + ); + + // Now it should work from SUDO + assert_ok!(PredictionMarkets::request_edit( + RuntimeOrigin::signed(SUDO), + 0, + edit_reason.clone() + )); + System::assert_last_event( + Event::MarketRequestedEdit( + 0, + edit_reason.try_into().expect("Conversion to BoundedVec failed"), + ) + .into(), + ); + + assert!(MarketIdsForEdit::::contains_key(0)); + }); +} + +#[test] +fn request_edit_fails_on_bad_origin() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 2..4, + ScoringRule::Lmsr, + ); + + // make sure it's in status proposed + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize]; + // Make sure it fails from the random joe + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(BOB), 0, edit_reason), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn edit_request_fails_if_edit_reason_is_too_long() { + ExtBuilder::default().build().execute_with(|| { + // Creates an advised market. + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Advised, + 0..2, + ScoringRule::Lmsr, + ); + + let market = MarketCommons::market(&0); + assert_eq!(market.unwrap().status, MarketStatus::Proposed); + + let edit_reason = vec![0_u8; ::MaxEditReasonLen::get() as usize + 1]; + + assert_noop!( + PredictionMarkets::request_edit(RuntimeOrigin::signed(SUDO), 0, edit_reason), + Error::::EditReasonLengthExceedsMaxEditReasonLen + ); + }); +} diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs new file mode 100644 index 000000000..cb8d83145 --- /dev/null +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -0,0 +1,339 @@ +// Copyright 2022-2024 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 super::*; + +use crate::{MarketIdsPerCloseBlock, MarketIdsPerCloseTimeFrame}; +use zeitgeist_primitives::{ + constants::MILLISECS_PER_BLOCK, + types::{EarlyClose, EarlyCloseState}, +}; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) RequesterNotCreator +// TODO(#1239) MarketIsNotActive +// TODO(#1239) OnlyAuthorizedCanScheduleEarlyClose +// TODO(#1239) Correct repatriations +// TODO(#1239) reserve_named failure + +#[test] +fn schedule_early_close_emits_event() { + ExtBuilder::default().build().execute_with(|| { + let end = 100; + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..end, + ScoringRule::Lmsr, + ); + + let market_id = 0; + + assert_ok!(PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id)); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + assert!(new_end < end); + + let new_period = MarketPeriod::Block(0..new_end); + System::assert_last_event( + Event::MarketEarlyCloseScheduled { + market_id, + new_period: new_period.clone(), + state: EarlyCloseState::ScheduledAsOther, + } + .into(), + ); + }); +} + +#[test] +fn sudo_schedule_early_close_at_block_works() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().next().unwrap(); + assert_eq!(market_ids_to_close.0, end); + assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyProtectionBlockPeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the old one without a market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end); + assert!(first.1.clone().into_inner().is_empty()); + + // The second entry is the new one with the market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, new_end); + assert_eq!(second.1.clone().into_inner(), vec![market_id]); + + run_to_block(new_end + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} + +#[test] +fn sudo_schedule_early_close_at_timeframe_works() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + run_blocks(start_block); + let start = >::now(); + + let end = start + (42 * MILLISECS_PER_BLOCK) as u64; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 1); + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert_eq!(first.1.clone().into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + assert_ok!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(SUDO), market_id,) + ); + + let now = >::now(); + let new_end = now + ::CloseEarlyProtectionTimeFramePeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Timestamp(start..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsOther, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the new one with the market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, new_end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert_eq!(first.1.clone().into_inner(), vec![market_id]); + + // The second entry is the old one without a market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, end.saturating_div(MILLISECS_PER_BLOCK.into())); + assert!(second.1.clone().into_inner().is_empty()); + + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64 + new_end); + run_to_block(start_block + new_end.saturating_div(MILLISECS_PER_BLOCK.into()) + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} + +#[test] +fn schedule_early_close_block_fails_if_early_close_request_too_late() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + run_to_block(end - 1); + + let market_id = 0; + assert_noop!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), + Error::::EarlyCloseRequestTooLate + ); + }); +} + +#[test] +fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { + ExtBuilder::default().build().execute_with(|| { + let start_block = 7; + set_timestamp_for_on_initialize(start_block * MILLISECS_PER_BLOCK as u64); + run_blocks(start_block); + let start = >::now(); + let end = start + (42 * MILLISECS_PER_BLOCK) as u64; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Timestamp(start..end), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + run_to_block(end.saturating_div(MILLISECS_PER_BLOCK.into()) - 1); + set_timestamp_for_on_initialize(end - MILLISECS_PER_BLOCK as u64); + + let market_id = 0; + assert_noop!( + PredictionMarkets::schedule_early_close(RuntimeOrigin::signed(ALICE), market_id,), + Error::::EarlyCloseRequestTooLate + ); + }); +} + +#[test] +fn schedule_early_close_as_market_creator_works() { + 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(::MinCategories::get()), + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr + )); + + let market_id = 0; + let market = MarketCommons::market(&market_id).unwrap(); + let old_market_period = market.period; + assert_eq!(market.status, MarketStatus::Active); + let market_ids_to_close = >::iter().next().unwrap(); + assert_eq!(market_ids_to_close.0, end); + assert_eq!(market_ids_to_close.1.into_inner(), vec![market_id]); + assert!(market.early_close.is_none()); + + let reserved_balance_alice = Balances::reserved_balance(ALICE); + + assert_ok!(PredictionMarkets::schedule_early_close( + RuntimeOrigin::signed(ALICE), + market_id, + )); + + let reserved_balance_alice_after = Balances::reserved_balance(ALICE); + assert_eq!( + reserved_balance_alice_after - reserved_balance_alice, + ::CloseEarlyRequestBond::get() + ); + + let now = >::block_number(); + let new_end = now + ::CloseEarlyBlockPeriod::get(); + assert!(new_end < end); + + let market = MarketCommons::market(&market_id).unwrap(); + let new_period = MarketPeriod::Block(0..new_end); + assert_eq!( + market.early_close.unwrap(), + EarlyClose { + old: old_market_period, + new: new_period, + state: EarlyCloseState::ScheduledAsMarketCreator, + } + ); + + let market_ids_to_close = >::iter().collect::>(); + assert_eq!(market_ids_to_close.len(), 2); + + // The first entry is the old one without a market id inside. + let first = market_ids_to_close.first().unwrap(); + assert_eq!(first.0, end); + assert!(first.1.clone().into_inner().is_empty()); + + // The second entry is the new one with the market id inside. + let second = market_ids_to_close.last().unwrap(); + assert_eq!(second.0, new_end); + assert_eq!(second.1.clone().into_inner(), vec![market_id]); + + run_to_block(new_end + 1); + + let market = MarketCommons::market(&0).unwrap(); + assert_eq!(market.status, MarketStatus::Closed); + }); +} diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs new file mode 100644 index 000000000..6814481c9 --- /dev/null +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -0,0 +1,142 @@ +// Copyright 2022-2024 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 super::*; +use test_case::test_case; + +// TODO(#1239) MarketDoesNotExist + +#[test_case(ScoringRule::Lmsr)] +#[test_case(ScoringRule::Orderbook)] +fn sell_complete_set_works(scoring_rule: ScoringRule) { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + scoring_rule, + ); + let market_id = 0; + let buy_amount = 5 * CENT; + let sell_amount = 3 * CENT; + let expected_amount = 2 * CENT; + let who = BOB; + + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + buy_amount + )); + + assert_ok!(PredictionMarkets::sell_complete_set( + RuntimeOrigin::signed(who), + market_id, + sell_amount + )); + + let market = MarketCommons::market(&market_id).unwrap(); + let assets = PredictionMarkets::outcome_assets(market_id, &market); + for asset in assets.iter() { + let bal = AssetManager::free_balance(*asset, &who); + assert_eq!(bal, expected_amount); + } + + let bal = AssetManager::free_balance(base_asset, &who); + assert_eq!(bal, 1_000 * BASE - expected_amount); + + System::assert_last_event(Event::SoldCompleteSet(market_id, sell_amount, who).into()); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test] +fn sell_complete_set_fails_on_zero_amount() { + ExtBuilder::default().build().execute_with(|| { + simple_create_categorical_market( + Asset::Ztg, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 0), + Error::::ZeroAmount + ); + }); +} + +#[test] +fn sell_complete_set_fails_on_insufficient_share_balance() { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + ScoringRule::Lmsr, + ); + let market_id = 0; + let amount = 2 * CENT; + let who = BOB; + assert_ok!(PredictionMarkets::buy_complete_set( + RuntimeOrigin::signed(who), + market_id, + amount + )); + assert_eq!(AssetManager::slash(Asset::CategoricalOutcome(market_id, 1), &who, 1), 0); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(who), market_id, amount), + Error::::InsufficientShareBalance + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} + +#[test_case(ScoringRule::Parimutuel; "parimutuel")] +fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { + let test = |base_asset: AssetOf| { + simple_create_categorical_market( + base_asset, + MarketCreation::Permissionless, + 0..2, + scoring_rule, + ); + assert_noop!( + PredictionMarkets::sell_complete_set(RuntimeOrigin::signed(BOB), 0, 2 * CENT), + Error::::InvalidScoringRule + ); + }; + ExtBuilder::default().build().execute_with(|| { + test(Asset::Ztg); + }); + #[cfg(feature = "parachain")] + ExtBuilder::default().build().execute_with(|| { + test(Asset::ForeignAsset(100)); + }); +} diff --git a/zrml/prediction-markets/src/tests/start_global_dispute.rs b/zrml/prediction-markets/src/tests/start_global_dispute.rs new file mode 100644 index 000000000..2b84d8ed7 --- /dev/null +++ b/zrml/prediction-markets/src/tests/start_global_dispute.rs @@ -0,0 +1,71 @@ +// Copyright 2022-2024 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 super::*; + +use zeitgeist_primitives::types::OutcomeReport; + +// TODO(#1239) MarketDoesNotExist +// TODO(#1239) NoDisputeMechanism +// TODO(#1239) InvalidMarketStatus +// TODO(#1239) GlobalDisputeExistsAlready +// TODO(#1239) MarketIsNotReported +// TODO(#1239) MarketDisputeMechanismNotFailed + +#[test] +fn start_global_dispute_fails_on_wrong_mdm() { + ExtBuilder::default().build().execute_with(|| { + let end = 2; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + Asset::Ztg, + Perbill::zero(), + BOB, + MarketPeriod::Block(0..2), + get_deadlines(), + gen_metadata(2), + MarketCreation::Permissionless, + MarketType::Categorical(::MaxDisputes::get() + 1), + Some(MarketDisputeMechanism::Authorized), + ScoringRule::Lmsr, + )); + let market_id = MarketCommons::latest_market_id().unwrap(); + + let market = MarketCommons::market(&market_id).unwrap(); + let grace_period = market.deadlines.grace_period; + run_to_block(end + grace_period + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + market_id, + OutcomeReport::Categorical(0) + )); + let dispute_at_0 = end + grace_period + 2; + run_to_block(dispute_at_0); + + // only one dispute allowed for authorized mdm + assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), market_id,)); + run_blocks(1); + let market = MarketCommons::market(&market_id).unwrap(); + assert_eq!(market.status, MarketStatus::Disputed); + + assert_noop!( + PredictionMarkets::start_global_dispute(RuntimeOrigin::signed(CHARLIE), market_id), + Error::::InvalidDisputeMechanism + ); + }); +} From 8ee0d1a4e00c4485a809e94f2117eff7a74feacc Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 24 Jan 2024 20:20:17 +0530 Subject: [PATCH 033/104] Improve fee payment management (#1246) * Improve fee payment management * Make code compileable * Do not remit TX fees for redeem_shares Co-authored-by: Malte Kliemann --------- Co-authored-by: Malte Kliemann Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zrml/prediction-markets/src/lib.rs | 46 +++++++++++++----------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index d2834b23d..fd5766af5 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -340,7 +340,7 @@ 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()), Pays::No))] + #[pallet::weight(T::WeightInfo::admin_move_market_to_closed(CacheSize::get()))] #[transactional] pub fn admin_move_market_to_closed( origin: OriginFor, @@ -364,7 +364,7 @@ mod pallet { /// Complexity: `O(n + m)`, where `n` is the number of market ids /// per dispute / report block, m is the number of disputes. #[pallet::call_index(2)] - #[pallet::weight(( + #[pallet::weight( T::WeightInfo::admin_move_market_to_resolved_scalar_reported(CacheSize::get()) .max( T::WeightInfo::admin_move_market_to_resolved_categorical_reported(CacheSize::get()) @@ -372,9 +372,8 @@ mod pallet { T::WeightInfo::admin_move_market_to_resolved_scalar_disputed(CacheSize::get()) ).max( T::WeightInfo::admin_move_market_to_resolved_categorical_disputed(CacheSize::get()) - ), - Pays::No, - ))] + ) + )] #[transactional] pub fn admin_move_market_to_resolved( origin: OriginFor, @@ -425,7 +424,7 @@ mod pallet { /// /// Complexity: `O(1)` #[pallet::call_index(3)] - #[pallet::weight((T::WeightInfo::approve_market(), Pays::No))] + #[pallet::weight(T::WeightInfo::approve_market())] #[transactional] pub fn approve_market( origin: OriginFor, @@ -448,7 +447,8 @@ mod pallet { 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()), Pays::No).into()) + let default_weight: Option = None; + Ok((default_weight, Pays::No).into()) } /// Request an edit to a proposed market. @@ -464,16 +464,15 @@ mod pallet { /// /// Complexity: `O(edit_reason.len())` #[pallet::call_index(4)] - #[pallet::weight(( - T::WeightInfo::request_edit(edit_reason.len() as u32), - Pays::No, - ))] + #[pallet::weight( + T::WeightInfo::request_edit(edit_reason.len() as u32) + )] #[transactional] pub fn request_edit( origin: OriginFor, #[pallet::compact] market_id: MarketIdOf, edit_reason: Vec, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { T::RequestEditOrigin::ensure_origin(origin)?; let edit_reason: EditReason = edit_reason .try_into() @@ -489,7 +488,8 @@ mod pallet { } })?; Self::deposit_event(Event::MarketRequestedEdit(market_id, edit_reason)); - Ok(()) + let default_weight: Option = None; + Ok((default_weight, Pays::No).into()) } /// Buy a complete set of outcome shares of a market. @@ -833,15 +833,11 @@ mod pallet { } } - // Weight correction - if let OutcomeReport::Categorical(_) = resolved_outcome { - return Ok(Some(T::WeightInfo::redeem_shares_categorical()).into()); - } else if let OutcomeReport::Scalar(_) = resolved_outcome { - return Ok(Some(T::WeightInfo::redeem_shares_scalar()).into()); - } - - let default_weight: Option = None; - Ok((default_weight, Pays::No).into()) + let weight = match resolved_outcome { + OutcomeReport::Categorical(_) => T::WeightInfo::redeem_shares_categorical(), + OutcomeReport::Scalar(_) => T::WeightInfo::redeem_shares_scalar(), + }; + Ok(Some(weight).into()) } /// Rejects a market that is waiting for approval from the advisory committee. @@ -854,10 +850,8 @@ mod pallet { /// and `m` is the number of market ids, /// which close at the same time as the specified market. #[pallet::call_index(13)] - #[pallet::weight(( - T::WeightInfo::reject_market(CacheSize::get(), reject_reason.len() as u32), - Pays::No, - ))] + #[pallet::weight( + T::WeightInfo::reject_market(CacheSize::get(), reject_reason.len() as u32))] #[transactional] pub fn reject_market( origin: OriginFor, From f755cc1ce0de3c37d7995c16ac195bc09f390f50 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 24 Jan 2024 21:41:44 +0530 Subject: [PATCH 034/104] Fix Rust and Discord badge (#1247) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac77d1939..83c30c73a 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ # Zeitgeist: An Evolving Blockchain for Prediction Markets and Futarchy -![Rust](https://github.com/zeitgeistpm/zeitgeist/workflows/Rust/badge.svg) +![Rust](https://github.com/zeitgeistpm/zeitgeist/actions/workflows/rust.yml/badge.svg) [![Codecov](https://codecov.io/gh/zeitgeistpm/zeitgeist/branch/main/graph/badge.svg)](https://codecov.io/gh/zeitgeistpm/zeitgeist) -[![Discord](https://img.shields.io/badge/discord-https%3A%2F%2Fdiscord.gg%2FMD3TbH3ctv-purple)](https://discord.gg/MD3TbH3ctv) +[![Discord](https://img.shields.io/badge/discord-https%3A%2F%2Fdiscord.gg%2FMD3TbH3ctv-purple)](https://discord.gg/XhAcFWYUej) [![Telegram](https://img.shields.io/badge/telegram-https%3A%2F%2Ft.me%2Fzeitgeist__official-blue)](https://t.me/zeitgeist_official) Zeitgeist is a decentralized network for creating, betting on, and resolving From 235f3415be0fe8c69eb5d3d20aef8a94d1c8990a Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Fri, 9 Feb 2024 14:05:52 +0100 Subject: [PATCH 035/104] Fix neo-swaps doc strings (#1250) * Improve `SellExecuted` documentation * Clean up math in comments and doc strings * Fix formatting --- zrml/neo-swaps/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index ed0a3f30a..6b17e91a1 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -126,8 +126,8 @@ mod pallet { type WeightInfo: WeightInfoZeitgeist; - /// The maximum allowed liquidity tree depth per pool. Each pool can support `2^(depth + 1) - /// - 1` liquidity providers. **Must** be less than 16. + /// The maximum allowed liquidity tree depth per pool. Each pool can support + /// `2^(depth + 1) - 1` liquidity providers. **Must** be less than 16. #[pallet::constant] type MaxLiquidityTreeDepth: Get; @@ -164,7 +164,8 @@ mod pallet { external_fee_amount: BalanceOf, }, /// Informant sold a position. `amount_out` is the amount of collateral received by `who`, - /// including swap and external fees. + /// with swap and external fees not yet deducted. The actual amount received is + /// `amount_out - swap_fee_amount - external_fee_amount`. SellExecuted { who: T::AccountId, market_id: MarketIdOf, From 20358a9b6b179398a40d4457cf3cafb64493d453 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 9 Feb 2024 14:10:12 +0100 Subject: [PATCH 036/104] Adjust style guide (loops) and add unreachable macro (#1252) * Adjust style guide (loops) and add unreachable macro * Add macros module to lib.rs * Update docs/STYLE_GUIDE.md Co-authored-by: Malte Kliemann * Move macro to zeitgeist-macros and add examples --------- Co-authored-by: Malte Kliemann --- docs/STYLE_GUIDE.md | 22 +++++++++++++---- macros/src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index 085fecad4..4974fb753 100644 --- a/docs/STYLE_GUIDE.md +++ b/docs/STYLE_GUIDE.md @@ -120,13 +120,13 @@ duplicating documentation. - 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. +- Prefer `for` loops over `while` loops. All loops (of any kind) 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. +- 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. @@ -156,3 +156,17 @@ duplicating documentation. - 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. +- If possible, test unreachable code and states thought to be impossible using + the following schema: + + ```rust + // In code logic + zeitgeist_macros::unreachable_non_terminating!(condition, log_target, message) + ``` + + ```rust + // In test + #[test] + #[should_panic(expected = message)] + // Cause assertion + ``` diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 783c292e7..2534ff00f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -32,3 +32,61 @@ macro_rules! create_b_tree_map { [$(($key, $value),)*].iter().cloned().collect::>() } } + +/// This macro does ensure that a condition `$condition` is met, and if it is not met +/// it will log a message `$message` with optional message arguments `message_args` to +/// an optional log target `$log_target`, cause an assertion in a test environment +/// and execute some optional extra code. +/// +/// ```ignore +/// // Examples: +/// unreachable_non_terminating!(a == b, "a does not equal b"); +/// unreachable_non_terminating!(a == b, log_target, "a does not equal b"); +/// unreachable_non_terminating!(a == b, "{:?} != {:?}", a, b); +/// unreachable_non_terminating!(a == b, log_target, "{:?} != {:?}", a, b); +/// ``` +#[macro_export] +macro_rules! unreachable_non_terminating { + ($condition: expr, $message: literal, $($message_args: tt)*) => { + let message = format!($message, $($message_args)*); + + #[cfg(test)] + assert!($condition, "{}", message); + + if !$condition { + log::warn!("{}", message); + } + }; + ($condition: expr, $log_target: ident, $message: literal, $($message_args: tt)*) => { + let message = format!($message, $($message_args)*); + + #[cfg(test)] + assert!($condition, "{}", message); + + if !$condition { + log::warn!(target: $log_target, "{}", message); + } + }; + ($condition: expr, $extra_code: expr, $message: literal, $($message_args: tt)*) => { + let message = format!($message, $($message_args)*); + + #[cfg(test)] + assert!($condition, "{}", message); + + if !$condition { + log::warn!("{}", message); + $extra_code; + } + }; + ($condition: expr, $log_target: ident, $extra_code: expr, $message: literal, $($message_args: tt)*) => { + let message = format!($message, $($message_args)*); + + #[cfg(test)] + assert!($condition, "{}", message); + + if !$condition { + log::warn!(target: $log_target, "{}", message); + $extra_code; + } + }; +} From 3b486c862b2ed0ce7a038e8b8a45a07381d9c743 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 13 Feb 2024 14:29:59 +0100 Subject: [PATCH 037/104] Merge Old* and New* asset variants --- primitives/src/assets/all_assets.rs | 42 +++---------- primitives/src/assets/currencies.rs | 18 +++--- primitives/src/assets/market_assets.rs | 40 +++--------- primitives/src/assets/tests/conversion.rs | 72 +++++----------------- primitives/src/assets/tests/scale_codec.rs | 36 +++-------- 5 files changed, 50 insertions(+), 158 deletions(-) diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index feff1d2cb..7145da0a0 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -73,50 +73,26 @@ pub enum Asset { #[codec(index = 6)] ParimutuelShare(MI, CategoryIndex), - // "New" outcomes will replace the previous outcome types after the lazy - // migration completed #[codec(index = 7)] - NewCategoricalOutcome(#[codec(compact)] MI, #[codec(compact)] CategoryIndex), - - #[codec(index = 9)] - NewScalarOutcome(#[codec(compact)] MI, ScalarPosition), - - #[codec(index = 10)] - NewParimutuelShare(#[codec(compact)] MI, #[codec(compact)] CategoryIndex), - - #[codec(index = 11)] - NewPoolShare(#[codec(compact)] PoolId), - - #[codec(index = 12)] CampaignAssetClass(#[codec(compact)] CampaignAssetId), - #[codec(index = 13)] + #[codec(index = 8)] CustomAssetClass(#[codec(compact)] CustomAssetId), } impl From> for Asset { fn from(value: MarketAssetClass) -> Self { match value { - MarketAssetClass::::OldCategoricalOutcome(market_id, cat_id) => { - Self::CategoricalOutcome(market_id, cat_id) - } - MarketAssetClass::::OldScalarOutcome(market_id, scalar_pos) => { - Self::ScalarOutcome(market_id, scalar_pos) - } - MarketAssetClass::::OldParimutuelShare(market_id, cat_id) => { - Self::ParimutuelShare(market_id, cat_id) - } - MarketAssetClass::::OldPoolShare(pool_id) => Self::PoolShare(pool_id), MarketAssetClass::::CategoricalOutcome(market_id, cat_id) => { - Self::NewCategoricalOutcome(market_id, cat_id) + Self::CategoricalOutcome(market_id, cat_id) } MarketAssetClass::::ScalarOutcome(market_id, scalar_pos) => { - Self::NewScalarOutcome(market_id, scalar_pos) + Self::ScalarOutcome(market_id, scalar_pos) } MarketAssetClass::::ParimutuelShare(market_id, cat_id) => { - Self::NewParimutuelShare(market_id, cat_id) + Self::ParimutuelShare(market_id, cat_id) } - MarketAssetClass::::PoolShare(pool_id) => Self::NewPoolShare(pool_id), + MarketAssetClass::::PoolShare(pool_id) => Self::PoolShare(pool_id), } } } @@ -136,16 +112,16 @@ impl From for Asset { impl From> for Asset { fn from(value: CurrencyClass) -> Self { match value { - CurrencyClass::::OldCategoricalOutcome(market_id, cat_id) => { + CurrencyClass::::CategoricalOutcome(market_id, cat_id) => { Self::CategoricalOutcome(market_id, cat_id) } - CurrencyClass::::OldScalarOutcome(market_id, scalar_pos) => { + CurrencyClass::::ScalarOutcome(market_id, scalar_pos) => { Self::ScalarOutcome(market_id, scalar_pos) } - CurrencyClass::::OldParimutuelShare(market_id, cat_id) => { + CurrencyClass::::ParimutuelShare(market_id, cat_id) => { Self::ParimutuelShare(market_id, cat_id) } - CurrencyClass::::OldPoolShare(pool_id) => Self::PoolShare(pool_id), + CurrencyClass::::PoolShare(pool_id) => Self::PoolShare(pool_id), CurrencyClass::::ForeignAsset(asset_id) => Self::ForeignAsset(asset_id), } } diff --git a/primitives/src/assets/currencies.rs b/primitives/src/assets/currencies.rs index 8f88865fb..25fac624e 100644 --- a/primitives/src/assets/currencies.rs +++ b/primitives/src/assets/currencies.rs @@ -25,23 +25,23 @@ use super::*; Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, Ord, PartialEq, PartialOrd, TypeInfo, )] pub enum CurrencyClass { - // All "Old" variants will be removed once the lazy migration from + // All Outcome and Share variants will be removed once the lazy migration from // orml-tokens to pallet-assets is complete #[codec(index = 0)] - OldCategoricalOutcome(MI, CategoryIndex), + CategoricalOutcome(MI, CategoryIndex), #[codec(index = 1)] - OldScalarOutcome(MI, ScalarPosition), + ScalarOutcome(MI, ScalarPosition), #[codec(index = 3)] - OldPoolShare(PoolId), + PoolShare(PoolId), // Type can not be compacted as it is already used uncompacted in the storage #[codec(index = 5)] ForeignAsset(u32), #[codec(index = 6)] - OldParimutuelShare(MI, CategoryIndex), + ParimutuelShare(MI, CategoryIndex), } impl Default for CurrencyClass { @@ -56,15 +56,15 @@ impl TryFrom> for CurrencyClass { fn try_from(value: Asset) -> Result { match value { Asset::::CategoricalOutcome(market_id, cat_id) => { - Ok(Self::OldCategoricalOutcome(market_id, cat_id)) + Ok(Self::CategoricalOutcome(market_id, cat_id)) } Asset::::ScalarOutcome(market_id, scalar_pos) => { - Ok(Self::OldScalarOutcome(market_id, scalar_pos)) + Ok(Self::ScalarOutcome(market_id, scalar_pos)) } Asset::::ParimutuelShare(market_id, cat_id) => { - Ok(Self::OldParimutuelShare(market_id, cat_id)) + Ok(Self::ParimutuelShare(market_id, cat_id)) } - Asset::::PoolShare(pool_id) => Ok(Self::OldPoolShare(pool_id)), + Asset::::PoolShare(pool_id) => Ok(Self::PoolShare(pool_id)), Asset::::ForeignAsset(id) => Ok(Self::ForeignAsset(id)), _ => Err(()), } diff --git a/primitives/src/assets/market_assets.rs b/primitives/src/assets/market_assets.rs index 62263c470..0789eaa27 100644 --- a/primitives/src/assets/market_assets.rs +++ b/primitives/src/assets/market_assets.rs @@ -27,31 +27,17 @@ use super::*; #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[derive(Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo)] pub enum MarketAssetClass { - // All "Old" variants will be removed once the lazy migration from - // orml-tokens to pallet-assets is complete #[codec(index = 0)] - OldCategoricalOutcome(MI, CategoryIndex), + CategoricalOutcome(MI, CategoryIndex), #[codec(index = 1)] - OldScalarOutcome(MI, ScalarPosition), + ScalarOutcome(MI, ScalarPosition), #[codec(index = 3)] - OldPoolShare(PoolId), + PoolShare(PoolId), #[codec(index = 6)] - OldParimutuelShare(MI, CategoryIndex), - - #[codec(index = 7)] - CategoricalOutcome(#[codec(compact)] MI, #[codec(compact)] CategoryIndex), - - #[codec(index = 9)] - ScalarOutcome(#[codec(compact)] MI, ScalarPosition), - - #[codec(index = 10)] - ParimutuelShare(#[codec(compact)] MI, #[codec(compact)] CategoryIndex), - - #[codec(index = 11)] - PoolShare(#[codec(compact)] PoolId), + ParimutuelShare(MI, CategoryIndex), } impl TryFrom> for MarketAssetClass { @@ -59,26 +45,16 @@ impl TryFrom> for MarketAssetClass fn try_from(value: Asset) -> Result { match value { - Asset::::NewCategoricalOutcome(market_id, cat_id) => { - Ok(Self::CategoricalOutcome(market_id, cat_id)) - } - Asset::::NewScalarOutcome(market_id, scalar_pos) => { - Ok(Self::ScalarOutcome(market_id, scalar_pos)) - } - Asset::::NewParimutuelShare(market_id, cat_id) => { - Ok(Self::ParimutuelShare(market_id, cat_id)) - } - Asset::::NewPoolShare(pool_id) => Ok(Self::PoolShare(pool_id)), Asset::::CategoricalOutcome(market_id, cat_id) => { - Ok(Self::OldCategoricalOutcome(market_id, cat_id)) + Ok(Self::CategoricalOutcome(market_id, cat_id)) } Asset::::ScalarOutcome(market_id, scalar_pos) => { - Ok(Self::OldScalarOutcome(market_id, scalar_pos)) + Ok(Self::ScalarOutcome(market_id, scalar_pos)) } Asset::::ParimutuelShare(market_id, cat_id) => { - Ok(Self::OldParimutuelShare(market_id, cat_id)) + Ok(Self::ParimutuelShare(market_id, cat_id)) } - Asset::::PoolShare(pool_id) => Ok(Self::OldPoolShare(pool_id)), + Asset::::PoolShare(pool_id) => Ok(Self::PoolShare(pool_id)), _ => Err(()), } } diff --git a/primitives/src/assets/tests/conversion.rs b/primitives/src/assets/tests/conversion.rs index 6d6e5bfc6..eaa8c0006 100644 --- a/primitives/src/assets/tests/conversion.rs +++ b/primitives/src/assets/tests/conversion.rs @@ -23,43 +23,23 @@ use test_case::test_case; // Assets <> MarketAssetClass #[test_case( Asset::::CategoricalOutcome(7, 8), - MarketAssetClass::::OldCategoricalOutcome(7, 8); + MarketAssetClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long); + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - MarketAssetClass::::OldPoolShare(7); + MarketAssetClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - MarketAssetClass::::OldParimutuelShare(7, 8); - "parimutuel_share" -)] -#[test_case( - Asset::::NewCategoricalOutcome(7, 8), - MarketAssetClass::::CategoricalOutcome(7, 8); - "new_categorical_outcome" -)] -#[test_case( - Asset::::NewScalarOutcome(7, ScalarPosition::Long), - MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); - "new_calar_outcome" -)] -#[test_case( - Asset::::NewPoolShare(7), - MarketAssetClass::::PoolShare(7); - "new_pool_share" -)] -#[test_case( - Asset::::NewParimutuelShare(7, 8), MarketAssetClass::::ParimutuelShare(7, 8); - "new_parimutuel_share" + "parimutuel_share" )] fn from_all_assets_to_market_assets( old_asset: Asset, @@ -70,45 +50,25 @@ fn from_all_assets_to_market_assets( } #[test_case( - MarketAssetClass::::OldCategoricalOutcome(7, 8), + MarketAssetClass::::CategoricalOutcome(7, 8), Asset::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( - MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long), Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( - MarketAssetClass::::OldPoolShare(7), + MarketAssetClass::::PoolShare(7), Asset::::PoolShare(7); "pool_share" )] #[test_case( - MarketAssetClass::::OldParimutuelShare(7, 8), + MarketAssetClass::::ParimutuelShare(7, 8), Asset::::ParimutuelShare(7, 8); "parimutuel_share" )] -#[test_case( - MarketAssetClass::::CategoricalOutcome(7, 8), - Asset::::NewCategoricalOutcome(7, 8); - "new_categorical_outcome" -)] -#[test_case( - MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long), - Asset::::NewScalarOutcome(7, ScalarPosition::Long); - "new_calar_outcome" -)] -#[test_case( - MarketAssetClass::::PoolShare(7), - Asset::::NewPoolShare(7); - "new_pool_share" -)] -#[test_case( - MarketAssetClass::::ParimutuelShare(7, 8), - Asset::::NewParimutuelShare(7, 8); - "new_parimutuel_share" -)] fn from_market_assets_to_all_assets( old_asset: MarketAssetClass, new_asset: Asset, @@ -120,22 +80,22 @@ fn from_market_assets_to_all_assets( // Assets <> CurrencyClass #[test_case( Asset::::CategoricalOutcome(7, 8), - CurrencyClass::::OldCategoricalOutcome(7, 8); + CurrencyClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long); + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - CurrencyClass::::OldPoolShare(7); + CurrencyClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - CurrencyClass::::OldParimutuelShare(7, 8); + CurrencyClass::::ParimutuelShare(7, 8); "parimutuel_share" )] #[test_case( @@ -149,22 +109,22 @@ fn from_all_assets_to_currencies(old_asset: Asset, new_asset: Currency } #[test_case( - CurrencyClass::::OldCategoricalOutcome(7, 8), + CurrencyClass::::CategoricalOutcome(7, 8), Asset::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( - CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long), + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long), Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( - CurrencyClass::::OldPoolShare(7), + CurrencyClass::::PoolShare(7), Asset::::PoolShare(7); "pool_share" )] #[test_case( - CurrencyClass::::OldParimutuelShare(7, 8), + CurrencyClass::::ParimutuelShare(7, 8), Asset::::ParimutuelShare(7, 8); "parimutuel_share" )] diff --git a/primitives/src/assets/tests/scale_codec.rs b/primitives/src/assets/tests/scale_codec.rs index 9c6d775d9..49017e375 100644 --- a/primitives/src/assets/tests/scale_codec.rs +++ b/primitives/src/assets/tests/scale_codec.rs @@ -23,43 +23,23 @@ use test_case::test_case; // Assets <> MarketAssetClass #[test_case( Asset::::CategoricalOutcome(7, 8), - MarketAssetClass::::OldCategoricalOutcome(7, 8); + MarketAssetClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long); + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - MarketAssetClass::::OldPoolShare(7); + MarketAssetClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - MarketAssetClass::::OldParimutuelShare(7, 8); - "parimutuel_share" -)] -#[test_case( - Asset::::NewCategoricalOutcome(7, 8), - MarketAssetClass::::CategoricalOutcome(7, 8); - "new_categorical_outcome" -)] -#[test_case( - Asset::::NewScalarOutcome(7, ScalarPosition::Long), - MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); - "new_calar_outcome" -)] -#[test_case( - Asset::::NewPoolShare(7), - MarketAssetClass::::PoolShare(7); - "new_pool_share" -)] -#[test_case( - Asset::::NewParimutuelShare(7, 8), MarketAssetClass::::ParimutuelShare(7, 8); - "new_parimutuel_share" + "parimutuel_share" )] fn index_matching_works_for_market_assets( old_asset: Asset, @@ -74,22 +54,22 @@ fn index_matching_works_for_market_assets( // Assets <> CurrencyClass #[test_case( Asset::::CategoricalOutcome(7, 8), - CurrencyClass::::OldCategoricalOutcome(7, 8); + CurrencyClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long); + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - CurrencyClass::::OldPoolShare(7); + CurrencyClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - CurrencyClass::::OldParimutuelShare(7, 8); + CurrencyClass::::ParimutuelShare(7, 8); "parimutuel_share" )] #[test_case( From cf2f1f446b22ef941ea253e68105f017d740fa05 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 13 Feb 2024 17:49:33 +0100 Subject: [PATCH 038/104] Partially integrate lazy migration routing --- zrml/asset-router/src/macros.rs | 21 ++++++++++++++------ zrml/asset-router/src/mock.rs | 2 +- zrml/asset-router/src/pallet_impl/inspect.rs | 8 ++++++-- zrml/asset-router/src/tests/destroy.rs | 2 +- zrml/asset-router/src/tests/inspect.rs | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/zrml/asset-router/src/macros.rs b/zrml/asset-router/src/macros.rs index 68fef365d..4a8d87849 100644 --- a/zrml/asset-router/src/macros.rs +++ b/zrml/asset-router/src/macros.rs @@ -19,14 +19,23 @@ /// implementation that handles it and finally calls the $method on it. macro_rules! route_call { ($currency_id:expr, $currency_method:ident, $asset_method:ident, $($args:expr),*) => { - if let Ok(currency) = T::CurrencyType::try_from($currency_id) { - Ok(>::$currency_method(currency, $($args),*)) - } else if let Ok(asset) = T::MarketAssetType::try_from($currency_id) { - Ok(T::MarketAssets::$asset_method(asset, $($args),*)) + if let Ok(asset) = T::MarketAssetType::try_from($currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + Ok(T::MarketAssets::$asset_method(asset, $($args),*)) + } else { + if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + Ok(>::$currency_method(currency, $($args),*)) + } else { + Ok(T::MarketAssets::$asset_method(asset, $($args),*)) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from($currency_id) { Ok(T::CampaignAssets::$asset_method(asset, $($args),*)) } else if let Ok(asset) = T::CustomAssetType::try_from($currency_id) { Ok(T::CustomAssets::$asset_method(asset, $($args),*)) + } else if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + Ok(>::$currency_method(currency, $($args),*)) } else { Err(Error::::UnknownAsset) } @@ -46,8 +55,8 @@ macro_rules! only_currency { }; } -/// This macro delegates a call to one *Asset instance if the asset does not represent a currency, otherwise -/// it returns an error. +/// This macro delegates a call to one *Asset instance if the asset does not represent a currency, +/// otherwise it returns an error. macro_rules! only_asset { ($asset_id:expr, $error:expr, $asset_trait:ident, $asset_method:ident, $($args:expr),*) => { if let Ok(asset) = T::MarketAssetType::try_from($asset_id) { diff --git a/zrml/asset-router/src/mock.rs b/zrml/asset-router/src/mock.rs index 1291b4d6b..7bb6c58ce 100644 --- a/zrml/asset-router/src/mock.rs +++ b/zrml/asset-router/src/mock.rs @@ -51,7 +51,7 @@ pub(super) const CAMPAIGN_ASSET: Assets = Assets::CampaignAssetClass(0); pub(super) const CAMPAIGN_ASSET_INTERNAL: CampaignAssetClass = CampaignAssetClass(0); pub(super) const CUSTOM_ASSET: Assets = Assets::CustomAssetClass(0); pub(super) const CUSTOM_ASSET_INTERNAL: CustomAssetClass = CustomAssetClass(0); -pub(super) const MARKET_ASSET: Assets = Assets::NewCategoricalOutcome(7, 8); +pub(super) const MARKET_ASSET: Assets = Assets::CategoricalOutcome(7, 8); pub(super) const MARKET_ASSET_INTERNAL: MarketAsset = MarketAsset::CategoricalOutcome(7, 8); pub(super) const CURRENCY: Assets = Assets::ForeignAsset(0); pub(super) const CURRENCY_INTERNAL: Currencies = Currencies::ForeignAsset(0); diff --git a/zrml/asset-router/src/pallet_impl/inspect.rs b/zrml/asset-router/src/pallet_impl/inspect.rs index 7b0de6537..e4c2f50d7 100644 --- a/zrml/asset-router/src/pallet_impl/inspect.rs +++ b/zrml/asset-router/src/pallet_impl/inspect.rs @@ -109,8 +109,12 @@ impl Inspect for Pallet { } fn asset_exists(asset: Self::AssetId) -> bool { - if T::CurrencyType::try_from(asset).is_ok() { - true + if let Ok(currency) = T::CurrencyType::try_from(asset) { + if T::Currencies::total_issuance(currency) > Zero::zero() { + true + } else { + only_asset!(asset, false, Inspect, asset_exists,) + } } else { only_asset!(asset, false, Inspect, asset_exists,) } diff --git a/zrml/asset-router/src/tests/destroy.rs b/zrml/asset-router/src/tests/destroy.rs index b27aff366..cca618b9b 100644 --- a/zrml/asset-router/src/tests/destroy.rs +++ b/zrml/asset-router/src/tests/destroy.rs @@ -21,12 +21,12 @@ use super::*; use frame_support::traits::tokens::fungibles::Inspect; fn test_helper(asset: Assets, initial_amount: ::Balance) { - assert!(AssetRouter::asset_exists(asset)); assert_ok!(>::deposit( asset, &ALICE, initial_amount )); + assert!(AssetRouter::asset_exists(asset)); assert_ok!(AssetRouter::start_destroy(asset, None)); assert_eq!(AssetRouter::destroy_accounts(asset, 100), Ok(1)); assert_eq!(AssetRouter::destroy_approvals(asset, 100), Ok(1)); diff --git a/zrml/asset-router/src/tests/inspect.rs b/zrml/asset-router/src/tests/inspect.rs index bf8065013..9d8d99a13 100644 --- a/zrml/asset-router/src/tests/inspect.rs +++ b/zrml/asset-router/src/tests/inspect.rs @@ -21,12 +21,12 @@ use super::*; use frame_support::traits::tokens::fungibles::Inspect; fn test_helper(asset: Assets, initial_amount: ::Balance) { - assert!(AssetRouter::asset_exists(asset)); assert_ok!(>::deposit( asset, &ALICE, initial_amount )); + assert!(AssetRouter::asset_exists(asset)); assert_eq!(AssetRouter::total_issuance(asset), initial_amount); assert_eq!(AssetRouter::balance(asset, &ALICE), initial_amount); assert_eq!(AssetRouter::reducible_balance(asset, &ALICE, false), initial_amount); From 79487b968f36259a723852ad9afc9cba908c09f5 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 14 Feb 2024 16:45:34 +0100 Subject: [PATCH 039/104] Integrate lazy migration routing --- zrml/asset-router/src/macros.rs | 16 ++- .../pallet_impl/multi_lockable_currency.rs | 36 +++++- .../src/pallet_impl/multicurrency.rs | 118 +++++++++++++----- .../src/pallet_impl/multicurrency_extended.rs | 65 +++++++--- .../named_multi_reserveable_currency.rs | 63 +++++++++- zrml/asset-router/src/tests/mod.rs | 2 +- .../{multi_currency.rs => multicurrency.rs} | 0 7 files changed, 241 insertions(+), 59 deletions(-) rename zrml/asset-router/src/tests/{multi_currency.rs => multicurrency.rs} (100%) diff --git a/zrml/asset-router/src/macros.rs b/zrml/asset-router/src/macros.rs index 4a8d87849..663c5ca51 100644 --- a/zrml/asset-router/src/macros.rs +++ b/zrml/asset-router/src/macros.rs @@ -46,7 +46,21 @@ macro_rules! route_call { /// it returns an error. macro_rules! only_currency { ($currency_id:expr, $error:expr, $currency_trait:ident, $currency_method:ident, $($args:expr),+) => { - if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from($currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + Self::log_unsupported($currency_id, stringify!($currency_method)); + $error + } else { + if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + >::$currency_method(currency, $($args),+) + } else { + Self::log_unsupported($currency_id, stringify!($currency_method)); + $error + } + } + } + else if let Ok(currency) = T::CurrencyType::try_from($currency_id) { >::$currency_method(currency, $($args),+) } else { Self::log_unsupported($currency_id, stringify!($currency_method)); diff --git a/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs index fb0b08334..6417b53fc 100644 --- a/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs +++ b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs @@ -26,7 +26,17 @@ impl MultiLockableCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + return Err(Error::::Unsupported.into()); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::set_lock( + lock_id, currency, who, amount, + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::set_lock( lock_id, currency, who, amount, ); @@ -41,7 +51,17 @@ impl MultiLockableCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + return Err(Error::::Unsupported.into()); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::extend_lock( + lock_id, currency, who, amount, + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::extend_lock( lock_id, currency, who, amount, ); @@ -55,7 +75,17 @@ impl MultiLockableCurrency for Pallet { currency_id: Self::CurrencyId, who: &T::AccountId, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + return Err(Error::::Unsupported.into()); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::remove_lock( + lock_id, currency, who, + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::remove_lock( lock_id, currency, who, ); diff --git a/zrml/asset-router/src/pallet_impl/multicurrency.rs b/zrml/asset-router/src/pallet_impl/multicurrency.rs index b05987584..abeca9ef8 100644 --- a/zrml/asset-router/src/pallet_impl/multicurrency.rs +++ b/zrml/asset-router/src/pallet_impl/multicurrency.rs @@ -46,14 +46,23 @@ impl MultiCurrency for Pallet { } fn free_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - >::free_balance(currency, who) - } else if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - T::MarketAssets::reducible_balance(asset, who, false) + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + T::MarketAssets::reducible_balance(asset, who, false) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::free_balance(currency, who) + } else { + T::MarketAssets::reducible_balance(asset, who, false) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::reducible_balance(asset, who, false) } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { T::CustomAssets::reducible_balance(asset, who, false) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::free_balance(currency, who) } else { Self::log_unsupported(currency_id, "free_balance"); Self::Balance::zero() @@ -65,18 +74,23 @@ impl MultiCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::ensure_can_withdraw( - currency, who, amount, - ); - } - let withdraw_consequence = if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - T::MarketAssets::can_withdraw(asset, who, amount) + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + T::MarketAssets::can_withdraw(asset, who, amount) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return T::Currencies::ensure_can_withdraw(currency, who, amount); + } else { + T::MarketAssets::can_withdraw(asset, who, amount) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::can_withdraw(asset, who, amount) } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { T::CustomAssets::can_withdraw(asset, who, amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return T::Currencies::ensure_can_withdraw(currency, who, amount); } else { return Err(Error::::UnknownAsset.into()); }; @@ -90,14 +104,23 @@ impl MultiCurrency for Pallet { to: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - >::transfer(currency, from, to, amount) - } else if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::transfer(currency, from, to, amount) + } else { + T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::transfer(asset, from, to, amount, false).map(|_| ()) } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { T::CustomAssets::transfer(asset, from, to, amount, false).map(|_| ()) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::transfer(currency, from, to, amount) } else { Err(Error::::UnknownAsset.into()) } @@ -116,30 +139,50 @@ impl MultiCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - >::withdraw(currency, who, amount) - } else if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - // Resulting balance can be ignored as `burn_from` ensures that the - // requested amount can be burned. - T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + // Resulting balance can be ignored as `burn_from` ensures that the + // requested amount can be burned. + T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::withdraw(currency, who, amount) + } else { + T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::burn_from(asset, who, amount).map(|_| ()) } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { T::CustomAssets::burn_from(asset, who, amount).map(|_| ()) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::withdraw(currency, who, amount) } else { Err(Error::::UnknownAsset.into()) } } fn can_slash(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - >::can_slash(currency, who, value) - } else if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - T::MarketAssets::reducible_balance(asset, who, false) >= value + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + // Resulting balance can be ignored as `burn_from` ensures that the + // requested amount can be burned. + T::MarketAssets::reducible_balance(asset, who, false) >= value + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::can_slash(currency, who, value) + } else { + T::MarketAssets::reducible_balance(asset, who, false) >= value + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::reducible_balance(asset, who, false) >= value } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { T::CustomAssets::reducible_balance(asset, who, false) >= value + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::can_slash(currency, who, value) } else { Self::log_unsupported(currency_id, "can_slash"); false @@ -151,12 +194,23 @@ impl MultiCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - >::slash(currency, who, amount) - } else if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { - T::MarketAssets::slash(asset, who, amount) - .map(|b| amount.saturating_sub(b)) - .unwrap_or_else(|_| amount) + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + // Resulting balance can be ignored as `burn_from` ensures that the + // requested amount can be burned. + T::MarketAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::slash(currency, who, amount) + } else { + T::MarketAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) + } + } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::slash(asset, who, amount) .map(|b| amount.saturating_sub(b)) @@ -165,6 +219,8 @@ impl MultiCurrency for Pallet { T::CustomAssets::slash(asset, who, amount) .map(|b| amount.saturating_sub(b)) .unwrap_or_else(|_| amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::slash(currency, who, amount) } else { Self::log_unsupported(currency_id, "slash"); amount diff --git a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs index fc2bf4f6c..549442704 100644 --- a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs +++ b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs @@ -17,6 +17,35 @@ use crate::pallet::*; +impl Pallet { + fn update_balance_asset( + currency_id: >::CurrencyId, + who: &T::AccountId, + by_amount: >::Amount, + ) -> DispatchResult { + if by_amount.is_zero() { + return Ok(()); + } + + // Ensure that no overflows happen during abs(). + let by_amount_abs = + if by_amount == >::Amount::min_value() { + return Err(Error::::AmountIntoBalanceFailed.into()); + } else { + by_amount.abs() + }; + + let by_balance = + TryInto::<>::Balance>::try_into(by_amount_abs) + .map_err(|_| Error::::AmountIntoBalanceFailed)?; + if by_amount.is_positive() { + Self::deposit(currency_id, who, by_balance) + } else { + Self::withdraw(currency_id, who, by_balance).map(|_| ()) + } + } +} + impl MultiCurrencyExtended for Pallet { type Amount = >::Amount; @@ -25,29 +54,29 @@ impl MultiCurrencyExtended for Pallet { who: &T::AccountId, by_amount: Self::Amount, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::update_balance( - currency, who, by_amount, - ); - } - if by_amount.is_zero() { return Ok(()); } - // Ensure that no overflows happen during abs(). - let by_amount_abs = if by_amount == Self::Amount::min_value() { - return Err(Error::::AmountIntoBalanceFailed.into()); - } else { - by_amount.abs() - }; - - let by_balance = TryInto::::try_into(by_amount_abs) - .map_err(|_| Error::::AmountIntoBalanceFailed)?; - if by_amount.is_positive() { - Self::deposit(currency_id, who, by_balance) + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + Self::update_balance_asset(currency_id, who, by_amount) + } else { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::update_balance(currency, who, by_amount) + } else { + Self::update_balance_asset(currency_id, who, by_amount) + } + } + } else if let Ok(_asset) = T::CampaignAssetType::try_from(currency_id) { + Self::update_balance_asset(currency_id, who, by_amount) + } else if let Ok(_asset) = T::CustomAssetType::try_from(currency_id) { + Self::update_balance_asset(currency_id, who, by_amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::update_balance(currency, who, by_amount) } else { - Self::withdraw(currency_id, who, by_balance).map(|_| ()) + Err(Error::::UnknownAsset.into()) } } } diff --git a/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs b/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs index 131ad4142..b90914b27 100644 --- a/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs +++ b/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs @@ -26,7 +26,18 @@ impl NamedMultiReservableCurrency for Pallet { currency_id: Self::CurrencyId, who: &T::AccountId, ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + Self::log_unsupported(currency_id, "reserved_balance_named"); + return Zero::zero(); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserved_balance_named( + id, currency, who, + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::reserved_balance_named( id, currency, who, ); @@ -42,7 +53,17 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + return Err(Error::::Unsupported.into()); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserve_named( + id, currency, who, value + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::reserve_named( id, currency, who, value, ); @@ -57,7 +78,18 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + Self::log_unsupported(currency_id, "unreserve_named"); + return value; + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::unreserve_named( + id, currency, who, value, + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::unreserve_named( id, currency, who, value, ); @@ -73,7 +105,18 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + Self::log_unsupported(currency_id, "slash_reserved_named"); + return value; + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::slash_reserved_named( + id, currency, who, value + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::slash_reserved_named( id, currency, who, value ); @@ -91,7 +134,17 @@ impl NamedMultiReservableCurrency for Pallet { value: Self::Balance, status: Status, ) -> Result { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + if T::MarketAssets::asset_exists(asset) { + return Err(Error::::Unsupported.into()); + } + + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::repatriate_reserved_named( + id, currency, slashed, beneficiary, value, status + ); + } + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { return >::repatriate_reserved_named( id, currency, slashed, beneficiary, value, status ); diff --git a/zrml/asset-router/src/tests/mod.rs b/zrml/asset-router/src/tests/mod.rs index d59169a37..8028801ae 100644 --- a/zrml/asset-router/src/tests/mod.rs +++ b/zrml/asset-router/src/tests/mod.rs @@ -41,7 +41,7 @@ mod custom_types; mod destroy; mod inspect; mod managed_destroy; -mod multi_currency; mod multi_lockable_currency; mod multi_reservable_currency; +mod multicurrency; mod named_multi_reservable_currency; diff --git a/zrml/asset-router/src/tests/multi_currency.rs b/zrml/asset-router/src/tests/multicurrency.rs similarity index 100% rename from zrml/asset-router/src/tests/multi_currency.rs rename to zrml/asset-router/src/tests/multicurrency.rs From 0268004263396915188200b2b860e888c7d685f5 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 14 Feb 2024 16:55:44 +0100 Subject: [PATCH 040/104] Fix ExistentialDeposit mapping & Satisfy Clippy --- runtime/battery-station/src/parameters.rs | 8 +-- runtime/zeitgeist/src/parameters.rs | 8 +-- .../src/pallet_impl/multicurrency.rs | 52 +++++++------------ .../src/pallet_impl/multicurrency_extended.rs | 8 ++- 4 files changed, 31 insertions(+), 45 deletions(-) diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index e8b301d7b..c4b5ee502 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -508,10 +508,10 @@ parameter_type_with_key! { // Explicit match arms are used to ensure new asset types are respected. pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Currencies::OldCategoricalOutcome(_,_) => ExistentialDeposit::get(), - Currencies::OldParimutuelShare(_,_) => ExistentialDeposit::get(), - Currencies::OldPoolShare(_) => ExistentialDeposit::get(), - Currencies::OldScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::CategoricalOutcome(_,_) => ExistentialDeposit::get(), + Currencies::ParimutuelShare(_,_) => ExistentialDeposit::get(), + Currencies::PoolShare(_) => ExistentialDeposit::get(), + Currencies::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] Currencies::ForeignAsset(id) => { let maybe_metadata = < diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 0e04a06f1..9a5fade32 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -506,10 +506,10 @@ parameter_type_with_key! { // Explicit match arms are used to ensure new asset types are respected. pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Currencies::OldCategoricalOutcome(_,_) => ExistentialDeposit::get(), - Currencies::OldParimutuelShare(_,_) => ExistentialDeposit::get(), - Currencies::OldPoolShare(_) => ExistentialDeposit::get(), - Currencies::OldScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::CategoricalOutcome(_,_) => ExistentialDeposit::get(), + Currencies::ParimutuelShare(_,_) => ExistentialDeposit::get(), + Currencies::PoolShare(_) => ExistentialDeposit::get(), + Currencies::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] Currencies::ForeignAsset(id) => { let maybe_metadata = < diff --git a/zrml/asset-router/src/pallet_impl/multicurrency.rs b/zrml/asset-router/src/pallet_impl/multicurrency.rs index abeca9ef8..77a87d3a8 100644 --- a/zrml/asset-router/src/pallet_impl/multicurrency.rs +++ b/zrml/asset-router/src/pallet_impl/multicurrency.rs @@ -50,12 +50,10 @@ impl MultiCurrency for Pallet { // Route "pre new asset system" market assets to `CurrencyType` if T::MarketAssets::asset_exists(asset) { T::MarketAssets::reducible_balance(asset, who, false) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::free_balance(currency, who) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::free_balance(currency, who) - } else { - T::MarketAssets::reducible_balance(asset, who, false) - } + T::MarketAssets::reducible_balance(asset, who, false) } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::reducible_balance(asset, who, false) @@ -78,12 +76,10 @@ impl MultiCurrency for Pallet { // Route "pre new asset system" market assets to `CurrencyType` if T::MarketAssets::asset_exists(asset) { T::MarketAssets::can_withdraw(asset, who, amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return T::Currencies::ensure_can_withdraw(currency, who, amount); } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return T::Currencies::ensure_can_withdraw(currency, who, amount); - } else { - T::MarketAssets::can_withdraw(asset, who, amount) - } + T::MarketAssets::can_withdraw(asset, who, amount) } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::can_withdraw(asset, who, amount) @@ -108,12 +104,10 @@ impl MultiCurrency for Pallet { // Route "pre new asset system" market assets to `CurrencyType` if T::MarketAssets::asset_exists(asset) { T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::transfer(currency, from, to, amount) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::transfer(currency, from, to, amount) - } else { - T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) - } + T::MarketAssets::transfer(asset, from, to, amount, false).map(|_| ()) } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::transfer(asset, from, to, amount, false).map(|_| ()) @@ -145,12 +139,10 @@ impl MultiCurrency for Pallet { // Resulting balance can be ignored as `burn_from` ensures that the // requested amount can be burned. T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::withdraw(currency, who, amount) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::withdraw(currency, who, amount) - } else { - T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) - } + T::MarketAssets::burn_from(asset, who, amount).map(|_| ()) } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::burn_from(asset, who, amount).map(|_| ()) @@ -170,12 +162,10 @@ impl MultiCurrency for Pallet { // Resulting balance can be ignored as `burn_from` ensures that the // requested amount can be burned. T::MarketAssets::reducible_balance(asset, who, false) >= value + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::can_slash(currency, who, value) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::can_slash(currency, who, value) - } else { - T::MarketAssets::reducible_balance(asset, who, false) >= value - } + T::MarketAssets::reducible_balance(asset, who, false) >= value } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::reducible_balance(asset, who, false) >= value @@ -202,14 +192,12 @@ impl MultiCurrency for Pallet { T::MarketAssets::slash(asset, who, amount) .map(|b| amount.saturating_sub(b)) .unwrap_or_else(|_| amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::slash(currency, who, amount) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::slash(currency, who, amount) - } else { - T::MarketAssets::slash(asset, who, amount) - .map(|b| amount.saturating_sub(b)) - .unwrap_or_else(|_| amount) - } + T::MarketAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) } } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { T::CampaignAssets::slash(asset, who, amount) diff --git a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs index 549442704..70ea1f499 100644 --- a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs +++ b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs @@ -62,12 +62,10 @@ impl MultiCurrencyExtended for Pallet { // Route "pre new asset system" market assets to `CurrencyType` if T::MarketAssets::asset_exists(asset) { Self::update_balance_asset(currency_id, who, by_amount) + } else if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + T::Currencies::update_balance(currency, who, by_amount) } else { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - T::Currencies::update_balance(currency, who, by_amount) - } else { - Self::update_balance_asset(currency_id, who, by_amount) - } + Self::update_balance_asset(currency_id, who, by_amount) } } else if let Ok(_asset) = T::CampaignAssetType::try_from(currency_id) { Self::update_balance_asset(currency_id, who, by_amount) From f793cf32ca83f3dc9741b36cd9c7a3b53bd33e2b Mon Sep 17 00:00:00 2001 From: Chralt Date: Thu, 15 Feb 2024 10:23:06 +0100 Subject: [PATCH 041/104] Integrate Moonwall (#1192) * modify integration tests * wip * correct typescript to be utilized by zndsl * integrate moonwall * use faster pnpm package manager * get moonwall network running * add scripts folder * add moonwall readme * update readme * add runtime upgrade integration tests * update readme * wip chopticks runtime upgrade test * prepare chopsticks battery station upgrade test * add chopsticks and sqlite3 * chopsticks runs locally after python installation * battery station rt-upgrade works, but not main-net * fix zeitgeist chopsticks upgrade test * add zndsl test to workflow * update integration test workflow * wip * update download polkadot * wip * wip * wip * wip * wip * wip * use artifact storage in github actions * use release instead of debug * update ci * update ci * update ci * correct CI * avoid creating zeitgeist folder * download to target/release * add executive permission * dump chopsticks * log chopsticks * increase timeouts for CI workflows * resolve ws port conflict, add xcm transfer test * add xcm tests to ci * fix log print for chopsticks * use polkadot v1.1.0 for it-tests * add xcm test to post rt upgrade suite * correct ci * manually increase blocks for hydradx * divide main and test-net it tests * avoid port conflict for parallel chopsticks jobs * fix CI and typescript errors * update ci * exit cat command in ci * update CI to stop immediately * restructure jobs * update CI to only show logs for failure * update readme * update readme * delete outdated test instruction * delete unused files * add license to integration tests * add copyright * add copyrights * add copyrights * mkdir integration-tests/tmp * update integration test readme * Update integration-tests/scripts/download-polkadot.sh Co-authored-by: Harald Heckmann * update gitignore * update download polkadot comment * build node for deploy-zombienet script * remove mkdir * Update integration-tests/scripts/download-polkadot.sh Co-authored-by: Harald Heckmann * Update integration-tests/scripts/download-polkadot.sh Co-authored-by: Harald Heckmann * Update .github/workflows/integration-tests.yml Co-authored-by: Harald Heckmann * build node in non CI environments * change integration test execution directory * remove CI run for this branch --------- Co-authored-by: Harald Heckmann --- .github/workflows/integration-tests.yml | 269 + .gitignore | 4 +- integration-tests/LICENSE | 674 ++ integration-tests/README.md | 104 + integration-tests/configs/basilisk.yml | 21 + integration-tests/configs/battery-station.yml | 29 + integration-tests/configs/hydradx.yml | 111 + integration-tests/configs/zeitgeist.yml | 29 + .../configs/zombieZeitgeist.json | 44 + integration-tests/moonwall.config.json | 130 + integration-tests/package-lock.json | 3732 ----------- integration-tests/package.json | 26 +- integration-tests/pnpm-lock.yaml | 5630 +++++++++++++++++ .../scripts/build-battery-station-spec.sh | 10 + .../scripts/build-node-configuration.sh | 5 + integration-tests/scripts/build-node.sh | 11 + .../scripts/build-zeitgeist-spec.sh | 10 + .../scripts/deploy-zombienet.sh | 18 +- .../scripts/download-polkadot.sh | 67 + integration-tests/tests/block.ts | 15 - integration-tests/tests/common-tests.ts | 195 + ...tery-station-chopsticks-runtime-upgrade.ts | 133 + ...st-zeitgeist-chopsticks-runtime-upgrade.ts | 125 + .../test-zombienet-runtime-upgrade.ts | 125 + integration-tests/tsconfig.json | 19 +- .../zombienet/0001-balance-transfer.ts | 83 + .../zombienet/produce-blocks.toml | 4 +- .../zombienet/produce-blocks.zndsl | 53 +- package-lock.json | 6 + 29 files changed, 7898 insertions(+), 3784 deletions(-) create mode 100644 .github/workflows/integration-tests.yml create mode 100644 integration-tests/LICENSE create mode 100644 integration-tests/configs/basilisk.yml create mode 100644 integration-tests/configs/battery-station.yml create mode 100644 integration-tests/configs/hydradx.yml create mode 100644 integration-tests/configs/zeitgeist.yml create mode 100644 integration-tests/configs/zombieZeitgeist.json create mode 100644 integration-tests/moonwall.config.json delete mode 100644 integration-tests/package-lock.json create mode 100644 integration-tests/pnpm-lock.yaml create mode 100755 integration-tests/scripts/build-battery-station-spec.sh create mode 100755 integration-tests/scripts/build-node-configuration.sh create mode 100755 integration-tests/scripts/build-node.sh create mode 100755 integration-tests/scripts/build-zeitgeist-spec.sh rename scripts/tests/spawn_network.sh => integration-tests/scripts/deploy-zombienet.sh (84%) create mode 100755 integration-tests/scripts/download-polkadot.sh delete mode 100644 integration-tests/tests/block.ts create mode 100644 integration-tests/tests/common-tests.ts create mode 100644 integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts create mode 100644 integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts create mode 100644 integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts create mode 100644 integration-tests/zombienet/0001-balance-transfer.ts create mode 100644 package-lock.json diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 000000000..e0333d9d7 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,269 @@ +name: Integration Tests + +on: + push: + tags: + - '^v[0-9]+.[0-9]+.[0-9]+(-rc[0-9]+)?$' + +env: + CARGO_TERM_COLOR: always + +jobs: + build_parachain: + name: Build Parachain + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - name: Build Parachain + run: cargo build --release --features parachain + + - name: Save runtime wasm + run: | + mkdir -p runtimes + cp target/release/wbuild/battery-station-runtime/battery_station_runtime.compact.compressed.wasm runtimes/; + cp target/release/wbuild/zeitgeist-runtime/zeitgeist_runtime.compact.compressed.wasm runtimes/; + + - name: Upload runtimes + uses: actions/upload-artifact@v3.1.2 + with: + name: runtimes + path: runtimes + + - name: Save zeitgeist binary + run: | + mkdir -p binaries + cp target/release/zeitgeist binaries/; + + - name: Upload binary + uses: actions/upload-artifact@v3.1.2 + with: + name: binaries + path: binaries + + zombienet_zndsl: + name: ZNDSL Tests + runs-on: ubuntu-20.04 + needs: ["build_parachain"] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "./integration-tests/pnpm-lock.yaml" + + - name: Install pnpm packages + run: | + cd integration-tests + pnpm install + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Create local folders + run: | + mkdir -p target/release/wbuild/zeitgeist-runtime/ + mkdir -p integration-tests/tmp + + - name: Download runtime + uses: actions/download-artifact@v3.0.2 + with: + name: runtimes + path: target/release/wbuild/zeitgeist-runtime/ + + - name: Download binary + uses: actions/download-artifact@v3.0.2 + with: + name: binaries + path: target/release + + - name: Display structure of downloaded files + run: ls -R + working-directory: target/ + + - name: Run ZNDSL integration tests + run: | + chmod uog+x target/release/zeitgeist + + cd integration-tests + ./scripts/download-polkadot.sh + ./scripts/deploy-zombienet.sh --no-build --test + + zombienet_zeitgeist_upgrade: + name: Zeitgeist Zombienet Post-Upgrade Tests + runs-on: ubuntu-20.04 + needs: ["build_parachain"] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "./integration-tests/pnpm-lock.yaml" + + - name: Install pnpm packages + run: | + cd integration-tests + pnpm install + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Create local folders + run: | + mkdir -p target/release/wbuild/zeitgeist-runtime/ + mkdir -p integration-tests/tmp + + - name: Download runtime + uses: actions/download-artifact@v3.0.2 + with: + name: runtimes + path: target/release/wbuild/zeitgeist-runtime/ + + - name: Download binary + uses: actions/download-artifact@v3.0.2 + with: + name: binaries + path: target/release + + - name: Display structure of downloaded files + run: ls -R + working-directory: target/ + + - name: Test zeitgeist runtime upgrade using Zombienet + run: | + chmod uog+x target/release/zeitgeist + + cd integration-tests + pnpm exec moonwall test zombienet_zeitgeist_upgrade + + chopsticks_battery_station_upgrade: + name: Battery Station Chopsticks Post-Upgrade Tests + runs-on: ubuntu-20.04 + needs: ["build_parachain"] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "./integration-tests/pnpm-lock.yaml" + + - name: Install pnpm packages + run: | + cd integration-tests + pnpm install + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Create local folders + run: | + mkdir -p target/release/wbuild/battery-station-runtime/ + mkdir -p integration-tests/tmp/node_logs + + - name: Download runtime + uses: actions/download-artifact@v3.0.2 + with: + name: runtimes + path: target/release/wbuild/battery-station-runtime/ + + - name: Display structure of downloaded files + run: ls -R + working-directory: target/ + + - name: Battery Station post-upgrade tests using Chopsticks + run: | + cd integration-tests + pnpm exec moonwall test chopsticks_battery_station_upgrade + + - name: Show chopsticks logs + if: ${{ failure() }} + run: | + cd integration-tests + ls -R tmp/node_logs/ + cat tmp/node_logs/*.log | tail -n 1000 + + chopsticks_zeitgeist_upgrade: + name: Zeitgeist Chopsticks Post-Upgrade Tests + runs-on: ubuntu-20.04 + needs: ["build_parachain"] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install build tools + run: ./scripts/init.sh + + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 20.x + cache: "pnpm" + cache-dependency-path: "./integration-tests/pnpm-lock.yaml" + + - name: Install pnpm packages + run: | + cd integration-tests + pnpm install + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Create local folders + run: | + mkdir -p target/release/wbuild/zeitgeist-runtime/ + mkdir -p integration-tests/tmp/node_logs + + - name: "Download runtime" + uses: actions/download-artifact@v3.0.2 + with: + name: runtimes + path: target/release/wbuild/zeitgeist-runtime/ + + - name: Display structure of downloaded files + run: ls -R + working-directory: target/ + + - name: Zeitgeist post-upgrade tests using Chopsticks + run: | + cd integration-tests + pnpm exec moonwall test chopsticks_zeitgeist_upgrade + + - name: Show chopsticks logs + if: ${{ failure() }} + run: | + cd integration-tests + ls -R tmp/node_logs/ + cat tmp/node_logs/*.log | tail -n 1000 diff --git a/.gitignore b/.gitignore index a962176f6..5aeff784e 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,6 @@ dmypy.json # Pyre type checker .pyre/ -/tmp \ No newline at end of file +/integration-tests/tmp + +/integration-tests/specs \ No newline at end of file diff --git a/integration-tests/LICENSE b/integration-tests/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/integration-tests/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/integration-tests/README.md b/integration-tests/README.md index 0ca287e97..40077fd19 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -1 +1,105 @@ # Integration tests + +## Description + +Integation testing for ZeitgeistPM using +[Moonwall](https://github.com/Moonsong-Labs/moonwall). + +Consider the documentation of the +[Moonwall repository](https://github.com/Moonsong-Labs/moonwall) for more +information. + +## Installation + +### NPM Installation + +> Package manager `pnpm` is required for the integration tests. You can install +> it with `npm install -g pnpm` or otherwise following +> [their instructions](https://pnpm.io/installation). + +#### Go to the `integration-tests` directory: + +```bash +cd integration-tests +``` + +The following commands all assume you are in the `integration-tests` directory. + +Run `pnpm install` in the `integration-tests` folder before running any of the +following commands. + +``` +pnpm install +``` + +You should have installed `python` for using `sqlite3` and then used +`pnpm rebuild && pnpm rebuild sqlite3`. + +### Running the test environments + +#### Deploy a local, running relay-parachain network for zombienet: + +This is useful for testing the parachain client. It starts producing blocks of +the relay and parachain from genesis. + +```bash +./scripts/download-polkadot.sh +./scripts/deploy-zombienet.sh +``` + +It is expected to see the following output multiple times before the network is launched: + +```text +Error fetching metrics from: http://127.0.0.1:/metrics +``` + +##### Run ZNDSL zombienet tests on a local relay-parachain network: + +Using the additional `--test` flag, you can run the ZNDSL tests on the network. + +```bash +./scripts/download-polkadot.sh +./scripts/deploy-zombienet.sh --test +``` + +#### Deploy a local, running relay-parachain fork network via chopsticks (e. g. to test XCM): + +This is useful for testing XCM or any other runtime interaction that needs to be +tested on the state of the production network. + +```bash +pnpm chopsticks xcm -r polkadot -p ./configs/hydradx.yml -p ./configs/zeitgeist.yml +``` + +The expected output looks like this: + +```text +Unable to map [u8; 32] to a lookup index +[16:36:13.005] INFO (xcm/24440): HydraDX RPC listening on port 8000 +Unable to map [u8; 32] to a lookup index +[16:36:14.895] INFO (xcm/24440): Zeitgeist RPC listening on port 8001 +[16:36:14.964] INFO (xcm/24440): Connected parachains [2034,2092] +[16:36:14.964] INFO (24440): Loading config file https://raw.githubusercontent.com/AcalaNetwork/chopsticks/master/configs/polkadot.yml +Unable to map [u8; 32] to a lookup index +[16:36:16.944] INFO (xcm/24440): Polkadot RPC listening on port 8002 +[16:36:17.112] INFO (xcm/24440): Connected relaychain 'Polkadot' with parachain 'HydraDX' +[16:36:17.240] INFO (xcm/24440): Connected relaychain 'Polkadot' with parachain 'Zeitgeist' +``` + +#### Test the upgrade to the WASM from `./target/release/wbuild/zeitgeist-runtime` on zombienet: + +```bash +pnpm exec moonwall test zombienet_zeitgeist_upgrade +``` + +#### Test the upgrade to the WASM from `./target/release/wbuild/zeitgeist-runtime` on the live main-net fork using chopsticks: + +```bash +pnpm exec moonwall test chopsticks_zeitgeist_upgrade +``` + +#### Test the upgrade to the WASM from `./target/release/wbuild/battery-station-runtime` on the live test-net fork using chopsticks: + +```bash +pnpm exec moonwall test chopsticks_battery_station_upgrade +``` diff --git a/integration-tests/configs/basilisk.yml b/integration-tests/configs/basilisk.yml new file mode 100644 index 000000000..c6317538a --- /dev/null +++ b/integration-tests/configs/basilisk.yml @@ -0,0 +1,21 @@ +endpoint: wss://basilisk-rococo-rpc.play.hydration.cloud +mock-signature-host: true +block: ${env.BASILISK_BLOCK_NUMBER} +db: ./tmp/basilisk_db_mba.sqlite + +import-storage: + System: + Account: + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - providers: 1 + data: + free: 1000000000000000 + Tokens: + Accounts: + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 17 + - free: '100000000000000000000' \ No newline at end of file diff --git a/integration-tests/configs/battery-station.yml b/integration-tests/configs/battery-station.yml new file mode 100644 index 000000000..5b192f244 --- /dev/null +++ b/integration-tests/configs/battery-station.yml @@ -0,0 +1,29 @@ +endpoint: wss://bsr.zeitgeist.pm +mock-signature-host: true +db: ./tmp/bs_db_mba.sqlite +# wasm-override: battery-station-runtime.compact.compressed.wasm + +import-storage: + Sudo: + Key: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" # Alice + System: + Account: + - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - providers: 1 + data: + free: "100000000000000000000000" + AdvisoryCommittee: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + AdvisoryCommitteeMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + Council: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + CouncilMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + TechnicalCommittee: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + TechnicalCommitteeMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + AuthorFilter: + EligibleRatio: 100 + EligibleCount: 100 \ No newline at end of file diff --git a/integration-tests/configs/hydradx.yml b/integration-tests/configs/hydradx.yml new file mode 100644 index 000000000..f89a6018c --- /dev/null +++ b/integration-tests/configs/hydradx.yml @@ -0,0 +1,111 @@ +endpoint: wss://hydradx-rpc.dwellir.com +mock-signature-host: true +block: ${env.HYDRADX_BLOCK_NUMBER} +db: ./tmp/hydradx_db_mba.sqlite +# wasm-override: hydradx_runtime.compact.compressed.wasm + +import-storage: + System: + Account: + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - providers: 1 + data: + free: 1000000000000000 + Tokens: + Accounts: + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 0 # HDX + - free: '10000000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 1 # Lerna + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 2 # DAI Stablecoin (via Wormhole) + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 3 # Wrapped Bitcoin (via Wormhole) + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 4 # Ethereum (via Wormhole) + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 5 # Polkadot + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 6 # ApeCoin (via Wormhole) + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 7 # USD Coin (via Wormhole) + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 8 # Phala + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 9 # Astar + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 10 # Statemint USDT + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 11 # interBTC + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 12 # Zeitgeist + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 13 # Centrifuge + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 14 # Bifrost Native Coin + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 15 # Bifrost Voucher DOT + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 16 # GLMR + - free: '100000000000000000000000' + - + - + - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - 17 # Interlay + - free: '100000000000000000000000' + TechnicalCommittee: + Members: ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"] + Council: + Members: ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"] \ No newline at end of file diff --git a/integration-tests/configs/zeitgeist.yml b/integration-tests/configs/zeitgeist.yml new file mode 100644 index 000000000..72c12906e --- /dev/null +++ b/integration-tests/configs/zeitgeist.yml @@ -0,0 +1,29 @@ +endpoint: wss://main.rpc.zeitgeist.pm/ws +# endpoint: wss://zeitgeist-rpc.dwellir.com +# endpoint: wss://zeitgeist.api.onfinality.io/public-ws +mock-signature-host: true +db: ./tmp/ztg_db_mba.sqlite +# wasm-override: zeitgeist-runtime.compact.compressed.wasm + +import-storage: + System: + Account: + - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + - providers: 1 + data: + free: "100000000000000000000000" + AdvisoryCommittee: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + AdvisoryCommitteeMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + Council: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + CouncilMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + TechnicalCommittee: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + TechnicalCommitteeMembership: + Members: [5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY] + AuthorFilter: + EligibleRatio: 100 + EligibleCount: 100 \ No newline at end of file diff --git a/integration-tests/configs/zombieZeitgeist.json b/integration-tests/configs/zombieZeitgeist.json new file mode 100644 index 000000000..5755048a1 --- /dev/null +++ b/integration-tests/configs/zombieZeitgeist.json @@ -0,0 +1,44 @@ +{ + "settings": { + "timeout": 1000, + "provider": "native" + }, + "relaychain": { + "chain": "rococo-dev", + "default_command": "./tmp/polkadot", + "default_args": ["--no-hardware-benchmarks", "-lparachain=debug", "--database=paritydb", "--no-beefy"], + "nodes": [ + { + "name": "charlie", + "ws_port": 9947, + "validator": true + }, + { + "name": "bob", + "validator": true + } + ] + }, + "parachains": [ + { + "id": 2101, + "chain": "dev", + "collators": [ + { + "name": "alice", + "command": "../target/release/zeitgeist", + "ws_port": 9944, + "p2p_port": 33049, + "args": ["-lparachain=debug", "--force-authoring"] + } + ] + } + ], + "types": { + "Header": { + "number": "u64", + "parent_hash": "Hash", + "post_state": "Hash" + } + } +} \ No newline at end of file diff --git a/integration-tests/moonwall.config.json b/integration-tests/moonwall.config.json new file mode 100644 index 000000000..18cec7399 --- /dev/null +++ b/integration-tests/moonwall.config.json @@ -0,0 +1,130 @@ +{ + "label": "moonwall_config", + "defaultTestTimeout": 120000, + "scriptsDir": "scripts/", + "environments": [ + { + "name": "zombienet_zeitgeist_upgrade", + "testFileDir": ["tests/rt-upgrade-zombienet"], + "runScripts": [ + "build-node.sh", + "build-zeitgeist-spec.sh", + "download-polkadot.sh" + ], + "foundation": { + "launchSpec": [ + { + "binPath": "../target/release/zeitgeist" + } + ], + "rtUpgradePath": "../target/release/wbuild/zeitgeist-runtime/zeitgeist_runtime.compact.compressed.wasm", + "type": "zombie", + "zombieSpec": { + "configPath": "./configs/zombieZeitgeist.json" + } + }, + "connections": [ + { + "name": "Relay", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:9947"] + }, + { + "name": "parachain", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:9944"] + } + ] + }, + { + "name": "chopsticks_zeitgeist_upgrade", + "testFileDir": ["tests/rt-upgrade-zeitgeist-chopsticks"], + "runScripts": ["build-node.sh"], + "foundation": { + "type": "chopsticks", + "rtUpgradePath": "../target/release/wbuild/zeitgeist-runtime/zeitgeist_runtime.compact.compressed.wasm", + "launchSpec": [ + { + "name": "ZeitgeistDB", + "type": "parachain", + "configPath": "./configs/zeitgeist.yml" + }, + { + "name": "HydraDXDB", + "type": "parachain", + "configPath": "./configs/hydradx.yml" + }, + { + "name": "PolkadotDB", + "type": "relaychain", + "configPath": "polkadot" + } + ] + }, + "envVars": ["LOG_LEVEL=debug", "VERBOSE_LOG"], + "buildBlockMode": "manual", + "connections": [ + { + "name": "ZeitgeistPara", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8000"] + }, + { + "name": "HydraDXPara", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8001"] + }, + { + "name": "PolkadotRelay", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8002"] + } + ] + }, + { + "name": "chopsticks_battery_station_upgrade", + "testFileDir": ["tests/rt-upgrade-battery-station-chopsticks"], + "runScripts": ["build-node.sh"], + "foundation": { + "type": "chopsticks", + "rtUpgradePath": "../target/release/wbuild/battery-station-runtime/battery_station_runtime.compact.compressed.wasm", + "launchSpec": [ + { + "name": "BatteryStationDB", + "type": "parachain", + "configPath": "./configs/battery-station.yml" + }, + { + "name": "BasiliskDB", + "type": "parachain", + "configPath": "./configs/basilisk.yml" + }, + { + "name": "RococoDB", + "type": "relaychain", + "configPath": "rococo" + } + ] + }, + "envVars": ["LOG_LEVEL=debug", "VERBOSE_LOG"], + "buildBlockMode": "manual", + "connections": [ + { + "name": "BatteryStationPara", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8000"] + }, + { + "name": "BasiliskPara", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8001"] + }, + { + "name": "RococoRelay", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:8002"] + } + ] + } + ] +} diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json deleted file mode 100644 index 996ae53cd..000000000 --- a/integration-tests/package-lock.json +++ /dev/null @@ -1,3732 +0,0 @@ -{ - "name": "integration-tests", - "version": "0.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/runtime": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", - "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@multiformats/base-x": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", - "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==", - "dev": true - }, - "@polkadot/api": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-4.15.1.tgz", - "integrity": "sha512-MsGKpTAmnkxIN+Zu21NPKWot4xLsDGn/cdranf+P2OjqB80E6m8zkQOE7ug3912WIi5byZp55TSOE9UUzAAW3Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/api-derive": "4.15.1", - "@polkadot/keyring": "^6.9.1", - "@polkadot/metadata": "4.15.1", - "@polkadot/rpc-core": "4.15.1", - "@polkadot/rpc-provider": "4.15.1", - "@polkadot/types": "4.15.1", - "@polkadot/types-known": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/util-crypto": "^6.9.1", - "@polkadot/x-rxjs": "^6.9.1", - "eventemitter3": "^4.0.7" - } - }, - "@polkadot/api-derive": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-4.15.1.tgz", - "integrity": "sha512-gAkKg4w09PThemRz0VI4YDzTy3RfCJrjpR0ZnYT93lZWsspSDiQIzMOTPbOn7MvC7FujeRIT0zqpzDeqKc4wFQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/api": "4.15.1", - "@polkadot/rpc-core": "4.15.1", - "@polkadot/types": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/util-crypto": "^6.9.1", - "@polkadot/x-rxjs": "^6.9.1" - } - }, - "@polkadot/keyring": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-6.9.1.tgz", - "integrity": "sha512-UF+9psVwVlar7LRJYWJB/xETYBPu1OcyVrxDe88w17Y+ZIsIeNfcR9quVS4M7AdAhplmscsz/wgEMjmggXH9/Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/util": "6.9.1", - "@polkadot/util-crypto": "6.9.1" - } - }, - "@polkadot/metadata": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/metadata/-/metadata-4.15.1.tgz", - "integrity": "sha512-pnOgJSFCVgQpZtp7ghS0AS7xWCp6POKrb5LhY1CK7XTy6iQ6VHcGnr1N4wfia1c3o5ZOPj8Xic29Da61tkjp2Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/types": "4.15.1", - "@polkadot/types-known": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/util-crypto": "^6.9.1" - } - }, - "@polkadot/networks": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-6.9.1.tgz", - "integrity": "sha512-imBPIrLN0W+7zuosD4WBtOkMzXc/271NhWn6dQmyA0xEoJx+6coJHQH/04fqO/gfZd4M1R70f3Gt5hlsfzCwlA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5" - } - }, - "@polkadot/rpc-core": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-4.15.1.tgz", - "integrity": "sha512-g9NxsHgOBzqLBWGV+hbXd8tmDWBdIZSxr3b107xY090wbQQ0R9I6D6wi+d7mJtcH1tazuhQgWQqyvvsbr2xPZg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/metadata": "4.15.1", - "@polkadot/rpc-provider": "4.15.1", - "@polkadot/types": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/x-rxjs": "^6.9.1" - } - }, - "@polkadot/rpc-provider": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-4.15.1.tgz", - "integrity": "sha512-NfgdBsLP56XAIrSu29iKmqLb4ZAbK50VINGv3Qi99s5V2FiFOAvZq22arkoCmFo1q4zFpQeQqul3ZyZv8QX/Kg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/types": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/util-crypto": "^6.9.1", - "@polkadot/x-fetch": "^6.9.1", - "@polkadot/x-global": "^6.9.1", - "@polkadot/x-ws": "^6.9.1", - "eventemitter3": "^4.0.7" - } - }, - "@polkadot/types": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-4.15.1.tgz", - "integrity": "sha512-daCblGDkiNHyDNkVDOQ0x/hblK7zkGAFQy+ykMt6euGGllUiKaAbCKlQN803aKmsB/5m8zQU63MHQC65tkpWXg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/metadata": "4.15.1", - "@polkadot/util": "^6.9.1", - "@polkadot/util-crypto": "^6.9.1", - "@polkadot/x-rxjs": "^6.9.1" - } - }, - "@polkadot/types-known": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-4.15.1.tgz", - "integrity": "sha512-yj2fVwEyYrc6eBoW6W3JQ5h1FZjcyyhDzfrtxronSLdT7H6zUtlLqn1A5uzkxPD0eSXkesh960GHeYzAi+p4cw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/networks": "^6.9.1", - "@polkadot/types": "4.15.1", - "@polkadot/util": "^6.9.1" - } - }, - "@polkadot/util": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-6.9.1.tgz", - "integrity": "sha512-RTIn8+Xdgywj8Bl7D12557zi8iIoUvDpAJztp/CwIq4O3jEw6Y/7lqNX/K6OXhZm/gZf7tFgFnvGlsIuNbtYcQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-textdecoder": "6.9.1", - "@polkadot/x-textencoder": "6.9.1", - "@types/bn.js": "^4.11.6", - "bn.js": "^4.11.9", - "camelcase": "^5.3.1", - "ip-regex": "^4.3.0" - } - }, - "@polkadot/util-crypto": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-6.9.1.tgz", - "integrity": "sha512-lniY8bhRoayA8PD3NHyvpAL2N9YETDll6HbxaOIkGS9nnVmlOjIvslcd343b30rj7/qSV72w+8qkReHj650aQw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/networks": "6.9.1", - "@polkadot/util": "6.9.1", - "@polkadot/wasm-crypto": "^4.0.2", - "@polkadot/x-randomvalues": "6.9.1", - "base-x": "^3.0.8", - "base64-js": "^1.5.1", - "blakejs": "^1.1.0", - "bn.js": "^4.11.9", - "create-hash": "^1.2.0", - "elliptic": "^6.5.4", - "hash.js": "^1.1.7", - "js-sha3": "^0.8.0", - "scryptsy": "^2.1.0", - "tweetnacl": "^1.0.3", - "xxhashjs": "^0.2.2" - } - }, - "@polkadot/wasm-crypto": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-4.0.2.tgz", - "integrity": "sha512-2h9FuQFkBc+B3TwSapt6LtyPvgtd0Hq9QsHW8g8FrmKBFRiiFKYRpfJKHCk0aCZzuRf9h95bQl/X6IXAIWF2ng==", - "dev": true, - "requires": { - "@babel/runtime": "^7.13.9", - "@polkadot/wasm-crypto-asmjs": "^4.0.2", - "@polkadot/wasm-crypto-wasm": "^4.0.2" - } - }, - "@polkadot/wasm-crypto-asmjs": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-4.0.2.tgz", - "integrity": "sha512-hlebqtGvfjg2ZNm4scwBGVHwOwfUhy2yw5RBHmPwkccUif3sIy4SAzstpcVBIVMdAEvo746bPWEInA8zJRcgJA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.13.9" - } - }, - "@polkadot/wasm-crypto-wasm": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-4.0.2.tgz", - "integrity": "sha512-de/AfNPZ0uDKFWzOZ1rJCtaUbakGN29ks6IRYu6HZTRg7+RtqvE1rIkxabBvYgQVHIesmNwvEA9DlIkS6hYRFQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.13.9" - } - }, - "@polkadot/x-fetch": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-6.9.1.tgz", - "integrity": "sha512-CckiiRiGM+7WGOw1WijDeN9NJcTBEjZ96LRMaUng+BNvNhUQplsmX6CUt86Qn6T1O8TQaAg93wtqb+deykkn2g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-global": "6.9.1", - "@types/node-fetch": "^2.5.10", - "node-fetch": "^2.6.1" - } - }, - "@polkadot/x-global": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-6.9.1.tgz", - "integrity": "sha512-/pVzvQUObccuk/f2BGcs0WMjveLQPr1Qf+uiSF/7ae9BZHIG4ydLz0/Lnzbt4YQkIEaRNvVFD1Vph5hyjo4VCA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@types/node-fetch": "^2.5.10", - "node-fetch": "^2.6.1" - } - }, - "@polkadot/x-randomvalues": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-6.9.1.tgz", - "integrity": "sha512-L1c5ddjzyPAvzRkbnrbVgQTUskM4vRtfxblOV/tmM1BP6mB1U3rWo0FeHNWN/uiUoibVFIDNUKqUnZ5vhYs1qg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-global": "6.9.1" - } - }, - "@polkadot/x-rxjs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-rxjs/-/x-rxjs-6.9.1.tgz", - "integrity": "sha512-sfQNA7so5KoeFEIIx7OGZL5D+0Hn0SRZJMbZSuGRFMKjyovpyzWi+Mjs3l6T6OXbKm972XAseNGDUWSVG4EpLQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "rxjs": "^6.6.7" - } - }, - "@polkadot/x-textdecoder": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-6.9.1.tgz", - "integrity": "sha512-U7Cu7PbY5CG5kjbKqAQ/e07FfvPYZwHZZbC+343vDHUnJlyONKxp+jhod+A1pepu15fsYH/iZC0Os6RwfKoEAA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-global": "6.9.1" - } - }, - "@polkadot/x-textencoder": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-6.9.1.tgz", - "integrity": "sha512-M9VNm7IpbBwQnCZhEBAXSQ+/g2psEv4zjJYdX4/HTcWNpnYEUHeayIVTw91Wkgo3dN8wBL245vVp/8apfKfMkQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-global": "6.9.1" - } - }, - "@polkadot/x-ws": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-6.9.1.tgz", - "integrity": "sha512-BeoVqFFLatrt3k8Leyi6LsOMz5rkdXbQ5oE2Db0V+ezfh5aEV/JjoLsaPkX+i6xsCYibB43rY64FBhpJ2O0iYg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.14.5", - "@polkadot/x-global": "6.9.1", - "@types/websocket": "^1.0.2", - "websocket": "^1.0.34" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "dev": true - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "dev": true - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dev": true, - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "dev": true - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "dev": true - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "dev": true - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "dev": true - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "dev": true - }, - "@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/chai": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.19.tgz", - "integrity": "sha512-jRJgpRBuY+7izT7/WNXP/LsMO9YonsstuL+xuvycDyESpoDoIAsMd7suwpB4h9oEWB+ZlPTqJJ8EHomzNhwTPQ==", - "dev": true - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", - "dev": true - }, - "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", - "dev": true - }, - "@types/node": { - "version": "15.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz", - "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.5.10", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", - "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/websocket": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", - "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@zeitgeistpm/sdk": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@zeitgeistpm/sdk/-/sdk-0.2.1.tgz", - "integrity": "sha512-0PLn5BUtj9BYDNSWM8OD/Z4oFyhcfp6biPTGwMdXHj1voAbt4ztgCwpUBIhg4H/WXBz3V2DM86jgIhrEsOi5Gg==", - "dev": true, - "requires": { - "@polkadot/api": "^4.4.1", - "@polkadot/keyring": "^6.0.5", - "@polkadot/util": "^6.0.5", - "@zeitgeistpm/type-defs": "^0.2.0", - "@zeitgeistpm/types": "^0.2.0", - "commander": "^7.2.0", - "ipfs-http-client": "^49.0.4", - "it-all": "^1.0.4", - "uint8arrays": "^2.1.4" - } - }, - "@zeitgeistpm/type-defs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@zeitgeistpm/type-defs/-/type-defs-0.2.0.tgz", - "integrity": "sha512-obSkYl3gNTeJFMF9JpcR0lLydgxMYiaySCsdmhK1VsJT2oBjzd67X5yFqzjurddXXIHw/kvjEUFLrCWO8C6UBw==", - "dev": true - }, - "@zeitgeistpm/types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@zeitgeistpm/types/-/types-0.2.0.tgz", - "integrity": "sha512-dEZB549N6MDY61BJHe1iYwDT9vMN3tXAqsP7VNagsXQ6UK+POjIdAbcaJHRfEfX9ogqL0LCCJh4LwgbUuqHewg==", - "dev": true, - "requires": { - "@polkadot/metadata": "^4.6.2", - "@polkadot/types": "^4.6.2", - "@zeitgeistpm/type-defs": "^0.2.0", - "axios": "^0.21.1" - } - }, - "@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "dev": true, - "optional": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "any-signal": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-2.1.2.tgz", - "integrity": "sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "native-abort-controller": "^1.0.3" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", - "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", - "dev": true - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", - "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "blakejs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", - "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", - "dev": true - }, - "blob-to-it": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/blob-to-it/-/blob-to-it-1.0.2.tgz", - "integrity": "sha512-yD8tikfTlUGEOSHExz4vDCIQFLaBPXIL0KcxGQt9RbwMVXBEh+jokdJyStvTXPgWrdKfwgk7RX8GPsgrYzsyng==", - "dev": true, - "requires": { - "browser-readablestream-to-it": "^1.0.2" - } - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", - "dev": true, - "requires": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-readablestream-to-it": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.2.tgz", - "integrity": "sha512-lv4M2Z6RKJpyJijJzBQL5MNssS7i8yedl+QkhnLCyPtgNGNSXv1KthzUnye9NlRAtBAI80X6S9i+vK09Rzjcvg==", - "dev": true - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "bufferutil": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", - "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", - "dev": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-is": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", - "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", - "dev": true - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "dns-over-http-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz", - "integrity": "sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA==", - "dev": true, - "requires": { - "debug": "^4.3.1", - "native-fetch": "^3.0.0", - "receptacle": "^1.3.2" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "electron-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.7.3.tgz", - "integrity": "sha512-1AVMaxrHXTTMqd7EK0MGWusdqNr07Rpj8Th6bG4at0oNgIi/1LBwa9CjT/0Zy+M0k/tSJPS04nFxHj0SXDVgVw==", - "dev": true, - "requires": { - "encoding": "^0.1.13" - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dev": true, - "requires": { - "type": "^2.0.0" - }, - "dependencies": { - "type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", - "dev": true - } - } - }, - "fast-fifo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.0.0.tgz", - "integrity": "sha512-4VEXmjxLj7sbs8J//cn2qhRap50dGzF5n8fjay8mau+Jn4hxSeR3xPFwxMaQq/pDaq7+KQk0PAbC2+nWDkJrmQ==", - "dev": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", - "dev": true - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-iterator": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-1.0.2.tgz", - "integrity": "sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true - }, - "ipfs-core-types": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.3.1.tgz", - "integrity": "sha512-xPBsowS951RsuskMo86AWz9y4ReaBot1YsjOhZvKl8ORd8taxIBTT72LnEPwIZ2G24U854Zjxvd/qUMqO14ivg==", - "dev": true, - "requires": { - "cids": "^1.1.5", - "multiaddr": "^8.0.0", - "peer-id": "^0.14.1" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipfs-core-utils": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ipfs-core-utils/-/ipfs-core-utils-0.7.2.tgz", - "integrity": "sha512-d7T72GxvhNN+tEHsJjxI5Y4LQVdMMbSwNbWB6nVsIHUEdwm3w85L2u1E/ctNd9aaNGvoBwEcnIZhSmqhMf7stw==", - "dev": true, - "requires": { - "any-signal": "^2.1.2", - "blob-to-it": "^1.0.1", - "browser-readablestream-to-it": "^1.0.1", - "cids": "^1.1.5", - "err-code": "^2.0.3", - "ipfs-core-types": "^0.3.1", - "ipfs-utils": "^6.0.1", - "it-all": "^1.0.4", - "it-map": "^1.0.4", - "it-peekable": "^1.0.1", - "multiaddr": "^8.0.0", - "multiaddr-to-uri": "^6.0.0", - "parse-duration": "^0.4.4", - "timeout-abort-controller": "^1.1.1", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipfs-http-client": { - "version": "49.0.4", - "resolved": "https://registry.npmjs.org/ipfs-http-client/-/ipfs-http-client-49.0.4.tgz", - "integrity": "sha512-qgWbkcB4glQrUkE2tZR+GVXyrO6aJyspWBjyct/6TzrhCHx7evjz+kUTK+wNm4S9zccUePEml5VNZUmUhoQtbA==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "any-signal": "^2.1.2", - "bignumber.js": "^9.0.1", - "cids": "^1.1.5", - "debug": "^4.1.1", - "form-data": "^3.0.0", - "ipfs-core-types": "^0.3.1", - "ipfs-core-utils": "^0.7.2", - "ipfs-utils": "^6.0.1", - "ipld-block": "^0.11.0", - "ipld-dag-cbor": "^0.17.0", - "ipld-dag-pb": "^0.20.0", - "ipld-raw": "^6.0.0", - "it-last": "^1.0.4", - "it-map": "^1.0.4", - "it-tar": "^1.2.2", - "it-to-stream": "^0.1.2", - "merge-options": "^3.0.4", - "multiaddr": "^8.0.0", - "multibase": "^4.0.2", - "multicodec": "^3.0.1", - "multihashes": "^4.0.2", - "nanoid": "^3.1.12", - "native-abort-controller": "^1.0.3", - "parse-duration": "^0.4.4", - "stream-to-it": "^0.2.2", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipfs-utils": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-6.0.8.tgz", - "integrity": "sha512-mDDQaDisI/uWk+X08wyw+jBcq76IXwMjgyaoyEgJDb/Izb+QbBCSJjo9q+EvbMxh6/l6q0NiAfbbsxEyQYPW9w==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "any-signal": "^2.1.0", - "buffer": "^6.0.1", - "electron-fetch": "^1.7.2", - "err-code": "^3.0.1", - "is-electron": "^2.2.0", - "iso-url": "^1.0.0", - "it-glob": "~0.0.11", - "it-to-stream": "^1.0.0", - "merge-options": "^3.0.4", - "nanoid": "^3.1.20", - "native-abort-controller": "^1.0.3", - "native-fetch": "^3.0.0", - "node-fetch": "^2.6.1", - "stream-to-it": "^0.2.2" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "dev": true - }, - "it-to-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/it-to-stream/-/it-to-stream-1.0.0.tgz", - "integrity": "sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA==", - "dev": true, - "requires": { - "buffer": "^6.0.3", - "fast-fifo": "^1.0.0", - "get-iterator": "^1.0.2", - "p-defer": "^3.0.0", - "p-fifo": "^1.0.0", - "readable-stream": "^3.6.0" - } - } - } - }, - "ipld-block": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/ipld-block/-/ipld-block-0.11.1.tgz", - "integrity": "sha512-sDqqLqD5qh4QzGq6ssxLHUCnH4emCf/8F8IwjQM2cjEEIEHMUj57XhNYgmGbemdYPznUhffxFGEHsruh5+HQRw==", - "dev": true, - "requires": { - "cids": "^1.0.0" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipld-dag-cbor": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/ipld-dag-cbor/-/ipld-dag-cbor-0.17.1.tgz", - "integrity": "sha512-Bakj/cnxQBdscORyf4LRHxQJQfoaY8KWc7PWROQgX+aw5FCzBt8ga0VM/59K+ABOznsqNvyLR/wz/oYImOpXJw==", - "dev": true, - "requires": { - "borc": "^2.1.2", - "cids": "^1.0.0", - "is-circular": "^1.0.2", - "multicodec": "^3.0.1", - "multihashing-async": "^2.0.0", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipld-dag-pb": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/ipld-dag-pb/-/ipld-dag-pb-0.20.0.tgz", - "integrity": "sha512-zfM0EdaolqNjAxIrtpuGKvXxWk5YtH9jKinBuQGTcngOsWFQhyybGCTJHGNGGtRjHNJi2hz5Udy/8pzv4kcKyg==", - "dev": true, - "requires": { - "cids": "^1.0.0", - "class-is": "^1.1.0", - "multicodec": "^2.0.0", - "multihashing-async": "^2.0.0", - "protons": "^2.0.0", - "reset": "^0.1.0", - "run": "^1.4.0", - "stable": "^0.1.8", - "uint8arrays": "^1.0.0" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - } - } - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-2.1.3.tgz", - "integrity": "sha512-0tOH2Gtio39uO41o+2xl9UhRkCWxU5ZmZSbFCh/OjGzkWJI8e6lkN/s4Mj1YfyWoBod+2+S3W+6wO6nhkwN8pA==", - "dev": true, - "requires": { - "uint8arrays": "1.1.0", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - } - }, - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "uint8arrays": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.1.0.tgz", - "integrity": "sha512-cLdlZ6jnFczsKf5IH1gPHTtcHtPGho5r4CvctohmQjw8K7Q3gFdfIGHxSTdTaCKrL4w09SsPRJTqRS0drYeszA==", - "dev": true, - "requires": { - "multibase": "^3.0.0", - "web-encoding": "^1.0.2" - }, - "dependencies": { - "multibase": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-3.1.2.tgz", - "integrity": "sha512-bpklWHs70LO3smJUHOjcnzGceJJvn9ui0Vau6Za0B/GBepaXswmW8Ufea0uD9pROf/qCQ4N4lZ3sf3U+SNf0tw==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1", - "web-encoding": "^1.0.6" - } - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "ipld-raw": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ipld-raw/-/ipld-raw-6.0.0.tgz", - "integrity": "sha512-UK7fjncAzs59iu/o2kwYtb8jgTtW6B+cNWIiNpAJkfRwqoMk1xD/6i25ktzwe4qO8gQgoR9RxA5ibC23nq8BLg==", - "dev": true, - "requires": { - "cids": "^1.0.0", - "multicodec": "^2.0.0", - "multihashing-async": "^2.0.0" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - } - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-2.1.3.tgz", - "integrity": "sha512-0tOH2Gtio39uO41o+2xl9UhRkCWxU5ZmZSbFCh/OjGzkWJI8e6lkN/s4Mj1YfyWoBod+2+S3W+6wO6nhkwN8pA==", - "dev": true, - "requires": { - "uint8arrays": "1.1.0", - "varint": "^6.0.0" - }, - "dependencies": { - "multibase": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-3.1.2.tgz", - "integrity": "sha512-bpklWHs70LO3smJUHOjcnzGceJJvn9ui0Vau6Za0B/GBepaXswmW8Ufea0uD9pROf/qCQ4N4lZ3sf3U+SNf0tw==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1", - "web-encoding": "^1.0.6" - } - }, - "uint8arrays": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.1.0.tgz", - "integrity": "sha512-cLdlZ6jnFczsKf5IH1gPHTtcHtPGho5r4CvctohmQjw8K7Q3gFdfIGHxSTdTaCKrL4w09SsPRJTqRS0drYeszA==", - "dev": true, - "requires": { - "multibase": "^3.0.0", - "web-encoding": "^1.0.2" - } - } - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-circular": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-circular/-/is-circular-1.0.2.tgz", - "integrity": "sha512-YttjnrswnUYRVJvxCvu8z+PGMUSzC2JttP0OEXezlAEdp3EXzhf7IZ3j0gRAybJBQupedIZFhY61Tga6E0qASA==", - "dev": true - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true - }, - "is-electron": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.0.tgz", - "integrity": "sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-generator-function": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz", - "integrity": "sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-ip": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", - "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "dev": true, - "requires": { - "ip-regex": "^4.0.0" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "iso-constants": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/iso-constants/-/iso-constants-0.1.2.tgz", - "integrity": "sha512-OTCM5ZCQsHBCI4Wdu4tSxvDIkmDHd5EwJDps5mKqnQnWJSKlnwMs3EDZ4n3Fh1tmkWkDlyd2vCDbEYuPbyrUNQ==", - "dev": true - }, - "iso-random-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/iso-random-stream/-/iso-random-stream-2.0.0.tgz", - "integrity": "sha512-lGuIu104KfBV9ubYTSaE3GeAr6I69iggXxBHbTBc5u/XKlwlWl0LCytnkIZissaKqvxablwRD9B3ktVnmIUnEg==", - "dev": true, - "requires": { - "events": "^3.3.0", - "readable-stream": "^3.4.0" - } - }, - "iso-url": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-1.1.5.tgz", - "integrity": "sha512-+3JqoKdBTGmyv9vOkS6b9iHhvK34UajfTibrH/1HOK8TI7K2VsM0qOCd+aJdWKtSOA8g3PqZfcwDmnR0p3klqQ==", - "dev": true - }, - "it-all": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.5.tgz", - "integrity": "sha512-ygD4kA4vp8fi+Y+NBgEKt6W06xSbv6Ub/0V8d1r3uCyJ9Izwa1UspkIOlqY9fOee0Z1w3WRo1+VWyAU4DgtufA==", - "dev": true - }, - "it-concat": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/it-concat/-/it-concat-1.0.3.tgz", - "integrity": "sha512-sjeZQ1BWQ9U/W2oI09kZgUyvSWzQahTkOkLIsnEPgyqZFaF9ME5gV6An4nMjlyhXKWQMKEakQU8oRHs2SdmeyA==", - "dev": true, - "requires": { - "bl": "^4.0.0" - } - }, - "it-glob": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/it-glob/-/it-glob-0.0.13.tgz", - "integrity": "sha512-0Hcd5BraJUPzL28NWiFbdNrcdyNxNTKKdU3sjdFiYynNTQpwlG2UKW31X7bp+XhJwux/oPzIquo5ioztVmc2RQ==", - "dev": true, - "requires": { - "@types/minimatch": "^3.0.4", - "minimatch": "^3.0.4" - } - }, - "it-last": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/it-last/-/it-last-1.0.5.tgz", - "integrity": "sha512-PV/2S4zg5g6dkVuKfgrQfN2rUN4wdTI1FzyAvU+i8RV96syut40pa2s9Dut5X7SkjwA3P0tOhLABLdnOJ0Y/4Q==", - "dev": true - }, - "it-map": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-1.0.5.tgz", - "integrity": "sha512-EElupuWhHVStUgUY+OfTJIS2MZed96lDrAXzJUuqiiqLnIKoBRqtX1ZG2oR0bGDsSppmz83MtzCeKLZ9TVAUxQ==", - "dev": true - }, - "it-peekable": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-1.0.2.tgz", - "integrity": "sha512-LRPLu94RLm+lxLZbChuc9iCXrKCOu1obWqxfaKhF00yIp30VGkl741b5P60U+rdBxuZD/Gt1bnmakernv7bVFg==", - "dev": true - }, - "it-reader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-2.1.0.tgz", - "integrity": "sha512-hSysqWTO9Tlwc5EGjVf8JYZzw0D2FsxD/g+eNNWrez9zODxWt6QlN6JAMmycK72Mv4jHEKEXoyzUN4FYGmJaZw==", - "dev": true, - "requires": { - "bl": "^4.0.0" - } - }, - "it-tar": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/it-tar/-/it-tar-1.2.2.tgz", - "integrity": "sha512-M8V4a9I+x/vwXTjqvixcEZbQZHjwDIb8iUQ+D4M2QbhAdNs3WKVSl+45u5/F2XFx6jYMFOGzMVlKNK/uONgNIA==", - "dev": true, - "requires": { - "bl": "^4.0.0", - "buffer": "^5.4.3", - "iso-constants": "^0.1.2", - "it-concat": "^1.0.0", - "it-reader": "^2.0.0", - "p-defer": "^3.0.0" - } - }, - "it-to-stream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/it-to-stream/-/it-to-stream-0.1.2.tgz", - "integrity": "sha512-DTB5TJRZG3untmZehcaFN0kGWl2bNv7tnJRgQHAO9QEt8jfvVRrebZtnD5NZd4SCj4WVPjl0LSrugNWE/UaZRQ==", - "dev": true, - "requires": { - "buffer": "^5.6.0", - "fast-fifo": "^1.0.0", - "get-iterator": "^1.0.2", - "p-defer": "^3.0.0", - "p-fifo": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "requires": { - "delimit-stream": "0.1.0" - } - }, - "keypair": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/keypair/-/keypair-1.0.3.tgz", - "integrity": "sha512-0wjZ2z/SfZZq01+3/8jYLd8aEShSa+aat1zyPGQY3IuKoEAp6DJGvu2zt6snELrQU9jbCkIlCyNOD7RdQbHhkQ==", - "dev": true - }, - "libp2p-crypto": { - "version": "0.19.4", - "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.4.tgz", - "integrity": "sha512-8iUwiNlU/sFEtXQpxaehmXUQ5Fw6r52H7NH0d8ZSb8nKBbO6r8y8ft6f1to8A81SrFOVd4/zsjEzokpedDvRgw==", - "dev": true, - "requires": { - "err-code": "^3.0.1", - "is-typedarray": "^1.0.0", - "iso-random-stream": "^2.0.0", - "keypair": "^1.0.1", - "multibase": "^4.0.3", - "multicodec": "^3.0.1", - "multihashes": "^4.0.2", - "multihashing-async": "^2.1.2", - "node-forge": "^0.10.0", - "pem-jwk": "^2.0.0", - "protobufjs": "^6.10.2", - "secp256k1": "^4.0.0", - "uint8arrays": "^2.1.4", - "ursa-optional": "^0.10.1" - }, - "dependencies": { - "err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "dev": true - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "dev": true, - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dev": true, - "requires": { - "mime-db": "1.48.0" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multiaddr": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/multiaddr/-/multiaddr-8.1.2.tgz", - "integrity": "sha512-r13IzW8+Sv9zab9Gt8RPMIN2WkptIPq99EpAzg4IbJ/zTELhiEwXWr9bAmEatSCI4j/LSA6ESJzvz95JZ+ZYXQ==", - "dev": true, - "requires": { - "cids": "^1.0.0", - "class-is": "^1.1.0", - "dns-over-http-resolver": "^1.0.0", - "err-code": "^2.0.3", - "is-ip": "^3.1.0", - "multibase": "^3.0.0", - "uint8arrays": "^1.1.0", - "varint": "^5.0.0" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - } - } - } - }, - "multibase": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-3.1.2.tgz", - "integrity": "sha512-bpklWHs70LO3smJUHOjcnzGceJJvn9ui0Vau6Za0B/GBepaXswmW8Ufea0uD9pROf/qCQ4N4lZ3sf3U+SNf0tw==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1", - "web-encoding": "^1.0.6" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - }, - "dependencies": { - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - } - } - } - }, - "uint8arrays": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-1.1.0.tgz", - "integrity": "sha512-cLdlZ6jnFczsKf5IH1gPHTtcHtPGho5r4CvctohmQjw8K7Q3gFdfIGHxSTdTaCKrL4w09SsPRJTqRS0drYeszA==", - "dev": true, - "requires": { - "multibase": "^3.0.0", - "web-encoding": "^1.0.2" - } - } - } - }, - "multiaddr-to-uri": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/multiaddr-to-uri/-/multiaddr-to-uri-6.0.0.tgz", - "integrity": "sha512-OjpkVHOXEmIKMO8WChzzQ7aZQcSQX8squxmvtDbRpy7/QNmJ3Z7jv6qyD74C28QtaeNie8O8ngW2AkeiMmKP7A==", - "dev": true, - "requires": { - "multiaddr": "^8.0.0" - } - }, - "multihashing-async": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/multihashing-async/-/multihashing-async-2.1.2.tgz", - "integrity": "sha512-FTPNnWWxwIK5dXXmTFhySSF8Fkdqf7vzqpV09+RWsmfUhrsL/b3Arg3+bRrBnXTtjxm3JRGI3wSAtQHL0QCxhQ==", - "dev": true, - "requires": { - "blakejs": "^1.1.0", - "err-code": "^3.0.0", - "js-sha3": "^0.8.0", - "multihashes": "^4.0.1", - "murmurhash3js-revisited": "^3.0.0", - "uint8arrays": "^2.1.3" - }, - "dependencies": { - "err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "dev": true - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - } - } - } - }, - "murmurhash3js-revisited": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", - "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", - "dev": true - }, - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true - }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, - "native-abort-controller": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/native-abort-controller/-/native-abort-controller-1.0.3.tgz", - "integrity": "sha512-fd5LY5q06mHKZPD5FmMrn7Lkd2H018oBGKNOAdLpctBDEPFKsfJ1nX9ke+XRa8PEJJpjqrpQkGjq2IZ27QNmYA==", - "dev": true - }, - "native-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-3.0.0.tgz", - "integrity": "sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true - }, - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-defer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", - "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", - "dev": true - }, - "p-fifo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-fifo/-/p-fifo-1.0.0.tgz", - "integrity": "sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A==", - "dev": true, - "requires": { - "fast-fifo": "^1.0.0", - "p-defer": "^3.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parse-duration": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.4.4.tgz", - "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "peer-id": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.14.8.tgz", - "integrity": "sha512-GpuLpob/9FrEFvyZrKKsISEkaBYsON2u0WtiawLHj1ii6ewkoeRiSDFLyIefYhw0jGvQoeoZS05jaT52X7Bvig==", - "dev": true, - "requires": { - "cids": "^1.1.5", - "class-is": "^1.1.0", - "libp2p-crypto": "^0.19.0", - "minimist": "^1.2.5", - "multihashes": "^4.0.2", - "protobufjs": "^6.10.2", - "uint8arrays": "^2.0.5" - }, - "dependencies": { - "cids": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.7.tgz", - "integrity": "sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^2.1.3" - } - }, - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - }, - "multicodec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.1.0.tgz", - "integrity": "sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ==", - "dev": true, - "requires": { - "uint8arrays": "^2.1.5", - "varint": "^6.0.0" - } - }, - "multihashes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz", - "integrity": "sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ==", - "dev": true, - "requires": { - "multibase": "^4.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.2" - }, - "dependencies": { - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - } - } - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", - "dev": true - } - } - }, - "pem-jwk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pem-jwk/-/pem-jwk-2.0.0.tgz", - "integrity": "sha512-rFxu7rVoHgQ5H9YsP50dDWf0rHjreVA2z0yPiWr5WdH/UHb29hKtF7h6l8vNd1cbYR1t0QL+JKhW55a2ZV4KtA==", - "dev": true, - "requires": { - "asn1.js": "^5.0.1" - } - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "protobufjs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", - "dev": true, - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, - "protocol-buffers-schema": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz", - "integrity": "sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw==", - "dev": true - }, - "protons": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/protons/-/protons-2.0.1.tgz", - "integrity": "sha512-FlmPorLEeCEDPu+uIn0Qardgiy5XqVA4IyNTz9wb9c0e2U7BEXdRcIbx64r09o4Abtf+4B7mkTtMbsIXMxZzKw==", - "dev": true, - "requires": { - "protocol-buffers-schema": "^3.3.1", - "signed-varint": "^2.0.1", - "uint8arrays": "^2.1.3", - "varint": "^5.0.0" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "receptacle": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", - "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", - "dev": true, - "requires": { - "ms": "^2.1.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "reset": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/reset/-/reset-0.1.0.tgz", - "integrity": "sha1-n8cxQXGZWubLC35YsGznUir0uvs=", - "dev": true - }, - "retimer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-2.0.0.tgz", - "integrity": "sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==", - "dev": true - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/run/-/run-1.4.0.tgz", - "integrity": "sha1-4X2ekEOrL+F3dsspnhI3848LT/o=", - "dev": true, - "requires": { - "minimatch": "*" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "scryptsy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true - }, - "secp256k1": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", - "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", - "dev": true, - "requires": { - "elliptic": "^6.5.2", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "signed-varint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", - "integrity": "sha1-UKmYnafJjCxh2tEZvJdHDvhSgSk=", - "dev": true, - "requires": { - "varint": "~5.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "stream-to-it": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/stream-to-it/-/stream-to-it-0.2.3.tgz", - "integrity": "sha512-xaK9EjPtLox5rrC7YLSBXSanTtUJN/lzJlMFvy9VaROmnyvy0U/X6m2uMhXPJRn3g3M9uOSIzTszW7BPiWSg9w==", - "dev": true, - "requires": { - "get-iterator": "^1.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgroccWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "timeout-abort-controller": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/timeout-abort-controller/-/timeout-abort-controller-1.1.1.tgz", - "integrity": "sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "retimer": "^2.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==" - }, - "uint8arrays": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.5.tgz", - "integrity": "sha512-CSR7AO+4AHUeSOnZ/NBNCElDeWfRh9bXtOck27083kc7SznmmHIhNEkEOCQOn0wvrIMjS3IH0TNLR16vuc46mA==", - "dev": true, - "requires": { - "multibase": "^4.0.1" - }, - "dependencies": { - "multibase": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.4.tgz", - "integrity": "sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg==", - "dev": true, - "requires": { - "@multiformats/base-x": "^4.0.1" - } - } - } - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "ursa-optional": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/ursa-optional/-/ursa-optional-0.10.2.tgz", - "integrity": "sha512-TKdwuLboBn7M34RcvVTuQyhvrA8gYKapuVdm0nBP0mnBc7oECOfUQZrY91cefL3/nm64ZyrejSRrhTVdX7NG/A==", - "dev": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.14.2" - } - }, - "utf-8-validate": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", - "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", - "dev": true, - "requires": { - "node-gyp-build": "^4.2.0" - } - }, - "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", - "dev": true - }, - "web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", - "dev": true, - "requires": { - "@zxing/text-encoding": "0.9.0", - "util": "^0.12.3" - } - }, - "websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dev": true, - "requires": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "dev": true, - "requires": { - "cuint": "^0.2.2" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - } - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/integration-tests/package.json b/integration-tests/package.json index 49dc1f3c3..3410fbf17 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -1,25 +1,27 @@ { "author": "Zeitgeist PM ", "dependencies": { - "typescript": "^3.9.6" + "@polkadot/keyring": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "sqlite3": "^5.1.6", + "typescript": "^5.2.2", + "ws": "^8.15.1" }, "devDependencies": { - "@polkadot/api": "^4.12.1", - "@polkadot/types": "^4.12.1", - "@types/chai": "^4.2.11", - "@types/mocha": "^8.2.2", - "@zeitgeistpm/sdk": "^0.2.1", - "chai": "^4.3.4", - "mocha": "^8.3.2", - "ts-node": "9.1" + "@acala-network/chopsticks": "0.9.3", + "@moonwall/cli": "^4.4.1", + "@moonwall/util": "^4.4.1", + "debug": "4.3.4", + "@polkadot/api": "^10.10.1", + "@polkadot/types": "^10.10.1", + "@types/node": "^20.9.0", + "ts-node": "^10.9.1" }, "description": "Integration tests", "license": "GPL-3.0", "repository": "https://github.com/zeitgeistpm/zeitgeist", "main": "index.js", "name": "integration-tests", - "scripts": { - "test": "./node_modules/mocha/bin/mocha --parallel -r ts-node/register 'tests/*.ts'" - }, + "scripts": {}, "version": "0.0.0" } diff --git a/integration-tests/pnpm-lock.yaml b/integration-tests/pnpm-lock.yaml new file mode 100644 index 000000000..f47d3d3ce --- /dev/null +++ b/integration-tests/pnpm-lock.yaml @@ -0,0 +1,5630 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@polkadot/keyring': + specifier: ^12.6.1 + version: 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/util-crypto': + specifier: ^12.6.1 + version: 12.6.1(@polkadot/util@12.6.1) + sqlite3: + specifier: ^5.1.6 + version: 5.1.6 + typescript: + specifier: ^5.2.2 + version: 5.3.3 + ws: + specifier: ^8.15.1 + version: 8.15.1 + +devDependencies: + '@acala-network/chopsticks': + specifier: 0.9.3 + version: 0.9.3(debug@4.3.4)(ts-node@10.9.2) + '@moonwall/cli': + specifier: ^4.4.1 + version: 4.4.4(@types/node@20.10.4)(ts-node@10.9.2)(typescript@5.3.3) + '@moonwall/util': + specifier: ^4.4.1 + version: 4.4.4(@types/node@20.10.4)(@vitest/ui@1.0.1)(typescript@5.3.3) + '@polkadot/api': + specifier: ^10.10.1 + version: 10.11.1 + '@polkadot/types': + specifier: ^10.10.1 + version: 10.11.1 + '@types/node': + specifier: ^20.9.0 + version: 20.10.4 + debug: + specifier: 4.3.4 + version: 4.3.4(supports-color@8.1.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.10.4)(typescript@5.3.3) + +packages: + + /@acala-network/chopsticks-core@0.9.3: + resolution: {integrity: sha512-dAw3ATDooLewks+D6PslWafpOhE1s6zQo/nYiW9/bXHuSyvLNh+jgT+z+EUWQTL3cxWqiSaxkkCIjUx9p+jYQw==} + dependencies: + '@acala-network/chopsticks-executor': 0.9.3 + '@polkadot/rpc-provider': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/types-known': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + comlink: 4.4.1 + eventemitter3: 5.0.1 + lodash: 4.17.21 + lru-cache: 10.1.0 + pino: 8.17.1 + pino-pretty: 10.3.0 + rxjs: 7.8.1 + zod: 3.22.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@acala-network/chopsticks-db@0.9.3(ts-node@10.9.2): + resolution: {integrity: sha512-DoRqI0GClUkoaxjwHHsgup8/yZ5ObeOWrK1XghsKBq8Ct1THkQghol/Gneu2iyuLTn0F4ldpxjFOpG6IpkCblA==} + dependencies: + '@acala-network/chopsticks-core': 0.9.3 + '@polkadot/util': 12.6.1 + idb: 7.1.1 + sqlite3: 5.1.6 + typeorm: 0.3.17(sqlite3@5.1.6)(ts-node@10.9.2) + transitivePeerDependencies: + - '@google-cloud/spanner' + - '@sap/hana-client' + - better-sqlite3 + - bluebird + - bufferutil + - encoding + - hdb-pool + - ioredis + - mongodb + - mssql + - mysql2 + - oracledb + - pg + - pg-native + - pg-query-stream + - redis + - sql.js + - supports-color + - ts-node + - typeorm-aurora-data-api-driver + - utf-8-validate + dev: true + + /@acala-network/chopsticks-executor@0.9.3: + resolution: {integrity: sha512-K5zOBFdmMMCkI2ze+g01x6LG+KJ/RWV54Ze97MUXJh0xx7X9FSjhejXU53AmtMEYOZdTjSyHLTOBMoQktZuILw==} + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + dev: true + + /@acala-network/chopsticks@0.9.3(debug@4.3.4)(ts-node@10.9.2): + resolution: {integrity: sha512-O15PB3G1rYAlGYPEilDvDKJD91DUhBlcl3kiDrtCm22u1ECr8jXv2EETwTmDhPiCiP2QADmsjRuxZKbzs/tQxQ==} + hasBin: true + dependencies: + '@acala-network/chopsticks-core': 0.9.3 + '@acala-network/chopsticks-db': 0.9.3(ts-node@10.9.2) + '@pnpm/npm-conf': 2.2.2 + '@polkadot/api-augment': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + axios: 1.6.2(debug@4.3.4) + dotenv: 16.3.1 + global-agent: 3.0.0 + js-yaml: 4.1.0 + jsondiffpatch: 0.5.0 + lodash: 4.17.21 + ws: 8.15.1 + yargs: 17.7.2 + zod: 3.22.4 + transitivePeerDependencies: + - '@google-cloud/spanner' + - '@sap/hana-client' + - better-sqlite3 + - bluebird + - bufferutil + - debug + - encoding + - hdb-pool + - ioredis + - mongodb + - mssql + - mysql2 + - oracledb + - pg + - pg-native + - pg-query-stream + - redis + - sql.js + - supports-color + - ts-node + - typeorm-aurora-data-api-driver + - utf-8-validate + dev: true + + /@adraffy/ens-normalize@1.10.0: + resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} + dev: true + + /@adraffy/ens-normalize@1.9.4: + resolution: {integrity: sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw==} + dev: true + + /@babel/runtime@7.23.5: + resolution: {integrity: sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: true + + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true + optional: true + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@esbuild/android-arm64@0.19.8: + resolution: {integrity: sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.8: + resolution: {integrity: sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.8: + resolution: {integrity: sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.8: + resolution: {integrity: sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.8: + resolution: {integrity: sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.8: + resolution: {integrity: sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.8: + resolution: {integrity: sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.8: + resolution: {integrity: sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.8: + resolution: {integrity: sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.8: + resolution: {integrity: sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.8: + resolution: {integrity: sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.8: + resolution: {integrity: sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.8: + resolution: {integrity: sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.8: + resolution: {integrity: sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.8: + resolution: {integrity: sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.8: + resolution: {integrity: sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.8: + resolution: {integrity: sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.8: + resolution: {integrity: sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.8: + resolution: {integrity: sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.8: + resolution: {integrity: sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.8: + resolution: {integrity: sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.8: + resolution: {integrity: sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@ethereumjs/rlp@4.0.1: + resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /@gar/promisify@1.1.3: + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + requiresBuild: true + optional: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@ljharb/through@2.3.11: + resolution: {integrity: sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + dev: true + + /@mapbox/node-pre-gyp@1.0.11: + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + dependencies: + detect-libc: 2.0.2 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.0 + transitivePeerDependencies: + - encoding + - supports-color + + /@moonbeam-network/api-augment@0.2600.0: + resolution: {integrity: sha512-Hnn7mw8Im+X8GgbX11EjydbPM5MTQSG6yHs7JE2mPYCgSf6PkqUqOil5F4nACI4hMn6rM6GTTdRXscu0ztiNvQ==} + engines: {node: '>=14.0.0'} + dev: true + + /@moonwall/cli@4.4.4(@types/node@20.10.4)(ts-node@10.9.2)(typescript@5.3.3): + resolution: {integrity: sha512-PVSDutjYnf3Lw3wp7bSf7779gDO5PezcsUMwYSnC4rhHBs36mS8CaIks8ZdTRdYy8zArTSUf9aCxkFueHDSbMg==} + engines: {node: '>=20.0.0', pnpm: '>=7'} + hasBin: true + dependencies: + '@acala-network/chopsticks': 0.9.3(debug@4.3.4)(ts-node@10.9.2) + '@moonbeam-network/api-augment': 0.2600.0 + '@moonwall/types': 4.4.4(typescript@5.3.3) + '@moonwall/util': 4.4.4(@types/node@20.10.4)(@vitest/ui@1.0.1)(typescript@5.3.3) + '@polkadot/api': 10.11.1 + '@polkadot/api-augment': 10.11.1 + '@polkadot/api-derive': 10.11.1 + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + '@vitest/ui': 1.0.1(vitest@1.0.1) + '@zombienet/orchestrator': 0.0.68(@polkadot/util@12.6.1)(@types/node@20.10.4) + '@zombienet/utils': 0.0.24(@types/node@20.10.4)(typescript@5.3.3) + bottleneck: 2.19.5 + chalk: 5.3.0 + clear: 0.1.0 + cli-progress: 3.12.0 + colors: 1.4.0 + debug: 4.3.4(supports-color@8.1.1) + dotenv: 16.3.1 + ethers: 6.8.0 + inquirer: 9.2.12 + inquirer-press-to-continue: 1.2.0(inquirer@9.2.12) + jsonc-parser: 3.2.0 + minimatch: 9.0.3 + node-fetch: 3.3.2 + semver: 7.5.4 + viem: 1.18.0(typescript@5.3.3) + vitest: 1.0.1(@types/node@20.10.4)(@vitest/ui@1.0.1) + web3: 4.2.1(typescript@5.3.3) + web3-providers-ws: 4.0.7 + ws: 8.14.2 + yaml: 2.3.3 + yargs: 17.7.2 + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@google-cloud/spanner' + - '@sap/hana-client' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - '@vitest/browser' + - better-sqlite3 + - bluebird + - bufferutil + - canvas + - chokidar + - encoding + - happy-dom + - hdb-pool + - ioredis + - jsdom + - less + - lightningcss + - mongodb + - mssql + - mysql2 + - oracledb + - pg + - pg-native + - pg-query-stream + - redis + - sass + - sql.js + - stylus + - sugarss + - supports-color + - terser + - ts-node + - typeorm-aurora-data-api-driver + - typescript + - utf-8-validate + - zod + dev: true + + /@moonwall/types@4.4.4(typescript@5.3.3): + resolution: {integrity: sha512-zHhcInI8/5sR6FsB3Kz8nQxjGoC4FqR/E19cefhvh69X57x2q3yef04YA287xQ6otrc9yk5+oz9YyrFMl/uhUg==} + engines: {node: '>=14.16.0', pnpm: '>=7'} + dependencies: + '@polkadot/api': 10.11.1 + '@polkadot/api-base': 10.11.1 + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/types': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + '@types/node': 20.10.3 + '@zombienet/utils': 0.0.24(@types/node@20.10.3)(typescript@5.3.3) + bottleneck: 2.19.5 + debug: 4.3.4(supports-color@8.1.1) + ethers: 6.8.0 + viem: 1.18.0(typescript@5.3.3) + web3: 4.2.1(typescript@5.3.3) + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - bufferutil + - chokidar + - encoding + - supports-color + - typescript + - utf-8-validate + - zod + dev: true + + /@moonwall/util@4.4.4(@types/node@20.10.4)(@vitest/ui@1.0.1)(typescript@5.3.3): + resolution: {integrity: sha512-J36G9pjnDuN+SdwnBGWdpPyCRbolb6gxqKqQPhay1LSjPpihzMSZknPHh10FdFw6swRKsah36yXtF59i7nd0Bw==} + engines: {node: '>=14.16.0', pnpm: '>=7'} + dependencies: + '@moonbeam-network/api-augment': 0.2600.0 + '@moonwall/types': 4.4.4(typescript@5.3.3) + '@polkadot/api': 10.11.1 + '@polkadot/api-augment': 10.11.1 + '@polkadot/api-derive': 10.11.1 + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/rpc-provider': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + bottleneck: 2.19.5 + chalk: 5.3.0 + clear: 0.1.0 + cli-progress: 3.12.0 + colors: 1.4.0 + debug: 4.3.4(supports-color@8.1.1) + dotenv: 16.3.1 + ethers: 6.8.0 + inquirer: 9.2.12 + inquirer-press-to-continue: 1.2.0(inquirer@9.2.12) + node-fetch: 3.3.2 + rlp: 3.0.0 + semver: 7.5.4 + viem: 1.16.6(typescript@5.3.3) + vitest: 1.0.0-beta.5(@types/node@20.10.4)(@vitest/ui@1.0.1) + web3: 4.2.1(typescript@5.3.3) + ws: 8.14.2 + yaml: 2.3.4 + yargs: 17.7.2 + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - '@vitest/browser' + - '@vitest/ui' + - bufferutil + - chokidar + - encoding + - happy-dom + - jsdom + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - typescript + - utf-8-validate + - zod + dev: true + + /@noble/curves@1.1.0: + resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} + dependencies: + '@noble/hashes': 1.3.1 + dev: true + + /@noble/curves@1.2.0: + resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + dependencies: + '@noble/hashes': 1.3.2 + + /@noble/ed25519@1.7.3: + resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==} + dev: true + + /@noble/hashes@1.3.1: + resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} + engines: {node: '>= 16'} + dev: true + + /@noble/hashes@1.3.2: + resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + engines: {node: '>= 16'} + + /@noble/secp256k1@1.7.1: + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@npmcli/fs@1.1.1: + resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} + requiresBuild: true + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.5.4 + optional: true + + /@npmcli/move-file@1.1.2: + resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} + engines: {node: '>=10'} + deprecated: This functionality has been moved to @npmcli/fs + requiresBuild: true + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + optional: true + + /@pnpm/config.env-replace@1.1.0: + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + dev: true + + /@pnpm/network.ca-file@1.0.2: + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + dependencies: + graceful-fs: 4.2.10 + dev: true + + /@pnpm/npm-conf@2.2.2: + resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} + engines: {node: '>=12'} + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + dev: true + + /@polka/url@1.0.0-next.24: + resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + dev: true + + /@polkadot/api-augment@10.11.1: + resolution: {integrity: sha512-9Sk7fi6wzvxAoxvGJPcMt0hU4WzuIAlBy4Rng6WPiS6Ed0HJLr1dkZaqFFmV5my2pb3tu//1JGYkt+MUVB0Kqw==} + engines: {node: '>=18'} + dependencies: + '@polkadot/api-base': 10.11.1 + '@polkadot/rpc-augment': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-augment': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/api-base@10.11.1: + resolution: {integrity: sha512-A645Hj9bGtq0EOEWcwTaGoD40vp8/ih1suwinl5il8Psg+bdDmzodnVH5Jhuwe1dNKOuXuvxZvOmbYUPWyIqyg==} + engines: {node: '>=18'} + dependencies: + '@polkadot/rpc-core': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/util': 12.6.1 + rxjs: 7.8.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/api-derive@10.11.1: + resolution: {integrity: sha512-i48okJr0l1IrFTPa9KVkoJnDL2EHKExR6XC0Z7I9+kW9noxYWqo0tIoi5s1bNVD475xWK/rUjT7qHxiDbPaCUQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/api': 10.11.1 + '@polkadot/api-augment': 10.11.1 + '@polkadot/api-base': 10.11.1 + '@polkadot/rpc-core': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + rxjs: 7.8.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/api@10.11.1: + resolution: {integrity: sha512-WEgUYvY90AHX9drmsvWQ4DDuqlE7h4x3f28K5eOoJF4dQ5AkWsFogxwJ4TH57POWLfyi8AIn6/f1vsqPtReDhA==} + engines: {node: '>=18'} + dependencies: + '@polkadot/api-augment': 10.11.1 + '@polkadot/api-base': 10.11.1 + '@polkadot/api-derive': 10.11.1 + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/rpc-augment': 10.11.1 + '@polkadot/rpc-core': 10.11.1 + '@polkadot/rpc-provider': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-augment': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/types-create': 10.11.1 + '@polkadot/types-known': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + eventemitter3: 5.0.1 + rxjs: 7.8.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/keyring@12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1): + resolution: {integrity: sha512-cicTctZr5Jy5vgNT2FsNiKoTZnz6zQkgDoIYv79NI+p1Fhwc9C+DN/iMCnk3Cm9vR2gSAd2fSV+Y5iKVDhAmUw==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1 + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + tslib: 2.6.2 + + /@polkadot/networks@12.6.1: + resolution: {integrity: sha512-pzyirxTYAnsx+6kyLYcUk26e4TLz3cX6p2KhTgAVW77YnpGX5VTKTbYykyXC8fXFd/migeQsLaa2raFN47mwoA==} + engines: {node: '>=18'} + dependencies: + '@polkadot/util': 12.6.1 + '@substrate/ss58-registry': 1.44.0 + tslib: 2.6.2 + + /@polkadot/rpc-augment@10.11.1: + resolution: {integrity: sha512-wrtxHnEwqS3b1GuZ3sA1pzLuUjjLnW4FPawOklONRcIuKbGmFuvu7QvEIHmxBV1FAS/fs8gbvp8ImKWUPnT93Q==} + engines: {node: '>=18'} + dependencies: + '@polkadot/rpc-core': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/rpc-core@10.11.1: + resolution: {integrity: sha512-3l4l+zL7MDWzQx3WnaieXXUKsbeA1Miu4wsje5trYJEE+hm+nMW8h7fiFKfYzXBi7ty/wMS+S7BfQPTrDkYHxA==} + engines: {node: '>=18'} + dependencies: + '@polkadot/rpc-augment': 10.11.1 + '@polkadot/rpc-provider': 10.11.1 + '@polkadot/types': 10.11.1 + '@polkadot/util': 12.6.1 + rxjs: 7.8.1 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/rpc-provider@10.11.1: + resolution: {integrity: sha512-86aDUOnaG42si0jSOAgn6Fs3F3rz57x+iNBK1JpM0PLL2XvmPuoMZL5dZwzqSIey3nVdGJqRYfnFquWuyQpnOQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/types': 10.11.1 + '@polkadot/types-support': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + '@polkadot/x-fetch': 12.6.1 + '@polkadot/x-global': 12.6.1 + '@polkadot/x-ws': 12.6.1 + eventemitter3: 5.0.1 + mock-socket: 9.3.1 + nock: 13.4.0 + tslib: 2.6.2 + optionalDependencies: + '@substrate/connect': 0.7.35 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /@polkadot/types-augment@10.11.1: + resolution: {integrity: sha512-Exd5mMCuSOXXz73iWqy8ocScWTrwAPqHz0Kxpz5OWlAu+5usipMuhjoeaZA803FHQntZh9lHUN31fuc50Exhew==} + engines: {node: '>=18'} + dependencies: + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + dev: true + + /@polkadot/types-codec@10.11.1: + resolution: {integrity: sha512-B9Fu2hq3cRpJpGPcgfZ8Qi1OSX9u82J46adlbIG95ktoA+70eZ83VS3Zvtt9ACsdLVGETCJfDjSO25XptjhZKQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/x-bigint': 12.6.1 + tslib: 2.6.2 + dev: true + + /@polkadot/types-create@10.11.1: + resolution: {integrity: sha512-oeaI185F3XeWSz9/fe//qZ0KsQyE6C6c13WuOa+5cX/Yuz7cSAXawrhl58HRaU+fueaE/ijEHLcuK1sdM6e1JQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/types-codec': 10.11.1 + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + dev: true + + /@polkadot/types-known@10.11.1: + resolution: {integrity: sha512-BPHI7EbdRaznZR4RVVrQC5epyxL6caJ5dkluZP6rRwx7VmQK0FTGIwgh3UP724mzQhM8rT77MD3h2ftnq1cteg==} + engines: {node: '>=18'} + dependencies: + '@polkadot/networks': 12.6.1 + '@polkadot/types': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/types-create': 10.11.1 + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + dev: true + + /@polkadot/types-support@10.11.1: + resolution: {integrity: sha512-eCvWjdpELsHvXiTq201DdbIeOIaEr53zTD7HqC2wR/Z1bkQuw79Z+CyIU4sp79GL1vZ1PxS7vUH9M3FKNaTl1Q==} + engines: {node: '>=18'} + dependencies: + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + dev: true + + /@polkadot/types@10.11.1: + resolution: {integrity: sha512-4uKnzW2GZqNA5qRZpTPJ7z+G/ARTvXI89etv9xXXVttUdfTaYZsMf4rMuMThOAE/mAUn70LoH0JKthZLwzVgNQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/types-augment': 10.11.1 + '@polkadot/types-codec': 10.11.1 + '@polkadot/types-create': 10.11.1 + '@polkadot/util': 12.6.1 + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + rxjs: 7.8.1 + tslib: 2.6.2 + dev: true + + /@polkadot/util-crypto@12.6.1(@polkadot/util@12.6.1): + resolution: {integrity: sha512-2ezWFLmdgeDXqB9NAUdgpp3s2rQztNrZLY+y0SJYNOG4ch+PyodTW/qSksnOrVGVdRhZ5OESRE9xvo9LYV5UAw==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': 12.6.1 + dependencies: + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@polkadot/networks': 12.6.1 + '@polkadot/util': 12.6.1 + '@polkadot/wasm-crypto': 7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1) + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/x-bigint': 12.6.1 + '@polkadot/x-randomvalues': 12.6.1(@polkadot/util@12.6.1)(@polkadot/wasm-util@7.3.2) + '@scure/base': 1.1.3 + tslib: 2.6.2 + + /@polkadot/util@12.6.1: + resolution: {integrity: sha512-10ra3VfXtK8ZSnWI7zjhvRrhupg3rd4iFC3zCaXmRpOU+AmfIoCFVEmuUuC66gyXiz2/g6k5E6j0lWQCOProSQ==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-bigint': 12.6.1 + '@polkadot/x-global': 12.6.1 + '@polkadot/x-textdecoder': 12.6.1 + '@polkadot/x-textencoder': 12.6.1 + '@types/bn.js': 5.1.5 + bn.js: 5.2.1 + tslib: 2.6.2 + + /@polkadot/wasm-bridge@7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1): + resolution: {integrity: sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/x-randomvalues': 12.6.1(@polkadot/util@12.6.1)(@polkadot/wasm-util@7.3.2) + tslib: 2.6.2 + + /@polkadot/wasm-crypto-asmjs@7.3.2(@polkadot/util@12.6.1): + resolution: {integrity: sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + dependencies: + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + + /@polkadot/wasm-crypto-init@7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1): + resolution: {integrity: sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-bridge': 7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1) + '@polkadot/wasm-crypto-asmjs': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/wasm-crypto-wasm': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/x-randomvalues': 12.6.1(@polkadot/util@12.6.1)(@polkadot/wasm-util@7.3.2) + tslib: 2.6.2 + + /@polkadot/wasm-crypto-wasm@7.3.2(@polkadot/util@12.6.1): + resolution: {integrity: sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + tslib: 2.6.2 + + /@polkadot/wasm-crypto@7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1): + resolution: {integrity: sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + '@polkadot/x-randomvalues': '*' + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-bridge': 7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1) + '@polkadot/wasm-crypto-asmjs': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/wasm-crypto-init': 7.3.2(@polkadot/util@12.6.1)(@polkadot/x-randomvalues@12.6.1) + '@polkadot/wasm-crypto-wasm': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/x-randomvalues': 12.6.1(@polkadot/util@12.6.1)(@polkadot/wasm-util@7.3.2) + tslib: 2.6.2 + + /@polkadot/wasm-util@7.3.2(@polkadot/util@12.6.1): + resolution: {integrity: sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': '*' + dependencies: + '@polkadot/util': 12.6.1 + tslib: 2.6.2 + + /@polkadot/x-bigint@12.6.1: + resolution: {integrity: sha512-YlABeVIlgYQZJ4ZpW/+akFGGxw5jMGt4g5vaP7EumlORGneJHzzWJYDmI5v2y7j1zvC9ofOle7z4tRmtN/QDew==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-global': 12.6.1 + tslib: 2.6.2 + + /@polkadot/x-fetch@12.6.1: + resolution: {integrity: sha512-iyBv0ecfCsqGSv26CPJk9vSoKtry/Fn7x549ysA4hlc9KboraMHxOHTpcNZYC/OdgvbFZl40zIXCY0SA1ai8aw==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-global': 12.6.1 + node-fetch: 3.3.2 + tslib: 2.6.2 + dev: true + + /@polkadot/x-global@12.6.1: + resolution: {integrity: sha512-w5t19HIdBPuyu7X/AiCyH2DsKqxBF0KpF4Ymolnx8PfcSIgnq9ZOmgs74McPR6FgEmeEkr9uNKujZrsfURi1ug==} + engines: {node: '>=18'} + dependencies: + tslib: 2.6.2 + + /@polkadot/x-randomvalues@12.6.1(@polkadot/util@12.6.1)(@polkadot/wasm-util@7.3.2): + resolution: {integrity: sha512-1uVKlfYYbgIgGV5v1Dgn960cGovenWm5pmg+aTMeUGXVYiJwRD2zOpLyC1i/tP454iA74j74pmWb8Nkn0tJZUQ==} + engines: {node: '>=18'} + peerDependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-util': '*' + dependencies: + '@polkadot/util': 12.6.1 + '@polkadot/wasm-util': 7.3.2(@polkadot/util@12.6.1) + '@polkadot/x-global': 12.6.1 + tslib: 2.6.2 + + /@polkadot/x-textdecoder@12.6.1: + resolution: {integrity: sha512-IasodJeV1f2Nr/VtA207+LXCQEqYcG8y9qB/EQcRsrEP58NbwwxM5Z2obV0lSjJOxRTJ4/OlhUwnLHwcbIp6+g==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-global': 12.6.1 + tslib: 2.6.2 + + /@polkadot/x-textencoder@12.6.1: + resolution: {integrity: sha512-sTq/+tXqBhGe01a1rjieSHFh3y935vuRgtahVgVJZnfqh5SmLPgSN5tTPxZWzyx7gHIfotle8laTJbJarv7V1A==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-global': 12.6.1 + tslib: 2.6.2 + + /@polkadot/x-ws@12.6.1: + resolution: {integrity: sha512-fs9V+XekjJLpVLLwxnqq3llqSZu2T/b9brvld8anvzS/htDLPbi7+c5W3VGJ9Po8fS67IsU3HCt0Gu6F6mGrMA==} + engines: {node: '>=18'} + dependencies: + '@polkadot/x-global': 12.6.1 + tslib: 2.6.2 + ws: 8.15.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: true + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: true + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: true + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: true + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: true + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: true + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: true + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: true + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: true + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: true + + /@rollup/rollup-android-arm-eabi@4.7.0: + resolution: {integrity: sha512-rGku10pL1StFlFvXX5pEv88KdGW6DHUghsxyP/aRYb9eH+74jTGJ3U0S/rtlsQ4yYq1Hcc7AMkoJOb1xu29Fxw==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.7.0: + resolution: {integrity: sha512-/EBw0cuJ/KVHiU2qyVYUhogXz7W2vXxBzeE9xtVIMC+RyitlY2vvaoysMUqASpkUtoNIHlnKTu/l7mXOPgnKOA==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.7.0: + resolution: {integrity: sha512-4VXG1bgvClJdbEYYjQ85RkOtwN8sqI3uCxH0HC5w9fKdqzRzgG39K7GAehATGS8jghA7zNoS5CjSKkDEqWmNZg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.7.0: + resolution: {integrity: sha512-/ImhO+T/RWJ96hUbxiCn2yWI0/MeQZV/aeukQQfhxiSXuZJfyqtdHPUPrc84jxCfXTxbJLmg4q+GBETeb61aNw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.7.0: + resolution: {integrity: sha512-zhye8POvTyUXlKbfPBVqoHy3t43gIgffY+7qBFqFxNqVtltQLtWeHNAbrMnXiLIfYmxcoL/feuLDote2tx+Qbg==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.7.0: + resolution: {integrity: sha512-RAdr3OJnUum6Vs83cQmKjxdTg31zJnLLTkjhcFt0auxM6jw00GD6IPFF42uasYPr/wGC6TRm7FsQiJyk0qIEfg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.7.0: + resolution: {integrity: sha512-nhWwYsiJwZGq7SyR3afS3EekEOsEAlrNMpPC4ZDKn5ooYSEjDLe9W/xGvoIV8/F/+HNIY6jY8lIdXjjxfxopXw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.7.0: + resolution: {integrity: sha512-rlfy5RnQG1aop1BL/gjdH42M2geMUyVQqd52GJVirqYc787A/XVvl3kQ5NG/43KXgOgE9HXgCaEH05kzQ+hLoA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.7.0: + resolution: {integrity: sha512-cCkoGlGWfBobdDtiiypxf79q6k3/iRVGu1HVLbD92gWV5WZbmuWJCgRM4x2N6i7ljGn1cGytPn9ZAfS8UwF6vg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.7.0: + resolution: {integrity: sha512-R2oBf2p/Arc1m+tWmiWbpHBjEcJnHVnv6bsypu4tcKdrYTpDfl1UT9qTyfkIL1iiii5D4WHxUHCg5X0pzqmxFg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.7.0: + resolution: {integrity: sha512-CPtgaQL1aaPc80m8SCVEoxFGHxKYIt3zQYC3AccL/SqqiWXblo3pgToHuBwR8eCP2Toa+X1WmTR/QKFMykws7g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.7.0: + resolution: {integrity: sha512-pmioUlttNh9GXF5x2CzNa7Z8kmRTyhEzzAC+2WOOapjewMbl+3tGuAnxbwc5JyG8Jsz2+hf/QD/n5VjimOZ63g==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.7.0: + resolution: {integrity: sha512-SeZzC2QhhdBQUm3U0c8+c/P6UlRyBcLL2Xp5KX7z46WXZxzR8RJSIWL9wSUeBTgxog5LTPJuPj0WOT9lvrtP7Q==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@scure/base@1.1.3: + resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} + + /@scure/bip32@1.3.1: + resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==} + dependencies: + '@noble/curves': 1.1.0 + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + dev: true + + /@scure/bip32@1.3.2: + resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} + dependencies: + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + dev: true + + /@scure/bip39@1.2.1: + resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} + dependencies: + '@noble/hashes': 1.3.2 + '@scure/base': 1.1.3 + dev: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: true + + /@substrate/connect-extension-protocol@1.0.1: + resolution: {integrity: sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==} + requiresBuild: true + dev: true + optional: true + + /@substrate/connect@0.7.35: + resolution: {integrity: sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw==} + requiresBuild: true + dependencies: + '@substrate/connect-extension-protocol': 1.0.1 + smoldot: 2.0.7 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + optional: true + + /@substrate/ss58-registry@1.44.0: + resolution: {integrity: sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A==} + + /@tootallnate/once@1.1.2: + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + requiresBuild: true + optional: true + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@types/bn.js@5.1.5: + resolution: {integrity: sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==} + dependencies: + '@types/node': 20.10.4 + + /@types/long@4.0.2: + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + dev: true + + /@types/node@18.15.13: + resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} + dev: true + + /@types/node@20.10.3: + resolution: {integrity: sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/node@20.10.4: + resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} + dependencies: + undici-types: 5.26.5 + + /@types/ws@8.5.3: + resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} + dependencies: + '@types/node': 20.10.4 + dev: true + + /@vitest/expect@1.0.0-beta.5: + resolution: {integrity: sha512-q/TPdbXuEZZNFKILEVicojSWEq1y8qPLcAiZRQD8DsYUAV2cIjsD5lJWYaAjjUAV4lzovSci3KeISQdjUdfxQQ==} + dependencies: + '@vitest/spy': 1.0.0-beta.5 + '@vitest/utils': 1.0.0-beta.5 + chai: 4.3.10 + dev: true + + /@vitest/expect@1.0.1: + resolution: {integrity: sha512-3cdrb/eKD/0tygDX75YscuHEHMUJ70u3UoLSq2eqhWks57AyzvsDQbyn53IhZ0tBN7gA8Jj2VhXiOV2lef7thw==} + dependencies: + '@vitest/spy': 1.0.1 + '@vitest/utils': 1.0.1 + chai: 4.3.10 + dev: true + + /@vitest/runner@1.0.0-beta.5: + resolution: {integrity: sha512-o/6ZqQoKCIdI4dmdc4Yb1u3n56dU69SABXyO5yhFZTDjEMJs1DdCQ68JK+UcrpJMQndr6q5lTFrfHEhj4XJy6w==} + dependencies: + '@vitest/utils': 1.0.0-beta.5 + p-limit: 5.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/runner@1.0.1: + resolution: {integrity: sha512-/+z0vhJ0MfRPT3AyTvAK6m57rzlew/ct8B2a4LMv7NhpPaiI2QLGyOBMB3lcioWdJHjRuLi9aYppfOv0B5aRQA==} + dependencies: + '@vitest/utils': 1.0.1 + p-limit: 5.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@1.0.0-beta.5: + resolution: {integrity: sha512-fsWoc/mokLawqrLFqK9MHEyzJaGeDzU5gAgky2yZJR58VSsSvW+cesvmdv9ch39xHlTzFTRPgrWkNsmbdm2gbg==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/snapshot@1.0.1: + resolution: {integrity: sha512-wIPtPDGSxEZ+DpNMc94AsybX6LV6uN6sosf5TojyP1m2QbKwiRuLV/5RSsjt1oWViHsTj8mlcwrQQ1zHGO0fMw==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.0.0-beta.5: + resolution: {integrity: sha512-B5dx87eCiJidWGdURMS/etHE9P3JRdFEQj8HQRGI3OhMy5XcSrdAwg5oEADoqXm32GUGc7bC8Dw/q9PiCJSBIQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/spy@1.0.1: + resolution: {integrity: sha512-yXwm1uKhBVr/5MhVeSmtNqK+0q2RXIchJt8kokEKdrWLtkPeDgdbZ6SjR1VQGZuNdWL6sSBnLayIyVvcS0qLfA==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/ui@1.0.1(vitest@1.0.1): + resolution: {integrity: sha512-3hFMgy/RExKi7UlYEqqnZ65QALdkgXyW1k7Zn7PykVmVBcKe/aI4ZpZ006WeTWvnUWeR+37lbpUD0JhnmKn72A==} + peerDependencies: + vitest: ^1.0.0 + dependencies: + '@vitest/utils': 1.0.1 + fast-glob: 3.3.2 + fflate: 0.8.1 + flatted: 3.2.9 + pathe: 1.1.1 + picocolors: 1.0.0 + sirv: 2.0.3 + vitest: 1.0.1(@types/node@20.10.4)(@vitest/ui@1.0.1) + dev: true + + /@vitest/utils@1.0.0-beta.5: + resolution: {integrity: sha512-5ippiVcc6KjnAZiMc5Gz5g1tWTG+21g5scr+cedYC+YxAjqZzOG/ncJuM/Eqq9a+/MAJJc5zOGBcDYl27x62jg==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /@vitest/utils@1.0.1: + resolution: {integrity: sha512-MGPCHkzXbbAyscrhwGzh8uP1HPrTYLWaj1WTDtWSGrpe2yJWLRN9mF9ooKawr6NMOg9vTBtg2JqWLfuLC7Dknw==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /@zombienet/orchestrator@0.0.68(@polkadot/util@12.6.1)(@types/node@20.10.4): + resolution: {integrity: sha512-n/Gj1VWGz6W4Phzw5r/rb56uMT3H3B06xRzP+PZJtG2dGMqWAUZP2DcNhLWZ8w3/NEsWrrNTfSejaQvuaUApGg==} + engines: {node: '>=18'} + dependencies: + '@polkadot/api': 10.11.1 + '@polkadot/keyring': 12.6.1(@polkadot/util-crypto@12.6.1)(@polkadot/util@12.6.1) + '@polkadot/util-crypto': 12.6.1(@polkadot/util@12.6.1) + '@zombienet/utils': 0.0.24(@types/node@20.10.4)(typescript@5.3.3) + JSONStream: 1.3.5 + chai: 4.3.10 + debug: 4.3.4(supports-color@8.1.1) + execa: 5.1.1 + fs-extra: 11.2.0 + jsdom: 23.0.1 + json-bigint: 1.0.0 + libp2p-crypto: 0.21.2 + minimatch: 9.0.3 + mocha: 10.2.0 + napi-maybe-compressed-blob: 0.0.11 + peer-id: 0.16.0 + tmp-promise: 3.0.3 + typescript: 5.3.3 + yaml: 2.3.4 + transitivePeerDependencies: + - '@polkadot/util' + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - bufferutil + - canvas + - chokidar + - supports-color + - utf-8-validate + dev: true + + /@zombienet/utils@0.0.24(@types/node@20.10.3)(typescript@5.3.3): + resolution: {integrity: sha512-CUHn4u04ryfRqCQQsZHSpMIpMxzdMvSZR86Gp3Hwexf7wZTkHNZ5hsJnQO+J/yl28ny0GcjLJSU1hZ2kMV+hqw==} + engines: {node: '>=18'} + dependencies: + cli-table3: 0.6.3 + debug: 4.3.4(supports-color@8.1.1) + mocha: 10.2.0 + nunjucks: 3.2.4 + toml: 3.0.0 + ts-node: 10.9.2(@types/node@20.10.3)(typescript@5.3.3) + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - chokidar + - supports-color + - typescript + dev: true + + /@zombienet/utils@0.0.24(@types/node@20.10.4)(typescript@5.3.3): + resolution: {integrity: sha512-CUHn4u04ryfRqCQQsZHSpMIpMxzdMvSZR86Gp3Hwexf7wZTkHNZ5hsJnQO+J/yl28ny0GcjLJSU1hZ2kMV+hqw==} + engines: {node: '>=18'} + dependencies: + cli-table3: 0.6.3 + debug: 4.3.4(supports-color@8.1.1) + mocha: 10.2.0 + nunjucks: 3.2.4 + toml: 3.0.0 + ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3) + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - chokidar + - supports-color + - typescript + dev: true + + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + dev: true + + /a-sync-waterfall@1.0.1: + resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + dev: true + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + requiresBuild: true + + /abitype@0.7.1(typescript@5.3.3): + resolution: {integrity: sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==} + peerDependencies: + typescript: '>=4.9.4' + zod: ^3 >=3.19.1 + peerDependenciesMeta: + zod: + optional: true + dependencies: + typescript: 5.3.3 + dev: true + + /abitype@0.9.8(typescript@5.3.3): + resolution: {integrity: sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.19.1 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + dependencies: + typescript: 5.3.3 + dev: true + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: true + + /acorn-walk@8.3.1: + resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + /agent-base@7.1.0: + resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + requiresBuild: true + dependencies: + humanize-ms: 1.2.1 + optional: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + optional: true + + /ansi-colors@4.1.1: + resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} + engines: {node: '>=6'} + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: true + + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + requiresBuild: true + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.5 + is-array-buffer: 3.0.2 + dev: true + + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + + /atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /axios@1.6.2(debug@4.3.4): + resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} + dependencies: + follow-redirects: 1.15.3(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /bl@5.1.0: + resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + dependencies: + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + dev: true + + /bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + requiresBuild: true + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + dev: true + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + requiresBuild: true + dependencies: + '@npmcli/fs': 1.1.1 + '@npmcli/move-file': 1.1.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 7.2.3 + infer-owner: 1.0.4 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 8.0.1 + tar: 6.2.0 + unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird + optional: true + + /call-bind@1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + /class-is@1.1.0: + resolution: {integrity: sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==} + dev: true + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + requiresBuild: true + optional: true + + /clear@0.1.0: + resolution: {integrity: sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==} + dev: true + + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + dev: true + + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + dev: true + + /cli-progress@3.12.0: + resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} + engines: {node: '>=4'} + dependencies: + string-width: 4.2.3 + dev: true + + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true + + /cli-table3@0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + dev: true + + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: true + + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + + /colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + dev: true + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + + /comlink@4.4.1: + resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==} + dev: true + + /commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + requiresBuild: true + + /config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: true + + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + /crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + dev: true + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /cssstyle@3.0.0: + resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==} + engines: {node: '>=14'} + dependencies: + rrweb-cssom: 0.6.0 + dev: true + + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + + /data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + dev: true + + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.23.5 + dev: true + + /dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dev: true + + /debug@4.3.4(supports-color@8.1.1): + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + supports-color: 8.1.1 + + /decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + dev: true + + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.5 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.2 + is-arguments: 1.1.1 + is-array-buffer: 3.0.2 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.1 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.13 + dev: true + + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + + /define-data-property@1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + has-property-descriptors: 1.0.1 + object-keys: 1.1.1 + dev: true + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true + + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + + /detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + dev: true + + /diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: true + + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + optional: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + requiresBuild: true + optional: true + + /err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + requiresBuild: true + optional: true + + /err-code@3.0.1: + resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} + dev: true + + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + dev: true + + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + dev: true + + /esbuild@0.19.8: + resolution: {integrity: sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.19.8 + '@esbuild/android-arm64': 0.19.8 + '@esbuild/android-x64': 0.19.8 + '@esbuild/darwin-arm64': 0.19.8 + '@esbuild/darwin-x64': 0.19.8 + '@esbuild/freebsd-arm64': 0.19.8 + '@esbuild/freebsd-x64': 0.19.8 + '@esbuild/linux-arm': 0.19.8 + '@esbuild/linux-arm64': 0.19.8 + '@esbuild/linux-ia32': 0.19.8 + '@esbuild/linux-loong64': 0.19.8 + '@esbuild/linux-mips64el': 0.19.8 + '@esbuild/linux-ppc64': 0.19.8 + '@esbuild/linux-riscv64': 0.19.8 + '@esbuild/linux-s390x': 0.19.8 + '@esbuild/linux-x64': 0.19.8 + '@esbuild/netbsd-x64': 0.19.8 + '@esbuild/openbsd-x64': 0.19.8 + '@esbuild/sunos-x64': 0.19.8 + '@esbuild/win32-arm64': 0.19.8 + '@esbuild/win32-ia32': 0.19.8 + '@esbuild/win32-x64': 0.19.8 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + + /ethereum-cryptography@2.1.2: + resolution: {integrity: sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==} + dependencies: + '@noble/curves': 1.1.0 + '@noble/hashes': 1.3.1 + '@scure/bip32': 1.3.1 + '@scure/bip39': 1.2.1 + dev: true + + /ethers@6.8.0: + resolution: {integrity: sha512-zrFbmQRlraM+cU5mE4CZTLBurZTs2gdp2ld0nG/f3ecBK+x6lZ69KSxBqZ4NjclxwfTxl5LeNufcBbMsTdY53Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: true + + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + dev: true + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-copy@3.0.1: + resolution: {integrity: sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==} + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-redact@3.3.0: + resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + engines: {node: '>=6'} + dev: true + + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dev: true + + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: true + + /fflate@0.8.1: + resolution: {integrity: sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==} + dev: true + + /figures@5.0.0: + resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} + engines: {node: '>=14'} + dependencies: + escape-string-regexp: 5.0.0 + is-unicode-supported: 1.3.0 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: true + + /flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + dev: true + + /follow-redirects@1.15.3(debug@4.3.4): + resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) + dev: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + + /fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + requiresBuild: true + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-intrinsic@1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + dev: true + + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + requiresBuild: true + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + + /global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.5.4 + serialize-error: 7.0.1 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.1 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-property-descriptors@1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: true + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + dev: true + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: true + + /html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + dependencies: + whatwg-encoding: 3.1.1 + dev: true + + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + requiresBuild: true + optional: true + + /http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + requiresBuild: true + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + optional: true + + /http-proxy-agent@7.0.0: + resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + /https-proxy-agent@7.0.2: + resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + requiresBuild: true + dependencies: + ms: 2.1.3 + optional: true + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + requiresBuild: true + optional: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + requiresBuild: true + optional: true + + /infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + requiresBuild: true + optional: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true + + /inquirer-press-to-continue@1.2.0(inquirer@9.2.12): + resolution: {integrity: sha512-HdKOgEAydYhI3OKLy5S4LMi7a/AHJjPzF06mHqbdVxlTmHOaytQVBaVbQcSytukD70K9FYLhYicNOPuNjFiWVQ==} + peerDependencies: + inquirer: '>=8.0.0 <10.0.0' + dependencies: + deep-equal: 2.2.3 + inquirer: 9.2.12 + ora: 6.3.1 + dev: true + + /inquirer@9.2.12: + resolution: {integrity: sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==} + engines: {node: '>=14.18.0'} + dependencies: + '@ljharb/through': 2.3.11 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 5.0.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /internal-slot@1.0.6: + resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + hasown: 2.0.0 + side-channel: 1.0.4 + dev: true + + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + requiresBuild: true + optional: true + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: true + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + + /is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + dev: true + + /is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + requiresBuild: true + optional: true + + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: true + + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.5 + dev: true + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.13 + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + dev: true + + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + requiresBuild: true + + /iso-random-stream@2.0.2: + resolution: {integrity: sha512-yJvs+Nnelic1L2vH2JzWvvPQFA4r7kSTnpST/+LkAQjSz0hos2oqLD+qIVi9Qk38Hoe7mNDt3j0S27R58MVjLQ==} + engines: {node: '>=10'} + dependencies: + events: 3.3.0 + readable-stream: 3.6.2 + dev: true + + /isomorphic-ws@5.0.0(ws@8.15.1): + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + dependencies: + ws: 8.15.1 + dev: true + + /isows@1.0.3(ws@8.13.0): + resolution: {integrity: sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==} + peerDependencies: + ws: '*' + dependencies: + ws: 8.13.0 + dev: true + + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsdom@23.0.1: + resolution: {integrity: sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + cssstyle: 3.0.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.0 + https-proxy-agent: 7.0.2 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.7 + parse5: 7.1.2 + rrweb-cssom: 0.6.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.15.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + dependencies: + bignumber.js: 9.1.2 + dev: true + + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsondiffpatch@0.5.0: + resolution: {integrity: sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw==} + engines: {node: '>=8.17.0'} + hasBin: true + dependencies: + chalk: 3.0.0 + diff-match-patch: 1.0.5 + dev: true + bundledDependencies: [] + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: true + + /libp2p-crypto@0.21.2: + resolution: {integrity: sha512-EXFrhSpiHtJ+/L8xXDvQNK5VjUMG51u878jzZcaT5XhuN/zFg6PWJFnl/qB2Y2j7eMWnvCRP7Kp+ua2H36cG4g==} + engines: {node: '>=12.0.0'} + dependencies: + '@noble/ed25519': 1.7.3 + '@noble/secp256k1': 1.7.1 + err-code: 3.0.1 + iso-random-stream: 2.0.2 + multiformats: 9.9.0 + node-forge: 1.3.1 + protobufjs: 6.11.4 + uint8arrays: 3.1.1 + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.4.2 + pkg-types: 1.0.3 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-symbols@5.1.0: + resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} + engines: {node: '>=12'} + dependencies: + chalk: 5.3.0 + is-unicode-supported: 1.3.0 + dev: true + + /long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + dev: true + + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /lru-cache@10.1.0: + resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} + engines: {node: 14 || >=16.14} + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.1 + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + requiresBuild: true + dependencies: + agentkeepalive: 4.5.0 + cacache: 15.3.0 + http-cache-semantics: 4.1.1 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 1.4.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 6.2.1 + ssri: 8.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + + /matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 4.0.0 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimatch@5.0.1: + resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + optional: true + + /minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + optional: true + + /minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + optional: true + + /minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + optional: true + + /minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + optional: true + + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.11.2 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.2 + dev: true + + /mocha@10.2.0: + resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} + engines: {node: '>= 14.0.0'} + hasBin: true + dependencies: + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.4(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.0.1 + ms: 2.1.3 + nanoid: 3.3.3 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.2.1 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + dev: true + + /mock-socket@9.3.1: + resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==} + engines: {node: '>= 8'} + dev: true + + /mrmime@1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + dev: true + + /mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /nanoid@3.3.3: + resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /napi-maybe-compressed-blob-darwin-arm64@0.0.11: + resolution: {integrity: sha512-hZ9ye4z8iMDVPEnx9A/Ag6k7xHX/BcK5Lntw/VANBUm9ioLSuRvHTALG4XaqVDGXo4U2NFDwSLRDyhFPYvqckQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /napi-maybe-compressed-blob-darwin-x64@0.0.11: + resolution: {integrity: sha512-TqWNP7Vehi73xLXyUGjdLppP0W6T0Ef2D/X9HmAZNwglt+MkTujX10CDODfbFWvGy+NkaC5XqnzxCn19wbZZcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /napi-maybe-compressed-blob-linux-arm64-gnu@0.0.11: + resolution: {integrity: sha512-7D5w6MDZghcb3VtXRg2ShCEh9Z3zMeBVRG4xsMulEWT2j9/09Nopu+9KfI/2ngRvm78MniWSIlqds5PRAlCROA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /napi-maybe-compressed-blob-linux-x64-gnu@0.0.11: + resolution: {integrity: sha512-JKY8KcZpQtKiL1smMKfukcOmsDVeZaw9fKXKsWC+wySc2wsvH7V2wy8PffSQ0lWERkI7Yn3k7xPjB463m/VNtg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /napi-maybe-compressed-blob@0.0.11: + resolution: {integrity: sha512-1dj4ET34TfEes0+josVLvwpJe337Jk6txd3XUjVmVs3budSo2eEjvN6pX4myYE1pS4x/k2Av57n/ypRl2u++AQ==} + engines: {node: '>= 10'} + optionalDependencies: + napi-maybe-compressed-blob-darwin-arm64: 0.0.11 + napi-maybe-compressed-blob-darwin-x64: 0.0.11 + napi-maybe-compressed-blob-linux-arm64-gnu: 0.0.11 + napi-maybe-compressed-blob-linux-x64-gnu: 0.0.11 + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + requiresBuild: true + optional: true + + /nock@13.4.0: + resolution: {integrity: sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==} + engines: {node: '>= 10.13'} + dependencies: + debug: 4.3.4(supports-color@8.1.1) + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /node-addon-api@4.3.0: + resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + + /node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + dev: true + + /node-gyp@8.4.1: + resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} + engines: {node: '>= 10.12.0'} + hasBin: true + requiresBuild: true + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 9.1.0 + nopt: 5.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.0 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + abbrev: 1.1.1 + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + requiresBuild: true + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + optional: true + + /nunjucks@3.2.4: + resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} + engines: {node: '>= 6.9.0'} + hasBin: true + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + a-sync-waterfall: 1.0.1 + asap: 2.0.6 + commander: 5.1.0 + dev: true + + /nwsapi@2.2.7: + resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + dev: true + + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + + /ora@6.3.1: + resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + chalk: 5.3.0 + cli-cursor: 4.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 1.3.0 + log-symbols: 5.1.0 + stdin-discarder: 0.1.0 + strip-ansi: 7.1.0 + wcwidth: 1.0.1 + dev: true + + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + aggregate-error: 3.1.0 + optional: true + + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: true + + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: true + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: true + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /peer-id@0.16.0: + resolution: {integrity: sha512-EmL7FurFUduU9m1PS9cfJ5TAuCvxKQ7DKpfx3Yj6IKWyBRtosriFuOag/l3ni/dtPgPLwiA4R9IvpL7hsDLJuQ==} + engines: {node: '>=15.0.0'} + dependencies: + class-is: 1.1.0 + libp2p-crypto: 0.21.2 + multiformats: 9.9.0 + protobufjs: 6.11.4 + uint8arrays: 3.1.1 + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pino-abstract-transport@1.1.0: + resolution: {integrity: sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==} + dependencies: + readable-stream: 4.4.2 + split2: 4.2.0 + dev: true + + /pino-pretty@10.3.0: + resolution: {integrity: sha512-JthvQW289q3454mhM3/38wFYGWPiBMR28T3CpDNABzoTQOje9UKS7XCJQSnjWF9LQGQkGd8D7h0oq+qwiM3jFA==} + hasBin: true + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.1 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.1.0 + pump: 3.0.0 + readable-stream: 4.4.2 + secure-json-parse: 2.7.0 + sonic-boom: 3.7.0 + strip-json-comments: 3.1.1 + dev: true + + /pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + dev: true + + /pino@8.17.1: + resolution: {integrity: sha512-YoN7/NJgnsJ+fkADZqjhRt96iepWBndQHeClmSBH0sQWCb8zGD74t00SK4eOtKFi/f8TUmQnfmgglEhd2kI1RQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.3.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.1.0 + pino-std-serializers: 6.2.2 + process-warning: 2.3.2 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 3.7.0 + thread-stream: 2.4.1 + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + + /postcss@8.4.32: + resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /process-warning@2.3.2: + resolution: {integrity: sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==} + dev: true + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + + /promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + requiresBuild: true + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + optional: true + + /promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + optional: true + + /propagate@2.0.1: + resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} + engines: {node: '>= 8'} + dev: true + + /proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: true + + /protobufjs@6.11.4: + resolution: {integrity: sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==} + hasBin: true + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + '@types/node': 20.10.4 + long: 4.0.0 + dev: true + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: true + + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readable-stream@4.4.2: + resolution: {integrity: sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: true + + /reflect-metadata@0.1.14: + resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} + dev: true + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: true + + /regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + set-function-name: 2.0.1 + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + requiresBuild: true + optional: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + + /rlp@3.0.0: + resolution: {integrity: sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==} + hasBin: true + dev: true + + /roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.3 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + dev: true + + /rollup@4.7.0: + resolution: {integrity: sha512-7Kw0dUP4BWH78zaZCqF1rPyQ8D5DSU6URG45v1dqS/faNsx9WXyess00uTOZxKr7oR/4TOjO1CPudT8L1UsEgw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.7.0 + '@rollup/rollup-android-arm64': 4.7.0 + '@rollup/rollup-darwin-arm64': 4.7.0 + '@rollup/rollup-darwin-x64': 4.7.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.7.0 + '@rollup/rollup-linux-arm64-gnu': 4.7.0 + '@rollup/rollup-linux-arm64-musl': 4.7.0 + '@rollup/rollup-linux-riscv64-gnu': 4.7.0 + '@rollup/rollup-linux-x64-gnu': 4.7.0 + '@rollup/rollup-linux-x64-musl': 4.7.0 + '@rollup/rollup-win32-arm64-msvc': 4.7.0 + '@rollup/rollup-win32-ia32-msvc': 4.7.0 + '@rollup/rollup-win32-x64-msvc': 4.7.0 + fsevents: 2.3.3 + dev: true + + /rrweb-cssom@0.6.0: + resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} + dev: true + + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.2 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + dev: true + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + + /secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: true + + /semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + dev: true + + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + dependencies: + type-fest: 0.13.1 + dev: true + + /serialize-javascript@6.0.0: + resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} + dependencies: + randombytes: 2.1.0 + dev: true + + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + /set-function-length@1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.1 + dev: true + + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: true + + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + object-inspect: 1.13.1 + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /sirv@2.0.3: + resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.24 + mrmime: 1.0.1 + totalist: 3.0.1 + dev: true + + /smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + requiresBuild: true + optional: true + + /smoldot@2.0.7: + resolution: {integrity: sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA==} + requiresBuild: true + dependencies: + ws: 8.15.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + optional: true + + /socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + requiresBuild: true + dependencies: + agent-base: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + optional: true + + /socks@2.7.1: + resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + requiresBuild: true + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + optional: true + + /sonic-boom@3.7.0: + resolution: {integrity: sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==} + dependencies: + atomic-sleep: 1.0.0 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: true + + /sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + dev: true + + /sqlite3@5.1.6: + resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} + requiresBuild: true + peerDependenciesMeta: + node-gyp: + optional: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 4.3.0 + tar: 6.2.0 + optionalDependencies: + node-gyp: 8.4.1 + transitivePeerDependencies: + - bluebird + - encoding + - supports-color + + /ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + requiresBuild: true + dependencies: + minipass: 3.3.6 + optional: true + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.6.0: + resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==} + dev: true + + /stdin-discarder@0.1.0: + resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + bl: 5.1.0 + dev: true + + /stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.6 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.2 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + + /tar@6.2.0: + resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + + /thread-stream@2.4.1: + resolution: {integrity: sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==} + dependencies: + real-require: 0.2.0 + dev: true + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + + /tinypool@0.8.1: + resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + + /tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + dependencies: + tmp: 0.2.1 + dev: true + + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /tmp@0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: true + + /totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: true + + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + /tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + dependencies: + punycode: 2.3.1 + dev: true + + /ts-node@10.9.2(@types/node@20.10.3)(typescript@5.3.3): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.10.3 + acorn: 8.11.2 + acorn-walk: 8.3.1 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.10.4 + acorn: 8.11.2 + acorn-walk: 8.3.1 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tslib@2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + + /typeorm@0.3.17(sqlite3@5.1.6)(ts-node@10.9.2): + resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} + engines: {node: '>= 12.9.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.2.0 + mssql: ^9.1.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^5.1.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + date-fns: 2.30.0 + debug: 4.3.4(supports-color@8.1.1) + dotenv: 16.3.1 + glob: 8.1.0 + mkdirp: 2.1.6 + reflect-metadata: 0.1.14 + sha.js: 2.4.11 + sqlite3: 5.1.6 + ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3) + tslib: 2.6.2 + uuid: 9.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: true + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + /ufo@1.3.2: + resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + dev: true + + /uint8arrays@3.1.1: + resolution: {integrity: sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==} + dependencies: + multiformats: 9.9.0 + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + /unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + requiresBuild: true + dependencies: + unique-slug: 2.0.2 + optional: true + + /unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} + requiresBuild: true + dependencies: + imurmurhash: 0.1.4 + optional: true + + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: true + + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + requiresBuild: true + + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.12 + which-typed-array: 1.1.13 + dev: true + + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: true + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /viem@1.16.6(typescript@5.3.3): + resolution: {integrity: sha512-jcWcFQ+xzIfDwexwPJRvCuCRJKEkK9iHTStG7mpU5MmuSBpACs4nATBDyXNFtUiyYTFzLlVEwWkt68K0nCSImg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.9.4 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 0.9.8(typescript@5.3.3) + isows: 1.0.3(ws@8.13.0) + typescript: 5.3.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + dev: true + + /viem@1.18.0(typescript@5.3.3): + resolution: {integrity: sha512-NeKi5RFj7fHdsnk5pojivHFLkTyBWyehxeSE/gSPTDJKCWnR9i+Ra0W++VwN5ghciEG55O8b4RdpYhzGmhnr7A==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.9.4 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 0.9.8(typescript@5.3.3) + isows: 1.0.3(ws@8.13.0) + typescript: 5.3.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + dev: true + + /vite-node@1.0.0-beta.5(@types/node@20.10.4): + resolution: {integrity: sha512-iXm+GTJbR9R6V/bCM1+LQqIohL/tncZVNGIcTtzpYThBD8yiTkDPvEjy1Mf7KFACtG3qY/0VDMrkuMtqG/JFhg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.7(@types/node@20.10.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite-node@1.0.1(@types/node@20.10.4): + resolution: {integrity: sha512-Y2Jnz4cr2azsOMMYuVPrQkp3KMnS/0WV8ezZjCy4hU7O5mUHCAVOnFmoEvs1nvix/4mYm74Len8bYRWZJMNP6g==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.7(@types/node@20.10.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@5.0.7(@types/node@20.10.4): + resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.10.4 + esbuild: 0.19.8 + postcss: 8.4.32 + rollup: 4.7.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitest@1.0.0-beta.5(@types/node@20.10.4)(@vitest/ui@1.0.1): + resolution: {integrity: sha512-wmrGmXMKysR+JBvIwy0COgLrRSsZTR00dN+IpWBxGC4ACF5Mt/uYyrPLJZ0ixK4P3bxI16vd92JXMsuGnm9gQQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.10.4 + '@vitest/expect': 1.0.0-beta.5 + '@vitest/runner': 1.0.0-beta.5 + '@vitest/snapshot': 1.0.0-beta.5 + '@vitest/spy': 1.0.0-beta.5 + '@vitest/ui': 1.0.1(vitest@1.0.1) + '@vitest/utils': 1.0.0-beta.5 + acorn-walk: 8.3.1 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4(supports-color@8.1.1) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.6.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.1 + vite: 5.0.7(@types/node@20.10.4) + vite-node: 1.0.0-beta.5(@types/node@20.10.4) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vitest@1.0.1(@types/node@20.10.4)(@vitest/ui@1.0.1): + resolution: {integrity: sha512-MHsOj079S28hDsvdDvyD1pRj4dcS51EC5Vbe0xvOYX+WryP8soiK2dm8oULi+oA/8Xa/h6GoJEMTmcmBy5YM+Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.10.4 + '@vitest/expect': 1.0.1 + '@vitest/runner': 1.0.1 + '@vitest/snapshot': 1.0.1 + '@vitest/spy': 1.0.1 + '@vitest/ui': 1.0.1(vitest@1.0.1) + '@vitest/utils': 1.0.1 + acorn-walk: 8.3.1 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4(supports-color@8.1.1) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.6.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.1 + vite: 5.0.7(@types/node@20.10.4) + vite-node: 1.0.1(@types/node@20.10.4) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + dependencies: + xml-name-validator: 5.0.0 + dev: true + + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + + /web3-core@4.3.2: + resolution: {integrity: sha512-uIMVd/j4BgOnwfpY8ZT+QKubOyM4xohEhFZXz9xB8wimXWMMlYVlIK/TbfHqFolS9uOerdSGhsMbcK9lETae8g==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-errors: 1.1.4 + web3-eth-accounts: 4.1.0 + web3-eth-iban: 4.0.7 + web3-providers-http: 4.1.0 + web3-providers-ws: 4.0.7 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + optionalDependencies: + web3-providers-ipc: 4.0.7 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + + /web3-errors@1.1.4: + resolution: {integrity: sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-types: 1.3.1 + dev: true + + /web3-eth-abi@4.1.4(typescript@5.3.3): + resolution: {integrity: sha512-YLOBVVxxxLYKXjaiwZjEWYEnkMmmrm0nswZsvzSsINy/UgbWbzfoiZU+zn4YNWIEhORhx1p37iS3u/dP6VyC2w==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + abitype: 0.7.1(typescript@5.3.3) + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - typescript + - zod + dev: true + + /web3-eth-accounts@4.1.0: + resolution: {integrity: sha512-UFtAsOANsvihTQ6SSvOKguupmQkResyR9M9JNuOxYpKh7+3W+sTnbLXw2UbOSYIsKlc1mpqqW9bVr1SjqHDpUQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + '@ethereumjs/rlp': 4.0.1 + crc-32: 1.2.2 + ethereum-cryptography: 2.1.2 + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + dev: true + + /web3-eth-contract@4.1.4(typescript@5.3.3): + resolution: {integrity: sha512-tJ4z6QLgtu8EQu2sXnLA7g427oxmngnbAUh+9kJKbP6Yep/oe+z79PqJv7H3MwqwUNW9T+/FeB2PnSQSyxz6ig==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-core: 4.3.2 + web3-errors: 1.1.4 + web3-eth: 4.3.1(typescript@5.3.3) + web3-eth-abi: 4.1.4(typescript@5.3.3) + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + dev: true + + /web3-eth-ens@4.0.8(typescript@5.3.3): + resolution: {integrity: sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + '@adraffy/ens-normalize': 1.10.0 + web3-core: 4.3.2 + web3-errors: 1.1.4 + web3-eth: 4.3.1(typescript@5.3.3) + web3-eth-contract: 4.1.4(typescript@5.3.3) + web3-net: 4.0.7 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + dev: true + + /web3-eth-iban@4.0.7: + resolution: {integrity: sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + dev: true + + /web3-eth-personal@4.0.8(typescript@5.3.3): + resolution: {integrity: sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-core: 4.3.2 + web3-eth: 4.3.1(typescript@5.3.3) + web3-rpc-methods: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + dev: true + + /web3-eth@4.3.1(typescript@5.3.3): + resolution: {integrity: sha512-zJir3GOXooHQT85JB8SrufE+Voo5TtXdjhf1D8IGXmxM8MrhI8AT+Pgt4siBTupJcu5hF17iGmTP/Nj2XnaibQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + setimmediate: 1.0.5 + web3-core: 4.3.2 + web3-errors: 1.1.4 + web3-eth-abi: 4.1.4(typescript@5.3.3) + web3-eth-accounts: 4.1.0 + web3-net: 4.0.7 + web3-providers-ws: 4.0.7 + web3-rpc-methods: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + dev: true + + /web3-net@4.0.7: + resolution: {integrity: sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-core: 4.3.2 + web3-rpc-methods: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + + /web3-providers-http@4.1.0: + resolution: {integrity: sha512-6qRUGAhJfVQM41E5t+re5IHYmb5hSaLc02BE2MaRQsz2xKA6RjmHpOA5h/+ojJxEpI9NI2CrfDKOAgtJfoUJQg==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + cross-fetch: 4.0.0 + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + transitivePeerDependencies: + - encoding + dev: true + + /web3-providers-ipc@4.0.7: + resolution: {integrity: sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g==} + engines: {node: '>=14', npm: '>=6.12.0'} + requiresBuild: true + dependencies: + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + dev: true + optional: true + + /web3-providers-ws@4.0.7: + resolution: {integrity: sha512-n4Dal9/rQWjS7d6LjyEPM2R458V8blRm0eLJupDEJOOIBhGYlxw5/4FthZZ/cqB7y/sLVi7K09DdYx2MeRtU5w==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + '@types/ws': 8.5.3 + isomorphic-ws: 5.0.0(ws@8.15.1) + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + ws: 8.15.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + + /web3-rpc-methods@1.1.4: + resolution: {integrity: sha512-LTFNg4LFaeU8K9ecuT8fHDp/LOXyxCneeZjCrRYIW1u82Ly52SrY55FIzMIISGoG/iT5Wh7UiHOB3CQsWLBmbQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + web3-core: 4.3.2 + web3-types: 1.3.1 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: true + + /web3-types@1.3.1: + resolution: {integrity: sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dev: true + + /web3-utils@4.1.0: + resolution: {integrity: sha512-+VJWR6FtCsgwuJr5tvSvQlSEG06586df8h2CxGc9tcNtIDyJKNkSDDWJkdNPvyDhhXFzQYFh8QOGymD1CIP6fw==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + ethereum-cryptography: 2.1.2 + web3-errors: 1.1.4 + web3-types: 1.3.1 + web3-validator: 2.0.3 + dev: true + + /web3-validator@2.0.3: + resolution: {integrity: sha512-fJbAQh+9LSNWy+l5Ze6HABreml8fra98o5+vS073T35jUcLbRZ0IOjF/ZPJhJNbJDt+jP1vseZsc3z3uX9mxxQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + dependencies: + ethereum-cryptography: 2.1.2 + util: 0.12.5 + web3-errors: 1.1.4 + web3-types: 1.3.1 + zod: 3.22.4 + dev: true + + /web3@4.2.1(typescript@5.3.3): + resolution: {integrity: sha512-zSB+Ds1lSMu/IhAX0xKhiFI7ZA1BQ6y2WOqFE9ikqPjaMkpOEBBkl61nzWfLJRoerTB1ohEXAP20jLDXcFd4hQ==} + engines: {node: '>=14.0.0', npm: '>=6.12.0'} + dependencies: + web3-core: 4.3.2 + web3-errors: 1.1.4 + web3-eth: 4.3.1(typescript@5.3.3) + web3-eth-abi: 4.1.4(typescript@5.3.3) + web3-eth-accounts: 4.1.0 + web3-eth-contract: 4.1.4(typescript@5.3.3) + web3-eth-ens: 4.0.8(typescript@5.3.3) + web3-eth-iban: 4.0.7 + web3-eth-personal: 4.0.8(typescript@5.3.3) + web3-net: 4.0.7 + web3-providers-http: 4.1.0 + web3-providers-ws: 4.0.7 + web3-rpc-methods: 1.1.4 + web3-types: 1.3.1 + web3-utils: 4.1.0 + web3-validator: 2.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + - zod + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + + /whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + dependencies: + iconv-lite: 0.6.3 + dev: true + + /whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + dev: true + + /whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array@1.1.13: + resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + + /workerpool@6.2.1: + resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} + dev: true + + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + requiresBuild: true + + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /ws@8.14.2: + resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /ws@8.15.1: + resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + /ws@8.5.0: + resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + + /xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + dev: true + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yaml@2.3.3: + resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==} + engines: {node: '>= 14'} + dev: true + + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + dev: true + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + dev: true + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.4 + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: true diff --git a/integration-tests/scripts/build-battery-station-spec.sh b/integration-tests/scripts/build-battery-station-spec.sh new file mode 100755 index 000000000..81f1401b8 --- /dev/null +++ b/integration-tests/scripts/build-battery-station-spec.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Always run the commands from the "integration-tests" dir +cd $(dirname $0)/.. + +mkdir -p specs +../target/release/zeitgeist build-spec --chain=dev --raw > specs/battery-station-parachain-2101.json \ No newline at end of file diff --git a/integration-tests/scripts/build-node-configuration.sh b/integration-tests/scripts/build-node-configuration.sh new file mode 100755 index 000000000..8cc34b90a --- /dev/null +++ b/integration-tests/scripts/build-node-configuration.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +export PROFILE="${PROFILE:-release}" + +export FEATURES="${FEATURES:-parachain}" \ No newline at end of file diff --git a/integration-tests/scripts/build-node.sh b/integration-tests/scripts/build-node.sh new file mode 100755 index 000000000..0863aebff --- /dev/null +++ b/integration-tests/scripts/build-node.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +source ./scripts/build-node-configuration.sh + +pushd .. + +if [ -z "$CI" ]; then + cargo build --profile=$PROFILE --features=$FEATURES --bin=zeitgeist +fi + +popd \ No newline at end of file diff --git a/integration-tests/scripts/build-zeitgeist-spec.sh b/integration-tests/scripts/build-zeitgeist-spec.sh new file mode 100755 index 000000000..bf0379c86 --- /dev/null +++ b/integration-tests/scripts/build-zeitgeist-spec.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Always run the commands from the "integration-tests" dir +cd $(dirname $0)/.. + +mkdir -p specs +../target/release/zeitgeist build-spec --chain=zeitgeist --raw > specs/zeitgeist-parachain-2092.json \ No newline at end of file diff --git a/scripts/tests/spawn_network.sh b/integration-tests/scripts/deploy-zombienet.sh similarity index 84% rename from scripts/tests/spawn_network.sh rename to integration-tests/scripts/deploy-zombienet.sh index 1382417aa..2c9c2121d 100755 --- a/scripts/tests/spawn_network.sh +++ b/integration-tests/scripts/deploy-zombienet.sh @@ -1,14 +1,12 @@ #!/usr/bin/env bash -if [ ! -d "./scripts/tests" ]; then - echo "Please execute this script from the root of the Zeitgeist project folder" +if [ "$(dirname "$0")" != "./scripts" ]; then + echo "Please execute this script from the integration-tests directory." exit 1 fi; -cargo build --features parachain - export ADDITIONAL_ZOMBIECONFIG="${ADDITIONAL_ZOMBIECONFIG:-}" -export ZOMBIENET_CONFIG_FILE="${ZOMBIENET_CONFIG_FILE:-"./integration-tests/zombienet/produce-blocks.toml"}" +export ZOMBIENET_CONFIG_FILE="${ZOMBIENET_CONFIG_FILE:-"./zombienet/produce-blocks.toml"}" export ZOMBIENET_DSL_FILE="${ZOMBIENET_CONFIG_FILE%.toml}.zndsl" # Define destination path @@ -16,6 +14,7 @@ ZOMBIENET_BINARY="./tmp/zombienet" # Default values for flags RUN_TESTS=0 # This flag will be set to 1 if the -t or --test option is present +BUILD=true # This flag will be set to false if the --no-build option is present # Parse command-line arguments while [[ $# -gt 0 ]]; do @@ -26,6 +25,10 @@ while [[ $# -gt 0 ]]; do RUN_TESTS=1 shift # Remove argument name from processing ;; + --no-build) + BUILD=false + shift # Remove argument name from processing + ;; *) # Unknown option shift # Remove generic argument from processing @@ -33,6 +36,9 @@ while [[ $# -gt 0 ]]; do esac done +if $BUILD ; then + ./scripts/build-node.sh +fi function download_zombienet { # Get the appropriate download link based on the OS @@ -92,4 +98,4 @@ if [[ $RUN_TESTS -eq 1 ]]; then $ZOMBIENET_BINARY test --provider native $ZOMBIENET_DSL_FILE $ADDITIONAL_ZOMBIECONFIG else $ZOMBIENET_BINARY spawn --provider native $ZOMBIENET_CONFIG_FILE $ADDITIONAL_ZOMBIECONFIG -fi +fi \ No newline at end of file diff --git a/integration-tests/scripts/download-polkadot.sh b/integration-tests/scripts/download-polkadot.sh new file mode 100755 index 000000000..88b4b6bc1 --- /dev/null +++ b/integration-tests/scripts/download-polkadot.sh @@ -0,0 +1,67 @@ +# Copyright (C) Moondance Labs Ltd. +# Copyright 2022-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 . + +#!/bin/bash + +# Exit on any error +set -e + +# Check if the operating system is macOS +if [[ $(uname) == "Darwin" ]]; then + echo "This script is not intended for MacOS, since the prebuild binaries are meant to be executed on Linux. But keep in mind you need to have 'polkadot', 'polkadot-execute-worker', 'polkadot-prepare-worker' in any case! So compile those yourself! Exiting..." + exit 1 +fi + +# TODO: Use following line instead of the one above once Zeitgeist uses >=polkadot-v1.1.0 +# Note: Version 1.4.0 of relaychain didn't allow the parachain to produce blocks +# branch=$(egrep -o '/polkadot.*#([^\"]*)' $(dirname $0)/../../Cargo.lock | head -1 | sed 's/.*release-//#') +# polkadot_release=$(echo $branch | sed 's/#.*//' | sed 's/\/polkadot-sdk?branch=polkadot-v//' | sed 's/-.*//') +polkadot_release="1.1.0" + +# Always run the commands from the "integration-tests" dir +cd $(dirname $0)/.. + +if [[ -f tmp/polkadot ]]; then + POLKADOT_VERSION=$(tmp/polkadot --version) + if [[ $POLKADOT_VERSION == *$polkadot_release* ]]; then + echo "Polkadot binary has correct version" + exit 0 + else + echo "Updating polkadot binary..." + + pnpm moonwall download polkadot $polkadot_release tmp + chmod +x tmp/polkadot + + pnpm moonwall download polkadot-execute-worker $polkadot_release tmp + chmod +x tmp/polkadot-execute-worker + + pnpm moonwall download polkadot-prepare-worker $polkadot_release tmp + chmod +x tmp/polkadot-prepare-worker + + fi +else + echo "Polkadot binary not found, downloading..." + pnpm moonwall download polkadot $polkadot_release tmp + chmod +x tmp/polkadot + + pnpm moonwall download polkadot-execute-worker $polkadot_release tmp + chmod +x tmp/polkadot-execute-worker + + pnpm moonwall download polkadot-prepare-worker $polkadot_release tmp + chmod +x tmp/polkadot-prepare-worker +fi \ No newline at end of file diff --git a/integration-tests/tests/block.ts b/integration-tests/tests/block.ts deleted file mode 100644 index fe5f8efaf..000000000 --- a/integration-tests/tests/block.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from 'chai'; -import SDK from "@zeitgeistpm/sdk"; - -describe('Blocks', function() { - it('block number should not be zero', async function(done) { - this.timeout(5000); - - const sdk = await SDK.initialize("ws://127.0.0.1:9944"); - const blockHash = await sdk.api.rpc.chain.getFinalizedHead(); - const block = await sdk.api.rpc.chain.getBlock(blockHash); - expect(block.block.header.number).to.be.gt(0); - - setTimeout(done, 100); - }); -}); diff --git a/integration-tests/tests/common-tests.ts b/integration-tests/tests/common-tests.ts new file mode 100644 index 000000000..a7025b19e --- /dev/null +++ b/integration-tests/tests/common-tests.ts @@ -0,0 +1,195 @@ +// Copyright (C) Moondance Labs Ltd. + +import { expect, ChopsticksContext } from "@moonwall/cli"; +import { generateKeyringPair } from "@moonwall/util"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { AccountInfo, AccountData } from "@polkadot/types/interfaces"; +import WebSocket from "ws"; +import { Debugger } from "debug"; + +const MAX_BALANCE_TRANSFER_TRIES = 5; + +export async function canCreateBlocks( + context: ChopsticksContext, + providerName: string, + paraApi: ApiPromise +) { + const currentHeight = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + await context.createBlock({ providerName: providerName, count: 2 }); + const newHeight = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + expect(newHeight - currentHeight, "Block difference is not correct!").toBe(2); +} + +export async function canSendBalanceTransfer( + context: ChopsticksContext, + providerName: string, + paraApi: ApiPromise +) { + const randomAccount = generateKeyringPair("sr25519"); + const keyring = new Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + + let tries = 0; + const amount = BigInt("1000000000"); + const balanceBefore = ( + (await paraApi.query.system.account(randomAccount.address)) as AccountInfo + ).data.free.toBigInt(); + + /// It might happen that by accident we hit a session change + /// A block in which a session change occurs cannot hold any tx + /// Chopsticks does not have the notion of tx pool either, so we need to retry + /// Therefore we just retry at most MAX_BALANCE_TRANSFER_TRIES + while (tries < MAX_BALANCE_TRANSFER_TRIES) { + const tx = await paraApi.tx.balances.transfer( + randomAccount.address, + amount + ); + const txHash = tx.signAndSend(alice, { nonce: -1 }); + const result = await context.createBlock({ + providerName: providerName, + count: 1, + }); + + const block = await paraApi.rpc.chain.getBlock(result.result); + const includedTxHashes = block.block.extrinsics.map((x) => + x.hash.toString() + ); + if (includedTxHashes.includes(txHash.toString())) { + break; + } + tries++; + } + + // without this, the xcm transfer `canSendXcmTransfer` test below has a timeout + await context.createBlock({ providerName: providerName, count: 1 }); + + const balanceAfter = ( + (await paraApi.query.system.account(randomAccount.address)) as AccountInfo + ).data.free.toBigInt(); + expect(balanceAfter > balanceBefore, "Balance did not increase").toBeTruthy(); +} + +export async function canSendXcmTransfer( + context: ChopsticksContext, + log: Debugger, + senderProviderName: string, + senderParaApi: ApiPromise, + receiverParaApi: ApiPromise, + receiverParaId: number, + tokensIndex: number +) { + const keyring = new Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + const bob = keyring.addFromUri("//Bob", { name: "Bob default" }); + + const senderBalanceBefore = ( + (await senderParaApi.query.system.account(alice.address)) as AccountInfo + ).data.free.toBigInt(); + const receiverBalanceBefore = ( + (await receiverParaApi.query.tokens.accounts( + bob.address, + tokensIndex + )) as AccountData + ).free.toBigInt(); + + const ztg = { Ztg: null }; + const amount: bigint = BigInt("192913122185847181"); + const bobAccountId = senderParaApi + .createType("AccountId32", bob.address) + .toHex(); + const destination = { + V3: { + parents: 1, + interior: { + X2: [ + { Parachain: receiverParaId }, + { AccountId32: { id: bobAccountId, network: null } }, + ], + }, + }, + }; + const destWeightLimit = { Unlimited: null }; + + const xcmTransfer = senderParaApi.tx.xTokens.transfer( + ztg, + amount, + destination, + destWeightLimit + ); + + await xcmTransfer.signAndSend(alice, { nonce: -1 }); + + await context.createBlock({ + providerName: senderProviderName, + count: 1, + allowFailures: false, + }); + + const { partialFee, weight } = await xcmTransfer.paymentInfo(alice.address); + const transferFee: bigint = partialFee.toBigInt(); + const senderBalanceAfter = ( + (await senderParaApi.query.system.account(alice.address)) as AccountInfo + ).data.free.toBigInt(); + expect( + senderBalanceBefore - senderBalanceAfter, + "Unexpected balance diff" + ).toBe(amount + transferFee); + + // RpcError: 1: Block 0x... not found, if using this `await context.createBlock({ providerName: "ReceiverPara", count: 1 });` + // Reported Bug here https://github.com/Moonsong-Labs/moonwall/issues/343 + + // use a workaround for creating a block + const newBlockPromise = new Promise((resolve, reject) => { + // ws://127.0.0.1:8001 represents the receiver chain endpoint + const ws = new WebSocket("ws://127.0.0.1:8001"); + + ws.on("open", function open() { + const message = { + jsonrpc: "2.0", + id: 1, + method: "dev_newBlock", + params: [{ count: 1 }], + }; + + ws.send(JSON.stringify(message)); + }); + + ws.on("message", async function message(data) { + const dataObj = JSON.parse(data.toString()); + log("Received message:", dataObj); + resolve(dataObj.result); + }); + + ws.on("error", function error(error) { + log("Error:", error.toString()); + reject(error); + }); + }); + + await newBlockPromise; + const receiverBalanceAfter: bigint = ( + (await receiverParaApi.query.tokens.accounts( + bob.address, + tokensIndex + )) as AccountData + ).free.toBigInt(); + expect( + receiverBalanceAfter > receiverBalanceBefore, + "Balance did not increase" + ).toBeTruthy(); + const xcmFee: bigint = + receiverBalanceBefore + amount - transferFee - receiverBalanceAfter; + // between 0.02 ZTG and 0.10 ZTG XCM fee + const approxXcmFeeLow = 200000000; + const approxXcmFeeHigh = 1000000000; + expect(xcmFee).toBeGreaterThanOrEqual(approxXcmFeeLow); + expect(xcmFee).toBeLessThanOrEqual(approxXcmFeeHigh); + expect( + receiverBalanceAfter - receiverBalanceBefore, + "Unexpected xcm transfer balance diff" + ).toBe(amount - transferFee - xcmFee); +} diff --git a/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts new file mode 100644 index 000000000..7385abf26 --- /dev/null +++ b/integration-tests/tests/rt-upgrade-battery-station-chopsticks/test-battery-station-chopsticks-runtime-upgrade.ts @@ -0,0 +1,133 @@ +// Copyright (C) Moondance Labs Ltd. +// Copyright 2022-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 . + +import { + MoonwallContext, + beforeAll, + describeSuite, + expect, +} from "@moonwall/cli"; +import { KeyringPair } from "@moonwall/util"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { + canCreateBlocks, + canSendBalanceTransfer, + canSendXcmTransfer, +} from "tests/common-tests"; +import { RuntimeVersion } from "@polkadot/types/interfaces"; + +const ZEITGEIST_TOKENS_INDEX = 12; +const BASILISK_PARA_ID = 2090; +describeSuite({ + id: "CAN", + title: "Chopsticks Battery Station Post-Upgrade Tests", + foundationMethods: "chopsticks", + testCases: function ({ it, context, log }) { + let batteryStationParaApi: ApiPromise; + let relayApi: ApiPromise; + let basiliskParaApi: ApiPromise; + let alice: KeyringPair; + + beforeAll(async () => { + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + batteryStationParaApi = context.polkadotJs("BatteryStationPara"); + relayApi = context.polkadotJs("RococoRelay"); + basiliskParaApi = context.polkadotJs("BasiliskPara"); + + const paraZeitgeistNetwork = ( + batteryStationParaApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(paraZeitgeistNetwork, "Para API incorrect").to.contain( + "zeitgeist" + ); + + const relayNetwork = ( + relayApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(relayNetwork, "Relay API incorrect").to.contain("rococo"); + + const paraBasiliskNetwork = ( + basiliskParaApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(paraBasiliskNetwork, "Para API incorrect").to.contain("basilisk"); + + const rtBefore = ( + batteryStationParaApi.consts.system.version as RuntimeVersion + ).specVersion.toNumber(); + log(`About to upgrade to runtime at:`); + log(MoonwallContext.getContext().rtUpgradePath); + + await context.upgradeRuntime(); + + const rtafter = ( + batteryStationParaApi.consts.system.version as RuntimeVersion + ).specVersion.toNumber(); + log( + `RT upgrade has increased specVersion from ${rtBefore} to ${rtafter}` + ); + }, 60000); + + it({ + id: "T1", + timeout: 60000, + title: "Can create new blocks", + test: async () => { + await canCreateBlocks( + context, + "BatteryStationPara", + batteryStationParaApi + ); + }, + }); + + it({ + id: "T2", + timeout: 60000, + title: "Can send balance transfers", + test: async () => { + await canSendBalanceTransfer( + context, + "BatteryStationPara", + batteryStationParaApi + ); + }, + }); + + /* + Currently not working, bug tracked here https://github.com/galacticcouncil/HydraDX-node/issues/725 + + it({ + id: "T3", + timeout: 60000, + title: "Can send ZBS to Basilisk", + test: async () => { + await canSendXcmTransfer( + context, + log, + "BatteryStationPara", + batteryStationParaApi, + basiliskParaApi, + BASILISK_PARA_ID, + ZEITGEIST_TOKENS_INDEX + ); + }, + }); + */ + }, +}); diff --git a/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts new file mode 100644 index 000000000..3f2d9df10 --- /dev/null +++ b/integration-tests/tests/rt-upgrade-zeitgeist-chopsticks/test-zeitgeist-chopsticks-runtime-upgrade.ts @@ -0,0 +1,125 @@ +// Copyright (C) Moondance Labs Ltd. +// Copyright 2022-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 . + +import { + MoonwallContext, + beforeAll, + describeSuite, + expect, +} from "@moonwall/cli"; +import { KeyringPair } from "@moonwall/util"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { + canCreateBlocks, + canSendBalanceTransfer, + canSendXcmTransfer, +} from "tests/common-tests"; +import { RuntimeVersion } from "@polkadot/types/interfaces"; + +const ZEITGEIST_TOKENS_INDEX = 12; +const HYDRADX_PARA_ID = 2034; +describeSuite({ + id: "CAN", + title: "Chopsticks Zeitgeist Post-Upgrade Tests", + foundationMethods: "chopsticks", + testCases: function ({ it, context, log }) { + let zeitgeistParaApi: ApiPromise; + let relayApi: ApiPromise; + let hydradxParaApi: ApiPromise; + let alice: KeyringPair; + + beforeAll(async () => { + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + zeitgeistParaApi = context.polkadotJs("ZeitgeistPara"); + relayApi = context.polkadotJs("PolkadotRelay"); + hydradxParaApi = context.polkadotJs("HydraDXPara"); + + const paraZeitgeistNetwork = ( + zeitgeistParaApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(paraZeitgeistNetwork, "Para API incorrect").to.contain( + "zeitgeist" + ); + + const relayNetwork = ( + relayApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(relayNetwork, "Relay API incorrect").to.contain("polkadot"); + + const paraHydraDXNetwork = ( + hydradxParaApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(paraHydraDXNetwork, "Para API incorrect").to.contain("hydradx"); + + const rtBefore = ( + zeitgeistParaApi.consts.system.version as RuntimeVersion + ).specVersion.toNumber(); + log(`About to upgrade to runtime at:`); + log(MoonwallContext.getContext().rtUpgradePath); + + await context.upgradeRuntime(); + + const rtafter = ( + zeitgeistParaApi.consts.system.version as RuntimeVersion + ).specVersion.toNumber(); + log( + `RT upgrade has increased specVersion from ${rtBefore} to ${rtafter}` + ); + }, 60000); + + it({ + id: "T1", + timeout: 60000, + title: "Can create new blocks", + test: async () => { + await canCreateBlocks(context, "ZeitgeistPara", zeitgeistParaApi); + }, + }); + + it({ + id: "T2", + timeout: 60000, + title: "Can send balance transfers", + test: async () => { + await canSendBalanceTransfer( + context, + "ZeitgeistPara", + zeitgeistParaApi + ); + }, + }); + + it({ + id: "T3", + timeout: 60000, + title: "Can send ZTG to HydraDX", + test: async () => { + await canSendXcmTransfer( + context, + log, + "ZeitgeistPara", + zeitgeistParaApi, + hydradxParaApi, + HYDRADX_PARA_ID, + ZEITGEIST_TOKENS_INDEX + ); + }, + }); + }, +}); diff --git a/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts new file mode 100644 index 000000000..b1b7d19f5 --- /dev/null +++ b/integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts @@ -0,0 +1,125 @@ +// Copyright (C) Moondance Labs Ltd. +// Copyright 2022-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 . + +import { + MoonwallContext, + beforeAll, + describeSuite, + expect, +} from "@moonwall/cli"; +import { KeyringPair } from "@moonwall/util"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import fs from "node:fs"; +import { RuntimeVersion } from "@polkadot/types/interfaces"; + +describeSuite({ + id: "R01", + title: "Zombie Zeitgeist Upgrade Test", + foundationMethods: "zombie", + testCases: function ({ it, context, log }) { + let paraApi: ApiPromise; + let relayApi: ApiPromise; + let alice: KeyringPair; + + beforeAll(async () => { + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + paraApi = context.polkadotJs("parachain"); + relayApi = context.polkadotJs("Relay"); + + const relayNetwork = ( + relayApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(relayNetwork, "Relay API incorrect").to.contain("rococo"); + + const paraNetwork = ( + paraApi.consts.system.version as RuntimeVersion + ).specName.toString(); + expect(paraNetwork, "Para API incorrect").to.contain("zeitgeist"); + + const currentBlock = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + expect(currentBlock, "Parachain not producing blocks").to.be.greaterThan( + 0 + ); + }, 120000); + + it({ + id: "T01", + title: "Blocks are being produced on parachain", + test: async function () { + const blockNum = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + expect(blockNum).to.be.greaterThan(0); + }, + }); + + it({ + id: "T02", + title: "Chain can be upgraded", + timeout: 600000, + test: async function () { + const blockNumberBefore = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + const currentCode = await paraApi.rpc.state.getStorage(":code"); + const codeString = currentCode.toString(); + + const moonwallContext = MoonwallContext.getContext(); + log( + "Moonwall Context providers: " + + moonwallContext.providers.map((p) => p.name).join(", ") + ); + const wasm = fs.readFileSync(moonwallContext.rtUpgradePath); + const rtHex = `0x${wasm.toString("hex")}`; + + if (rtHex === codeString) { + log("Runtime already upgraded, skipping test"); + return; + } else { + log("Runtime not upgraded, proceeding with test"); + log( + "Current runtime hash: " + + rtHex.slice(0, 10) + + "..." + + rtHex.slice(-10) + ); + log( + "New runtime hash: " + + codeString.slice(0, 10) + + "..." + + codeString.slice(-10) + ); + } + + await context.upgradeRuntime({ from: alice, logger: log }); + await context.waitBlock(2); + const blockNumberAfter = ( + await paraApi.rpc.chain.getBlock() + ).block.header.number.toNumber(); + log(`Before: #${blockNumberBefore}, After: #${blockNumberAfter}`); + expect( + blockNumberAfter, + "Block number did not increase" + ).to.be.greaterThan(blockNumberBefore); + }, + }); + }, +}); diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 6f527790e..3e25c101c 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -1,7 +1,18 @@ { "compilerOptions": { - "target": "ES2020", - "moduleResolution": "node", - "esModuleInterop": true + "module": "ESNext", + "target": "ESNext", + "baseUrl": "./", + "moduleResolution": "Bundler", + "importHelpers": true, + "skipLibCheck": true, + "removeComments": true, + "noEmit": true, + "preserveConstEnums": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "allowImportingTsExtensions": true, } -} +} \ No newline at end of file diff --git a/integration-tests/zombienet/0001-balance-transfer.ts b/integration-tests/zombienet/0001-balance-transfer.ts new file mode 100644 index 000000000..0d0dce289 --- /dev/null +++ b/integration-tests/zombienet/0001-balance-transfer.ts @@ -0,0 +1,83 @@ +// Copyright 2022-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 . + +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { Keyring } from '@polkadot/keyring'; +import { cryptoWaitReady } from '@polkadot/util-crypto'; +import { AccountInfo } from '@polkadot/types/interfaces'; + +// Addresses for Alice and Bob on the dev chain +const ALICE = '//Alice'; +const BOB = '//Bob'; + +export const run = async (nodeName: string, networkInfo: any, args: any) => { + const provider = new WsProvider('ws://127.0.0.1:9966'); + const api = await ApiPromise.create({ provider }); + + // Wait for the crypto library to be ready + await cryptoWaitReady(); + + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri(ALICE); + const bob = keyring.addFromUri(BOB); + + const aliceFreeBalanceBefore = (await api.query.system.account(alice.address)) as unknown as AccountInfo; + const bobFreeBalanceBefore = (await api.query.system.account(bob.address)) as unknown as AccountInfo; + + console.log(`Alice has ${aliceFreeBalanceBefore.data.free} before transfer.`); + console.log(`Bob has ${bobFreeBalanceBefore.data.free} before transfer.`); + + const transfer_amount = "42000000000000000"; + + // Create a transfer transaction from Alice to Bob + const transfer = api.tx.balances.transfer(bob.address, transfer_amount); + + // Get weight info + const { partialFee, weight } = await transfer.paymentInfo(alice.address); + + console.log(`Transaction weight: ${weight}`); + console.log(`Transaction fee: ${partialFee.toString()}`); + + // Wait for the transaction to be finalized + await new Promise((resolve, reject) => { + transfer.signAndSend(alice, ({ status }) => { + if (status.isInBlock || status.isFinalized) { + resolve(status); + } + }).catch(reject); + }); + + console.log(`Transfer sent`); + + const aliceFreeBalanceAfter = (await api.query.system.account(alice.address)) as unknown as AccountInfo; + const bobFreeBalanceAfter = (await api.query.system.account(bob.address)) as unknown as AccountInfo; + + const aliceLostAmount = aliceFreeBalanceBefore.data.free.sub(aliceFreeBalanceAfter.data.free); + const bobGainedAmount = bobFreeBalanceAfter.data.free.sub(bobFreeBalanceBefore.data.free); + + console.log(`Alice lost ${aliceLostAmount.toString()} tokens.`); + console.log(`Bob gained ${bobGainedAmount.toString()} tokens.`); + + console.log(`Alice has ${aliceFreeBalanceAfter.data.free} after transfer.`); + console.log(`Bob has ${bobFreeBalanceAfter.data.free} after transfer.`); + + const testPassed = transfer_amount == bobGainedAmount.toString(); + console.log(`Test passed: ${testPassed}`); + await api.disconnect(); + + return testPassed ? 1 : 0; +} \ No newline at end of file diff --git a/integration-tests/zombienet/produce-blocks.toml b/integration-tests/zombienet/produce-blocks.toml index b8d07c6ff..eaa1d2551 100644 --- a/integration-tests/zombienet/produce-blocks.toml +++ b/integration-tests/zombienet/produce-blocks.toml @@ -18,6 +18,6 @@ id = 2101 [parachains.collator] args = ["-lparachain=debug"] -command = "./target/debug/zeitgeist" +command = "../target/release/zeitgeist" name = "alice" -ws_port = 9944 +ws_port = 9966 diff --git a/integration-tests/zombienet/produce-blocks.zndsl b/integration-tests/zombienet/produce-blocks.zndsl index fff7e520b..72f7db0ba 100644 --- a/integration-tests/zombienet/produce-blocks.zndsl +++ b/integration-tests/zombienet/produce-blocks.zndsl @@ -1,29 +1,58 @@ +# Copyright (C) Parity Technologies (UK) Ltd. +# Copyright 2022-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 . + Description: Produce blocks test Network: ./produce-blocks.toml Creds: config # Tracing -# alice: trace with traceID 94c1501a78a0d83c498cc92deec264d9 contains ["answer-chunk-request", "answer-chunk-request"] +# bob: trace with traceID 94c1501a78a0d83c498cc92deec264d9 contains ["answer-chunk-request", "answer-chunk-request"] # metrics alice: reports node_roles is 4 alice: reports sub_libp2p_is_major_syncing is 0 -# histogram -alice: reports histogram polkadot_pvf_execution_time has at least 2 samples in buckets ["0.1", "0.25", "0.5", "+Inf"] within 100 seconds - -# logs +# validator logs bob: log line matches glob "*rted #1*" within 10 seconds bob: log line matches "Imported #[0-9]+" within 10 seconds +bob: log line contains regex "best: #2" within 30 seconds +bob: log line matches "best: #[1-9]+" within 10 seconds + +bob: is up +charlie: is up # system events -bob: system event contains "A candidate was included" within 20 seconds -alice: system event matches glob "*was backed*" within 10 seconds +# alice: system event matches glob "*was backed*" within 10 seconds + +## test the block height - (or minus) finalised block +bob: reports block height minus finalised block is lower than 10 within 60 seconds +bob: reports block height - finalised block is lower than 10 within 60 seconds #parachain tests -alice: parachain 2101 is registered within 225 seconds -alice: parachain 2101 block height is at least 10 within 200 seconds +bob: parachain 2101 is registered within 80 seconds -## test the block height - (or minus) finalised block -alice: reports block height minus finalised block is lower than 10 within 20 seconds -alice: reports block height - finalised block is lower than 10 within 20 seconds \ No newline at end of file +alice: is up +alice: log line contains regex "Zeitgeist Parachain.*best: #1" within 100 seconds +alice: log line contains regex "Zeitgeist Parachain.*finalized #[1-9]+" within 120 seconds + +alice: ts-script ./0001-balance-transfer.ts return is equal to 1 within 200 seconds + +bob: parachain 2101 block height is at least 10 within 200 seconds + +alice: reports block height minus finalised block is lower than 10 within 200 seconds +alice: reports block height - finalised block is lower than 10 within 200 seconds \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..ea2e70ab0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "zeitgeist", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From be75fa9c0c55b6a679b3148b9f924320bee6f9bf Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Thu, 15 Feb 2024 10:23:44 +0100 Subject: [PATCH 042/104] Use `Balance` instead of `u128` as type for Balancer weights (#1251) * Replace hard-coded `u128` fo weights with balance types * Replace modulo operator with `checked_rem_res` * Update copyright notices * Revert changes to modulo operations --- primitives/src/traits/swaps.rs | 3 +-- zrml/swaps/src/benchmarks.rs | 6 +++--- zrml/swaps/src/lib.rs | 35 ++++++++++++++++++---------------- zrml/swaps/src/types/pool.rs | 6 ++---- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/primitives/src/traits/swaps.rs b/primitives/src/traits/swaps.rs index e5c182701..74412ba0d 100644 --- a/primitives/src/traits/swaps.rs +++ b/primitives/src/traits/swaps.rs @@ -23,7 +23,6 @@ use frame_support::dispatch::{DispatchError, Weight}; pub trait Swaps { type Asset; type Balance; - // TODO(#1216): Add weight type which implements `Into` and `From` /// Creates an initial active pool. /// @@ -44,7 +43,7 @@ pub trait Swaps { assets: Vec, swap_fee: Self::Balance, amount: Self::Balance, - weights: Vec, + weights: Vec, ) -> Result; /// Close the specified pool. diff --git a/zrml/swaps/src/benchmarks.rs b/zrml/swaps/src/benchmarks.rs index d4d414be0..dd1dabcb9 100644 --- a/zrml/swaps/src/benchmarks.rs +++ b/zrml/swaps/src/benchmarks.rs @@ -72,7 +72,7 @@ fn bench_create_pool( caller: T::AccountId, asset_count: usize, opt_asset_amount: Option>, - opt_weights: Option>, + opt_weights: Option>>, open: bool, ) -> (u128, Vec>, BalanceOf) where @@ -169,7 +169,7 @@ benchmarks! { let balance: BalanceOf = LIQUIDITY.saturated_into(); let asset_amount_in: BalanceOf = balance.bmul(T::MaxInRatio::get()).unwrap(); let weight_in = T::MinWeight::get(); - let weight_out = 2 * weight_in; + let weight_out = weight_in * 2u8.into(); let mut weights = vec![weight_in; asset_count as usize]; weights[asset_count as usize - 1] = weight_out; let caller: T::AccountId = whitelisted_caller(); @@ -205,7 +205,7 @@ benchmarks! { let balance: BalanceOf = LIQUIDITY.saturated_into(); let asset_amount_out: BalanceOf = balance.bmul(T::MaxOutRatio::get()).unwrap(); let weight_out = T::MinWeight::get(); - let weight_in = 4 * weight_out; + let weight_in = weight_out * 4u8.into(); let mut weights = vec![weight_out; asset_count as usize]; weights[0] = weight_in; let caller: T::AccountId = whitelisted_caller(); diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 4774d2ce6..50feb354e 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -211,7 +211,7 @@ mod pallet { ensure!(pool_amount <= mul, Error::::MaxInRatio); let asset_amount: BalanceOf = crate::math::calc_single_out_given_pool_in( asset_balance.saturated_into(), - Self::pool_weight_rslt(&pool, &asset)?, + Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), total_supply.saturated_into(), pool.total_weight.saturated_into(), pool_amount.saturated_into(), @@ -372,7 +372,7 @@ mod pallet { ensure!(pool_amount <= mul, Error::::MaxOutRatio); let asset_amount: BalanceOf = crate::math::calc_single_in_given_pool_out( asset_balance.saturated_into(), - Self::pool_weight_rslt(&pool, &asset)?, + Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), total_supply.saturated_into(), pool.total_weight.saturated_into(), pool_amount.saturated_into(), @@ -537,17 +537,17 @@ mod pallet { type MaxSwapFee: Get>; #[pallet::constant] - type MaxTotalWeight: Get; + type MaxTotalWeight: Get>; #[pallet::constant] - type MaxWeight: Get; + type MaxWeight: Get>; #[pallet::constant] /// The minimum amount of assets in a pool. type MinAssets: Get; #[pallet::constant] - type MinWeight: Get; + type MinWeight: Get>; /// The module identifier. #[pallet::constant] @@ -784,9 +784,9 @@ mod pallet { Ok(crate::math::calc_spot_price( balance_in.saturated_into(), - in_weight, + in_weight.saturated_into(), balance_out.saturated_into(), - out_weight, + out_weight.saturated_into(), swap_fee, )? .saturated_into()) @@ -938,7 +938,10 @@ mod pallet { }) } - fn pool_weight_rslt(pool: &PoolOf, asset: &AssetOf) -> Result> { + fn pool_weight_rslt( + pool: &PoolOf, + asset: &AssetOf, + ) -> Result, Error> { pool.weights.get(asset).cloned().ok_or(Error::::AssetNotBound) } @@ -980,7 +983,7 @@ mod pallet { assets: Vec>, swap_fee: BalanceOf, amount: BalanceOf, - weights: Vec, + weights: Vec>, ) -> Result { ensure!(assets.len() <= usize::from(T::MaxAssets::get()), Error::::TooManyAssets); ensure!(assets.len() >= usize::from(T::MinAssets::get()), Error::::TooFewAssets); @@ -988,7 +991,7 @@ mod pallet { 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 mut total_weight = 0; + let mut total_weight: BalanceOf = Zero::zero(); let mut sorted_assets = assets.clone(); sorted_assets.sort(); let has_duplicates = sorted_assets @@ -1132,7 +1135,7 @@ mod pallet { pool_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { let pool_amount: BalanceOf = crate::math::calc_pool_in_given_single_out( asset_balance.saturated_into(), - Self::pool_weight_rslt(pool_ref, &asset)?, + Self::pool_weight_rslt(pool_ref, &asset)?.saturated_into(), total_supply.saturated_into(), pool_ref.total_weight.saturated_into(), asset_amount.saturated_into(), @@ -1190,7 +1193,7 @@ mod pallet { ensure!(asset_amount <= mul, Error::::MaxInRatio); let pool_amount: BalanceOf = crate::math::calc_pool_out_given_single_in( asset_balance.saturated_into(), - Self::pool_weight_rslt(pool_ref, &asset_in)?, + Self::pool_weight_rslt(pool_ref, &asset_in)?.saturated_into(), total_supply.saturated_into(), pool_ref.total_weight.saturated_into(), asset_amount.saturated_into(), @@ -1258,9 +1261,9 @@ mod pallet { ); let asset_amount_out: BalanceOf = crate::math::calc_out_given_in( balance_in.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_in)?, + Self::pool_weight_rslt(&pool, &asset_in)?.saturated_into(), balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?, + Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), asset_amount_in.saturated_into(), pool.swap_fee.saturated_into(), )? @@ -1329,9 +1332,9 @@ mod pallet { 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)?, + Self::pool_weight_rslt(&pool, &asset_in)?.saturated_into(), balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?, + Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), asset_amount_out.saturated_into(), pool.swap_fee.saturated_into(), )? diff --git a/zrml/swaps/src/types/pool.rs b/zrml/swaps/src/types/pool.rs index 79fe8c234..917fe4a47 100644 --- a/zrml/swaps/src/types/pool.rs +++ b/zrml/swaps/src/types/pool.rs @@ -30,15 +30,13 @@ impl Get for MaxAssets { } } -// 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, + pub total_weight: Balance, + pub weights: BoundedBTreeMap, } impl Pool From 45f42b83699cbffdf2b729e77facd080e9d83e07 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 15 Feb 2024 10:41:40 +0100 Subject: [PATCH 043/104] Remove primitives/macros --- Cargo.lock | 1 + macros/src/lib.rs | 2 ++ primitives/src/lib.rs | 1 - primitives/src/macros.rs | 66 ------------------------------------ zrml/asset-router/Cargo.toml | 1 + zrml/asset-router/src/lib.rs | 5 ++- 6 files changed, 6 insertions(+), 70 deletions(-) delete mode 100644 primitives/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index ced756cfc..44c60c1df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14556,6 +14556,7 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", ] diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2534ff00f..b6ee80362 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#![cfg_attr(not(feature = "std"), no_std)] + /// Creates an `alloc::collections::BTreeMap` from the pattern `{ key => value, ... }`. /// /// ```ignore diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 15808ddbc..e46cd5fbf 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -22,7 +22,6 @@ extern crate alloc; mod assets; pub mod constants; -pub mod macros; mod market; pub mod math; mod max_runtime_usize; diff --git a/primitives/src/macros.rs b/primitives/src/macros.rs deleted file mode 100644 index f4b4c04b4..000000000 --- a/primitives/src/macros.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 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 . - -/// This macro does ensure that a condition `$condition` is met, and if it is not met -/// it will log a message `$message` with optional message arguments `message_args` to -/// an optional log target `$log_target`, cause an assertion in a test environment -/// and execute some optional extra code. -#[macro_export] -macro_rules! unreachable_non_terminating { - ($condition: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); - - #[cfg(test)] - assert!($condition, "{}", message); - - if !$condition { - log::warn!("{}", message); - } - }; - ($condition: expr, $log_target: ident, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); - - #[cfg(test)] - assert!($condition, "{}", message); - - if !$condition { - log::warn!(target: $log_target, "{}", message); - } - }; - ($condition: expr, $extra_code: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); - - #[cfg(test)] - assert!($condition, "{}", message); - - if !$condition { - log::warn!("{}", message); - $extra_code; - } - }; - ($condition: expr, $log_target: ident, $extra_code: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); - - #[cfg(test)] - assert!($condition, "{}", message); - - if !$condition { - log::warn!(target: $log_target, "{}", message); - $extra_code; - } - }; -} diff --git a/zrml/asset-router/Cargo.toml b/zrml/asset-router/Cargo.toml index 787fb5d73..cf278b61a 100644 --- a/zrml/asset-router/Cargo.toml +++ b/zrml/asset-router/Cargo.toml @@ -7,6 +7,7 @@ pallet-assets = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } +zeitgeist-macros = { workspace = true } zeitgeist-primitives = { workspace = true } [dev-dependencies] diff --git a/zrml/asset-router/src/lib.rs b/zrml/asset-router/src/lib.rs index 08dbd5d1d..50ce4b38e 100644 --- a/zrml/asset-router/src/lib.rs +++ b/zrml/asset-router/src/lib.rs @@ -67,9 +67,8 @@ pub mod pallet { }, FixedPointOperand, SaturatedConversion, }; - pub(crate) use zeitgeist_primitives::{ - traits::CheckedDivPerComponent, unreachable_non_terminating, - }; + use zeitgeist_macros::unreachable_non_terminating; + pub(crate) use zeitgeist_primitives::traits::CheckedDivPerComponent; pub(crate) const LOG_TARGET: &str = "runtime::asset-router"; pub(crate) const MAX_ASSET_DESTRUCTIONS_PER_BLOCK: u8 = 128; From 8115543906c521c70e022d2808b0752adb37db0b Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 15 Feb 2024 17:39:47 +0100 Subject: [PATCH 044/104] Filter certain asset destroy calls (they're managed) --- runtime/battery-station/src/lib.rs | 21 +++++++++++++++++++++ runtime/zeitgeist/src/lib.rs | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index 84e050e86..5f1365c29 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -50,6 +50,7 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSigned, EnsureWithSuccess}; use orml_currencies::Call::transfer; +use pallet_assets::Call::{destroy_accounts, destroy_approvals, finish_destroy}; use pallet_collective::{EnsureProportionAtLeast, PrimeDefaultVote}; use parity_scale_codec::Compact; use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256}; @@ -172,6 +173,26 @@ impl Contains for IsCallable { fn contains(call: &RuntimeCall) -> bool { #[allow(clippy::match_like_matches_macro)] match call { + // Asset destruction is managed. Instead of deleting those dispatchable calls, they are + // filtered here instead to allow root to interact in case of emergency. + RuntimeCall::CampaignAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + }, + RuntimeCall::CustomAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + }, + RuntimeCall::MarketAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + }, RuntimeCall::SimpleDisputes(_) => false, RuntimeCall::LiquidityMining(_) => false, RuntimeCall::PredictionMarkets(inner_call) => match inner_call { diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index bd24a80b3..03cdf1d89 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -116,6 +116,7 @@ impl Contains for IsCallable { kill_prefix, kill_storage, set_code, set_code_without_checks, set_storage, }; use orml_currencies::Call::update_balance; + use pallet_assets::Call::{destroy_accounts, destroy_approvals, finish_destroy}; use pallet_balances::Call::{force_transfer, set_balance}; use pallet_collective::Call::set_members; use pallet_contracts::Call::{ @@ -149,6 +150,26 @@ impl Contains for IsCallable { _ => true, } } + // Asset destruction is managed. Instead of deleting those dispatchable calls, they are + // filtered here instead to allow root to interact in case of emergency. + RuntimeCall::CampaignAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + } + RuntimeCall::CustomAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + } + RuntimeCall::MarketAssets(inner_call) => match inner_call { + destroy_accounts { .. } => false, + destroy_approvals { .. } => false, + finish_destroy { .. } => false, + _ => true, + } // Permissioned contracts: Only deployable via utility.dispatch_as(...) RuntimeCall::Contracts(inner_call) => match inner_call { call { .. } => true, From b7e75e63c5b5869d957c9239c43a22bcee0491b0 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 20 Feb 2024 00:00:25 +0100 Subject: [PATCH 045/104] Remove unchecked modulo operations from production (#1254) * Remove unchecked modulo operations from production * Update copyright --- primitives/src/math/checked_ops_res.rs | 21 +++++++++++++++++++-- zrml/court/src/lib.rs | 21 ++++++++++++++------- zrml/swaps/src/fixed.rs | 10 ++++++---- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs index 02e9ec3de..1265a6b2b 100644 --- a/primitives/src/math/checked_ops_res.rs +++ b/primitives/src/math/checked_ops_res.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -18,7 +18,7 @@ use frame_support::dispatch::DispatchError; use num_traits::{checked_pow, One}; use sp_arithmetic::{ - traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}, + traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedRem, CheckedSub}, ArithmeticError, }; @@ -57,6 +57,13 @@ where fn checked_pow_res(&self, exp: usize) -> Result; } +pub trait CheckedRemRes +where + Self: Sized, +{ + fn checked_rem_res(&self, other: &Self) -> Result; +} + impl CheckedAddRes for T where T: CheckedAdd, @@ -106,3 +113,13 @@ where checked_pow(*self, exp).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) } } + +impl CheckedRemRes for T +where + T: CheckedRem, +{ + #[inline] + fn checked_rem_res(&self, other: &Self) -> Result { + self.checked_rem(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) + } +} diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 10cf08bcb..bfcc95600 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/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. @@ -52,12 +52,16 @@ use frame_system::{ }; use rand::{seq::SliceRandom, Rng, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; -use sp_arithmetic::{per_things::Perquintill, traits::One}; +use sp_arithmetic::{ + per_things::Perquintill, + traits::{CheckedRem, One}, +}; use sp_runtime::{ traits::{AccountIdConversion, Hash, Saturating, StaticLookup, Zero}, DispatchError, Perbill, SaturatedConversion, }; use zeitgeist_primitives::{ + math::checked_ops_res::CheckedRemRes, traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ Asset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, @@ -1299,9 +1303,10 @@ mod pallet { // Handle the external incentivisation of the court system. pub(crate) fn handle_inflation(now: T::BlockNumber) -> Weight { let inflation_period = T::InflationPeriod::get(); - if !(now % inflation_period).is_zero() { - return Weight::zero(); - } + match now.checked_rem(&inflation_period) { + Some(rem) if rem.is_zero() => (), + Some(_) | None => return Weight::zero(), + }; let yearly_inflation_rate = >::get(); if yearly_inflation_rate.is_zero() { @@ -1776,7 +1781,7 @@ mod pallet { .stake .saturating_sub(pool_item.consumed_stake) .saturated_into::(); - let remainder = unconsumed % min_juror_stake; + let remainder = unconsumed.checked_rem_res(&min_juror_stake)?; let unconsumed = unconsumed.saturating_sub(remainder); total_unconsumed = total_unconsumed.saturating_add(unconsumed); running_total = running_total.saturating_add(unconsumed); @@ -2394,7 +2399,9 @@ mod pallet { .stake .saturating_sub(pool_item.consumed_stake) .saturated_into::(); - let remainder = unconsumed % min_juror_stake; + // `unwrap_or_else` is infallible unless the module is misconfigured. + let remainder = + unconsumed.checked_rem_res(&min_juror_stake).unwrap_or_else(|_| Zero::zero()); let unconsumed = unconsumed.saturating_sub(remainder); acc.saturating_add(unconsumed) }); diff --git a/zrml/swaps/src/fixed.rs b/zrml/swaps/src/fixed.rs index ee74c799b..aa1aec648 100644 --- a/zrml/swaps/src/fixed.rs +++ b/zrml/swaps/src/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. @@ -26,7 +26,9 @@ use frame_support::dispatch::DispatchError; use zeitgeist_primitives::{ constants::BASE, math::{ - checked_ops_res::{CheckedAddRes, CheckedDivRes, CheckedMulRes, CheckedSubRes}, + checked_ops_res::{ + CheckedAddRes, CheckedDivRes, CheckedMulRes, CheckedRemRes, CheckedSubRes, + }, fixed::{FixedDiv, FixedMul}, }, }; @@ -53,7 +55,7 @@ pub fn bsub_sign(a: u128, b: u128) -> Result<(u128, bool), DispatchError> { } pub fn bpowi(a: u128, n: u128) -> Result { - let mut z = if n % 2 != 0 { a } else { BASE }; + let mut z = if n.checked_rem_res(&2)? != 0 { a } else { BASE }; let mut b = a; let mut m = n.checked_div_res(&2)?; @@ -61,7 +63,7 @@ pub fn bpowi(a: u128, n: u128) -> Result { while m != 0 { b = b.bmul(b)?; - if m % 2 != 0 { + if m.checked_rem_res(&2)? != 0 { z = z.bmul(b)?; } From 934bf0fe3af1304d61789a9b44f2549d78824cad Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Tue, 20 Feb 2024 01:07:13 +0100 Subject: [PATCH 046/104] Refactor swaps (#1255) * First pass fixing docs, comments and code style * Fix typos * Use `do_*` pattern, fix docs, remove unnecessary clippy exceptions * Replace `slash` with `withdraw` * Replace `slash` with `withdraw` * Remove reference to CPMM from weight function names * Hard-code `Max*Ratio` * Clean up, fix formatting * Fix `pool_exit` behavior * Remove unnecessary clones * Clean up events * Fix `pool_exit` behavior on odd pools and add a test * Fix test * Update copyright notices * Update docs * Update primitives/src/traits/swaps.rs Co-authored-by: Harald Heckmann * Update primitives/src/traits/swaps.rs Co-authored-by: Harald Heckmann * Remove fixed TODO * Fix merge --------- Co-authored-by: Harald Heckmann --- primitives/src/constants/mock.rs | 2 - primitives/src/math/root.rs | 3 +- primitives/src/traits/swaps.rs | 110 +- runtime/battery-station/src/parameters.rs | 6 +- runtime/common/src/lib.rs | 2 - runtime/zeitgeist/src/parameters.rs | 6 +- zrml/swaps/README.md | 35 +- zrml/swaps/src/benchmarks.rs | 12 +- zrml/swaps/src/lib.rs | 1236 ++++++++++----------- zrml/swaps/src/math.rs | 10 +- zrml/swaps/src/mock.rs | 10 +- zrml/swaps/src/tests.rs | 73 +- zrml/swaps/src/utils.rs | 12 +- zrml/swaps/src/weights.rs | 8 +- 14 files changed, 775 insertions(+), 750 deletions(-) diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 112a07834..11627c67c 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -120,8 +120,6 @@ parameter_types! { pub const ExitFee: Balance = 3 * BASE / 1000; // 0.3% pub const MinAssets: u16 = 2; pub const MaxAssets: u16 = MaxCategories::get() + 1; - pub const MaxInRatio: Balance = (BASE / 3) + 1; - pub const MaxOutRatio: Balance = (BASE / 3) + 1; pub const MaxSwapFee: Balance = BASE / 10; // 10% pub const MaxTotalWeight: Balance = 50 * BASE; pub const MaxWeight: Balance = 50 * BASE; diff --git a/primitives/src/math/root.rs b/primitives/src/math/root.rs index bb0cea540..2479e1ab4 100644 --- a/primitives/src/math/root.rs +++ b/primitives/src/math/root.rs @@ -44,8 +44,7 @@ // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Helper functions for approximating preimages of increasing or decreasing functions used in -//! [`crate::arbitrage`]. +//! Helper functions for approximating preimages of increasing or decreasing functions. use sp_runtime::traits::AtLeast32BitUnsigned; diff --git a/primitives/src/traits/swaps.rs b/primitives/src/traits/swaps.rs index 74412ba0d..cff467ac9 100644 --- a/primitives/src/traits/swaps.rs +++ b/primitives/src/traits/swaps.rs @@ -19,27 +19,23 @@ use crate::types::PoolId; use alloc::vec::Vec; use frame_support::dispatch::{DispatchError, Weight}; +use sp_runtime::DispatchResult; pub trait Swaps { type Asset; type Balance; - /// Creates an initial active pool. + /// Creates a new pool. /// /// # Arguments /// - /// * `who`: The account that is the creator of the pool. Must have enough - /// funds for each of the assets to cover the minimum balance. + /// * `who`: The account that is the creator of the pool. /// * `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 (in case the scoring rule doesn't provide fees). - /// * `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). + /// * `swap_fee`: The fee applied to each swap. + /// * `amount`: The amount of each asset added to the pool. + /// * `weights`: The denormalized weights. fn create_pool( - creator: AccountId, + who: AccountId, assets: Vec, swap_fee: Self::Balance, amount: Self::Balance, @@ -49,68 +45,66 @@ pub trait Swaps { /// Close the specified pool. fn close_pool(pool_id: PoolId) -> Result; - /// Destroy CPMM pool, slash pool account assets and destroy pool shares of the liquidity providers. + /// Destroy pool, slash pool account assets and destroy pool shares of the liquidity providers. fn destroy_pool(pool_id: PoolId) -> Result; + /// Open the specified pool. fn open_pool(pool_id: PoolId) -> Result; - /// Pool - Exit with exact pool amount - /// - /// Takes an asset from `pool_id` and transfers to `origin`. Differently from `pool_exit`, - /// this method injects the exactly amount of `asset_amount` to `origin`. + /// Exchanges an LP's (liquidity provider's) pool shares for a proportionate and exact + /// amount of _one_ of the pool's assets. The assets received are distributed according to + /// the LP's percentage ownership of the pool. /// /// # Arguments /// - /// * `who`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `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. + /// * `who`: The LP. + /// * `pool_id`: The ID of the pool to withdraw from. + /// * `asset`: The asset received by the LP. + /// * `asset_amount`: The amount of `asset` leaving the pool. + /// * `max_pool_amount`: The maximum amount of pool shares the LP is willing to burn. The + /// transaction is rolled back if this bound is violated. fn pool_exit_with_exact_asset_amount( who: AccountId, pool_id: PoolId, asset: Self::Asset, asset_amount: Self::Balance, max_pool_amount: Self::Balance, - ) -> Result; + ) -> DispatchResult; - /// Pool - Join with exact asset amount - /// - /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, - /// this method transfers the exactly amount of `asset_amount` to `pool_id`. + /// Exchanges an exact amount of an LP's (liquidity provider's) holds of _one_ of the assets in + /// the pool for pool shares. /// /// # Arguments /// - /// * `who`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `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. + /// * `who`: The LP. + /// * `pool_id`: The ID of the pool to withdraw from. + /// * `asset_in`: The asset entering the pool. + /// * `asset_amount`: Asset amount that is entering the pool. + /// * `min_pool_amount`: The minimum amount of pool shares the LP asks to receive. The + /// transaction is rolled back if this bound is violated. fn pool_join_with_exact_asset_amount( who: AccountId, pool_id: PoolId, asset_in: Self::Asset, asset_amount: Self::Balance, min_pool_amount: Self::Balance, - ) -> Result; + ) -> DispatchResult; - /// Swap - Exact amount in + /// Buy the `asset_out`/`asset_in` pair from the pool for an exact amount of `asset_in`. /// - /// Swaps a given `asset_amount_in` of the `asset_in/asset_out` pair to `pool_id`. + /// This function will error if both `min_asset_amount_out` and `max_price` are `None`. /// /// # Arguments /// - /// * `who`: The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Self::Asset entering the pool. - /// * `asset_amount_in`: Amount that will be transferred from the provider to 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) - #[allow(clippy::too_many_arguments)] + /// * `who`: The user executing the trade. + /// * `pool_id`: The pool to execute the trade on. + /// * `asset_in`: Asset entering the pool. + /// * `asset_amount_in`: Exact mount that will be transferred from the user to the pool. + /// * `asset_out`: Asset leaving the pool. + /// * `min_asset_amount_out`: Minimum asset amount requested by the user. The trade is rolled + /// back if this limit is violated. If this is `None`, there is no limit. + /// * `max_price`: The maximum price _after execution_ the user is willing to accept. The trade + /// is rolled back if this limit is violated. If this is `None`, there is no limit. fn swap_exact_amount_in( who: AccountId, pool_id: PoolId, @@ -119,23 +113,23 @@ pub trait Swaps { asset_out: Self::Asset, min_asset_amount_out: Option, max_price: Option, - ) -> Result; + ) -> DispatchResult; - /// Swap - Exact amount out + /// Buy the `asset_out`/`asset_in` pair from the pool, receiving an exact amount of `asset_out`. /// - /// Swaps a given `asset_amount_out` of the `asset_in/asset_out` pair to `origin`. + /// This function will error if both `min_asset_amount_out` and `max_price` are `None`. /// /// # Arguments /// - /// * `who`: The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Self::Asset entering the pool. - /// * `max_amount_asset_in`: Maximum asset amount that can enter 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) - #[allow(clippy::too_many_arguments)] + /// * `who`: The user executing the trade. + /// * `pool_id`: The pool to execute the trade on. + /// * `asset_in`: Asset entering the pool. + /// * `max_asset_amount_out`: Maximum asset amount the user is willing to pay. The trade is + /// rolled back if this limit is violated. + /// * `asset_out`: Asset leaving the pool. + /// * `asset_amount_out`: Exact amount that will be transferred from the user to the pool. + /// * `max_price`: The maximum price _after execution_ the user is willing to accept. The trade + /// is rolled back if this limit is violated. If this is `None`, there is no limit. fn swap_exact_amount_out( who: AccountId, pool_id: PoolId, @@ -144,5 +138,5 @@ pub trait Swaps { asset_out: Self::Asset, asset_amount_out: Self::Balance, max_price: Option, - ) -> Result; + ) -> DispatchResult; } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 945dabebe..a7175708e 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.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. @@ -309,10 +309,6 @@ parameter_types! { pub const MinAssets: u16 = 2; /// Maximum number of assets. `MaxCategories` plus one base asset. pub const MaxAssets: u16 = MAX_ASSETS; - /// Mathematical constraint set by the Balancer algorithm. DO NOT CHANGE. - pub const MaxInRatio: Balance = (BASE / 3) + 1; - /// Mathematical constraint set by the Balancer algorithm. DO NOT CHANGE. - pub const MaxOutRatio: Balance = (BASE / 3) + 1; /// The maximum fee that is charged for swaps and single asset LP operations. pub const MaxSwapFee: Balance = BASE / 10; // 10% /// The sum of all weights of the assets within the pool is limited by `MaxTotalWeight`. diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 12e5a57cb..f70ac5a37 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1225,8 +1225,6 @@ macro_rules! impl_config_traits { type ExitFee = ExitFee; type MinAssets = MinAssets; type MaxAssets = MaxAssets; - type MaxInRatio = MaxInRatio; - type MaxOutRatio = MaxOutRatio; type MaxSwapFee = MaxSwapFee; type MaxTotalWeight = MaxTotalWeight; type MaxWeight = MaxWeight; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 569449b09..d3e171c22 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.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. @@ -309,10 +309,6 @@ parameter_types! { pub const MinAssets: u16 = 2; /// Maximum number of assets. `MaxCategories` plus one base asset. pub const MaxAssets: u16 = MAX_ASSETS; - /// Mathematical constraint set by the Balancer algorithm. DO NOT CHANGE. - pub const MaxInRatio: Balance = (BASE / 3) + 1; - /// Mathematical constraint set by the Balancer algorithm. DO NOT CHANGE. - pub const MaxOutRatio: Balance = (BASE / 3) + 1; /// The maximum fee that is charged for swaps and single asset LP operations. pub const MaxSwapFee: Balance = BASE / 10; // 10% /// The sum of all weights of the assets within the pool is limited by `MaxTotalWeight`. diff --git a/zrml/swaps/README.md b/zrml/swaps/README.md index 36952b819..d706c6463 100644 --- a/zrml/swaps/README.md +++ b/zrml/swaps/README.md @@ -1,4 +1,35 @@ # Swaps Module -A module to handle swapping shares out for different ones. Allows liquidity -providers to deposit full outcome shares and earn fees. +Legacy module which implements Balancer style liquidity pools. + +## Overview + +See the [Balancer whitepaper](https://www.balancer.fi/whitepaper.pdf) for +details. + +### Terminology + +- _Exit_: Refers to removing (part of) the liquidity from a liquidity pool in + exchange for burning pool shares. Amounts received by the LP are + proportional to their ownership of the pool. +- _Join_: Refers to adding more liquidity to a pool and receiving pool shares + in return. Amounts moved into the pool are proportional to the LP's + ownership of the pool. +- _Liquidity provider_: A user who owns pool shares indicating their stake in + the liquidity pool. +- _Pool Shares_: A token indicating the owner's per rate share of the + liquidity pool. +- _Single Asset Operation_: An operation which combines joining or withdrawing + from the pool and selling unwanted tokens. The end result is like entering + (resp. leaving) the pool but paying (resp. receiving payment) in a single + asset. +- _Swap fees_: Part of the collateral paid or received by informants that is + moved to a separate account owned by the liquidity providers. They need to + be withdrawn using the `withdraw_fees` extrinsic. + +### Exact Amounts + +Almost all operations come in two variants, one where the assets entering the +pool are exact, the other where the assets leaving the pool are exact. Due to +the permissionless nature of the pallet, if multiple trades are placed in the +same block, the non-exact amount might slip for some of the parties. diff --git a/zrml/swaps/src/benchmarks.rs b/zrml/swaps/src/benchmarks.rs index dd1dabcb9..c7442491a 100644 --- a/zrml/swaps/src/benchmarks.rs +++ b/zrml/swaps/src/benchmarks.rs @@ -29,7 +29,7 @@ use super::*; #[cfg(test)] use crate::Pallet as Swaps; -use crate::{types::PoolStatus, AssetOf, Config, Event}; +use crate::{types::PoolStatus, AssetOf, Config, Event, MAX_IN_RATIO, MAX_OUT_RATIO}; use frame_benchmarking::{benchmarks, vec, whitelisted_caller, Vec}; use frame_support::traits::Get; use frame_system::RawOrigin; @@ -159,7 +159,7 @@ benchmarks! { let max_asset_amount: BalanceOf = u128::MAX.saturated_into(); }: _(RawOrigin::Signed(caller), pool_id, assets[0], pool_amount, max_asset_amount) - swap_exact_amount_in_cpmm { + swap_exact_amount_in { // 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 // calculating y^r within the set of values allowed in `swap_exact_amount_in` (see @@ -167,7 +167,7 @@ benchmarks! { // amount_in = 1/3 * balance_in, weight_in = 1, weight_out = 2. let asset_count = T::MaxAssets::get(); let balance: BalanceOf = LIQUIDITY.saturated_into(); - let asset_amount_in: BalanceOf = balance.bmul(T::MaxInRatio::get()).unwrap(); + let asset_amount_in: BalanceOf = balance.bmul(MAX_IN_RATIO.saturated_into()).unwrap(); let weight_in = T::MinWeight::get(); let weight_out = weight_in * 2u8.into(); let mut weights = vec![weight_in; asset_count as usize]; @@ -195,7 +195,7 @@ benchmarks! { max_price ) - swap_exact_amount_out_cpmm { + swap_exact_amount_out { // 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 // calculating y^r within the set of values allowed in `swap_exact_amount_out` (see @@ -203,7 +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 asset_amount_out: BalanceOf = balance.bmul(T::MaxOutRatio::get()).unwrap(); + let asset_amount_out: BalanceOf = balance.bmul(MAX_OUT_RATIO.saturated_into()).unwrap(); let weight_out = T::MinWeight::get(); let weight_in = weight_out * 4u8.into(); let mut weights = vec![weight_out; asset_count as usize]; @@ -269,7 +269,7 @@ benchmarks! { 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 50feb354e..33cee6776 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -58,9 +58,10 @@ mod pallet { use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ - dispatch::{DispatchResultWithPostInfo, Weight}, + dispatch::Weight, ensure, pallet_prelude::{OptionQuery, StorageMap, StorageValue, ValueQuery}, + require_transactional, traits::{Get, IsType, StorageVersion}, transactional, Blake2_128Concat, PalletError, PalletId, Parameter, }; @@ -74,7 +75,7 @@ mod pallet { DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::{ - constants::CENT, + constants::{BASE, CENT}, math::{ checked_ops_res::{CheckedAddRes, CheckedMulRes}, fixed::FixedMul, @@ -94,32 +95,28 @@ mod pallet { pub(crate) type PoolOf = Pool, BalanceOf>; const MIN_BALANCE: u128 = CENT; + pub(crate) const MAX_IN_RATIO: u128 = BASE / 3 + 1; + pub(crate) const MAX_OUT_RATIO: u128 = BASE / 3 + 1; #[pallet::call] impl Pallet { - /// Pool - Exit - /// - /// Retrieves a given set of assets from `pool_id` to `origin`. + /// Exchanges an LP's (liquidity provider's) pool shares for a proportionate amount of each + /// of the pool's assets. The assets received are distributed according to the LP's + /// percentage ownership of the pool. /// /// # Arguments /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `pool_amount`: The amount of LP shares of this pool being burned based on the - /// retrieved assets. - /// * `min_assets_out`: List of asset lower bounds. No asset should be lower than the - /// provided values. + /// * `pool_id`: The ID of the pool to withdraw from. + /// * `pool_amount`: The amount of pool shares to burn. + /// * `min_assets_out`: List of lower bounds on the assets received. The transaction is + /// rolled back if any of the specified lower bounds are violated. /// /// # Weight /// - /// Complexity: `O(n)` where `n` is the number of assets in the specified pool - // Using `min_assets_out.len()` is fine because we don't iterate over the assets before - // verifying that `min_assets_out` has the correct length. We do limit the linear factor to - // the maximum number of assets to prevent unnecessary spending in case of erroneous input, - // though. + /// Complexity: `O(n)` where `n` is the number of assets in the specified pool. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::pool_exit( - min_assets_out.len().min(T::MaxAssets::get().into()) as u32 + min_assets_out.len().min(T::MaxAssets::get().into()) as u32, ))] #[transactional] pub fn pool_exit( @@ -132,57 +129,43 @@ mod pallet { Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) } - /// Pool - Exit with exact pool amount - /// - /// Takes an asset from `pool_id` and transfers to `origin`. Differently from `pool_exit`, - /// this method injects the exactly amount of `asset_amount` to `origin`. - /// - /// # Arguments - /// - /// * `origin`: 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. - /// * `max_pool_amount`: The calculated amount of assets for the pool must be equal or - /// greater than the given value. + /// See [`zeitgeist_primitives::traits::Swaps::pool_exit_with_exact_asset_amount`]. /// /// # Weight /// /// Complexity: `O(1)` #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::pool_exit_with_exact_asset_amount())] - // MARK(non-transactional): Immediately calls and returns a transactional. + #[transactional] pub fn pool_exit_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: AssetOf, + asset_out: AssetOf, #[pallet::compact] asset_amount: BalanceOf, #[pallet::compact] max_pool_amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - >::pool_exit_with_exact_asset_amount( + Self::do_pool_exit_with_exact_asset_amount( who, pool_id, - asset, + asset_out, asset_amount, max_pool_amount, ) - .map(|_| ()) } - /// Pool - Exit with exact pool amount - /// - /// Takes an asset from `pool_id` and transfers to `origin`. Differently from `pool_exit`, - /// this method injects the exactly amount of `pool_amount` to `pool_id`. + /// Exchanges an exact amount of an LP's (liquidity provider's) pool shares for a + /// proportionate amount of _one_ of the pool's assets. The assets received are distributed + /// according to the LP's percentage ownership of the pool. /// /// # Arguments /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `asset`: Asset leaving the pool. + /// * `pool_id`: The ID of the pool to withdraw from. + /// * `asset`: The asset received by the LP. + /// * `asset_amount`: The amount of `asset` leaving the pool. /// * `pool_amount`: Pool amount that is entering the pool. - /// * `min_asset_amount`: The calculated amount for the asset must the equal or less - /// than the given value. + /// * `min_asset_amount`: The minimum amount the LP asks to receive. The transaction is + /// rolled back if this bound is violated. /// /// # Weight /// @@ -197,67 +180,29 @@ mod pallet { #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] min_asset_amount: BalanceOf, ) -> DispatchResult { - ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); - let pool = Self::pool_by_id(pool_id)?; - let pool_ref = &pool; let who = ensure_signed(origin)?; - let who_clone = who.clone(); - Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; - - let params = PoolExitWithExactAmountParams { - asset, - asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = total_supply.bmul(T::MaxInRatio::get())?; - ensure!(pool_amount <= mul, Error::::MaxInRatio); - let asset_amount: BalanceOf = crate::math::calc_single_out_given_pool_in( - asset_balance.saturated_into(), - Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), - total_supply.saturated_into(), - pool.total_weight.saturated_into(), - pool_amount.saturated_into(), - pool.swap_fee.saturated_into(), - T::ExitFee::get().saturated_into(), - )? - .saturated_into(); - ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); - ensure!(asset_amount >= min_asset_amount, Error::::LimitOut); - ensure!( - asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, - Error::::MaxOutRatio - ); - Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; - Ok(asset_amount) - }, - bound: min_asset_amount, - ensure_balance: |_| Ok(()), - event: |evt| Self::deposit_event(Event::PoolExitWithExactPoolAmount(evt)), - who: who_clone, - pool_amount: |_, _| Ok(pool_amount), + Self::do_pool_exit_with_exact_pool_amount( + who, pool_id, - pool: pool_ref, - }; - pool_exit_with_exact_amount::<_, _, _, _, T>(params) + asset, + pool_amount, + min_asset_amount, + ) } - /// Pool - Join - /// - /// Joins a given set of assets provided from `origin` to `pool_id`. + /// Exchanges a proportional amount of each asset of the pool for pool shares. /// /// # Arguments /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `pool_amount`: The amount of LP shares for this pool that should be minted to the provider. - /// * `max_assets_in`: List of asset upper bounds. No asset should be greater than the - /// provided values. + /// * `pool_id`: The ID of the pool to join. + /// * `pool_amount`: The amount of LP shares for this pool that should be minted to the + /// provider. + /// * `max_assets_in`: List of upper bounds on the assets to move to the pool. The + /// transaction is rolled back if any of the specified lower bounds are violated. /// /// # Weight /// /// Complexity: `O(n)` where `n` is the number of assets in the specified pool - // Using `min_assets_out.len()` is fine because we don't iterate over the assets before - // verifying that `min_assets_out` has the correct length. We do limit the linear factor to - // the maximum number of assets to prevent unnecessary spending in case of erroneous input, - // though. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::pool_join( max_assets_in.len().min(T::MaxAssets::get().into()) as u32, @@ -268,54 +213,19 @@ mod pallet { #[pallet::compact] pool_id: PoolId, #[pallet::compact] pool_amount: BalanceOf, max_assets_in: Vec>, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); - let pool = Self::pool_by_id(pool_id)?; - ensure!(pool.status == PoolStatus::Open, Error::::InvalidPoolStatus); - let pool_account_id = Pallet::::pool_account_id(&pool_id); - - let params = PoolParams { - asset_bounds: max_assets_in, - event: |evt| Self::deposit_event(Event::PoolJoin(evt)), - pool_account_id: &pool_account_id, - pool_amount, - pool_id, - pool: &pool, - transfer_asset: |amount, amount_bound, asset| { - ensure!(amount <= amount_bound, Error::::LimitIn); - T::AssetManager::transfer(asset, &who, &pool_account_id, amount)?; - Ok(()) - }, - transfer_pool: || Self::mint_pool_shares(pool_id, &who, pool_amount), - fee: |_| Ok(0u128.saturated_into()), - who: who.clone(), - }; - - crate::utils::pool::<_, _, _, _, T>(params)?; - Ok(Some(T::WeightInfo::pool_join(pool.assets.len().saturated_into())).into()) + Self::do_pool_join(who, pool_id, pool_amount, max_assets_in) } - /// Pool - Join with exact asset amount - /// - /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, - /// this method transfers the exactly amount of `asset_amount` to `pool_id`. - /// - /// # Arguments - /// - /// * `origin`: 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. - /// * `min_pool_amount`: The calculated amount for the pool must be equal or greater - /// than the given value. + /// See [`zeitgeist_primitives::traits::Swaps::pool_join_with_exact_asset_amount`]. /// /// # Weight /// /// Complexity: O(1) - // MARK(non-transactional): Immediately calls and returns a transactional. #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::pool_join_with_exact_asset_amount())] + #[transactional] pub fn pool_join_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, @@ -324,29 +234,25 @@ mod pallet { #[pallet::compact] min_pool_amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - >::pool_join_with_exact_asset_amount( + Self::do_pool_join_with_exact_asset_amount( who, pool_id, asset_in, asset_amount, min_pool_amount, ) - .map(|_| ()) } - /// Pool - Join with exact pool amount - /// - /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, - /// this method injects the exactly amount of `pool_amount` to `origin`. + /// Exchanges an LP's (liquidity provider's) holdings of _one_ of the assets in the pool for + /// an exact amount of pool shares. /// /// # Arguments /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `asset`: Asset entering the pool. + /// * `pool_id`: The ID of the pool to withdraw from. + /// * `asset`: The asset entering the pool. /// * `pool_amount`: Asset amount that is entering the pool. - /// * `max_asset_amount`: The calculated amount of assets for the pool must be equal or - /// less than the given value. + /// * `max_asset_amount`: The maximum amount of `asset` that the LP is willing to pay. The + /// transaction is rolled back if this bound is violated. /// /// # Weight /// @@ -361,63 +267,23 @@ mod pallet { #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] max_asset_amount: BalanceOf, ) -> DispatchResult { - let pool = Pallet::::pool_by_id(pool_id)?; - let pool_account_id = Pallet::::pool_account_id(&pool_id); let who = ensure_signed(origin)?; - let who_clone = who.clone(); - let params = PoolJoinWithExactAmountParams { - asset, - asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = total_supply.bmul(T::MaxOutRatio::get())?; - ensure!(pool_amount <= mul, Error::::MaxOutRatio); - let asset_amount: BalanceOf = crate::math::calc_single_in_given_pool_out( - asset_balance.saturated_into(), - Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), - total_supply.saturated_into(), - pool.total_weight.saturated_into(), - pool_amount.saturated_into(), - pool.swap_fee.saturated_into(), - )? - .saturated_into(); - ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); - ensure!(asset_amount <= max_asset_amount, Error::::LimitIn); - ensure!( - asset_amount <= asset_balance.checked_mul_res(&T::MaxInRatio::get())?, - Error::::MaxInRatio - ); - Ok(asset_amount) - }, - bound: max_asset_amount, - event: |evt| Self::deposit_event(Event::PoolJoinWithExactPoolAmount(evt)), - pool_account_id: &pool_account_id, - pool_amount: |_, _| Ok(pool_amount), + Self::do_pool_join_with_exact_pool_amount( + who, pool_id, - pool: &pool, - who: who_clone, - }; - pool_join_with_exact_amount::<_, _, _, T>(params) + asset, + pool_amount, + max_asset_amount, + ) } - /// Swap - Exact amount in - /// - /// Swaps a given `asset_amount_in` of the `asset_in/asset_out` pair to `pool_id`. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. - /// * `asset_amount_in`: Amount that will be transferred from the provider to the pool. - /// * `asset_out`: Asset leaving the pool. - /// * `min_asset_amount_out`: Minimum asset amount that can leave the pool. - /// * `max_price`: Market price must be equal or less than the provided value. + /// See [`zeitgeist_primitives::traits::Swaps::swap_exact_amount_in`]. /// /// # Weight /// - /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of - /// assets if the scoring rule is Rikiddo. + /// Complexity: `O(1)` #[pallet::call_index(9)] - #[pallet::weight(T::WeightInfo::swap_exact_amount_in_cpmm())] + #[pallet::weight(T::WeightInfo::swap_exact_amount_in())] #[transactional] pub fn swap_exact_amount_in( origin: OriginFor, @@ -427,9 +293,9 @@ mod pallet { asset_out: AssetOf, min_asset_amount_out: Option>, max_price: Option>, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - let weight = >::swap_exact_amount_in( + Self::do_swap_exact_amount_in( who, pool_id, asset_in, @@ -437,30 +303,16 @@ mod pallet { asset_out, min_asset_amount_out, max_price, - )?; - Ok(Some(weight).into()) + ) } - /// Swap - Exact amount out - /// - /// Swaps a given `asset_amount_out` of the `asset_in/asset_out` pair to `origin`. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be received. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. - /// * `max_asset_amount_in`: Maximum asset amount that can enter the pool. - /// * `asset_out`: Asset leaving the pool. - /// * `asset_amount_out`: Amount that will be transferred from the pool to the provider. - /// * `max_price`: Market price must be equal or less than the provided value. + /// See [`zeitgeist_primitives::traits::Swaps::swap_exact_amount_out`]. /// /// # Weight /// - /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of - /// assets if the scoring rule is Rikiddo. + /// Complexity: `O(1)` #[pallet::call_index(10)] - #[pallet::weight(T::WeightInfo::swap_exact_amount_out_cpmm())] + #[pallet::weight(T::WeightInfo::swap_exact_amount_out())] #[transactional] pub fn swap_exact_amount_out( origin: OriginFor, @@ -470,9 +322,9 @@ mod pallet { asset_out: AssetOf, #[pallet::compact] asset_amount_out: BalanceOf, max_price: Option>, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - let weight = >::swap_exact_amount_out( + Self::do_swap_exact_amount_out( who, pool_id, asset_in, @@ -480,13 +332,17 @@ mod pallet { asset_out, asset_amount_out, max_price, - )?; - Ok(Some(weight).into()) + ) } + /// Forcibly withdraw an LPs share. All parameters as in `exit`, except that `who` is the LP + /// whose shares are withdrawn. + /// + /// Used in the migration from swaps to neo-swaps. Deprecated and scheduled for removal in + /// v0.5.3. #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::pool_exit( - min_assets_out.len().min(T::MaxAssets::get().into()) as u32 + min_assets_out.len().min(T::MaxAssets::get().into()) as u32, ))] #[transactional] pub fn force_pool_exit( @@ -503,7 +359,6 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// Shares of outcome assets and native currency type AssetManager: ZeitgeistAssetManager; type Asset: Parameter @@ -517,125 +372,123 @@ mod pallet { 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>; + /// The maximum number of assets allowed in a single pool. #[pallet::constant] type MaxAssets: Get; - #[pallet::constant] - type MaxInRatio: Get>; - - #[pallet::constant] - type MaxOutRatio: Get>; - + /// The maximum allowed swap fee. #[pallet::constant] type MaxSwapFee: Get>; + /// The maximum total weight of assets in a pool. #[pallet::constant] type MaxTotalWeight: Get>; + /// The maximum weight of each individual asset in a pool. #[pallet::constant] type MaxWeight: Get>; + /// The minimum number of assets allowed in a single pool. #[pallet::constant] - /// The minimum amount of assets in a pool. type MinAssets: Get; + /// The minimum weight of each individual asset in a pool. #[pallet::constant] type MinWeight: Get>; - /// The module identifier. #[pallet::constant] type PalletId: Get; } #[pallet::error] pub enum Error { - /// The weight of an asset in a CPMM swap pool is greather than the upper weight cap. + /// The weight of an asset in a CPMM swap pool is greater than the upper weight cap. + #[codec(index = 0)] AboveMaximumWeight, - /// The weight of an asset in a CPMM swap pool could not be found. - AssetNotBound, /// The asset in question could not be found within the pool. + #[codec(index = 2)] AssetNotInPool, - /// The base asset of the swaps pool was None although a value was expected. - BaseAssetNotFound, /// The spot price of an asset pair was greater than the specified limit. + #[codec(index = 4)] BadLimitPrice, /// The weight of an asset in a CPMM swap pool is lower than the upper weight cap. + #[codec(index = 5)] BelowMinimumWeight, /// Some funds could not be transferred due to a too low balance. + #[codec(index = 6)] InsufficientBalance, /// Liquidity provided to new CPMM pool is less than the minimum allowed balance. + #[codec(index = 7)] InsufficientLiquidity, - /// 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. + #[codec(index = 10)] InvalidPoolStatus, /// A function was called for a swaps pool that does not fulfill the state requirement. + #[codec(index = 11)] InvalidStateTransition, - /// Could not create CPMM pool since no weights were supplied. - InvalidWeightArgument, - /// A transferal of funds into a swaps pool was above a threshhold specified by the sender. + /// A transferal of funds into a swaps pool was above a threshold specified by the sender. + #[codec(index = 13)] LimitIn, - /// Subsidy amount is too small. - InvalidSubsidyAmount, /// No limit was specified for a swap. + #[codec(index = 15)] LimitMissing, - /// A transferal of funds out of a swaps pool was below a threshhold specified by the + /// A transferal of funds out of a swaps pool was below a threshold specified by the /// receiver. + #[codec(index = 16)] LimitOut, /// The custom math library yielded an invalid result (most times unexpected zero value). + #[codec(index = 17)] MathApproximation, /// The proportion of an asset added into a pool in comparison to the amount - /// of that asset in the pool is above the threshhold specified by a constant. + /// of that asset in the pool is above the threshold specified by a constant. + #[codec(index = 18)] MaxInRatio, /// The proportion of an asset taken from a pool in comparison to the amount - /// of that asset in the pool is above the threshhold specified by a constant. + /// of that asset in the pool is above the threshold specified by a constant. + #[codec(index = 19)] MaxOutRatio, - /// The total weight of all assets within a CPMM pool is above a treshhold specified + /// The total weight of all assets within a CPMM pool is above a threshold specified /// by a constant. + #[codec(index = 20)] MaxTotalWeight, /// The pool in question does not exist. + #[codec(index = 21)] PoolDoesNotExist, /// A pool balance dropped below the allowed minimum. + #[codec(index = 22)] PoolDrain, /// The pool in question is inactive. + #[codec(index = 23)] PoolIsNotActive, - /// The CPMM pool in question does not have a fee, although it should. - PoolMissingFee, - /// The Rikiddo pool in question does not have subsidy, although it should. - PoolMissingSubsidy, - /// The CPPM pool in question does not have weights, although it should. - PoolMissingWeight, /// Two vectors do not have the same length (usually CPMM pool assets and weights). + #[codec(index = 27)] ProvidedValuesLenMustEqualAssetsLen, - /// No swap fee information found for CPMM pool - SwapFeeMissing, /// The swap fee is higher than the allowed maximum. + #[codec(index = 29)] SwapFeeTooHigh, - /// Tried to create a pool that has less assets than the lower threshhold specified by + /// Tried to create a pool that has less assets than the lower threshold specified by /// a constant. + #[codec(index = 30)] TooFewAssets, - /// Tried to create a pool that has more assets than the upper threshhold specified by + /// Tried to create a pool that has more assets than the upper threshold specified by /// a constant. + #[codec(index = 31)] TooManyAssets, /// Tried to create a pool with at least two identical assets. + #[codec(index = 32)] SomeIdenticalAssets, - /// The pool does not support swapping the assets in question. - UnsupportedTrade, - /// The outcome asset specified as the winning asset was not found in the pool. - WinningAssetNotFound, /// Some amount in a transaction equals zero. + #[codec(index = 35)] ZeroAmount, /// An unexpected error occurred. This is the result of faulty pallet logic and should be /// reported to the pallet maintainers. + #[codec(index = 36)] Unexpected(UnexpectedError), } @@ -656,56 +509,57 @@ mod pallet { where T: Config, { - /// 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>, - PoolOf, - BalanceOf, - T::AccountId, - ), - /// A pool was closed. \[pool_id\] - PoolClosed(PoolId), - /// A pool was cleaned up. \[pool_id\] - PoolCleanedUp(PoolId), - /// A pool was opened. \[pool_id\] - PoolActive(PoolId), - /// Someone has exited a pool. \[PoolAssetsEvent\] - PoolExit(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), - /// Exits a pool given an exact amount of an asset. \[PoolAssetEvent\] - PoolExitWithExactAssetAmount( - PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, - ), - /// Exits a pool given an exact pool's amount. \[PoolAssetEvent\] - PoolExitWithExactPoolAmount( - PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, - ), - /// Someone has joined a pool. \[PoolAssetsEvent\] - PoolJoin(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), - /// Joins a pool given an exact amount of an asset. \[PoolAssetEvent\] - PoolJoinWithExactAssetAmount( - PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, - ), - /// Joins a pool given an exact pool's amount. \[PoolAssetEvent\] - PoolJoinWithExactPoolAmount( - PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, - ), - /// Pool was manually destroyed. \[pool_id\] - PoolDestroyed(PoolId), - /// Pool destroyed due to insufficient subsidy. \[pool_id, \[(provider, subsidy), ...\]\] - PoolDestroyedInSubsidyPhase( - PoolId, - Vec<(::AccountId, BalanceOf)>, - ), - /// An exact amount of an asset is entering the pool. \[SwapEvent\] - SwapExactAmountIn( - SwapEvent<::AccountId, AssetOf, BalanceOf>, - ), - /// An exact amount of an asset is leaving the pool. \[SwapEvent\] - SwapExactAmountOut( - SwapEvent<::AccountId, AssetOf, BalanceOf>, - ), + /// Share holder rewards were distributed. + #[codec(index = 0)] + DistributeShareHolderRewards { + pool_id: PoolId, + num_accounts_rewarded: u64, + amount: BalanceOf, + }, + /// A new pool has been created. + #[codec(index = 1)] + PoolCreate { + common: CommonPoolEventParams>, + pool: PoolOf, + pool_amount: BalanceOf, + pool_account: T::AccountId, + }, + /// A pool was closed. + #[codec(index = 2)] + PoolClosed { pool_id: PoolId }, + /// A pool was cleaned up. + #[codec(index = 3)] + PoolCleanedUp { pool_id: PoolId }, + /// A pool was opened. + #[codec(index = 4)] + PoolActive { pool_id: PoolId }, + /// Someone has exited a pool. + #[codec(index = 5)] + PoolExit(PoolAssetsEvent, AssetOf, BalanceOf>), + /// Exits a pool given an exact amount of an asset. + #[codec(index = 6)] + PoolExitWithExactAssetAmount(PoolAssetEvent, AssetOf, BalanceOf>), + /// Exits a pool given an exact pool's amount. + #[codec(index = 7)] + PoolExitWithExactPoolAmount(PoolAssetEvent, AssetOf, BalanceOf>), + /// Someone has joined a pool. + #[codec(index = 8)] + PoolJoin(PoolAssetsEvent, AssetOf, BalanceOf>), + /// Joins a pool given an exact amount of an asset. + #[codec(index = 9)] + PoolJoinWithExactAssetAmount(PoolAssetEvent, AssetOf, BalanceOf>), + /// Joins a pool given an exact pool's amount. + #[codec(index = 10)] + PoolJoinWithExactPoolAmount(PoolAssetEvent, AssetOf, BalanceOf>), + /// Pool was manually destroyed. + #[codec(index = 11)] + PoolDestroyed { pool_id: PoolId }, + /// An exact amount of an asset is entering the pool. + #[codec(index = 13)] + SwapExactAmountIn(SwapEvent, AssetOf, BalanceOf>), + /// An exact amount of an asset is leaving the pool. + #[codec(index = 14)] + SwapExactAmountOut(SwapEvent, AssetOf, BalanceOf>), } #[pallet::pallet] @@ -723,6 +577,7 @@ mod pallet { pub(crate) type NextPoolId = StorageValue<_, PoolId, ValueQuery>; impl Pallet { + #[require_transactional] fn do_pool_exit( who: T::AccountId, pool_id: PoolId, @@ -730,11 +585,11 @@ mod pallet { 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)?; // 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)), @@ -744,8 +599,17 @@ mod pallet { 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)?; + // If transferring to `who` triggers the existential deposit, burn the tokens + // instead. + let new_balance = + T::AssetManager::free_balance(asset, &who).checked_add_res(&amount)?; + if new_balance >= T::AssetManager::minimum_balance(asset) { + ensure!(amount >= amount_bound, Error::::LimitOut); + T::AssetManager::transfer(asset, &pool_account_id, &who, amount)?; + } else { + ensure!(amount_bound.is_zero(), Error::::LimitOut); + T::AssetManager::withdraw(asset, &pool_account_id, amount)?; + } Ok(()) }, transfer_pool: || { @@ -756,74 +620,404 @@ mod pallet { let exit_fee_amount = amount.bmul(Self::calc_exit_fee(&pool))?; Ok(exit_fee_amount) }, - who: who_clone, + who: who.clone(), }; + crate::utils::pool::<_, _, _, _, T>(params) } - pub fn get_spot_price( - pool_id: &PoolId, - 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)?; + #[require_transactional] + fn do_pool_exit_with_exact_pool_amount( + who: AccountIdOf, + pool_id: PoolId, + asset: AssetOf, + pool_amount: BalanceOf, + min_asset_amount: BalanceOf, + ) -> DispatchResult { + ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); + let pool = Self::pool_by_id(pool_id)?; + let pool_ref = &pool; + Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; - let swap_fee = if with_fees { - pool.swap_fee.saturated_into() - } else { - BalanceOf::::zero().saturated_into() + let params = PoolExitWithExactAmountParams { + asset, + asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { + let mul = total_supply.bmul(MAX_IN_RATIO.saturated_into())?; + ensure!(pool_amount <= mul, Error::::MaxInRatio); + let asset_amount: BalanceOf = crate::math::calc_single_out_given_pool_in( + asset_balance.saturated_into(), + Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), + total_supply.saturated_into(), + pool.total_weight.saturated_into(), + pool_amount.saturated_into(), + pool.swap_fee.saturated_into(), + T::ExitFee::get().saturated_into(), + )? + .saturated_into(); + ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); + ensure!(asset_amount >= min_asset_amount, Error::::LimitOut); + ensure!( + asset_amount <= asset_balance.bmul(MAX_OUT_RATIO.saturated_into())?, + Error::::MaxOutRatio + ); + Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; + Ok(asset_amount) + }, + bound: min_asset_amount, + ensure_balance: |_| Ok(()), + event: |evt| Self::deposit_event(Event::PoolExitWithExactPoolAmount(evt)), + who, + pool_amount: |_, _| Ok(pool_amount), + pool_id, + pool: pool_ref, }; - Ok(crate::math::calc_spot_price( - balance_in.saturated_into(), - in_weight.saturated_into(), - balance_out.saturated_into(), - out_weight.saturated_into(), - swap_fee, - )? - .saturated_into()) + pool_exit_with_exact_amount::<_, _, _, _, T>(params) } - #[inline] - pub fn pool_account_id(pool_id: &PoolId) -> T::AccountId { - T::PalletId::get().into_sub_account_truncating((*pool_id).saturated_into::()) - } + #[require_transactional] + fn do_pool_join( + who: AccountIdOf, + pool_id: PoolId, + pool_amount: BalanceOf, + max_assets_in: Vec>, + ) -> DispatchResult { + ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); + let pool = Self::pool_by_id(pool_id)?; + ensure!(pool.status == PoolStatus::Open, Error::::InvalidPoolStatus); + let pool_account_id = Pallet::::pool_account_id(&pool_id); - /// The minimum allowed balance of `asset` in a liquidity pool. - pub(crate) fn min_balance(asset: AssetOf) -> BalanceOf { - T::AssetManager::minimum_balance(asset).max(MIN_BALANCE.saturated_into()) - } + let params = PoolParams { + asset_bounds: max_assets_in, + event: |evt| Self::deposit_event(Event::PoolJoin(evt)), + pool_account_id: &pool_account_id, + pool_amount, + pool_id, + pool: &pool, + transfer_asset: |amount, amount_bound, asset| { + ensure!(amount <= amount_bound, Error::::LimitIn); + T::AssetManager::transfer(asset, &who, &pool_account_id, amount)?; + Ok(()) + }, + transfer_pool: || Self::mint_pool_shares(pool_id, &who, pool_amount), + fee: |_| Ok(0u128.saturated_into()), + who: who.clone(), + }; - /// Returns the minimum allowed balance allowed for a pool with id `pool_id` containing - /// `assets`. - /// - /// The minimum allowed balance is the maximum of all minimum allowed balances of assets - /// contained in the pool, _including_ the pool shares asset. This ensures that none of the - /// accounts involved are slashed when a pool is created with the minimum amount. - /// - /// **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: &[AssetOf]) -> BalanceOf { - assets - .iter() - .map(|asset| Self::min_balance(*asset)) - .max() - .unwrap_or_else(|| MIN_BALANCE.saturated_into()) - .max(Self::min_balance(Self::pool_shares_id(pool_id))) + crate::utils::pool::<_, _, _, _, T>(params) } - fn ensure_minimum_liquidity_shares( + #[require_transactional] + fn do_pool_exit_with_exact_asset_amount( + who: AccountIdOf, pool_id: PoolId, - pool: &PoolOf, - amount: BalanceOf, + asset: AssetOf, + asset_amount: BalanceOf, + max_pool_amount: BalanceOf, + ) -> DispatchResult { + let pool = Self::pool_by_id(pool_id)?; + Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; + let pool_ref = &pool; + + let params = PoolExitWithExactAmountParams { + asset, + asset_amount: |_, _| Ok(asset_amount), + bound: max_pool_amount, + ensure_balance: |asset_balance: BalanceOf| { + ensure!( + asset_amount <= asset_balance.bmul(MAX_OUT_RATIO.saturated_into())?, + Error::::MaxOutRatio + ); + Ok(()) + }, + pool_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { + let pool_amount: BalanceOf = crate::math::calc_pool_in_given_single_out( + asset_balance.saturated_into(), + Self::pool_weight_rslt(pool_ref, &asset)?.saturated_into(), + total_supply.saturated_into(), + pool_ref.total_weight.saturated_into(), + asset_amount.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)?; + Ok(pool_amount) + }, + event: |evt| Self::deposit_event(Event::PoolExitWithExactAssetAmount(evt)), + who, + pool_id, + pool: pool_ref, + }; + + pool_exit_with_exact_amount::<_, _, _, _, T>(params) + } + + #[require_transactional] + fn do_pool_join_with_exact_asset_amount( + who: AccountIdOf, + pool_id: PoolId, + asset_in: AssetOf, + asset_amount: BalanceOf, + min_pool_amount: BalanceOf, + ) -> DispatchResult { + ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); + let pool = Pallet::::pool_by_id(pool_id)?; + let pool_ref = &pool; + let pool_account_id = Pallet::::pool_account_id(&pool_id); + + let params = PoolJoinWithExactAmountParams { + asset: asset_in, + asset_amount: |_, _| Ok(asset_amount), + bound: min_pool_amount, + pool_amount: move |asset_balance: BalanceOf, total_supply: BalanceOf| { + let mul = asset_balance.bmul(MAX_IN_RATIO.saturated_into())?; + ensure!(asset_amount <= mul, Error::::MaxInRatio); + let pool_amount: BalanceOf = crate::math::calc_pool_out_given_single_in( + asset_balance.saturated_into(), + Self::pool_weight_rslt(pool_ref, &asset_in)?.saturated_into(), + total_supply.saturated_into(), + pool_ref.total_weight.saturated_into(), + asset_amount.saturated_into(), + pool_ref.swap_fee.saturated_into(), + )? + .saturated_into(); + ensure!(pool_amount >= min_pool_amount, Error::::LimitOut); + Ok(pool_amount) + }, + event: |evt| Self::deposit_event(Event::PoolJoinWithExactAssetAmount(evt)), + who, + pool_account_id: &pool_account_id, + pool_id, + pool: pool_ref, + }; + + pool_join_with_exact_amount::<_, _, _, T>(params) + } + + #[require_transactional] + fn do_pool_join_with_exact_pool_amount( + who: AccountIdOf, + pool_id: PoolId, + asset: AssetOf, + pool_amount: BalanceOf, + max_asset_amount: BalanceOf, + ) -> DispatchResult { + let pool = Pallet::::pool_by_id(pool_id)?; + let pool_account_id = Pallet::::pool_account_id(&pool_id); + + let params = PoolJoinWithExactAmountParams { + asset, + asset_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { + let mul = total_supply.bmul(MAX_OUT_RATIO.saturated_into())?; + ensure!(pool_amount <= mul, Error::::MaxOutRatio); + let asset_amount: BalanceOf = crate::math::calc_single_in_given_pool_out( + asset_balance.saturated_into(), + Self::pool_weight_rslt(&pool, &asset)?.saturated_into(), + total_supply.saturated_into(), + pool.total_weight.saturated_into(), + pool_amount.saturated_into(), + pool.swap_fee.saturated_into(), + )? + .saturated_into(); + ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); + ensure!(asset_amount <= max_asset_amount, Error::::LimitIn); + ensure!( + asset_amount + <= asset_balance.checked_mul_res(&MAX_IN_RATIO.saturated_into())?, + Error::::MaxInRatio + ); + Ok(asset_amount) + }, + bound: max_asset_amount, + event: |evt| Self::deposit_event(Event::PoolJoinWithExactPoolAmount(evt)), + pool_account_id: &pool_account_id, + pool_amount: |_, _| Ok(pool_amount), + pool_id, + pool: &pool, + who, + }; + + pool_join_with_exact_amount::<_, _, _, T>(params) + } + + #[require_transactional] + fn do_swap_exact_amount_in( + who: T::AccountId, + pool_id: PoolId, + asset_in: AssetOf, + asset_amount_in: BalanceOf, + asset_out: AssetOf, + min_asset_amount_out: Option>, + max_price: Option>, + ) -> DispatchResult { + ensure!( + min_asset_amount_out.is_some() || max_price.is_some(), + Error::::LimitMissing, + ); + let pool = Pallet::::pool_by_id(pool_id)?; + let pool_account_id = Pallet::::pool_account_id(&pool_id); + ensure!( + T::AssetManager::free_balance(asset_in, &who) >= asset_amount_in, + Error::::InsufficientBalance + ); + + let params = SwapExactAmountParams { + // TODO(#1215): This probably doesn't need to be a closure. + asset_amounts: || { + 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(MAX_IN_RATIO.saturated_into())?, + Error::::MaxInRatio + ); + let asset_amount_out: BalanceOf = crate::math::calc_out_given_in( + balance_in.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_in)?.saturated_into(), + balance_out.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), + asset_amount_in.saturated_into(), + pool.swap_fee.saturated_into(), + )? + .saturated_into(); + + if let Some(maao) = min_asset_amount_out { + ensure!(asset_amount_out >= maao, Error::::LimitOut); + } + + Self::ensure_minimum_balance(pool_id, &pool, asset_out, asset_amount_out)?; + + Ok([asset_amount_in, asset_amount_out]) + }, + asset_bound: min_asset_amount_out, + asset_in, + asset_out, + event: |evt| Self::deposit_event(Event::SwapExactAmountIn(evt)), + max_price, + pool_account_id: &pool_account_id, + pool_id, + pool: &pool, + who: who.clone(), + }; + + swap_exact_amount::<_, _, T>(params) + } + + #[require_transactional] + fn do_swap_exact_amount_out( + who: AccountIdOf, + pool_id: PoolId, + asset_in: AssetOf, + max_asset_amount_in: Option>, + asset_out: AssetOf, + asset_amount_out: BalanceOf, + max_price: Option>, + ) -> DispatchResult { + 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 params = SwapExactAmountParams { + asset_amounts: || { + let balance_out = T::AssetManager::free_balance(asset_out, &pool_account_id); + ensure!( + asset_amount_out <= balance_out.bmul(MAX_OUT_RATIO.saturated_into())?, + 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)?.saturated_into(), + balance_out.saturated_into(), + Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), + asset_amount_out.saturated_into(), + pool.swap_fee.saturated_into(), + )? + .saturated_into(); + + if let Some(maai) = max_asset_amount_in { + ensure!(asset_amount_in <= maai, Error::::LimitIn); + } + + Ok([asset_amount_in, asset_amount_out]) + }, + asset_bound: max_asset_amount_in, + asset_in, + asset_out, + event: |evt| Self::deposit_event(Event::SwapExactAmountOut(evt)), + max_price, + pool_account_id: &pool_account_id, + pool_id, + pool: &pool, + who, + }; + + swap_exact_amount::<_, _, T>(params) + } + + pub fn get_spot_price( + pool_id: &PoolId, + 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)?; + + let swap_fee = if with_fees { pool.swap_fee } else { BalanceOf::::zero() }; + + Ok(crate::math::calc_spot_price( + balance_in.saturated_into(), + in_weight.saturated_into(), + balance_out.saturated_into(), + out_weight.saturated_into(), + swap_fee.saturated_into(), + )? + .saturated_into()) + } + + #[inline] + pub fn pool_account_id(pool_id: &PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating((*pool_id).saturated_into::()) + } + + /// The minimum allowed balance of `asset` in a liquidity pool. + pub(crate) fn min_balance(asset: AssetOf) -> BalanceOf { + T::AssetManager::minimum_balance(asset).max(MIN_BALANCE.saturated_into()) + } + + /// Returns the minimum allowed balance allowed for a pool with id `pool_id` containing + /// `assets`. + /// + /// The minimum allowed balance is the maximum of all minimum allowed balances of assets + /// contained in the pool, _including_ the pool shares asset. This ensures that none of the + /// accounts involved are slashed when a pool is created with the minimum amount. + /// + /// **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: &[AssetOf]) -> BalanceOf { + assets + .iter() + .map(|asset| Self::min_balance(*asset)) + .max() + .unwrap_or_else(|| MIN_BALANCE.saturated_into()) + .max(Self::min_balance(Self::pool_shares_id(pool_id))) + } + + fn ensure_minimum_liquidity_shares( + pool_id: PoolId, + pool: &PoolOf, + amount: BalanceOf, ) -> DispatchResult { if pool.status == PoolStatus::Closed { return Ok(()); @@ -862,14 +1056,7 @@ mod pallet { // Check that the account has at least as many free shares as we wish to burn! T::AssetManager::ensure_can_withdraw(shares_id, from, amount) .map_err(|_| Error::::InsufficientBalance)?; - let missing = T::AssetManager::slash(shares_id, from, amount); - debug_assert!( - missing.is_zero(), - "Could not slash all of the amount. shares_id {:?}, who: {:?}, amount: {:?}.", - shares_id, - &from, - amount, - ); + T::AssetManager::withdraw(shares_id, from, amount)?; Ok(()) } @@ -881,9 +1068,10 @@ mod pallet { where T: Config, { - if assets.len() != provided_values.len() { - return Err(Error::::ProvidedValuesLenMustEqualAssetsLen); - } + ensure!( + assets.len() == provided_values.len(), + Error::::ProvidedValuesLenMustEqualAssetsLen + ); Ok(()) } @@ -923,7 +1111,7 @@ mod pallet { Ok(id) } - // Mutates a stored pool. Returns `Err` if `pool_id` does not exist. + /// Mutates a stored pool. pub(crate) fn mutate_pool(pool_id: PoolId, mut cb: F) -> DispatchResult where F: FnMut(&mut PoolOf) -> DispatchResult, @@ -942,7 +1130,7 @@ mod pallet { pool: &PoolOf, asset: &AssetOf, ) -> Result, Error> { - pool.weights.get(asset).cloned().ok_or(Error::::AssetNotBound) + pool.weights.get(asset).cloned().ok_or(Error::::AssetNotInPool) } /// Calculate the exit fee percentage for `pool`. @@ -963,20 +1151,6 @@ mod pallet { type Asset = AssetOf; type Balance = BalanceOf; - /// Creates an initial active pool. - /// - /// # Arguments - /// - /// * `who`: The account that is the creator of the pool. Must have enough - /// funds for each of the assets to cover the `amount`. - /// * `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. - /// * `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 - /// `scoring_rule` is `RikiddoSigmoidFeeMarketEma`. - /// * `weights`: These are the raw/denormalized weights (mandatory if scoring rule is CPMM). #[frame_support::transactional] fn create_pool( who: T::AccountId, @@ -1038,12 +1212,12 @@ mod pallet { Pools::::insert(next_pool_id, pool.clone()); - Self::deposit_event(Event::PoolCreate( - CommonPoolEventParams { pool_id: next_pool_id, who }, + Self::deposit_event(Event::PoolCreate { + common: CommonPoolEventParams { pool_id: next_pool_id, who }, pool, - amount, + pool_amount: amount, pool_account, - )); + }); Ok(next_pool_id) } @@ -1056,7 +1230,7 @@ mod pallet { pool.status = PoolStatus::Closed; Ok(pool.assets.len() as u32) })?; - Self::deposit_event(Event::PoolClosed(pool_id)); + Self::deposit_event(Event::PoolClosed { pool_id }); Ok(T::WeightInfo::close_pool(asset_len)) } @@ -1066,20 +1240,12 @@ mod pallet { let asset_len = pool.assets.len() as u32; for asset in pool.assets.into_iter() { let amount = T::AssetManager::free_balance(asset, &pool_account); - let missing = T::AssetManager::slash(asset, &pool_account, amount); - debug_assert!( - missing.is_zero(), - "Could not slash all of the amount. asset {:?}, pool_account: {:?}, amount: \ - {:?}.", - asset, - &pool_account, - amount, - ); + T::AssetManager::withdraw(asset, &pool_account, amount)?; } // NOTE: Currently we don't clean up accounts with pool_share_id. // TODO(#792): Remove pool_share_id asset for accounts! It may require storage migration. Pools::::remove(pool_id); - Self::deposit_event(Event::PoolDestroyed(pool_id)); + Self::deposit_event(Event::PoolDestroyed { pool_id }); Ok(T::WeightInfo::destroy_pool(asset_len)) } @@ -1091,143 +1257,42 @@ mod pallet { })?; let pool = Pools::::get(pool_id).ok_or(Error::::PoolDoesNotExist)?; let asset_len = pool.assets.len() as u32; - Self::deposit_event(Event::PoolActive(pool_id)); + Self::deposit_event(Event::PoolActive { pool_id }); Ok(T::WeightInfo::open_pool(asset_len)) } - /// Pool - Exit with exact pool amount - /// - /// Takes an asset from `pool_id` and transfers to `origin`. Differently from `pool_exit`, - /// this method injects the exactly amount of `asset_amount` to `origin`. - /// - /// # Arguments - /// - /// * `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. - /// * `max_pool_amount`: The calculated amount of assets for the pool must be equal or - /// greater than the given value. - #[frame_support::transactional] fn pool_exit_with_exact_asset_amount( who: T::AccountId, pool_id: PoolId, asset: AssetOf, asset_amount: BalanceOf, max_pool_amount: BalanceOf, - ) -> Result { - let pool = Self::pool_by_id(pool_id)?; - Self::ensure_minimum_balance(pool_id, &pool, asset, asset_amount)?; - let pool_ref = &pool; - let who_clone = who.clone(); - - let params = PoolExitWithExactAmountParams { - asset, - asset_amount: |_, _| Ok(asset_amount), - bound: max_pool_amount, - ensure_balance: |asset_balance: BalanceOf| { - ensure!( - asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, - Error::::MaxOutRatio - ); - Ok(()) - }, - pool_amount: |asset_balance: BalanceOf, total_supply: BalanceOf| { - let pool_amount: BalanceOf = crate::math::calc_pool_in_given_single_out( - asset_balance.saturated_into(), - Self::pool_weight_rslt(pool_ref, &asset)?.saturated_into(), - total_supply.saturated_into(), - pool_ref.total_weight.saturated_into(), - asset_amount.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)?; - Ok(pool_amount) - }, - event: |evt| Self::deposit_event(Event::PoolExitWithExactAssetAmount(evt)), - who: who_clone, + ) -> DispatchResult { + Self::do_pool_exit_with_exact_asset_amount( + who, pool_id, - pool: pool_ref, - }; - let weight = T::WeightInfo::pool_exit_with_exact_asset_amount(); - pool_exit_with_exact_amount::<_, _, _, _, T>(params).map(|_| weight) + asset, + asset_amount, + max_pool_amount, + ) } - /// Pool - Join with exact asset amount - /// - /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, - /// this method transfers the exactly amount of `asset_amount` to `pool_id`. - /// - /// # Arguments - /// - /// * `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. - /// * `min_pool_amount`: The calculated amount for the pool must be equal or greater - /// than the given value. - #[frame_support::transactional] fn pool_join_with_exact_asset_amount( who: T::AccountId, pool_id: PoolId, asset_in: AssetOf, asset_amount: BalanceOf, min_pool_amount: BalanceOf, - ) -> Result { - ensure!(asset_amount != Zero::zero(), Error::::ZeroAmount); - let pool = Pallet::::pool_by_id(pool_id)?; - let pool_ref = &pool; - let pool_account_id = Pallet::::pool_account_id(&pool_id); - let who_clone = who.clone(); - - let params = PoolJoinWithExactAmountParams { - asset: asset_in, - asset_amount: |_, _| Ok(asset_amount), - bound: min_pool_amount, - pool_amount: move |asset_balance: BalanceOf, total_supply: BalanceOf| { - let mul: BalanceOf = asset_balance.bmul(T::MaxInRatio::get())?; - ensure!(asset_amount <= mul, Error::::MaxInRatio); - let pool_amount: BalanceOf = crate::math::calc_pool_out_given_single_in( - asset_balance.saturated_into(), - Self::pool_weight_rslt(pool_ref, &asset_in)?.saturated_into(), - total_supply.saturated_into(), - pool_ref.total_weight.saturated_into(), - asset_amount.saturated_into(), - pool_ref.swap_fee.saturated_into(), - )? - .saturated_into(); - ensure!(pool_amount >= min_pool_amount, Error::::LimitOut); - Ok(pool_amount) - }, - event: |evt| Self::deposit_event(Event::PoolJoinWithExactAssetAmount(evt)), - who: who_clone, - pool_account_id: &pool_account_id, + ) -> DispatchResult { + Self::do_pool_join_with_exact_asset_amount( + who, pool_id, - pool: pool_ref, - }; - let weight = T::WeightInfo::pool_join_with_exact_asset_amount(); - pool_join_with_exact_amount::<_, _, _, T>(params).map(|_| weight) + asset_in, + asset_amount, + min_pool_amount, + ) } - /// Swap - Exact amount in - /// - /// Swaps a given `asset_amount_in` of the `asset_in/asset_out` pair to `pool_id`. - /// - /// # Arguments - /// - /// * `who`: The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. - /// * `asset_amount_in`: Amount that will be transferred from the provider to the pool. - /// * `asset_out`: Asset leaving the pool. - /// * `min_asset_amount_out`: Minimum asset amount that can leave the pool. - /// * `max_price`: Market price must be equal or less than the provided value. - /// * `handle_fees`: Optional parameter to override the swap fee - #[allow(clippy::too_many_arguments)] fn swap_exact_amount_in( who: T::AccountId, pool_id: PoolId, @@ -1236,77 +1301,18 @@ mod pallet { asset_out: AssetOf, min_asset_amount_out: Option>, max_price: Option>, - ) -> Result { - ensure!( - min_asset_amount_out.is_some() || max_price.is_some(), - Error::::LimitMissing, - ); - - let pool = Pallet::::pool_by_id(pool_id)?; - let pool_account_id = Pallet::::pool_account_id(&pool_id); - - ensure!( - T::AssetManager::free_balance(asset_in, &who) >= asset_amount_in, - Error::::InsufficientBalance - ); - - let params = SwapExactAmountParams { - // TODO(#1215): This probably doesn't need to be a closure. - asset_amounts: || { - 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)?.saturated_into(), - balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), - asset_amount_in.saturated_into(), - pool.swap_fee.saturated_into(), - )? - .saturated_into(); - - if let Some(maao) = min_asset_amount_out { - ensure!(asset_amount_out >= maao, Error::::LimitOut); - } - - Self::ensure_minimum_balance(pool_id, &pool, asset_out, asset_amount_out)?; - - Ok([asset_amount_in, asset_amount_out]) - }, - asset_bound: min_asset_amount_out, + ) -> DispatchResult { + Self::do_swap_exact_amount_in( + who, + pool_id, asset_in, + asset_amount_in, asset_out, - event: |evt| Self::deposit_event(Event::SwapExactAmountIn(evt)), + min_asset_amount_out, max_price, - pool_account_id: &pool_account_id, - pool_id, - pool: &pool, - who: who.clone(), - }; - swap_exact_amount::<_, _, T>(params)?; - - Ok(T::WeightInfo::swap_exact_amount_in_cpmm()) + ) } - /// Swap - Exact amount out - /// - /// Swaps a given `asset_amount_out` of the `asset_in/asset_out` pair to `origin`. - /// - /// # Arguments - /// - /// * `who`: The account whose assets should be transferred. - /// * `pool_id`: Unique pool identifier. - /// * `asset_in`: Asset entering the pool. - /// * `max_amount_asset_in`: Maximum asset amount that can enter the pool. - /// * `asset_out`: Asset leaving the pool. - /// * `asset_amount_out`: Amount that will be transferred from the pool to the provider. - /// * `max_price`: Market price must be equal or less than the provided value. - /// * `handle_fees`: Whether additional fees are handled or not (sets LP fee to 0) - #[allow(clippy::too_many_arguments)] fn swap_exact_amount_out( who: T::AccountId, pool_id: PoolId, @@ -1315,50 +1321,16 @@ mod pallet { asset_out: AssetOf, asset_amount_out: BalanceOf, max_price: Option>, - ) -> 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 params = SwapExactAmountParams { - asset_amounts: || { - let balance_out = T::AssetManager::free_balance(asset_out, &pool_account_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)?.saturated_into(), - balance_out.saturated_into(), - Self::pool_weight_rslt(&pool, &asset_out)?.saturated_into(), - asset_amount_out.saturated_into(), - pool.swap_fee.saturated_into(), - )? - .saturated_into(); - - if let Some(maai) = max_asset_amount_in { - ensure!(asset_amount_in <= maai, Error::::LimitIn); - } - - Ok([asset_amount_in, asset_amount_out]) - }, - asset_bound: max_asset_amount_in, + ) -> DispatchResult { + Self::do_swap_exact_amount_out( + who, + pool_id, asset_in, + max_asset_amount_in, asset_out, - event: |evt| Self::deposit_event(Event::SwapExactAmountOut(evt)), + asset_amount_out, max_price, - pool_account_id: &pool_account_id, - pool_id, - pool: &pool, - who: who.clone(), - }; - swap_exact_amount::<_, _, T>(params)?; - - Ok(T::WeightInfo::swap_exact_amount_out_cpmm()) + ) } } } diff --git a/zrml/swaps/src/math.rs b/zrml/swaps/src/math.rs index c76ed5227..64b4d9606 100644 --- a/zrml/swaps/src/math.rs +++ b/zrml/swaps/src/math.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. @@ -127,7 +127,7 @@ pub fn calc_in_given_out( /// tokens of a single asset. /// /// See _Single-Asset Deposit/Withdrawal_ of Martinelli-Mushegian: Balancer Whitepaper v2019-09-19 -/// (https://balancer.fi/whitepaper.pdf) for details. +/// () for details. /// /// * `asset_balance_in` - The pool balance of the ingoing asset /// * `asset_weight_in` - The weight of the ingoing asset @@ -166,7 +166,7 @@ pub fn calc_pool_out_given_single_in( /// specified amount of pool tokens. /// /// See _Single-Asset Deposit/Withdrawal_ of Martinelli-Mushegian: Balancer Whitepaper v2019-09-19 -/// (https://balancer.fi/whitepaper.pdf) for details. +/// () for details. /// /// * `asset_balance_in` - The pool balance of the ingoing asset /// * `asset_weight_in` - The weight of the ingoing asset @@ -203,7 +203,7 @@ pub fn calc_single_in_given_pool_out( /// pool tokens. /// /// See _Single-Asset Deposit/Withdrawal_ of Martinelli-Mushegian: Balancer Whitepaper v2019-09-19 -/// (https://balancer.fi/whitepaper.pdf) for details. +/// () for details. /// /// * `asset_balance_in` - The pool balance of the ingoing asset /// * `asset_weight_in` - The weight of the ingoing asset @@ -243,7 +243,7 @@ pub fn calc_single_out_given_pool_in( /// Calculate the required amount of pool tokens to exit the pool with to receive the specified number of tokens of a single asset. /// /// See _Single-Asset Deposit/Withdrawal_ of Martinelli-Mushegian: Balancer Whitepaper v2019-09-19 -/// (https://balancer.fi/whitepaper.pdf) for details. +/// () for details. /// /// * `asset_balance_in` - The pool balance of the ingoing asset /// * `asset_weight_in` - The weight of the ingoing asset diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index cf07fb91e..9d6a592be 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -40,9 +40,9 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposit, GetNativeCurrencyId, MaxAssets, MaxInRatio, MaxLocks, - MaxOutRatio, MaxReserves, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, - MinimumPeriod, SwapsPalletId, BASE, + BlockHashCount, ExistentialDeposit, GetNativeCurrencyId, MaxAssets, MaxLocks, MaxReserves, + MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, MinimumPeriod, SwapsPalletId, + BASE, }, types::{ AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, @@ -99,8 +99,6 @@ impl crate::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ExitFee = ExitFeeMock; type MaxAssets = MaxAssets; - type MaxInRatio = MaxInRatio; - type MaxOutRatio = MaxOutRatio; type MaxSwapFee = MaxSwapFee; type MaxTotalWeight = MaxTotalWeight; type MaxWeight = MaxWeight; @@ -150,7 +148,7 @@ parameter_type_with_key! { match currency_id { &BASE_ASSET => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), - _ => 0, + _ => 10_000_000, } }; } diff --git a/zrml/swaps/src/tests.rs b/zrml/swaps/src/tests.rs index c12fbd0c1..b5e3e264e 100644 --- a/zrml/swaps/src/tests.rs +++ b/zrml/swaps/src/tests.rs @@ -29,14 +29,16 @@ use crate::{ events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, math::calc_out_given_in, mock::*, - types::PoolStatus, - AssetOf, BalanceOf, Config, Error, Event, + types::{Pool, PoolStatus}, + AssetOf, BalanceOf, Config, Error, Event, Pools, }; use frame_support::{assert_err, assert_noop, assert_ok}; use more_asserts::{assert_ge, assert_le}; use orml_traits::MultiCurrency; +use sp_arithmetic::traits::Zero; #[allow(unused_imports)] use test_case::test_case; +use zeitgeist_macros::create_b_tree_map; use zeitgeist_primitives::{ constants::BASE, traits::Swaps as _, @@ -153,7 +155,7 @@ fn destroy_pool_emits_correct_event() { frame_system::Pallet::::set_block_number(1); create_initial_pool(0, true); assert_ok!(Swaps::destroy_pool(DEFAULT_POOL_ID)); - System::assert_last_event(Event::PoolDestroyed(DEFAULT_POOL_ID).into()); + System::assert_last_event(Event::PoolDestroyed { pool_id: DEFAULT_POOL_ID }.into()); }); } @@ -256,7 +258,7 @@ fn assets_must_be_bounded() { Some(1), Some(1) ), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( Swaps::swap_exact_amount_in( @@ -268,7 +270,7 @@ fn assets_must_be_bounded() { Some(1), Some(1) ), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( @@ -281,7 +283,7 @@ fn assets_must_be_bounded() { 1, Some(1) ), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( Swaps::swap_exact_amount_out( @@ -293,7 +295,7 @@ fn assets_must_be_bounded() { 1, Some(1) ), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( @@ -304,16 +306,16 @@ fn assets_must_be_bounded() { 1, 1 ), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( Swaps::pool_join_with_exact_pool_amount(alice_signed(), DEFAULT_POOL_ID, ASSET_B, 1, 1), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( Swaps::pool_exit_with_exact_pool_amount(alice_signed(), DEFAULT_POOL_ID, ASSET_B, 1, 1), - Error::::AssetNotBound + Error::::AssetNotInPool ); assert_noop!( Swaps::pool_exit_with_exact_asset_amount( @@ -323,7 +325,7 @@ fn assets_must_be_bounded() { 1, 1 ), - Error::::AssetNotBound + Error::::AssetNotInPool ); }); } @@ -358,12 +360,12 @@ fn create_pool_generates_a_new_pool_with_correct_parameters_for_cpmm() { let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); System::assert_last_event( - Event::PoolCreate( - CommonPoolEventParams { pool_id: next_pool_before, who: BOB }, + Event::PoolCreate { + common: CommonPoolEventParams { pool_id: next_pool_before, who: BOB }, pool, - amount, + pool_amount: amount, pool_account, - ) + } .into(), ); }); @@ -1903,7 +1905,7 @@ fn close_pool_succeeds_and_emits_correct_event_if_pool_exists() { assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); 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()); + System::assert_last_event(Event::PoolClosed { pool_id: DEFAULT_POOL_ID }.into()); }); } @@ -1944,7 +1946,7 @@ fn open_pool_succeeds_and_emits_correct_event_if_pool_exists() { assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); 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()); + System::assert_last_event(Event::PoolActive { pool_id: DEFAULT_POOL_ID }.into()); }); } @@ -2566,6 +2568,43 @@ fn pool_exit_with_exact_asset_amount_fails_if_liquidity_drops_too_low() { }); } +#[test] +fn pool_exit_burns_small_amounts() { + ExtBuilder::default().build().execute_with(|| { + // Create a mock-up of a closed pool with ZTG balance dusted and winning outcome balance + // below ED. + let pool_id = 0; + let assets = vec![Asset::CategoricalOutcome(0, 3), Asset::Ztg].try_into().unwrap(); + let weights = create_b_tree_map!({ + Asset::CategoricalOutcome(0, 3) => 10_000_000_000, + Asset::Ztg => 100_000_000_000, + }) + .try_into() + .unwrap(); + let pool = Pool { + assets, + status: PoolStatus::Closed, + swap_fee: Zero::zero(), + total_weight: 200_000_000_000, + weights, + }; + Pools::::insert(pool_id, pool); + let pool_shares_amount = 14_624_689; + Currencies::deposit(Swaps::pool_shares_id(pool_id), &ALICE, pool_shares_amount).unwrap(); + let pool_account_id = Swaps::pool_account_id(&pool_id); + let balance = 445_496; + Currencies::deposit(Asset::CategoricalOutcome(0, 3), &pool_account_id, balance).unwrap(); + + assert_ok!(Swaps::pool_exit( + RuntimeOrigin::signed(ALICE), + pool_id, + pool_shares_amount, + vec![0; 2], + )); + assert_eq!(Currencies::free_balance(Asset::CategoricalOutcome(0, 3), &ALICE), 0); + }); +} + fn alice_signed() -> RuntimeOrigin { RuntimeOrigin::signed(ALICE) } diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index cf0979ebd..7e746b808 100644 --- a/zrml/swaps/src/utils.rs +++ b/zrml/swaps/src/utils.rs @@ -50,7 +50,7 @@ where T: Config, { Pallet::::ensure_pool_is_active(p.pool)?; - ensure!(p.pool.bound(&p.asset), Error::::AssetNotBound); + ensure!(p.pool.bound(&p.asset), Error::::AssetNotInPool); let pool_account = Pallet::::pool_account_id(&p.pool_id); let asset_balance = T::AssetManager::free_balance(p.asset, &pool_account); @@ -91,7 +91,7 @@ where let pool_account_id = Pallet::::pool_account_id(&p.pool_id); let total_issuance = T::AssetManager::total_issuance(pool_shares_id); - ensure!(p.pool.bound(&p.asset), Error::::AssetNotBound); + ensure!(p.pool.bound(&p.asset), Error::::AssetNotInPool); let asset_balance = T::AssetManager::free_balance(p.asset, p.pool_account_id); let asset_amount = (p.asset_amount)(asset_balance, total_issuance)?; @@ -131,6 +131,10 @@ where for (asset, amount_bound) in p.pool.assets.iter().cloned().zip(p.asset_bounds.iter().cloned()) { let balance = T::AssetManager::free_balance(asset, p.pool_account_id); + // Dusting may result in zero balances in the pool; just ignore these. + if balance.is_zero() { + continue; + } let amount = ratio.bmul(balance)?; let fee = (p.fee)(amount)?; let amount_minus_fee = amount.checked_sub_res(&fee)?; @@ -164,8 +168,8 @@ where 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); - 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::::AssetNotInPool); + ensure!(p.pool.bound(&p.asset_out), Error::::AssetNotInPool); let spot_price_before = Pallet::::get_spot_price(&p.pool_id, &p.asset_in, &p.asset_out, true)?; diff --git a/zrml/swaps/src/weights.rs b/zrml/swaps/src/weights.rs index 32deb55cc..b37d4beb2 100644 --- a/zrml/swaps/src/weights.rs +++ b/zrml/swaps/src/weights.rs @@ -55,8 +55,8 @@ pub trait WeightInfoZeitgeist { fn pool_join(a: u32) -> Weight; fn pool_join_with_exact_asset_amount() -> Weight; fn pool_join_with_exact_pool_amount() -> Weight; - fn swap_exact_amount_in_cpmm() -> Weight; - fn swap_exact_amount_out_cpmm() -> Weight; + fn swap_exact_amount_in() -> Weight; + fn swap_exact_amount_out() -> Weight; fn open_pool(a: u32) -> Weight; fn close_pool(a: u32) -> Weight; fn destroy_pool(a: u32) -> Weight; @@ -179,7 +179,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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) - fn swap_exact_amount_in_cpmm() -> Weight { + fn swap_exact_amount_in() -> Weight { // Proof Size summary in bytes: // Measured: `5144` // Estimated: `19053` @@ -194,7 +194,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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) - fn swap_exact_amount_out_cpmm() -> Weight { + fn swap_exact_amount_out() -> Weight { // Proof Size summary in bytes: // Measured: `5144` // Estimated: `19053` From f75e83ba481ac56b1f811587ed07745d35b91706 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 08:02:06 +0100 Subject: [PATCH 047/104] Integrate asset destruction into prediction markets --- zrml/prediction-markets/Cargo.toml | 5 +- zrml/prediction-markets/src/lib.rs | 51 ++++++++++++++++--- zrml/prediction-markets/src/mock.rs | 13 +++-- .../src/tests/on_resolution.rs | 5 +- 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 3c6ef2a18..8828bd559 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -3,6 +3,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } orml-traits = { workspace = true } +pallet-assets = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } serde = { workspace = true, optional = true } @@ -22,7 +23,6 @@ env_logger = { workspace = true, optional = true } orml-asset-registry = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } orml-tokens = { workspace = true, optional = true } -pallet-assets = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } pallet-randomness-collective-flip = { workspace = true, optional = true } pallet-timestamp = { workspace = true, optional = true } @@ -68,7 +68,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "orml-asset-registry?/runtime-benchmarks", - "pallet-assets?/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "zeitgeist-primitives/mock", ] @@ -78,6 +78,7 @@ std = [ "frame-system/std", "orml-asset-registry?/std", "orml-traits/std", + "pallet-assets/std", "parity-scale-codec/std", 'scale-info/std', "serde?/std", diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index a573a78fa..1a9bf1977 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -35,7 +35,7 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::weights::*; - use alloc::{format, vec, vec::Vec}; + use alloc::{collections::BTreeMap, format, vec, vec::Vec}; use core::{cmp, marker::PhantomData}; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays, Weight}, @@ -44,12 +44,13 @@ mod pallet { require_transactional, storage::{with_transaction, TransactionOutcome}, traits::{ - tokens::BalanceStatus, Currency, EnsureOrigin, Get, Hooks, Imbalance, IsType, - NamedReservableCurrency, OnUnbalanced, StorageVersion, + fungibles::Create, tokens::BalanceStatus, Currency, EnsureOrigin, Get, Hooks, + Imbalance, IsType, NamedReservableCurrency, OnUnbalanced, StorageVersion, }, transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use pallet_assets::ManagedDestroy; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "parachain")] @@ -823,9 +824,16 @@ mod pallet { &sender, actual_payout, )?; + // The if-check prevents scalar markets to emit events even if sender only owns one // of the outcome tokens. if balance != BalanceOf::::zero() { + if T::AssetManager::total_issuance(currency_id).is_zero() { + // Ensure managed_destroy does not error during lazy migration because + // it tried to delete an old outcome asset from orml-tokens + let _ = T::AssetDestroyer::managed_destroy(currency_id, None); + } + Self::deposit_event(Event::TokensRedeemed( market_id, currency_id, @@ -1518,7 +1526,13 @@ mod pallet { /// The origin that is allowed to approve / reject pending advised markets. type ApproveOrigin: EnsureOrigin; - /// Shares of outcome assets and native currency + /// The module handling the creation of market assets. + type AssetCreator: Create, Balance = BalanceOf>; + + /// The module handling the destruction of market assets. + type AssetDestroyer: ManagedDestroy, Balance = BalanceOf>; + + /// The module managing collateral and market assets. type AssetManager: MultiCurrency, CurrencyId = AssetOf> + NamedMultiReservableCurrency< Self::AccountId, @@ -1911,7 +1925,6 @@ mod pallet { #[pallet::hooks] impl Hooks for Pallet { - // TODO(#792): Remove outcome assets for accounts! Delete "resolved" assets of `orml_tokens` with storage migration. fn on_initialize(now: T::BlockNumber) -> Weight { let mut total_weight: Weight = Weight::zero(); @@ -2135,6 +2148,13 @@ mod pallet { let market_id = >::push_market(market.clone())?; let market_account = Self::market_account(market_id); + for outcome in Self::outcome_assets(market_id, &market) { + let admin = market_account.clone(); + let is_sufficient = true; + let min_balance = 1u8; + T::AssetCreator::create(outcome, admin, is_sufficient, min_balance.into())?; + } + let ids_amount: u32 = Self::insert_auto_close(&market_id)?; Self::deposit_event(Event::MarketCreated(market_id, market_account, market)); @@ -2761,14 +2781,31 @@ mod pallet { // Following call should return weight consumed by it. T::LiquidityMining::distribute_market_incentives(market_id)?; - // NOTE: Currently we don't clean up outcome assets. - // TODO(#792): Remove outcome assets for accounts! Delete "resolved" assets of `orml_tokens` with storage migration. >::mutate_market(market_id, |m| { m.status = MarketStatus::Resolved; m.resolved_outcome = Some(resolved_outcome.clone()); Ok(()) })?; + match resolved_outcome { + OutcomeReport::Categorical(winning_idx) => { + // Destroy losing categorical outcome assets. + let assets_to_destroy = BTreeMap::, Option>::from_iter( + Self::outcome_assets(*market_id, market) + .into_iter() + .filter(|outcome| { + *outcome + != AssetOf::::CategoricalOutcome(*market_id, winning_idx) + }) + .zip(vec![None]), + ); + // Ensure managed_destroy_multi does not error during lazy migration because + // it tried to delete an old outcome asset from orml-tokens + let _ = T::AssetDestroyer::managed_destroy_multi(assets_to_destroy); + } + OutcomeReport::Scalar(_) => (), + } + Self::deposit_event(Event::MarketResolved( *market_id, MarketStatus::Resolved, diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 2c70692e1..3a48dfd4b 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -24,8 +24,12 @@ use crate as prediction_markets; use frame_support::{ - construct_runtime, ord_parameter_types, parameter_types, - traits::{AsEnsureOriginWithArg, Everything, NeverEnsureOrigin, OnFinalize, OnInitialize}, + construct_runtime, ord_parameter_types, + pallet_prelude::Weight, + parameter_types, + traits::{ + AsEnsureOriginWithArg, Everything, NeverEnsureOrigin, OnFinalize, OnIdle, OnInitialize, + }, }; use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; #[cfg(feature = "parachain")] @@ -192,6 +196,9 @@ impl crate::Config for Runtime { type AdvisoryBond = AdvisoryBond; type AdvisoryBondSlashPercentage = AdvisoryBondSlashPercentage; type ApproveOrigin = EnsureSignedBy; + type AssetCreator = AssetRouter; + type AssetDestroyer = AssetRouter; + type AssetManager = AssetManager; #[cfg(feature = "parachain")] type AssetRegistry = MockRegistry; type Authorized = Authorized; @@ -229,7 +236,6 @@ impl crate::Config for Runtime { type RejectOrigin = EnsureSignedBy; type RequestEditOrigin = EnsureSignedBy; type ResolveOrigin = EnsureSignedBy; - type AssetManager = AssetManager; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; type ValidityBond = ValidityBond; @@ -628,6 +634,7 @@ pub fn run_to_block(n: BlockNumber) { PredictionMarkets::on_initialize(System::block_number()); Court::on_initialize(System::block_number()); Balances::on_initialize(System::block_number()); + AssetRouter::on_idle(System::block_number(), Weight::MAX); } } diff --git a/zrml/prediction-markets/src/tests/on_resolution.rs b/zrml/prediction-markets/src/tests/on_resolution.rs index 77308d65a..2f5bebf8b 100644 --- a/zrml/prediction-markets/src/tests/on_resolution.rs +++ b/zrml/prediction-markets/src/tests/on_resolution.rs @@ -67,12 +67,11 @@ fn it_correctly_resolves_a_market_that_was_reported_on() { let share_b_bal = AssetManager::free_balance(share_b, &CHARLIE); assert_eq!(share_b_bal, CENT); - // TODO(#792): Remove other assets. let share_a = Asset::CategoricalOutcome(0, 0); let share_a_total = AssetManager::total_issuance(share_a); - assert_eq!(share_a_total, CENT); + assert_eq!(share_a_total, 0); let share_a_bal = AssetManager::free_balance(share_a, &CHARLIE); - assert_eq!(share_a_bal, CENT); + assert_eq!(share_a_bal, 0); let share_c = Asset::CategoricalOutcome(0, 2); let share_c_total = AssetManager::total_issuance(share_c); From 92e20f9ce755170291a2dd214cbf5f401811b47c Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 08:40:30 +0100 Subject: [PATCH 048/104] Add BaseAssetClass --- primitives/src/assets.rs | 2 + primitives/src/assets/all_assets.rs | 18 +++++-- primitives/src/assets/campaign_assets.rs | 2 +- primitives/src/assets/custom_assets.rs | 2 +- primitives/src/assets/subsets.rs | 21 +++++++++ primitives/src/assets/subsets/base_assets.rs | 48 +++++++++++++++++++ primitives/src/assets/tests/conversion.rs | 49 ++++++++++++++++++-- primitives/src/assets/tests/scale_codec.rs | 26 +++++++++++ primitives/src/types.rs | 3 ++ 9 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 primitives/src/assets/subsets.rs create mode 100644 primitives/src/assets/subsets/base_assets.rs diff --git a/primitives/src/assets.rs b/primitives/src/assets.rs index c187096bd..7f56a8775 100644 --- a/primitives/src/assets.rs +++ b/primitives/src/assets.rs @@ -30,12 +30,14 @@ pub use campaign_assets::CampaignAssetClass; pub use currencies::CurrencyClass; pub use custom_assets::CustomAssetClass; pub use market_assets::MarketAssetClass; +pub use subsets::BaseAssetClass; mod all_assets; mod campaign_assets; mod currencies; mod custom_assets; mod market_assets; +mod subsets; #[cfg(test)] mod tests; diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index 120f02643..cb000d759 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -74,10 +74,10 @@ pub enum Asset { ParimutuelShare(MI, CategoryIndex), #[codec(index = 7)] - CampaignAssetClass(#[codec(compact)] CampaignAssetId), + CampaignAsset(#[codec(compact)] CampaignAssetId), #[codec(index = 8)] - CustomAssetClass(#[codec(compact)] CustomAssetId), + CustomAsset(#[codec(compact)] CustomAssetId), } impl PoolSharesId for Asset { @@ -112,13 +112,13 @@ impl From> for Asset { impl From for Asset { fn from(value: CampaignAssetClass) -> Self { - Self::CampaignAssetClass(value.0) + Self::CampaignAsset(value.0) } } impl From for Asset { fn from(value: CustomAssetClass) -> Self { - Self::CustomAssetClass(value.0) + Self::CustomAsset(value.0) } } @@ -139,3 +139,13 @@ impl From> for Asset { } } } + +impl From for Asset { + fn from(value: BaseAssetClass) -> Self { + match value { + BaseAssetClass::Ztg => Self::Ztg, + BaseAssetClass::ForeignAsset(id) => Self::ForeignAsset(id), + BaseAssetClass::CampaignAsset(id) => Self::CampaignAsset(id), + } + } +} \ No newline at end of file diff --git a/primitives/src/assets/campaign_assets.rs b/primitives/src/assets/campaign_assets.rs index 6f5b8de4b..dcf912a77 100644 --- a/primitives/src/assets/campaign_assets.rs +++ b/primitives/src/assets/campaign_assets.rs @@ -44,7 +44,7 @@ impl TryFrom> for CampaignAssetClass { fn try_from(value: Asset) -> Result { match value { - Asset::::CampaignAssetClass(id) => Ok(Self(id)), + Asset::::CampaignAsset(id) => Ok(Self(id)), _ => Err(()), } } diff --git a/primitives/src/assets/custom_assets.rs b/primitives/src/assets/custom_assets.rs index 0508a440e..b5f8339ec 100644 --- a/primitives/src/assets/custom_assets.rs +++ b/primitives/src/assets/custom_assets.rs @@ -42,7 +42,7 @@ impl TryFrom> for CustomAssetClass { fn try_from(value: Asset) -> Result { match value { - Asset::::CustomAssetClass(id) => Ok(Self(id)), + Asset::::CustomAsset(id) => Ok(Self(id)), _ => Err(()), } } diff --git a/primitives/src/assets/subsets.rs b/primitives/src/assets/subsets.rs new file mode 100644 index 000000000..d786fc71a --- /dev/null +++ b/primitives/src/assets/subsets.rs @@ -0,0 +1,21 @@ +// Copyright 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 . + +pub use base_assets::BaseAssetClass; +use super::*; + +mod base_assets; \ No newline at end of file diff --git a/primitives/src/assets/subsets/base_assets.rs b/primitives/src/assets/subsets/base_assets.rs new file mode 100644 index 000000000..181bb2b8a --- /dev/null +++ b/primitives/src/assets/subsets/base_assets.rs @@ -0,0 +1,48 @@ +// Copyright 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 . + +use super::*; + +/// The `BaseAssets` enum represents all assets that can be used as collateral in +/// prediction markets. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, Debug, Decode, Default, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo)] +pub enum BaseAssetClass { + #[codec(index = 4)] + #[default] + Ztg, + + #[codec(index = 5)] + ForeignAsset(u32), + + #[codec(index = 7)] + CampaignAsset(#[codec(compact)] CampaignAssetId), +} + +impl TryFrom> for BaseAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::Ztg => Ok(Self::Ztg), + Asset::::ForeignAsset(id) => Ok(Self::ForeignAsset(id)), + Asset::::CampaignAsset(id) => Ok(Self::CampaignAsset(id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/tests/conversion.rs b/primitives/src/assets/tests/conversion.rs index eaa8c0006..e95495a23 100644 --- a/primitives/src/assets/tests/conversion.rs +++ b/primitives/src/assets/tests/conversion.rs @@ -141,7 +141,7 @@ fn from_currencies_to_all_assets(old_asset: CurrencyClass, new_asset: // Assets <> CampaignAssetClass #[test] fn from_all_assets_to_campaign_assets() { - let old_asset = Asset::::CampaignAssetClass(7); + let old_asset = Asset::::CampaignAsset(7); let new_asset = CampaignAssetClass(7); let new_asset_converted: CampaignAssetClass = old_asset.try_into().unwrap(); @@ -151,7 +151,7 @@ fn from_all_assets_to_campaign_assets() { #[test] fn from_campaign_assets_to_all_assets() { let old_asset = CampaignAssetClass(7); - let new_asset = Asset::::CampaignAssetClass(7); + let new_asset = Asset::::CampaignAsset(7); let new_asset_converted: Asset = old_asset.into(); assert_eq!(new_asset, new_asset_converted); } @@ -159,7 +159,7 @@ fn from_campaign_assets_to_all_assets() { // Assets <> CustomAssetClass #[test] fn from_all_assets_to_custom_assets() { - let old_asset = Asset::::CustomAssetClass(7); + let old_asset = Asset::::CustomAsset(7); let new_asset = CustomAssetClass(7); let new_asset_converted: CustomAssetClass = old_asset.try_into().unwrap(); @@ -169,7 +169,48 @@ fn from_all_assets_to_custom_assets() { #[test] fn from_custom_assets_to_all_assets() { let old_asset = CampaignAssetClass(7); - let new_asset = Asset::::CampaignAssetClass(7); + let new_asset = Asset::::CampaignAsset(7); + let new_asset_converted: Asset = old_asset.into(); + assert_eq!(new_asset, new_asset_converted); +} + +// Assets <> BaseAssetClass +#[test_case( + Asset::::CampaignAsset(7), + BaseAssetClass::CampaignAsset(7); + "campaign_asset" +)] +#[test_case( + Asset::::ForeignAsset(7), + BaseAssetClass::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + Asset::::Ztg, + BaseAssetClass::Ztg; + "ztg" +)] +fn from_all_assets_to_base_assets(old_asset: Asset, new_asset: BaseAssetClass) { + let new_asset_converted: BaseAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test_case( + BaseAssetClass::CampaignAsset(7), + Asset::::CampaignAsset(7); + "campaign_asset" +)] +#[test_case( + BaseAssetClass::ForeignAsset(7), + Asset::::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + BaseAssetClass::Ztg, + Asset::::Ztg; + "ztg" +)] +fn from_base_assets_to_all_assets(old_asset: BaseAssetClass, new_asset: Asset) { let new_asset_converted: Asset = old_asset.into(); assert_eq!(new_asset, new_asset_converted); } diff --git a/primitives/src/assets/tests/scale_codec.rs b/primitives/src/assets/tests/scale_codec.rs index 49017e375..cedb74f0b 100644 --- a/primitives/src/assets/tests/scale_codec.rs +++ b/primitives/src/assets/tests/scale_codec.rs @@ -20,6 +20,32 @@ use super::*; use test_case::test_case; +// Assets <> BaseAssetClass +#[test_case( + Asset::::CampaignAsset(7), + BaseAssetClass::CampaignAsset(7); + "campaign_asset" +)] +#[test_case( + Asset::::ForeignAsset(7), + BaseAssetClass::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + Asset::::Ztg, + BaseAssetClass::Ztg; + "ztg" +)] +fn index_matching_works_for_base_assets( + old_asset: Asset, + new_asset: BaseAssetClass, +) { + let old_asset_encoded: Vec = old_asset.encode(); + let new_asset_decoded = + ::decode(&mut old_asset_encoded.as_slice()).unwrap(); + assert_eq!(new_asset_decoded, new_asset); +} + // Assets <> MarketAssetClass #[test_case( Asset::::CategoricalOutcome(7, 8), diff --git a/primitives/src/types.rs b/primitives/src/types.rs index e759f3b65..2e3856ae3 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -83,6 +83,9 @@ pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; +/// Asset type representing base assets used by prediction markets +pub type BaseAsset = BaseAssetClass; + /// Asset type representing campaign assets pub type CampaignAsset = CampaignAssetClass; From d1b53a109258f41b53f309fb2fecfb3de06e67e3 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 19:14:38 +0100 Subject: [PATCH 049/104] Update prediction-markets & market-commons --- primitives/src/traits/dispute_api.rs | 6 +- .../src/traits/market_commons_pallet_api.rs | 6 +- zrml/authorized/src/lib.rs | 8 +- zrml/court/src/lib.rs | 4 +- zrml/market-commons/src/lib.rs | 13 +- .../fuzz/pm_full_workflow.rs | 4 +- zrml/prediction-markets/src/benchmarks.rs | 16 +-- zrml/prediction-markets/src/lib.rs | 59 ++++----- zrml/prediction-markets/src/mock.rs | 24 +++- .../src/tests/admin_move_market_to_closed.rs | 10 +- .../tests/admin_move_market_to_resolved.rs | 11 +- .../src/tests/approve_market.rs | 13 +- .../src/tests/buy_complete_set.rs | 28 +++-- .../src/tests/close_trusted_market.rs | 6 +- .../src/tests/create_market.rs | 46 +++---- .../tests/create_market_and_deploy_pool.rs | 2 +- zrml/prediction-markets/src/tests/dispute.rs | 14 +-- .../src/tests/dispute_early_close.rs | 16 +-- .../src/tests/edit_market.rs | 18 +-- .../src/tests/integration.rs | 66 ++++++----- .../src/tests/manually_close_market.rs | 8 +- zrml/prediction-markets/src/tests/mod.rs | 12 +- .../src/tests/on_initialize.rs | 2 +- .../src/tests/on_market_close.rs | 30 +++-- .../src/tests/on_resolution.rs | 112 ++++++++++++------ .../src/tests/redeem_shares.rs | 50 +++++--- .../src/tests/reject_early_close.rs | 10 +- .../src/tests/reject_market.rs | 25 ++-- zrml/prediction-markets/src/tests/report.rs | 26 ++-- .../src/tests/request_edit.rs | 6 +- .../src/tests/schedule_early_close.rs | 12 +- .../src/tests/sell_complete_set.rs | 31 +++-- .../src/tests/start_global_dispute.rs | 2 +- zrml/simple-disputes/src/lib.rs | 6 +- 34 files changed, 404 insertions(+), 298 deletions(-) diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index 6d23c8c98..6ba9db4fc 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.rs @@ -20,7 +20,7 @@ extern crate alloc; use crate::{ outcome_report::OutcomeReport, - types::{Asset, GlobalDisputeItem, Market, ResultWithWeightInfo}, + types::{BaseAsset, GlobalDisputeItem, Market, ResultWithWeightInfo}, }; use alloc::vec::Vec; use frame_support::pallet_prelude::Weight; @@ -34,7 +34,7 @@ type MarketOfDisputeApi = Market< ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + BaseAsset, >; type GlobalDisputeItemOfDisputeApi = @@ -150,7 +150,7 @@ type MarketOfDisputeResolutionApi = Market< ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + BaseAsset, >; pub trait DisputeResolutionApi { diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index 1c8f7ae93..12c1ad7ef 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -18,7 +18,7 @@ #![allow(clippy::type_complexity)] -use crate::types::{Asset, Market, PoolId}; +use crate::types::{BaseAsset, Market, PoolId}; use frame_support::{ dispatch::{fmt::Debug, DispatchError, DispatchResult}, pallet_prelude::{MaybeSerializeDeserialize, Member}, @@ -30,12 +30,12 @@ use sp_runtime::traits::{AtLeast32Bit, AtLeast32BitUnsigned}; // Abstraction of the market type, which is not a part of `MarketCommonsPalletApi` because Rust // doesn't support type aliases in traits. -type MarketOf = Market< +pub type MarketOf = Market< ::AccountId, ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + BaseAsset, >; /// Abstraction over storage operations for markets diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index e491fc75e..309c96513 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -49,7 +49,7 @@ mod pallet { use zeitgeist_primitives::{ traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - Asset, AuthorityReport, GlobalDisputeItem, Market, MarketDisputeMechanism, + BaseAsset, AuthorityReport, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, ResultWithWeightInfo, }, }; @@ -72,7 +72,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - Asset>, + BaseAsset, >; #[pallet::call] @@ -371,12 +371,12 @@ where use frame_support::traits::Get; use sp_runtime::{traits::AccountIdConversion, Perbill}; use zeitgeist_primitives::types::{ - Asset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + BaseAsset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, ScoringRule, }; zeitgeist_primitives::types::Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), creator: T::PalletId::get().into_account_truncating(), diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 10cf08bcb..060970b3c 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -60,7 +60,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - Asset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, + BaseAsset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, ResultWithWeightInfo, }, }; @@ -219,7 +219,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - Asset>, + BaseAsset, >; pub(crate) type HashOf = ::Hash; pub(crate) type AccountIdLookupOf = diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index 8abd983b6..209d0eab4 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -50,18 +50,17 @@ mod pallet { }; use zeitgeist_primitives::{ math::checked_ops_res::CheckedAddRes, - types::{Asset, Market, PoolId}, + types::{BaseAsset, Market, PoolId}, }; /// The current storage version. 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 AccountIdOf = ::AccountId; + pub type BalanceOf = ::Balance; + pub type BlockNumberOf = ::BlockNumber; + pub type MarketOf = + Market, BalanceOf, BlockNumberOf, MomentOf, BaseAsset>; pub type MarketIdOf = ::MarketId; pub type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; diff --git a/zrml/prediction-markets/fuzz/pm_full_workflow.rs b/zrml/prediction-markets/fuzz/pm_full_workflow.rs index a0beb9583..29a9a8fe8 100644 --- a/zrml/prediction-markets/fuzz/pm_full_workflow.rs +++ b/zrml/prediction-markets/fuzz/pm_full_workflow.rs @@ -26,7 +26,7 @@ use sp_arithmetic::Perbill; use zeitgeist_primitives::{ constants::mock::MaxCreatorFee, types::{ - Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketType, + BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; @@ -48,7 +48,7 @@ fuzz_target!(|data: Data| { let fee = Perbill::from_parts(bounded_parts.try_into().unwrap()); let _ = PredictionMarkets::create_market( RuntimeOrigin::signed(data.create_scalar_market_origin.into()), - Asset::Ztg, + BaseAsset::Ztg, fee, data.create_scalar_market_oracle.into(), MarketPeriod::Block(data.create_scalar_market_period), diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index f5cb54eb8..8cac42607 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -46,7 +46,7 @@ use zeitgeist_primitives::{ math::fixed::{BaseProvider, ZeitgeistBase}, traits::DisputeApi, types::{ - Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, + Asset, BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; @@ -97,7 +97,7 @@ fn create_market_common( let (caller, oracle, deadlines, metadata) = create_market_common_parameters::(dispute_mechanism.is_some())?; Call::::create_market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creator_fee, oracle, period, @@ -491,7 +491,7 @@ benchmarks! { } }: _( RawOrigin::Signed(caller), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), oracle, period, @@ -515,7 +515,7 @@ benchmarks! { let (caller, oracle, deadlines, metadata) = create_market_common_parameters::(true)?; Call::::create_market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creator_fee: Perbill::zero(), oracle: oracle.clone(), period: period.clone(), @@ -547,7 +547,7 @@ benchmarks! { }; }: _( RawOrigin::Signed(caller), - Asset::Ztg, + BaseAsset::Ztg, market_id, oracle, period, @@ -856,7 +856,7 @@ benchmarks! { let end: MomentOf = 1_000_000u64.saturated_into(); let (caller, oracle, _, metadata) = create_market_common_parameters::(false)?; Call::::create_market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creator_fee: Perbill::zero(), oracle: caller.clone(), period: MarketPeriod::Timestamp(start..end), @@ -1288,7 +1288,7 @@ benchmarks! { let m in 0..63; // Number of markets closing on the same block. let n in 2..T::MaxCategories::get() as u32; // Number of assets in the market. - let base_asset = Asset::Ztg; + let base_asset = BaseAsset::Ztg; let range_start = (5 * MILLISECS_PER_BLOCK) as u64; let range_end = (100 * MILLISECS_PER_BLOCK) as u64; let period = MarketPeriod::Timestamp(range_start..range_end); @@ -1298,7 +1298,7 @@ benchmarks! { let amount = (10u128 * BASE).saturated_into(); ::AssetManager::deposit( - base_asset, + base_asset.into(), &caller, amount, )?; diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 1a9bf1977..e15880eb6 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -44,8 +44,10 @@ mod pallet { require_transactional, storage::{with_transaction, TransactionOutcome}, traits::{ - fungibles::Create, tokens::BalanceStatus, Currency, EnsureOrigin, Get, Hooks, - Imbalance, IsType, NamedReservableCurrency, OnUnbalanced, StorageVersion, + fungibles::{Create, Inspect}, + tokens::BalanceStatus, + Currency, EnsureOrigin, Get, Hooks, Imbalance, IsType, NamedReservableCurrency, + OnUnbalanced, StorageVersion, }, transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, }; @@ -55,7 +57,7 @@ mod pallet { #[cfg(feature = "parachain")] use { - orml_traits::asset_registry::Inspect, + orml_traits::asset_registry::Inspect as RegistryInspect, zeitgeist_primitives::types::{CurrencyClass, CustomMetadata}, }; @@ -72,10 +74,10 @@ mod pallet { DisputeResolutionApi, }, types::{ - Asset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, Market, - MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, MultiHash, OutcomeReport, Report, ResultWithWeightInfo, ScalarPosition, - ScoringRule, + Asset, BaseAsset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, + Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, MultiHash, OutcomeReport, Report, ResultWithWeightInfo, + ScalarPosition, ScoringRule, }, }; use zrml_global_disputes::{types::InitialItem, GlobalDisputesPalletApi}; @@ -90,7 +92,6 @@ mod pallet { /// 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 AccountIdOf = ::AccountId; pub(crate) type AssetOf = Asset>; pub(crate) type BalanceOf = ::Balance; @@ -104,7 +105,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - AssetOf, + BaseAsset, >; pub(crate) type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; @@ -597,7 +598,7 @@ mod pallet { #[transactional] pub fn create_market( origin: OriginFor, - base_asset: AssetOf, + base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -649,7 +650,7 @@ mod pallet { #[transactional] pub fn edit_market( origin: OriginFor, - base_asset: AssetOf, + base_asset: BaseAsset, market_id: MarketIdOf, oracle: T::AccountId, period: MarketPeriod>, @@ -735,7 +736,7 @@ mod pallet { // Ensure the market account has enough to pay out - if this is // ever not true then we have an accounting problem. ensure!( - T::AssetManager::free_balance(market.base_asset, &market_account) + T::AssetManager::free_balance(market.base_asset.into(), &market_account) >= winning_balance, Error::::InsufficientFundsInMarketAccount, ); @@ -789,7 +790,7 @@ mod pallet { // Ensure the market account has enough to pay out - if this is // ever not true then we have an accounting problem. ensure!( - T::AssetManager::free_balance(market.base_asset, &market_account) + T::AssetManager::free_balance(market.base_asset.into(), &market_account) >= long_payout.saturating_add(short_payout), Error::::InsufficientFundsInMarketAccount, ); @@ -815,11 +816,11 @@ mod pallet { // Pay out the winner. let remaining_bal = - T::AssetManager::free_balance(market.base_asset, &market_account); + T::AssetManager::free_balance(market.base_asset.into(), &market_account); let actual_payout = payout.min(remaining_bal); T::AssetManager::transfer( - market.base_asset, + market.base_asset.into(), &market_account, &sender, actual_payout, @@ -1069,7 +1070,7 @@ mod pallet { #[pallet::call_index(17)] pub fn create_market_and_deploy_pool( origin: OriginFor, - base_asset: AssetOf, + base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -1542,7 +1543,7 @@ mod pallet { >; #[cfg(feature = "parachain")] - type AssetRegistry: Inspect< + type AssetRegistry: RegistryInspect< AssetId = CurrencyClass>, Balance = BalanceOf, CustomMetadata = CustomMetadata, @@ -2097,7 +2098,7 @@ mod pallet { #[require_transactional] fn do_create_market( who: T::AccountId, - base_asset: AssetOf, + base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -2294,7 +2295,7 @@ mod pallet { let market_account = Self::market_account(market_id); ensure!( - T::AssetManager::free_balance(market.base_asset, &market_account) >= amount, + T::AssetManager::free_balance(market.base_asset.into(), &market_account) >= amount, "Market account does not have sufficient reserves.", ); @@ -2322,7 +2323,7 @@ mod pallet { ); } - T::AssetManager::transfer(market.base_asset, &market_account, &who, amount)?; + T::AssetManager::transfer(market.base_asset.into(), &market_account, &who, amount)?; Self::deposit_event(Event::SoldCompleteSet(market_id, amount, who)); @@ -2338,14 +2339,14 @@ mod pallet { ensure!(amount != BalanceOf::::zero(), Error::::ZeroAmount); let market = >::market(&market_id)?; ensure!( - T::AssetManager::free_balance(market.base_asset, &who) >= amount, + T::AssetManager::free_balance(market.base_asset.into(), &who) >= amount, Error::::NotEnoughBalance ); ensure!(market.is_redeemable(), Error::::InvalidScoringRule); Self::ensure_market_is_active(&market)?; let market_account = Self::market_account(market_id); - T::AssetManager::transfer(market.base_asset, &who, &market_account, amount)?; + T::AssetManager::transfer(market.base_asset.into(), &who, &market_account, amount)?; let assets = Self::outcome_assets(market_id, &market); for asset in assets.iter() { @@ -2914,7 +2915,7 @@ mod pallet { } fn construct_market( - base_asset: AssetOf, + base_asset: BaseAsset, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, @@ -2930,12 +2931,15 @@ mod pallet { bonds: MarketBondsOf, ) -> Result, DispatchError> { let valid_base_asset = match base_asset { - Asset::Ztg => true, + BaseAsset::CampaignAsset(idx) => { + T::AssetCreator::asset_exists(BaseAsset::CampaignAsset(idx).into()) + } + BaseAsset::Ztg => true, #[cfg(feature = "parachain")] - Asset::ForeignAsset(fa) => { + BaseAsset::ForeignAsset(id) => { if let Some(metadata) = T::AssetRegistry::metadata(&CurrencyClass::>::ForeignAsset( - fa, + id, )) { metadata.additional.allow_as_base_asset @@ -2943,7 +2947,8 @@ mod pallet { return Err(Error::::UnregisteredForeignAsset.into()); } } - _ => false, + #[cfg(not(feature = "parachain"))] + BaseAsset::ForeignAsset(_) => false, }; ensure!(creator_fee <= T::MaxCreatorFee::get(), Error::::FeeTooHigh); diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 3a48dfd4b..6421b9255 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -28,7 +28,8 @@ use frame_support::{ pallet_prelude::Weight, parameter_types, traits::{ - AsEnsureOriginWithArg, Everything, NeverEnsureOrigin, OnFinalize, OnIdle, OnInitialize, + AsEnsureOriginWithArg, Everything, GenesisBuild, NeverEnsureOrigin, OnFinalize, OnIdle, + OnInitialize, }, }; use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; @@ -64,8 +65,8 @@ use zeitgeist_primitives::{ traits::DeployPoolApi, types::{ AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CampaignAsset, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, Hash, Index, - MarketAsset, MarketId, Moment, UncheckedExtrinsicTest, + CampaignAsset, CampaignAssetClass, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, + Hash, Index, MarketAsset, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -569,11 +570,22 @@ impl ExtBuilder { // see the logs in tests when using `RUST_LOG=debug cargo test -- --nocapture` let _ = env_logger::builder().is_test(true).try_init(); + pallet_assets::GenesisConfig:: { + assets: vec![(CampaignAssetClass(100), ALICE, true, 1)], + metadata: vec![], + accounts: self + .balances + .iter() + .map(|(account, balance)| (CampaignAssetClass(100), *account, *balance)) + .collect(), + } + .assimilate_storage(&mut t) + .unwrap(); + pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); - #[cfg(feature = "parachain")] - use frame_support::traits::GenesisBuild; + #[cfg(feature = "parachain")] orml_tokens::GenesisConfig:: { balances: (0..69) @@ -582,11 +594,13 @@ impl ExtBuilder { } .assimilate_storage(&mut t) .unwrap(); + #[cfg(feature = "parachain")] let custom_metadata = zeitgeist_primitives::types::CustomMetadata { allow_as_base_asset: true, ..Default::default() }; + #[cfg(feature = "parachain")] orml_asset_registry_mock::GenesisConfig { metadata: vec![ diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs index b28662cfb..5cc92b88c 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_closed.rs @@ -28,7 +28,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_blocknumb let now = frame_system::Pallet::::block_number(); let end = 42; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, now..end, ScoringRule::Lmsr, @@ -59,7 +59,7 @@ fn admin_move_market_to_closed_successfully_closes_market_and_sets_end_timestamp let end = start + 42u64 * (MILLISECS_PER_BLOCK as u64); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(start..end), @@ -111,7 +111,7 @@ fn admin_move_market_to_closed_fails_if_market_does_not_exist() { 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, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, @@ -134,7 +134,7 @@ fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Block(22..66), @@ -147,7 +147,7 @@ fn admin_move_market_to_closed_correctly_clears_auto_close_blocks() { )); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Block(33..66), diff --git a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs index d4a8a8cca..1d1826093 100644 --- a/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs +++ b/zrml/prediction-markets/src/tests/admin_move_market_to_resolved.rs @@ -24,7 +24,7 @@ use zeitgeist_primitives::types::OutcomeReport; #[test] fn admin_move_market_to_resolved_resolves_reported_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let end = 33; simple_create_categorical_market( base_asset, @@ -83,11 +83,14 @@ fn admin_move_market_to_resolved_resolves_reported_market() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -101,7 +104,7 @@ fn admin_move_market_to_resolved_fails_if_market_is_not_reported_or_disputed( ) { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..33, ScoringRule::Lmsr, diff --git a/zrml/prediction-markets/src/tests/approve_market.rs b/zrml/prediction-markets/src/tests/approve_market.rs index d5a0db67c..e4231bd3f 100644 --- a/zrml/prediction-markets/src/tests/approve_market.rs +++ b/zrml/prediction-markets/src/tests/approve_market.rs @@ -28,7 +28,7 @@ use sp_runtime::DispatchError; fn it_allows_advisory_origin_to_approve_markets() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -56,7 +56,7 @@ fn market_with_edit_request_cannot_be_approved() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -81,7 +81,7 @@ fn market_with_edit_request_cannot_be_approved() { #[test] fn approve_market_correctly_unreserves_advisory_bond() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), @@ -106,10 +106,13 @@ fn approve_market_correctly_unreserves_advisory_bond() { assert!(market.bonds.creation.unwrap().is_settled); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 73ac62752..d37781ba8 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -23,7 +23,7 @@ use test_case::test_case; #[test] fn buy_complete_set_works() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -48,20 +48,23 @@ fn buy_complete_set_works() { assert_eq!(bal, amount); } - let bal = AssetManager::free_balance(base_asset, &who); + let bal = AssetManager::free_balance(base_asset.into(), &who); assert_eq!(bal, 1_000 * BASE - amount); let market_account = PredictionMarkets::market_account(market_id); - let market_bal = AssetManager::free_balance(base_asset, &market_account); + let market_bal = AssetManager::free_balance(base_asset.into(), &market_account); assert_eq!(market_bal, amount); System::assert_last_event(Event::BoughtCompleteSet(market_id, amount, who).into()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -69,7 +72,7 @@ fn buy_complete_set_works() { fn buy_complete_fails_on_zero_amount() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, @@ -83,7 +86,7 @@ fn buy_complete_fails_on_zero_amount() { #[test] fn buy_complete_set_fails_on_insufficient_balance() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -96,11 +99,14 @@ fn buy_complete_set_fails_on_insufficient_balance() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -112,7 +118,7 @@ fn buy_complete_set_fails_on_insufficient_balance() { fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, @@ -133,7 +139,7 @@ fn buy_complete_set_fails_if_market_is_not_active(status: MarketStatus) { fn buy_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, scoring_rule, diff --git a/zrml/prediction-markets/src/tests/close_trusted_market.rs b/zrml/prediction-markets/src/tests/close_trusted_market.rs index 8fcdcb2f9..51d9c715b 100644 --- a/zrml/prediction-markets/src/tests/close_trusted_market.rs +++ b/zrml/prediction-markets/src/tests/close_trusted_market.rs @@ -32,7 +32,7 @@ fn close_trusted_market_works() { let market_creator = ALICE; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(market_creator), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -87,7 +87,7 @@ fn close_trusted_market_fails_if_not_trusted() { let market_creator = ALICE; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(market_creator), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -133,7 +133,7 @@ fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { let market_creator = ALICE; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(market_creator), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index 151ab1407..cb0ecb5c2 100644 --- a/zrml/prediction-markets/src/tests/create_market.rs +++ b/zrml/prediction-markets/src/tests/create_market.rs @@ -37,7 +37,7 @@ fn create_scalar_market_fails_on_invalid_range(range: RangeInclusive) { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -64,7 +64,7 @@ fn create_market_fails_on_min_dispute_period() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -91,7 +91,7 @@ fn create_market_fails_on_min_oracle_duration() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -118,7 +118,7 @@ fn create_market_fails_on_max_dispute_period() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -145,7 +145,7 @@ fn create_market_fails_on_max_grace_period() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -172,7 +172,7 @@ fn create_market_fails_on_max_oracle_duration() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -204,7 +204,7 @@ fn create_market_with_foreign_assets() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), + BaseAsset::ForeignAsset(420), Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -221,7 +221,7 @@ fn create_market_with_foreign_assets() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), + BaseAsset::ForeignAsset(50), Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -237,7 +237,7 @@ fn create_market_with_foreign_assets() { // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), + BaseAsset::ForeignAsset(100), Perbill::zero(), BOB, MarketPeriod::Block(123..456), @@ -249,7 +249,7 @@ fn create_market_with_foreign_assets() { ScoringRule::Lmsr, )); let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + assert_eq!(market.base_asset, BaseAsset::ForeignAsset(100)); }); } @@ -259,7 +259,7 @@ fn it_does_not_create_market_with_too_few_categories() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..100), @@ -281,7 +281,7 @@ fn it_does_not_create_market_with_too_many_categories() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..100), @@ -310,7 +310,7 @@ fn create_categorical_market_fails_if_market_period_is_invalid( assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, period, @@ -334,7 +334,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end_block), @@ -352,7 +352,7 @@ fn create_categorical_market_fails_if_end_is_not_far_enough_ahead() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..end_time), @@ -381,7 +381,7 @@ fn create_market_succeeds_if_market_duration_is_maximal_in_blocks() { ); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(start..end), @@ -409,7 +409,7 @@ fn create_market_suceeds_if_market_duration_is_maximal_in_moments() { ); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(start..end), @@ -437,7 +437,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_blocks() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(start..end), @@ -468,7 +468,7 @@ fn create_market_fails_if_market_duration_is_too_long_in_moments() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(start..end), @@ -532,7 +532,7 @@ fn create_market_sets_the_correct_market_parameters_and_reserves_the_correct_amo let creator_fee = Perbill::from_parts(1); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(creator), - Asset::Ztg, + BaseAsset::Ztg, creator_fee, oracle, period.clone(), @@ -567,7 +567,7 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { assert_noop!( PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(1..2), @@ -591,7 +591,7 @@ fn create_market_fails_on_trusted_market_with_non_zero_dispute_period() { fn create_categorical_market_deposits_the_correct_event() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 1..2, ScoringRule::Lmsr, @@ -607,7 +607,7 @@ fn create_categorical_market_deposits_the_correct_event() { fn create_scalar_market_deposits_the_correct_event() { ExtBuilder::default().build().execute_with(|| { simple_create_scalar_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 1..2, ScoringRule::Lmsr, diff --git a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs index 73264f96d..e82431816 100644 --- a/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs +++ b/zrml/prediction-markets/src/tests/create_market_and_deploy_pool.rs @@ -45,7 +45,7 @@ fn create_market_and_deploy_pool_works() { let market_id = 0; assert_ok!(PredictionMarkets::create_market_and_deploy_pool( RuntimeOrigin::signed(creator), - Asset::Ztg, + BaseAsset::Ztg, creator_fee, oracle, period.clone(), diff --git a/zrml/prediction-markets/src/tests/dispute.rs b/zrml/prediction-markets/src/tests/dispute.rs index 947f2caee..9896a75ad 100644 --- a/zrml/prediction-markets/src/tests/dispute.rs +++ b/zrml/prediction-markets/src/tests/dispute.rs @@ -31,7 +31,7 @@ fn it_allows_to_dispute_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { let end = 2; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -81,7 +81,7 @@ fn dispute_fails_disputed_already() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -122,7 +122,7 @@ fn dispute_fails_if_market_not_reported() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -157,7 +157,7 @@ fn dispute_reserves_dispute_bond() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -203,7 +203,7 @@ fn dispute_updates_market() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -250,7 +250,7 @@ fn dispute_emits_event() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -292,7 +292,7 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { ExtBuilder::default().build().execute_with(|| { // Creates a permissionless market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, diff --git a/zrml/prediction-markets/src/tests/dispute_early_close.rs b/zrml/prediction-markets/src/tests/dispute_early_close.rs index 8e2f7fdf1..074b10012 100644 --- a/zrml/prediction-markets/src/tests/dispute_early_close.rs +++ b/zrml/prediction-markets/src/tests/dispute_early_close.rs @@ -30,7 +30,7 @@ fn dispute_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -55,7 +55,7 @@ fn dispute_early_close_from_market_creator_works() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -129,7 +129,7 @@ fn dispute_early_close_fails_if_scheduled_as_sudo() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -161,7 +161,7 @@ fn dispute_early_close_fails_if_already_disputed() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -199,7 +199,7 @@ fn dispute_early_close_fails_if_already_rejected() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -239,7 +239,7 @@ fn settles_early_close_bonds_with_resolution_in_state_disputed() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -302,7 +302,7 @@ fn settles_early_close_bonds_with_resolution_in_state_scheduled_as_market_creato let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -350,7 +350,7 @@ fn schedule_early_close_disputed_sudo_schedule_and_settle_bonds() { let old_period = MarketPeriod::Block(0..end); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, old_period.clone(), diff --git a/zrml/prediction-markets/src/tests/edit_market.rs b/zrml/prediction-markets/src/tests/edit_market.rs index 731b93d2c..7bc90dc9f 100644 --- a/zrml/prediction-markets/src/tests/edit_market.rs +++ b/zrml/prediction-markets/src/tests/edit_market.rs @@ -29,7 +29,7 @@ use crate::MarketIdsForEdit; fn only_creator_can_edit_market() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -50,7 +50,7 @@ fn only_creator_can_edit_market() { assert_noop!( PredictionMarkets::edit_market( RuntimeOrigin::signed(BOB), - Asset::Ztg, + BaseAsset::Ztg, 0, CHARLIE, MarketPeriod::Block(0..2), @@ -69,7 +69,7 @@ fn only_creator_can_edit_market() { fn edit_cycle_for_proposed_markets() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 2..4, ScoringRule::Lmsr, @@ -89,7 +89,7 @@ fn edit_cycle_for_proposed_markets() { // After this edit its changed to ALICE assert_ok!(PredictionMarkets::edit_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, 0, CHARLIE, MarketPeriod::Block(2..4), @@ -114,7 +114,7 @@ fn edit_market_with_foreign_asset() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -136,7 +136,7 @@ fn edit_market_with_foreign_asset() { assert_noop!( PredictionMarkets::edit_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(50), + BaseAsset::ForeignAsset(50), 0, CHARLIE, MarketPeriod::Block(0..2), @@ -152,7 +152,7 @@ fn edit_market_with_foreign_asset() { assert_noop!( PredictionMarkets::edit_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(420), + BaseAsset::ForeignAsset(420), 0, CHARLIE, MarketPeriod::Block(0..2), @@ -167,7 +167,7 @@ fn edit_market_with_foreign_asset() { // As per Mock asset_registry genesis ForeignAsset(100) has allow_as_base_asset set to true. assert_ok!(PredictionMarkets::edit_market( RuntimeOrigin::signed(ALICE), - Asset::ForeignAsset(100), + BaseAsset::ForeignAsset(100), 0, CHARLIE, MarketPeriod::Block(0..2), @@ -178,6 +178,6 @@ fn edit_market_with_foreign_asset() { ScoringRule::Lmsr )); let market = MarketCommons::market(&0).unwrap(); - assert_eq!(market.base_asset, Asset::ForeignAsset(100)); + assert_eq!(market.base_asset, BaseAsset::ForeignAsset(100)); }); } diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs index b892bac20..c19931c59 100644 --- a/zrml/prediction-markets/src/tests/integration.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -31,7 +31,7 @@ use zrml_global_disputes::{ #[test] fn it_appeals_a_court_market_to_global_dispute() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let mut free_before = BTreeMap::new(); let jurors = 1000..(1000 + ::MaxSelectedDraws::get() as u128); @@ -126,11 +126,14 @@ fn it_appeals_a_court_market_to_global_dispute() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -140,7 +143,7 @@ fn the_entire_market_lifecycle_works_with_timestamps() { // Creates a permissionless market. assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -177,7 +180,7 @@ fn the_entire_market_lifecycle_works_with_timestamps() { #[test] fn full_scalar_market_lifecycle() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), base_asset, @@ -264,8 +267,8 @@ fn full_scalar_market_lifecycle() { } // check payouts is right for each CHARLIE and EVE - let base_asset_bal_charlie = AssetManager::free_balance(base_asset, &CHARLIE); - let base_asset_bal_eve = AssetManager::free_balance(base_asset, &EVE); + let base_asset_bal_charlie = AssetManager::free_balance(base_asset.into(), &CHARLIE); + let base_asset_bal_eve = AssetManager::free_balance(base_asset.into(), &EVE); assert_eq!(base_asset_bal_charlie, 98750 * CENT); // 75 (LONG) + 12.5 (SHORT) + 900 (balance) assert_eq!(base_asset_bal_eve, 1000 * BASE); System::assert_has_event( @@ -290,7 +293,7 @@ fn full_scalar_market_lifecycle() { ); assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); - let base_asset_bal_eve_after = AssetManager::free_balance(base_asset, &EVE); + let base_asset_bal_eve_after = AssetManager::free_balance(base_asset.into(), &EVE); assert_eq!(base_asset_bal_eve_after, 101250 * CENT); // 12.5 (SHORT) + 1000 (balance) System::assert_last_event( Event::TokensRedeemed( @@ -304,18 +307,21 @@ fn full_scalar_market_lifecycle() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn authorized_correctly_resolves_disputed_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), @@ -341,14 +347,14 @@ fn authorized_correctly_resolves_disputed_market() { OutcomeReport::Categorical(0) )); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); let dispute_at = grace_period + 1 + 1; run_to_block(dispute_at); assert_ok!(PredictionMarkets::dispute(RuntimeOrigin::signed(CHARLIE), 0,)); - if base_asset == Asset::Ztg { + if base_asset == BaseAsset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!( charlie_balance, @@ -357,7 +363,7 @@ fn authorized_correctly_resolves_disputed_market() { } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -385,7 +391,7 @@ fn authorized_correctly_resolves_disputed_market() { ); assert_eq!(market_ids_1.len(), 1); - if base_asset == Asset::Ztg { + if base_asset == BaseAsset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!( charlie_balance, @@ -394,7 +400,7 @@ fn authorized_correctly_resolves_disputed_market() { } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -403,7 +409,7 @@ fn authorized_correctly_resolves_disputed_market() { let market_after = MarketCommons::market(&0).unwrap(); assert_eq!(market_after.status, MarketStatus::Disputed); - if base_asset == Asset::Ztg { + if base_asset == BaseAsset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!( charlie_balance, @@ -412,13 +418,13 @@ fn authorized_correctly_resolves_disputed_market() { } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - ::DisputeBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } run_blocks(1); - if base_asset == Asset::Ztg { + if base_asset == BaseAsset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!( charlie_balance, @@ -427,7 +433,7 @@ fn authorized_correctly_resolves_disputed_market() { } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE - CENT); } @@ -438,13 +444,13 @@ fn authorized_correctly_resolves_disputed_market() { assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); - if base_asset == Asset::Ztg { + if base_asset == BaseAsset::Ztg { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); } else { let charlie_balance = AssetManager::free_balance(Asset::Ztg, &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE + ::OracleBond::get()); - let charlie_balance = AssetManager::free_balance(base_asset, &CHARLIE); + let charlie_balance = AssetManager::free_balance(base_asset.into(), &CHARLIE); assert_eq!(charlie_balance, 1_000 * BASE); } let charlie_reserved_2 = AssetManager::reserved_balance(Asset::Ztg, &CHARLIE); @@ -462,18 +468,21 @@ fn authorized_correctly_resolves_disputed_market() { assert!(market_after.bonds.oracle.unwrap().is_settled); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn outsider_reports_wrong_outcome() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; @@ -548,10 +557,13 @@ fn outsider_reports_wrong_outcome() { assert_eq!(Balances::free_balance(DAVE), dave_balance_before + outcome_bond); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } diff --git a/zrml/prediction-markets/src/tests/manually_close_market.rs b/zrml/prediction-markets/src/tests/manually_close_market.rs index 9c61a8d33..d6f09d98e 100644 --- a/zrml/prediction-markets/src/tests/manually_close_market.rs +++ b/zrml/prediction-markets/src/tests/manually_close_market.rs @@ -36,7 +36,7 @@ fn manually_close_market_after_long_stall() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -49,7 +49,7 @@ fn manually_close_market_after_long_stall() { )); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -105,7 +105,7 @@ fn manually_close_market_fails_if_market_not_in_close_time_frame_list() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -146,7 +146,7 @@ fn manually_close_market_fails_if_not_allowed_for_block_based_markets() { let end = 5; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Block(0..end), diff --git a/zrml/prediction-markets/src/tests/mod.rs b/zrml/prediction-markets/src/tests/mod.rs index e11639a55..0365c2c37 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -42,9 +42,7 @@ mod schedule_early_close; mod sell_complete_set; mod start_global_dispute; -use crate::{ - mock::*, AccountIdOf, AssetOf, BalanceOf, Config, Error, Event, MarketIdsPerDisputeBlock, -}; +use crate::{mock::*, AccountIdOf, BalanceOf, Config, Error, Event, MarketIdsPerDisputeBlock}; use core::ops::Range; use frame_support::{assert_noop, assert_ok, traits::NamedReservableCurrency}; use orml_traits::MultiCurrency; @@ -53,8 +51,8 @@ use sp_runtime::traits::{BlakeTwo256, Hash, Zero}; use zeitgeist_primitives::{ constants::mock::{BASE, CENT}, types::{ - Asset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, - MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, + Asset, BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, + MarketPeriod, MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; use zrml_court::types::VoteItem; @@ -78,7 +76,7 @@ fn gen_metadata(byte: u8) -> MultiHash { } fn simple_create_categorical_market( - base_asset: AssetOf, + base_asset: BaseAsset, creation: MarketCreation, period: Range, scoring_rule: ScoringRule, @@ -99,7 +97,7 @@ fn simple_create_categorical_market( } fn simple_create_scalar_market( - base_asset: AssetOf, + base_asset: BaseAsset, creation: MarketCreation, period: Range, scoring_rule: ScoringRule, diff --git a/zrml/prediction-markets/src/tests/on_initialize.rs b/zrml/prediction-markets/src/tests/on_initialize.rs index 0d5d74427..1370297a7 100644 --- a/zrml/prediction-markets/src/tests/on_initialize.rs +++ b/zrml/prediction-markets/src/tests/on_initialize.rs @@ -31,7 +31,7 @@ fn on_initialize_skips_the_genesis_block() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), diff --git a/zrml/prediction-markets/src/tests/on_market_close.rs b/zrml/prediction-markets/src/tests/on_market_close.rs index 23ce18632..ae8762b21 100644 --- a/zrml/prediction-markets/src/tests/on_market_close.rs +++ b/zrml/prediction-markets/src/tests/on_market_close.rs @@ -24,7 +24,7 @@ use zeitgeist_primitives::constants::MILLISECS_PER_BLOCK; #[test] fn on_market_close_auto_rejects_expired_advised_market() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); @@ -60,17 +60,20 @@ fn on_market_close_auto_rejects_expired_advised_market() { System::assert_has_event(Event::MarketExpired(market_id).into()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { // Give ALICE `SENTINEL_AMOUNT` free and reserved ZTG; we record the free balance to check // that the AdvisoryBond and the OracleBond gets unreserved, when the advised market expires. assert_ok!(AssetManager::deposit(Asset::Ztg, &ALICE, 2 * SENTINEL_AMOUNT)); @@ -119,11 +122,14 @@ fn on_market_close_auto_rejects_expired_advised_market_with_edit_request() { System::assert_has_event(Event::MarketExpired(market_id).into()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -134,7 +140,7 @@ fn on_market_close_successfully_auto_closes_market_with_blocks() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Block(0..end), @@ -166,7 +172,7 @@ fn on_market_close_successfully_auto_closes_market_with_timestamps() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -206,7 +212,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -219,7 +225,7 @@ fn on_market_close_successfully_auto_closes_multiple_markets_after_stall() { )); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -257,7 +263,7 @@ fn on_market_close_market_status_manager_exceeds_max_recovery_time_frames_after_ let category_count = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), @@ -270,7 +276,7 @@ fn on_market_close_market_status_manager_exceeds_max_recovery_time_frames_after_ )); assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), ALICE, MarketPeriod::Timestamp(0..end), diff --git a/zrml/prediction-markets/src/tests/on_resolution.rs b/zrml/prediction-markets/src/tests/on_resolution.rs index 2f5bebf8b..04269fd1c 100644 --- a/zrml/prediction-markets/src/tests/on_resolution.rs +++ b/zrml/prediction-markets/src/tests/on_resolution.rs @@ -31,7 +31,7 @@ fn it_correctly_resolves_a_market_that_was_reported_on() { ExtBuilder::default().build().execute_with(|| { let end = 2; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -86,7 +86,7 @@ fn it_correctly_resolves_a_market_that_was_reported_on() { #[test] fn it_resolves_a_disputed_market() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let end = 2; simple_create_categorical_market( base_asset, @@ -232,17 +232,20 @@ fn it_resolves_a_disputed_market() { assert!(market_after.bonds.dispute.unwrap().is_settled); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn it_resolves_a_disputed_court_market() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let juror_0 = 1000; let juror_1 = 1001; let juror_2 = 1002; @@ -468,11 +471,14 @@ fn it_resolves_a_disputed_court_market() { assert_eq!(free_juror_2_after, free_juror_2_before + juror_2_share * total_slashed); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -480,7 +486,7 @@ fn it_resolves_a_disputed_court_market() { fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_oracle_report() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -514,11 +520,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -526,7 +535,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_market_on_outsider_report() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -560,11 +569,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma assert_eq!(Balances::free_balance(ALICE), alice_balance_before); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -573,7 +585,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark { // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -611,11 +623,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark assert_eq!(Balances::free_balance(ALICE), alice_balance_before + ValidityBond::get()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -624,7 +639,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma { // Oracle reports in time but incorrect report, so OracleBond gets slashed on resolution // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -663,11 +678,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_approved_advised_ma assert_eq!(Balances::free_balance(ALICE), alice_balance_before); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -676,7 +694,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark { // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -723,11 +741,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -736,7 +757,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma { // Oracle reports in time and correct report, so OracleBond does not get slashed on resolution // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -781,11 +802,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma assert_eq!(Balances::free_balance(ALICE), alice_balance_before + OracleBond::get()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -794,7 +818,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark { // Oracle does not report in time, so OracleBond gets slashed on resolution // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -853,11 +877,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -866,7 +893,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma { // Oracle does not report in time, so OracleBond gets slashed on resolution // NOTE: Bonds are always in ZTG - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -925,11 +952,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_advised_approved_ma ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -939,7 +969,7 @@ fn trusted_market_complete_lifecycle() { let end = 3; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -986,7 +1016,7 @@ fn trusted_market_complete_lifecycle() { fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_oracle_report() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -1020,11 +1050,14 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -1032,7 +1065,7 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_market_on_outsider_report() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { reserve_sentinel_amounts(); let end = 100; assert_ok!(PredictionMarkets::create_market( @@ -1091,10 +1124,13 @@ fn on_resolution_correctly_reserves_and_unreserves_bonds_for_permissionless_mark assert!(market.bonds.outsider.unwrap().is_settled); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index d7542018a..b6c957dfd 100644 --- a/zrml/prediction-markets/src/tests/redeem_shares.rs +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -27,7 +27,7 @@ use zeitgeist_primitives::types::{OutcomeReport, ScalarPosition}; #[test] fn it_allows_to_redeem_shares() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let end = 2; simple_create_categorical_market( base_asset, @@ -58,17 +58,20 @@ fn it_allows_to_redeem_shares() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test_case(ScoringRule::Parimutuel; "parimutuel")] fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule) { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { let end = 2; simple_create_categorical_market( base_asset, @@ -88,48 +91,57 @@ fn redeem_shares_fails_if_invalid_resolution_mechanism(scoring_rule: ScoringRule ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn scalar_market_correctly_resolves_on_out_of_range_outcomes_below_threshold() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { scalar_market_correctly_resolves_common(base_asset, 50); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1100 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &EVE), 1100 * BASE); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test] fn scalar_market_correctly_resolves_on_out_of_range_outcomes_above_threshold() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { scalar_market_correctly_resolves_common(base_asset, 250); - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 1000 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &CHARLIE), 1000 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &EVE), 1000 * BASE); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } // Common code of `scalar_market_correctly_resolves_*` -fn scalar_market_correctly_resolves_common(base_asset: AssetOf, reported_value: u128) { +fn scalar_market_correctly_resolves_common(base_asset: BaseAsset, reported_value: u128) { let end = 100; simple_create_scalar_market( base_asset, @@ -167,8 +179,8 @@ fn scalar_market_correctly_resolves_common(base_asset: AssetOf, reporte // Check balances before redeeming (just to make sure that our tests are based on correct // assumptions)! - assert_eq!(AssetManager::free_balance(base_asset, &CHARLIE), 900 * BASE); - assert_eq!(AssetManager::free_balance(base_asset, &EVE), 1000 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &CHARLIE), 900 * BASE); + assert_eq!(AssetManager::free_balance(base_asset.into(), &EVE), 1000 * BASE); assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); diff --git a/zrml/prediction-markets/src/tests/reject_early_close.rs b/zrml/prediction-markets/src/tests/reject_early_close.rs index 77cd26a85..2a61054ad 100644 --- a/zrml/prediction-markets/src/tests/reject_early_close.rs +++ b/zrml/prediction-markets/src/tests/reject_early_close.rs @@ -29,7 +29,7 @@ fn reject_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -55,7 +55,7 @@ fn reject_early_close_fails_if_state_is_scheduled_as_market_creator() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -83,7 +83,7 @@ fn reject_early_close_fails_if_state_is_rejected() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -113,7 +113,7 @@ fn reject_early_close_resets_to_old_market_period() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -153,7 +153,7 @@ fn reject_early_close_settles_bonds() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), diff --git a/zrml/prediction-markets/src/tests/reject_market.rs b/zrml/prediction-markets/src/tests/reject_market.rs index 38eded944..02e0b979d 100644 --- a/zrml/prediction-markets/src/tests/reject_market.rs +++ b/zrml/prediction-markets/src/tests/reject_market.rs @@ -27,7 +27,7 @@ use crate::{MarketIdsForEdit, MarketIdsPerCloseBlock}; fn it_allows_the_advisory_origin_to_reject_markets() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 4..6, ScoringRule::Lmsr, @@ -58,7 +58,7 @@ fn reject_errors_if_reject_reason_is_too_long() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -82,7 +82,7 @@ fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, @@ -110,7 +110,7 @@ fn it_allows_the_advisory_origin_to_reject_markets_with_edit_request() { #[test] fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { // NOTE: Bonds are always in ZTG, irrespective of base_asset. - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Advised, @@ -163,11 +163,14 @@ fn reject_market_unreserves_oracle_bond_and_slashes_advisory_bond() { assert_eq!(balance_treasury_after, slash_amount_advisory_bond); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -177,19 +180,19 @@ fn reject_market_clears_auto_close_blocks() { // can not be deployed on pending advised pools. ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 33..66, ScoringRule::Lmsr, ); simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 22..66, ScoringRule::Lmsr, ); simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 22..33, ScoringRule::Lmsr, @@ -209,7 +212,7 @@ fn reject_market_fails_on_permissionless_market() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, @@ -228,7 +231,7 @@ fn reject_market_fails_on_approved_market() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, diff --git a/zrml/prediction-markets/src/tests/report.rs b/zrml/prediction-markets/src/tests/report.rs index 4d28e8cea..697abcd5f 100644 --- a/zrml/prediction-markets/src/tests/report.rs +++ b/zrml/prediction-markets/src/tests/report.rs @@ -32,7 +32,7 @@ fn it_allows_to_report_the_outcome_of_a_market() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -78,7 +78,7 @@ fn report_fails_before_grace_period_is_over() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -104,7 +104,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -146,7 +146,7 @@ fn it_allows_only_oracle_to_report_the_outcome_of_a_market_during_oracle_duratio ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -187,7 +187,7 @@ fn report_fails_on_mismatched_outcome_for_categorical_market() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -210,7 +210,7 @@ fn report_fails_on_out_of_range_outcome_for_categorical_market() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -233,7 +233,7 @@ fn report_fails_on_mismatched_outcome_for_scalar_market() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_scalar_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -256,7 +256,7 @@ fn it_allows_anyone_to_report_an_unreported_market() { ExtBuilder::default().build().execute_with(|| { let end = 2; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -294,7 +294,7 @@ fn report_fails_on_market_state_proposed() { ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -317,7 +317,7 @@ fn report_fails_on_market_state_closed_for_advised_market() { ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -340,7 +340,7 @@ fn report_fails_on_market_state_active() { ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -363,7 +363,7 @@ fn report_fails_on_market_state_resolved() { ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), @@ -390,7 +390,7 @@ fn report_fails_if_reporter_is_not_the_oracle() { ExtBuilder::default().build().execute_with(|| { assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(0..100_000_000), diff --git a/zrml/prediction-markets/src/tests/request_edit.rs b/zrml/prediction-markets/src/tests/request_edit.rs index 8851e82de..31c4e022b 100644 --- a/zrml/prediction-markets/src/tests/request_edit.rs +++ b/zrml/prediction-markets/src/tests/request_edit.rs @@ -27,7 +27,7 @@ use sp_runtime::DispatchError; fn it_allows_request_edit_origin_to_request_edits_for_markets() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 2..4, ScoringRule::Lmsr, @@ -68,7 +68,7 @@ fn request_edit_fails_on_bad_origin() { frame_system::Pallet::::set_block_number(1); // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 2..4, ScoringRule::Lmsr, @@ -92,7 +92,7 @@ fn edit_request_fails_if_edit_reason_is_too_long() { ExtBuilder::default().build().execute_with(|| { // Creates an advised market. simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Advised, 0..2, ScoringRule::Lmsr, diff --git a/zrml/prediction-markets/src/tests/schedule_early_close.rs b/zrml/prediction-markets/src/tests/schedule_early_close.rs index cb8d83145..d29ca7f8f 100644 --- a/zrml/prediction-markets/src/tests/schedule_early_close.rs +++ b/zrml/prediction-markets/src/tests/schedule_early_close.rs @@ -36,7 +36,7 @@ fn schedule_early_close_emits_event() { ExtBuilder::default().build().execute_with(|| { let end = 100; simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..end, ScoringRule::Lmsr, @@ -68,7 +68,7 @@ fn sudo_schedule_early_close_at_block_works() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -139,7 +139,7 @@ fn sudo_schedule_early_close_at_timeframe_works() { let end = start + (42 * MILLISECS_PER_BLOCK) as u64; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(start..end), @@ -208,7 +208,7 @@ fn schedule_early_close_block_fails_if_early_close_request_too_late() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), @@ -240,7 +240,7 @@ fn schedule_early_close_timestamp_fails_if_early_close_request_too_late() { let end = start + (42 * MILLISECS_PER_BLOCK) as u64; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Timestamp(start..end), @@ -269,7 +269,7 @@ fn schedule_early_close_as_market_creator_works() { let end = 100; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..end), diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 6814481c9..828bc0ea1 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -24,7 +24,7 @@ use test_case::test_case; #[test_case(ScoringRule::Lmsr)] #[test_case(ScoringRule::Orderbook)] fn sell_complete_set_works(scoring_rule: ScoringRule) { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -56,17 +56,20 @@ fn sell_complete_set_works(scoring_rule: ScoringRule) { assert_eq!(bal, expected_amount); } - let bal = AssetManager::free_balance(base_asset, &who); + let bal = AssetManager::free_balance(base_asset.into(), &who); assert_eq!(bal, 1_000 * BASE - expected_amount); System::assert_last_event(Event::SoldCompleteSet(market_id, sell_amount, who).into()); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } @@ -74,7 +77,7 @@ fn sell_complete_set_works(scoring_rule: ScoringRule) { fn sell_complete_set_fails_on_zero_amount() { ExtBuilder::default().build().execute_with(|| { simple_create_categorical_market( - Asset::Ztg, + BaseAsset::Ztg, MarketCreation::Permissionless, 0..2, ScoringRule::Lmsr, @@ -88,7 +91,7 @@ fn sell_complete_set_fails_on_zero_amount() { #[test] fn sell_complete_set_fails_on_insufficient_share_balance() { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -110,17 +113,20 @@ fn sell_complete_set_fails_on_insufficient_share_balance() { ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } #[test_case(ScoringRule::Parimutuel; "parimutuel")] fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: ScoringRule) { - let test = |base_asset: AssetOf| { + let test = |base_asset: BaseAsset| { simple_create_categorical_market( base_asset, MarketCreation::Permissionless, @@ -133,10 +139,13 @@ fn sell_complete_set_fails_if_market_has_wrong_scoring_rule(scoring_rule: Scorin ); }; ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); + test(BaseAsset::CampaignAsset(100)); + }); + ExtBuilder::default().build().execute_with(|| { + test(BaseAsset::Ztg); }); #[cfg(feature = "parachain")] ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); + test(BaseAsset::ForeignAsset(100)); }); } diff --git a/zrml/prediction-markets/src/tests/start_global_dispute.rs b/zrml/prediction-markets/src/tests/start_global_dispute.rs index 2b84d8ed7..f9450043f 100644 --- a/zrml/prediction-markets/src/tests/start_global_dispute.rs +++ b/zrml/prediction-markets/src/tests/start_global_dispute.rs @@ -33,7 +33,7 @@ fn start_global_dispute_fails_on_wrong_mdm() { let end = 2; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, MarketPeriod::Block(0..2), diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index c0466b4c7..e91104d44 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -33,7 +33,7 @@ pub use simple_disputes_pallet_api::SimpleDisputesPalletApi; use zeitgeist_primitives::{ traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - Asset, GlobalDisputeItem, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, + BaseAsset, GlobalDisputeItem, Market, MarketDispute, MarketDisputeMechanism, MarketStatus, OutcomeReport, Report, ResultWithWeightInfo, }, }; @@ -115,7 +115,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - Asset>, + BaseAsset, >; pub(crate) type DisputesOf = BoundedVec< MarketDispute< @@ -556,7 +556,7 @@ where use zeitgeist_primitives::types::{MarketBonds, ScoringRule}; zeitgeist_primitives::types::Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: zeitgeist_primitives::types::MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: T::PalletId::get().into_account_truncating(), From 60ce9496f58a9932c80cc80ce6742246e65e245d Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 19:44:57 +0100 Subject: [PATCH 050/104] Update authorized --- zrml/authorized/src/lib.rs | 2 +- zrml/authorized/src/mock.rs | 4 ++-- zrml/prediction-markets/src/benchmarks.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 309c96513..1f0af8b74 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -49,7 +49,7 @@ mod pallet { use zeitgeist_primitives::{ traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - BaseAsset, AuthorityReport, GlobalDisputeItem, Market, MarketDisputeMechanism, + AuthorityReport, BaseAsset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, ResultWithWeightInfo, }, }; diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 556f969c2..9aabd5c4f 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -38,7 +38,7 @@ use zeitgeist_primitives::{ }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, BlockTest, Hash, Index, Market, MarketId, + AccountIdTest, Balance, BaseAsset, BlockNumber, BlockTest, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -85,7 +85,7 @@ impl DisputeResolutionApi for MockResolution { Self::Balance, Self::BlockNumber, Self::Moment, - Asset, + BaseAsset, >, ) -> Result { Ok(Weight::zero()) diff --git a/zrml/prediction-markets/src/benchmarks.rs b/zrml/prediction-markets/src/benchmarks.rs index 8cac42607..eb19904b4 100644 --- a/zrml/prediction-markets/src/benchmarks.rs +++ b/zrml/prediction-markets/src/benchmarks.rs @@ -46,8 +46,8 @@ use zeitgeist_primitives::{ math::fixed::{BaseProvider, ZeitgeistBase}, traits::DisputeApi, types::{ - Asset, BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, MultiHash, OutcomeReport, ScoringRule, + Asset, BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; use zrml_authorized::Pallet as AuthorizedPallet; From f21512857bb396487a298f475e00d671b47b82ed Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 19:47:45 +0100 Subject: [PATCH 051/104] Update liquidity-mining --- zrml/liquidity-mining/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zrml/liquidity-mining/src/tests.rs b/zrml/liquidity-mining/src/tests.rs index 420f0dae9..44d2f339f 100644 --- a/zrml/liquidity-mining/src/tests.rs +++ b/zrml/liquidity-mining/src/tests.rs @@ -32,8 +32,8 @@ use frame_support::{ }; use frame_system::RawOrigin; use zeitgeist_primitives::types::{ - Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, - MarketStatus, MarketType, ScoringRule, + BaseAsset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, ScoringRule, }; use zrml_market_commons::Markets; @@ -203,7 +203,7 @@ fn create_default_market(market_id: u128, period: Range) { Markets::::insert( market_id, Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: 0, From d23da955dc0038605670387ef02306cb81b72102 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 20 Feb 2024 19:49:48 +0100 Subject: [PATCH 052/104] Update simple-disputes --- zrml/simple-disputes/src/mock.rs | 4 ++-- zrml/simple-disputes/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index e53bd66f4..267470416 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -35,7 +35,7 @@ use zeitgeist_primitives::{ }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Amount, Asset, Assets, Balance, BasicCurrencyAdapter, BlockNumber, + AccountIdTest, Amount, Assets, Balance, BaseAsset, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -88,7 +88,7 @@ impl DisputeResolutionApi for NoopResolution { Self::Balance, Self::BlockNumber, Self::Moment, - Asset, + BaseAsset, >, ) -> Result { Ok(Weight::zero()) diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index 03ee1e578..2f04a4499 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -27,13 +27,13 @@ use zeitgeist_primitives::{ constants::mock::{OutcomeBond, OutcomeFactor}, traits::DisputeApi, types::{ - Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDispute, + BaseAsset, Deadlines, Market, MarketBonds, MarketCreation, MarketDispute, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, OutcomeReport, ScoringRule, }, }; const DEFAULT_MARKET: MarketOf = Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: 0, From de0cda44a325ce3653e0c98cea2e2fcbf2aa7cac Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 21 Feb 2024 16:49:24 +0100 Subject: [PATCH 053/104] Update parimutuels (wip) --- Cargo.lock | 1 + zrml/parimutuel/Cargo.toml | 3 +++ zrml/parimutuel/src/lib.rs | 41 ++++++++++++++++++++++++++---------- zrml/parimutuel/src/utils.rs | 4 ++-- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44c60c1df..1623a9933 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14752,6 +14752,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-timestamp", "parity-scale-codec", diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index 52e1b0d8a..f0fdc1adc 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -3,6 +3,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } orml-traits = { workspace = true } +pallet-assets = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } @@ -26,12 +27,14 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "orml-traits/std", + "pallet-assets/std", "parity-scale-codec/std", "sp-runtime/std", "zeitgeist-primitives/std", diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 3bbcbd441..f4e4277ae 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -34,7 +34,7 @@ mod pallet { ensure, log, pallet_prelude::{Decode, DispatchError, Encode, TypeInfo}, require_transactional, - traits::{Get, IsType, StorageVersion}, + traits::{fungibles::Create, Get, IsType, StorageVersion}, PalletId, RuntimeDebug, }; use frame_system::{ @@ -42,6 +42,7 @@ mod pallet { pallet_prelude::{BlockNumberFor, OriginFor}, }; use orml_traits::MultiCurrency; + use pallet_assets::ManagedDestroy; use sp_runtime::{ traits::{AccountIdConversion, CheckedSub, Zero}, DispatchResult, @@ -49,12 +50,18 @@ mod pallet { use zeitgeist_primitives::{ math::fixed::FixedMulDiv, traits::DistributeFees, - types::{Asset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, + types::{Asset, BaseAsset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; #[pallet::config] pub trait Config: frame_system::Config { + /// The module handling the creation of market assets. + type AssetCreator: Create, Balance = BalanceOf>; + + /// The module handling the destruction of market assets. + type AssetDestroyer: ManagedDestroy, Balance = BalanceOf>; + /// The api to handle different asset classes. type AssetManager: MultiCurrency>; @@ -98,7 +105,7 @@ mod pallet { <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; pub(crate) type MarketOf = - Market, BalanceOf, BlockNumberFor, MomentOf, Asset>>; + Market, BalanceOf, BlockNumberFor, MomentOf, BaseAsset>; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -313,7 +320,7 @@ mod pallet { let market = T::MarketCommons::market(&market_id)?; let base_asset = market.base_asset; ensure!( - T::AssetManager::ensure_can_withdraw(base_asset, &who, amount).is_ok(), + T::AssetManager::ensure_can_withdraw(base_asset.into(), &who, amount).is_ok(), Error::::InsufficientBalance ); ensure!(market.status == MarketStatus::Active, Error::::MarketIsNotActive); @@ -324,7 +331,8 @@ mod pallet { ); Self::market_assets_contains(&market, &asset)?; - let external_fees = T::ExternalFees::distribute(market_id, base_asset, &who, amount); + let external_fees = + T::ExternalFees::distribute(market_id, base_asset.into(), &who, amount); let amount_minus_fees = amount.checked_sub(&external_fees).ok_or(Error::::Unexpected)?; ensure!( @@ -334,7 +342,7 @@ mod pallet { let pot_account = Self::pot_account(market_id); - T::AssetManager::transfer(market.base_asset, &who, &pot_account, amount_minus_fees)?; + T::AssetManager::transfer(base_asset.into(), &who, &pot_account, amount_minus_fees)?; T::AssetManager::deposit(asset, &who, amount_minus_fees)?; Self::deposit_event(Event::OutcomeBought { @@ -396,7 +404,7 @@ mod pallet { } let pot_account = Self::pot_account(market_id); - let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account); + let pot_total = T::AssetManager::free_balance(market.base_asset.into(), &pot_account); let payoff = pot_total.bmul_bdiv(winning_balance, outcome_total)?; Self::check_values(winning_balance, pot_total, outcome_total, payoff)?; @@ -405,10 +413,16 @@ mod pallet { T::AssetManager::withdraw(winning_asset, &who, withdrawn_asset_balance)?; - let remaining_bal = T::AssetManager::free_balance(market.base_asset, &pot_account); + let remaining_bal = + T::AssetManager::free_balance(market.base_asset.into(), &pot_account); let base_asset_payoff = payoff.min(remaining_bal); - T::AssetManager::transfer(market.base_asset, &pot_account, &who, base_asset_payoff)?; + T::AssetManager::transfer( + market.base_asset.into(), + &pot_account, + &who, + base_asset_payoff, + )?; Self::deposit_event(Event::RewardsClaimed { market_id, @@ -449,7 +463,7 @@ mod pallet { T::AssetManager::withdraw(refund_asset, &who, refund_balance)?; let pot_account = Self::pot_account(market_id); - let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account); + let pot_total = T::AssetManager::free_balance(market.base_asset.into(), &pot_account); if pot_total < refund_balance { log::debug!( target: LOG_TARGET, @@ -459,7 +473,12 @@ mod pallet { } let refund_balance = refund_balance.min(pot_total); - T::AssetManager::transfer(market.base_asset, &pot_account, &who, refund_balance)?; + T::AssetManager::transfer( + market.base_asset.into(), + &pot_account, + &who, + refund_balance, + )?; Self::deposit_event(Event::BalanceRefunded { market_id, diff --git a/zrml/parimutuel/src/utils.rs b/zrml/parimutuel/src/utils.rs index 3ac1f28cb..6b596ae5c 100644 --- a/zrml/parimutuel/src/utils.rs +++ b/zrml/parimutuel/src/utils.rs @@ -23,12 +23,12 @@ where use frame_support::traits::Get; use sp_runtime::{traits::AccountIdConversion, Perbill}; use zeitgeist_primitives::types::{ - Asset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + BaseAsset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, ScoringRule, }; zeitgeist_primitives::types::Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), creator, From eb2621a41050b9732bc39fb01d175215269fa9b1 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 11:52:03 +0100 Subject: [PATCH 054/104] Merge release v0.5.0 into main (#1262) * Update versions (#1227) * Update weights (#1232) * Update copyright years * Revert Court handle_inflation() weight --- Cargo.lock | 44 +- HEADER_GPL3 | 2 +- macros/Cargo.toml | 2 +- node/Cargo.toml | 2 +- primitives/Cargo.toml | 2 +- runtime/battery-station/Cargo.toml | 2 +- runtime/battery-station/src/lib.rs | 4 +- runtime/common/Cargo.toml | 2 +- .../src/weights/cumulus_pallet_xcmp_queue.rs | 12 +- runtime/common/src/weights/frame_system.rs | 48 +- runtime/common/src/weights/orml_currencies.rs | 24 +- runtime/common/src/weights/orml_tokens.rs | 24 +- .../src/weights/pallet_author_inherent.rs | 8 +- .../src/weights/pallet_author_mapping.rs | 24 +- .../src/weights/pallet_author_slot_filter.rs | 8 +- runtime/common/src/weights/pallet_balances.rs | 32 +- runtime/common/src/weights/pallet_bounties.rs | 56 +- .../common/src/weights/pallet_collective.rs | 126 +-- .../common/src/weights/pallet_contracts.rs | 946 +++++++++--------- .../common/src/weights/pallet_democracy.rs | 124 ++- runtime/common/src/weights/pallet_identity.rs | 148 ++- .../common/src/weights/pallet_membership.rs | 60 +- runtime/common/src/weights/pallet_multisig.rs | 70 +- .../src/weights/pallet_parachain_staking.rs | 212 ++-- runtime/common/src/weights/pallet_preimage.rs | 60 +- runtime/common/src/weights/pallet_proxy.rs | 92 +- .../common/src/weights/pallet_scheduler.rs | 76 +- .../common/src/weights/pallet_timestamp.rs | 12 +- runtime/common/src/weights/pallet_treasury.rs | 36 +- runtime/common/src/weights/pallet_utility.rs | 36 +- runtime/common/src/weights/pallet_vesting.rs | 90 +- runtime/zeitgeist/Cargo.toml | 2 +- runtime/zeitgeist/src/lib.rs | 4 +- zrml/authorized/Cargo.toml | 2 +- zrml/authorized/src/weights.rs | 48 +- zrml/court/Cargo.toml | 2 +- zrml/court/src/weights.rs | 216 ++-- zrml/global-disputes/Cargo.toml | 2 +- zrml/global-disputes/src/weights.rs | 90 +- zrml/liquidity-mining/Cargo.toml | 2 +- zrml/liquidity-mining/src/weights.rs | 12 +- zrml/market-commons/Cargo.toml | 2 +- zrml/neo-swaps/Cargo.toml | 2 +- zrml/neo-swaps/src/weights.rs | 88 +- zrml/orderbook/Cargo.toml | 2 +- zrml/orderbook/src/weights.rs | 26 +- zrml/parimutuel/Cargo.toml | 2 +- zrml/parimutuel/src/weights.rs | 29 +- zrml/prediction-markets/Cargo.toml | 2 +- .../prediction-markets/runtime-api/Cargo.toml | 2 +- zrml/prediction-markets/src/weights.rs | 591 +++++------ zrml/rikiddo/Cargo.toml | 2 +- zrml/simple-disputes/Cargo.toml | 2 +- zrml/styx/Cargo.toml | 2 +- zrml/styx/src/weights.rs | 16 +- zrml/swaps/Cargo.toml | 2 +- zrml/swaps/rpc/Cargo.toml | 2 +- zrml/swaps/runtime-api/Cargo.toml | 2 +- zrml/swaps/src/weights.rs | 84 +- 59 files changed, 1751 insertions(+), 1871 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56a6c80f1..e16ddfcfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "battery-station-runtime" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "common-runtime", @@ -1206,7 +1206,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "cumulus-pallet-xcmp-queue", @@ -14279,11 +14279,11 @@ dependencies = [ [[package]] name = "zeitgeist-macros" -version = "0.4.3" +version = "0.5.0" [[package]] name = "zeitgeist-node" -version = "0.4.3" +version = "0.5.0" dependencies = [ "battery-station-runtime", "cfg-if", @@ -14375,7 +14375,7 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arbitrary", "fixed", @@ -14398,7 +14398,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "common-runtime", @@ -14522,7 +14522,7 @@ dependencies = [ [[package]] name = "zrml-authorized" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14540,7 +14540,7 @@ dependencies = [ [[package]] name = "zrml-court" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arrayvec 0.7.4", "env_logger 0.10.1", @@ -14565,7 +14565,7 @@ dependencies = [ [[package]] name = "zrml-global-disputes" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14586,7 +14586,7 @@ dependencies = [ [[package]] name = "zrml-liquidity-mining" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14605,7 +14605,7 @@ dependencies = [ [[package]] name = "zrml-market-commons" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-support", @@ -14623,7 +14623,7 @@ dependencies = [ [[package]] name = "zrml-neo-swaps" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "env_logger 0.10.1", @@ -14668,7 +14668,7 @@ dependencies = [ [[package]] name = "zrml-orderbook" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14703,7 +14703,7 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14725,7 +14725,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14777,7 +14777,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets-runtime-api" -version = "0.4.3" +version = "0.5.0" dependencies = [ "parity-scale-codec", "sp-api", @@ -14786,7 +14786,7 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arbitrary", "cfg-if", @@ -14820,7 +14820,7 @@ dependencies = [ [[package]] name = "zrml-simple-disputes" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14841,7 +14841,7 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14858,7 +14858,7 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14902,7 +14902,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.3" +version = "0.5.0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14915,7 +14915,7 @@ dependencies = [ [[package]] name = "zrml-swaps-runtime-api" -version = "0.4.3" +version = "0.5.0" dependencies = [ "parity-scale-codec", "sp-api", diff --git a/HEADER_GPL3 b/HEADER_GPL3 index 3bc5ddb7c..7bcc6e2e5 100644 --- a/HEADER_GPL3 +++ b/HEADER_GPL3 @@ -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. diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 86492935c..1f4e6ea2a 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -2,4 +2,4 @@ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-macros" -version = "0.4.3" +version = "0.5.0" diff --git a/node/Cargo.toml b/node/Cargo.toml index 59ef25dd7..37f29b324 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.3" +version = "0.5.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 4a4bca7b9..7f346a898 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -39,4 +39,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-primitives" -version = "0.4.3" +version = "0.5.0" diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 557c420bf..6031e0c83 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.3" +version = "0.5.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index de55e6b7c..7b3415104 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -100,10 +100,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 52, + spec_version: 53, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 26, + transaction_version: 27, state_version: 1, }; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 2c750dc9d..c23e89d33 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.3" +version = "0.5.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs index 28629b098..4d12a9a14 100644 --- a/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.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,7 +19,7 @@ //! Autogenerated weights for cumulus_pallet_xcmp_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `142` // Estimated: `637` - // Minimum execution time: 10_160 nanoseconds. - Weight::from_parts(10_570_000, 637) + // Minimum execution time: 9_070 nanoseconds. + Weight::from_parts(11_070_000, 637) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,8 +69,8 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `142` // Estimated: `637` - // Minimum execution time: 8_410 nanoseconds. - Weight::from_parts(10_630_000, 637) + // Minimum execution time: 9_260 nanoseconds. + Weight::from_parts(10_780_000, 637) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/frame_system.rs b/runtime/common/src/weights/frame_system.rs index 344171162..0725f4c1d 100644 --- a/runtime/common/src/weights/frame_system.rs +++ b/runtime/common/src/weights/frame_system.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,7 +19,7 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-14`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,20 +57,20 @@ impl frame_system::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_050 nanoseconds. - Weight::from_parts(68_712_625, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_640, 0).saturating_mul(b.into())) + // Minimum execution time: 11_840 nanoseconds. + Weight::from_parts(12_040_000, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(2_456, 0).saturating_mul(b.into())) } /// Storage: System Digest (r:1 w:1) /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) @@ -80,8 +80,8 @@ impl frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo frame_system::weights::WeightInfo for WeightInfo orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 82_441 nanoseconds. - Weight::from_parts(84_561_000, 7803) + // Minimum execution time: 71_681 nanoseconds. + Weight::from_parts(73_901_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -68,8 +68,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1461` // Estimated: `2607` - // Minimum execution time: 67_971 nanoseconds. - Weight::from_parts(69_580_000, 2607) + // Minimum execution time: 58_831 nanoseconds. + Weight::from_parts(59_660_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -83,8 +83,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1361` // Estimated: `7723` - // Minimum execution time: 56_770 nanoseconds. - Weight::from_parts(58_510_000, 7723) + // Minimum execution time: 49_950 nanoseconds. + Weight::from_parts(57_160_000, 7723) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -94,8 +94,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1401` // Estimated: `2607` - // Minimum execution time: 57_050 nanoseconds. - Weight::from_parts(66_200_000, 2607) + // Minimum execution time: 49_830 nanoseconds. + Weight::from_parts(67_881_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,8 +105,8 @@ impl orml_currencies::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1525` // Estimated: `2607` - // Minimum execution time: 54_111 nanoseconds. - Weight::from_parts(55_320_000, 2607) + // Minimum execution time: 47_410 nanoseconds. + Weight::from_parts(53_200_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/orml_tokens.rs b/runtime/common/src/weights/orml_tokens.rs index 28a68af8f..0e4ff524f 100644 --- a/runtime/common/src/weights/orml_tokens.rs +++ b/runtime/common/src/weights/orml_tokens.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,7 +19,7 @@ //! Autogenerated weights for orml_tokens //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,8 +57,8 @@ impl orml_tokens::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 81_270 nanoseconds. - Weight::from_parts(83_770_000, 7803) + // Minimum execution time: 80_890 nanoseconds. + Weight::from_parts(98_321_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -70,8 +70,8 @@ impl orml_tokens::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1753` // Estimated: `7803` - // Minimum execution time: 85_521 nanoseconds. - Weight::from_parts(101_970_000, 7803) + // Minimum execution time: 84_510 nanoseconds. + Weight::from_parts(86_860_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -83,8 +83,8 @@ impl orml_tokens::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1577` // Estimated: `7803` - // Minimum execution time: 65_911 nanoseconds. - Weight::from_parts(81_130_000, 7803) + // Minimum execution time: 66_190 nanoseconds. + Weight::from_parts(79_850_000, 7803) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -96,8 +96,8 @@ impl orml_tokens::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1753` // Estimated: `10410` - // Minimum execution time: 73_730 nanoseconds. - Weight::from_parts(76_910_000, 10410) + // Minimum execution time: 74_240 nanoseconds. + Weight::from_parts(79_240_000, 10410) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -111,8 +111,8 @@ impl orml_tokens::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1361` // Estimated: `7723` - // Minimum execution time: 66_870 nanoseconds. - Weight::from_parts(68_280_000, 7723) + // Minimum execution time: 56_260 nanoseconds. + Weight::from_parts(57_490_000, 7723) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/common/src/weights/pallet_author_inherent.rs b/runtime/common/src/weights/pallet_author_inherent.rs index ae904fda0..e510cdd28 100644 --- a/runtime/common/src/weights/pallet_author_inherent.rs +++ b/runtime/common/src/weights/pallet_author_inherent.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,7 +19,7 @@ //! Autogenerated weights for pallet_author_inherent //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -68,8 +68,8 @@ impl pallet_author_inherent::weights::WeightInfo for We // Proof Size summary in bytes: // Measured: `572` // Estimated: `7316` - // Minimum execution time: 45_881 nanoseconds. - Weight::from_parts(46_831_000, 7316) + // Minimum execution time: 45_440 nanoseconds. + Weight::from_parts(46_281_000, 7316) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_author_mapping.rs b/runtime/common/src/weights/pallet_author_mapping.rs index 4a90dc514..2ab08d513 100644 --- a/runtime/common/src/weights/pallet_author_mapping.rs +++ b/runtime/common/src/weights/pallet_author_mapping.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,7 +19,7 @@ //! Autogenerated weights for pallet_author_mapping //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,8 +62,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `462` // Estimated: `6006` - // Minimum execution time: 39_380 nanoseconds. - Weight::from_parts(48_610_000, 6006) + // Minimum execution time: 39_201 nanoseconds. + Weight::from_parts(47_790_000, 6006) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `407` // Estimated: `5764` - // Minimum execution time: 36_621 nanoseconds. - Weight::from_parts(38_220_000, 5764) + // Minimum execution time: 30_870 nanoseconds. + Weight::from_parts(37_440_000, 5764) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -90,8 +90,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `583` // Estimated: `6248` - // Minimum execution time: 51_020 nanoseconds. - Weight::from_parts(52_870_000, 6248) + // Minimum execution time: 50_370 nanoseconds. + Weight::from_parts(51_670_000, 6248) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -105,8 +105,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `689` // Estimated: `8935` - // Minimum execution time: 47_300 nanoseconds. - Weight::from_parts(58_340_000, 8935) + // Minimum execution time: 47_440 nanoseconds. + Weight::from_parts(58_350_000, 8935) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -118,8 +118,8 @@ impl pallet_author_mapping::weights::WeightInfo for Wei // Proof Size summary in bytes: // Measured: `513` // Estimated: `8451` - // Minimum execution time: 38_050 nanoseconds. - Weight::from_parts(45_481_000, 8451) + // Minimum execution time: 37_090 nanoseconds. + Weight::from_parts(45_430_000, 8451) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/common/src/weights/pallet_author_slot_filter.rs b/runtime/common/src/weights/pallet_author_slot_filter.rs index 55db2d0fb..cbc8e8f6c 100644 --- a/runtime/common/src/weights/pallet_author_slot_filter.rs +++ b/runtime/common/src/weights/pallet_author_slot_filter.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,7 +19,7 @@ //! Autogenerated weights for pallet_author_slot_filter //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,7 +58,7 @@ impl pallet_author_slot_filter::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_041 nanoseconds. - Weight::from_parts(13_590_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 12_991 nanoseconds. + Weight::from_parts(13_580_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/runtime/common/src/weights/pallet_balances.rs b/runtime/common/src/weights/pallet_balances.rs index c2d889284..3c6be6587 100644 --- a/runtime/common/src/weights/pallet_balances.rs +++ b/runtime/common/src/weights/pallet_balances.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,7 +19,7 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-14`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1585` // Estimated: `5214` - // Minimum execution time: 93_020 nanoseconds. - Weight::from_parts(113_901_000, 5214) + // Minimum execution time: 90_831 nanoseconds. + Weight::from_parts(92_820_000, 5214) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -69,8 +69,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1409` // Estimated: `2607` - // Minimum execution time: 60_390 nanoseconds. - Weight::from_parts(73_620_000, 2607) + // Minimum execution time: 59_480 nanoseconds. + Weight::from_parts(72_070_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,8 +80,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1559` // Estimated: `2607` - // Minimum execution time: 46_590 nanoseconds. - Weight::from_parts(57_410_000, 2607) + // Minimum execution time: 46_560 nanoseconds. + Weight::from_parts(57_250_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -91,8 +91,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1559` // Estimated: `2607` - // Minimum execution time: 52_560 nanoseconds. - Weight::from_parts(64_171_000, 2607) + // Minimum execution time: 51_850 nanoseconds. + Weight::from_parts(54_300_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -102,8 +102,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1581` // Estimated: `7821` - // Minimum execution time: 90_000 nanoseconds. - Weight::from_parts(110_571_000, 7821) + // Minimum execution time: 88_700 nanoseconds. + Weight::from_parts(107_780_000, 7821) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -113,8 +113,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1409` // Estimated: `2607` - // Minimum execution time: 70_510 nanoseconds. - Weight::from_parts(84_700_000, 2607) + // Minimum execution time: 68_470 nanoseconds. + Weight::from_parts(81_521_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -124,8 +124,8 @@ impl pallet_balances::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `1443` // Estimated: `2607` - // Minimum execution time: 45_320 nanoseconds. - Weight::from_parts(50_600_000, 2607) + // Minimum execution time: 40_810 nanoseconds. + Weight::from_parts(49_180_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_bounties.rs b/runtime/common/src/weights/pallet_bounties.rs index 599b456d3..ac27c2c9d 100644 --- a/runtime/common/src/weights/pallet_bounties.rs +++ b/runtime/common/src/weights/pallet_bounties.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,7 +19,7 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-14`, STEPS: `50`, REPEAT: `20`, 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` @@ -65,10 +65,10 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `141` // Estimated: `3106` - // Minimum execution time: 37_401 nanoseconds. - Weight::from_parts(44_892_227, 3106) - // Standard Error: 73 - .saturating_add(Weight::from_parts(1_733, 0).saturating_mul(d.into())) + // Minimum execution time: 37_250 nanoseconds. + Weight::from_parts(42_264_142, 3106) + // Standard Error: 80 + .saturating_add(Weight::from_parts(1_638, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -80,8 +80,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `229` // Estimated: `3553` - // Minimum execution time: 18_550 nanoseconds. - Weight::from_parts(23_220_000, 3553) + // Minimum execution time: 18_260 nanoseconds. + Weight::from_parts(18_980_000, 3553) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -91,8 +91,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `249` // Estimated: `2656` - // Minimum execution time: 15_770 nanoseconds. - Weight::from_parts(16_330_000, 2656) + // Minimum execution time: 14_450 nanoseconds. + Weight::from_parts(16_610_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,8 +104,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `641` // Estimated: `7870` - // Minimum execution time: 55_190 nanoseconds. - Weight::from_parts(67_611_000, 7870) + // Minimum execution time: 49_830 nanoseconds. + Weight::from_parts(57_470_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -117,8 +117,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `457` // Estimated: `5263` - // Minimum execution time: 31_291 nanoseconds. - Weight::from_parts(37_790_000, 5263) + // Minimum execution time: 31_400 nanoseconds. + Weight::from_parts(34_300_000, 5263) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -128,8 +128,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `289` // Estimated: `2656` - // Minimum execution time: 24_201 nanoseconds. - Weight::from_parts(30_110_000, 2656) + // Minimum execution time: 22_240 nanoseconds. + Weight::from_parts(25_100_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +143,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `674` // Estimated: `10477` - // Minimum execution time: 98_680 nanoseconds. - Weight::from_parts(120_110_000, 10477) + // Minimum execution time: 89_050 nanoseconds. + Weight::from_parts(93_931_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -158,8 +158,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `541` // Estimated: `7870` - // Minimum execution time: 58_310 nanoseconds. - Weight::from_parts(70_830_000, 7870) + // Minimum execution time: 52_760 nanoseconds. + Weight::from_parts(65_670_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -173,8 +173,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `818` // Estimated: `10477` - // Minimum execution time: 74_601 nanoseconds. - Weight::from_parts(89_900_000, 10477) + // Minimum execution time: 68_251 nanoseconds. + Weight::from_parts(90_760_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -184,8 +184,8 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `289` // Estimated: `2656` - // Minimum execution time: 24_050 nanoseconds. - Weight::from_parts(29_480_000, 2656) + // Minimum execution time: 22_600 nanoseconds. + Weight::from_parts(25_090_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -200,10 +200,10 @@ impl pallet_bounties::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `98 + b * (357 ±0)` // Estimated: `897 + b * (7870 ±0)` - // Minimum execution time: 6_590 nanoseconds. - Weight::from_parts(6_790_000, 897) - // Standard Error: 110_731 - .saturating_add(Weight::from_parts(48_237_374, 0).saturating_mul(b.into())) + // Minimum execution time: 6_500 nanoseconds. + Weight::from_parts(7_220_000, 897) + // Standard Error: 127_073 + .saturating_add(Weight::from_parts(48_355_191, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/runtime/common/src/weights/pallet_collective.rs b/runtime/common/src/weights/pallet_collective.rs index d29e0f58a..39ca9d87e 100644 --- a/runtime/common/src/weights/pallet_collective.rs +++ b/runtime/common/src/weights/pallet_collective.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,7 +19,7 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-14`, STEPS: `50`, REPEAT: `20`, 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` @@ -66,13 +66,13 @@ impl pallet_collective::weights::WeightInfo for WeightI fn set_members(m: u32, _n: u32, p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0 + m * (8195 ±0) + p * (3227 ±0)` - // Estimated: `33167 + m * (19751 ±60) + p * (10255 ±23)` - // Minimum execution time: 31_030 nanoseconds. - Weight::from_parts(34_031_000, 33167) - // Standard Error: 302_126 - .saturating_add(Weight::from_parts(24_404_344, 0).saturating_mul(m.into())) - // Standard Error: 118_621 - .saturating_add(Weight::from_parts(14_920_443, 0).saturating_mul(p.into())) + // Estimated: `33167 + m * (19751 ±63) + p * (10255 ±24)` + // Minimum execution time: 26_570 nanoseconds. + Weight::from_parts(29_900_000, 33167) + // Standard Error: 326_248 + .saturating_add(Weight::from_parts(25_790_752, 0).saturating_mul(m.into())) + // Standard Error: 128_092 + .saturating_add(Weight::from_parts(15_085_049, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -88,12 +88,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `100 + m * (32 ±0)` // Estimated: `596 + m * (32 ±0)` - // Minimum execution time: 25_280 nanoseconds. - Weight::from_parts(32_839_166, 596) - // Standard Error: 351 - .saturating_add(Weight::from_parts(1_690, 0).saturating_mul(b.into())) - // Standard Error: 3_619 - .saturating_add(Weight::from_parts(4_276, 0).saturating_mul(m.into())) + // Minimum execution time: 25_581 nanoseconds. + Weight::from_parts(29_243_977, 596) + // Standard Error: 389 + .saturating_add(Weight::from_parts(2_599, 0).saturating_mul(b.into())) + // Standard Error: 4_014 + .saturating_add(Weight::from_parts(18_848, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } @@ -107,12 +107,10 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `100 + m * (32 ±0)` // Estimated: `3172 + m * (64 ±0)` - // Minimum execution time: 31_490 nanoseconds. - Weight::from_parts(31_614_460, 3172) - // Standard Error: 648 - .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(b.into())) - // Standard Error: 6_684 - .saturating_add(Weight::from_parts(82_588, 0).saturating_mul(m.into())) + // Minimum execution time: 29_510 nanoseconds. + Weight::from_parts(39_239_765, 3172) + // Standard Error: 624 + .saturating_add(Weight::from_parts(2_489, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } @@ -133,14 +131,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `565 + m * (32 ±0) + p * (33 ±0)` // Estimated: `6570 + m * (160 ±0) + p * (170 ±0)` - // Minimum execution time: 43_310 nanoseconds. - Weight::from_parts(37_615_320, 6570) - // Standard Error: 862 - .saturating_add(Weight::from_parts(15_233, 0).saturating_mul(b.into())) - // Standard Error: 9_003 - .saturating_add(Weight::from_parts(21_795, 0).saturating_mul(m.into())) - // Standard Error: 3_466 - .saturating_add(Weight::from_parts(196_387, 0).saturating_mul(p.into())) + // Minimum execution time: 38_280 nanoseconds. + Weight::from_parts(34_499_820, 6570) + // Standard Error: 843 + .saturating_add(Weight::from_parts(9_755, 0).saturating_mul(b.into())) + // Standard Error: 8_797 + .saturating_add(Weight::from_parts(24_088, 0).saturating_mul(m.into())) + // Standard Error: 3_386 + .saturating_add(Weight::from_parts(192_506, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 160).saturating_mul(m.into())) @@ -155,8 +153,10 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `1240 + m * (64 ±0)` // Estimated: `5448 + m * (128 ±0)` - // Minimum execution time: 40_050 nanoseconds. - Weight::from_parts(53_695_231, 5448) + // Minimum execution time: 38_090 nanoseconds. + Weight::from_parts(45_241_332, 5448) + // Standard Error: 6_225 + .saturating_add(Weight::from_parts(123_734, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 128).saturating_mul(m.into())) @@ -175,12 +175,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `683 + m * (64 ±0) + p * (33 ±0)` // Estimated: `6017 + m * (260 ±0) + p * (136 ±0)` - // Minimum execution time: 40_470 nanoseconds. - Weight::from_parts(46_112_135, 6017) - // Standard Error: 10_933 - .saturating_add(Weight::from_parts(78_433, 0).saturating_mul(m.into())) - // Standard Error: 4_155 - .saturating_add(Weight::from_parts(161_800, 0).saturating_mul(p.into())) + // Minimum execution time: 40_550 nanoseconds. + Weight::from_parts(48_542_021, 6017) + // Standard Error: 8_566 + .saturating_add(Weight::from_parts(3_180, 0).saturating_mul(m.into())) + // Standard Error: 3_255 + .saturating_add(Weight::from_parts(145_858, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 260).saturating_mul(m.into())) @@ -201,14 +201,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `926 + b * (1 ±0) + m * (64 ±0) + p * (36 ±0)` // Estimated: `9916 + b * (4 ±0) + m * (248 ±0) + p * (144 ±0)` - // Minimum execution time: 56_840 nanoseconds. - Weight::from_parts(66_416_383, 9916) - // Standard Error: 1_152 - .saturating_add(Weight::from_parts(662, 0).saturating_mul(b.into())) - // Standard Error: 12_177 - .saturating_add(Weight::from_parts(21_417, 0).saturating_mul(m.into())) - // Standard Error: 4_628 - .saturating_add(Weight::from_parts(223_068, 0).saturating_mul(p.into())) + // Minimum execution time: 56_730 nanoseconds. + Weight::from_parts(60_995_866, 9916) + // Standard Error: 1_124 + .saturating_add(Weight::from_parts(7_475, 0).saturating_mul(b.into())) + // Standard Error: 11_882 + .saturating_add(Weight::from_parts(24_932, 0).saturating_mul(m.into())) + // Standard Error: 4_516 + .saturating_add(Weight::from_parts(202_498, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 4).saturating_mul(b.into())) @@ -231,12 +231,12 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `703 + m * (64 ±0) + p * (33 ±0)` // Estimated: `7250 + m * (325 ±0) + p * (170 ±0)` - // Minimum execution time: 44_370 nanoseconds. - Weight::from_parts(53_029_434, 7250) - // Standard Error: 10_566 - .saturating_add(Weight::from_parts(62_376, 0).saturating_mul(m.into())) - // Standard Error: 4_015 - .saturating_add(Weight::from_parts(159_872, 0).saturating_mul(p.into())) + // Minimum execution time: 44_580 nanoseconds. + Weight::from_parts(42_251_455, 7250) + // Standard Error: 11_312 + .saturating_add(Weight::from_parts(79_601, 0).saturating_mul(m.into())) + // Standard Error: 4_299 + .saturating_add(Weight::from_parts(177_260, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 325).saturating_mul(m.into())) @@ -259,14 +259,14 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `946 + b * (1 ±0) + m * (64 ±0) + p * (36 ±0)` // Estimated: `11505 + b * (5 ±0) + m * (310 ±0) + p * (180 ±0)` - // Minimum execution time: 66_090 nanoseconds. - Weight::from_parts(66_646_932, 11505) - // Standard Error: 1_083 - .saturating_add(Weight::from_parts(11_363, 0).saturating_mul(b.into())) - // Standard Error: 11_446 - .saturating_add(Weight::from_parts(56_961, 0).saturating_mul(m.into())) - // Standard Error: 4_350 - .saturating_add(Weight::from_parts(223_823, 0).saturating_mul(p.into())) + // Minimum execution time: 61_120 nanoseconds. + Weight::from_parts(61_360_635, 11505) + // Standard Error: 1_164 + .saturating_add(Weight::from_parts(10_168, 0).saturating_mul(b.into())) + // Standard Error: 12_309 + .saturating_add(Weight::from_parts(31_252, 0).saturating_mul(m.into())) + // Standard Error: 4_678 + .saturating_add(Weight::from_parts(211_197, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 5).saturating_mul(b.into())) @@ -284,10 +284,10 @@ impl pallet_collective::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `258 + p * (32 ±0)` // Estimated: `1266 + p * (96 ±0)` - // Minimum execution time: 23_990 nanoseconds. - Weight::from_parts(29_777_910, 1266) - // Standard Error: 3_885 - .saturating_add(Weight::from_parts(172_382, 0).saturating_mul(p.into())) + // Minimum execution time: 24_060 nanoseconds. + Weight::from_parts(31_125_083, 1266) + // Standard Error: 2_857 + .saturating_add(Weight::from_parts(122_760, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 96).saturating_mul(p.into())) diff --git a/runtime/common/src/weights/pallet_contracts.rs b/runtime/common/src/weights/pallet_contracts.rs index 642253dbb..685b6e73e 100644 --- a/runtime/common/src/weights/pallet_contracts.rs +++ b/runtime/common/src/weights/pallet_contracts.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,7 +19,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `42` // Estimated: `0` - // Minimum execution time: 6_390 nanoseconds. - Weight::from_parts(6_800_000, 0).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 5_220 nanoseconds. + Weight::from_parts(5_660_000, 0).saturating_add(T::DbWeight::get().reads(1)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) @@ -68,10 +68,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `414 + k * (69 ±0)` // Estimated: `0` - // Minimum execution time: 16_110 nanoseconds. - Weight::from_parts(17_498_174, 0) - // Standard Error: 8_764 - .saturating_add(Weight::from_parts(1_845_920, 0).saturating_mul(k.into())) + // Minimum execution time: 16_010 nanoseconds. + Weight::from_parts(23_064_332, 0) + // Standard Error: 10_096 + .saturating_add(Weight::from_parts(1_760_195, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -83,10 +83,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `214 + q * (33 ±0)` // Estimated: `0` - // Minimum execution time: 5_320 nanoseconds. - Weight::from_parts(17_768_678, 0) - // Standard Error: 16_408 - .saturating_add(Weight::from_parts(2_245_997, 0).saturating_mul(q.into())) + // Minimum execution time: 6_150 nanoseconds. + Weight::from_parts(20_444_138, 0) + // Standard Error: 16_865 + .saturating_add(Weight::from_parts(2_019_484, 0).saturating_mul(q.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -99,10 +99,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `203 + c * (1 ±0)` // Estimated: `0` - // Minimum execution time: 61_031 nanoseconds. - Weight::from_parts(96_901_594, 0) - // Standard Error: 496 - .saturating_add(Weight::from_parts(104_553, 0).saturating_mul(c.into())) + // Minimum execution time: 49_871 nanoseconds. + Weight::from_parts(72_979_979, 0) + // Standard Error: 535 + .saturating_add(Weight::from_parts(96_053, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -121,10 +121,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `675` // Estimated: `0` - // Minimum execution time: 548_392 nanoseconds. - Weight::from_parts(731_430_648, 0) - // Standard Error: 274 - .saturating_add(Weight::from_parts(59_880, 0).saturating_mul(c.into())) + // Minimum execution time: 386_581 nanoseconds. + Weight::from_parts(483_335_740, 0) + // Standard Error: 205 + .saturating_add(Weight::from_parts(57_256, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -151,14 +151,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `157` // Estimated: `0` - // Minimum execution time: 6_948_252 nanoseconds. - Weight::from_parts(842_384_814, 0) - // Standard Error: 832 - .saturating_add(Weight::from_parts(181_029, 0).saturating_mul(c.into())) - // Standard Error: 49 - .saturating_add(Weight::from_parts(2_922, 0).saturating_mul(i.into())) - // Standard Error: 49 - .saturating_add(Weight::from_parts(3_510, 0).saturating_mul(s.into())) + // Minimum execution time: 6_121_492 nanoseconds. + Weight::from_parts(689_215_210, 0) + // Standard Error: 704 + .saturating_add(Weight::from_parts(177_888, 0).saturating_mul(c.into())) + // Standard Error: 41 + .saturating_add(Weight::from_parts(2_798, 0).saturating_mul(i.into())) + // Standard Error: 41 + .saturating_add(Weight::from_parts(2_894, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(9)) } @@ -182,12 +182,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `433` // Estimated: `0` - // Minimum execution time: 2_540_808 nanoseconds. - Weight::from_parts(595_650_884, 0) - // Standard Error: 48 - .saturating_add(Weight::from_parts(2_130, 0).saturating_mul(i.into())) - // Standard Error: 48 - .saturating_add(Weight::from_parts(3_126, 0).saturating_mul(s.into())) + // Minimum execution time: 2_331_815 nanoseconds. + Weight::from_parts(510_378_005, 0) + // Standard Error: 42 + .saturating_add(Weight::from_parts(1_952, 0).saturating_mul(i.into())) + // Standard Error: 42 + .saturating_add(Weight::from_parts(2_981, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -205,8 +205,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `727` // Estimated: `0` - // Minimum execution time: 228_391 nanoseconds. - Weight::from_parts(278_201_000, 0) + // Minimum execution time: 228_951 nanoseconds. + Weight::from_parts(268_301_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -223,10 +223,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `42` // Estimated: `0` - // Minimum execution time: 602_562 nanoseconds. - Weight::from_parts(697_000_805, 0) - // Standard Error: 1_063 - .saturating_add(Weight::from_parts(188_416, 0).saturating_mul(c.into())) + // Minimum execution time: 447_811 nanoseconds. + Weight::from_parts(526_447_412, 0) + // Standard Error: 835 + .saturating_add(Weight::from_parts(175_016, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -242,8 +242,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `218` // Estimated: `0` - // Minimum execution time: 37_290 nanoseconds. - Weight::from_parts(45_580_000, 0) + // Minimum execution time: 40_410 nanoseconds. + Weight::from_parts(49_250_000, 0) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -257,8 +257,8 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `567` // Estimated: `0` - // Minimum execution time: 42_671 nanoseconds. - Weight::from_parts(51_910_000, 0) + // Minimum execution time: 47_480 nanoseconds. + Weight::from_parts(57_290_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -277,10 +277,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `697 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 535_552 nanoseconds. - Weight::from_parts(684_787_809, 0) - // Standard Error: 678_512 - .saturating_add(Weight::from_parts(40_079_220, 0).saturating_mul(r.into())) + // Minimum execution time: 372_390 nanoseconds. + Weight::from_parts(476_865_760, 0) + // Standard Error: 744_335 + .saturating_add(Weight::from_parts(36_409_937, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -299,10 +299,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `749 + r * (19218 ±0)` // Estimated: `0` - // Minimum execution time: 536_032 nanoseconds. - Weight::from_parts(483_493_910, 0) - // Standard Error: 1_504_096 - .saturating_add(Weight::from_parts(458_565_900, 0).saturating_mul(r.into())) + // Minimum execution time: 372_401 nanoseconds. + Weight::from_parts(140_531_070, 0) + // Standard Error: 1_865_123 + .saturating_add(Weight::from_parts(462_201_901, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -322,10 +322,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `741 + r * (19539 ±0)` // Estimated: `0` - // Minimum execution time: 539_622 nanoseconds. - Weight::from_parts(404_765_458, 0) - // Standard Error: 1_736_557 - .saturating_add(Weight::from_parts(546_753_159, 0).saturating_mul(r.into())) + // Minimum execution time: 375_671 nanoseconds. + Weight::from_parts(233_905_041, 0) + // Standard Error: 1_698_368 + .saturating_add(Weight::from_parts(538_201_180, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -345,10 +345,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `704 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 537_862 nanoseconds. - Weight::from_parts(702_633_145, 0) - // Standard Error: 668_343 - .saturating_add(Weight::from_parts(41_612_969, 0).saturating_mul(r.into())) + // Minimum execution time: 376_331 nanoseconds. + Weight::from_parts(454_664_062, 0) + // Standard Error: 461_840 + .saturating_add(Weight::from_parts(45_194_203, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -367,10 +367,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 533_112 nanoseconds. - Weight::from_parts(653_370_992, 0) - // Standard Error: 440_087 - .saturating_add(Weight::from_parts(22_783_189, 0).saturating_mul(r.into())) + // Minimum execution time: 372_641 nanoseconds. + Weight::from_parts(451_741_155, 0) + // Standard Error: 337_133 + .saturating_add(Weight::from_parts(22_608_395, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -389,10 +389,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `698 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_612 nanoseconds. - Weight::from_parts(679_142_053, 0) - // Standard Error: 471_149 - .saturating_add(Weight::from_parts(35_590_449, 0).saturating_mul(r.into())) + // Minimum execution time: 373_390 nanoseconds. + Weight::from_parts(455_581_915, 0) + // Standard Error: 351_337 + .saturating_add(Weight::from_parts(38_061_966, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -411,10 +411,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `699 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_341 nanoseconds. - Weight::from_parts(650_082_231, 0) - // Standard Error: 500_514 - .saturating_add(Weight::from_parts(37_595_102, 0).saturating_mul(r.into())) + // Minimum execution time: 373_951 nanoseconds. + Weight::from_parts(436_724_141, 0) + // Standard Error: 526_654 + .saturating_add(Weight::from_parts(39_277_146, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -433,10 +433,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `873 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_442 nanoseconds. - Weight::from_parts(671_569_610, 0) - // Standard Error: 834_847 - .saturating_add(Weight::from_parts(171_590_994, 0).saturating_mul(r.into())) + // Minimum execution time: 373_040 nanoseconds. + Weight::from_parts(510_048_107, 0) + // Standard Error: 1_188_911 + .saturating_add(Weight::from_parts(164_874_498, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -455,10 +455,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `708 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 532_762 nanoseconds. - Weight::from_parts(657_616_211, 0) - // Standard Error: 899_157 - .saturating_add(Weight::from_parts(37_407_507, 0).saturating_mul(r.into())) + // Minimum execution time: 370_661 nanoseconds. + Weight::from_parts(464_743_072, 0) + // Standard Error: 389_720 + .saturating_add(Weight::from_parts(34_707_919, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -477,10 +477,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `706 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_672 nanoseconds. - Weight::from_parts(666_305_025, 0) - // Standard Error: 674_283 - .saturating_add(Weight::from_parts(37_129_240, 0).saturating_mul(r.into())) + // Minimum execution time: 373_821 nanoseconds. + Weight::from_parts(457_544_246, 0) + // Standard Error: 342_484 + .saturating_add(Weight::from_parts(37_066_167, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -499,10 +499,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `703 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_661 nanoseconds. - Weight::from_parts(657_489_104, 0) - // Standard Error: 521_523 - .saturating_add(Weight::from_parts(36_933_388, 0).saturating_mul(r.into())) + // Minimum execution time: 372_531 nanoseconds. + Weight::from_parts(461_517_147, 0) + // Standard Error: 423_642 + .saturating_add(Weight::from_parts(37_619_649, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -521,10 +521,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 535_102 nanoseconds. - Weight::from_parts(676_467_296, 0) - // Standard Error: 600_497 - .saturating_add(Weight::from_parts(35_420_595, 0).saturating_mul(r.into())) + // Minimum execution time: 373_951 nanoseconds. + Weight::from_parts(471_348_887, 0) + // Standard Error: 461_267 + .saturating_add(Weight::from_parts(35_572_043, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -545,10 +545,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `809 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 535_841 nanoseconds. - Weight::from_parts(683_857_279, 0) - // Standard Error: 715_815 - .saturating_add(Weight::from_parts(160_158_895, 0).saturating_mul(r.into())) + // Minimum execution time: 373_160 nanoseconds. + Weight::from_parts(476_375_827, 0) + // Standard Error: 703_470 + .saturating_add(Weight::from_parts(159_986_083, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -567,10 +567,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `661 + r * (320 ±0)` // Estimated: `0` - // Minimum execution time: 195_350 nanoseconds. - Weight::from_parts(235_736_945, 0) - // Standard Error: 237_408 - .saturating_add(Weight::from_parts(17_693_780, 0).saturating_mul(r.into())) + // Minimum execution time: 196_551 nanoseconds. + Weight::from_parts(236_901_120, 0) + // Standard Error: 246_159 + .saturating_add(Weight::from_parts(16_806_917, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -589,10 +589,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `696 + r * (480 ±0)` // Estimated: `0` - // Minimum execution time: 534_762 nanoseconds. - Weight::from_parts(645_918_150, 0) - // Standard Error: 738_459 - .saturating_add(Weight::from_parts(35_776_568, 0).saturating_mul(r.into())) + // Minimum execution time: 373_561 nanoseconds. + Weight::from_parts(456_913_040, 0) + // Standard Error: 381_825 + .saturating_add(Weight::from_parts(32_528_974, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -611,10 +611,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1176` // Estimated: `0` - // Minimum execution time: 568_112 nanoseconds. - Weight::from_parts(839_531_613, 0) - // Standard Error: 50_315 - .saturating_add(Weight::from_parts(12_273_119, 0).saturating_mul(n.into())) + // Minimum execution time: 405_761 nanoseconds. + Weight::from_parts(469_301_224, 0) + // Standard Error: 28_817 + .saturating_add(Weight::from_parts(12_130_953, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -629,12 +629,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_return(_r: u32) -> Weight { + fn seal_return(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `684 + r * (8 ±0)` // Estimated: `0` - // Minimum execution time: 530_752 nanoseconds. - Weight::from_parts(664_866_575, 0) + // Minimum execution time: 368_561 nanoseconds. + Weight::from_parts(451_273_430, 0) + // Standard Error: 6_515_716 + .saturating_add(Weight::from_parts(482_669, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -653,10 +655,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694` // Estimated: `0` - // Minimum execution time: 533_101 nanoseconds. - Weight::from_parts(626_179_160, 0) - // Standard Error: 10_080 - .saturating_add(Weight::from_parts(389_208, 0).saturating_mul(n.into())) + // Minimum execution time: 370_231 nanoseconds. + Weight::from_parts(465_472_667, 0) + // Standard Error: 15_743 + .saturating_add(Weight::from_parts(253_489, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -679,10 +681,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `726 + r * (285 ±0)` // Estimated: `0` - // Minimum execution time: 533_922 nanoseconds. - Weight::from_parts(662_814_704, 0) - // Standard Error: 10_208_426 - .saturating_add(Weight::from_parts(65_759_495, 0).saturating_mul(r.into())) + // Minimum execution time: 372_101 nanoseconds. + Weight::from_parts(448_849_726, 0) + // Standard Error: 5_514_552 + .saturating_add(Weight::from_parts(93_040_573, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -705,10 +707,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `775 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 534_572 nanoseconds. - Weight::from_parts(647_025_351, 0) - // Standard Error: 1_159_894 - .saturating_add(Weight::from_parts(207_568_972, 0).saturating_mul(r.into())) + // Minimum execution time: 373_251 nanoseconds. + Weight::from_parts(459_699_605, 0) + // Standard Error: 727_496 + .saturating_add(Weight::from_parts(205_088_259, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -727,10 +729,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `694 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 529_741 nanoseconds. - Weight::from_parts(613_973_354, 0) - // Standard Error: 984_670 - .saturating_add(Weight::from_parts(405_101_002, 0).saturating_mul(r.into())) + // Minimum execution time: 370_051 nanoseconds. + Weight::from_parts(438_086_622, 0) + // Standard Error: 790_178 + .saturating_add(Weight::from_parts(397_599_297, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -750,12 +752,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1630 + t * (2608 ±0) + n * (8 ±0)` // Estimated: `0` - // Minimum execution time: 1_973_097 nanoseconds. - Weight::from_parts(946_904_790, 0) - // Standard Error: 3_997_989 - .saturating_add(Weight::from_parts(347_080_943, 0).saturating_mul(t.into())) - // Standard Error: 1_098_041 - .saturating_add(Weight::from_parts(124_035_590, 0).saturating_mul(n.into())) + // Minimum execution time: 1_802_725 nanoseconds. + Weight::from_parts(852_897_158, 0) + // Standard Error: 3_127_556 + .saturating_add(Weight::from_parts(302_274_453, 0).saturating_mul(t.into())) + // Standard Error: 858_978 + .saturating_add(Weight::from_parts(106_450_863, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -776,10 +778,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (800 ±0)` // Estimated: `0` - // Minimum execution time: 223_441 nanoseconds. - Weight::from_parts(290_935_537, 0) - // Standard Error: 241_403 - .saturating_add(Weight::from_parts(30_440_276, 0).saturating_mul(r.into())) + // Minimum execution time: 211_790 nanoseconds. + Weight::from_parts(253_046_166, 0) + // Standard Error: 233_833 + .saturating_add(Weight::from_parts(26_380_907, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -790,10 +792,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `720 + r * (23420 ±0)` // Estimated: `0` - // Minimum execution time: 533_902 nanoseconds. - Weight::from_parts(577_630_485, 0) - // Standard Error: 2_408_816 - .saturating_add(Weight::from_parts(821_568_409, 0).saturating_mul(r.into())) + // Minimum execution time: 373_161 nanoseconds. + Weight::from_parts(342_179_957, 0) + // Standard Error: 2_706_339 + .saturating_add(Weight::from_parts(785_547_282, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -806,10 +808,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `12402 + n * (12006 ±0)` // Estimated: `0` - // Minimum execution time: 758_442 nanoseconds. - Weight::from_parts(1_221_798_222, 0) - // Standard Error: 3_439_887 - .saturating_add(Weight::from_parts(184_887_572, 0).saturating_mul(n.into())) + // Minimum execution time: 595_541 nanoseconds. + Weight::from_parts(1_021_154_936, 0) + // Standard Error: 3_184_757 + .saturating_add(Weight::from_parts(160_421_420, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(52)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(50)) @@ -822,10 +824,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14990 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 756_943 nanoseconds. - Weight::from_parts(1_236_797_439, 0) - // Standard Error: 3_349_250 - .saturating_add(Weight::from_parts(106_340_610, 0).saturating_mul(n.into())) + // Minimum execution time: 593_591 nanoseconds. + Weight::from_parts(963_969_421, 0) + // Standard Error: 2_784_028 + .saturating_add(Weight::from_parts(120_308_782, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(49)) @@ -838,10 +840,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `720 + r * (23100 ±0)` // Estimated: `0` - // Minimum execution time: 536_812 nanoseconds. - Weight::from_parts(544_422_299, 0) - // Standard Error: 3_373_768 - .saturating_add(Weight::from_parts(809_752_042, 0).saturating_mul(r.into())) + // Minimum execution time: 376_051 nanoseconds. + Weight::from_parts(333_556_076, 0) + // Standard Error: 2_485_494 + .saturating_add(Weight::from_parts(765_161_529, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -854,10 +856,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14670 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 704_063 nanoseconds. - Weight::from_parts(1_168_926_033, 0) - // Standard Error: 3_012_712 - .saturating_add(Weight::from_parts(113_252_251, 0).saturating_mul(n.into())) + // Minimum execution time: 543_432 nanoseconds. + Weight::from_parts(934_552_910, 0) + // Standard Error: 2_935_698 + .saturating_add(Weight::from_parts(120_632_071, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -870,10 +872,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `730 + r * (23740 ±0)` // Estimated: `0` - // Minimum execution time: 540_782 nanoseconds. - Weight::from_parts(604_103_979, 0) - // Standard Error: 2_452_163 - .saturating_add(Weight::from_parts(659_347_493, 0).saturating_mul(r.into())) + // Minimum execution time: 374_881 nanoseconds. + Weight::from_parts(376_682_471, 0) + // Standard Error: 2_163_407 + .saturating_add(Weight::from_parts(645_678_089, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -885,10 +887,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `15321 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 678_092 nanoseconds. - Weight::from_parts(1_155_612_178, 0) - // Standard Error: 3_624_142 - .saturating_add(Weight::from_parts(278_889_523, 0).saturating_mul(n.into())) + // Minimum execution time: 514_911 nanoseconds. + Weight::from_parts(901_753_961, 0) + // Standard Error: 3_073_152 + .saturating_add(Weight::from_parts(268_092_838, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -900,10 +902,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `723 + r * (23100 ±0)` // Estimated: `0` - // Minimum execution time: 537_101 nanoseconds. - Weight::from_parts(554_645_766, 0) - // Standard Error: 2_641_073 - .saturating_add(Weight::from_parts(632_807_668, 0).saturating_mul(r.into())) + // Minimum execution time: 375_781 nanoseconds. + Weight::from_parts(376_656_280, 0) + // Standard Error: 1_745_890 + .saturating_add(Weight::from_parts(609_711_562, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -915,10 +917,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `14673 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 665_912 nanoseconds. - Weight::from_parts(1_040_953_721, 0) - // Standard Error: 2_696_149 - .saturating_add(Weight::from_parts(106_678_921, 0).saturating_mul(n.into())) + // Minimum execution time: 505_481 nanoseconds. + Weight::from_parts(827_166_600, 0) + // Standard Error: 2_292_810 + .saturating_add(Weight::from_parts(114_430_176, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -930,10 +932,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `731 + r * (23740 ±0)` // Estimated: `0` - // Minimum execution time: 538_482 nanoseconds. - Weight::from_parts(422_000_029, 0) - // Standard Error: 3_577_317 - .saturating_add(Weight::from_parts(856_722_439, 0).saturating_mul(r.into())) + // Minimum execution time: 375_801 nanoseconds. + Weight::from_parts(345_672_663, 0) + // Standard Error: 2_690_704 + .saturating_add(Weight::from_parts(811_617_030, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -946,10 +948,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `15322 + n * (175775 ±0)` // Estimated: `0` - // Minimum execution time: 713_993 nanoseconds. - Weight::from_parts(1_267_575_312, 0) - // Standard Error: 3_924_466 - .saturating_add(Weight::from_parts(272_437_447, 0).saturating_mul(n.into())) + // Minimum execution time: 549_851 nanoseconds. + Weight::from_parts(953_956_973, 0) + // Standard Error: 3_160_926 + .saturating_add(Weight::from_parts(287_644_412, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(51)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(48)) @@ -970,10 +972,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1322 + r * (3601 ±0)` // Estimated: `0` - // Minimum execution time: 537_791 nanoseconds. - Weight::from_parts(617_094_789, 0) - // Standard Error: 2_893_420 - .saturating_add(Weight::from_parts(2_340_438_170, 0).saturating_mul(r.into())) + // Minimum execution time: 376_691 nanoseconds. + Weight::from_parts(395_809_124, 0) + // Standard Error: 2_965_424 + .saturating_add(Weight::from_parts(2_320_023_998, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -994,10 +996,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `948 + r * (20495 ±0)` // Estimated: `0` - // Minimum execution time: 538_052 nanoseconds. - Weight::from_parts(543_252_000, 0) - // Standard Error: 39_197_014 - .saturating_add(Weight::from_parts(50_755_358_343, 0).saturating_mul(r.into())) + // Minimum execution time: 376_431 nanoseconds. + Weight::from_parts(381_331_000, 0) + // Standard Error: 29_375_102 + .saturating_add(Weight::from_parts(33_644_863_948, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().reads((160_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1018,10 +1020,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0 + r * (71670 ±0)` // Estimated: `0` - // Minimum execution time: 548_332 nanoseconds. - Weight::from_parts(664_512_000, 0) - // Standard Error: 38_649_522 - .saturating_add(Weight::from_parts(49_980_161_132, 0).saturating_mul(r.into())) + // Minimum execution time: 378_351 nanoseconds. + Weight::from_parts(433_201_000, 0) + // Standard Error: 22_581_471 + .saturating_add(Weight::from_parts(33_215_908_203, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((150_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1043,12 +1045,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `21128 + t * (15897 ±0)` // Estimated: `0` - // Minimum execution time: 16_839_323 nanoseconds. - Weight::from_parts(16_275_890_867, 0) - // Standard Error: 71_746_414 - .saturating_add(Weight::from_parts(2_118_955_274, 0).saturating_mul(t.into())) - // Standard Error: 107_579 - .saturating_add(Weight::from_parts(13_869_639, 0).saturating_mul(c.into())) + // Minimum execution time: 17_107_858 nanoseconds. + Weight::from_parts(16_043_302_144, 0) + // Standard Error: 58_116_472 + .saturating_add(Weight::from_parts(1_846_422_945, 0).saturating_mul(t.into())) + // Standard Error: 87_142 + .saturating_add(Weight::from_parts(13_152_589, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(167)) .saturating_add(T::DbWeight::get().reads((81_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(163)) @@ -1073,10 +1075,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1512 + r * (25573 ±0)` // Estimated: `0` - // Minimum execution time: 543_001 nanoseconds. - Weight::from_parts(593_271_000, 0) - // Standard Error: 75_051_630 - .saturating_add(Weight::from_parts(61_709_863_004, 0).saturating_mul(r.into())) + // Minimum execution time: 381_671 nanoseconds. + Weight::from_parts(443_361_000, 0) + // Standard Error: 56_475_539 + .saturating_add(Weight::from_parts(43_697_686_582, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().reads((400_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -1103,14 +1105,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `5505 + t * (68 ±0)` // Estimated: `0` - // Minimum execution time: 198_323_873 nanoseconds. - Weight::from_parts(8_482_437_089, 0) - // Standard Error: 473_750_492 - .saturating_add(Weight::from_parts(1_198_807_215, 0).saturating_mul(t.into())) - // Standard Error: 772_554 - .saturating_add(Weight::from_parts(213_368_181, 0).saturating_mul(i.into())) - // Standard Error: 772_554 - .saturating_add(Weight::from_parts(211_682_191, 0).saturating_mul(s.into())) + // Minimum execution time: 189_683_742 nanoseconds. + Weight::from_parts(191_893_498_000, 0) + // Standard Error: 1_450_448 + .saturating_add(Weight::from_parts(109_556_339, 0).saturating_mul(i.into())) + // Standard Error: 1_450_448 + .saturating_add(Weight::from_parts(105_093_613, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(249)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(247)) @@ -1127,12 +1127,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_hash_sha2_256(_r: u32) -> Weight { + fn seal_hash_sha2_256(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `691 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 534_132 nanoseconds. - Weight::from_parts(676_132_087, 0) + // Minimum execution time: 370_911 nanoseconds. + Weight::from_parts(454_911_953, 0) + // Standard Error: 6_307_367 + .saturating_add(Weight::from_parts(33_770_346, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1151,10 +1153,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1493` // Estimated: `0` - // Minimum execution time: 583_172 nanoseconds. - Weight::from_parts(654_967_242, 0) - // Standard Error: 193_658 - .saturating_add(Weight::from_parts(91_163_648, 0).saturating_mul(n.into())) + // Minimum execution time: 419_851 nanoseconds. + Weight::from_parts(563_700_369, 0) + // Standard Error: 86_058 + .saturating_add(Weight::from_parts(79_816_436, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1173,10 +1175,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 532_132 nanoseconds. - Weight::from_parts(677_504_183, 0) - // Standard Error: 11_506_745 - .saturating_add(Weight::from_parts(100_381_316, 0).saturating_mul(r.into())) + // Minimum execution time: 370_391 nanoseconds. + Weight::from_parts(443_662_740, 0) + // Standard Error: 6_359_305 + .saturating_add(Weight::from_parts(123_904_859, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1195,10 +1197,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 620_762 nanoseconds. - Weight::from_parts(208_832_043, 0) - // Standard Error: 638_611 - .saturating_add(Weight::from_parts(356_290_502, 0).saturating_mul(n.into())) + // Minimum execution time: 453_621 nanoseconds. + Weight::from_parts(510_883_151, 0) + // Standard Error: 328_339 + .saturating_add(Weight::from_parts(334_185_553, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1217,10 +1219,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 531_411 nanoseconds. - Weight::from_parts(663_543_053, 0) - // Standard Error: 11_568_076 - .saturating_add(Weight::from_parts(47_810_346, 0).saturating_mul(r.into())) + // Minimum execution time: 369_611 nanoseconds. + Weight::from_parts(447_081_130, 0) + // Standard Error: 6_014_639 + .saturating_add(Weight::from_parts(707_866_169, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1239,10 +1241,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 588_752 nanoseconds. - Weight::from_parts(614_542_000, 0) - // Standard Error: 177_639 - .saturating_add(Weight::from_parts(161_643_433, 0).saturating_mul(n.into())) + // Minimum execution time: 535_971 nanoseconds. + Weight::from_parts(963_976_677, 0) + // Standard Error: 185_438 + .saturating_add(Weight::from_parts(145_209_174, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1257,12 +1259,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:2 w:2) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_hash_blake2_128(_r: u32) -> Weight { + fn seal_hash_blake2_128(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `693 + r * (642 ±0)` // Estimated: `0` - // Minimum execution time: 530_851 nanoseconds. - Weight::from_parts(667_968_451, 0) + // Minimum execution time: 369_141 nanoseconds. + Weight::from_parts(450_974_714, 0) + // Standard Error: 6_233_732 + .saturating_add(Weight::from_parts(103_403_485, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1281,10 +1285,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1495` // Estimated: `0` - // Minimum execution time: 585_602 nanoseconds. - Weight::from_parts(658_555_116, 0) - // Standard Error: 279_324 - .saturating_add(Weight::from_parts(159_288_859, 0).saturating_mul(n.into())) + // Minimum execution time: 425_391 nanoseconds. + Weight::from_parts(535_996_920, 0) + // Standard Error: 128_427 + .saturating_add(Weight::from_parts(145_977_335, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1303,10 +1307,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `737 + r * (6083 ±0)` // Estimated: `0` - // Minimum execution time: 535_382 nanoseconds. - Weight::from_parts(670_345_814, 0) - // Standard Error: 12_790_892 - .saturating_add(Weight::from_parts(5_618_499_485, 0).saturating_mul(r.into())) + // Minimum execution time: 374_071 nanoseconds. + Weight::from_parts(448_879_583, 0) + // Standard Error: 6_289_470 + .saturating_add(Weight::from_parts(5_364_455_516, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1325,10 +1329,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `706 + r * (3362 ±0)` // Estimated: `0` - // Minimum execution time: 534_352 nanoseconds. - Weight::from_parts(655_424_953, 0) - // Standard Error: 10_053_361 - .saturating_add(Weight::from_parts(1_196_174_946, 0).saturating_mul(r.into())) + // Minimum execution time: 373_781 nanoseconds. + Weight::from_parts(465_446_085, 0) + // Standard Error: 8_340_917 + .saturating_add(Weight::from_parts(1_305_631_914, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1349,10 +1353,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0 + r * (79300 ±0)` // Estimated: `0` - // Minimum execution time: 535_492 nanoseconds. - Weight::from_parts(570_292_000, 0) - // Standard Error: 9_317_085 - .saturating_add(Weight::from_parts(2_915_076_582, 0).saturating_mul(r.into())) + // Minimum execution time: 374_451 nanoseconds. + Weight::from_parts(437_481_000, 0) + // Standard Error: 9_632_424 + .saturating_add(Weight::from_parts(2_866_065_318, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((225_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -1373,10 +1377,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `689 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 537_632 nanoseconds. - Weight::from_parts(657_899_518, 0) - // Standard Error: 481_203 - .saturating_add(Weight::from_parts(22_629_133, 0).saturating_mul(r.into())) + // Minimum execution time: 376_041 nanoseconds. + Weight::from_parts(455_809_468, 0) + // Standard Error: 314_547 + .saturating_add(Weight::from_parts(22_290_995, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1395,10 +1399,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `1387 + r * (3140 ±0)` // Estimated: `0` - // Minimum execution time: 539_212 nanoseconds. - Weight::from_parts(734_683_366, 0) - // Standard Error: 499_077 - .saturating_add(Weight::from_parts(34_230_055, 0).saturating_mul(r.into())) + // Minimum execution time: 379_831 nanoseconds. + Weight::from_parts(561_378_274, 0) + // Standard Error: 429_319 + .saturating_add(Weight::from_parts(34_733_895, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1419,10 +1423,10 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `692 + r * (240 ±0)` // Estimated: `0` - // Minimum execution time: 542_631 nanoseconds. - Weight::from_parts(668_665_110, 0) - // Standard Error: 482_581 - .saturating_add(Weight::from_parts(18_074_689, 0).saturating_mul(r.into())) + // Minimum execution time: 373_671 nanoseconds. + Weight::from_parts(475_085_212, 0) + // Standard Error: 380_687 + .saturating_add(Weight::from_parts(16_409_940, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -1431,260 +1435,260 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_630 nanoseconds. - Weight::from_parts(2_955_102, 0) - // Standard Error: 6_860 - .saturating_add(Weight::from_parts(592_425, 0).saturating_mul(r.into())) + // Minimum execution time: 1_660 nanoseconds. + Weight::from_parts(3_168_062, 0) + // Standard Error: 5_526 + .saturating_add(Weight::from_parts(571_579, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_730 nanoseconds. - Weight::from_parts(10_644_917, 0) - // Standard Error: 25_684 - .saturating_add(Weight::from_parts(1_679_787, 0).saturating_mul(r.into())) + // Minimum execution time: 1_710 nanoseconds. + Weight::from_parts(4_516_931, 0) + // Standard Error: 83_416 + .saturating_add(Weight::from_parts(2_087_407, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_880 nanoseconds. - Weight::from_parts(6_059_713, 0) - // Standard Error: 29_527 - .saturating_add(Weight::from_parts(1_743_677, 0).saturating_mul(r.into())) + // Minimum execution time: 1_760 nanoseconds. + Weight::from_parts(7_877_061, 0) + // Standard Error: 15_747 + .saturating_add(Weight::from_parts(1_609_519, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600 nanoseconds. - Weight::from_parts(2_399_198, 0) - // Standard Error: 13_033 - .saturating_add(Weight::from_parts(1_580_765, 0).saturating_mul(r.into())) + // Minimum execution time: 1_500 nanoseconds. + Weight::from_parts(2_958_548, 0) + // Standard Error: 15_164 + .saturating_add(Weight::from_parts(1_568_447, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_390 nanoseconds. - Weight::from_parts(62_941, 0) - // Standard Error: 21_738 - .saturating_add(Weight::from_parts(2_766_819, 0).saturating_mul(r.into())) + // Minimum execution time: 1_470 nanoseconds. + Weight::from_parts(1_996_972, 0) + // Standard Error: 14_597 + .saturating_add(Weight::from_parts(1_953_898, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_640 nanoseconds. - Weight::from_parts(2_534_460, 0) - // Standard Error: 8_493 - .saturating_add(Weight::from_parts(926_397, 0).saturating_mul(r.into())) + // Minimum execution time: 1_590 nanoseconds. + Weight::from_parts(1_766_553, 0) + // Standard Error: 8_065 + .saturating_add(Weight::from_parts(963_351, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_630 nanoseconds. - Weight::from_parts(1_997_345, 0) - // Standard Error: 11_639 - .saturating_add(Weight::from_parts(1_327_716, 0).saturating_mul(r.into())) + // Minimum execution time: 1_500 nanoseconds. + Weight::from_parts(1_416_954, 0) + // Standard Error: 11_542 + .saturating_add(Weight::from_parts(1_325_071, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_500 nanoseconds. - Weight::from_parts(1_603_143, 0) - // Standard Error: 11_826 - .saturating_add(Weight::from_parts(1_679_173, 0).saturating_mul(r.into())) + // Minimum execution time: 1_570 nanoseconds. + Weight::from_parts(1_345_137, 0) + // Standard Error: 12_050 + .saturating_add(Weight::from_parts(1_695_664, 0).saturating_mul(r.into())) } /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_740 nanoseconds. - Weight::from_parts(6_848_621, 0) - // Standard Error: 345 - .saturating_add(Weight::from_parts(5_503, 0).saturating_mul(e.into())) + // Minimum execution time: 5_530 nanoseconds. + Weight::from_parts(6_821_544, 0) + // Standard Error: 369 + .saturating_add(Weight::from_parts(5_216, 0).saturating_mul(e.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_460 nanoseconds. - Weight::from_parts(7_424_094, 0) - // Standard Error: 34_669 - .saturating_add(Weight::from_parts(4_915_951, 0).saturating_mul(r.into())) + // Minimum execution time: 1_520 nanoseconds. + Weight::from_parts(3_273_726, 0) + // Standard Error: 36_533 + .saturating_add(Weight::from_parts(5_189_851, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_850 nanoseconds. - Weight::from_parts(6_753_900, 0) - // Standard Error: 48_303 - .saturating_add(Weight::from_parts(5_582_232, 0).saturating_mul(r.into())) + // Minimum execution time: 1_880 nanoseconds. + Weight::from_parts(14_122_632, 0) + // Standard Error: 44_455 + .saturating_add(Weight::from_parts(5_295_954, 0).saturating_mul(r.into())) } /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_630 nanoseconds. - Weight::from_parts(11_477_522, 0) - // Standard Error: 3_997 - .saturating_add(Weight::from_parts(324_801, 0).saturating_mul(p.into())) + // Minimum execution time: 7_470 nanoseconds. + Weight::from_parts(14_922_938, 0) + // Standard Error: 5_230 + .saturating_add(Weight::from_parts(291_823, 0).saturating_mul(p.into())) } /// The range of component `l` is `[0, 1024]`. fn instr_call_per_local(l: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_320 nanoseconds. - Weight::from_parts(13_022_522, 0) - // Standard Error: 2_869 - .saturating_add(Weight::from_parts(171_599, 0).saturating_mul(l.into())) + // Minimum execution time: 6_180 nanoseconds. + Weight::from_parts(8_670_245, 0) + // Standard Error: 751 + .saturating_add(Weight::from_parts(85_677, 0).saturating_mul(l.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_500 nanoseconds. - Weight::from_parts(7_235_171, 0) - // Standard Error: 7_322 - .saturating_add(Weight::from_parts(722_209, 0).saturating_mul(r.into())) + // Minimum execution time: 3_660 nanoseconds. + Weight::from_parts(4_891_829, 0) + // Standard Error: 7_656 + .saturating_add(Weight::from_parts(771_850, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_460 nanoseconds. - Weight::from_parts(6_543_158, 0) - // Standard Error: 9_723 - .saturating_add(Weight::from_parts(1_075_029, 0).saturating_mul(r.into())) + // Minimum execution time: 3_620 nanoseconds. + Weight::from_parts(4_735_955, 0) + // Standard Error: 9_087 + .saturating_add(Weight::from_parts(1_078_303, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_660 nanoseconds. - Weight::from_parts(6_543_532, 0) - // Standard Error: 10_775 - .saturating_add(Weight::from_parts(1_286_049, 0).saturating_mul(r.into())) + // Minimum execution time: 3_270 nanoseconds. + Weight::from_parts(4_593_509, 0) + // Standard Error: 11_409 + .saturating_add(Weight::from_parts(1_326_807, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_740 nanoseconds. - Weight::from_parts(5_132_745, 0) - // Standard Error: 11_822 - .saturating_add(Weight::from_parts(1_335_886, 0).saturating_mul(r.into())) + // Minimum execution time: 1_570 nanoseconds. + Weight::from_parts(4_167_653, 0) + // Standard Error: 16_230 + .saturating_add(Weight::from_parts(1_524_288, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550 nanoseconds. - Weight::from_parts(2_308_029, 0) - // Standard Error: 13_767 - .saturating_add(Weight::from_parts(1_812_020, 0).saturating_mul(r.into())) + // Minimum execution time: 1_760 nanoseconds. + Weight::from_parts(1_918_192, 0) + // Standard Error: 15_384 + .saturating_add(Weight::from_parts(1_829_680, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_940 nanoseconds. - Weight::from_parts(1_188_446, 0) - // Standard Error: 17_898 - .saturating_add(Weight::from_parts(1_367_101, 0).saturating_mul(r.into())) + // Minimum execution time: 1_900 nanoseconds. + Weight::from_parts(3_163_935, 0) + // Standard Error: 10_556 + .saturating_add(Weight::from_parts(1_329_884, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_620 nanoseconds. - Weight::from_parts(2_001_616, 0) - // Standard Error: 1_874_724 - .saturating_add(Weight::from_parts(405_491_583, 0).saturating_mul(r.into())) + // Minimum execution time: 1_650 nanoseconds. + Weight::from_parts(1_910_965, 0) + // Standard Error: 590_041 + .saturating_add(Weight::from_parts(273_492_034, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600 nanoseconds. - Weight::from_parts(3_615_630, 0) - // Standard Error: 8_138 - .saturating_add(Weight::from_parts(861_404, 0).saturating_mul(r.into())) + // Minimum execution time: 1_650 nanoseconds. + Weight::from_parts(3_501_879, 0) + // Standard Error: 10_283 + .saturating_add(Weight::from_parts(869_051, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(2_724_879, 0) - // Standard Error: 8_882 - .saturating_add(Weight::from_parts(865_619, 0).saturating_mul(r.into())) + // Minimum execution time: 1_650 nanoseconds. + Weight::from_parts(3_304_482, 0) + // Standard Error: 11_321 + .saturating_add(Weight::from_parts(885_112, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_770 nanoseconds. - Weight::from_parts(3_299_778, 0) - // Standard Error: 9_856 - .saturating_add(Weight::from_parts(875_967, 0).saturating_mul(r.into())) + // Minimum execution time: 1_610 nanoseconds. + Weight::from_parts(2_855_883, 0) + // Standard Error: 7_818 + .saturating_add(Weight::from_parts(881_952, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_650 nanoseconds. - Weight::from_parts(2_361_703, 0) - // Standard Error: 12_413 - .saturating_add(Weight::from_parts(905_102, 0).saturating_mul(r.into())) + // Minimum execution time: 1_470 nanoseconds. + Weight::from_parts(3_726_969, 0) + // Standard Error: 7_884 + .saturating_add(Weight::from_parts(840_088, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_450 nanoseconds. - Weight::from_parts(2_740_861, 0) - // Standard Error: 8_377 - .saturating_add(Weight::from_parts(935_400, 0).saturating_mul(r.into())) + // Minimum execution time: 1_590 nanoseconds. + Weight::from_parts(3_127_941, 0) + // Standard Error: 9_901 + .saturating_add(Weight::from_parts(883_028, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_510 nanoseconds. - Weight::from_parts(4_248_233, 0) - // Standard Error: 8_536 - .saturating_add(Weight::from_parts(880_675, 0).saturating_mul(r.into())) + // Minimum execution time: 1_590 nanoseconds. + Weight::from_parts(2_898_727, 0) + // Standard Error: 9_739 + .saturating_add(Weight::from_parts(875_353, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32) -> Weight { @@ -1692,229 +1696,229 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Measured: `0` // Estimated: `0` // Minimum execution time: 1_450 nanoseconds. - Weight::from_parts(2_308_635, 0) - // Standard Error: 11_590 - .saturating_add(Weight::from_parts(926_551, 0).saturating_mul(r.into())) + Weight::from_parts(3_491_418, 0) + // Standard Error: 7_092 + .saturating_add(Weight::from_parts(916_425, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600 nanoseconds. - Weight::from_parts(3_635_193, 0) - // Standard Error: 13_678 - .saturating_add(Weight::from_parts(1_135_022, 0).saturating_mul(r.into())) + // Minimum execution time: 1_630 nanoseconds. + Weight::from_parts(2_829_404, 0) + // Standard Error: 9_690 + .saturating_add(Weight::from_parts(1_224_285, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(4_333_685, 0) - // Standard Error: 11_208 - .saturating_add(Weight::from_parts(1_117_647, 0).saturating_mul(r.into())) + // Minimum execution time: 1_530 nanoseconds. + Weight::from_parts(4_770_101, 0) + // Standard Error: 11_181 + .saturating_add(Weight::from_parts(1_138_350, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_420 nanoseconds. - Weight::from_parts(3_017_200, 0) - // Standard Error: 14_098 - .saturating_add(Weight::from_parts(1_156_701, 0).saturating_mul(r.into())) + // Minimum execution time: 1_590 nanoseconds. + Weight::from_parts(2_647_931, 0) + // Standard Error: 11_363 + .saturating_add(Weight::from_parts(1_158_647, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_470 nanoseconds. - Weight::from_parts(2_813_602, 0) - // Standard Error: 14_605 - .saturating_add(Weight::from_parts(1_187_315, 0).saturating_mul(r.into())) + // Minimum execution time: 1_650 nanoseconds. + Weight::from_parts(2_245_796, 0) + // Standard Error: 16_171 + .saturating_add(Weight::from_parts(1_209_505, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_410 nanoseconds. - Weight::from_parts(4_405_895, 0) - // Standard Error: 14_237 - .saturating_add(Weight::from_parts(1_136_192, 0).saturating_mul(r.into())) + // Minimum execution time: 1_580 nanoseconds. + Weight::from_parts(2_564_015, 0) + // Standard Error: 20_451 + .saturating_add(Weight::from_parts(1_193_100, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_580 nanoseconds. - Weight::from_parts(4_529_323, 0) - // Standard Error: 12_311 - .saturating_add(Weight::from_parts(1_109_286, 0).saturating_mul(r.into())) + // Minimum execution time: 1_670 nanoseconds. + Weight::from_parts(1_616_329, 0) + // Standard Error: 14_490 + .saturating_add(Weight::from_parts(1_226_218, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_400 nanoseconds. - Weight::from_parts(2_160_653, 0) - // Standard Error: 12_773 - .saturating_add(Weight::from_parts(1_177_547, 0).saturating_mul(r.into())) + // Minimum execution time: 1_630 nanoseconds. + Weight::from_parts(3_235_449, 0) + // Standard Error: 9_510 + .saturating_add(Weight::from_parts(1_130_045, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_570 nanoseconds. - Weight::from_parts(2_052_661, 0) - // Standard Error: 14_481 - .saturating_add(Weight::from_parts(1_179_417, 0).saturating_mul(r.into())) + // Minimum execution time: 1_640 nanoseconds. + Weight::from_parts(2_187_572, 0) + // Standard Error: 8_978 + .saturating_add(Weight::from_parts(1_146_713, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_640 nanoseconds. - Weight::from_parts(3_584_030, 0) - // Standard Error: 13_594 - .saturating_add(Weight::from_parts(1_135_684, 0).saturating_mul(r.into())) + // Minimum execution time: 1_480 nanoseconds. + Weight::from_parts(2_755_297, 0) + // Standard Error: 9_644 + .saturating_add(Weight::from_parts(1_147_002, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(3_398_760, 0) - // Standard Error: 9_956 - .saturating_add(Weight::from_parts(1_136_977, 0).saturating_mul(r.into())) + // Minimum execution time: 1_520 nanoseconds. + Weight::from_parts(2_271_019, 0) + // Standard Error: 13_005 + .saturating_add(Weight::from_parts(1_173_251, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_460 nanoseconds. - Weight::from_parts(2_489_958, 0) - // Standard Error: 10_550 - .saturating_add(Weight::from_parts(1_148_385, 0).saturating_mul(r.into())) + // Minimum execution time: 1_560 nanoseconds. + Weight::from_parts(4_637_066, 0) + // Standard Error: 10_643 + .saturating_add(Weight::from_parts(1_114_717, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_470 nanoseconds. - Weight::from_parts(2_184_614, 0) - // Standard Error: 13_847 - .saturating_add(Weight::from_parts(1_158_666, 0).saturating_mul(r.into())) + // Minimum execution time: 1_570 nanoseconds. + Weight::from_parts(2_885_717, 0) + // Standard Error: 16_282 + .saturating_add(Weight::from_parts(1_173_908, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_620 nanoseconds. - Weight::from_parts(3_821_258, 0) - // Standard Error: 14_517 - .saturating_add(Weight::from_parts(1_151_944, 0).saturating_mul(r.into())) + // Minimum execution time: 1_510 nanoseconds. + Weight::from_parts(2_930_571, 0) + // Standard Error: 12_242 + .saturating_add(Weight::from_parts(1_185_040, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_640 nanoseconds. - Weight::from_parts(4_219_927, 0) - // Standard Error: 14_360 - .saturating_add(Weight::from_parts(1_269_226, 0).saturating_mul(r.into())) + // Minimum execution time: 1_600 nanoseconds. + Weight::from_parts(1_624_233, 0) + // Standard Error: 16_359 + .saturating_add(Weight::from_parts(1_390_432, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_610 nanoseconds. - Weight::from_parts(4_103_772, 0) - // Standard Error: 13_814 - .saturating_add(Weight::from_parts(1_166_162, 0).saturating_mul(r.into())) + // Minimum execution time: 1_600 nanoseconds. + Weight::from_parts(2_410_271, 0) + // Standard Error: 16_801 + .saturating_add(Weight::from_parts(1_218_286, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(2_837_444, 0) - // Standard Error: 11_492 - .saturating_add(Weight::from_parts(1_353_230, 0).saturating_mul(r.into())) + // Minimum execution time: 1_430 nanoseconds. + Weight::from_parts(3_137_425, 0) + // Standard Error: 10_578 + .saturating_add(Weight::from_parts(1_368_263, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_390 nanoseconds. - Weight::from_parts(2_324_540, 0) - // Standard Error: 12_241 - .saturating_add(Weight::from_parts(1_260_290, 0).saturating_mul(r.into())) + // Minimum execution time: 1_490 nanoseconds. + Weight::from_parts(2_270_098, 0) + // Standard Error: 12_685 + .saturating_add(Weight::from_parts(1_290_536, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_410 nanoseconds. - Weight::from_parts(3_622_678, 0) - // Standard Error: 11_988 - .saturating_add(Weight::from_parts(1_108_472, 0).saturating_mul(r.into())) + // Minimum execution time: 1_510 nanoseconds. + Weight::from_parts(2_641_167, 0) + // Standard Error: 12_983 + .saturating_add(Weight::from_parts(1_186_671, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_370 nanoseconds. - Weight::from_parts(1_528_294, 0) - // Standard Error: 13_141 - .saturating_add(Weight::from_parts(1_216_523, 0).saturating_mul(r.into())) + // Minimum execution time: 1_610 nanoseconds. + Weight::from_parts(3_370_887, 0) + // Standard Error: 9_813 + .saturating_add(Weight::from_parts(1_134_070, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_560 nanoseconds. - Weight::from_parts(3_488_953, 0) - // Standard Error: 10_562 - .saturating_add(Weight::from_parts(1_128_100, 0).saturating_mul(r.into())) + // Minimum execution time: 1_530 nanoseconds. + Weight::from_parts(2_956_547, 0) + // Standard Error: 10_113 + .saturating_add(Weight::from_parts(1_149_330, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_510 nanoseconds. - Weight::from_parts(3_112_301, 0) - // Standard Error: 12_563 - .saturating_add(Weight::from_parts(1_158_825, 0).saturating_mul(r.into())) + // Minimum execution time: 1_580 nanoseconds. + Weight::from_parts(2_991_547, 0) + // Standard Error: 15_007 + .saturating_add(Weight::from_parts(1_186_614, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_440 nanoseconds. - Weight::from_parts(2_421_867, 0) - // Standard Error: 10_727 - .saturating_add(Weight::from_parts(1_162_108, 0).saturating_mul(r.into())) + // Minimum execution time: 1_670 nanoseconds. + Weight::from_parts(3_947_502, 0) + // Standard Error: 10_950 + .saturating_add(Weight::from_parts(1_132_980, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32) -> Weight { @@ -1922,28 +1926,28 @@ impl pallet_contracts::weights::WeightInfo for WeightIn // Measured: `0` // Estimated: `0` // Minimum execution time: 1_470 nanoseconds. - Weight::from_parts(3_888_383, 0) - // Standard Error: 12_429 - .saturating_add(Weight::from_parts(1_115_583, 0).saturating_mul(r.into())) + Weight::from_parts(2_614_403, 0) + // Standard Error: 13_580 + .saturating_add(Weight::from_parts(1_190_584, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_410 nanoseconds. - Weight::from_parts(2_561_771, 0) - // Standard Error: 10_053 - .saturating_add(Weight::from_parts(1_161_925, 0).saturating_mul(r.into())) + // Minimum execution time: 1_510 nanoseconds. + Weight::from_parts(2_626_456, 0) + // Standard Error: 11_759 + .saturating_add(Weight::from_parts(1_209_798, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_420 nanoseconds. - Weight::from_parts(3_075_199, 0) - // Standard Error: 12_838 - .saturating_add(Weight::from_parts(1_171_290, 0).saturating_mul(r.into())) + // Minimum execution time: 1_560 nanoseconds. + Weight::from_parts(3_145_135, 0) + // Standard Error: 10_314 + .saturating_add(Weight::from_parts(1_163_468, 0).saturating_mul(r.into())) } } diff --git a/runtime/common/src/weights/pallet_democracy.rs b/runtime/common/src/weights/pallet_democracy.rs index bcaab3f85..a5f9fbf6b 100644 --- a/runtime/common/src/weights/pallet_democracy.rs +++ b/runtime/common/src/weights/pallet_democracy.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,7 +19,7 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -64,8 +64,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `4835` // Estimated: `23413` - // Minimum execution time: 51_540 nanoseconds. - Weight::from_parts(57_860_000, 23413) + // Minimum execution time: 52_740 nanoseconds. + Weight::from_parts(64_651_000, 23413) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3591` // Estimated: `5705` - // Minimum execution time: 43_800 nanoseconds. - Weight::from_parts(46_960_000, 5705) + // Minimum execution time: 44_080 nanoseconds. + Weight::from_parts(54_891_000, 5705) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -90,8 +90,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3500` // Estimated: `12732` - // Minimum execution time: 61_480 nanoseconds. - Weight::from_parts(69_830_000, 12732) + // Minimum execution time: 60_500 nanoseconds. + Weight::from_parts(74_880_000, 12732) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -105,8 +105,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3522` // Estimated: `12732` - // Minimum execution time: 60_080 nanoseconds. - Weight::from_parts(78_800_000, 12732) + // Minimum execution time: 60_861 nanoseconds. + Weight::from_parts(74_420_000, 12732) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -118,8 +118,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `295` // Estimated: `5192` - // Minimum execution time: 29_690 nanoseconds. - Weight::from_parts(35_980_000, 5192) + // Minimum execution time: 25_880 nanoseconds. + Weight::from_parts(31_360_000, 5192) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -139,8 +139,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `6251` // Estimated: `31427` - // Minimum execution time: 115_081 nanoseconds. - Weight::from_parts(132_430_000, 31427) + // Minimum execution time: 114_571 nanoseconds. + Weight::from_parts(117_761_000, 31427) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -152,8 +152,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3419` // Estimated: `6344` - // Minimum execution time: 20_001 nanoseconds. - Weight::from_parts(20_720_000, 6344) + // Minimum execution time: 23_560 nanoseconds. + Weight::from_parts(24_710_000, 6344) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -163,8 +163,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_560 nanoseconds. - Weight::from_parts(6_880_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_150 nanoseconds. + Weight::from_parts(5_440_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy NextExternal (r:0 w:1) /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) @@ -172,8 +172,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_450 nanoseconds. - Weight::from_parts(6_790_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_170 nanoseconds. + Weight::from_parts(5_410_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy NextExternal (r:1 w:1) /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) @@ -185,8 +185,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `179` // Estimated: `1126` - // Minimum execution time: 28_470 nanoseconds. - Weight::from_parts(34_400_000, 1126) + // Minimum execution time: 25_400 nanoseconds. + Weight::from_parts(34_080_000, 1126) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -198,8 +198,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `3448` // Estimated: `6344` - // Minimum execution time: 35_710 nanoseconds. - Weight::from_parts(36_730_000, 6344) + // Minimum execution time: 35_771 nanoseconds. + Weight::from_parts(40_850_000, 6344) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -213,8 +213,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `6122` // Estimated: `28116` - // Minimum execution time: 94_120 nanoseconds. - Weight::from_parts(110_771_000, 28116) + // Minimum execution time: 94_010 nanoseconds. + Weight::from_parts(100_940_000, 28116) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -224,8 +224,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_860 nanoseconds. - Weight::from_parts(13_300_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 12_611 nanoseconds. + Weight::from_parts(15_100_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy LowestUnbaked (r:1 w:1) /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -238,10 +238,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `174 + r * (125 ±0)` // Estimated: `998 + r * (2684 ±0)` - // Minimum execution time: 12_920 nanoseconds. - Weight::from_parts(23_702_780, 998) - // Standard Error: 46_221 - .saturating_add(Weight::from_parts(4_492_691, 0).saturating_mul(r.into())) + // Minimum execution time: 13_280 nanoseconds. + Weight::from_parts(14_127_260, 998) + // Standard Error: 85_267 + .saturating_add(Weight::from_parts(4_607_877, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -264,10 +264,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `174 + r * (125 ±0)` // Estimated: `19318 + r * (2684 ±0)` - // Minimum execution time: 19_170 nanoseconds. - Weight::from_parts(30_529_069, 19318) - // Standard Error: 66_363 - .saturating_add(Weight::from_parts(4_703_710, 0).saturating_mul(r.into())) + // Minimum execution time: 16_280 nanoseconds. + Weight::from_parts(21_622_107, 19318) + // Standard Error: 37_442 + .saturating_add(Weight::from_parts(4_611_492, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -284,10 +284,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `897 + r * (147 ±0)` // Estimated: `22596 + r * (2684 ±0)` - // Minimum execution time: 56_800 nanoseconds. - Weight::from_parts(74_850_031, 22596) - // Standard Error: 58_350 - .saturating_add(Weight::from_parts(5_921_222, 0).saturating_mul(r.into())) + // Minimum execution time: 57_840 nanoseconds. + Weight::from_parts(77_974_592, 22596) + // Standard Error: 82_756 + .saturating_add(Weight::from_parts(5_829_269, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -303,10 +303,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `522 + r * (147 ±0)` // Estimated: `12548 + r * (2684 ±0)` - // Minimum execution time: 33_660 nanoseconds. - Weight::from_parts(54_095_840, 12548) - // Standard Error: 79_513 - .saturating_add(Weight::from_parts(5_832_475, 0).saturating_mul(r.into())) + // Minimum execution time: 33_540 nanoseconds. + Weight::from_parts(51_316_369, 12548) + // Standard Error: 45_199 + .saturating_add(Weight::from_parts(5_718_552, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -319,8 +319,8 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_120 nanoseconds. - Weight::from_parts(5_630_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 5_190 nanoseconds. + Weight::from_parts(5_960_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Democracy VotingOf (r:1 w:1) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) @@ -329,14 +329,12 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn unlock_remove(r: u32) -> Weight { + fn unlock_remove(_r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `554` // Estimated: `12655` - // Minimum execution time: 31_930 nanoseconds. - Weight::from_parts(43_131_431, 12655) - // Standard Error: 8_315 - .saturating_add(Weight::from_parts(5_162, 0).saturating_mul(r.into())) + // Minimum execution time: 32_230 nanoseconds. + Weight::from_parts(43_644_064, 12655) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -351,10 +349,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `555 + r * (22 ±0)` // Estimated: `12655` - // Minimum execution time: 35_201 nanoseconds. - Weight::from_parts(40_141_301, 12655) - // Standard Error: 8_747 - .saturating_add(Weight::from_parts(96_832, 0).saturating_mul(r.into())) + // Minimum execution time: 34_951 nanoseconds. + Weight::from_parts(41_750_676, 12655) + // Standard Error: 8_881 + .saturating_add(Weight::from_parts(54_025, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -367,10 +365,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `760 + r * (26 ±0)` // Estimated: `8958` - // Minimum execution time: 23_131 nanoseconds. - Weight::from_parts(29_833_309, 8958) - // Standard Error: 5_916 - .saturating_add(Weight::from_parts(124_672, 0).saturating_mul(r.into())) + // Minimum execution time: 23_310 nanoseconds. + Weight::from_parts(31_749_195, 8958) + // Standard Error: 7_279 + .saturating_add(Weight::from_parts(80_521, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -383,10 +381,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `760 + r * (26 ±0)` // Estimated: `8958` - // Minimum execution time: 26_630 nanoseconds. - Weight::from_parts(32_266_893, 8958) - // Standard Error: 5_138 - .saturating_add(Weight::from_parts(90_624, 0).saturating_mul(r.into())) + // Minimum execution time: 23_340 nanoseconds. + Weight::from_parts(32_404_729, 8958) + // Standard Error: 9_008 + .saturating_add(Weight::from_parts(72_842, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_identity.rs b/runtime/common/src/weights/pallet_identity.rs index 627d9add2..8421574d9 100644 --- a/runtime/common/src/weights/pallet_identity.rs +++ b/runtime/common/src/weights/pallet_identity.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,7 +19,7 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -59,10 +59,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `64 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 19_230 nanoseconds. - Weight::from_parts(22_092_935, 952) - // Standard Error: 60_132 - .saturating_add(Weight::from_parts(298_384, 0).saturating_mul(r.into())) + // Minimum execution time: 17_540 nanoseconds. + Weight::from_parts(19_412_422, 952) + // Standard Error: 44_718 + .saturating_add(Weight::from_parts(716_331, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -74,10 +74,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `474 + r * (5 ±0)` // Estimated: `7313` - // Minimum execution time: 25_480 nanoseconds. - Weight::from_parts(45_732_528, 7313) - // Standard Error: 11_669 - .saturating_add(Weight::from_parts(623_699, 0).saturating_mul(x.into())) + // Minimum execution time: 25_450 nanoseconds. + Weight::from_parts(49_207_597, 7313) + // Standard Error: 14_331 + .saturating_add(Weight::from_parts(513_639, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -92,10 +92,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `101` // Estimated: `11894 + s * (2589 ±0)` - // Minimum execution time: 12_700 nanoseconds. - Weight::from_parts(36_155_572, 11894) - // Standard Error: 48_562 - .saturating_add(Weight::from_parts(4_605_038, 0).saturating_mul(s.into())) + // Minimum execution time: 13_130 nanoseconds. + Weight::from_parts(40_210_154, 11894) + // Standard Error: 39_792 + .saturating_add(Weight::from_parts(4_319_815, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(s.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -113,10 +113,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `226 + p * (32 ±0)` // Estimated: `11894` - // Minimum execution time: 13_930 nanoseconds. - Weight::from_parts(34_268_734, 11894) - // Standard Error: 33_963 - .saturating_add(Weight::from_parts(1_902_476, 0).saturating_mul(p.into())) + // Minimum execution time: 13_910 nanoseconds. + Weight::from_parts(38_666_951, 11894) + // Standard Error: 51_955 + .saturating_add(Weight::from_parts(1_774_301, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) @@ -134,12 +134,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `535 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` // Estimated: `11894` - // Minimum execution time: 52_030 nanoseconds. - Weight::from_parts(52_304_738, 11894) - // Standard Error: 27_639 - .saturating_add(Weight::from_parts(1_796_839, 0).saturating_mul(s.into())) - // Standard Error: 27_639 - .saturating_add(Weight::from_parts(280_343, 0).saturating_mul(x.into())) + // Minimum execution time: 51_390 nanoseconds. + Weight::from_parts(48_950_763, 11894) + // Standard Error: 31_792 + .saturating_add(Weight::from_parts(1_828_765, 0).saturating_mul(s.into())) + // Standard Error: 31_792 + .saturating_add(Weight::from_parts(268_175, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -154,12 +154,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `431 + r * (57 ±0) + x * (66 ±0)` // Estimated: `8265` - // Minimum execution time: 38_201 nanoseconds. - Weight::from_parts(40_514_631, 8265) - // Standard Error: 109_831 - .saturating_add(Weight::from_parts(521_267, 0).saturating_mul(r.into())) - // Standard Error: 12_757 - .saturating_add(Weight::from_parts(723_546, 0).saturating_mul(x.into())) + // Minimum execution time: 37_700 nanoseconds. + Weight::from_parts(34_649_694, 8265) + // Standard Error: 103_076 + .saturating_add(Weight::from_parts(1_038_785, 0).saturating_mul(r.into())) + // Standard Error: 11_972 + .saturating_add(Weight::from_parts(660_722, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -171,10 +171,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `430 + x * (66 ±0)` // Estimated: `7313` - // Minimum execution time: 36_100 nanoseconds. - Weight::from_parts(46_093_276, 7313) - // Standard Error: 14_527 - .saturating_add(Weight::from_parts(650_226, 0).saturating_mul(x.into())) + // Minimum execution time: 34_050 nanoseconds. + Weight::from_parts(50_054_855, 7313) + // Standard Error: 13_231 + .saturating_add(Weight::from_parts(537_492, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -185,10 +185,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 11_600 nanoseconds. - Weight::from_parts(14_311_547, 952) - // Standard Error: 30_165 - .saturating_add(Weight::from_parts(312_857, 0).saturating_mul(r.into())) + // Minimum execution time: 11_460 nanoseconds. + Weight::from_parts(12_342_541, 952) + // Standard Error: 26_113 + .saturating_add(Weight::from_parts(711_064, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -199,24 +199,22 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 11_420 nanoseconds. - Weight::from_parts(13_852_062, 952) - // Standard Error: 41_801 - .saturating_add(Weight::from_parts(58_707, 0).saturating_mul(r.into())) + // Minimum execution time: 10_260 nanoseconds. + Weight::from_parts(13_295_725, 952) + // Standard Error: 31_155 + .saturating_add(Weight::from_parts(66_512, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Identity Registrars (r:1 w:1) /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(457), added: 952, mode: MaxEncodedLen) /// The range of component `r` is `[1, 7]`. - fn set_fields(r: u32) -> Weight { + fn set_fields(_r: u32) -> Weight { // Proof Size summary in bytes: // Measured: `121 + r * (57 ±0)` // Estimated: `952` - // Minimum execution time: 10_180 nanoseconds. - Weight::from_parts(12_701_169, 952) - // Standard Error: 25_078 - .saturating_add(Weight::from_parts(66_396, 0).saturating_mul(r.into())) + // Minimum execution time: 10_060 nanoseconds. + Weight::from_parts(12_931_747, 952) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -226,16 +224,14 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Proof: Identity IdentityOf (max_values: None, max_size: Some(4838), added: 7313, mode: MaxEncodedLen) /// The range of component `r` is `[1, 7]`. /// The range of component `x` is `[0, 64]`. - fn provide_judgement(r: u32, x: u32) -> Weight { + fn provide_judgement(_r: u32, x: u32) -> Weight { // Proof Size summary in bytes: // Measured: `509 + r * (57 ±0) + x * (66 ±0)` // Estimated: `8265` - // Minimum execution time: 28_990 nanoseconds. - Weight::from_parts(31_391_358, 8265) - // Standard Error: 166_351 - .saturating_add(Weight::from_parts(536_354, 0).saturating_mul(r.into())) - // Standard Error: 17_000 - .saturating_add(Weight::from_parts(1_040_806, 0).saturating_mul(x.into())) + // Minimum execution time: 30_500 nanoseconds. + Weight::from_parts(35_394_279, 8265) + // Standard Error: 16_146 + .saturating_add(Weight::from_parts(1_027_013, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -254,12 +250,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `954 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` // Estimated: `17108` - // Minimum execution time: 75_361 nanoseconds. - Weight::from_parts(82_306_973, 17108) - // Standard Error: 33_223 - .saturating_add(Weight::from_parts(1_805_205, 0).saturating_mul(s.into())) - // Standard Error: 33_223 - .saturating_add(Weight::from_parts(242_228, 0).saturating_mul(x.into())) + // Minimum execution time: 74_220 nanoseconds. + Weight::from_parts(77_339_608, 17108) + // Standard Error: 36_161 + .saturating_add(Weight::from_parts(1_737_934, 0).saturating_mul(s.into())) + // Standard Error: 36_161 + .saturating_add(Weight::from_parts(184_480, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -271,14 +267,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Storage: Identity SubsOf (r:1 w:1) /// Proof: Identity SubsOf (max_values: None, max_size: Some(2106), added: 4581, mode: MaxEncodedLen) /// The range of component `s` is `[0, 63]`. - fn add_sub(s: u32) -> Weight { + fn add_sub(_s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `355 + s * (41 ±0)` // Estimated: `14483` - // Minimum execution time: 38_960 nanoseconds. - Weight::from_parts(49_299_144, 14483) - // Standard Error: 10_280 - .saturating_add(Weight::from_parts(66_362, 0).saturating_mul(s.into())) + // Minimum execution time: 36_160 nanoseconds. + Weight::from_parts(52_735_396, 14483) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -291,10 +285,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `464 + s * (7 ±0)` // Estimated: `9902` - // Minimum execution time: 19_540 nanoseconds. - Weight::from_parts(22_842_434, 9902) - // Standard Error: 8_528 - .saturating_add(Weight::from_parts(62_406, 0).saturating_mul(s.into())) + // Minimum execution time: 17_690 nanoseconds. + Weight::from_parts(22_790_076, 9902) + // Standard Error: 4_956 + .saturating_add(Weight::from_parts(32_433, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -309,10 +303,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `544 + s * (39 ±0)` // Estimated: `14483` - // Minimum execution time: 44_010 nanoseconds. - Weight::from_parts(50_338_094, 14483) - // Standard Error: 11_434 - .saturating_add(Weight::from_parts(72_877, 0).saturating_mul(s.into())) + // Minimum execution time: 43_240 nanoseconds. + Weight::from_parts(52_869_158, 14483) + // Standard Error: 16_108 + .saturating_add(Weight::from_parts(46_916, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -325,10 +319,10 @@ impl pallet_identity::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `469 + s * (42 ±0)` // Estimated: `7170` - // Minimum execution time: 29_920 nanoseconds. - Weight::from_parts(35_959_708, 7170) - // Standard Error: 7_561 - .saturating_add(Weight::from_parts(81_229, 0).saturating_mul(s.into())) + // Minimum execution time: 29_340 nanoseconds. + Weight::from_parts(34_148_899, 7170) + // Standard Error: 7_721 + .saturating_add(Weight::from_parts(101_826, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_membership.rs b/runtime/common/src/weights/pallet_membership.rs index 5dac0f811..c268003a6 100644 --- a/runtime/common/src/weights/pallet_membership.rs +++ b/runtime/common/src/weights/pallet_membership.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,7 +19,7 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -65,10 +65,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `237 + m * (64 ±0)` // Estimated: `4900 + m * (192 ±0)` - // Minimum execution time: 27_350 nanoseconds. - Weight::from_parts(30_991_548, 4900) - // Standard Error: 4_777 - .saturating_add(Weight::from_parts(67_925, 0).saturating_mul(m.into())) + // Minimum execution time: 27_951 nanoseconds. + Weight::from_parts(32_200_122, 4900) + // Standard Error: 4_679 + .saturating_add(Weight::from_parts(51_964, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -88,10 +88,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 29_760 nanoseconds. - Weight::from_parts(40_730_106, 5739) - // Standard Error: 10_014 - .saturating_add(Weight::from_parts(6_852, 0).saturating_mul(m.into())) + // Minimum execution time: 29_581 nanoseconds. + Weight::from_parts(37_774_872, 5739) + // Standard Error: 5_232 + .saturating_add(Weight::from_parts(27_127, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -111,10 +111,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 33_020 nanoseconds. - Weight::from_parts(35_957_095, 5739) - // Standard Error: 5_726 - .saturating_add(Weight::from_parts(102_602, 0).saturating_mul(m.into())) + // Minimum execution time: 31_120 nanoseconds. + Weight::from_parts(34_443_383, 5739) + // Standard Error: 9_979 + .saturating_add(Weight::from_parts(173_291, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -134,10 +134,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 32_760 nanoseconds. - Weight::from_parts(37_298_323, 5739) - // Standard Error: 6_902 - .saturating_add(Weight::from_parts(247_508, 0).saturating_mul(m.into())) + // Minimum execution time: 28_450 nanoseconds. + Weight::from_parts(35_667_710, 5739) + // Standard Error: 7_001 + .saturating_add(Weight::from_parts(258_261, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -157,10 +157,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `341 + m * (64 ±0)` // Estimated: `5739 + m * (192 ±0)` - // Minimum execution time: 34_380 nanoseconds. - Weight::from_parts(39_553_425, 5739) - // Standard Error: 5_627 - .saturating_add(Weight::from_parts(51_990, 0).saturating_mul(m.into())) + // Minimum execution time: 30_690 nanoseconds. + Weight::from_parts(38_368_668, 5739) + // Standard Error: 5_617 + .saturating_add(Weight::from_parts(70_863, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(Weight::from_parts(0, 192).saturating_mul(m.into())) @@ -176,10 +176,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `136 + m * (32 ±0)` // Estimated: `3833 + m * (32 ±0)` - // Minimum execution time: 14_780 nanoseconds. - Weight::from_parts(16_653_218, 3833) - // Standard Error: 4_995 - .saturating_add(Weight::from_parts(41_193, 0).saturating_mul(m.into())) + // Minimum execution time: 12_980 nanoseconds. + Weight::from_parts(14_958_235, 3833) + // Standard Error: 2_397 + .saturating_add(Weight::from_parts(37_346, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) @@ -193,10 +193,10 @@ impl pallet_membership::weights::WeightInfo for WeightI // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_420 nanoseconds. - Weight::from_parts(6_328_054, 0) - // Standard Error: 816 - .saturating_add(Weight::from_parts(2_277, 0).saturating_mul(m.into())) + // Minimum execution time: 5_110 nanoseconds. + Weight::from_parts(6_020_436, 0) + // Standard Error: 783 + .saturating_add(Weight::from_parts(2_089, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/runtime/common/src/weights/pallet_multisig.rs b/runtime/common/src/weights/pallet_multisig.rs index 6675fc14d..c331618bc 100644 --- a/runtime/common/src/weights/pallet_multisig.rs +++ b/runtime/common/src/weights/pallet_multisig.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,7 +19,7 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,10 +57,10 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 17_090 nanoseconds. - Weight::from_parts(22_547_170, 0) + // Minimum execution time: 17_250 nanoseconds. + Weight::from_parts(18_910_472, 0) // Standard Error: 48 - .saturating_add(Weight::from_parts(769, 0).saturating_mul(z.into())) + .saturating_add(Weight::from_parts(1_138, 0).saturating_mul(z.into())) } /// Storage: Multisig Multisigs (r:1 w:1) /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3350), added: 5825, mode: MaxEncodedLen) @@ -70,12 +70,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `339 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 50_160 nanoseconds. - Weight::from_parts(52_808_971, 5825) - // Standard Error: 10_853 - .saturating_add(Weight::from_parts(48_861, 0).saturating_mul(s.into())) - // Standard Error: 106 - .saturating_add(Weight::from_parts(2_424, 0).saturating_mul(z.into())) + // Minimum execution time: 48_960 nanoseconds. + Weight::from_parts(49_551_474, 5825) + // Standard Error: 9_903 + .saturating_add(Weight::from_parts(98_277, 0).saturating_mul(s.into())) + // Standard Error: 97 + .saturating_add(Weight::from_parts(2_458, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,12 +87,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `283` // Estimated: `5825` - // Minimum execution time: 36_181 nanoseconds. - Weight::from_parts(39_999_128, 5825) - // Standard Error: 8_544 - .saturating_add(Weight::from_parts(15_566, 0).saturating_mul(s.into())) - // Standard Error: 83 - .saturating_add(Weight::from_parts(2_593, 0).saturating_mul(z.into())) + // Minimum execution time: 35_221 nanoseconds. + Weight::from_parts(40_315_882, 5825) + // Standard Error: 7_735 + .saturating_add(Weight::from_parts(34_630, 0).saturating_mul(s.into())) + // Standard Error: 75 + .saturating_add(Weight::from_parts(2_013, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,12 +106,12 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `425 + s * (33 ±0)` // Estimated: `8432` - // Minimum execution time: 57_120 nanoseconds. - Weight::from_parts(43_350_397, 8432) - // Standard Error: 15_458 - .saturating_add(Weight::from_parts(198_127, 0).saturating_mul(s.into())) - // Standard Error: 151 - .saturating_add(Weight::from_parts(3_362, 0).saturating_mul(z.into())) + // Minimum execution time: 56_270 nanoseconds. + Weight::from_parts(53_319_412, 8432) + // Standard Error: 11_781 + .saturating_add(Weight::from_parts(119_161, 0).saturating_mul(s.into())) + // Standard Error: 115 + .saturating_add(Weight::from_parts(2_391, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -122,10 +122,10 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `343 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 38_630 nanoseconds. - Weight::from_parts(45_470_511, 5825) - // Standard Error: 13_700 - .saturating_add(Weight::from_parts(97_958, 0).saturating_mul(s.into())) + // Minimum execution time: 38_210 nanoseconds. + Weight::from_parts(42_949_786, 5825) + // Standard Error: 8_888 + .saturating_add(Weight::from_parts(103_842, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -136,22 +136,24 @@ impl pallet_multisig::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `283` // Estimated: `5825` - // Minimum execution time: 25_900 nanoseconds. - Weight::from_parts(32_176_176, 5825) - // Standard Error: 5_037 - .saturating_add(Weight::from_parts(66_127, 0).saturating_mul(s.into())) + // Minimum execution time: 26_130 nanoseconds. + Weight::from_parts(27_231_490, 5825) + // Standard Error: 7_243 + .saturating_add(Weight::from_parts(121_899, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Multisig Multisigs (r:1 w:1) /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3350), added: 5825, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. - fn cancel_as_multi(_s: u32) -> Weight { + fn cancel_as_multi(s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `491 + s * (1 ±0)` // Estimated: `5825` - // Minimum execution time: 41_270 nanoseconds. - Weight::from_parts(56_230_114, 5825) + // Minimum execution time: 39_530 nanoseconds. + Weight::from_parts(44_170_155, 5825) + // Standard Error: 6_233 + .saturating_add(Weight::from_parts(85_488, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_parachain_staking.rs b/runtime/common/src/weights/pallet_parachain_staking.rs index 94a68df27..2e0fcb849 100644 --- a/runtime/common/src/weights/pallet_parachain_staking.rs +++ b/runtime/common/src/weights/pallet_parachain_staking.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,7 +19,7 @@ //! Autogenerated weights for pallet_parachain_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-26`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -58,8 +58,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `119` // Estimated: `614` - // Minimum execution time: 18_800 nanoseconds. - Weight::from_parts(19_820_000, 614) + // Minimum execution time: 17_690 nanoseconds. + Weight::from_parts(21_220_000, 614) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,8 +69,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `119` // Estimated: `614` - // Minimum execution time: 83_421 nanoseconds. - Weight::from_parts(95_940_000, 614) + // Minimum execution time: 62_130 nanoseconds. + Weight::from_parts(75_020_000, 614) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,8 +80,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `52` // Estimated: `547` - // Minimum execution time: 15_710 nanoseconds. - Weight::from_parts(19_280_000, 547) + // Minimum execution time: 15_670 nanoseconds. + Weight::from_parts(19_500_000, 547) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -91,8 +91,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `52` // Estimated: `547` - // Minimum execution time: 15_150 nanoseconds. - Weight::from_parts(19_240_000, 547) + // Minimum execution time: 14_900 nanoseconds. + Weight::from_parts(18_460_000, 547) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -102,8 +102,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `28` // Estimated: `523` - // Minimum execution time: 16_560 nanoseconds. - Weight::from_parts(20_240_000, 523) + // Minimum execution time: 16_140 nanoseconds. + Weight::from_parts(19_481_000, 523) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -113,8 +113,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `27` // Estimated: `522` - // Minimum execution time: 15_630 nanoseconds. - Weight::from_parts(19_030_000, 522) + // Minimum execution time: 17_750 nanoseconds. + Weight::from_parts(18_270_000, 522) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -126,8 +126,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `147` // Estimated: `1284` - // Minimum execution time: 67_041 nanoseconds. - Weight::from_parts(68_000_000, 1284) + // Minimum execution time: 66_920 nanoseconds. + Weight::from_parts(81_800_000, 1284) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -152,10 +152,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1898 + x * (49 ±0)` // Estimated: `22371 + x * (300 ±0)` - // Minimum execution time: 66_950 nanoseconds. - Weight::from_parts(87_411_011, 22371) - // Standard Error: 2_278 - .saturating_add(Weight::from_parts(208_670, 0).saturating_mul(x.into())) + // Minimum execution time: 67_450 nanoseconds. + Weight::from_parts(87_054_208, 22371) + // Standard Error: 2_162 + .saturating_add(Weight::from_parts(203_040, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(7)) .saturating_add(Weight::from_parts(0, 300).saturating_mul(x.into())) @@ -169,10 +169,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `990 + x * (48 ±0)` // Estimated: `4794 + x * (98 ±0)` - // Minimum execution time: 36_050 nanoseconds. - Weight::from_parts(40_438_357, 4794) - // Standard Error: 2_205 - .saturating_add(Weight::from_parts(178_269, 0).saturating_mul(x.into())) + // Minimum execution time: 36_370 nanoseconds. + Weight::from_parts(40_724_049, 4794) + // Standard Error: 2_126 + .saturating_add(Weight::from_parts(171_862, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 98).saturating_mul(x.into())) @@ -200,10 +200,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `238 + x * (595 ±0)` // Estimated: `18229 + x * (12995 ±0)` - // Minimum execution time: 101_280 nanoseconds. - Weight::from_parts(122_190_000, 18229) - // Standard Error: 216_165 - .saturating_add(Weight::from_parts(47_163_489, 0).saturating_mul(x.into())) + // Minimum execution time: 121_730 nanoseconds. + Weight::from_parts(123_981_000, 18229) + // Standard Error: 154_533 + .saturating_add(Weight::from_parts(45_598_956, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -219,10 +219,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `946 + x * (48 ±0)` // Estimated: `4704 + x * (98 ±0)` - // Minimum execution time: 30_160 nanoseconds. - Weight::from_parts(34_225_633, 4704) - // Standard Error: 2_459 - .saturating_add(Weight::from_parts(196_665, 0).saturating_mul(x.into())) + // Minimum execution time: 26_500 nanoseconds. + Weight::from_parts(36_332_094, 4704) + // Standard Error: 2_111 + .saturating_add(Weight::from_parts(187_547, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 98).saturating_mul(x.into())) @@ -235,8 +235,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `371` // Estimated: `3712` - // Minimum execution time: 41_220 nanoseconds. - Weight::from_parts(59_570_000, 3712) + // Minimum execution time: 28_900 nanoseconds. + Weight::from_parts(35_300_000, 3712) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -248,8 +248,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `322` // Estimated: `3614` - // Minimum execution time: 43_800 nanoseconds. - Weight::from_parts(57_670_000, 3614) + // Minimum execution time: 30_500 nanoseconds. + Weight::from_parts(35_560_000, 3614) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -267,8 +267,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `650` // Estimated: `11796` - // Minimum execution time: 75_390 nanoseconds. - Weight::from_parts(105_331_000, 11796) + // Minimum execution time: 63_281 nanoseconds. + Weight::from_parts(75_700_000, 11796) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -278,8 +278,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `216` // Estimated: `2691` - // Minimum execution time: 25_120 nanoseconds. - Weight::from_parts(29_190_000, 2691) + // Minimum execution time: 23_790 nanoseconds. + Weight::from_parts(29_200_000, 2691) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -297,8 +297,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `670` // Estimated: `11856` - // Minimum execution time: 66_220 nanoseconds. - Weight::from_parts(75_400_000, 11856) + // Minimum execution time: 54_520 nanoseconds. + Weight::from_parts(74_880_000, 11856) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -308,8 +308,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `236` // Estimated: `2711` - // Minimum execution time: 23_360 nanoseconds. - Weight::from_parts(26_850_000, 2711) + // Minimum execution time: 26_350 nanoseconds. + Weight::from_parts(27_350_000, 2711) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -333,12 +333,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `2374 + x * (103 ±0) + y * (52 ±0)` // Estimated: `25391 + x * (530 ±0) + y * (265 ±0)` - // Minimum execution time: 113_761 nanoseconds. - Weight::from_parts(117_983_111, 25391) - // Standard Error: 14_091 - .saturating_add(Weight::from_parts(327_450, 0).saturating_mul(x.into())) - // Standard Error: 4_622 - .saturating_add(Weight::from_parts(179_482, 0).saturating_mul(y.into())) + // Minimum execution time: 117_260 nanoseconds. + Weight::from_parts(127_979_202, 25391) + // Standard Error: 7_758 + .saturating_add(Weight::from_parts(273_960, 0).saturating_mul(x.into())) + // Standard Error: 2_545 + .saturating_add(Weight::from_parts(138_185, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) .saturating_add(Weight::from_parts(0, 530).saturating_mul(x.into())) @@ -352,8 +352,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 30_450 nanoseconds. - Weight::from_parts(34_650_000, 5302) + // Minimum execution time: 25_010 nanoseconds. + Weight::from_parts(33_430_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -380,10 +380,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `755 + x * (558 ±0)` // Estimated: `26542 + x * (13492 ±2)` - // Minimum execution time: 100_680 nanoseconds. - Weight::from_parts(122_370_000, 26542) - // Standard Error: 170_841 - .saturating_add(Weight::from_parts(39_189_991, 0).saturating_mul(x.into())) + // Minimum execution time: 107_520 nanoseconds. + Weight::from_parts(111_190_000, 26542) + // Standard Error: 83_588 + .saturating_add(Weight::from_parts(37_116_448, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -398,8 +398,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 31_101 nanoseconds. - Weight::from_parts(37_080_000, 5546) + // Minimum execution time: 28_590 nanoseconds. + Weight::from_parts(37_630_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -411,8 +411,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 27_590 nanoseconds. - Weight::from_parts(33_460_000, 5302) + // Minimum execution time: 30_010 nanoseconds. + Weight::from_parts(33_610_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -436,8 +436,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1066` // Estimated: `23667` - // Minimum execution time: 75_210 nanoseconds. - Weight::from_parts(105_021_000, 23667) + // Minimum execution time: 91_720 nanoseconds. + Weight::from_parts(94_170_000, 23667) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -449,8 +449,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `176` // Estimated: `5302` - // Minimum execution time: 32_550 nanoseconds. - Weight::from_parts(34_180_000, 5302) + // Minimum execution time: 27_890 nanoseconds. + Weight::from_parts(33_760_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -476,8 +476,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1243` // Estimated: `28447` - // Minimum execution time: 91_421 nanoseconds. - Weight::from_parts(113_260_000, 28447) + // Minimum execution time: 109_950 nanoseconds. + Weight::from_parts(127_880_000, 28447) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -501,8 +501,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `1188` // Estimated: `24399` - // Minimum execution time: 78_020 nanoseconds. - Weight::from_parts(105_651_000, 24399) + // Minimum execution time: 93_580 nanoseconds. + Weight::from_parts(108_650_000, 24399) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -514,8 +514,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 33_220 nanoseconds. - Weight::from_parts(34_500_000, 5546) + // Minimum execution time: 30_350 nanoseconds. + Weight::from_parts(34_140_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -527,8 +527,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `298` // Estimated: `5546` - // Minimum execution time: 31_110 nanoseconds. - Weight::from_parts(34_490_000, 5546) + // Minimum execution time: 30_280 nanoseconds. + Weight::from_parts(36_430_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -550,8 +550,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `438` // Estimated: `11670` - // Minimum execution time: 57_050 nanoseconds. - Weight::from_parts(58_430_000, 11670) + // Minimum execution time: 47_530 nanoseconds. + Weight::from_parts(57_600_000, 11670) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -564,10 +564,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `118 + y * (48 ±0)` // Estimated: `5180 + y * (96 ±0)` - // Minimum execution time: 9_300 nanoseconds. - Weight::from_parts(11_069_735, 5180) - // Standard Error: 2_533 - .saturating_add(Weight::from_parts(128_697, 0).saturating_mul(y.into())) + // Minimum execution time: 9_090 nanoseconds. + Weight::from_parts(12_813_747, 5180) + // Standard Error: 3_345 + .saturating_add(Weight::from_parts(115_123, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(Weight::from_parts(0, 96).saturating_mul(y.into())) } @@ -593,12 +593,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0 + x * (5122 ±0) + y * (2400 ±0)` // Estimated: `13898 + x * (26124 ±53) + y * (6816 ±26)` - // Minimum execution time: 36_200 nanoseconds. - Weight::from_parts(43_250_000, 13898) - // Standard Error: 182_967 - .saturating_add(Weight::from_parts(30_359_281, 0).saturating_mul(x.into())) - // Standard Error: 91_241 - .saturating_add(Weight::from_parts(2_857_843, 0).saturating_mul(y.into())) + // Minimum execution time: 37_530 nanoseconds. + Weight::from_parts(38_070_000, 13898) + // Standard Error: 167_246 + .saturating_add(Weight::from_parts(29_946_513, 0).saturating_mul(x.into())) + // Standard Error: 83_401 + .saturating_add(Weight::from_parts(2_881_371, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -621,10 +621,10 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `485 + y * (219 ±0)` // Estimated: `16898 + y * (3483 ±0)` - // Minimum execution time: 59_200 nanoseconds. - Weight::from_parts(39_786_426, 16898) - // Standard Error: 129_057 - .saturating_add(Weight::from_parts(22_159_064, 0).saturating_mul(y.into())) + // Minimum execution time: 53_111 nanoseconds. + Weight::from_parts(59_768_220, 16898) + // Standard Error: 56_493 + .saturating_add(Weight::from_parts(21_235_473, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(y.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -635,8 +635,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_930 nanoseconds. - Weight::from_parts(2_270_000, 0) + // Minimum execution time: 2_050 nanoseconds. + Weight::from_parts(2_300_000, 0) } /// Storage: ParachainStaking DelegatorState (r:1 w:0) /// Proof Skipped: ParachainStaking DelegatorState (max_values: None, max_size: None, mode: Measured) @@ -648,12 +648,12 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `718 + x * (34 ±0) + y * (48 ±0)` // Estimated: `6134 + x * (70 ±0) + y * (98 ±0)` - // Minimum execution time: 46_420 nanoseconds. - Weight::from_parts(53_869_963, 6134) - // Standard Error: 1_713 - .saturating_add(Weight::from_parts(85_404, 0).saturating_mul(x.into())) - // Standard Error: 5_128 - .saturating_add(Weight::from_parts(70_357, 0).saturating_mul(y.into())) + // Minimum execution time: 43_000 nanoseconds. + Weight::from_parts(54_178_821, 6134) + // Standard Error: 920 + .saturating_add(Weight::from_parts(78_822, 0).saturating_mul(x.into())) + // Standard Error: 2_754 + .saturating_add(Weight::from_parts(66_407, 0).saturating_mul(y.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(Weight::from_parts(0, 70).saturating_mul(x.into())) @@ -684,14 +684,14 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `0 + x * (84 ±0) + y * (33 ±0) + z * (114 ±0)` // Estimated: `127262 + x * (367 ±0) + y * (73 ±0) + z * (230 ±1)` - // Minimum execution time: 124_000 nanoseconds. - Weight::from_parts(113_273_209, 127262) - // Standard Error: 4_496 - .saturating_add(Weight::from_parts(250_836, 0).saturating_mul(x.into())) - // Standard Error: 4_508 - .saturating_add(Weight::from_parts(38_880, 0).saturating_mul(y.into())) - // Standard Error: 15_879 - .saturating_add(Weight::from_parts(321_534, 0).saturating_mul(z.into())) + // Minimum execution time: 117_820 nanoseconds. + Weight::from_parts(111_718_224, 127262) + // Standard Error: 2_457 + .saturating_add(Weight::from_parts(237_230, 0).saturating_mul(x.into())) + // Standard Error: 2_464 + .saturating_add(Weight::from_parts(43_725, 0).saturating_mul(y.into())) + // Standard Error: 8_678 + .saturating_add(Weight::from_parts(311_558, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(8)) .saturating_add(Weight::from_parts(0, 367).saturating_mul(x.into())) @@ -704,8 +704,8 @@ impl pallet_parachain_staking::weights::WeightInfo for // Proof Size summary in bytes: // Measured: `139` // Estimated: `2607` - // Minimum execution time: 26_010 nanoseconds. - Weight::from_parts(28_280_000, 2607) + // Minimum execution time: 28_180 nanoseconds. + Weight::from_parts(28_980_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_preimage.rs b/runtime/common/src/weights/pallet_preimage.rs index 58eda6a9a..7aa0e5999 100644 --- a/runtime/common/src/weights/pallet_preimage.rs +++ b/runtime/common/src/weights/pallet_preimage.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,7 +19,7 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -61,10 +61,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `179` // Estimated: `2566` - // Minimum execution time: 38_370 nanoseconds. - Weight::from_parts(39_390_000, 2566) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_081, 0).saturating_mul(s.into())) + // Minimum execution time: 35_760 nanoseconds. + Weight::from_parts(42_580_000, 2566) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_952, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -77,10 +77,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 23_661 nanoseconds. - Weight::from_parts(24_500_000, 2566) + // Minimum execution time: 25_440 nanoseconds. + Weight::from_parts(25_870_000, 2566) // Standard Error: 5 - .saturating_add(Weight::from_parts(3_039, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(2_931, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -93,10 +93,10 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 24_910 nanoseconds. - Weight::from_parts(25_440_000, 2566) + // Minimum execution time: 24_220 nanoseconds. + Weight::from_parts(24_880_000, 2566) // Standard Error: 6 - .saturating_add(Weight::from_parts(3_036, 0).saturating_mul(s.into())) + .saturating_add(Weight::from_parts(2_963, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -108,8 +108,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `357` // Estimated: `2566` - // Minimum execution time: 60_480 nanoseconds. - Weight::from_parts(69_720_000, 2566) + // Minimum execution time: 60_650 nanoseconds. + Weight::from_parts(71_670_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -121,8 +121,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 43_861 nanoseconds. - Weight::from_parts(49_840_000, 2566) + // Minimum execution time: 44_380 nanoseconds. + Weight::from_parts(50_081_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -132,8 +132,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `220` // Estimated: `2566` - // Minimum execution time: 39_420 nanoseconds. - Weight::from_parts(44_010_000, 2566) + // Minimum execution time: 39_850 nanoseconds. + Weight::from_parts(44_890_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +143,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 26_310 nanoseconds. - Weight::from_parts(30_330_000, 2566) + // Minimum execution time: 26_380 nanoseconds. + Weight::from_parts(30_250_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,8 +154,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `42` // Estimated: `2566` - // Minimum execution time: 31_400 nanoseconds. - Weight::from_parts(34_880_000, 2566) + // Minimum execution time: 32_940 nanoseconds. + Weight::from_parts(37_240_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -165,8 +165,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 14_320 nanoseconds. - Weight::from_parts(17_110_000, 2566) + // Minimum execution time: 14_740 nanoseconds. + Weight::from_parts(15_950_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +178,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `144` // Estimated: `2566` - // Minimum execution time: 40_830 nanoseconds. - Weight::from_parts(48_820_000, 2566) + // Minimum execution time: 40_510 nanoseconds. + Weight::from_parts(47_441_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -189,8 +189,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 14_470 nanoseconds. - Weight::from_parts(16_390_000, 2566) + // Minimum execution time: 15_111 nanoseconds. + Weight::from_parts(18_110_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -200,8 +200,8 @@ impl pallet_preimage::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 15_280 nanoseconds. - Weight::from_parts(16_370_000, 2566) + // Minimum execution time: 15_910 nanoseconds. + Weight::from_parts(16_980_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_proxy.rs b/runtime/common/src/weights/pallet_proxy.rs index 76e7767ff..f47d56e53 100644 --- a/runtime/common/src/weights/pallet_proxy.rs +++ b/runtime/common/src/weights/pallet_proxy.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,7 +19,7 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -59,10 +59,10 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo Weight { + fn reject_announcement(a: u32, p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `404 + a * (72 ±0)` // Estimated: `7443` - // Minimum execution time: 27_000 nanoseconds. - Weight::from_parts(38_221_317, 7443) - // Standard Error: 15_117 - .saturating_add(Weight::from_parts(98_396, 0).saturating_mul(a.into())) + // Minimum execution time: 27_690 nanoseconds. + Weight::from_parts(31_813_643, 7443) + // Standard Error: 15_496 + .saturating_add(Weight::from_parts(175_113, 0).saturating_mul(a.into())) + // Standard Error: 16_011 + .saturating_add(Weight::from_parts(72_840, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -132,12 +134,12 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo pallet_proxy::weights::WeightInfo for WeightInfo Weight { + fn create_pure(_p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `102` // Estimated: `3844` - // Minimum execution time: 30_770 nanoseconds. - Weight::from_parts(36_796_359, 3844) - // Standard Error: 14_971 - .saturating_add(Weight::from_parts(7_957, 0).saturating_mul(p.into())) + // Minimum execution time: 31_130 nanoseconds. + Weight::from_parts(38_086_277, 3844) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -202,10 +202,10 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `35` // Estimated: `503` - // Minimum execution time: 6_280 nanoseconds. - Weight::from_parts(6_660_000, 503) + // Minimum execution time: 6_040 nanoseconds. + Weight::from_parts(7_220_000, 503) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -70,10 +70,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 5_500 nanoseconds. - Weight::from_parts(13_195_112, 13142) - // Standard Error: 12_609 - .saturating_add(Weight::from_parts(584_778, 0).saturating_mul(s.into())) + // Minimum execution time: 4_780 nanoseconds. + Weight::from_parts(11_602_646, 13142) + // Standard Error: 11_992 + .saturating_add(Weight::from_parts(563_088, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,8 +81,8 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_840 nanoseconds. - Weight::from_parts(12_220_000, 0) + // Minimum execution time: 7_360 nanoseconds. + Weight::from_parts(7_750_000, 0) } /// Storage: Preimage PreimageFor (r:1 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) @@ -93,10 +93,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `211 + s * (1 ±0)` // Estimated: `5252 + s * (1 ±0)` - // Minimum execution time: 31_230 nanoseconds. - Weight::from_parts(31_790_000, 5252) - // Standard Error: 7 - .saturating_add(Weight::from_parts(2_427, 0).saturating_mul(s.into())) + // Minimum execution time: 31_830 nanoseconds. + Weight::from_parts(37_380_000, 5252) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_229, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) @@ -107,29 +107,29 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_810 nanoseconds. - Weight::from_parts(11_990_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 10_700 nanoseconds. + Weight::from_parts(13_030_000, 0).saturating_add(T::DbWeight::get().writes(1)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_410 nanoseconds. - Weight::from_parts(9_010_000, 0) + // Minimum execution time: 7_980 nanoseconds. + Weight::from_parts(8_790_000, 0) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_910 nanoseconds. - Weight::from_parts(4_870_000, 0) + // Minimum execution time: 3_680 nanoseconds. + Weight::from_parts(4_700_000, 0) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_180 nanoseconds. - Weight::from_parts(4_950_000, 0) + // Minimum execution time: 3_570 nanoseconds. + Weight::from_parts(4_540_000, 0) } /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) @@ -138,10 +138,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 27_770 nanoseconds. - Weight::from_parts(32_473_125, 13142) - // Standard Error: 15_953 - .saturating_add(Weight::from_parts(471_984, 0).saturating_mul(s.into())) + // Minimum execution time: 18_750 nanoseconds. + Weight::from_parts(25_732_878, 13142) + // Standard Error: 21_418 + .saturating_add(Weight::from_parts(627_490, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,10 +154,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `114 + s * (181 ±0)` // Estimated: `13142` - // Minimum execution time: 27_441 nanoseconds. - Weight::from_parts(30_393_509, 13142) - // Standard Error: 15_436 - .saturating_add(Weight::from_parts(835_686, 0).saturating_mul(s.into())) + // Minimum execution time: 23_610 nanoseconds. + Weight::from_parts(28_867_441, 13142) + // Standard Error: 12_753 + .saturating_add(Weight::from_parts(732_013, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -170,10 +170,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `297 + s * (189 ±0)` // Estimated: `15669` - // Minimum execution time: 24_280 nanoseconds. - Weight::from_parts(39_886_526, 15669) - // Standard Error: 24_317 - .saturating_add(Weight::from_parts(501_616, 0).saturating_mul(s.into())) + // Minimum execution time: 21_810 nanoseconds. + Weight::from_parts(34_859_221, 15669) + // Standard Error: 14_855 + .saturating_add(Weight::from_parts(533_329, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -186,10 +186,10 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `321 + s * (189 ±0)` // Estimated: `15669` - // Minimum execution time: 30_500 nanoseconds. - Weight::from_parts(36_611_230, 15669) - // Standard Error: 16_371 - .saturating_add(Weight::from_parts(775_681, 0).saturating_mul(s.into())) + // Minimum execution time: 26_600 nanoseconds. + Weight::from_parts(38_582_510, 15669) + // Standard Error: 24_205 + .saturating_add(Weight::from_parts(646_684, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_timestamp.rs b/runtime/common/src/weights/pallet_timestamp.rs index 572f73ec3..1f872fb41 100644 --- a/runtime/common/src/weights/pallet_timestamp.rs +++ b/runtime/common/src/weights/pallet_timestamp.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,7 +19,7 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -60,8 +60,8 @@ impl pallet_timestamp::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `256` // Estimated: `1006` - // Minimum execution time: 22_700 nanoseconds. - Weight::from_parts(27_141_000, 1006) + // Minimum execution time: 22_220 nanoseconds. + Weight::from_parts(23_490_000, 1006) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -69,7 +69,7 @@ impl pallet_timestamp::weights::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `128` // Estimated: `0` - // Minimum execution time: 8_751 nanoseconds. - Weight::from_parts(9_010_000, 0) + // Minimum execution time: 8_560 nanoseconds. + Weight::from_parts(9_640_000, 0) } } diff --git a/runtime/common/src/weights/pallet_treasury.rs b/runtime/common/src/weights/pallet_treasury.rs index 43c0ca822..8cd14ef08 100644 --- a/runtime/common/src/weights/pallet_treasury.rs +++ b/runtime/common/src/weights/pallet_treasury.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,7 +19,7 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,8 +62,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `42` // Estimated: `1396` - // Minimum execution time: 23_510 nanoseconds. - Weight::from_parts(24_020_000, 1396) + // Minimum execution time: 23_341 nanoseconds. + Weight::from_parts(24_721_000, 1396) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -75,8 +75,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `179` // Estimated: `499` - // Minimum execution time: 36_650 nanoseconds. - Weight::from_parts(37_840_000, 499) + // Minimum execution time: 36_180 nanoseconds. + Weight::from_parts(37_520_000, 499) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -88,8 +88,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `545` // Estimated: `7797` - // Minimum execution time: 51_540 nanoseconds. - Weight::from_parts(62_330_000, 7797) + // Minimum execution time: 56_780 nanoseconds. + Weight::from_parts(58_360_000, 7797) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -102,10 +102,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `500 + p * (8 ±0)` // Estimated: `3480` - // Minimum execution time: 17_280 nanoseconds. - Weight::from_parts(21_235_363, 3480) - // Standard Error: 5_654 - .saturating_add(Weight::from_parts(90_177, 0).saturating_mul(p.into())) + // Minimum execution time: 17_220 nanoseconds. + Weight::from_parts(22_887_238, 3480) + // Standard Error: 6_556 + .saturating_add(Weight::from_parts(35_460, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,8 +115,8 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `127` // Estimated: `897` - // Minimum execution time: 11_930 nanoseconds. - Weight::from_parts(12_280_000, 897) + // Minimum execution time: 13_500 nanoseconds. + Weight::from_parts(16_180_000, 897) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,10 +137,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `383 + p * (318 ±0)` // Estimated: `5423 + p * (7797 ±0)` - // Minimum execution time: 57_160 nanoseconds. - Weight::from_parts(63_741_900, 5423) - // Standard Error: 309_254 - .saturating_add(Weight::from_parts(48_798_272, 0).saturating_mul(p.into())) + // Minimum execution time: 64_110 nanoseconds. + Weight::from_parts(112_864_897, 5423) + // Standard Error: 178_607 + .saturating_add(Weight::from_parts(45_837_241, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(5)) diff --git a/runtime/common/src/weights/pallet_utility.rs b/runtime/common/src/weights/pallet_utility.rs index 3868dd5fa..0c0d143e8 100644 --- a/runtime/common/src/weights/pallet_utility.rs +++ b/runtime/common/src/weights/pallet_utility.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,7 +19,7 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -57,43 +57,43 @@ impl pallet_utility::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_100 nanoseconds. - Weight::from_parts(59_724_347, 0) - // Standard Error: 39_385 - .saturating_add(Weight::from_parts(6_610_000, 0).saturating_mul(c.into())) + // Minimum execution time: 10_870 nanoseconds. + Weight::from_parts(11_510_000, 0) + // Standard Error: 9_150 + .saturating_add(Weight::from_parts(6_647_766, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_340 nanoseconds. - Weight::from_parts(9_110_000, 0) + // Minimum execution time: 7_520 nanoseconds. + Weight::from_parts(9_410_000, 0) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_930 nanoseconds. - Weight::from_parts(31_588_737, 0) - // Standard Error: 20_855 - .saturating_add(Weight::from_parts(6_941_164, 0).saturating_mul(c.into())) + // Minimum execution time: 10_510 nanoseconds. + Weight::from_parts(78_999_068, 0) + // Standard Error: 17_202 + .saturating_add(Weight::from_parts(6_728_886, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_720 nanoseconds. - Weight::from_parts(14_040_000, 0) + // Minimum execution time: 13_910 nanoseconds. + Weight::from_parts(14_460_000, 0) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_960 nanoseconds. - Weight::from_parts(49_724_719, 0) - // Standard Error: 59_993 - .saturating_add(Weight::from_parts(6_735_206, 0).saturating_mul(c.into())) + // Minimum execution time: 11_260 nanoseconds. + Weight::from_parts(8_094_244, 0) + // Standard Error: 58_787 + .saturating_add(Weight::from_parts(6_673_782, 0).saturating_mul(c.into())) } } diff --git a/runtime/common/src/weights/pallet_vesting.rs b/runtime/common/src/weights/pallet_vesting.rs index 8dfcf5a38..460b986e7 100644 --- a/runtime/common/src/weights/pallet_vesting.rs +++ b/runtime/common/src/weights/pallet_vesting.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,7 +19,7 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -62,12 +62,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `405 + l * (25 ±0) + s * (40 ±0)` // Estimated: `7418` - // Minimum execution time: 38_890 nanoseconds. - Weight::from_parts(42_969_294, 7418) - // Standard Error: 12_255 - .saturating_add(Weight::from_parts(40_030, 0).saturating_mul(l.into())) - // Standard Error: 21_805 - .saturating_add(Weight::from_parts(81_302, 0).saturating_mul(s.into())) + // Minimum execution time: 38_830 nanoseconds. + Weight::from_parts(40_243_387, 7418) + // Standard Error: 13_742 + .saturating_add(Weight::from_parts(93_058, 0).saturating_mul(l.into())) + // Standard Error: 24_449 + .saturating_add(Weight::from_parts(163_917, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -81,12 +81,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `405 + l * (25 ±0) + s * (40 ±0)` // Estimated: `7418` - // Minimum execution time: 38_010 nanoseconds. - Weight::from_parts(39_519_436, 7418) - // Standard Error: 11_747 - .saturating_add(Weight::from_parts(103_431, 0).saturating_mul(l.into())) - // Standard Error: 20_901 - .saturating_add(Weight::from_parts(128_041, 0).saturating_mul(s.into())) + // Minimum execution time: 38_450 nanoseconds. + Weight::from_parts(43_347_252, 7418) + // Standard Error: 9_766 + .saturating_add(Weight::from_parts(48_570, 0).saturating_mul(l.into())) + // Standard Error: 17_375 + .saturating_add(Weight::from_parts(17_934, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -98,14 +98,16 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. - fn vest_other_locked(l: u32, _s: u32) -> Weight { + fn vest_other_locked(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `544 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 43_310 nanoseconds. - Weight::from_parts(54_519_294, 10025) - // Standard Error: 12_505 - .saturating_add(Weight::from_parts(56_976, 0).saturating_mul(l.into())) + // Minimum execution time: 43_880 nanoseconds. + Weight::from_parts(46_021_890, 10025) + // Standard Error: 12_235 + .saturating_add(Weight::from_parts(93_769, 0).saturating_mul(l.into())) + // Standard Error: 21_768 + .saturating_add(Weight::from_parts(115_349, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -117,14 +119,16 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. - fn vest_other_unlocked(l: u32, _s: u32) -> Weight { + fn vest_other_unlocked(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `544 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 43_180 nanoseconds. - Weight::from_parts(49_079_525, 10025) - // Standard Error: 10_464 - .saturating_add(Weight::from_parts(74_298, 0).saturating_mul(l.into())) + // Minimum execution time: 43_890 nanoseconds. + Weight::from_parts(45_171_083, 10025) + // Standard Error: 14_381 + .saturating_add(Weight::from_parts(100_199, 0).saturating_mul(l.into())) + // Standard Error: 25_587 + .saturating_add(Weight::from_parts(158_229, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -136,12 +140,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[0, 27]`. - fn vested_transfer(_l: u32, _s: u32) -> Weight { + fn vested_transfer(l: u32, _s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `615 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 62_560 nanoseconds. - Weight::from_parts(83_541_824, 10025) + // Minimum execution time: 62_820 nanoseconds. + Weight::from_parts(74_828_952, 10025) + // Standard Error: 20_587 + .saturating_add(Weight::from_parts(27_325, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -157,10 +163,10 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `754 + l * (25 ±0) + s * (40 ±0)` // Estimated: `12632` - // Minimum execution time: 66_010 nanoseconds. - Weight::from_parts(74_974_962, 12632) - // Standard Error: 17_154 - .saturating_add(Weight::from_parts(76_153, 0).saturating_mul(l.into())) + // Minimum execution time: 66_680 nanoseconds. + Weight::from_parts(78_411_765, 12632) + // Standard Error: 16_120 + .saturating_add(Weight::from_parts(6_898, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -172,16 +178,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. - fn not_unlocking_merge_schedules(l: u32, s: u32) -> Weight { + fn not_unlocking_merge_schedules(_l: u32, _s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `542 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 44_480 nanoseconds. - Weight::from_parts(49_199_052, 10025) - // Standard Error: 14_207 - .saturating_add(Weight::from_parts(89_429, 0).saturating_mul(l.into())) - // Standard Error: 26_237 - .saturating_add(Weight::from_parts(43_668, 0).saturating_mul(s.into())) + // Minimum execution time: 45_190 nanoseconds. + Weight::from_parts(54_486_163, 10025) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -193,14 +195,16 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. - fn unlocking_merge_schedules(l: u32, _s: u32) -> Weight { + fn unlocking_merge_schedules(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `542 + l * (25 ±0) + s * (40 ±0)` // Estimated: `10025` - // Minimum execution time: 45_050 nanoseconds. - Weight::from_parts(54_111_045, 10025) - // Standard Error: 14_322 - .saturating_add(Weight::from_parts(67_882, 0).saturating_mul(l.into())) + // Minimum execution time: 45_100 nanoseconds. + Weight::from_parts(46_158_877, 10025) + // Standard Error: 14_211 + .saturating_add(Weight::from_parts(105_475, 0).saturating_mul(l.into())) + // Standard Error: 26_245 + .saturating_add(Weight::from_parts(168_563, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 6c8d91b0d..59d4edf57 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.3" +version = "0.5.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index c57f3f4b0..a3eb21547 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -90,10 +90,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("zeitgeist"), impl_name: create_runtime_str!("zeitgeist"), authoring_version: 1, - spec_version: 52, + spec_version: 53, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 26, + transaction_version: 27, state_version: 1, }; diff --git a/zrml/authorized/Cargo.toml b/zrml/authorized/Cargo.toml index 8dbfc6e58..b55fdd302 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.3" +version = "0.5.0" diff --git a/zrml/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index 9a1cadd91..93ba4256a 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/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,7 +19,7 @@ //! Autogenerated weights for zrml_authorized //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-10-25`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_authorized // --extrinsic=* // --execution=wasm @@ -72,12 +72,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[1, 63]`. fn authorize_market_outcome_first_report(m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `823 + m * (22 ±0)` + // Measured: `805 + m * (22 ±0)` // Estimated: `9194` - // Minimum execution time: 37_880 nanoseconds. - Weight::from_parts(44_691_872, 9194) - // Standard Error: 2_332 - .saturating_add(Weight::from_parts(27_672, 0).saturating_mul(m.into())) + // Minimum execution time: 40_600 nanoseconds. + Weight::from_parts(46_642_457, 9194) + // Standard Error: 26_413 + .saturating_add(Weight::from_parts(190_563, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -89,8 +89,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `577` // Estimated: `5677` - // Minimum execution time: 30_361 nanoseconds. - Weight::from_parts(34_830_000, 5677) + // Minimum execution time: 35_410 nanoseconds. + Weight::from_parts(36_251_000, 5677) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -98,8 +98,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 300 nanoseconds. - Weight::from_parts(360_000, 0) + // Minimum execution time: 290 nanoseconds. + Weight::from_parts(320_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -107,8 +107,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `217` // Estimated: `2524` - // Minimum execution time: 8_330 nanoseconds. - Weight::from_parts(10_160_000, 2524) + // Minimum execution time: 10_110 nanoseconds. + Weight::from_parts(12_690_000, 2524) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -116,8 +116,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_440 nanoseconds. - Weight::from_parts(2_710_000, 0) + // Minimum execution time: 2_680 nanoseconds. + Weight::from_parts(2_980_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -125,22 +125,22 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `217` // Estimated: `2524` - // Minimum execution time: 7_620 nanoseconds. - Weight::from_parts(9_170_000, 2524).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 9_200 nanoseconds. + Weight::from_parts(9_520_000, 2524).saturating_add(T::DbWeight::get().reads(1)) } fn has_failed_weight() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260 nanoseconds. - Weight::from_parts(330_000, 0) + // Minimum execution time: 250 nanoseconds. + Weight::from_parts(280_000, 0) } fn on_global_dispute_weight() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 270 nanoseconds. - Weight::from_parts(340_000, 0) + Weight::from_parts(310_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:0 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) @@ -148,7 +148,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_150 nanoseconds. - Weight::from_parts(2_470_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 2_390 nanoseconds. + Weight::from_parts(2_970_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index 4e2c9eec6..5bad41489 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.3" +version = "0.5.0" diff --git a/zrml/court/src/weights.rs b/zrml/court/src/weights.rs index bfc2762fe..021007cd0 100644 --- a/zrml/court/src/weights.rs +++ b/zrml/court/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,7 +19,7 @@ //! Autogenerated weights for zrml_court //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_court // --extrinsic=* // --execution=wasm @@ -83,12 +83,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `j` is `[0, 999]`. fn join_court(j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1130 + j * (72 ±0)` + // Measured: `1122 + j * (72 ±0)` // Estimated: `78997` - // Minimum execution time: 42_390 nanoseconds. - Weight::from_parts(50_893_889, 78997) - // Standard Error: 356 - .saturating_add(Weight::from_parts(131_671, 0).saturating_mul(j.into())) + // Minimum execution time: 48_070 nanoseconds. + Weight::from_parts(53_578_638, 78997) + // Standard Error: 1_163 + .saturating_add(Weight::from_parts(123_220, 0).saturating_mul(j.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -102,14 +102,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 5]`. fn delegate(j: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + j * (74 ±0) + d * (685 ±0)` + // Measured: `0 + j * (74 ±0) + d * (683 ±0)` // Estimated: `78997 + d * (2726 ±0)` - // Minimum execution time: 71_251 nanoseconds. - Weight::from_parts(42_003_599, 78997) - // Standard Error: 489 - .saturating_add(Weight::from_parts(169_094, 0).saturating_mul(j.into())) - // Standard Error: 106_757 - .saturating_add(Weight::from_parts(9_146_226, 0).saturating_mul(d.into())) + // Minimum execution time: 81_570 nanoseconds. + Weight::from_parts(49_772_271, 78997) + // Standard Error: 1_786 + .saturating_add(Weight::from_parts(165_209, 0).saturating_mul(j.into())) + // Standard Error: 387_683 + .saturating_add(Weight::from_parts(8_924_347, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(3)) @@ -122,12 +122,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `j` is `[0, 999]`. fn prepare_exit_court(j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1068 + j * (72 ±0)` + // Measured: `1060 + j * (72 ±0)` // Estimated: `75223` - // Minimum execution time: 27_380 nanoseconds. - Weight::from_parts(35_546_685, 75223) - // Standard Error: 287 - .saturating_add(Weight::from_parts(106_675, 0).saturating_mul(j.into())) + // Minimum execution time: 31_170 nanoseconds. + Weight::from_parts(39_874_247, 75223) + // Standard Error: 1_059 + .saturating_add(Weight::from_parts(104_918, 0).saturating_mul(j.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -139,8 +139,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `307` // Estimated: `6500` - // Minimum execution time: 37_730 nanoseconds. - Weight::from_parts(43_521_000, 6500) + // Minimum execution time: 42_610 nanoseconds. + Weight::from_parts(44_230_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -152,8 +152,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `307` // Estimated: `6500` - // Minimum execution time: 36_050 nanoseconds. - Weight::from_parts(43_831_000, 6500) + // Minimum execution time: 37_370 nanoseconds. + Weight::from_parts(43_080_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -164,12 +164,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `450 + d * (53 ±0)` + // Measured: `451 + d * (53 ±0)` // Estimated: `155273` - // Minimum execution time: 52_671 nanoseconds. - Weight::from_parts(62_979_576, 155273) - // Standard Error: 465 - .saturating_add(Weight::from_parts(118_018, 0).saturating_mul(d.into())) + // Minimum execution time: 55_760 nanoseconds. + Weight::from_parts(63_696_914, 155273) + // Standard Error: 1_663 + .saturating_add(Weight::from_parts(128_245, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,12 +186,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn denounce_vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1563 + d * (53 ±0)` + // Measured: `1576 + d * (53 ±0)` // Estimated: `163667` - // Minimum execution time: 56_160 nanoseconds. - Weight::from_parts(66_113_555, 163667) - // Standard Error: 640 - .saturating_add(Weight::from_parts(164_953, 0).saturating_mul(d.into())) + // Minimum execution time: 60_591 nanoseconds. + Weight::from_parts(68_553_544, 163667) + // Standard Error: 2_739 + .saturating_add(Weight::from_parts(209_821, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -208,12 +208,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn reveal_vote(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2145 + d * (53 ±0)` + // Measured: `2146 + d * (53 ±0)` // Estimated: `163667` - // Minimum execution time: 89_220 nanoseconds. - Weight::from_parts(105_277_297, 163667) - // Standard Error: 689 - .saturating_add(Weight::from_parts(114_305, 0).saturating_mul(d.into())) + // Minimum execution time: 98_871 nanoseconds. + Weight::from_parts(112_254_457, 163667) + // Standard Error: 2_693 + .saturating_add(Weight::from_parts(122_916, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -245,20 +245,20 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `f` is `[0, 62]`. fn appeal(j: u32, a: u32, _r: u32, _f: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + j * (132 ±0) + a * (35486 ±0) + r * (16 ±0) + f * (16 ±0)` - // Estimated: `515439 + j * (203 ±1) + a * (314898 ±368)` - // Minimum execution time: 3_470_854 nanoseconds. - Weight::from_parts(3_772_958_000, 515439) - // Standard Error: 25_467 - .saturating_add(Weight::from_parts(6_401_836, 0).saturating_mul(j.into())) - // Standard Error: 9_089_079 - .saturating_add(Weight::from_parts(4_296_122_826, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(109)) - .saturating_add(T::DbWeight::get().reads((116_u64).saturating_mul(a.into()))) + // Measured: `880 + j * (129 ±0) + a * (27536 ±0) + r * (16 ±0) + f * (16 ±0)` + // Estimated: `515439 + j * (194 ±3) + a * (318078 ±1_049)` + // Minimum execution time: 3_512_041 nanoseconds. + Weight::from_parts(3_757_631_000, 515439) + // Standard Error: 80_324 + .saturating_add(Weight::from_parts(5_861_528, 0).saturating_mul(j.into())) + // Standard Error: 27_966_670 + .saturating_add(Weight::from_parts(4_428_133_284, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((127_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(100)) - .saturating_add(T::DbWeight::get().writes((116_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 203).saturating_mul(j.into())) - .saturating_add(Weight::from_parts(0, 314898).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().writes((117_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 194).saturating_mul(j.into())) + .saturating_add(Weight::from_parts(0, 318078).saturating_mul(a.into())) } /// Storage: Court Courts (r:1 w:1) /// Proof: Court Courts (max_values: None, max_size: Some(349), added: 2824, mode: MaxEncodedLen) @@ -271,12 +271,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[5, 510]`. fn reassign_court_stakes(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `945 + d * (587 ±0)` + // Measured: `1011 + d * (587 ±0)` // Estimated: `157880 + d * (5333 ±0)` - // Minimum execution time: 148_160 nanoseconds. - Weight::from_parts(151_951_000, 157880) - // Standard Error: 38_869 - .saturating_add(Weight::from_parts(66_881_062, 0).saturating_mul(d.into())) + // Minimum execution time: 154_111 nanoseconds. + Weight::from_parts(176_730_000, 157880) + // Standard Error: 111_534 + .saturating_add(Weight::from_parts(67_369_442, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -289,15 +289,11 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_650 nanoseconds. - Weight::from_parts(14_450_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 17_290 nanoseconds. + Weight::from_parts(22_370_000, 0).saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Court YearlyInflation (r:1 w:0) /// Proof: Court YearlyInflation (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Court CourtPool (r:1 w:0) - /// Proof: Court CourtPool (max_values: Some(1), max_size: Some(72002), added: 72497, mode: MaxEncodedLen) - /// Storage: System Account (r:1000 w:1000) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `j` is `[1, 1000]`. fn handle_inflation(j: u32) -> Weight { // Proof Size summary in bytes: @@ -323,17 +319,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[0, 3]`. fn select_participants(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `84052 + a * (19595 ±0)` - // Estimated: `133335 + a * (162878 ±713)` - // Minimum execution time: 1_523_714 nanoseconds. - Weight::from_parts(864_759_111, 133335) - // Standard Error: 18_632_603 - .saturating_add(Weight::from_parts(3_449_129_449, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(24)) - .saturating_add(T::DbWeight::get().reads((60_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(19)) - .saturating_add(T::DbWeight::get().writes((60_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 162878).saturating_mul(a.into())) + // Measured: `90735 + a * (16253 ±0)` + // Estimated: `151425 + a * (136685 ±1_677)` + // Minimum execution time: 1_553_414 nanoseconds. + Weight::from_parts(1_418_346_039, 151425) + // Standard Error: 42_495_557 + .saturating_add(Weight::from_parts(2_742_869_543, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(31)) + .saturating_add(T::DbWeight::get().reads((50_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(26)) + .saturating_add(T::DbWeight::get().writes((50_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 136685).saturating_mul(a.into())) } /// Storage: Court NextCourtId (r:1 w:1) /// Proof: Court NextCourtId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) @@ -361,18 +357,18 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `r` is `[0, 62]`. fn on_dispute(j: u32, r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `6073 + j * (80 ±0) + r * (16 ±0)` - // Estimated: `153295 + j * (11 ±0) + r * (29 ±1)` - // Minimum execution time: 298_061 nanoseconds. - Weight::from_parts(348_386_954, 153295) - // Standard Error: 1_339 - .saturating_add(Weight::from_parts(259_856, 0).saturating_mul(j.into())) - // Standard Error: 20_722 - .saturating_add(Weight::from_parts(277_477, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(33)) - .saturating_add(T::DbWeight::get().writes(35)) - .saturating_add(Weight::from_parts(0, 11).saturating_mul(j.into())) - .saturating_add(Weight::from_parts(0, 29).saturating_mul(r.into())) + // Measured: `6187 + j * (80 ±0) + r * (16 ±0)` + // Estimated: `157024 + j * (8 ±0) + r * (25 ±4)` + // Minimum execution time: 305_361 nanoseconds. + Weight::from_parts(406_257_471, 157024) + // Standard Error: 5_212 + .saturating_add(Weight::from_parts(234_425, 0).saturating_mul(j.into())) + // Standard Error: 80_784 + .saturating_add(Weight::from_parts(320_266, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(34)) + .saturating_add(T::DbWeight::get().writes(36)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(j.into())) + .saturating_add(Weight::from_parts(0, 25).saturating_mul(r.into())) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -389,12 +385,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn on_resolution(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `968 + d * (256 ±0)` + // Measured: `970 + d * (256 ±0)` // Estimated: `163456 + d * (2726 ±0)` - // Minimum execution time: 45_351 nanoseconds. - Weight::from_parts(46_740_000, 163456) - // Standard Error: 5_341 - .saturating_add(Weight::from_parts(7_102_104, 0).saturating_mul(d.into())) + // Minimum execution time: 49_430 nanoseconds. + Weight::from_parts(51_040_000, 163456) + // Standard Error: 22_733 + .saturating_add(Weight::from_parts(7_308_501, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -414,10 +410,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `420 + a * (352 ±0)` // Estimated: `5339 + a * (6331 ±0)` - // Minimum execution time: 14_230 nanoseconds. - Weight::from_parts(17_978_307, 5339) - // Standard Error: 68_789 - .saturating_add(Weight::from_parts(31_714_281, 0).saturating_mul(a.into())) + // Minimum execution time: 15_130 nanoseconds. + Weight::from_parts(18_804_523, 5339) + // Standard Error: 178_834 + .saturating_add(Weight::from_parts(34_697_386, 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()))) @@ -431,8 +427,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `423` // Estimated: `5339` - // Minimum execution time: 11_790 nanoseconds. - Weight::from_parts(12_930_000, 5339).saturating_add(T::DbWeight::get().reads(2)) + // Minimum execution time: 12_750 nanoseconds. + Weight::from_parts(15_520_000, 5339).saturating_add(T::DbWeight::get().reads(2)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -448,8 +444,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `3189` // Estimated: `83504` - // Minimum execution time: 36_060 nanoseconds. - Weight::from_parts(43_690_000, 83504).saturating_add(T::DbWeight::get().reads(5)) + // Minimum execution time: 38_700 nanoseconds. + Weight::from_parts(45_630_000, 83504).saturating_add(T::DbWeight::get().reads(5)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -463,12 +459,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn on_global_dispute(_a: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `482 + a * (66 ±0) + d * (256 ±0)` + // Measured: `486 + a * (66 ±0) + d * (256 ±0)` // Estimated: `157788 + d * (2726 ±0)` - // Minimum execution time: 30_490 nanoseconds. - Weight::from_parts(12_392_980, 157788) - // Standard Error: 8_466 - .saturating_add(Weight::from_parts(7_354_052, 0).saturating_mul(d.into())) + // Minimum execution time: 45_390 nanoseconds. + Weight::from_parts(49_259_424, 157788) + // Standard Error: 31_231 + .saturating_add(Weight::from_parts(7_486_776, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -486,12 +482,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 510]`. fn clear(d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `397 + d * (256 ±0)` + // Measured: `399 + d * (256 ±0)` // Estimated: `154964 + d * (2726 ±0)` - // Minimum execution time: 24_940 nanoseconds. - Weight::from_parts(25_650_000, 154964) - // Standard Error: 5_781 - .saturating_add(Weight::from_parts(6_956_726, 0).saturating_mul(d.into())) + // Minimum execution time: 26_250 nanoseconds. + Weight::from_parts(26_920_000, 154964) + // Standard Error: 22_958 + .saturating_add(Weight::from_parts(7_260_221, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/zrml/global-disputes/Cargo.toml b/zrml/global-disputes/Cargo.toml index 8740e5b02..50d018b3e 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.3" +version = "0.5.0" diff --git a/zrml/global-disputes/src/weights.rs b/zrml/global-disputes/src/weights.rs index cd74c28c5..329d7710a 100644 --- a/zrml/global-disputes/src/weights.rs +++ b/zrml/global-disputes/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,7 +19,7 @@ //! Autogenerated weights for zrml_global_disputes //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_global_disputes // --extrinsic=* // --execution=wasm @@ -74,14 +74,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `v` is `[0, 49]`. fn vote_on_outcome(o: u32, v: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `556 + o * (26 ±0) + v * (32 ±0)` + // Measured: `510 + o * (33 ±0) + v * (32 ±0)` // Estimated: `13631` - // Minimum execution time: 56_570 nanoseconds. - Weight::from_parts(62_319_654, 13631) - // Standard Error: 20_343 - .saturating_add(Weight::from_parts(133_505, 0).saturating_mul(o.into())) - // Standard Error: 3_569 - .saturating_add(Weight::from_parts(91_527, 0).saturating_mul(v.into())) + // Minimum execution time: 59_970 nanoseconds. + Weight::from_parts(70_944_535, 13631) + // Standard Error: 99_660 + .saturating_add(Weight::from_parts(158_915, 0).saturating_mul(o.into())) + // Standard Error: 17_527 + .saturating_add(Weight::from_parts(73_873, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -99,12 +99,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + l * (467 ±0) + o * (1600 ±0)` // Estimated: `10497 + l * (2871 ±0)` - // Minimum execution time: 31_550 nanoseconds. - Weight::from_parts(35_536_478, 10497) - // Standard Error: 11_671 - .saturating_add(Weight::from_parts(3_910_915, 0).saturating_mul(l.into())) - // Standard Error: 65_337 - .saturating_add(Weight::from_parts(1_067_736, 0).saturating_mul(o.into())) + // Minimum execution time: 35_780 nanoseconds. + Weight::from_parts(44_476_307, 10497) + // Standard Error: 53_653 + .saturating_add(Weight::from_parts(4_271_681, 0).saturating_mul(l.into())) + // Standard Error: 281_603 + .saturating_add(Weight::from_parts(439_604, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(l.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -120,14 +120,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `l` is `[0, 50]`. /// The range of component `o` is `[1, 10]`. - fn unlock_vote_balance_remove(l: u32, _o: u32) -> Weight { + fn unlock_vote_balance_remove(l: u32, o: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0 + l * (451 ±0) + o * (1600 ±0)` // Estimated: `10497 + l * (2871 ±0)` - // Minimum execution time: 31_640 nanoseconds. - Weight::from_parts(38_456_566, 10497) - // Standard Error: 10_816 - .saturating_add(Weight::from_parts(3_883_534, 0).saturating_mul(l.into())) + // Minimum execution time: 35_680 nanoseconds. + Weight::from_parts(25_536_660, 10497) + // Standard Error: 40_297 + .saturating_add(Weight::from_parts(4_579_529, 0).saturating_mul(l.into())) + // Standard Error: 211_506 + .saturating_add(Weight::from_parts(1_769_468, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(l.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -142,14 +144,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `w` is `[1, 10]`. - fn add_vote_outcome(w: u32) -> Weight { + fn add_vote_outcome(_w: u32) -> Weight { // Proof Size summary in bytes: // Measured: `690 + w * (32 ±0)` // Estimated: `11501` - // Minimum execution time: 67_621 nanoseconds. - Weight::from_parts(77_496_340, 11501) - // Standard Error: 25_163 - .saturating_add(Weight::from_parts(48_228, 0).saturating_mul(w.into())) + // Minimum execution time: 69_591 nanoseconds. + Weight::from_parts(88_511_398, 11501) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -163,16 +163,16 @@ impl WeightInfoZeitgeist for WeightInfo { fn reward_outcome_owner_shared_possession(o: u32) -> Weight { // Proof Size summary in bytes: // Measured: `490 + o * (41 ±0)` - // Estimated: `8869 + o * (2702 ±6)` - // Minimum execution time: 66_650 nanoseconds. - Weight::from_parts(53_409_131, 8869) - // Standard Error: 97_889 - .saturating_add(Weight::from_parts(30_309_941, 0).saturating_mul(o.into())) + // Estimated: `8574 + o * (2790 ±20)` + // Minimum execution time: 67_260 nanoseconds. + Weight::from_parts(40_126_687, 8574) + // Standard Error: 326_372 + .saturating_add(Weight::from_parts(36_368_285, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(o.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(o.into()))) - .saturating_add(Weight::from_parts(0, 2702).saturating_mul(o.into())) + .saturating_add(Weight::from_parts(0, 2790).saturating_mul(o.into())) } /// Storage: GlobalDisputes Outcomes (r:1 w:0) /// Proof: GlobalDisputes Outcomes (max_values: None, max_size: Some(395), added: 2870, mode: MaxEncodedLen) @@ -184,8 +184,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `537` // Estimated: `10955` - // Minimum execution time: 66_220 nanoseconds. - Weight::from_parts(75_191_000, 10955) + // Minimum execution time: 74_690 nanoseconds. + Weight::from_parts(90_080_000, 10955) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -197,12 +197,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `o` is `[1, 10]`. fn purge_outcomes(k: u32, _o: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `407 + k * (122 ±0) + o * (32 ±0)` + // Measured: `409 + k * (122 ±0) + o * (32 ±0)` // Estimated: `8611 + k * (2870 ±0)` - // Minimum execution time: 76_340 nanoseconds. - Weight::from_parts(8_305_894, 8611) - // Standard Error: 21_012 - .saturating_add(Weight::from_parts(18_168_686, 0).saturating_mul(k.into())) + // Minimum execution time: 88_640 nanoseconds. + Weight::from_parts(106_570_000, 8611) + // Standard Error: 41_012 + .saturating_add(Weight::from_parts(18_091_486, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -217,12 +217,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `o` is `[1, 10]`. fn refund_vote_fees(k: u32, _o: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `407 + k * (122 ±0) + o * (32 ±0)` + // Measured: `409 + k * (122 ±0) + o * (32 ±0)` // Estimated: `8611 + k * (2870 ±0)` - // Minimum execution time: 73_140 nanoseconds. - Weight::from_parts(232_428_016, 8611) - // Standard Error: 18_813 - .saturating_add(Weight::from_parts(17_740_443, 0).saturating_mul(k.into())) + // Minimum execution time: 85_661 nanoseconds. + Weight::from_parts(320_805_229, 8611) + // Standard Error: 87_560 + .saturating_add(Weight::from_parts(17_351_127, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/zrml/liquidity-mining/Cargo.toml b/zrml/liquidity-mining/Cargo.toml index ce3c51820..358d2b125 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.3" +version = "0.5.0" diff --git a/zrml/liquidity-mining/src/weights.rs b/zrml/liquidity-mining/src/weights.rs index 69e9ef043..27fe33018 100644 --- a/zrml/liquidity-mining/src/weights.rs +++ b/zrml/liquidity-mining/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,7 +19,7 @@ //! Autogenerated weights for zrml_liquidity_mining //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_liquidity_mining // --extrinsic=* // --execution=wasm @@ -61,7 +61,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_980 nanoseconds. - Weight::from_parts(4_750_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 4_910 nanoseconds. + Weight::from_parts(5_880_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/market-commons/Cargo.toml b/zrml/market-commons/Cargo.toml index 215042a49..730d0e5ed 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -33,4 +33,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-market-commons" -version = "0.4.3" +version = "0.5.0" diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 2ac64b8e1..a03c25c42 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -106,4 +106,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-neo-swaps" -version = "0.4.3" +version = "0.5.0" diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index c7114792f..327cacd9b 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-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,18 +19,18 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-12-20`, STEPS: `10`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `mkl-mac`, CPU: `` +//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=2 +// --steps=50 +// --repeat=20 // --pallet=zrml_neo_swaps // --extrinsic=* // --execution=wasm @@ -75,12 +75,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn buy(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2790 + n * (195 ±0)` + // Measured: `2693 + n * (195 ±0)` // Estimated: `160792 + n * (5116 ±0)` - // Minimum execution time: 510_000 nanoseconds. - Weight::from_parts(480_304_329, 160792) - // Standard Error: 593_647 - .saturating_add(Weight::from_parts(23_811_471, 0).saturating_mul(n.into())) + // Minimum execution time: 450_981 nanoseconds. + Weight::from_parts(456_952_561, 160792) + // Standard Error: 210_027 + .saturating_add(Weight::from_parts(19_290_495, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -100,12 +100,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn sell(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2952 + n * (195 ±0)` + // Measured: `2855 + n * (195 ±0)` // Estimated: `160792 + n * (5116 ±0)` - // Minimum execution time: 380_000 nanoseconds. - Weight::from_parts(404_645_021, 160792) - // Standard Error: 514_733 - .saturating_add(Weight::from_parts(33_359_307, 0).saturating_mul(n.into())) + // Minimum execution time: 315_041 nanoseconds. + Weight::from_parts(261_744_587, 160792) + // Standard Error: 130_952 + .saturating_add(Weight::from_parts(28_294_554, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -123,12 +123,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn join_in_place(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `140919 + n * (261 ±0)` + // Measured: `140822 + n * (261 ±0)` // Estimated: `152980 + n * (5196 ±0)` - // Minimum execution time: 507_000 nanoseconds. - Weight::from_parts(381_461_038, 152980) - // Standard Error: 6_222_921 - .saturating_add(Weight::from_parts(43_846_753, 0).saturating_mul(n.into())) + // Minimum execution time: 541_431 nanoseconds. + Weight::from_parts(901_153_315, 152980) + // Standard Error: 248_938 + .saturating_add(Weight::from_parts(46_826_279, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -146,12 +146,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn join_reassigned(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `140715 + n * (261 ±0)` + // Measured: `140618 + n * (261 ±0)` // Estimated: `152980 + n * (5196 ±0)` - // Minimum execution time: 445_000 nanoseconds. - Weight::from_parts(295_293_073, 152980) - // Standard Error: 2_105_968 - .saturating_add(Weight::from_parts(41_721_645, 0).saturating_mul(n.into())) + // Minimum execution time: 770_081 nanoseconds. + Weight::from_parts(977_585_660, 152980) + // Standard Error: 240_547 + .saturating_add(Weight::from_parts(47_067_211, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -169,12 +169,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn join_leaf(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `140720 + n * (261 ±0)` + // Measured: `140623 + n * (261 ±0)` // Estimated: `152980 + n * (5196 ±0)` - // Minimum execution time: 489_000 nanoseconds. - Weight::from_parts(426_883_549, 152980) - // Standard Error: 1_583_212 - .saturating_add(Weight::from_parts(41_776_406, 0).saturating_mul(n.into())) + // Minimum execution time: 1_106_843 nanoseconds. + Weight::from_parts(1_104_397_667, 152980) + // Standard Error: 342_655 + .saturating_add(Weight::from_parts(49_860_093, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -192,12 +192,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn exit(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `140816 + n * (261 ±0)` + // Measured: `140719 + n * (261 ±0)` // Estimated: `152980 + n * (5196 ±0)` - // Minimum execution time: 449_000 nanoseconds. - Weight::from_parts(555_190_909, 152980) - // Standard Error: 2_997_117 - .saturating_add(Weight::from_parts(39_540_909, 0).saturating_mul(n.into())) + // Minimum execution time: 1_197_914 nanoseconds. + Weight::from_parts(1_716_602_393, 152980) + // Standard Error: 271_840 + .saturating_add(Weight::from_parts(47_371_723, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -210,10 +210,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn withdraw_fees() -> Weight { // Proof Size summary in bytes: - // Measured: `139382` + // Measured: `139283` // Estimated: `152434` - // Minimum execution time: 431_000 nanoseconds. - Weight::from_parts(452_000_000, 152434) + // Minimum execution time: 1_407_515 nanoseconds. + Weight::from_parts(1_604_315_000, 152434) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -228,12 +228,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[2, 128]`. fn deploy_pool(n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2167 + n * (113 ±0)` + // Measured: `2069 + n * (113 ±0)` // Estimated: `152980 + n * (5196 ±0)` - // Minimum execution time: 233_000 nanoseconds. - Weight::from_parts(553_764_069, 152980) - // Standard Error: 3_614_297 - .saturating_add(Weight::from_parts(46_549_783, 0).saturating_mul(n.into())) + // Minimum execution time: 239_391 nanoseconds. + Weight::from_parts(102_113_981, 152980) + // Standard Error: 537_232 + .saturating_add(Weight::from_parts(47_234_501, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index dd4963def..9639b9545 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.3" +version = "0.5.0" diff --git a/zrml/orderbook/src/weights.rs b/zrml/orderbook/src/weights.rs index 5ebda41a3..d0a6a4c1a 100644 --- a/zrml/orderbook/src/weights.rs +++ b/zrml/orderbook/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,18 +19,18 @@ //! Autogenerated weights for zrml_orderbook //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-11-03`, STEPS: `10`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `chralt`, CPU: `` +//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` //! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_orderbook // --extrinsic=* // --execution=wasm @@ -65,8 +65,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `283` // Estimated: `6341` - // Minimum execution time: 26_000 nanoseconds. - Weight::from_parts(27_000_000, 6341) + // Minimum execution time: 41_041 nanoseconds. + Weight::from_parts(50_010_000, 6341) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -84,8 +84,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `1081` // Estimated: `17297` - // Minimum execution time: 67_000 nanoseconds. - Weight::from_parts(68_000_000, 17297) + // Minimum execution time: 115_061 nanoseconds. + Weight::from_parts(139_860_000, 17297) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -99,10 +99,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Orderbook Orders (max_values: None, max_size: Some(142), added: 2617, mode: MaxEncodedLen) fn place_order() -> Weight { // Proof Size summary in bytes: - // Measured: `372` + // Measured: `334` // Estimated: `7388` - // Minimum execution time: 33_000 nanoseconds. - Weight::from_parts(34_000_000, 7388) + // Minimum execution time: 45_140 nanoseconds. + Weight::from_parts(56_180_000, 7388) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index 52e1b0d8a..2aaed4d75 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.3" +version = "0.5.0" diff --git a/zrml/parimutuel/src/weights.rs b/zrml/parimutuel/src/weights.rs index b44a0f130..d22df535a 100644 --- a/zrml/parimutuel/src/weights.rs +++ b/zrml/parimutuel/src/weights.rs @@ -1,4 +1,5 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. // @@ -14,10 +15,11 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + //! Autogenerated weights for zrml_parimutuel //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -27,14 +29,15 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_parimutuel // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs +// --header=./HEADER_GPL3 // --output=./zrml/parimutuel/src/weights.rs #![allow(unused_parens)] @@ -64,10 +67,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) fn buy() -> Weight { // Proof Size summary in bytes: - // Measured: `1814` + // Measured: `1815` // Estimated: `10876` - // Minimum execution time: 107_021 nanoseconds. - Weight::from_parts(131_400_000, 10876) + // Minimum execution time: 134_190 nanoseconds. + Weight::from_parts(155_681_000, 10876) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -81,10 +84,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn claim_rewards() -> Weight { // Proof Size summary in bytes: - // Measured: `2311` + // Measured: `2312` // Estimated: `10876` - // Minimum execution time: 106_420 nanoseconds. - Weight::from_parts(146_081_000, 10876) + // Minimum execution time: 127_580 nanoseconds. + Weight::from_parts(180_511_000, 10876) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -98,10 +101,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn claim_refunds() -> Weight { // Proof Size summary in bytes: - // Measured: `2311` + // Measured: `2312` // Estimated: `13394` - // Minimum execution time: 100_691 nanoseconds. - Weight::from_parts(123_451_000, 13394) + // Minimum execution time: 119_110 nanoseconds. + Weight::from_parts(132_660_000, 13394) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 3d1af3594..52019ef27 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -95,4 +95,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-prediction-markets" -version = "0.4.3" +version = "0.5.0" diff --git a/zrml/prediction-markets/runtime-api/Cargo.toml b/zrml/prediction-markets/runtime-api/Cargo.toml index 24211e93d..81c32e411 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.3" +version = "0.5.0" diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 2c9ae0bc8..e4c3e7a97 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for zrml_prediction_markets //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_prediction_markets // --extrinsic=* // --execution=wasm @@ -59,7 +59,6 @@ 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(a: u32) -> Weight; fn start_global_dispute(m: u32, n: u32) -> Weight; fn dispute_authorized() -> Weight; fn handle_expired_advised_market() -> Weight; @@ -68,18 +67,14 @@ pub trait WeightInfoZeitgeist { fn internal_resolve_scalar_reported() -> Weight; fn internal_resolve_scalar_disputed() -> Weight; fn on_initialize_resolve_overhead() -> Weight; - fn process_subsidy_collecting_markets_raw(a: u32) -> Weight; fn redeem_shares_categorical() -> Weight; fn redeem_shares_scalar() -> 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; - fn start_subsidy(a: u32) -> Weight; fn market_status_manager(b: u32, f: u32) -> Weight; fn market_resolution_manager(r: u32, d: u32) -> Weight; - fn process_subsidy_collecting_markets_dummy() -> Weight; - fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight; fn schedule_early_close_as_authority(o: u32, n: u32) -> Weight; fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight; fn schedule_early_close_as_market_creator(o: u32, n: u32) -> Weight; @@ -87,6 +82,7 @@ pub trait WeightInfoZeitgeist { fn reject_early_close_after_authority(o: u32, n: u32) -> Weight; fn reject_early_close_after_dispute() -> Weight; fn close_trusted_market(c: u32) -> Weight; + fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight; fn manually_close_market(o: u32) -> Weight; } @@ -95,26 +91,19 @@ pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, 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) /// 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 `o` is `[0, 63]`. /// The range of component `c` is `[0, 63]`. - 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(62_317, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) + fn admin_move_market_to_closed(_c: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `710 + c * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 46_460 nanoseconds. + Weight::from_parts(64_183_834, 7181) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -122,18 +111,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, 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 `r` is `[0, 63]`. fn admin_move_market_to_resolved_scalar_reported(r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `789 + r * (16 ±0)` - // Estimated: `12917` - // Minimum execution time: 81_630 nanoseconds. - Weight::from_parts(90_598_731, 12917) - // Standard Error: 4_875 - .saturating_add(Weight::from_parts(61_887, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `756 + r * (16 ±0)` + // Estimated: `10394` + // Minimum execution time: 83_360 nanoseconds. + Weight::from_parts(101_009_762, 10394) + // Standard Error: 30_301 + .saturating_add(Weight::from_parts(208_870, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) @@ -142,21 +129,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, 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 `r` is `[0, 63]`. - fn admin_move_market_to_resolved_categorical_reported(r: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `4482 + r * (16 ±0)` - // Estimated: `19043` - // Minimum execution time: 132_880 nanoseconds. - Weight::from_parts(148_466_242, 19043) - // Standard Error: 8_108 - .saturating_add(Weight::from_parts(4_693, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) + fn admin_move_market_to_resolved_categorical_reported(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `712 + r * (16 ±0)` + // Estimated: `10394` + // Minimum execution time: 82_630 nanoseconds. + Weight::from_parts(102_975_254, 10394) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -170,18 +151,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, 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 `r` is `[0, 63]`. - fn admin_move_market_to_resolved_scalar_disputed(r: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `1315 + r * (16 ±0)` - // Estimated: `24643` - // Minimum execution time: 136_411 nanoseconds. - Weight::from_parts(149_906_505, 24643) - // Standard Error: 7_982 - .saturating_add(Weight::from_parts(82_359, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(8)) + fn admin_move_market_to_resolved_scalar_disputed(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `1282 + r * (16 ±0)` + // Estimated: `22120` + // Minimum execution time: 139_521 nanoseconds. + Weight::from_parts(172_661_579, 22120) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: MarketCommons Markets (r:1 w:1) @@ -196,19 +173,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, 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 `r` is `[0, 63]`. fn admin_move_market_to_resolved_categorical_disputed(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `4993 + r * (16 ±0)` - // Estimated: `30769` - // Minimum execution time: 188_101 nanoseconds. - Weight::from_parts(213_433_833, 30769) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(7)) + // Measured: `1223 + r * (16 ±0)` + // Estimated: `22120` + // Minimum execution time: 138_250 nanoseconds. + Weight::from_parts(195_683_366, 22120) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -220,8 +193,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `580` // Estimated: `10402` - // Minimum execution time: 50_640 nanoseconds. - Weight::from_parts(56_810_000, 10402) + // Minimum execution time: 53_530 nanoseconds. + Weight::from_parts(64_901_000, 10402) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -234,10 +207,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `519` // Estimated: `6678` - // Minimum execution time: 25_330 nanoseconds. - Weight::from_parts(29_109_736, 6678) - // Standard Error: 94 - .saturating_add(Weight::from_parts(2_012, 0).saturating_mul(r.into())) + // Minimum execution time: 26_100 nanoseconds. + Weight::from_parts(32_383_275, 6678) + // Standard Error: 190 + .saturating_add(Weight::from_parts(4_809, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -254,10 +227,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `554` // Estimated: `5760 + a * (5116 ±0)` - // Minimum execution time: 97_011 nanoseconds. - Weight::from_parts(68_920_978, 5760) - // Standard Error: 34_434 - .saturating_add(Weight::from_parts(20_262_853, 0).saturating_mul(a.into())) + // Minimum execution time: 110_781 nanoseconds. + Weight::from_parts(95_160_144, 5760) + // Standard Error: 200_254 + .saturating_add(Weight::from_parts(22_776_250, 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(1)) @@ -275,12 +248,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: MarketCommons Markets (r:0 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// The range of component `m` is `[0, 63]`. - fn create_market(_m: u32) -> Weight { + fn create_market(m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `240 + m * (16 ±0)` // Estimated: `8263` - // Minimum execution time: 51_080 nanoseconds. - Weight::from_parts(64_414_026, 8263) + // Minimum execution time: 55_550 nanoseconds. + Weight::from_parts(74_259_340, 8263) + // Standard Error: 7_534 + .saturating_add(Weight::from_parts(63_015, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -293,50 +268,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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 `m` is `[0, 63]`. - fn edit_market(m: u32) -> Weight { + fn edit_market(_m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `735 + m * (16 ±0)` // Estimated: `10706` - // Minimum execution time: 50_710 nanoseconds. - Weight::from_parts(60_055_013, 10706) - // Standard Error: 3_263 - .saturating_add(Weight::from_parts(43_939, 0).saturating_mul(m.into())) + // Minimum execution time: 53_490 nanoseconds. + Weight::from_parts(75_146_182, 10706) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// 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(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `1112 + a * (119 ±0)` - // Estimated: `14413 + a * (5196 ±0)` - // Minimum execution time: 185_011 nanoseconds. - Weight::from_parts(149_265_363, 14413) - // Standard Error: 44_213 - .saturating_add(Weight::from_parts(33_798_818, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(6)) - .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: GlobalDisputes GlobalDisputesInfo (r:1 w:1) /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: Court MarketIdToCourtId (r:1 w:0) @@ -359,14 +301,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[1, 64]`. fn start_global_dispute(m: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `9191 + m * (16 ±0)` + // Measured: `9158 + m * (16 ±0)` // Estimated: `329717` - // Minimum execution time: 319_451 nanoseconds. - Weight::from_parts(355_402_117, 329717) - // Standard Error: 15_398 - .saturating_add(Weight::from_parts(188_238, 0).saturating_mul(m.into())) - // Standard Error: 15_398 - .saturating_add(Weight::from_parts(21_335, 0).saturating_mul(n.into())) + // Minimum execution time: 325_801 nanoseconds. + Weight::from_parts(403_175_654, 329717) + // Standard Error: 66_094 + .saturating_add(Weight::from_parts(104_261, 0).saturating_mul(m.into())) + // Standard Error: 66_094 + .saturating_add(Weight::from_parts(153_430, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(40)) .saturating_add(T::DbWeight::get().writes(36)) } @@ -378,8 +320,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `626` // Estimated: `6877` - // Minimum execution time: 42_770 nanoseconds. - Weight::from_parts(51_110_000, 6877) + // Minimum execution time: 59_780 nanoseconds. + Weight::from_parts(60_860_000, 6877) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -393,8 +335,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `539` // Estimated: `6877` - // Minimum execution time: 56_180 nanoseconds. - Weight::from_parts(62_890_000, 6877) + // Minimum execution time: 66_010 nanoseconds. + Weight::from_parts(80_071_000, 6877) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -402,18 +344,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, 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 internal_resolve_categorical_reported() -> Weight { // Proof Size summary in bytes: - // Measured: `4319` - // Estimated: `15526` - // Minimum execution time: 108_570 nanoseconds. - Weight::from_parts(126_090_000, 15526) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `582` + // Estimated: `6877` + // Minimum execution time: 64_000 nanoseconds. + Weight::from_parts(78_640_000, 6877) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -423,32 +361,26 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, 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 internal_resolve_categorical_disputed() -> Weight { // Proof Size summary in bytes: - // Measured: `4574` - // Estimated: `20921` - // Minimum execution time: 156_021 nanoseconds. - Weight::from_parts(163_240_000, 20921) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `837` + // Estimated: `12272` + // Minimum execution time: 123_140 nanoseconds. + Weight::from_parts(139_681_000, 12272) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:0) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn internal_resolve_scalar_reported() -> Weight { // Proof Size summary in bytes: // Measured: `626` - // Estimated: `9400` - // Minimum execution time: 59_240 nanoseconds. - Weight::from_parts(66_770_000, 9400) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `6877` + // Minimum execution time: 58_300 nanoseconds. + Weight::from_parts(71_201_000, 6877) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) @@ -459,27 +391,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: GlobalDisputes GlobalDisputesInfo (max_values: None, max_size: Some(396), added: 2871, mode: MaxEncodedLen) /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:0) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn internal_resolve_scalar_disputed() -> Weight { // Proof Size summary in bytes: // Measured: `896` - // Estimated: `14795` - // Minimum execution time: 101_160 nanoseconds. - Weight::from_parts(114_551_000, 14795) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `12272` + // Minimum execution time: 103_430 nanoseconds. + Weight::from_parts(125_710_000, 12272) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Timestamp Now (r:1 w:0) /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) - /// Proof: PredictionMarkets MarketsCollectingSubsidy (max_values: Some(1), max_size: Some(529), added: 1024, mode: MaxEncodedLen) /// Storage: PredictionMarkets LastTimeFrame (r:1 w:1) /// Proof: PredictionMarkets LastTimeFrame (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenBlock (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenBlock (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerCloseBlock (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) @@ -491,25 +415,11 @@ impl WeightInfoZeitgeist for WeightInfo { fn on_initialize_resolve_overhead() -> Weight { // Proof Size summary in bytes: // Measured: `79` - // Estimated: `23164` - // Minimum execution time: 32_670 nanoseconds. - Weight::from_parts(37_650_000, 23164) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(8)) - } - /// Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) - /// Proof: PredictionMarkets MarketsCollectingSubsidy (max_values: Some(1), max_size: Some(529), added: 1024, mode: MaxEncodedLen) - /// The range of component `a` is `[0, 10]`. - fn process_subsidy_collecting_markets_raw(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `63 + a * (33 ±0)` - // Estimated: `1024` - // Minimum execution time: 4_950 nanoseconds. - Weight::from_parts(7_737_240, 1024) - // Standard Error: 4_953 - .saturating_add(Weight::from_parts(354_749, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Estimated: `15090` + // Minimum execution time: 24_590 nanoseconds. + Weight::from_parts(30_770_000, 15090) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -523,8 +433,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `2062` // Estimated: `10876` - // Minimum execution time: 92_011 nanoseconds. - Weight::from_parts(97_061_000, 10876) + // Minimum execution time: 123_791 nanoseconds. + Weight::from_parts(144_701_000, 10876) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -540,15 +450,13 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `1209` // Estimated: `15992` - // Minimum execution time: 116_260 nanoseconds. - Weight::from_parts(130_120_000, 15992) + // Minimum execution time: 120_670 nanoseconds. + Weight::from_parts(186_740_000, 15992) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) @@ -556,20 +464,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: PredictionMarkets MarketIdsForEdit (r:0 w:1) /// Proof: PredictionMarkets MarketIdsForEdit (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// 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, r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `723 + c * (16 ±0) + o * (16 ±0)` - // Estimated: `13927` - // Minimum execution time: 94_580 nanoseconds. - 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: 262 - .saturating_add(Weight::from_parts(2_551, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `643 + c * (16 ±0)` + // Estimated: `10402` + // Minimum execution time: 89_320 nanoseconds. + Weight::from_parts(101_732_045, 10402) + // Standard Error: 10_471 + .saturating_add(Weight::from_parts(137_908, 0).saturating_mul(c.into())) + // Standard Error: 648 + .saturating_add(Weight::from_parts(8_275, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) @@ -578,14 +485,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: PredictionMarkets MarketIdsPerReportBlock (r:1 w:1) /// Proof: PredictionMarkets MarketIdsPerReportBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// The range of component `m` is `[0, 63]`. - fn report_market_with_dispute_mechanism(m: u32) -> Weight { + fn report_market_with_dispute_mechanism(_m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `749` + // Measured: `716` // Estimated: `7173` - // Minimum execution time: 40_560 nanoseconds. - Weight::from_parts(47_301_834, 7173) - // Standard Error: 2_470 - .saturating_add(Weight::from_parts(18_180, 0).saturating_mul(m.into())) + // Minimum execution time: 43_530 nanoseconds. + Weight::from_parts(62_963_271, 7173) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -593,15 +498,13 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:0) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn report_trusted_market() -> Weight { // Proof Size summary in bytes: // Measured: `538` - // Estimated: `9400` - // Minimum execution time: 80_430 nanoseconds. - Weight::from_parts(91_210_000, 9400) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `6877` + // Minimum execution time: 96_401 nanoseconds. + Weight::from_parts(97_750_000, 6877) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:0) @@ -615,60 +518,36 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 64]`. fn sell_complete_set(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `794 + a * (161 ±0)` + // Measured: `793 + a * (161 ±0)` // Estimated: `5760 + a * (5116 ±0)` - // Minimum execution time: 111_700 nanoseconds. - Weight::from_parts(63_689_088, 5760) - // Standard Error: 37_295 - .saturating_add(Weight::from_parts(28_230_283, 0).saturating_mul(a.into())) + // Minimum execution time: 114_750 nanoseconds. + Weight::from_parts(73_429_594, 5760) + // Standard Error: 110_546 + .saturating_add(Weight::from_parts(32_544_002, 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(1)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) .saturating_add(Weight::from_parts(0, 5116).saturating_mul(a.into())) } - /// Storage: Swaps NextPoolId (r:1 w:1) - /// Proof: Swaps NextPoolId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:1 w:0) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: RikiddoSigmoidFeeMarketEma RikiddoPerPool (r:1 w:1) - /// Proof: RikiddoSigmoidFeeMarketEma RikiddoPerPool (max_values: None, max_size: Some(320), added: 2795, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketPool (r:1 w:1) - /// Proof: MarketCommons MarketPool (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) - /// Proof: PredictionMarkets MarketsCollectingSubsidy (max_values: Some(1), max_size: Some(529), added: 1024, 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 start_subsidy(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `529` - // Estimated: `10006` - // Minimum execution time: 43_680 nanoseconds. - Weight::from_parts(50_565_118, 10006) - // Standard Error: 2_953 - .saturating_add(Weight::from_parts(83_396, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - /// Storage: PredictionMarkets MarketIdsPerOpenBlock (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenBlock (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseBlock (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseBlock (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:62 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// The range of component `b` is `[1, 31]`. /// The range of component `f` is `[1, 31]`. fn market_status_manager(b: u32, f: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2536 + b * (329 ±0) + f * (331 ±0)` + // Measured: `2539 + b * (329 ±0) + f * (331 ±0)` // Estimated: `7050 + b * (3153 ±0) + f * (3153 ±0)` - // Minimum execution time: 159_650 nanoseconds. - Weight::from_parts(51_830_020, 7050) - // Standard Error: 23_348 - .saturating_add(Weight::from_parts(3_911_908, 0).saturating_mul(b.into())) - // Standard Error: 23_348 - .saturating_add(Weight::from_parts(4_074_663, 0).saturating_mul(f.into())) + // Minimum execution time: 174_211 nanoseconds. + Weight::from_parts(67_095_551, 7050) + // Standard Error: 63_801 + .saturating_add(Weight::from_parts(4_617_980, 0).saturating_mul(b.into())) + // Standard Error: 63_801 + .saturating_add(Weight::from_parts(4_855_146, 0).saturating_mul(f.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(f.into()))) @@ -686,14 +565,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `d` is `[1, 31]`. fn market_resolution_manager(r: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2487 + r * (329 ±0) + d * (331 ±0)` + // Measured: `2483 + r * (329 ±0) + d * (331 ±0)` // Estimated: `7034 + r * (3153 ±0) + d * (3153 ±0)` - // Minimum execution time: 158_350 nanoseconds. - Weight::from_parts(46_712_053, 7034) - // Standard Error: 23_276 - .saturating_add(Weight::from_parts(4_006_693, 0).saturating_mul(r.into())) - // Standard Error: 23_276 - .saturating_add(Weight::from_parts(4_167_508, 0).saturating_mul(d.into())) + // Minimum execution time: 169_091 nanoseconds. + Weight::from_parts(56_570_719, 7034) + // Standard Error: 89_416 + .saturating_add(Weight::from_parts(4_792_326, 0).saturating_mul(r.into())) + // Standard Error: 89_416 + .saturating_add(Weight::from_parts(4_925_168, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(d.into()))) @@ -701,53 +580,6 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(Weight::from_parts(0, 3153).saturating_mul(r.into())) .saturating_add(Weight::from_parts(0, 3153).saturating_mul(d.into())) } - /// Storage: PredictionMarkets MarketsCollectingSubsidy (r:1 w:1) - /// Proof: PredictionMarkets MarketsCollectingSubsidy (max_values: Some(1), max_size: Some(529), added: 1024, mode: MaxEncodedLen) - fn process_subsidy_collecting_markets_dummy() -> Weight { - // Proof Size summary in bytes: - // Measured: `27` - // Estimated: `1024` - // Minimum execution time: 4_990 nanoseconds. - Weight::from_parts(5_360_000, 1024) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: Timestamp Now (r:1 w:0) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Balances Reserves (r:1 w:1) - /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) - /// Storage: MarketCommons MarketCounter (r:1 w:1) - /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) - /// Storage: Tokens Accounts (r:126 w:126) - /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) - /// Storage: Tokens TotalIssuance (r:63 w:63) - /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) - /// Storage: NeoSwaps Pools (r:1 w:1) - /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(4652), added: 7127, mode: MaxEncodedLen) - /// Storage: MarketCommons Markets (r:0 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) - /// The range of component `m` is `[0, 63]`. - /// The range of component `n` is `[2, 64]`. - fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `410 + m * (16 ±0)` - // Estimated: `20604 + n * (7714 ±0)` - // Minimum execution time: 372_000 nanoseconds. - Weight::from_parts(215_092_617, 20604) - // Standard Error: 998_171 - .saturating_add(Weight::from_parts(565_260, 0).saturating_mul(m.into())) - // Standard Error: 1_014_199 - .saturating_add(Weight::from_parts(65_201_886, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(7)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 7714).saturating_mul(n.into())) - } /// Storage: MarketCommons Markets (r:1 w:1) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Timestamp Now (r:1 w:0) @@ -760,12 +592,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `747 + o * (16 ±0)` // Estimated: `10706` - // Minimum execution time: 48_910 nanoseconds. - Weight::from_parts(55_628_507, 10706) - // Standard Error: 2_340 - .saturating_add(Weight::from_parts(63_826, 0).saturating_mul(o.into())) - // Standard Error: 2_340 - .saturating_add(Weight::from_parts(10_393, 0).saturating_mul(n.into())) + // Minimum execution time: 51_230 nanoseconds. + Weight::from_parts(61_363_456, 10706) + // Standard Error: 13_970 + .saturating_add(Weight::from_parts(50_244, 0).saturating_mul(o.into())) + // Standard Error: 13_970 + .saturating_add(Weight::from_parts(62_184, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -779,14 +611,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// The range of component `o` is `[0, 63]`. /// The range of component `n` is `[0, 63]`. - fn schedule_early_close_after_dispute(o: u32, _n: u32) -> Weight { + fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `970 + o * (16 ±0)` // Estimated: `14430` - // Minimum execution time: 92_361 nanoseconds. - Weight::from_parts(106_196_533, 14430) - // Standard Error: 4_746 - .saturating_add(Weight::from_parts(35_448, 0).saturating_mul(o.into())) + // Minimum execution time: 96_271 nanoseconds. + Weight::from_parts(119_475_237, 14430) + // Standard Error: 22_292 + .saturating_add(Weight::from_parts(90_541, 0).saturating_mul(o.into())) + // Standard Error: 22_292 + .saturating_add(Weight::from_parts(12_780, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -800,12 +634,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) /// The range of component `o` is `[0, 63]`. /// The range of component `n` is `[0, 63]`. - fn schedule_early_close_as_market_creator(_o: u32, _n: u32) -> Weight { + fn schedule_early_close_as_market_creator(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: // Measured: `808 + o * (16 ±0)` // Estimated: `14430` - // Minimum execution time: 70_741 nanoseconds. - Weight::from_parts(86_536_582, 14430) + // Minimum execution time: 74_650 nanoseconds. + Weight::from_parts(92_510_070, 14430) + // Standard Error: 11_870 + .saturating_add(Weight::from_parts(29_343, 0).saturating_mul(o.into())) + // Standard Error: 11_870 + .saturating_add(Weight::from_parts(37_672, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -823,12 +661,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `924 + o * (16 ±0) + n * (16 ±0)` // Estimated: `14430` - // Minimum execution time: 68_421 nanoseconds. - Weight::from_parts(74_036_489, 14430) - // Standard Error: 3_573 - .saturating_add(Weight::from_parts(69_851, 0).saturating_mul(o.into())) - // Standard Error: 3_573 - .saturating_add(Weight::from_parts(43_246, 0).saturating_mul(n.into())) + // Minimum execution time: 71_610 nanoseconds. + Weight::from_parts(86_696_361, 14430) + // Standard Error: 13_050 + .saturating_add(Weight::from_parts(59_792, 0).saturating_mul(o.into())) + // Standard Error: 13_050 + .saturating_add(Weight::from_parts(64_792, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -844,12 +682,12 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `814 + o * (16 ±0) + n * (16 ±0)` // Estimated: `10706` - // Minimum execution time: 53_440 nanoseconds. - Weight::from_parts(57_491_752, 10706) - // Standard Error: 2_714 - .saturating_add(Weight::from_parts(50_284, 0).saturating_mul(o.into())) - // Standard Error: 2_714 - .saturating_add(Weight::from_parts(53_840, 0).saturating_mul(n.into())) + // Minimum execution time: 55_950 nanoseconds. + Weight::from_parts(70_714_615, 10706) + // Standard Error: 14_905 + .saturating_add(Weight::from_parts(63_828, 0).saturating_mul(o.into())) + // Standard Error: 14_905 + .saturating_add(Weight::from_parts(40_198, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -861,40 +699,81 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `672` // Estimated: `6877` - // Minimum execution time: 66_780 nanoseconds. - Weight::from_parts(80_781_000, 6877) + // Minimum execution time: 80_670 nanoseconds. + Weight::from_parts(84_290_000, 6877) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, 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 `c` is `[0, 63]`. + fn close_trusted_market(c: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `709 + c * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 46_550 nanoseconds. + Weight::from_parts(57_766_022, 7181) + // Standard Error: 7_003 + .saturating_add(Weight::from_parts(122_020, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Balances Reserves (r:1 w:1) + /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) + /// Storage: MarketCommons MarketCounter (r:1 w:1) + /// Proof: MarketCommons MarketCounter (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: PredictionMarkets MarketIdsPerCloseTimeFrame (r:1 w:1) + /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: Tokens Accounts (r:128 w:128) + /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) + /// Storage: Tokens TotalIssuance (r:64 w:64) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) + /// Storage: NeoSwaps Pools (r:1 w:1) + /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketCommons Markets (r:0 w:1) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// The range of component `m` is `[0, 63]`. + /// The range of component `n` is `[2, 64]`. + fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `410 + m * (16 ±0)` + // Estimated: `160697 + n * (7714 ±0)` + // Minimum execution time: 308_311 nanoseconds. + Weight::from_parts(79_670_419, 160697) + // Standard Error: 618_510 + .saturating_add(Weight::from_parts(1_958_104, 0).saturating_mul(m.into())) + // Standard Error: 627_776 + .saturating_add(Weight::from_parts(61_182_580, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 7714).saturating_mul(n.into())) + } + /// Storage: MarketCommons Markets (r:1 w:1) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, 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)) + // Measured: `694 + o * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 53_830 nanoseconds. + Weight::from_parts(57_209_302, 7181) + // Standard Error: 9_825 + .saturating_add(Weight::from_parts(91_256, 0).saturating_mul(o.into())) + .saturating_add(T::DbWeight::get().reads(3)) .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(62_317, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } } diff --git a/zrml/rikiddo/Cargo.toml b/zrml/rikiddo/Cargo.toml index 8859b1f7e..4732ba6ba 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.3" +version = "0.5.0" diff --git a/zrml/simple-disputes/Cargo.toml b/zrml/simple-disputes/Cargo.toml index e85f005a0..8841cdfee 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.3" +version = "0.5.0" diff --git a/zrml/styx/Cargo.toml b/zrml/styx/Cargo.toml index 95889877d..d24e381de 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.3" +version = "0.5.0" diff --git a/zrml/styx/src/weights.rs b/zrml/styx/src/weights.rs index e789d22d1..63715de60 100644 --- a/zrml/styx/src/weights.rs +++ b/zrml/styx/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,7 +19,7 @@ //! Autogenerated weights for zrml_styx //! //! 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-15`, STEPS: `50`, REPEAT: `20`, 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` @@ -29,8 +29,8 @@ // benchmark // pallet // --chain=dev -// --steps=10 -// --repeat=1000 +// --steps=50 +// --repeat=20 // --pallet=zrml_styx // --extrinsic=* // --execution=wasm @@ -64,8 +64,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `76` // Estimated: `3034` - // Minimum execution time: 31_250 nanoseconds. - Weight::from_parts(32_820_000, 3034) + // Minimum execution time: 33_900 nanoseconds. + Weight::from_parts(43_980_000, 3034) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -75,7 +75,7 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 10_430 nanoseconds. - Weight::from_parts(11_110_000, 0).saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 14_260 nanoseconds. + Weight::from_parts(15_690_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 80abf4639..4756741be 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -67,4 +67,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps" -version = "0.4.3" +version = "0.5.0" diff --git a/zrml/swaps/rpc/Cargo.toml b/zrml/swaps/rpc/Cargo.toml index f162e166d..a5f97ab16 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.3" +version = "0.5.0" diff --git a/zrml/swaps/runtime-api/Cargo.toml b/zrml/swaps/runtime-api/Cargo.toml index 00c0c3276..a3a1c88b5 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.3" +version = "0.5.0" diff --git a/zrml/swaps/src/weights.rs b/zrml/swaps/src/weights.rs index b37d4beb2..37e4ccd80 100644 --- a/zrml/swaps/src/weights.rs +++ b/zrml/swaps/src/weights.rs @@ -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: `2024-01-05`, STEPS: `12`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `mkl-mac`, CPU: `` -//! EXECUTION: `Some(Native)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/zeitgeist +// ./target/production/zeitgeist // benchmark // pallet // --chain=dev -// --steps=12 -// --repeat=2 +// --steps=50 +// --repeat=20 // --pallet=zrml_swaps // --extrinsic=* -// --execution=native +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -76,12 +76,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_exit(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `843 + a * (176 ±0)` + // Measured: `848 + 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())) + // Minimum execution time: 129_570 nanoseconds. + Weight::from_parts(128_926_395, 13777) + // Standard Error: 377_426 + .saturating_add(Weight::from_parts(36_975_518, 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)) @@ -100,8 +100,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `5546` // Estimated: `18973` - // Minimum execution time: 97_000 nanoseconds. - Weight::from_parts(112_000_000, 18973) + // Minimum execution time: 119_830 nanoseconds. + Weight::from_parts(147_671_000, 18973) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -117,8 +117,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `5546` // Estimated: `18973` - // Minimum execution time: 92_000 nanoseconds. - Weight::from_parts(172_000_000, 18973) + // Minimum execution time: 141_210 nanoseconds. + Weight::from_parts(147_801_000, 18973) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -131,12 +131,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn pool_join(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `723 + a * (289 ±0)` + // Measured: `726 + 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())) + // Minimum execution time: 125_590 nanoseconds. + Weight::from_parts(63_107_045, 11170) + // Standard Error: 353_437 + .saturating_add(Weight::from_parts(30_963_348, 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)) @@ -153,8 +153,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `6229` // Estimated: `16366` - // Minimum execution time: 90_000 nanoseconds. - Weight::from_parts(92_000_000, 16366) + // Minimum execution time: 120_300 nanoseconds. + Weight::from_parts(138_451_000, 16366) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -168,8 +168,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `6229` // Estimated: `16366` - // Minimum execution time: 89_000 nanoseconds. - Weight::from_parts(122_000_000, 16366) + // Minimum execution time: 101_230 nanoseconds. + Weight::from_parts(126_591_000, 16366) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -183,8 +183,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `5144` // Estimated: `19053` - // Minimum execution time: 136_000 nanoseconds. - Weight::from_parts(138_000_000, 19053) + // Minimum execution time: 163_771 nanoseconds. + Weight::from_parts(201_801_000, 19053) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -198,8 +198,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `5144` // Estimated: `19053` - // Minimum execution time: 138_000 nanoseconds. - Weight::from_parts(144_000_000, 19053) + // Minimum execution time: 166_981 nanoseconds. + Weight::from_parts(202_971_000, 19053) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -210,10 +210,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // 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())) + // Minimum execution time: 18_030 nanoseconds. + Weight::from_parts(19_889_995, 6054) + // Standard Error: 12_386 + .saturating_add(Weight::from_parts(570_444, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -224,10 +224,10 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // 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())) + // Minimum execution time: 17_140 nanoseconds. + Weight::from_parts(19_467_788, 6054) + // Standard Error: 3_011 + .saturating_add(Weight::from_parts(283_373, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -242,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: `568 + a * (214 ±0)` + // Measured: `569 + 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())) + // Minimum execution time: 99_530 nanoseconds. + Weight::from_parts(13_849_323, 8661) + // Standard Error: 316_948 + .saturating_add(Weight::from_parts(31_161_021, 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)) From 048ceca3c44657bfaad191b9adfd384bfdedfe8c Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 15:15:25 +0100 Subject: [PATCH 055/104] Move functions into market struct and properly delete assets --- primitives/src/assets/all_assets.rs | 2 +- primitives/src/assets/subsets.rs | 4 +- primitives/src/assets/tests/scale_codec.rs | 5 +- primitives/src/market.rs | 259 ++++++++++++++++-- zrml/parimutuel/src/lib.rs | 12 +- zrml/prediction-markets/src/lib.rs | 70 ++--- .../src/tests/buy_complete_set.rs | 4 +- .../src/tests/integration.rs | 7 +- .../src/tests/redeem_shares.rs | 7 +- .../src/tests/sell_complete_set.rs | 4 +- 10 files changed, 281 insertions(+), 93 deletions(-) diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index cb000d759..cbf5d0229 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -148,4 +148,4 @@ impl From for Asset { BaseAssetClass::CampaignAsset(id) => Self::CampaignAsset(id), } } -} \ No newline at end of file +} diff --git a/primitives/src/assets/subsets.rs b/primitives/src/assets/subsets.rs index d786fc71a..259fddf4e 100644 --- a/primitives/src/assets/subsets.rs +++ b/primitives/src/assets/subsets.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -pub use base_assets::BaseAssetClass; use super::*; +pub use base_assets::BaseAssetClass; -mod base_assets; \ No newline at end of file +mod base_assets; diff --git a/primitives/src/assets/tests/scale_codec.rs b/primitives/src/assets/tests/scale_codec.rs index cedb74f0b..d3a9f96ad 100644 --- a/primitives/src/assets/tests/scale_codec.rs +++ b/primitives/src/assets/tests/scale_codec.rs @@ -36,10 +36,7 @@ use test_case::test_case; BaseAssetClass::Ztg; "ztg" )] -fn index_matching_works_for_base_assets( - old_asset: Asset, - new_asset: BaseAssetClass, -) { +fn index_matching_works_for_base_assets(old_asset: Asset, new_asset: BaseAssetClass) { let old_asset_encoded: Vec = old_asset.encode(); let new_asset_decoded = ::decode(&mut old_asset_encoded.as_slice()).unwrap(); diff --git a/primitives/src/market.rs b/primitives/src/market.rs index cae1eb2bc..fc66c6ed5 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -16,10 +16,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::types::OutcomeReport; +use crate::types::{MarketAssetClass, OutcomeReport, ScalarPosition}; use alloc::vec::Vec; use core::ops::{Range, RangeInclusive}; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::per_things::Perbill; use sp_runtime::RuntimeDebug; @@ -79,6 +79,110 @@ impl Market { pub fn is_redeemable(&self) -> bool { matches!(self.resolution_mechanism(), ResolutionMechanism::RedeemTokens) } + + // Returns the number of outcomes for a market. + pub fn outcomes(&self) -> u16 { + match self.market_type { + MarketType::Categorical(categories) => categories, + MarketType::Scalar(_) => 2, + } + } + + /// Check if `outcome_report` matches the type of this market. + pub fn matches_outcome_report(&self, outcome_report: &OutcomeReport) -> bool { + match outcome_report { + OutcomeReport::Categorical(ref inner) => { + if let MarketType::Categorical(ref categories) = &self.market_type { + inner < categories + } else { + false + } + } + OutcomeReport::Scalar(_) => { + matches!(&self.market_type, MarketType::Scalar(_)) + } + } + } + + pub fn outcome_assets( + &self, + market_id: MI, + ) -> Vec> { + match self.market_type { + MarketType::Categorical(categories) => { + let mut assets = Vec::new(); + + for i in 0..categories { + match self.scoring_rule { + ScoringRule::Orderbook => { + assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) + } + ScoringRule::Lmsr => { + assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) + } + ScoringRule::Parimutuel => { + assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) + } + }; + } + + assets + } + MarketType::Scalar(_) => { + vec![ + MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Short), + ] + } + } + } + + pub fn report_into_outcome( + &self, + market_id: MI, + ) -> Option> { + let outcome = if let Some(ref report) = self.report { + &report.outcome + } else { + return None; + }; + + self.outcome_report_into_outcome(market_id, &outcome) + } + + pub fn resolved_outcome_into_outcome( + &self, + market_id: MI, + ) -> Option> { + let outcome = if let Some(ref outcome) = self.resolved_outcome { + outcome + } else { + return None; + }; + + self.outcome_report_into_outcome(market_id, &outcome) + } + + fn outcome_report_into_outcome( + &self, + market_id: MI, + outcome_report: &OutcomeReport, + ) -> Option> { + match outcome_report { + OutcomeReport::Categorical(idx) => match self.scoring_rule { + ScoringRule::Orderbook => { + Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) + } + ScoringRule::Lmsr => { + Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) + } + ScoringRule::Parimutuel => { + Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) + } + }, + OutcomeReport::Scalar(_) => None, + } + } } /// Tracks the status of a bond. @@ -139,32 +243,6 @@ impl Default for MarketBonds { } } -impl Market { - // Returns the number of outcomes for a market. - pub fn outcomes(&self) -> u16 { - match self.market_type { - MarketType::Categorical(categories) => categories, - MarketType::Scalar(_) => 2, - } - } - - /// Check if `outcome_report` matches the type of this market. - pub fn matches_outcome_report(&self, outcome_report: &OutcomeReport) -> bool { - match outcome_report { - OutcomeReport::Categorical(ref inner) => { - if let MarketType::Categorical(ref categories) = &self.market_type { - inner < categories - } else { - false - } - } - OutcomeReport::Scalar(_) => { - matches!(&self.market_type, MarketType::Scalar(_)) - } - } - } -} - impl MaxEncodedLen for Market where AI: MaxEncodedLen, @@ -345,7 +423,10 @@ pub enum ResolutionMechanism { #[cfg(test)] mod tests { - use crate::{market::*, types::Asset}; + use crate::{ + market::*, + types::{Asset, MarketAsset}, + }; use test_case::test_case; type Market = crate::market::Market>; @@ -427,6 +508,126 @@ mod tests { assert_eq!(market.matches_outcome_report(&outcome_report), expected); } + #[test_case( + MarketType::Categorical(2), + ScoringRule::Lmsr, + vec![MarketAsset::CategoricalOutcome(0, 0), MarketAsset::CategoricalOutcome(0, 1)]; + "categorical_market_lmsr" + )] + #[test_case( + MarketType::Categorical(2), + ScoringRule::Orderbook, + vec![MarketAsset::CategoricalOutcome(0, 0), MarketAsset::CategoricalOutcome(0, 1)]; + "categorical_market_orderbook" + )] + #[test_case( + MarketType::Categorical(2), + ScoringRule::Parimutuel, + vec![MarketAsset::CategoricalOutcome(0, 0), MarketAsset::CategoricalOutcome(0, 1)]; + "categorical_market_parimutuel" + )] + #[test_case( + MarketType::Scalar(12..=34), + ScoringRule::Lmsr, + vec![MarketAsset::ScalarOutcome(0, ScalarPosition::Long), MarketAsset::ScalarOutcome(0, ScalarPosition::Short)]; + "scalar_market" + )] + fn provides_correct_list_of_assets( + market_type: MarketType, + scoring_rule: ScoringRule, + expected: Vec, + ) { + let market = Market { + base_asset: Asset::Ztg, + creator: 1, + creation: MarketCreation::Permissionless, + creator_fee: Default::default(), + oracle: 3, + metadata: vec![4u8; 5], + market_type, + period: MarketPeriod::Block(7..8), + deadlines: Deadlines { + grace_period: 1_u32, + oracle_duration: 1_u32, + dispute_duration: 1_u32, + }, + scoring_rule, + status: MarketStatus::Active, + report: None, + resolved_outcome: None, + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + bonds: MarketBonds::default(), + early_close: None, + }; + assert_eq!(market.outcome_assets(0), expected); + } + + #[test_case( + MarketType::Categorical(2), + ScoringRule::Lmsr, + OutcomeReport::Categorical(2), + Some(MarketAsset::CategoricalOutcome(0, 2)); + "categorical_market_lmsr" + )] + #[test_case( + MarketType::Categorical(2), + ScoringRule::Orderbook, + OutcomeReport::Categorical(2), + Some(MarketAsset::CategoricalOutcome(0, 2)); + "categorical_market_orderbook" + )] + #[test_case( + MarketType::Categorical(2), + ScoringRule::Parimutuel, + OutcomeReport::Categorical(2), + Some(MarketAsset::CategoricalOutcome(0, 2)); + "categorical_market_parimutuel" + )] + #[test_case( + MarketType::Scalar(12..=34), + ScoringRule::Lmsr, + OutcomeReport::Scalar(2), + None; + "scalar_market" + )] + fn converts_outcome_correctly( + market_type: MarketType, + scoring_rule: ScoringRule, + outcome: OutcomeReport, + expected: Option, + ) { + let report = Some(Report { + at: Default::default(), + by: Default::default(), + outcome: outcome.clone(), + }); + + let market = Market { + base_asset: Asset::Ztg, + creator: 1, + creation: MarketCreation::Permissionless, + creator_fee: Default::default(), + oracle: 3, + metadata: vec![4u8; 5], + market_type, + period: MarketPeriod::Block(7..8), + deadlines: Deadlines { + grace_period: 1_u32, + oracle_duration: 1_u32, + dispute_duration: 1_u32, + }, + scoring_rule, + status: MarketStatus::Active, + report, + resolved_outcome: Some(outcome), + dispute_mechanism: Some(MarketDisputeMechanism::Authorized), + bonds: MarketBonds::default(), + early_close: None, + }; + assert_eq!(market.resolved_outcome_into_outcome(0), expected); + assert_eq!(market.report_into_outcome(0), expected); + } + #[test] fn max_encoded_len_market_type() { // `MarketType::Scalar` is the largest enum variant. diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index f4e4277ae..d59e6f40e 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -34,7 +34,7 @@ mod pallet { ensure, log, pallet_prelude::{Decode, DispatchError, Encode, TypeInfo}, require_transactional, - traits::{fungibles::Create, Get, IsType, StorageVersion}, + traits::{fungibles::{Inspect, Create}, Get, IsType, StorageVersion}, PalletId, RuntimeDebug, }; use frame_system::{ @@ -330,6 +330,14 @@ mod pallet { Error::::NotCategorical ); Self::market_assets_contains(&market, &asset)?; + let pot_account = Self::pot_account(market_id); + + if !T::AssetCreator::asset_exists(asset) { + let admin = pot_account.clone(); + let is_sufficient = true; + let min_balance = 1u8; + T::AssetCreator::create(asset, admin, is_sufficient, min_balance.into())?; + } let external_fees = T::ExternalFees::distribute(market_id, base_asset.into(), &who, amount); @@ -340,8 +348,6 @@ mod pallet { Error::::AmountBelowMinimumBetSize ); - let pot_account = Self::pot_account(market_id); - T::AssetManager::transfer(base_asset.into(), &who, &pot_account, amount_minus_fees)?; T::AssetManager::deposit(asset, &who, amount_minus_fees)?; diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index e15880eb6..6ceda3f5d 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -525,7 +525,7 @@ mod pallet { let sender = ensure_signed(origin)?; Self::do_buy_complete_set(sender, market_id, amount)?; let market = >::market(&market_id)?; - let assets = Self::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); let assets_len: u32 = assets.len().saturated_into(); Ok(Some(T::WeightInfo::buy_complete_set(assets_len)).into()) } @@ -945,7 +945,7 @@ mod pallet { let sender = ensure_signed(origin)?; Self::do_sell_complete_set(sender, market_id, amount)?; let market = >::market(&market_id)?; - let assets = Self::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); let assets_len: u32 = assets.len().saturated_into(); Ok(Some(T::WeightInfo::sell_complete_set(assets_len)).into()) } @@ -2149,11 +2149,11 @@ mod pallet { let market_id = >::push_market(market.clone())?; let market_account = Self::market_account(market_id); - for outcome in Self::outcome_assets(market_id, &market) { + for outcome in market.outcome_assets(market_id) { let admin = market_account.clone(); let is_sufficient = true; let min_balance = 1u8; - T::AssetCreator::create(outcome, admin, is_sufficient, min_balance.into())?; + T::AssetCreator::create(outcome.into(), admin, is_sufficient, min_balance.into())?; } let ids_amount: u32 = Self::insert_auto_close(&market_id)?; @@ -2163,24 +2163,6 @@ mod pallet { Ok((ids_amount, market_id)) } - pub fn outcome_assets(market_id: MarketIdOf, market: &MarketOf) -> Vec> { - match market.market_type { - MarketType::Categorical(categories) => { - let mut assets = Vec::new(); - for i in 0..categories { - assets.push(Asset::CategoricalOutcome(market_id, i)); - } - assets - } - MarketType::Scalar(_) => { - vec![ - Asset::ScalarOutcome(market_id, ScalarPosition::Long), - Asset::ScalarOutcome(market_id, ScalarPosition::Short), - ] - } - } - } - fn insert_auto_close(market_id: &MarketIdOf) -> Result { let market = >::market(market_id)?; @@ -2299,21 +2281,21 @@ mod pallet { "Market account does not have sufficient reserves.", ); - let assets = Self::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); // verify first. for asset in assets.iter() { // Ensures that the sender has sufficient amount of each // share in the set. ensure!( - T::AssetManager::free_balance(*asset, &who) >= amount, + T::AssetManager::free_balance((*asset).into(), &who) >= amount, Error::::InsufficientShareBalance, ); } // write last. for asset in assets.iter() { - let missing = T::AssetManager::slash(*asset, &who, amount); + let missing = T::AssetManager::slash((*asset).into(), &who, amount); debug_assert!( missing.is_zero(), "Could not slash all of the amount. asset {:?}, who: {:?}, amount: {:?}.", @@ -2348,9 +2330,9 @@ mod pallet { let market_account = Self::market_account(market_id); T::AssetManager::transfer(market.base_asset.into(), &who, &market_account, amount)?; - let assets = Self::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); for asset in assets.iter() { - T::AssetManager::deposit(*asset, &who, amount)?; + T::AssetManager::deposit((*asset).into(), &who, amount)?; } Self::deposit_event(Event::BoughtCompleteSet(market_id, amount, who)); @@ -2782,29 +2764,29 @@ mod pallet { // Following call should return weight consumed by it. T::LiquidityMining::distribute_market_incentives(market_id)?; + let mut updated_market = market.clone(); + >::mutate_market(market_id, |m| { m.status = MarketStatus::Resolved; m.resolved_outcome = Some(resolved_outcome.clone()); + updated_market = m.clone(); Ok(()) })?; - match resolved_outcome { - OutcomeReport::Categorical(winning_idx) => { - // Destroy losing categorical outcome assets. - let assets_to_destroy = BTreeMap::, Option>::from_iter( - Self::outcome_assets(*market_id, market) - .into_iter() - .filter(|outcome| { - *outcome - != AssetOf::::CategoricalOutcome(*market_id, winning_idx) - }) - .zip(vec![None]), - ); - // Ensure managed_destroy_multi does not error during lazy migration because - // it tried to delete an old outcome asset from orml-tokens - let _ = T::AssetDestroyer::managed_destroy_multi(assets_to_destroy); - } - OutcomeReport::Scalar(_) => (), + let winning_outcome = updated_market.resolved_outcome_into_outcome(*market_id); + if let Some(winning_outcome_inner) = winning_outcome { + // Destroy losing assets. + let assets_to_destroy = BTreeMap::, Option>::from_iter( + updated_market + .outcome_assets(*market_id) + .into_iter() + .filter(|outcome| *outcome != winning_outcome_inner) + .map(|asset| AssetOf::::from(asset)) + .zip(vec![None]), + ); + // Ensure managed_destroy_multi does not error during lazy migration because + // it tried to delete an old outcome asset from orml-tokens + let _ = T::AssetDestroyer::managed_destroy_multi(assets_to_destroy); } Self::deposit_event(Event::MarketResolved( diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index d37781ba8..8a5ee2140 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -42,9 +42,9 @@ fn buy_complete_set_works() { let market = MarketCommons::market(&market_id).unwrap(); - let assets = PredictionMarkets::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); for asset in assets.iter() { - let bal = AssetManager::free_balance(*asset, &who); + let bal = AssetManager::free_balance((*asset).into(), &who); assert_eq!(bal, amount); } diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs index c19931c59..1c1e2b1d0 100644 --- a/zrml/prediction-markets/src/tests/integration.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -202,10 +202,11 @@ fn full_scalar_market_lifecycle() { )); // check balances - let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); + let market = &MarketCommons::market(&0).unwrap(); + let assets = market.outcome_assets(0); assert_eq!(assets.len(), 2); for asset in assets.iter() { - let bal = AssetManager::free_balance(*asset, &CHARLIE); + let bal = AssetManager::free_balance((*asset).into(), &CHARLIE); assert_eq!(bal, 100 * BASE); } let market = MarketCommons::market(&0).unwrap(); @@ -262,7 +263,7 @@ fn full_scalar_market_lifecycle() { assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); for asset in assets.iter() { - let bal = AssetManager::free_balance(*asset, &CHARLIE); + let bal = AssetManager::free_balance((*asset).into(), &CHARLIE); assert_eq!(bal, 0); } diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index b6c957dfd..50800a488 100644 --- a/zrml/prediction-markets/src/tests/redeem_shares.rs +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -184,9 +184,10 @@ fn scalar_market_correctly_resolves_common(base_asset: BaseAsset, reported_value assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); - let assets = PredictionMarkets::outcome_assets(0, &MarketCommons::market(&0).unwrap()); + let market = &MarketCommons::market(&0).unwrap(); + let assets = market.outcome_assets(0); for asset in assets.iter() { - assert_eq!(AssetManager::free_balance(*asset, &CHARLIE), 0); - assert_eq!(AssetManager::free_balance(*asset, &EVE), 0); + assert_eq!(AssetManager::free_balance((*asset).into(), &CHARLIE), 0); + assert_eq!(AssetManager::free_balance((*asset).into(), &EVE), 0); } } diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 828bc0ea1..45e539ae7 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -50,9 +50,9 @@ fn sell_complete_set_works(scoring_rule: ScoringRule) { )); let market = MarketCommons::market(&market_id).unwrap(); - let assets = PredictionMarkets::outcome_assets(market_id, &market); + let assets = market.outcome_assets(market_id); for asset in assets.iter() { - let bal = AssetManager::free_balance(*asset, &who); + let bal = AssetManager::free_balance((*asset).into(), &who); assert_eq!(bal, expected_amount); } From ec042123dbbc6115a3a76761a718345559e2f68f Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 16:48:01 +0100 Subject: [PATCH 056/104] Add ParimutuelAssetClass --- primitives/src/assets.rs | 2 +- primitives/src/assets/all_assets.rs | 10 ++++ .../src/assets/{subsets.rs => subsets/mod.rs} | 3 + primitives/src/assets/tests/conversion.rs | 27 +++++++++ primitives/src/assets/tests/scale_codec.rs | 55 ++++++++++++------- primitives/src/market.rs | 8 +-- primitives/src/types.rs | 21 ++++--- zrml/parimutuel/src/lib.rs | 7 --- 8 files changed, 93 insertions(+), 40 deletions(-) rename primitives/src/assets/{subsets.rs => subsets/mod.rs} (93%) diff --git a/primitives/src/assets.rs b/primitives/src/assets.rs index 7f56a8775..7bcd24126 100644 --- a/primitives/src/assets.rs +++ b/primitives/src/assets.rs @@ -30,7 +30,7 @@ pub use campaign_assets::CampaignAssetClass; pub use currencies::CurrencyClass; pub use custom_assets::CustomAssetClass; pub use market_assets::MarketAssetClass; -pub use subsets::BaseAssetClass; +pub use subsets::{BaseAssetClass, ParimutuelAssetClass}; mod all_assets; mod campaign_assets; diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index cbf5d0229..7a541a6f0 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -149,3 +149,13 @@ impl From for Asset { } } } + +impl From> for Asset { + fn from(value: ParimutuelAssetClass) -> Self { + match value { + ParimutuelAssetClass::::Share(market_id, cat_id) => { + Self::ParimutuelShare(market_id, cat_id) + } + } + } +} diff --git a/primitives/src/assets/subsets.rs b/primitives/src/assets/subsets/mod.rs similarity index 93% rename from primitives/src/assets/subsets.rs rename to primitives/src/assets/subsets/mod.rs index 259fddf4e..5ba3760c8 100644 --- a/primitives/src/assets/subsets.rs +++ b/primitives/src/assets/subsets/mod.rs @@ -16,6 +16,9 @@ // along with Zeitgeist. If not, see . use super::*; + pub use base_assets::BaseAssetClass; +pub use parimutuel::ParimutuelAssetClass; mod base_assets; +mod parimutuel; diff --git a/primitives/src/assets/tests/conversion.rs b/primitives/src/assets/tests/conversion.rs index e95495a23..243fbd4a6 100644 --- a/primitives/src/assets/tests/conversion.rs +++ b/primitives/src/assets/tests/conversion.rs @@ -215,6 +215,33 @@ fn from_base_assets_to_all_assets(old_asset: BaseAssetClass, new_asset: Asset ParimutuelAssetClass +#[test_case( + Asset::::ParimutuelShare(7, 8), + ParimutuelAssetClass::::Share(7, 8); + "parimutuel_share" +)] +fn from_all_assets_to_parimutuel_assets( + old_asset: Asset, + new_asset: ParimutuelAssetClass, +) { + let new_asset_converted: ParimutuelAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test_case( + ParimutuelAssetClass::::Share(7, 8), + Asset::::ParimutuelShare(7, 8); + "parimutuel_share" +)] +fn from_parimutuel_assets_to_all_assets( + old_asset: ParimutuelAssetClass, + new_asset: Asset, +) { + let new_asset_converted: Asset = old_asset.into(); + assert_eq!(new_asset, new_asset_converted); +} + // CampaignAssetId <> CampaignAssetClass #[test] fn from_campaign_asset_id_to_campaign_asset() { diff --git a/primitives/src/assets/tests/scale_codec.rs b/primitives/src/assets/tests/scale_codec.rs index d3a9f96ad..11e4caf9a 100644 --- a/primitives/src/assets/tests/scale_codec.rs +++ b/primitives/src/assets/tests/scale_codec.rs @@ -43,69 +43,86 @@ fn index_matching_works_for_base_assets(old_asset: Asset, new_asset: B assert_eq!(new_asset_decoded, new_asset); } -// Assets <> MarketAssetClass +// Assets <> CurrencyClass #[test_case( Asset::::CategoricalOutcome(7, 8), - MarketAssetClass::::CategoricalOutcome(7, 8); + CurrencyClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - MarketAssetClass::::PoolShare(7); + CurrencyClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - MarketAssetClass::::ParimutuelShare(7, 8); + CurrencyClass::::ParimutuelShare(7, 8); "parimutuel_share" )] -fn index_matching_works_for_market_assets( +#[test_case( + Asset::::ForeignAsset(7), + CurrencyClass::::ForeignAsset(7); + "foreign_asset" +)] +fn index_matching_works_for_currencies( old_asset: Asset, - new_asset: MarketAssetClass, + new_asset: CurrencyClass, ) { let old_asset_encoded: Vec = old_asset.encode(); let new_asset_decoded = - as Decode>::decode(&mut old_asset_encoded.as_slice()).unwrap(); + as Decode>::decode(&mut old_asset_encoded.as_slice()).unwrap(); assert_eq!(new_asset_decoded, new_asset); } -// Assets <> CurrencyClass +// Assets <> MarketAssetClass #[test_case( Asset::::CategoricalOutcome(7, 8), - CurrencyClass::::CategoricalOutcome(7, 8); + MarketAssetClass::::CategoricalOutcome(7, 8); "categorical_outcome" )] #[test_case( Asset::::ScalarOutcome(7, ScalarPosition::Long), - CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome" )] #[test_case( Asset::::PoolShare(7), - CurrencyClass::::PoolShare(7); + MarketAssetClass::::PoolShare(7); "pool_share" )] #[test_case( Asset::::ParimutuelShare(7, 8), - CurrencyClass::::ParimutuelShare(7, 8); + MarketAssetClass::::ParimutuelShare(7, 8); "parimutuel_share" )] +fn index_matching_works_for_market_assets( + old_asset: Asset, + new_asset: MarketAssetClass, +) { + let old_asset_encoded: Vec = old_asset.encode(); + let new_asset_decoded = + as Decode>::decode(&mut old_asset_encoded.as_slice()).unwrap(); + assert_eq!(new_asset_decoded, new_asset); +} + +// Assets <> ParimutuelAssetClass #[test_case( - Asset::::ForeignAsset(7), - CurrencyClass::::ForeignAsset(7); - "foreign_asset" + Asset::::ParimutuelShare(7, 8), + ParimutuelAssetClass::Share(7, 8); + "parimutuel_share" )] -fn index_matching_works_for_currencies( +fn index_matching_works_for_parimutuel_assets( old_asset: Asset, - new_asset: CurrencyClass, + new_asset: ParimutuelAssetClass, ) { let old_asset_encoded: Vec = old_asset.encode(); let new_asset_decoded = - as Decode>::decode(&mut old_asset_encoded.as_slice()).unwrap(); + as Decode>::decode(&mut old_asset_encoded.as_slice()) + .unwrap(); assert_eq!(new_asset_decoded, new_asset); } diff --git a/primitives/src/market.rs b/primitives/src/market.rs index fc66c6ed5..45f86ba22 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -121,7 +121,7 @@ impl Market { assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) } ScoringRule::Parimutuel => { - assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) + assets.push(MarketAssetClass::::ParimutuelShare(market_id, i)) } }; } @@ -177,7 +177,7 @@ impl Market { Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) } ScoringRule::Parimutuel => { - Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) + Some(MarketAssetClass::::ParimutuelShare(market_id, *idx)) } }, OutcomeReport::Scalar(_) => None, @@ -523,7 +523,7 @@ mod tests { #[test_case( MarketType::Categorical(2), ScoringRule::Parimutuel, - vec![MarketAsset::CategoricalOutcome(0, 0), MarketAsset::CategoricalOutcome(0, 1)]; + vec![MarketAsset::ParimutuelShare(0, 0), MarketAsset::ParimutuelShare(0, 1)]; "categorical_market_parimutuel" )] #[test_case( @@ -580,7 +580,7 @@ mod tests { MarketType::Categorical(2), ScoringRule::Parimutuel, OutcomeReport::Categorical(2), - Some(MarketAsset::CategoricalOutcome(0, 2)); + Some(MarketAsset::ParimutuelShare(0, 2)); "categorical_market_parimutuel" )] #[test_case( diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 2e3856ae3..ca6b985fe 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -77,33 +77,36 @@ impl<'a> Arbitrary<'a> for MultiHash { } } -/// ORML adapter +/// ORML adapter. pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; -/// ID type for any asset class +/// ID type for any asset class. pub type Assets = Asset; -/// Asset type representing base assets used by prediction markets +/// Asset type representing base assets used by prediction markets. pub type BaseAsset = BaseAssetClass; -/// Asset type representing campaign assets +/// Asset type representing campaign assets. pub type CampaignAsset = CampaignAssetClass; -// ID type of the campaign asset class +// ID type of the campaign asset class. pub type CampaignAssetId = u128; -/// Asset type representing custom assets +/// Asset type representing custom assets. pub type CustomAsset = CustomAssetClass; -// ID type of the custom asset class +// ID type of the custom asset class. pub type CustomAssetId = u128; -// Asset type representing currencies (excluding ZTG) +// Asset type representing currencies (excluding ZTG). pub type Currencies = CurrencyClass; -/// Asset type representing assets used by prediction markets +/// Asset type representing assets used by prediction markets. pub type MarketAsset = MarketAssetClass; +/// Asset type representing types used in parimutuel markets. +pub type ParimutuelAsset = ParimutuelAssetClass; + /// The asset id specifically used for pallet_assets_tx_payment for /// paying transaction fees in different assets. /// Since the polkadot extension and wallets can't handle custom asset ids other than just u32, diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index d59e6f40e..a12958283 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -332,13 +332,6 @@ mod pallet { Self::market_assets_contains(&market, &asset)?; let pot_account = Self::pot_account(market_id); - if !T::AssetCreator::asset_exists(asset) { - let admin = pot_account.clone(); - let is_sufficient = true; - let min_balance = 1u8; - T::AssetCreator::create(asset, admin, is_sufficient, min_balance.into())?; - } - let external_fees = T::ExternalFees::distribute(market_id, base_asset.into(), &who, amount); let amount_minus_fees = From 44cdd8e5614c6dfc4716d1f218a1370509267243 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 18:57:51 +0100 Subject: [PATCH 057/104] Add parimutuel.rs --- primitives/src/assets/subsets/parimutuel.rs | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 primitives/src/assets/subsets/parimutuel.rs diff --git a/primitives/src/assets/subsets/parimutuel.rs b/primitives/src/assets/subsets/parimutuel.rs new file mode 100644 index 000000000..fe9596fcb --- /dev/null +++ b/primitives/src/assets/subsets/parimutuel.rs @@ -0,0 +1,39 @@ +// Copyright 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 . + +use super::*; + +/// The `BaseAssets` enum represents all assets that can be used as collateral in +/// prediction markets. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive(Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo)] +pub enum ParimutuelAssetClass { + #[codec(index = 6)] + Share(MI, CategoryIndex), +} + +impl TryFrom> for ParimutuelAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::ParimutuelShare(market_id, cat_id) => Ok(Self::Share(market_id, cat_id)), + _ => Err(()), + } + } +} From 9986c09416dbf77ca9eb1d27ee3bf9e12903b13a Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 19:49:13 +0100 Subject: [PATCH 058/104] Remove duplicate code --- primitives/src/assets/all_assets.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index edf9e741f..7a541a6f0 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -93,19 +93,6 @@ impl ZeitgeistAssetEnumerator for Asset } } -impl PoolSharesId for Asset { - fn pool_shares_id(pool_id: PoolId) -> Self { - Self::PoolShare(pool_id) - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl ZeitgeistAssetEnumerator for Asset { - fn create_asset_id(t: MI) -> Self { - Asset::CategoricalOutcome(t, 0) - } -} - impl From> for Asset { fn from(value: MarketAssetClass) -> Self { match value { From 9f403acf6772a334426b132fbee85777a5025c57 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 23 Feb 2024 11:08:15 +0100 Subject: [PATCH 059/104] Adjust asset type and managed destroy after refund in parimutuels --- Cargo.lock | 2 + zrml/parimutuel/Cargo.toml | 3 +- zrml/parimutuel/src/benchmarking.rs | 4 +- zrml/parimutuel/src/lib.rs | 104 ++++++++++------ zrml/parimutuel/src/mock.rs | 185 ++++++++++++++++++++++++---- zrml/parimutuel/src/tests/buy.rs | 65 +++++----- zrml/parimutuel/src/tests/claim.rs | 130 ++++++++++++++----- zrml/parimutuel/src/tests/refund.rs | 46 +++---- 8 files changed, 374 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1623a9933..d98f220cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14760,7 +14760,9 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", + "zrml-asset-router", "zrml-market-commons", ] diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index f0fdc1adc..a80d669af 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -9,6 +9,7 @@ scale-info = { workspace = true, features = ["derive"] } sp-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } +zeitgeist-macros = { workspace = true } [dev-dependencies] env_logger = { workspace = true } @@ -17,6 +18,7 @@ orml-tokens = { workspace = true, features = ["default"] } pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } +zrml-asset-router = { workspace = true } zeitgeist-primitives = { workspace = true, features = ["mock", "default"] } test-case = { workspace = true } @@ -27,7 +29,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", diff --git a/zrml/parimutuel/src/benchmarking.rs b/zrml/parimutuel/src/benchmarking.rs index 2aec79bbb..20f946a73 100644 --- a/zrml/parimutuel/src/benchmarking.rs +++ b/zrml/parimutuel/src/benchmarking.rs @@ -47,7 +47,7 @@ fn buy_asset( amount: BalanceOf, ) { let market = T::MarketCommons::market(&market_id).unwrap(); - T::AssetManager::deposit(market.base_asset, buyer, amount).unwrap(); + T::AssetManager::deposit(market.base_asset.into(), buyer, amount).unwrap(); Parimutuel::::buy(RawOrigin::Signed(buyer.clone()).into(), asset, amount).unwrap(); } @@ -65,7 +65,7 @@ mod benchmarks { let asset = Asset::ParimutuelShare(market_id, 0u16); let market = T::MarketCommons::market(&market_id).unwrap(); - T::AssetManager::deposit(market.base_asset, &buyer, amount).unwrap(); + T::AssetManager::deposit(market.base_asset.into(), &buyer, amount).unwrap(); #[extrinsic_call] buy(RawOrigin::Signed(buyer), asset, amount); diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 2bbe35afd..92474a380 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -34,7 +34,7 @@ mod pallet { ensure, log, pallet_prelude::{Decode, DispatchError, Encode, TypeInfo}, require_transactional, - traits::{fungibles::{Inspect, Create}, Get, IsType, StorageVersion}, + traits::{Get, IsType, StorageVersion}, PalletId, RuntimeDebug, }; use frame_system::{ @@ -47,18 +47,19 @@ mod pallet { traits::{AccountIdConversion, CheckedSub, Zero}, DispatchResult, }; + use zeitgeist_macros::unreachable_non_terminating; use zeitgeist_primitives::{ math::fixed::FixedMulDiv, traits::DistributeFees, - types::{Asset, BaseAsset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, + types::{ + Asset, BaseAsset, Market, MarketStatus, MarketType, OutcomeReport, + ParimutuelAssetClass, ScoringRule, + }, }; use zrml_market_commons::MarketCommonsPalletApi; #[pallet::config] pub trait Config: frame_system::Config { - /// The module handling the creation of market assets. - type AssetCreator: Create, Balance = BalanceOf>; - /// The module handling the destruction of market assets. type AssetDestroyer: ManagedDestroy, Balance = BalanceOf>; @@ -67,7 +68,7 @@ mod pallet { /// The way how fees are taken from the market base asset. type ExternalFees: DistributeFees< - Asset = Asset>, + Asset = AssetOf, AccountId = AccountIdOf, Balance = BalanceOf, MarketId = MarketIdOf, @@ -98,6 +99,7 @@ mod pallet { const LOG_TARGET: &str = "runtime::zrml-parimutuel"; pub(crate) type AssetOf = Asset>; + pub(crate) type ParimutuelShareOf = ParimutuelAssetClass>; pub(crate) type AccountIdOf = ::AccountId; pub(crate) type BalanceOf = <::AssetManager as MultiCurrency>>::Balance; @@ -147,38 +149,51 @@ mod pallet { /// There was no buyer for the winning outcome or all winners already claimed their rewards. /// Use the `refund` extrinsic to get the initial bet back, /// in case there was no buyer for the winning outcome. + #[codec(index = 0)] NoRewardShareOutstanding, /// The market is not active. + #[codec(index = 1)] MarketIsNotActive, /// The specified amount is below the minimum bet size. + #[codec(index = 2)] AmountBelowMinimumBetSize, - /// The specified asset is not a parimutuel share. - NotParimutuelOutcome, /// The specified asset was not found in the market assets. + #[codec(index = 4)] InvalidOutcomeAsset, /// The scoring rule is not parimutuel. + #[codec(index = 5)] InvalidScoringRule, /// The specified amount can not be transferred. + #[codec(index = 6)] InsufficientBalance, /// The market is not resolved yet. + #[codec(index = 7)] MarketIsNotResolvedYet, /// An unexpected error occured. This should never happen! /// There was an internal coding mistake. + #[codec(index = 8)] Unexpected, /// There is no resolved outcome present for the market. + #[codec(index = 9)] NoResolvedOutcome, /// The refund is not allowed. + #[codec(index = 10)] RefundNotAllowed, /// There is no balance to refund. + #[codec(index = 11)] RefundableBalanceIsZero, /// There is no reward, because there are no winning shares. + #[codec(index = 12)] NoWinningShares, /// Only categorical markets are allowed for parimutuels. + #[codec(index = 13)] NotCategorical, /// There is no reward to distribute. + #[codec(index = 14)] NoRewardToDistribute, /// Action cannot be completed because an unexpected error has occurred. This should be /// reported to protocol maintainers. + #[codec(index = 15)] InconsistentState(InconsistentStateError), } @@ -208,7 +223,7 @@ mod pallet { #[frame_support::transactional] pub fn buy( origin: OriginFor, - asset: Asset>, + asset: ParimutuelShareOf, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -243,7 +258,10 @@ mod pallet { #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::claim_refunds())] #[frame_support::transactional] - pub fn claim_refunds(origin: OriginFor, refund_asset: AssetOf) -> DispatchResult { + pub fn claim_refunds( + origin: OriginFor, + refund_asset: ParimutuelShareOf, + ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_claim_refunds(who, refund_asset)?; @@ -298,24 +316,31 @@ mod pallet { Ok(()) } - pub fn market_assets_contains(market: &MarketOf, asset: &AssetOf) -> DispatchResult { - if let Asset::ParimutuelShare(_, i) = asset { - match market.market_type { - MarketType::Categorical(categories) => { - ensure!(*i < categories, Error::::InvalidOutcomeAsset); - return Ok(()); - } - MarketType::Scalar(_) => return Err(Error::::NotCategorical.into()), + pub fn market_assets_contains( + market: &MarketOf, + asset: &ParimutuelShareOf, + ) -> DispatchResult { + let index = match asset { + ParimutuelShareOf::::Share(_, idx) => *idx, + }; + + match market.market_type { + MarketType::Categorical(categories) => { + ensure!(index < categories, Error::::InvalidOutcomeAsset); + return Ok(()); } + MarketType::Scalar(_) => return Err(Error::::NotCategorical.into()), } - Err(Error::::NotParimutuelOutcome.into()) } #[require_transactional] - fn do_buy(who: T::AccountId, asset: AssetOf, amount: BalanceOf) -> DispatchResult { + fn do_buy( + who: T::AccountId, + asset: ParimutuelShareOf, + amount: BalanceOf, + ) -> DispatchResult { let market_id = match asset { - Asset::ParimutuelShare(market_id, _) => market_id, - _ => return Err(Error::::NotParimutuelOutcome.into()), + ParimutuelShareOf::::Share(market_id, _) => market_id, }; let market = T::MarketCommons::market(&market_id)?; let base_asset = market.base_asset; @@ -342,12 +367,12 @@ mod pallet { let pot_account = Self::pot_account(market_id); T::AssetManager::transfer(base_asset.into(), &who, &pot_account, amount_minus_fees)?; - T::AssetManager::deposit(asset, &who, amount_minus_fees)?; + T::AssetManager::deposit(asset.into(), &who, amount_minus_fees)?; Self::deposit_event(Event::OutcomeBought { market_id, buyer: who, - asset, + asset: asset.into(), amount_minus_fees, fees: external_fees, }); @@ -403,11 +428,7 @@ mod pallet { } let pot_account = Self::pot_account(market_id); -<<<<<<< HEAD let pot_total = T::AssetManager::free_balance(market.base_asset.into(), &pot_account); -======= - let pot_total = T::AssetManager::free_balance(market.base_asset, &pot_account); ->>>>>>> sea212-new-asset-system let payoff = pot_total.bmul_bdiv(winning_balance, outcome_total)?; Self::check_values(winning_balance, pot_total, outcome_total, payoff)?; @@ -427,6 +448,16 @@ mod pallet { base_asset_payoff, )?; + if outcome_total == winning_balance { + let res = T::AssetDestroyer::managed_destroy(winning_asset, None); + unreachable_non_terminating!( + res.is_ok(), + LOG_TARGET, + "Can't destroy winning outcome asset: {:?}", + res.err() + ); + } + Self::deposit_event(Event::RewardsClaimed { market_id, asset: winning_asset, @@ -439,10 +470,12 @@ mod pallet { } #[require_transactional] - fn do_claim_refunds(who: T::AccountId, refund_asset: AssetOf) -> DispatchResult { + fn do_claim_refunds( + who: T::AccountId, + refund_asset: ParimutuelShareOf, + ) -> DispatchResult { let market_id = match refund_asset { - Asset::ParimutuelShare(market_id, _) => market_id, - _ => return Err(Error::::NotParimutuelOutcome.into()), + ParimutuelShareOf::::Share(market_id, _) => market_id, }; let market = T::MarketCommons::market(&market_id)?; Self::ensure_parimutuel_market_resolved(&market)?; @@ -451,9 +484,10 @@ mod pallet { let outcome_total = T::AssetManager::total_issuance(winning_asset); ensure!(outcome_total == >::zero(), Error::::RefundNotAllowed); - let refund_balance = T::AssetManager::free_balance(refund_asset, &who); + let refund_asset_general: AssetOf = refund_asset.into(); + let refund_balance = T::AssetManager::free_balance(refund_asset_general, &who); ensure!(!refund_balance.is_zero(), Error::::RefundableBalanceIsZero); - if refund_asset == winning_asset { + if refund_asset_general == winning_asset { log::debug!( target: LOG_TARGET, "Since we were checking the total issuance of the winning asset to be zero, if \ @@ -463,7 +497,7 @@ mod pallet { debug_assert!(false); } - T::AssetManager::withdraw(refund_asset, &who, refund_balance)?; + T::AssetManager::withdraw(refund_asset_general, &who, refund_balance)?; let pot_account = Self::pot_account(market_id); let pot_total = T::AssetManager::free_balance(market.base_asset.into(), &pot_account); @@ -485,7 +519,7 @@ mod pallet { Self::deposit_event(Event::BalanceRefunded { market_id, - asset: refund_asset, + asset: refund_asset.into(), refunded_balance: refund_balance, sender: who.clone(), }); diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index 9468a4187..dc9f244d4 100644 --- a/zrml/parimutuel/src/mock.rs +++ b/zrml/parimutuel/src/mock.rs @@ -23,8 +23,15 @@ use crate as zrml_parimutuel; use crate::{AssetOf, BalanceOf, MarketIdOf}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; -use frame_support::{construct_runtime, pallet_prelude::Get, parameter_types, traits::Everything}; +use frame_support::{ + construct_runtime, + pallet_prelude::{ConstU32, Get}, + parameter_types, + traits::{AsEnsureOriginWithArg, Everything}, +}; +use frame_system::{EnsureRoot, EnsureSigned}; use orml_traits::MultiCurrency; +use parity_scale_codec::Compact; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -32,13 +39,16 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxReserves, MinBetSize, - MinimumPeriod, ParimutuelPalletId, BASE, + AssetsAccountDeposit, AssetsApprovalDeposit, AssetsDeposit, AssetsMetadataDepositBase, + AssetsMetadataDepositPerByte, AssetsStringLimit, BlockHashCount, DestroyAccountWeight, + DestroyApprovalWeight, DestroyFinishWeight, ExistentialDepositsNew, GetNativeCurrencyId, + MaxReserves, MinBetSize, MinimumPeriod, ParimutuelPalletId, BASE, }, traits::DistributeFees, types::{ - AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, - Index, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, + CampaignAsset, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, Hash, Index, + MarketAsset, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -78,6 +88,10 @@ where } } +type CustomAssetsInstance = pallet_assets::Instance1; +type CampaignAssetsInstance = pallet_assets::Instance2; +type MarketAssetsInstance = pallet_assets::Instance3; + construct_runtime!( pub enum Runtime where @@ -85,21 +99,26 @@ construct_runtime!( NodeBlock = BlockTest, UncheckedExtrinsic = UncheckedExtrinsicTest, { - Parimutuel: zrml_parimutuel::{Event, Pallet, Storage}, - Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, AssetManager: orml_currencies::{Call, Pallet, Storage}, - Tokens: orml_tokens::{Config, Event, Pallet, Storage}, + AssetRouter: zrml_asset_router::{Pallet}, + Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + CustomAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event}, MarketCommons: zrml_market_commons::{Pallet, Storage}, + Parimutuel: zrml_parimutuel::{Event, Pallet, Storage}, System: frame_system::{Call, Config, Event, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, + Tokens: orml_tokens::{Config, Event, Pallet, Storage}, } ); impl crate::Config for Runtime { + type AssetDestroyer = AssetRouter; + type AssetManager = AssetManager; type ExternalFees = ExternalFees; type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; - type AssetManager = AssetManager; type MinBetSize = MinBetSize; type PalletId = ParimutuelPalletId; type WeightInfo = crate::weights::WeightInfo; @@ -132,6 +151,106 @@ impl frame_system::Config for Runtime { type OnSetCode = (); } +pallet_assets::runtime_benchmarks_enabled! { + pub struct AssetsBenchmarkHelper; + + impl pallet_assets::BenchmarkHelper + for AssetsBenchmarkHelper + where + AssetIdParameter: From, + { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter { + (id as u128).into() + } + } +} + +pallet_assets::runtime_benchmarks_enabled! { + use zeitgeist_primitives::types::CategoryIndex; + + pub struct MarketAssetsBenchmarkHelper; + + impl pallet_assets::BenchmarkHelper + for MarketAssetsBenchmarkHelper + { + fn create_asset_id_parameter(id: u32) -> MarketAsset { + MarketAsset::CategoricalOutcome(0, id as CategoryIndex) + } + } +} + +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = AssetsApprovalDeposit; + type AssetAccountDeposit = AssetsAccountDeposit; + type AssetDeposit = AssetsDeposit; + type AssetId = CampaignAsset; + type AssetIdParameter = Compact; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRoot; + type Freezer = (); + type Destroyer = AssetRouter; + type MetadataDepositBase = AssetsMetadataDepositBase; + type MetadataDepositPerByte = AssetsMetadataDepositPerByte; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = AssetsStringLimit; + type WeightInfo = (); +} + +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = AssetsApprovalDeposit; + type AssetAccountDeposit = AssetsAccountDeposit; + type AssetDeposit = AssetsDeposit; + type AssetId = CustomAsset; + type AssetIdParameter = Compact; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRoot; + type Freezer = (); + type Destroyer = AssetRouter; + type MetadataDepositBase = AssetsMetadataDepositBase; + type MetadataDepositPerByte = AssetsMetadataDepositPerByte; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = AssetsStringLimit; + type WeightInfo = (); +} + +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = AssetsApprovalDeposit; + type AssetAccountDeposit = AssetsAccountDeposit; + type AssetDeposit = AssetsDeposit; + type AssetId = MarketAsset; + type AssetIdParameter = MarketAsset; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = MarketAssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRoot; + type Freezer = (); + type Destroyer = AssetRouter; + type MetadataDepositBase = AssetsMetadataDepositBase; + type MetadataDepositPerByte = AssetsMetadataDepositPerByte; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = AssetsStringLimit; + type WeightInfo = (); +} + impl pallet_balances::Config for Runtime { type AccountStore = System; type Balance = Balance; @@ -144,22 +263,9 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -impl zrml_market_commons::Config for Runtime { - type Balance = Balance; - type MarketId = MarketId; - type Timestamp = Timestamp; -} - -impl pallet_timestamp::Config for Runtime { - type MinimumPeriod = MinimumPeriod; - type Moment = Moment; - type OnTimestampSet = (); - type WeightInfo = (); -} - impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; + type MultiCurrency = AssetRouter; type NativeCurrency = BasicCurrencyAdapter; type WeightInfo = (); } @@ -167,10 +273,10 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = Assets; + type CurrencyId = Currencies; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; - type ExistentialDeposits = ExistentialDeposits; + type ExistentialDeposits = ExistentialDepositsNew; type MaxLocks = (); type MaxReserves = MaxReserves; type CurrencyHooks = (); @@ -178,6 +284,35 @@ impl orml_tokens::Config for Runtime { type WeightInfo = (); } +impl zrml_asset_router::Config for Runtime { + type AssetType = Assets; + type Balance = Balance; + type CurrencyType = Currencies; + type Currencies = Tokens; + type CampaignAssetType = CampaignAsset; + type CampaignAssets = CampaignAssets; + type CustomAssetType = CustomAsset; + type CustomAssets = CustomAssets; + type DestroyAccountWeight = DestroyAccountWeight; + type DestroyApprovalWeight = DestroyApprovalWeight; + type DestroyFinishWeight = DestroyFinishWeight; + type MarketAssetType = MarketAsset; + type MarketAssets = MarketAssets; +} + +impl zrml_market_commons::Config for Runtime { + type Balance = Balance; + type MarketId = MarketId; + type Timestamp = Timestamp; +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + pub struct ExtBuilder { balances: Vec<(AccountIdTest, Balance)>, } diff --git a/zrml/parimutuel/src/tests/buy.rs b/zrml/parimutuel/src/tests/buy.rs index 1ca6b13b5..20e827b4b 100644 --- a/zrml/parimutuel/src/tests/buy.rs +++ b/zrml/parimutuel/src/tests/buy.rs @@ -22,7 +22,7 @@ use core::ops::RangeInclusive; use frame_support::{assert_noop, assert_ok}; use orml_traits::MultiCurrency; use test_case::test_case; -use zeitgeist_primitives::types::{Asset, MarketStatus, MarketType, ScoringRule}; +use zeitgeist_primitives::types::{MarketStatus, MarketType, ParimutuelAsset, ScoringRule}; use zrml_market_commons::{Error as MError, Markets}; #[test] @@ -33,7 +33,7 @@ fn buy_emits_event() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); @@ -42,7 +42,14 @@ fn buy_emits_event() { assert_eq!(amount, amount_minus_fees + fees); System::assert_last_event( - Event::OutcomeBought { market_id, buyer: ALICE, asset, amount_minus_fees, fees }.into(), + Event::OutcomeBought { + market_id, + buyer: ALICE, + asset: asset.into(), + amount_minus_fees, + fees, + } + .into(), ); }); } @@ -57,12 +64,12 @@ fn buy_balances_change_correctly() { let base_asset = market.base_asset; - let free_alice_before = AssetManager::free_balance(base_asset, &ALICE); - let free_creator_before = AssetManager::free_balance(base_asset, &market.creator); + let free_alice_before = AssetManager::free_balance(base_asset.into(), &ALICE); + let free_creator_before = AssetManager::free_balance(base_asset.into(), &market.creator); let free_pot_before = - AssetManager::free_balance(base_asset, &Parimutuel::pot_account(market_id)); + AssetManager::free_balance(base_asset.into(), &Parimutuel::pot_account(market_id)); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); @@ -70,37 +77,23 @@ fn buy_balances_change_correctly() { let fees = 1000000000; assert_eq!(amount, amount_minus_fees + fees); - assert_eq!(AssetManager::free_balance(base_asset, &ALICE), free_alice_before - amount); assert_eq!( - AssetManager::free_balance(base_asset, &Parimutuel::pot_account(market_id)) + AssetManager::free_balance(base_asset.into(), &ALICE), + free_alice_before - amount + ); + assert_eq!( + AssetManager::free_balance(base_asset.into(), &Parimutuel::pot_account(market_id)) - free_pot_before, amount_minus_fees ); - assert_eq!(AssetManager::free_balance(asset, &ALICE), amount_minus_fees); + assert_eq!(AssetManager::free_balance(asset.into(), &ALICE), amount_minus_fees); assert_eq!( - AssetManager::free_balance(base_asset, &market.creator) - free_creator_before, + AssetManager::free_balance(base_asset.into(), &market.creator) - free_creator_before, fees ); }); } -#[test] -fn buy_fails_if_asset_not_parimutuel_share() { - ExtBuilder::default().build().execute_with(|| { - let market_id = 0; - let mut market = market_mock::(MARKET_CREATOR); - market.status = MarketStatus::Active; - Markets::::insert(market_id, market.clone()); - - let asset = Asset::CategoricalOutcome(market_id, 0u16); - let amount = ::MinBetSize::get(); - assert_noop!( - Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), - Error::::NotParimutuelOutcome - ); - }); -} - #[test_case(ScoringRule::Orderbook; "orderbook")] #[test_case(ScoringRule::Lmsr; "lmsr")] fn buy_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { @@ -112,7 +105,7 @@ fn buy_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { Markets::::insert(market_id, market.clone()); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get(); assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), @@ -134,7 +127,7 @@ fn buy_fails_if_market_status_is_not_active(status: MarketStatus) { Markets::::insert(market_id, market.clone()); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get(); assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), @@ -153,7 +146,7 @@ fn buy_fails_if_market_type_is_scalar() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get() + ::MinBetSize::get(); assert_noop!( @@ -171,10 +164,10 @@ fn buy_fails_if_insufficient_balance() { market.status = MarketStatus::Active; Markets::::insert(market_id, market.clone()); - let free_alice = AssetManager::free_balance(market.base_asset, &ALICE); - AssetManager::slash(market.base_asset, &ALICE, free_alice); + let free_alice = AssetManager::free_balance(market.base_asset.into(), &ALICE); + AssetManager::slash(market.base_asset.into(), &ALICE, free_alice); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get(); assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), @@ -191,7 +184,7 @@ fn buy_fails_if_below_minimum_bet_size() { market.status = MarketStatus::Active; Markets::::insert(market_id, market.clone()); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get() - 1; assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), @@ -205,7 +198,7 @@ fn buy_fails_if_market_does_not_exist() { ExtBuilder::default().build().execute_with(|| { let market_id = 0; - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = ::MinBetSize::get(); assert_noop!( Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount), diff --git a/zrml/parimutuel/src/tests/claim.rs b/zrml/parimutuel/src/tests/claim.rs index 5103b97b9..f2c245389 100644 --- a/zrml/parimutuel/src/tests/claim.rs +++ b/zrml/parimutuel/src/tests/claim.rs @@ -19,11 +19,20 @@ use crate::{mock::*, utils::*, *}; use core::ops::RangeInclusive; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{ + assert_noop, assert_ok, + pallet_prelude::Weight, + traits::{ + fungibles::{Create, Inspect}, + OnIdle, + }, +}; use orml_traits::MultiCurrency; use sp_runtime::Percent; use test_case::test_case; -use zeitgeist_primitives::types::{Asset, MarketStatus, MarketType, OutcomeReport, ScoringRule}; +use zeitgeist_primitives::types::{ + MarketStatus, MarketType, OutcomeReport, ParimutuelAsset, ScoringRule, +}; use zrml_market_commons::{Error as MError, Markets}; #[test] @@ -34,11 +43,12 @@ fn claim_rewards_emits_event() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let winner_asset = Asset::ParimutuelShare(market_id, 0u16); + let winner_asset = ParimutuelAsset::Share(market_id, 0u16); + AssetRouter::create(winner_asset.into(), Default::default(), true, 1).unwrap(); let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); - let loser_asset = Asset::ParimutuelShare(market_id, 1u16); + let loser_asset = ParimutuelAsset::Share(market_id, 1u16); let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); @@ -55,7 +65,7 @@ fn claim_rewards_emits_event() { System::assert_last_event( Event::RewardsClaimed { market_id, - asset: winner_asset, + asset: winner_asset.into(), withdrawn_asset_balance, base_asset_payoff: actual_payoff, sender: ALICE, @@ -73,14 +83,15 @@ fn claim_rewards_categorical_changes_balances_correctly() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let winner_asset = Asset::ParimutuelShare(market_id, 0u16); + let winner_asset = ParimutuelAsset::Share(market_id, 0u16); + AssetRouter::create(winner_asset.into(), Default::default(), true, 1).unwrap(); let winner_amount_0 = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount_0)); let winner_amount_1 = 30 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(CHARLIE), winner_asset, winner_amount_1)); - let loser_asset = Asset::ParimutuelShare(market_id, 1u16); + let loser_asset = ParimutuelAsset::Share(market_id, 1u16); let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); @@ -103,57 +114,95 @@ fn claim_rewards_categorical_changes_balances_correctly() { assert_eq!(Percent::from_percent(60) * actual_payoff, actual_payoff_charlie); assert_eq!(actual_payoff_alice + actual_payoff_charlie, actual_payoff); - let free_winner_asset_alice_before = AssetManager::free_balance(winner_asset, &ALICE); + let free_winner_asset_alice_before = + AssetManager::free_balance(winner_asset.into(), &ALICE); let winner_amount_0_minus_fees = winner_amount_0 - Percent::from_percent(1) * winner_amount_0; assert_eq!(free_winner_asset_alice_before, winner_amount_0_minus_fees); - let free_base_asset_alice_before = AssetManager::free_balance(market.base_asset, &ALICE); - let free_base_asset_pot_before = - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)); + let free_base_asset_alice_before = + AssetManager::free_balance(market.base_asset.into(), &ALICE); + let free_base_asset_pot_before = AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id), + ); assert_eq!(free_base_asset_pot_before, total_pot_amount - total_fees); assert_ok!(Parimutuel::claim_rewards(RuntimeOrigin::signed(ALICE), market_id)); assert_eq!( - free_winner_asset_alice_before - AssetManager::free_balance(winner_asset, &ALICE), + free_winner_asset_alice_before + - AssetManager::free_balance(winner_asset.into(), &ALICE), winner_amount_0_minus_fees ); assert_eq!( - AssetManager::free_balance(market.base_asset, &ALICE) - free_base_asset_alice_before, + AssetManager::free_balance(market.base_asset.into(), &ALICE) + - free_base_asset_alice_before, actual_payoff_alice ); assert_eq!( - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)), + AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id) + ), actual_payoff_charlie ); - let free_winner_asset_charlie_before = AssetManager::free_balance(winner_asset, &CHARLIE); + let free_winner_asset_charlie_before = + AssetManager::free_balance(winner_asset.into(), &CHARLIE); let winner_amount_1_minus_fees = winner_amount_1 - Percent::from_percent(1) * winner_amount_1; assert_eq!(free_winner_asset_charlie_before, winner_amount_1_minus_fees); let free_base_asset_charlie_before = - AssetManager::free_balance(market.base_asset, &CHARLIE); + AssetManager::free_balance(market.base_asset.into(), &CHARLIE); assert_ok!(Parimutuel::claim_rewards(RuntimeOrigin::signed(CHARLIE), market_id)); assert_eq!( - free_winner_asset_charlie_before - AssetManager::free_balance(winner_asset, &CHARLIE), + free_winner_asset_charlie_before + - AssetManager::free_balance(winner_asset.into(), &CHARLIE), winner_amount_1_minus_fees ); assert_eq!( - AssetManager::free_balance(market.base_asset, &CHARLIE) + AssetManager::free_balance(market.base_asset.into(), &CHARLIE) - free_base_asset_charlie_before, actual_payoff_charlie ); assert_eq!( - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)), + AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id) + ), 0 ); }); } +#[test] +fn claim_rewards_destroys_assets() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0; + let mut market = market_mock::(MARKET_CREATOR); + market.status = MarketStatus::Active; + Markets::::insert(market_id, market); + + let winner_asset = ParimutuelAsset::Share(market_id, 0u16); + AssetRouter::create(winner_asset.into(), Default::default(), true, 1).unwrap(); + let winner_amount = 20 * ::MinBetSize::get(); + assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); + + let mut market = Markets::::get(market_id).unwrap(); + market.status = MarketStatus::Resolved; + market.resolved_outcome = Some(OutcomeReport::Categorical(0u16)); + Markets::::insert(market_id, market.clone()); + + assert_ok!(Parimutuel::claim_rewards(RuntimeOrigin::signed(ALICE), market_id)); + AssetRouter::on_idle(System::block_number(), Weight::MAX); + assert!(!AssetRouter::asset_exists(winner_asset.into())); + }); +} + #[test] fn claim_rewards_fails_if_market_type_is_scalar() { ExtBuilder::default().build().execute_with(|| { @@ -245,11 +294,11 @@ fn claim_rewards_categorical_fails_if_no_winner() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let winner_asset = Asset::ParimutuelShare(market_id, 0u16); + let winner_asset = ParimutuelAsset::Share(market_id, 0u16); let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); - let loser_asset = Asset::ParimutuelShare(market_id, 1u16); + let loser_asset = ParimutuelAsset::Share(market_id, 1u16); let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); @@ -275,11 +324,11 @@ fn claim_rewards_categorical_fails_if_no_winning_shares() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let winner_asset = Asset::ParimutuelShare(market_id, 0u16); + let winner_asset = ParimutuelAsset::Share(market_id, 0u16); let winner_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), winner_asset, winner_amount)); - let loser_asset = Asset::ParimutuelShare(market_id, 1u16); + let loser_asset = ParimutuelAsset::Share(market_id, 1u16); let loser_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), loser_asset, loser_amount)); @@ -305,11 +354,11 @@ fn claim_refunds_works() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let alice_asset = Asset::ParimutuelShare(market_id, 0u16); + let alice_asset = ParimutuelAsset::Share(market_id, 0u16); let alice_amount = 20 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), alice_asset, alice_amount)); - let bob_asset = Asset::ParimutuelShare(market_id, 1u16); + let bob_asset = ParimutuelAsset::Share(market_id, 1u16); let bob_amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(BOB), bob_asset, bob_amount)); @@ -330,33 +379,46 @@ fn claim_refunds_works() { let alice_amount_minus_fees = alice_amount - alice_paid_fees; let bob_amount_minus_fees = bob_amount - bob_paid_fees; - let free_base_asset_alice_before = AssetManager::free_balance(market.base_asset, &ALICE); - let free_base_asset_bob_before = AssetManager::free_balance(market.base_asset, &BOB); - let free_base_asset_pot_before = - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)); + let free_base_asset_alice_before = + AssetManager::free_balance(market.base_asset.into(), &ALICE); + let free_base_asset_bob_before = AssetManager::free_balance(market.base_asset.into(), &BOB); + let free_base_asset_pot_before = AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id), + ); assert_ok!(Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), alice_asset)); assert_eq!( - AssetManager::free_balance(market.base_asset, &ALICE) - free_base_asset_alice_before, + AssetManager::free_balance(market.base_asset.into(), &ALICE) + - free_base_asset_alice_before, alice_amount_minus_fees ); assert_eq!( - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)), + AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id) + ), free_base_asset_pot_before - alice_amount_minus_fees ); assert_eq!( - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)), + AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id) + ), bob_amount_minus_fees ); assert_ok!(Parimutuel::claim_refunds(RuntimeOrigin::signed(BOB), bob_asset)); assert_eq!( - AssetManager::free_balance(market.base_asset, &BOB) - free_base_asset_bob_before, + AssetManager::free_balance(market.base_asset.into(), &BOB) - free_base_asset_bob_before, bob_amount_minus_fees ); assert_eq!( - AssetManager::free_balance(market.base_asset, &Parimutuel::pot_account(market_id)), + AssetManager::free_balance( + market.base_asset.into(), + &Parimutuel::pot_account(market_id) + ), 0 ); }); diff --git a/zrml/parimutuel/src/tests/refund.rs b/zrml/parimutuel/src/tests/refund.rs index 14e5c7a48..b7cd6a12a 100644 --- a/zrml/parimutuel/src/tests/refund.rs +++ b/zrml/parimutuel/src/tests/refund.rs @@ -21,29 +21,11 @@ use crate::{mock::*, utils::*, *}; use frame_support::{assert_noop, assert_ok}; use sp_runtime::Percent; use test_case::test_case; -use zeitgeist_primitives::types::{Asset, MarketStatus, MarketType, OutcomeReport, ScoringRule}; +use zeitgeist_primitives::types::{ + MarketStatus, MarketType, OutcomeReport, ParimutuelAsset, ScoringRule, +}; use zrml_market_commons::Markets; -#[test] -fn refund_fails_if_not_parimutuel_outcome() { - ExtBuilder::default().build().execute_with(|| { - let market_id = 0; - let mut market = market_mock::(MARKET_CREATOR); - market.market_type = MarketType::Categorical(10u16); - market.resolved_outcome = Some(OutcomeReport::Categorical(0u16)); - market.status = MarketStatus::Resolved; - Markets::::insert(market_id, market); - - assert_noop!( - Parimutuel::claim_refunds( - RuntimeOrigin::signed(ALICE), - Asset::CategoricalOutcome(market_id, 0u16) - ), - Error::::NotParimutuelOutcome - ); - }); -} - #[test_case(MarketStatus::Active; "active")] #[test_case(MarketStatus::Proposed; "proposed")] #[test_case(MarketStatus::Closed; "closed")] @@ -57,7 +39,7 @@ fn refund_fails_if_market_not_resolved(status: MarketStatus) { market.status = status; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_noop!( Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset), Error::::MarketIsNotResolvedYet @@ -78,7 +60,7 @@ fn refund_fails_if_invalid_scoring_rule(scoring_rule: ScoringRule) { market.scoring_rule = scoring_rule; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_noop!( Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset), Error::::InvalidScoringRule @@ -96,7 +78,7 @@ fn refund_fails_if_invalid_outcome_asset() { market.status = MarketStatus::Resolved; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 20u16); + let asset = ParimutuelAsset::Share(market_id, 20u16); assert_noop!( Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset), Error::::InvalidOutcomeAsset @@ -114,7 +96,7 @@ fn refund_fails_if_no_resolved_outcome() { market.resolved_outcome = None; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_noop!( Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset), Error::::NoResolvedOutcome @@ -131,7 +113,7 @@ fn refund_fails_if_refund_not_allowed() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); @@ -140,7 +122,7 @@ fn refund_fails_if_refund_not_allowed() { market.status = MarketStatus::Resolved; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_noop!( Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset), Error::::RefundNotAllowed @@ -157,7 +139,7 @@ fn refund_fails_if_refundable_balance_is_zero() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = 2 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); @@ -166,7 +148,7 @@ fn refund_fails_if_refundable_balance_is_zero() { market.status = MarketStatus::Resolved; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_ok!(Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset)); // already refunded above @@ -186,7 +168,7 @@ fn refund_emits_event() { market.status = MarketStatus::Active; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); let amount = 10 * ::MinBetSize::get(); assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), asset, amount)); @@ -195,7 +177,7 @@ fn refund_emits_event() { market.status = MarketStatus::Resolved; Markets::::insert(market_id, market); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelAsset::Share(market_id, 0u16); assert_ok!(Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), asset)); let amount_minus_fees = amount - (Percent::from_percent(1) * amount); @@ -203,7 +185,7 @@ fn refund_emits_event() { System::assert_last_event( Event::BalanceRefunded { market_id, - asset, + asset: asset.into(), refunded_balance: amount_minus_fees, sender: ALICE, } From b2c3fcb3ff7d377aa596ce8d142fad3cb24a91ca Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 23 Feb 2024 12:06:52 +0100 Subject: [PATCH 060/104] Add MarketTransitionApi This will allow prediction-markets to signal other pallets that state transitions happened --- Cargo.lock | 1 + primitives/Cargo.toml | 1 + primitives/src/traits.rs | 2 ++ .../src/traits/market_transition_api.rs | 29 +++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 primitives/src/traits/market_transition_api.rs diff --git a/Cargo.lock b/Cargo.lock index d98f220cc..b5f6d5206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14399,6 +14399,7 @@ dependencies = [ "fixed", "frame-support", "frame-system", + "impl-trait-for-tuples", "more-asserts", "num-traits", "orml-currencies", diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 4a4bca7b9..0d28af56c 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -3,6 +3,7 @@ arbitrary = { workspace = true, optional = true } fixed = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = "0.2.2" num-traits = { workspace = true } orml-currencies = { workspace = true } orml-tokens = { workspace = true } diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 02d8d70f1..16bd860c0 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -21,6 +21,7 @@ mod deploy_pool_api; mod dispute_api; mod distribute_fees; mod market_commons_pallet_api; +mod market_transition_api; mod market_id; mod swaps; mod weights; @@ -33,6 +34,7 @@ pub use dispute_api::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}; pub use distribute_fees::DistributeFees; pub use market_commons_pallet_api::MarketCommonsPalletApi; pub use market_id::MarketId; +pub use market_transition_api::MarketTransitionApi; pub use swaps::Swaps; pub use weights::CheckedDivPerComponent; pub use zeitgeist_asset::*; diff --git a/primitives/src/traits/market_transition_api.rs b/primitives/src/traits/market_transition_api.rs new file mode 100644 index 000000000..2fffbf472 --- /dev/null +++ b/primitives/src/traits/market_transition_api.rs @@ -0,0 +1,29 @@ +// Copyright 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 . + +use parity_scale_codec::{HasCompact, MaxEncodedLen}; + +/// API that provides a signal on each market state transition +#[impl_trait_for_tuples::impl_for_tuples(8)] +pub trait MarketTransitionApi { + fn on_proposal(_market_id: &MI) {} + fn on_activation(_market_id: &MI) {} + fn on_closure(_market_id: &MI) {} + fn on_report(_market_id: &MI) {} + fn on_dispute(_market_id: &MI) {} + fn on_resolution(_market_id: &MI) {} +} From 15b52393a0bc5a75e9e974086c529aa45db7ceea Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 23 Feb 2024 16:26:45 +0100 Subject: [PATCH 061/104] Add MarketTransitionApi --- primitives/src/traits.rs | 2 +- .../src/traits/market_transition_api.rs | 61 ++++++++++++++++--- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 16bd860c0..d01d0a8c4 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -21,8 +21,8 @@ mod deploy_pool_api; mod dispute_api; mod distribute_fees; mod market_commons_pallet_api; -mod market_transition_api; mod market_id; +mod market_transition_api; mod swaps; mod weights; mod zeitgeist_asset; diff --git a/primitives/src/traits/market_transition_api.rs b/primitives/src/traits/market_transition_api.rs index 2fffbf472..8c0d48ad5 100644 --- a/primitives/src/traits/market_transition_api.rs +++ b/primitives/src/traits/market_transition_api.rs @@ -15,15 +15,60 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use parity_scale_codec::{HasCompact, MaxEncodedLen}; +use frame_support::{pallet_prelude::DispatchResult, require_transactional}; /// API that provides a signal on each market state transition +pub trait MarketTransitionApi { + #[require_transactional] + fn on_proposal(_market_id: &MI) -> DispatchResult { + Ok(()) + } + #[require_transactional] + fn on_activation(_market_id: &MI) -> DispatchResult { + Ok(()) + } + #[require_transactional] + fn on_closure(_market_id: &MI) -> DispatchResult { + Ok(()) + } + #[require_transactional] + fn on_report(_market_id: &MI) -> DispatchResult { + Ok(()) + } + #[require_transactional] + fn on_dispute(_market_id: &MI) -> DispatchResult { + Ok(()) + } + #[require_transactional] + fn on_resolution(_market_id: &MI) -> DispatchResult { + Ok(()) + } +} + #[impl_trait_for_tuples::impl_for_tuples(8)] -pub trait MarketTransitionApi { - fn on_proposal(_market_id: &MI) {} - fn on_activation(_market_id: &MI) {} - fn on_closure(_market_id: &MI) {} - fn on_report(_market_id: &MI) {} - fn on_dispute(_market_id: &MI) {} - fn on_resolution(_market_id: &MI) {} +impl MarketTransitionApi for Tuple { + fn on_proposal(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_proposal(market_id)?; )* ); + Ok(()) + } + fn on_activation(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_activation(market_id)?; )* ); + Ok(()) + } + fn on_closure(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_closure(market_id)?; )* ); + Ok(()) + } + fn on_report(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_report(market_id)?; )* ); + Ok(()) + } + fn on_dispute(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_dispute(market_id)?; )* ); + Ok(()) + } + fn on_resolution(market_id: &MI) -> DispatchResult { + for_tuples!( #( Tuple::on_resolution(market_id)?; )* ); + Ok(()) + } } From faee7efbf1729fa7dbb046aeed0cffdfa28ee1bb Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 23 Feb 2024 16:41:42 +0100 Subject: [PATCH 062/104] Implement MarketTransitionApi for Parimutuels --- zrml/parimutuel/src/lib.rs | 77 +++++++++++++++++++++++++++++++++---- zrml/parimutuel/src/mock.rs | 1 + 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 92474a380..4b4671d1c 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -18,6 +18,8 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + mod benchmarking; mod mock; mod tests; @@ -29,12 +31,13 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::weights::WeightInfoZeitgeist; + use alloc::collections::BTreeMap; use core::marker::PhantomData; use frame_support::{ ensure, log, pallet_prelude::{Decode, DispatchError, Encode, TypeInfo}, require_transactional, - traits::{Get, IsType, StorageVersion}, + traits::{fungibles::Create, Get, IsType, StorageVersion}, PalletId, RuntimeDebug, }; use frame_system::{ @@ -50,7 +53,7 @@ mod pallet { use zeitgeist_macros::unreachable_non_terminating; use zeitgeist_primitives::{ math::fixed::FixedMulDiv, - traits::DistributeFees, + traits::{DistributeFees, MarketTransitionApi}, types::{ Asset, BaseAsset, Market, MarketStatus, MarketType, OutcomeReport, ParimutuelAssetClass, ScoringRule, @@ -60,12 +63,15 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// The module handling the destruction of market assets. - type AssetDestroyer: ManagedDestroy, Balance = BalanceOf>; + /// The module handling the creation of market assets. + type AssetCreator: Create, Balance = BalanceOf>; /// The api to handle different asset classes. type AssetManager: MultiCurrency>; + /// The module handling the destruction of market assets. + type AssetDestroyer: ManagedDestroy, Balance = BalanceOf>; + /// The way how fees are taken from the market base asset. type ExternalFees: DistributeFees< Asset = AssetOf, @@ -449,12 +455,12 @@ mod pallet { )?; if outcome_total == winning_balance { - let res = T::AssetDestroyer::managed_destroy(winning_asset, None); + let destroy_result = T::AssetDestroyer::managed_destroy(winning_asset, None); unreachable_non_terminating!( - res.is_ok(), + destroy_result.is_ok(), LOG_TARGET, "Can't destroy winning outcome asset: {:?}", - res.err() + destroy_result.err() ); } @@ -527,4 +533,61 @@ mod pallet { Ok(()) } } + + impl MarketTransitionApi> for Pallet { + fn on_activation(market_id: &MarketIdOf) -> DispatchResult { + let market = T::MarketCommons::market(market_id)?; + if market.scoring_rule != ScoringRule::Parimutuel { return Ok(()) }; + + for outcome in market.outcome_assets(*market_id) { + let admin = Self::pot_account(*market_id); + let is_sufficient = true; + let min_balance = 1u8; + T::AssetCreator::create(outcome.into(), admin, is_sufficient, min_balance.into())?; + } + + Ok(()) + } + fn on_resolution(market_id: &MarketIdOf) -> DispatchResult { + let market = T::MarketCommons::market(market_id)?; + if market.scoring_rule != ScoringRule::Parimutuel { return Ok(()) }; + + let winning_asset = Self::get_winning_asset(*market_id, &market)?; + let outcome_total = T::AssetManager::total_issuance(winning_asset); + // Allow to refund shares. + if outcome_total.is_zero() { + return Ok(()); + } + + let winning_outcome = market.resolved_outcome_into_outcome(*market_id); + if let Some(winning_outcome_inner) = winning_outcome { + // Destroy losing assets. + let assets_to_destroy = BTreeMap::, Option>::from_iter( + market + .outcome_assets(*market_id) + .into_iter() + .filter(|outcome| *outcome != winning_outcome_inner) + .map(|asset| AssetOf::::from(asset)) + .zip(vec![None]), + ); + + let destroy_result = T::AssetDestroyer::managed_destroy_multi(assets_to_destroy); + unreachable_non_terminating!( + destroy_result.is_ok(), + LOG_TARGET, + "Can't destroy losing outcome asset: {:?}", + destroy_result + ); + } else { + unreachable_non_terminating!( + winning_outcome.is_some(), + LOG_TARGET, + "Resolved market with id {:?} does not have a resolved outcome", + market_id, + ); + } + + Ok(()) + } + } } diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index dc9f244d4..20751b709 100644 --- a/zrml/parimutuel/src/mock.rs +++ b/zrml/parimutuel/src/mock.rs @@ -114,6 +114,7 @@ construct_runtime!( ); impl crate::Config for Runtime { + type AssetCreator = AssetRouter; type AssetDestroyer = AssetRouter; type AssetManager = AssetManager; type ExternalFees = ExternalFees; From 8bd8fa54c00366c50e2ec4426085d7c5cd15ab54 Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Sat, 24 Feb 2024 12:35:44 +0100 Subject: [PATCH 063/104] Only run copyright CI when merging into `main` (#1263) --- .github/workflows/copyright.yml | 34 +++++++++++++++++++++++++++++++++ .github/workflows/rust.yml | 25 ------------------------ 2 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/copyright.yml diff --git a/.github/workflows/copyright.yml b/.github/workflows/copyright.yml new file mode 100644 index 000000000..f7136a793 --- /dev/null +++ b/.github/workflows/copyright.yml @@ -0,0 +1,34 @@ +name: Copyright + +on: + pull_request: + types: [ labeled ] + branches: [ main ] + push: + branches: [ main ] + +jobs: + copyright: + name: Copyright Notices + if: | + github.event_name == 'pull_request' && + (contains(github.event.pull_request.labels.*.name, 's:review-needed') || + contains(github.event.pull_request.labels.*.name, 's:accepted')) || + github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + - name: Install check-license and dependencies + run: | + pip install scripts/check-license + pip install -r scripts/check-license/requirements.txt + - name: Query files changed + id: files_changed + uses: Ana06/get-changed-files@v1.2 + with: + filter: '*.rs$' + - name: Check copyright notices + run: check-license ${{ steps.files_changed.outputs.added_modified }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4d52a85ec..064bf936a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -36,31 +36,6 @@ jobs: - name: Format run: ./scripts/tests/format.sh --check - copyright: - name: Copyright Notices - if: | - github.event_name == 'pull_request' && - (contains(github.event.pull_request.labels.*.name, 's:review-needed') || - contains(github.event.pull_request.labels.*.name, 's:accepted')) || - github.event_name == 'push' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - - name: Install check-license and dependencies - run: | - pip install scripts/check-license - pip install -r scripts/check-license/requirements.txt - - name: Query files changed - id: files_changed - uses: Ana06/get-changed-files@v1.2 - with: - filter: '*.rs$' - - name: Check copyright notices - run: check-license ${{ steps.files_changed.outputs.added_modified }} - checks: name: Checks if: | From 5d8847421f21e79b342abd2182d5c8484956769e Mon Sep 17 00:00:00 2001 From: Malte Kliemann Date: Mon, 26 Feb 2024 13:35:33 +0100 Subject: [PATCH 064/104] Bring README up to date (#1264) --- GH-banner.jpg | Bin 684061 -> 0 bytes GH-banner.svg | 21 ++++++++++++++ README.md | 75 +++++++++++++++++++++++--------------------------- 3 files changed, 55 insertions(+), 41 deletions(-) delete mode 100644 GH-banner.jpg create mode 100644 GH-banner.svg diff --git a/GH-banner.jpg b/GH-banner.jpg deleted file mode 100644 index 2d7e3b25bba1aafdae3d59d12164c2ad23bc01a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684061 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