From 3c95d8b75aa4de11bd538926859e9c2adf498d35 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 22 Dec 2023 16:07:03 +0100 Subject: [PATCH 01/14] New asset system - Separate asset types and add pallet-assets instances to runtime (#1177) * Add new asset types * Add custom assets to runtime * Add market assets to runtime * Add pallet_assets benchmark and weights * Expose MarketAssets call enum * Update todo comment to incluse issue number * Add campaign assets instance to runtime * Cargo fmt * Taplo fmt * Fix comments Co-authored-by: Malte Kliemann * Adjust ForceOrigins * Fix typo * Update primitives/src/types.rs Co-authored-by: Chralt * Remove CombinatorialOutcome from PredictionMarketAsset --------- Co-authored-by: Malte Kliemann Co-authored-by: Chralt --- Cargo.lock | 18 + Cargo.toml | 1 + primitives/src/asset.rs | 19 + primitives/src/types.rs | 7 + runtime/battery-station/Cargo.toml | 4 + runtime/battery-station/src/lib.rs | 12 +- runtime/battery-station/src/parameters.rs | 32 ++ runtime/common/Cargo.toml | 2 + runtime/common/src/lib.rs | 111 ++++++ runtime/common/src/weights/mod.rs | 1 + runtime/common/src/weights/pallet_assets.rs | 417 ++++++++++++++++++++ runtime/zeitgeist/Cargo.toml | 4 + runtime/zeitgeist/src/lib.rs | 10 +- runtime/zeitgeist/src/parameters.rs | 30 ++ scripts/benchmarks/configuration.sh | 6 +- 15 files changed, 664 insertions(+), 10 deletions(-) create mode 100644 runtime/common/src/weights/pallet_assets.rs diff --git a/Cargo.lock b/Cargo.lock index f7e33ea0a..f20131183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,6 +562,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-asset-tx-payment", + "pallet-assets", "pallet-aura", "pallet-author-inherent", "pallet-author-mapping", @@ -1214,6 +1215,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "pallet-asset-tx-payment", + "pallet-assets", "pallet-author-inherent", "pallet-author-mapping", "pallet-author-slot-filter", @@ -5857,6 +5859,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.38#bcff60a227d455d95b4712b6cb356ce56b1ff672" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-aura" version = "4.0.0-dev" @@ -14422,6 +14439,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-asset-tx-payment", + "pallet-assets", "pallet-aura", "pallet-author-inherent", "pallet-author-mapping", diff --git a/Cargo.toml b/Cargo.toml index e4718530d..00ca1d25f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,7 @@ frame-system-benchmarking = { git = "https://github.com/paritytech/substrate", b frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } frame-try-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } pallet-asset-tx-payment = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } +pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } pallet-bounties = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38", default-features = false } diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index bc9c61637..597241ab8 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -23,6 +23,9 @@ use scale_info::TypeInfo; /// The `Asset` enum represents all types of assets available in the Zeitgeist /// system. /// +/// **Deprecated:** Market and Pool assets are "lazy" migrated to pallet-assets +/// Do not create any new market or pool assets using this enumeration. +/// /// # Types /// /// * `MI`: Market Id @@ -53,6 +56,22 @@ pub enum Asset { ParimutuelShare(MI, CategoryIndex), } +/// The `MarketAsset` enum represents all types of assets available in the +/// prediction market protocol. +/// +/// # Types +/// +/// * `MI`: Market Id +#[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 PredictionMarketAsset { + CategoricalOutcome(MI, CategoryIndex), + ScalarOutcome(MI, ScalarPosition), + ParimutuelShare(MI, CategoryIndex), + PoolShare(PoolId), +} + /// 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/types.rs b/primitives/src/types.rs index 27b61b513..13b74921f 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -81,8 +81,15 @@ impl<'a> Arbitrary<'a> for MultiHash { /// ORML adapter pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; +/// ID type for any asset class pub type CurrencyId = Asset; +/// ID type for asset classes that anyone can create +pub type AssetId = u128; + +/// ID type for the asset classes are used within the prediction market protocol +pub type MarketAsset = PredictionMarketAsset; + /// 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/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 5cc792b90..acf436504 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -11,6 +11,7 @@ orml-currencies = { workspace = true } orml-tokens = { workspace = true } orml-traits = { workspace = true } pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-bounties = { workspace = true } pallet-collective = { workspace = true } @@ -182,6 +183,7 @@ runtime-benchmarks = [ "orml-benchmarking", "orml-tokens/runtime-benchmarks", "orml-xtokens?/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-author-inherent?/runtime-benchmarks", "pallet-author-mapping?/runtime-benchmarks", "pallet-author-slot-filter?/runtime-benchmarks", @@ -231,6 +233,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "pallet-assets/std", "pallet-asset-tx-payment/std", "pallet-balances/std", "pallet-bounties/std", @@ -352,6 +355,7 @@ try-runtime = [ "pallet-preimage/try-runtime", # Money runtime pallets + "pallet-assets/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-balances/try-runtime", "pallet-bounties/try-runtime", diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index 1475c43e7..d99e2dc95 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -30,7 +30,7 @@ use common_runtime::{ }; pub use frame_system::{ Call as SystemCall, CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, - CheckTxVersion, CheckWeight, + CheckTxVersion, CheckWeight, EnsureNever, }; #[cfg(feature = "parachain")] pub use pallet_author_slot_filter::EligibilityValue; @@ -42,12 +42,16 @@ pub use crate::parachain_params::*; pub use crate::parameters::*; use alloc::vec; use frame_support::{ - traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, InstanceFilter}, + traits::{ + AsEnsureOriginWithArg, ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, + InstanceFilter, + }, weights::{constants::RocksDbWeight, ConstantMultiplier, IdentityFee, Weight}, }; -use frame_system::{EnsureRoot, EnsureWithSuccess}; +use frame_system::{EnsureRoot, EnsureSigned, EnsureWithSuccess}; use orml_currencies::Call::transfer; use pallet_collective::{EnsureProportionAtLeast, PrimeDefaultVote}; +use parity_scale_codec::Compact; use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256}; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -66,7 +70,7 @@ use zrml_swaps::Call::{ }; #[cfg(feature = "parachain")] use { - frame_support::traits::{AsEnsureOriginWithArg, Everything, Nothing}, + frame_support::traits::{Everything, Nothing}, xcm_builder::{EnsureXcmOrigin, FixedWeightBounds}, xcm_config::{ asset_registry::CustomAssetProcessor, diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 40cce1de7..f2dfd713d 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -63,6 +63,38 @@ parameter_types! { } parameter_types! { + // Assets (Campaign) + pub const CampaignAssetsAccountDeposit: Balance = deposit(1, 16); + pub const CampaignAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + /// The amount of native currency that is frozen during the whole lifetime + /// if an asset class. Freezing happens at asset class creation. + /// Irrelevant - No origin can successfully call the associated dispatchable call. + pub const CampaignAssetsDeposit: Balance = BASE; + pub const CampaignAssetsStringLimit: u32 = 256; + pub const CampaignAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const CampaignAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + + // Assets (Custom) + pub const CustomAssetsAccountDeposit: Balance = deposit(1, 16); + pub const CustomAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + /// The amount of native currency that is frozen during the whole lifetime + /// of an asset class. Freezing happens at asset class creation. + pub const CustomAssetsDeposit: Balance = BASE; + pub const CustomAssetsStringLimit: u32 = 50; + pub const CustomAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const CustomAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + + // Assets (Market) + pub const MarketAssetsAccountDeposit: Balance = deposit(1, 16); + pub const MarketAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + /// The amount of native currency that is frozen during the whole lifetime + /// of an asset class. Freezing happens at asset class creation. + /// Irrelevant - No origin can successfully call the associated dispatchable call. + pub const MarketAssetsDeposit: Balance = BASE; + pub const MarketAssetsStringLimit: u32 = 50; + pub const MarketAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const MarketAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + // Authorized pub const AuthorizedPalletId: PalletId = AUTHORIZED_PALLET_ID; pub const CorrectionPeriod: BlockNumber = BLOCKS_PER_DAY; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 1e6d468f3..3663efd37 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -6,6 +6,7 @@ frame-system = { workspace = true } orml-currencies = { workspace = true } orml-tokens = { workspace = true } pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } pallet-author-inherent = { workspace = true, optional = true } pallet-author-mapping = { workspace = true, optional = true } pallet-author-slot-filter = { workspace = true, optional = true } @@ -49,6 +50,7 @@ std = [ "frame-support/std", "orml-currencies/std", "orml-tokens/std", + "pallet-assets/std", "pallet-asset-tx-payment/std", "pallet-author-inherent?/std", "pallet-author-mapping?/std", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 0e29d2223..4298d2517 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -93,6 +93,11 @@ macro_rules! decl_common_types { pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + // Asset instances + type CustomAssetsInstance = pallet_assets::Instance1; + type CampaignAssetsInstance = pallet_assets::Instance2; + type MarketAssetsInstance = pallet_assets::Instance3; + // Governance type AdvisoryCommitteeInstance = pallet_collective::Instance1; type AdvisoryCommitteeMembershipInstance = pallet_membership::Instance1; @@ -284,6 +289,9 @@ macro_rules! create_runtime { Multisig: pallet_multisig::{Call, Event, Pallet, Storage} = 14, Bounties: pallet_bounties::{Call, Event, Pallet, Storage} = 15, AssetTxPayment: pallet_asset_tx_payment::{Event, Pallet} = 16, + Assets: pallet_assets::::{Call, Pallet, Storage, Event} = 17, + CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event} = 18, + MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event} = 19, // Governance Democracy: pallet_democracy::{Pallet, Call, Storage, Config, Event} = 20, @@ -625,6 +633,107 @@ macro_rules! impl_config_traits { type XcmExecutor = xcm_executor::XcmExecutor; } + // Required for runtime benchmarks + pallet_assets::runtime_benchmarks_enabled! { + pub struct CustomAssetsBenchmarkHelper; + + impl pallet_assets::BenchmarkHelper + for CustomAssetsBenchmarkHelper + where + AssetIdParameter: From, + { + fn create_asset_id_parameter(id: u32) -> AssetIdParameter { + (id as u128).into() + } + } + } + + impl pallet_assets::Config for Runtime { + type ApprovalDeposit = CustomAssetsApprovalDeposit; + type AssetAccountDeposit = CustomAssetsAccountDeposit; + type AssetDeposit = CustomAssetsDeposit; + type AssetId = AssetId; + type AssetIdParameter = Compact; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = CustomAssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; + type Freezer = (); + type MetadataDepositBase = CustomAssetsMetadataDepositBase; + type MetadataDepositPerByte = CustomAssetsMetadataDepositPerByte; + // TODO(#1176): Figure out sensible number after benchmark on reference machine + type RemoveItemsLimit = ConstU32<{ 50 }>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = CustomAssetsStringLimit; + type WeightInfo = weights::pallet_assets::WeightInfo; + } + + impl pallet_assets::Config for Runtime { + type ApprovalDeposit = CampaignAssetsApprovalDeposit; + type AssetAccountDeposit = CampaignAssetsAccountDeposit; + type AssetDeposit = CampaignAssetsDeposit; + type AssetId = AssetId; + type AssetIdParameter = Compact; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = CustomAssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRootOrTwoThirdsCouncil; + type Freezer = (); + type MetadataDepositBase = CampaignAssetsMetadataDepositBase; + type MetadataDepositPerByte = CampaignAssetsMetadataDepositPerByte; + // TODO(#1176): Figure out sensible number after benchmark on reference machine + type RemoveItemsLimit = ConstU32<{ 50 }>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = CampaignAssetsStringLimit; + type WeightInfo = weights::pallet_assets::WeightInfo; + } + + // Required for runtime benchmarks + pallet_assets::runtime_benchmarks_enabled! { + 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 = MarketAssetsApprovalDeposit; + type AssetAccountDeposit = MarketAssetsAccountDeposit; + type AssetDeposit = MarketAssetsDeposit; + 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 = EnsureRootOrAllTechnicalCommittee; + type Freezer = (); + type MetadataDepositBase = MarketAssetsMetadataDepositBase; + type MetadataDepositPerByte = MarketAssetsMetadataDepositPerByte; + // TODO(#1176): Figure out sensible number after benchmark on reference machine + type RemoveItemsLimit = ConstU32<{ 50 }>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = MarketAssetsStringLimit; + type WeightInfo = weights::pallet_assets::WeightInfo; + } + + impl pallet_balances::Config for Runtime { type AccountStore = System; type Balance = Balance; @@ -1372,6 +1481,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, frame_system, SystemBench::); orml_list_benchmark!(list, extra, orml_currencies, crate::benchmarks::currencies); orml_list_benchmark!(list, extra, orml_tokens, crate::benchmarks::tokens); + list_benchmark!(list, extra, pallet_assets, Assets); list_benchmark!(list, extra, pallet_balances, Balances); list_benchmark!(list, extra, pallet_bounties, Bounties); list_benchmark!(list, extra, pallet_collective, AdvisoryCommittee); @@ -1476,6 +1586,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, frame_system, SystemBench::); orml_add_benchmark!(params, batches, orml_currencies, crate::benchmarks::currencies); orml_add_benchmark!(params, batches, orml_tokens, crate::benchmarks::tokens); + add_benchmark!(params, batches, pallet_assets, Assets); add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_bounties, Bounties); add_benchmark!(params, batches, pallet_collective, AdvisoryCommittee); diff --git a/runtime/common/src/weights/mod.rs b/runtime/common/src/weights/mod.rs index 826493ca0..37ccda9ec 100644 --- a/runtime/common/src/weights/mod.rs +++ b/runtime/common/src/weights/mod.rs @@ -32,6 +32,7 @@ cfg_if::cfg_if! { pub mod frame_system; pub mod orml_currencies; pub mod orml_tokens; +pub mod pallet_assets; pub mod pallet_balances; pub mod pallet_bounties; pub mod pallet_collective; diff --git a/runtime/common/src/weights/pallet_assets.rs b/runtime/common/src/weights/pallet_assets.rs new file mode 100644 index 000000000..3a30a53f5 --- /dev/null +++ b/runtime/common/src/weights/pallet_assets.rs @@ -0,0 +1,417 @@ +// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2023-11-01`, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `sea212-PC`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/debug/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=2 +// --repeat=2 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./misc/frame_weight_template.hbs +// --header=./HEADER_GPL3 +// --output=./runtime/common/src/weights/ + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; + +/// Weight functions for pallet_assets (automatically generated) +pub struct WeightInfo(PhantomData); +impl pallet_assets::weights::WeightInfo for WeightInfo { + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `218` + // Estimated: `5304` + // Minimum execution time: 294_576 nanoseconds. + Weight::from_parts(316_096_000, 5304) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_create() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `2697` + // Minimum execution time: 163_914 nanoseconds. + Weight::from_parts(187_984_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn start_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `2697` + // Minimum execution time: 160_353 nanoseconds. + Weight::from_parts(172_194_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:51 w:50) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: System Account (r:50 w:50) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// The range of component `c` is `[0, 50]`. + fn destroy_accounts(c: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + c * (242 ±0)` + // Estimated: `135636 + c * (2589 ±0)` + // Minimum execution time: 224_474 nanoseconds. + Weight::from_parts(225_024_000, 135636) + // Standard Error: 432_869 + .saturating_add(Weight::from_parts(176_285_210, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2589).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:51 w:50) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// The range of component `a` is `[0, 50]`. + fn destroy_approvals(a: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `358 + a * (88 ±0)` + // Estimated: `5332 + a * (2635 ±0)` + // Minimum execution time: 201_885 nanoseconds. + Weight::from_parts(210_474_500, 5332) + // Standard Error: 807_700 + .saturating_add(Weight::from_parts(184_051_830, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn finish_destroy() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `5324` + // Minimum execution time: 184_174 nanoseconds. + Weight::from_parts(207_774_000, 5324) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + fn mint() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `5286` + // Minimum execution time: 303_436 nanoseconds. + Weight::from_parts(316_737_000, 5286) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + fn burn() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `5286` + // Minimum execution time: 388_118 nanoseconds. + Weight::from_parts(402_668_000, 5286) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `483` + // Estimated: `10482` + // Minimum execution time: 566_502 nanoseconds. + Weight::from_parts(587_882_000, 10482) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// 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: `483` + // Estimated: `10482` + // Minimum execution time: 500_120 nanoseconds. + Weight::from_parts(517_041_000, 10482) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `483` + // Estimated: `10482` + // Minimum execution time: 565_382 nanoseconds. + Weight::from_parts(581_262_000, 10482) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + fn freeze() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `5286` + // Minimum execution time: 212_584 nanoseconds. + Weight::from_parts(231_845_000, 5286) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + fn thaw() -> Weight { + // Proof Size summary in bytes: + // Measured: `444` + // Estimated: `5286` + // Minimum execution time: 213_705 nanoseconds. + Weight::from_parts(225_135_000, 5286) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn freeze_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `2697` + // Minimum execution time: 157_223 nanoseconds. + Weight::from_parts(169_343_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn thaw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `2697` + // Minimum execution time: 158_273 nanoseconds. + Weight::from_parts(170_884_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn transfer_ownership() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `5324` + // Minimum execution time: 189_044 nanoseconds. + Weight::from_parts(201_334_000, 5324) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn set_team() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `2697` + // Minimum execution time: 166_284 nanoseconds. + Weight::from_parts(180_313_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn set_metadata(n: u32, s: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `5324` + // Minimum execution time: 282_556 nanoseconds. + Weight::from_parts(287_195_500, 5324) + // Standard Error: 211_675 + .saturating_add(Weight::from_parts(55_910, 0).saturating_mul(n.into())) + // Standard Error: 211_675 + .saturating_add(Weight::from_parts(115_310, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `532` + // Estimated: `5324` + // Minimum execution time: 295_517 nanoseconds. + Weight::from_parts(310_096_000, 5324) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 50]`. + /// The range of component `s` is `[0, 50]`. + fn force_set_metadata(n: u32, s: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `131` + // Estimated: `5324` + // Minimum execution time: 186_034 nanoseconds. + Weight::from_parts(189_209_000, 5324) + // Standard Error: 183_142 + .saturating_add(Weight::from_parts(54_710, 0).saturating_mul(n.into())) + // Standard Error: 183_142 + .saturating_add(Weight::from_parts(76_500, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + fn force_clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `532` + // Estimated: `5324` + // Minimum execution time: 291_577 nanoseconds. + Weight::from_parts(308_826_000, 5324) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + fn force_asset_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `324` + // Estimated: `2697` + // Minimum execution time: 163_124 nanoseconds. + Weight::from_parts(177_773_000, 2697) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn approve_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `5332` + // Minimum execution time: 326_807 nanoseconds. + Weight::from_parts(340_847_000, 5332) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + fn transfer_approved() -> Weight { + // Proof Size summary in bytes: + // Measured: `665` + // Estimated: `13117` + // Minimum execution time: 739_565 nanoseconds. + Weight::from_parts(755_396_000, 13117) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `540` + // Estimated: `5332` + // Minimum execution time: 356_107 nanoseconds. + Weight::from_parts(379_428_000, 5332) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + fn force_cancel_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `540` + // Estimated: `5332` + // Minimum execution time: 360_088 nanoseconds. + Weight::from_parts(375_088_000, 5332) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 170503a16..25d46bd22 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -11,6 +11,7 @@ orml-currencies = { workspace = true } orml-tokens = { workspace = true } orml-traits = { workspace = true } pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-bounties = { workspace = true } pallet-collective = { workspace = true } @@ -182,6 +183,7 @@ runtime-benchmarks = [ "orml-benchmarking", "orml-tokens/runtime-benchmarks", "orml-xtokens?/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-author-inherent?/runtime-benchmarks", "pallet-author-mapping?/runtime-benchmarks", "pallet-author-slot-filter?/runtime-benchmarks", @@ -230,6 +232,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "pallet-assets/std", "pallet-asset-tx-payment/std", "pallet-balances/std", "pallet-bounties/std", @@ -342,6 +345,7 @@ try-runtime = [ "pallet-preimage/try-runtime", # Money runtime pallets + "pallet-assets/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-balances/try-runtime", "pallet-bounties/try-runtime", diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 1e8a0b2ea..f9cf35c7b 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -30,7 +30,7 @@ use common_runtime::{ }; pub use frame_system::{ Call as SystemCall, CheckEra, CheckGenesis, CheckNonZeroSender, CheckNonce, CheckSpecVersion, - CheckTxVersion, CheckWeight, + CheckTxVersion, CheckWeight, EnsureNever, EnsureSigned, }; #[cfg(feature = "parachain")] pub use pallet_author_slot_filter::EligibilityValue; @@ -41,11 +41,15 @@ pub use crate::parachain_params::*; pub use crate::parameters::*; use alloc::vec; use frame_support::{ - traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, InstanceFilter, Nothing}, + traits::{ + AsEnsureOriginWithArg, ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, + InstanceFilter, Nothing, + }, weights::{constants::RocksDbWeight, ConstantMultiplier, IdentityFee, Weight}, }; use frame_system::{EnsureRoot, EnsureWithSuccess}; use pallet_collective::{EnsureProportionAtLeast, EnsureProportionMoreThan, PrimeDefaultVote}; +use parity_scale_codec::Compact; use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256}; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -54,7 +58,7 @@ use zeitgeist_primitives::{constants::*, types::*}; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; #[cfg(feature = "parachain")] use { - frame_support::traits::{AsEnsureOriginWithArg, Everything}, + frame_support::traits::Everything, xcm_builder::{EnsureXcmOrigin, FixedWeightBounds}, xcm_config::{ asset_registry::CustomAssetProcessor, diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index c9e718f13..5172970ab 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -63,6 +63,36 @@ parameter_types! { } parameter_types! { + // Assets (Campaign) + pub const CampaignAssetsAccountDeposit: Balance = deposit(1, 16); + pub const CampaignAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + /// The amount of native currency that is frozen during the whole lifetime + /// of an asset class. Freezing happens at asset class creation. + /// Irrelevant - No origin can successfully call the associated dispatchable call. + pub const CampaignAssetsDeposit: Balance = 400 * BASE; + pub const CampaignAssetsStringLimit: u32 = 256; + pub const CampaignAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const CampaignAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + + // Assets (Custom) + pub const CustomAssetsAccountDeposit: Balance = deposit(1, 16); + pub const CustomAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + pub const CustomAssetsDeposit: Balance = 400 * BASE; + pub const CustomAssetsStringLimit: u32 = 50; + pub const CustomAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const CustomAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + + // Assets (Market) + pub const MarketAssetsAccountDeposit: Balance = deposit(1, 16); + pub const MarketAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); + /// The amount of native currency that is frozen during the whole lifetime + /// if an asset class. Freezing happens at asset class creation. + /// Irrelevant - No origin can successfully call the associated dispatchable call. + pub const MarketAssetsDeposit: Balance = 400 * BASE; + pub const MarketAssetsStringLimit: u32 = 50; + pub const MarketAssetsMetadataDepositBase: Balance = deposit(1, 68); + pub const MarketAssetsMetadataDepositPerByte: Balance = deposit(0, 1); + // Authorized pub const AuthorizedPalletId: PalletId = AUTHORIZED_PALLET_ID; pub const CorrectionPeriod: BlockNumber = BLOCKS_PER_DAY; diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 7543915c1..71ebb67a1 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -4,9 +4,9 @@ EXTERNAL_WEIGHTS_PATH="./runtime/common/src/weights/" # This script contains the configuration for other benchmarking scripts. export FRAME_PALLETS=( - frame_system pallet_balances pallet_bounties pallet_collective pallet_contracts \ - pallet_democracy pallet_identity pallet_membership pallet_multisig pallet_preimage \ - pallet_proxy pallet_scheduler pallet_timestamp pallet_treasury pallet_utility \ + frame_system pallet_assets pallet_balances pallet_bounties pallet_collective \ + pallet_contracts pallet_democracy pallet_identity pallet_membership pallet_multisig \ + pallet_preimage pallet_proxy pallet_scheduler pallet_timestamp pallet_treasury pallet_utility \ pallet_vesting \ ) # pallet_grandpa ) export FRAME_PALLETS_RUNS="${FRAME_PALLETS_RUNS:-20}" From 4628f8c0af5f44eab01add840fee8e7b78aced1e Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 12 Jan 2024 14:04:11 +0100 Subject: [PATCH 02/14] New Asset System - Prepare extension of Asset Manager (#1181) * Add new asset types * Add custom assets to runtime * Add market assets to runtime * Add pallet_assets benchmark and weights * Expose MarketAssets call enum * Update todo comment to incluse issue number * Add campaign assets instance to runtime * Cargo fmt * Taplo fmt * Refine asset class types * Use efficient asset types * Add all variants to overarching Asset enum * Make MarketId compactable * Adjust SerdeWrapper Soon to be deprecated * Make Battery Station compileable * Make Zeitgeist compileable * Cleanup code * Remove NewForeignAsset Conversion to compact encoding implies massive migration effort * Implement asset type conversions * Add Currency asset class * Remove deprecated SerdeWrapper * Add Currencies asset class Also renames existing CurrencyId to Assets * Add scale codec index matching tests * Add asset conversion tests * Update docstring * Improve assets module structure * Update license * Update copyright * Repair errorneous merge * Update license * Remove CombinatorialOutcome * Improve tests * Improve variable names * Prettify code Co-authored-by: Malte Kliemann * Fix conversion CampaignAssetId <> CampaignAssetClass * Reorder asset enum variants * Update runtime/zeitgeist/src/parameters.rs Co-authored-by: Chralt * Improve CurrencyClass docstring Co-authored-by: Malte Kliemann --------- Co-authored-by: Malte Kliemann Co-authored-by: Chralt --- primitives/src/asset.rs | 87 ------- primitives/src/assets.rs | 49 ++++ primitives/src/assets/all_assets.rs | 152 ++++++++++++ primitives/src/assets/campaign_assets.rs | 51 ++++ primitives/src/assets/currencies.rs | 69 ++++++ primitives/src/assets/custom_assets.rs | 49 ++++ primitives/src/assets/market_assets.rs | 85 +++++++ primitives/src/assets/tests.rs | 24 ++ primitives/src/assets/tests/conversion.rs | 233 ++++++++++++++++++ primitives/src/assets/tests/scale_codec.rs | 108 ++++++++ primitives/src/constants/mock.rs | 10 +- primitives/src/lib.rs | 5 +- primitives/src/pool.rs | 10 +- primitives/src/serde_wrapper.rs | 65 ----- primitives/src/traits.rs | 2 +- primitives/src/traits/dispute_api.rs | 8 +- .../src/traits/market_commons_pallet_api.rs | 5 +- primitives/src/traits/market_id.rs | 13 +- primitives/src/traits/swaps.rs | 6 +- primitives/src/types.rs | 29 ++- .../src/integration_tests/xcm/setup.rs | 19 +- .../src/integration_tests/xcm/test_net.rs | 8 +- .../xcm/tests/currency_id_convert.rs | 10 +- .../integration_tests/xcm/tests/transfers.rs | 12 +- runtime/battery-station/src/parameters.rs | 15 +- .../src/xcm_config/asset_registry.rs | 12 +- .../battery-station/src/xcm_config/config.rs | 50 ++-- .../battery-station/src/xcm_config/fees.rs | 6 +- runtime/common/src/fees.rs | 4 +- runtime/common/src/lib.rs | 54 ++-- .../src/integration_tests/xcm/setup.rs | 21 +- .../src/integration_tests/xcm/test_net.rs | 8 +- .../xcm/tests/currency_id_convert.rs | 10 +- .../integration_tests/xcm/tests/transfers.rs | 16 +- runtime/zeitgeist/src/parameters.rs | 17 +- .../src/xcm_config/asset_registry.rs | 12 +- runtime/zeitgeist/src/xcm_config/config.rs | 50 ++-- runtime/zeitgeist/src/xcm_config/fees.rs | 6 +- zrml/market-commons/src/lib.rs | 5 +- zrml/neo-swaps/src/mock.rs | 10 +- .../fuzz/orderbook_v1_full_workflow.rs | 6 +- zrml/orderbook/src/mock.rs | 8 +- zrml/orderbook/src/types.rs | 6 +- zrml/parimutuel/src/mock.rs | 8 +- .../prediction-markets/runtime-api/src/lib.rs | 5 +- zrml/prediction-markets/src/mock.rs | 18 +- zrml/simple-disputes/src/mock.rs | 8 +- zrml/swaps/fuzz/pool_exit.rs | 9 +- .../fuzz/pool_exit_with_exact_asset_amount.rs | 9 +- .../fuzz/pool_exit_with_exact_pool_amount.rs | 9 +- zrml/swaps/fuzz/utils.rs | 6 +- zrml/swaps/rpc/src/lib.rs | 32 ++- zrml/swaps/runtime-api/src/lib.rs | 14 +- zrml/swaps/src/arbitrage.rs | 12 +- zrml/swaps/src/lib.rs | 6 +- zrml/swaps/src/mock.rs | 20 +- 56 files changed, 1143 insertions(+), 438 deletions(-) delete mode 100644 primitives/src/asset.rs create mode 100644 primitives/src/assets.rs create mode 100644 primitives/src/assets/all_assets.rs create mode 100644 primitives/src/assets/campaign_assets.rs create mode 100644 primitives/src/assets/currencies.rs create mode 100644 primitives/src/assets/custom_assets.rs create mode 100644 primitives/src/assets/market_assets.rs create mode 100644 primitives/src/assets/tests.rs create mode 100644 primitives/src/assets/tests/conversion.rs create mode 100644 primitives/src/assets/tests/scale_codec.rs delete mode 100644 primitives/src/serde_wrapper.rs diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs deleted file mode 100644 index 597241ab8..000000000 --- a/primitives/src/asset.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. -// Copyright 2021-2022 Zeitgeist PM LLC. -// -// This file is part of Zeitgeist. -// -// Zeitgeist is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the -// Free Software Foundation, either version 3 of the License, or (at -// your option) any later version. -// -// Zeitgeist is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Zeitgeist. If not, see . - -use crate::types::{CategoryIndex, PoolId, SerdeWrapper}; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; - -/// The `Asset` enum represents all types of assets available in the Zeitgeist -/// system. -/// -/// **Deprecated:** Market and Pool assets are "lazy" migrated to pallet-assets -/// Do not create any new market or pool assets using this enumeration. -/// -/// # Types -/// -/// * `MI`: Market Id -#[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, - Ord, - PartialEq, - PartialOrd, - TypeInfo, -)] -pub enum Asset { - CategoricalOutcome(MI, CategoryIndex), - ScalarOutcome(MI, ScalarPosition), - CombinatorialOutcome, - PoolShare(SerdeWrapper), - #[default] - Ztg, - ForeignAsset(u32), - ParimutuelShare(MI, CategoryIndex), -} - -/// The `MarketAsset` enum represents all types of assets available in the -/// prediction market protocol. -/// -/// # Types -/// -/// * `MI`: Market Id -#[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 PredictionMarketAsset { - CategoricalOutcome(MI, CategoryIndex), - ScalarOutcome(MI, ScalarPosition), - ParimutuelShare(MI, CategoryIndex), - PoolShare(PoolId), -} - -/// 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 -/// to the lower bound. -#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[derive( - Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, Ord, PartialEq, PartialOrd, TypeInfo, -)] -pub enum ScalarPosition { - Long, - Short, -} diff --git a/primitives/src/assets.rs b/primitives/src/assets.rs new file mode 100644 index 000000000..104df8c3a --- /dev/null +++ b/primitives/src/assets.rs @@ -0,0 +1,49 @@ +// 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 crate::types::{CampaignAssetId, CategoryIndex, CustomAssetId, PoolId}; +use parity_scale_codec::{Compact, CompactAs, Decode, Encode, HasCompact, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub use all_assets::Asset; +pub use campaign_assets::CampaignAssetClass; +pub use currencies::CurrencyClass; +pub use custom_assets::CustomAssetClass; +pub use market_assets::MarketAssetClass; + +mod all_assets; +mod campaign_assets; +mod currencies; +mod custom_assets; +mod market_assets; +#[cfg(test)] +mod tests; + +/// 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 +/// to the lower bound. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, Ord, PartialEq, PartialOrd, TypeInfo, +)] +pub enum ScalarPosition { + Long, + Short, +} diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs new file mode 100644 index 000000000..feff1d2cb --- /dev/null +++ b/primitives/src/assets/all_assets.rs @@ -0,0 +1,152 @@ +// 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 . + +use super::*; + +/// The `Asset` enum represents all types of assets available in the Zeitgeist +/// system. +/// +/// This complete enumeration is intended to abstract the common interaction +/// with tokens away. For example, the developer is not forced to be aware +/// about which exact implementation will handle the desired asset class to +/// instruct operations such as `transfer` or `freeze`, instead it is +/// sufficient to call a crate that manages the routing. +/// While it is not recommended to use this enum in storage, it should not pose +/// a problem as long as all other asset types use the same scale encoding for +/// a matching asset variant in this enum. +/// +/// **Deprecated:** Market and Pool assets are "lazy" migrated to +/// pallet-assets. +/// Do not create any new market or pool assets using the deprecated variants +/// in this enum. +/// +/// # Types +/// +/// * `MI`: Market Id +#[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, + Ord, + PartialEq, + PartialOrd, + TypeInfo, +)] +pub enum Asset { + #[codec(index = 0)] + CategoricalOutcome(MI, CategoryIndex), + + #[codec(index = 1)] + ScalarOutcome(MI, ScalarPosition), + + #[codec(index = 3)] + PoolShare(PoolId), + + #[codec(index = 4)] + #[default] + Ztg, + + #[codec(index = 5)] + ForeignAsset(u32), + + #[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)] + 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) + } + MarketAssetClass::::ScalarOutcome(market_id, scalar_pos) => { + Self::NewScalarOutcome(market_id, scalar_pos) + } + MarketAssetClass::::ParimutuelShare(market_id, cat_id) => { + Self::NewParimutuelShare(market_id, cat_id) + } + MarketAssetClass::::PoolShare(pool_id) => Self::NewPoolShare(pool_id), + } + } +} + +impl From for Asset { + fn from(value: CampaignAssetClass) -> Self { + Self::CampaignAssetClass(value.0) + } +} + +impl From for Asset { + fn from(value: CustomAssetClass) -> Self { + Self::CustomAssetClass(value.0) + } +} + +impl From> for Asset { + fn from(value: CurrencyClass) -> Self { + match value { + CurrencyClass::::OldCategoricalOutcome(market_id, cat_id) => { + Self::CategoricalOutcome(market_id, cat_id) + } + CurrencyClass::::OldScalarOutcome(market_id, scalar_pos) => { + Self::ScalarOutcome(market_id, scalar_pos) + } + CurrencyClass::::OldParimutuelShare(market_id, cat_id) => { + Self::ParimutuelShare(market_id, cat_id) + } + CurrencyClass::::OldPoolShare(pool_id) => Self::PoolShare(pool_id), + CurrencyClass::::ForeignAsset(asset_id) => Self::ForeignAsset(asset_id), + } + } +} diff --git a/primitives/src/assets/campaign_assets.rs b/primitives/src/assets/campaign_assets.rs new file mode 100644 index 000000000..245b60ce4 --- /dev/null +++ b/primitives/src/assets/campaign_assets.rs @@ -0,0 +1,51 @@ +// 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 . + +use super::*; + +/// The `CampaignAsset` tuple struct represents all campaign assets. +/// Campaign assets can have special properties, such as the capability +/// to pay fees. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + Clone, CompactAs, Copy, Debug, Decode, Default, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo, +)] +pub struct CampaignAssetClass(#[codec(compact)] pub(super) CampaignAssetId); + +impl From> for CampaignAssetClass { + fn from(value: Compact) -> CampaignAssetClass { + CampaignAssetClass(value.into()) + } +} + +impl From for Compact { + fn from(value: CampaignAssetClass) -> Compact { + value.0.into() + } +} + +impl TryFrom> for CampaignAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::CampaignAssetClass(id) => Ok(Self(id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/currencies.rs b/primitives/src/assets/currencies.rs new file mode 100644 index 000000000..02b4cd86a --- /dev/null +++ b/primitives/src/assets/currencies.rs @@ -0,0 +1,69 @@ +// 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 . + +use super::*; + +/// The `CurrencyClass` enum represents all non-ztg currencies. +#[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 CurrencyClass { + // All "Old" variants will be removed once the lazy migration from + // orml-tokens to pallet-assets is complete + #[codec(index = 0)] + OldCategoricalOutcome(MI, CategoryIndex), + + #[codec(index = 1)] + OldScalarOutcome(MI, ScalarPosition), + + #[codec(index = 3)] + OldPoolShare(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), +} + +impl Default for CurrencyClass { + fn default() -> Self { + Self::ForeignAsset(u32::default()) + } +} + +impl TryFrom> for CurrencyClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::CategoricalOutcome(market_id, cat_id) => { + Ok(Self::OldCategoricalOutcome(market_id, cat_id)) + } + Asset::::ScalarOutcome(market_id, scalar_pos) => { + Ok(Self::OldScalarOutcome(market_id, scalar_pos)) + } + Asset::::ParimutuelShare(market_id, cat_id) => { + Ok(Self::OldParimutuelShare(market_id, cat_id)) + } + Asset::::PoolShare(pool_id) => Ok(Self::OldPoolShare(pool_id)), + Asset::::ForeignAsset(id) => Ok(Self::ForeignAsset(id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/custom_assets.rs b/primitives/src/assets/custom_assets.rs new file mode 100644 index 000000000..e3264c770 --- /dev/null +++ b/primitives/src/assets/custom_assets.rs @@ -0,0 +1,49 @@ +// 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 . + +use super::*; + +/// The `CustomAsset` tuple struct represents all custom assets. +#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[derive( + Clone, CompactAs, Copy, Debug, Decode, Default, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo, +)] +pub struct CustomAssetClass(#[codec(compact)] pub(super) CustomAssetId); + +impl From> for CustomAssetClass { + fn from(value: Compact) -> CustomAssetClass { + CustomAssetClass(value.into()) + } +} + +impl From for Compact { + fn from(value: CustomAssetClass) -> Compact { + value.0.into() + } +} + +impl TryFrom> for CustomAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::CustomAssetClass(id) => Ok(Self(id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/market_assets.rs b/primitives/src/assets/market_assets.rs new file mode 100644 index 000000000..62263c470 --- /dev/null +++ b/primitives/src/assets/market_assets.rs @@ -0,0 +1,85 @@ +// 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 . + +use super::*; + +/// The `MarketAsset` enum represents all types of assets available in the +/// Prediction Market protocol +/// +/// # Types +/// +/// * `MI`: Market Id +#[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 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), + + #[codec(index = 1)] + OldScalarOutcome(MI, ScalarPosition), + + #[codec(index = 3)] + OldPoolShare(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), +} + +impl TryFrom> for MarketAssetClass { + type Error = (); + + 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)) + } + Asset::::ScalarOutcome(market_id, scalar_pos) => { + Ok(Self::OldScalarOutcome(market_id, scalar_pos)) + } + Asset::::ParimutuelShare(market_id, cat_id) => { + Ok(Self::OldParimutuelShare(market_id, cat_id)) + } + Asset::::PoolShare(pool_id) => Ok(Self::OldPoolShare(pool_id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/tests.rs b/primitives/src/assets/tests.rs new file mode 100644 index 000000000..9aa7bec90 --- /dev/null +++ b/primitives/src/assets/tests.rs @@ -0,0 +1,24 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use crate::types::MarketId; + +mod conversion; +mod scale_codec; diff --git a/primitives/src/assets/tests/conversion.rs b/primitives/src/assets/tests/conversion.rs new file mode 100644 index 000000000..6d6e5bfc6 --- /dev/null +++ b/primitives/src/assets/tests/conversion.rs @@ -0,0 +1,233 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use test_case::test_case; + +// Assets <> MarketAssetClass +#[test_case( + Asset::::CategoricalOutcome(7, 8), + MarketAssetClass::::OldCategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + MarketAssetClass::::OldPoolShare(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" +)] +fn from_all_assets_to_market_assets( + old_asset: Asset, + new_asset: MarketAssetClass, +) { + let new_asset_converted: MarketAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test_case( + MarketAssetClass::::OldCategoricalOutcome(7, 8), + Asset::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long), + Asset::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + MarketAssetClass::::OldPoolShare(7), + Asset::::PoolShare(7); + "pool_share" +)] +#[test_case( + MarketAssetClass::::OldParimutuelShare(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, +) { + let new_asset_converted: Asset = old_asset.into(); + assert_eq!(new_asset, new_asset_converted); +} + +// Assets <> CurrencyClass +#[test_case( + Asset::::CategoricalOutcome(7, 8), + CurrencyClass::::OldCategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + CurrencyClass::::OldPoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::ParimutuelShare(7, 8), + CurrencyClass::::OldParimutuelShare(7, 8); + "parimutuel_share" +)] +#[test_case( + Asset::::ForeignAsset(7), + CurrencyClass::::ForeignAsset(7); + "foreign_asset" +)] +fn from_all_assets_to_currencies(old_asset: Asset, new_asset: CurrencyClass) { + let new_asset_converted: CurrencyClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test_case( + CurrencyClass::::OldCategoricalOutcome(7, 8), + Asset::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long), + Asset::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + CurrencyClass::::OldPoolShare(7), + Asset::::PoolShare(7); + "pool_share" +)] +#[test_case( + CurrencyClass::::OldParimutuelShare(7, 8), + Asset::::ParimutuelShare(7, 8); + "parimutuel_share" +)] +#[test_case( + CurrencyClass::::ForeignAsset(7), + Asset::::ForeignAsset(7); + "foreign_asset" +)] +fn from_currencies_to_all_assets(old_asset: CurrencyClass, new_asset: Asset) { + let new_asset_converted: Asset = old_asset.into(); + assert_eq!(new_asset, new_asset_converted); +} + +// Assets <> CampaignAssetClass +#[test] +fn from_all_assets_to_campaign_assets() { + let old_asset = Asset::::CampaignAssetClass(7); + let new_asset = CampaignAssetClass(7); + + let new_asset_converted: CampaignAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test] +fn from_campaign_assets_to_all_assets() { + let old_asset = CampaignAssetClass(7); + let new_asset = Asset::::CampaignAssetClass(7); + let new_asset_converted: Asset = old_asset.into(); + assert_eq!(new_asset, new_asset_converted); +} + +// Assets <> CustomAssetClass +#[test] +fn from_all_assets_to_custom_assets() { + let old_asset = Asset::::CustomAssetClass(7); + let new_asset = CustomAssetClass(7); + + let new_asset_converted: CustomAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test] +fn from_custom_assets_to_all_assets() { + let old_asset = CampaignAssetClass(7); + let new_asset = Asset::::CampaignAssetClass(7); + 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() { + let campaign_asset_id = Compact(7); + let campaign_asset = CampaignAssetClass::from(campaign_asset_id); + let campaign_asset_id_converted = campaign_asset.into(); + assert_eq!(campaign_asset_id, campaign_asset_id_converted); +} + +// CustomAssetId <> CustomAssetClass +#[test] +fn from_custom_asset_id_to_custom_asset() { + let custom_asset_id = Compact(7); + let custom_asset = CustomAssetClass::from(custom_asset_id); + let custom_asset_id_converted = custom_asset.into(); + assert_eq!(custom_asset_id, custom_asset_id_converted); +} diff --git a/primitives/src/assets/tests/scale_codec.rs b/primitives/src/assets/tests/scale_codec.rs new file mode 100644 index 000000000..9c6d775d9 --- /dev/null +++ b/primitives/src/assets/tests/scale_codec.rs @@ -0,0 +1,108 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use test_case::test_case; + +// Assets <> MarketAssetClass +#[test_case( + Asset::::CategoricalOutcome(7, 8), + MarketAssetClass::::OldCategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + MarketAssetClass::::OldScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + MarketAssetClass::::OldPoolShare(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" +)] +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 <> CurrencyClass +#[test_case( + Asset::::CategoricalOutcome(7, 8), + CurrencyClass::::OldCategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + CurrencyClass::::OldScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + CurrencyClass::::OldPoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::ParimutuelShare(7, 8), + CurrencyClass::::OldParimutuelShare(7, 8); + "parimutuel_share" +)] +#[test_case( + Asset::::ForeignAsset(7), + CurrencyClass::::ForeignAsset(7); + "foreign_asset" +)] +fn index_matching_works_for_currencies( + old_asset: Asset, + 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(); + assert_eq!(new_asset_decoded, new_asset); +} diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 299cf27b6..8f8c49b9b 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. @@ -20,8 +20,8 @@ pub use super::*; use crate::{ - asset::Asset, - types::{Balance, CurrencyId, Moment}, + assets::Asset, + types::{Assets, Balance, Moment}, }; use frame_support::{parameter_types, traits::LockIdentifier, PalletId}; use orml_traits::parameter_type_with_key; @@ -161,11 +161,11 @@ parameter_types! { // ORML parameter_types! { // ORML - pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; + pub const GetNativeCurrencyId: Assets = Asset::Ztg; } parameter_type_with_key! { - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Assets| -> Balance { match currency_id { Asset::Ztg => ExistentialDeposit::get(), _ => 10 diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5a3d424d8..7b5fce9dc 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. @@ -20,7 +20,7 @@ extern crate alloc; -mod asset; +mod assets; pub mod constants; mod market; pub mod math; @@ -29,6 +29,5 @@ mod outcome_report; mod pool; mod pool_status; mod proxy_type; -mod serde_wrapper; pub mod traits; pub mod types; diff --git a/primitives/src/pool.rs b/primitives/src/pool.rs index 2b1c70529..ac4184819 100644 --- a/primitives/src/pool.rs +++ b/primitives/src/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. @@ -21,14 +21,14 @@ use crate::{ types::{Asset, PoolStatus}, }; use alloc::{collections::BTreeMap, vec::Vec}; -use parity_scale_codec::{Compact, Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::{Compact, Decode, Encode, HasCompact, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{RuntimeDebug, SaturatedConversion}; #[derive(TypeInfo, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] pub struct Pool where - MarketId: MaxEncodedLen, + MarketId: MaxEncodedLen + HasCompact, { pub assets: Vec>, pub base_asset: Asset, @@ -43,7 +43,7 @@ where impl Pool where - MarketId: MaxEncodedLen + Ord, + MarketId: MaxEncodedLen + Ord + HasCompact, { pub fn bound(&self, asset: &Asset) -> bool { if let Some(weights) = &self.weights { @@ -57,7 +57,7 @@ where impl MaxEncodedLen for Pool where Balance: MaxEncodedLen, - MarketId: MaxEncodedLen, + MarketId: MaxEncodedLen + HasCompact, { fn max_encoded_len() -> usize { let max_encoded_length_bytes = >::max_encoded_len(); diff --git a/primitives/src/serde_wrapper.rs b/primitives/src/serde_wrapper.rs deleted file mode 100644 index bc4196632..000000000 --- a/primitives/src/serde_wrapper.rs +++ /dev/null @@ -1,65 +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 . - -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -#[cfg(feature = "std")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -/// Used to workaround serde serialization/deserialization problems involving `u128`. -/// -/// # Types -/// -/// * `B`: Balance -#[derive( - scale_info::TypeInfo, - Clone, - Copy, - Debug, - Decode, - Default, - Encode, - Eq, - MaxEncodedLen, - Ord, - PartialEq, - PartialOrd, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -pub struct SerdeWrapper( - #[cfg_attr(feature = "std", serde(bound(serialize = "B: std::fmt::Display")))] - #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] - #[cfg_attr(feature = "std", serde(bound(deserialize = "B: std::str::FromStr")))] - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] - pub B, -); - -#[cfg(feature = "std")] -fn serialize_as_string( - t: &T, - serializer: S, -) -> Result { - serializer.serialize_str(&t.to_string()) -} - -#[cfg(feature = "std")] -fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>( - deserializer: D, -) -> Result { - let s = String::deserialize(deserializer)?; - s.parse::().map_err(|_| serde::de::Error::custom("Parse from string failed")) -} diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index cea15e4d0..bef79bfae 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. diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index 7c7accf1c..6d23c8c98 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.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. @@ -24,7 +24,7 @@ use crate::{ }; use alloc::vec::Vec; use frame_support::pallet_prelude::Weight; -use parity_scale_codec::MaxEncodedLen; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; use sp_runtime::DispatchError; // Abstraction of the market type, which is not a part of `DisputeApi` because Rust doesn't support @@ -45,7 +45,7 @@ pub trait DisputeApi { type Balance; type NegativeImbalance; type BlockNumber; - type MarketId: MaxEncodedLen; + type MarketId: MaxEncodedLen + HasCompact; type Moment; type Origin; @@ -157,7 +157,7 @@ pub trait DisputeResolutionApi { type AccountId; type Balance; type BlockNumber; - type MarketId: MaxEncodedLen; + type MarketId: MaxEncodedLen + HasCompact; type Moment; /// Resolve a market. diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index e5866f9f9..59aecfc43 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. @@ -25,7 +25,7 @@ use frame_support::{ storage::PrefixIterator, Parameter, }; -use parity_scale_codec::{FullCodec, MaxEncodedLen}; +use parity_scale_codec::{FullCodec, HasCompact, MaxEncodedLen}; use sp_runtime::traits::{AtLeast32Bit, AtLeast32BitUnsigned}; // Abstraction of the market type, which is not a part of `MarketCommonsPalletApi` because Rust @@ -55,6 +55,7 @@ pub trait MarketCommonsPalletApi { + Default + MaybeSerializeDeserialize + MaxEncodedLen + + HasCompact + Member + Parameter; type Moment: AtLeast32Bit + Copy + Default + Parameter + MaxEncodedLen; diff --git a/primitives/src/traits/market_id.rs b/primitives/src/traits/market_id.rs index fdd6b34ad..1965d408c 100644 --- a/primitives/src/traits/market_id.rs +++ b/primitives/src/traits/market_id.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -19,11 +20,18 @@ use frame_support::{ pallet_prelude::{MaybeSerializeDeserialize, Member}, Parameter, }; -use parity_scale_codec::MaxEncodedLen; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; use sp_runtime::traits::AtLeast32Bit; pub trait MarketId: - AtLeast32Bit + Copy + Default + MaxEncodedLen + MaybeSerializeDeserialize + Member + Parameter + AtLeast32Bit + + Copy + + Default + + HasCompact + + MaxEncodedLen + + MaybeSerializeDeserialize + + Member + + Parameter { } @@ -31,6 +39,7 @@ impl MarketId for T where T: AtLeast32Bit + Copy + Default + + HasCompact + MaxEncodedLen + MaybeSerializeDeserialize + Member diff --git a/primitives/src/traits/swaps.rs b/primitives/src/traits/swaps.rs index b193dcfb0..43cf3f9de 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. @@ -21,11 +21,11 @@ use crate::types::{ }; use alloc::vec::Vec; use frame_support::dispatch::{DispatchError, Weight}; -use parity_scale_codec::MaxEncodedLen; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; pub trait Swaps { type Balance: MaxEncodedLen; - type MarketId: MaxEncodedLen; + type MarketId: MaxEncodedLen + HasCompact; /// Creates an initial active pool. /// diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 13b74921f..ab69b5be2 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::*, + assets::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, pool::*, + pool_status::PoolStatus, proxy_type::*, }; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; @@ -82,14 +82,29 @@ impl<'a> Arbitrary<'a> for MultiHash { pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; /// ID type for any asset class -pub type CurrencyId = Asset; +pub type Assets = Asset; + +/// Asset type representing campaign assets +pub type CampaignAsset = CampaignAssetClass; + +// ID type for campaign asset class +pub type CampaignAssetId = u128; + +/// Asset type representing custom assets +pub type CustomAsset = CustomAssetClass; + +// ID type for campaign asset class +pub type CustomAssetId = u128; + +// Asset type representing currencies (excluding ZTG) +pub type Currencies = CurrencyClass; + +/// Asset type representing assets used by prediction markets +pub type MarketAsset = MarketAssetClass; /// ID type for asset classes that anyone can create pub type AssetId = u128; -/// ID type for the asset classes are used within the prediction market protocol -pub type MarketAsset = PredictionMarketAsset; - /// 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/runtime/battery-station/src/integration_tests/xcm/setup.rs b/runtime/battery-station/src/integration_tests/xcm/setup.rs index 6086541ee..06481e4b0 100644 --- a/runtime/battery-station/src/integration_tests/xcm/setup.rs +++ b/runtime/battery-station/src/integration_tests/xcm/setup.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. @@ -18,8 +18,7 @@ use crate::{ xcm_config::config::{battery_station, general_key}, - AccountId, AssetRegistry, Balance, CurrencyId, ExistentialDeposit, Runtime, RuntimeOrigin, - System, + AccountId, AssetRegistry, Assets, Balance, ExistentialDeposit, Runtime, RuntimeOrigin, System, }; use frame_support::{assert_ok, traits::GenesisBuild}; use orml_traits::asset_registry::AssetMetadata; @@ -31,7 +30,7 @@ use xcm::{ use zeitgeist_primitives::types::{Asset, CustomMetadata}; pub(super) struct ExtBuilder { - balances: Vec<(AccountId, CurrencyId, Balance)>, + balances: Vec<(AccountId, Assets, Balance)>, parachain_id: u32, } @@ -42,7 +41,7 @@ impl Default for ExtBuilder { } impl ExtBuilder { - pub fn set_balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + pub fn set_balances(mut self, balances: Vec<(AccountId, Assets, Balance)>) -> Self { self.balances = balances; self } @@ -54,7 +53,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let native_currency_id = CurrencyId::Ztg; + let native_currency_id = Assets::Ztg; pallet_balances::GenesisConfig:: { balances: self .balances @@ -104,10 +103,10 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Asset = CurrencyId::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Asset = CurrencyId::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Asset = CurrencyId::ForeignAsset(2); -pub const BTC_ID: Asset = CurrencyId::ForeignAsset(3); +pub const FOREIGN_ZTG_ID: Asset = Assets::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Asset = Assets::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Asset = Assets::ForeignAsset(2); +pub const BTC_ID: Asset = Assets::ForeignAsset(3); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/battery-station/src/integration_tests/xcm/test_net.rs b/runtime/battery-station/src/integration_tests/xcm/test_net.rs index 7e1becfd9..c6ce4d520 100644 --- a/runtime/battery-station/src/integration_tests/xcm/test_net.rs +++ b/runtime/battery-station/src/integration_tests/xcm/test_net.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Centrifuge GmbH (centrifuge.io). // // This file is part of Zeitgeist. @@ -17,8 +17,8 @@ // along with Zeitgeist. If not, see . use crate::{ - parameters::ZeitgeistTreasuryAccount, xcm_config::config::battery_station, CurrencyId, - DmpQueue, Runtime, RuntimeOrigin, XcmpQueue, + parameters::ZeitgeistTreasuryAccount, xcm_config::config::battery_station, Assets, DmpQueue, + Runtime, RuntimeOrigin, XcmpQueue, }; use frame_support::{traits::GenesisBuild, weights::Weight}; use polkadot_primitives::{ @@ -102,7 +102,7 @@ pub(super) fn relay_ext() -> sp_io::TestExternalities { pub(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { ExtBuilder::default() .set_balances(vec![ - (ALICE, CurrencyId::Ztg, ztg(10)), + (ALICE, Assets::Ztg, ztg(10)), (ALICE, FOREIGN_PARENT_ID, roc(10)), (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, roc(1)), ]) diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs index 539ee4afa..ac8b380ca 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.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. @@ -26,7 +26,7 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{battery_station, general_key, AssetConvert}, - CurrencyId, + Assets, }; use frame_support::assert_err; @@ -43,12 +43,12 @@ fn convert_native() { let ztg_location_inner: MultiLocation = MultiLocation::new(0, X1(general_key(battery_station::KEY))); - assert_eq!(>::convert(ztg_location_inner), Ok(CurrencyId::Ztg)); + assert_eq!(>::convert(ztg_location_inner), Ok(Assets::Ztg)); // The canonical way Ztg is represented out in the wild Zeitgeist::execute_with(|| { assert_eq!( - >::convert(CurrencyId::Ztg), + >::convert(Assets::Ztg), Some(foreign_ztg_multilocation()) ) }); @@ -117,6 +117,6 @@ fn convert_unkown_multilocation() { #[test] fn convert_unsupported_currency() { Zeitgeist::execute_with(|| { - assert_eq!(>::convert(CurrencyId::CombinatorialOutcome), None) + assert_eq!(>::convert(Assets::PoolShare(42)), None) }); } 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..95ec4c5f2 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. @@ -26,7 +26,7 @@ use crate::{ test_net::{RococoNet, Sibling, TestNet, Zeitgeist}, }, xcm_config::{config::battery_station, fees::default_per_second}, - AssetRegistry, Balance, Balances, CurrencyId, RuntimeOrigin, Tokens, XTokens, + AssetRegistry, Assets, Balance, Balances, RuntimeOrigin, Tokens, XTokens, ZeitgeistTreasuryAccount, }; @@ -59,7 +59,7 @@ fn transfer_ztg_to_sibling() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - CurrencyId::Ztg, + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -195,8 +195,8 @@ fn transfer_btc_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret CurrencyId::Ztg as BTC in this context. - CurrencyId::Ztg, + // Target chain will interpret Assets::Ztg as BTC in this context. + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -384,7 +384,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - CurrencyId::Ztg, + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index f2dfd713d..6b9b8d160 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. @@ -227,7 +227,7 @@ parameter_types! { pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; // ORML - pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; + pub const GetNativeCurrencyId: Assets = Asset::Ztg; // Prediction Market parameters /// (Slashable) Bond that is provided for creating an advised market that needs approval. @@ -497,10 +497,9 @@ parameter_type_with_key! { // are cleaned up automatically. In case of scalar outcomes, the market account can have dust. // Unless LPs use `pool_exit_with_exact_asset_amount`, there can be some dust pool shares remaining. // Explicit match arms are used to ensure new asset types are respected. - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Assets| -> Balance { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] @@ -519,6 +518,14 @@ parameter_type_with_key! { Asset::ForeignAsset(_) => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), Asset::ParimutuelShare(_,_) => ExistentialDeposit::get(), + + // The following assets are irrelevant, as they are managed by pallet-assets + Asset::NewParimutuelShare(_,_) + | Asset::NewCategoricalOutcome(_, _) + | Asset::NewScalarOutcome(_,_) + | Asset::NewPoolShare(_) + | Asset::CustomAssetClass(_) + | Asset::CampaignAssetClass(_) => ExistentialDeposit::get(), } }; } diff --git a/runtime/battery-station/src/xcm_config/asset_registry.rs b/runtime/battery-station/src/xcm_config/asset_registry.rs index 1b2f16f11..bdf5435af 100644 --- a/runtime/battery-station/src/xcm_config/asset_registry.rs +++ b/runtime/battery-station/src/xcm_config/asset_registry.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, CurrencyId}; +use crate::{Assets, Balance}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(CurrencyId, AssetMetadata), DispatchError> { + ) -> Result<(Assets, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for Cust } fn post_register( - _id: CurrencyId, + _id: Assets, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs index adc09a7de..09c649ebd 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. // @@ -17,9 +17,9 @@ use super::fees::{native_per_second, FixedConversionRateProvider}; use crate::{ - AccountId, AssetManager, AssetRegistry, Balance, CurrencyId, MaxAssetsIntoHolding, - MaxInstructions, ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, - RuntimeCall, RuntimeOrigin, UnitWeightCost, UniversalLocation, UnknownTokens, XcmpQueue, + AccountId, AssetManager, AssetRegistry, Assets, Balance, MaxAssetsIntoHolding, MaxInstructions, + ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, RuntimeCall, + RuntimeOrigin, UnitWeightCost, UniversalLocation, UnknownTokens, XcmpQueue, ZeitgeistTreasuryAccount, }; use alloc::vec::Vec; @@ -50,7 +50,7 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, }; -use xcm_executor::{traits::TransactAsset, Assets, Config}; +use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; pub mod battery_station { @@ -154,7 +154,7 @@ impl TakeRevenue for ToTreasury { if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { if let Ok(asset_id) = - >::convert(location) + >::convert(location) { let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); } @@ -204,9 +204,9 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, - CurrencyIdConvert: Convert>, + CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, > AlignedFractionalTransactAsset< @@ -247,8 +247,8 @@ impl< } impl< - AssetRegistry: Inspect, - CurrencyIdConvert: Convert>, + AssetRegistry: Inspect, + CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, > TransactAsset @@ -272,7 +272,7 @@ impl< asset: &MultiAsset, location: &MultiLocation, maybe_context: Option<&XcmContext>, - ) -> Result { + ) -> Result { let asset_adjusted = Self::adjust_fractional_places(asset); TransactAssetDelegate::withdraw_asset(&asset_adjusted, location, maybe_context) } @@ -282,7 +282,7 @@ impl< from: &MultiLocation, to: &MultiLocation, context: &XcmContext, - ) -> Result { + ) -> Result { let asset_adjusted = Self::adjust_fractional_places(asset); TransactAssetDelegate::transfer_asset(&asset_adjusted, from, to, context) } @@ -303,17 +303,17 @@ pub type MultiAssetTransactor = MultiCurrencyAdapter< UnknownTokens, // This means that this adapter should handle any token that `AssetConvert` can convert // using AssetManager and UnknownTokens in all other cases. - IsNativeConcrete, + IsNativeConcrete, // Our chain's account ID type (we can't get away without mentioning it explicitly). AccountId, // Convert an XCM `MultiLocation` into a local account id. LocationToAccountId, // The AssetId that corresponds to the native currency. - CurrencyId, + Assets, // Struct that provides functions to convert `Asset` <=> `MultiLocation`. AssetConvert, // In case of deposit failure, known assets will be placed in treasury. - DepositToAlternative, + DepositToAlternative, >; /// AssetConvert @@ -325,8 +325,8 @@ pub struct AssetConvert; /// Convert our `Asset` type into its `MultiLocation` representation. /// Other chains need to know how this conversion takes place in order to /// handle it on their side. -impl Convert> for AssetConvert { - fn convert(id: CurrencyId) -> Option { +impl Convert> for AssetConvert { + fn convert(id: Assets) -> Option { match id { Asset::Ztg => Some(MultiLocation::new( 1, @@ -344,14 +344,14 @@ impl Convert> for AssetConvert { /// Convert an incoming `MultiLocation` into a `Asset` if possible. /// Here we need to know the canonical representation of all the tokens we handle in order to /// correctly convert their `MultiLocation` representation into our internal `Asset` type. -impl xcm_executor::traits::Convert for AssetConvert { - fn convert(location: MultiLocation) -> Result { +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { match location { MultiLocation { parents: 0, interior: X1(GeneralKey { data, length }) } => { let key = &data[..data.len().min(length as usize)]; if key == battery_station::KEY { - return Ok(CurrencyId::Ztg); + return Ok(Assets::Ztg); } Err(location) @@ -364,7 +364,7 @@ impl xcm_executor::traits::Convert for AssetConvert { if para_id == u32::from(ParachainInfo::parachain_id()) { if key == battery_station::KEY { - return Ok(CurrencyId::Ztg); + return Ok(Assets::Ztg); } return Err(location); @@ -377,8 +377,8 @@ impl xcm_executor::traits::Convert for AssetConvert { } } -impl Convert> for AssetConvert { - fn convert(asset: MultiAsset) -> Option { +impl Convert> for AssetConvert { + fn convert(asset: MultiAsset) -> Option { if let MultiAsset { id: Concrete(location), .. } = asset { >::convert(location).ok() } else { @@ -387,8 +387,8 @@ impl Convert> for AssetConvert { } } -impl Convert> for AssetConvert { - fn convert(location: MultiLocation) -> Option { +impl Convert> for AssetConvert { + fn convert(location: MultiLocation) -> Option { >::convert(location).ok() } } diff --git a/runtime/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs index 38562ca0f..12e25935a 100644 --- a/runtime/battery-station/src/xcm_config/fees.rs +++ b/runtime/battery-station/src/xcm_config/fees.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. @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, CurrencyId}; +use crate::{Assets, Balance}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -55,7 +55,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = CurrencyId, + AssetId = Assets, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index 85ddc7543..d16e357e3 100644 --- a/runtime/common/src/fees.rs +++ b/runtime/common/src/fees.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. // @@ -119,7 +119,7 @@ macro_rules! impl_foreign_fees { #[cfg(feature = "parachain")] pub(crate) fn get_fee_factor( - currency_id: CurrencyId, + currency_id: Assets, ) -> Result { let metadata = ::metadata(¤cy_id).ok_or( TransactionValidityError::Invalid(InvalidTransaction::Custom( diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 4298d2517..c588b328a 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. // @@ -289,7 +289,7 @@ macro_rules! create_runtime { Multisig: pallet_multisig::{Call, Event, Pallet, Storage} = 14, Bounties: pallet_bounties::{Call, Event, Pallet, Storage} = 15, AssetTxPayment: pallet_asset_tx_payment::{Event, Pallet} = 16, - Assets: pallet_assets::::{Call, Pallet, Storage, Event} = 17, + CustomAssets: pallet_assets::::{Call, Pallet, Storage, Event} = 17, CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event} = 18, MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event} = 19, @@ -568,7 +568,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl orml_asset_registry::Config for Runtime { - type AssetId = CurrencyId; + type AssetId = Assets; type AssetProcessor = CustomAssetProcessor; type AuthorityOrigin = AsEnsureOriginWithArg; type Balance = Balance; @@ -585,7 +585,7 @@ 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 = (); @@ -600,7 +600,7 @@ macro_rules! impl_config_traits { type Amount = Amount; type Balance = Balance; type CurrencyHooks = CurrencyHooks; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -620,7 +620,7 @@ macro_rules! impl_config_traits { type AccountIdToMultiLocation = AccountIdToMultiLocation; type Balance = Balance; type BaseXcmWeight = BaseXcmWeight; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type CurrencyIdConvert = AssetConvert; type RuntimeEvent = RuntimeEvent; type MaxAssetsForTransfer = MaxAssetsForTransfer; @@ -635,10 +635,10 @@ macro_rules! impl_config_traits { // Required for runtime benchmarks pallet_assets::runtime_benchmarks_enabled! { - pub struct CustomAssetsBenchmarkHelper; + pub struct AssetsBenchmarkHelper; impl pallet_assets::BenchmarkHelper - for CustomAssetsBenchmarkHelper + for AssetsBenchmarkHelper where AssetIdParameter: From, { @@ -652,11 +652,11 @@ macro_rules! impl_config_traits { type ApprovalDeposit = CustomAssetsApprovalDeposit; type AssetAccountDeposit = CustomAssetsAccountDeposit; type AssetDeposit = CustomAssetsDeposit; - type AssetId = AssetId; - type AssetIdParameter = Compact; + type AssetId = CustomAsset; + type AssetIdParameter = Compact; type Balance = Balance; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = CustomAssetsBenchmarkHelper; + type BenchmarkHelper = AssetsBenchmarkHelper; type CallbackHandle = (); type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; @@ -676,11 +676,11 @@ macro_rules! impl_config_traits { type ApprovalDeposit = CampaignAssetsApprovalDeposit; type AssetAccountDeposit = CampaignAssetsAccountDeposit; type AssetDeposit = CampaignAssetsDeposit; - type AssetId = AssetId; - type AssetIdParameter = Compact; + type AssetId = CampaignAsset; + type AssetIdParameter = Compact; type Balance = Balance; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = CustomAssetsBenchmarkHelper; + type BenchmarkHelper = AssetsBenchmarkHelper; type CallbackHandle = (); type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; @@ -1481,7 +1481,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, frame_system, SystemBench::); orml_list_benchmark!(list, extra, orml_currencies, crate::benchmarks::currencies); orml_list_benchmark!(list, extra, orml_tokens, crate::benchmarks::tokens); - list_benchmark!(list, extra, pallet_assets, Assets); + list_benchmark!(list, extra, pallet_assets, CustomAssets); list_benchmark!(list, extra, pallet_balances, Balances); list_benchmark!(list, extra, pallet_bounties, Bounties); list_benchmark!(list, extra, pallet_collective, AdvisoryCommittee); @@ -1586,7 +1586,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, frame_system, SystemBench::); orml_add_benchmark!(params, batches, orml_currencies, crate::benchmarks::currencies); orml_add_benchmark!(params, batches, orml_tokens, crate::benchmarks::tokens); - add_benchmark!(params, batches, pallet_assets, Assets); + add_benchmark!(params, batches, pallet_assets, CustomAssets); add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_bounties, Bounties); add_benchmark!(params, batches, pallet_collective, AdvisoryCommittee); @@ -1895,16 +1895,16 @@ macro_rules! create_runtime_api { asset_in: &Asset, asset_out: &Asset, with_fees: bool, - ) -> SerdeWrapper { - SerdeWrapper(Swaps::get_spot_price(pool_id, asset_in, asset_out, with_fees).ok().unwrap_or(0)) + ) -> Balance { + Swaps::get_spot_price(pool_id, asset_in, asset_out, with_fees).ok().unwrap_or(0) } fn pool_account_id(pool_id: &PoolId) -> AccountId { Swaps::pool_account_id(pool_id) } - fn pool_shares_id(pool_id: PoolId) -> Asset> { - Asset::PoolShare(SerdeWrapper(pool_id)) + fn pool_shares_id(pool_id: PoolId) -> Asset { + Asset::PoolShare(pool_id) } fn get_all_spot_prices( @@ -1981,7 +1981,7 @@ macro_rules! create_common_benchmark_logic { pub(crate) mod currencies { use super::utils::{lookup_of_account, set_balance}; use crate::{ - AccountId, Amount, AssetManager, Balance, CurrencyId, ExistentialDeposit, + AccountId, Amount, AssetManager, Balance, Assets, ExistentialDeposit, GetNativeCurrencyId, Runtime }; use frame_benchmarking::{account, vec, whitelisted_caller}; @@ -1995,8 +1995,8 @@ macro_rules! create_common_benchmark_logic { }; const SEED: u32 = 0; - const NATIVE: CurrencyId = GetNativeCurrencyId::get(); - const ASSET: CurrencyId = Asset::CategoricalOutcome(0, 0); + const NATIVE: Assets = GetNativeCurrencyId::get(); + const ASSET: Assets = Asset::CategoricalOutcome(0, 0); runtime_benchmarks! { { Runtime, orml_currencies } @@ -2096,7 +2096,7 @@ macro_rules! create_common_benchmark_logic { pub(crate) mod tokens { use super::utils::{lookup_of_account, set_balance as update_balance}; - use crate::{AccountId, Balance, CurrencyId, Tokens, Runtime}; + use crate::{AccountId, Balance, Assets, Tokens, Runtime}; use frame_benchmarking::{account, vec, whitelisted_caller}; use frame_system::RawOrigin; use orml_benchmarking::runtime_benchmarks; @@ -2104,7 +2104,7 @@ macro_rules! create_common_benchmark_logic { use zeitgeist_primitives::{constants::BASE, types::Asset}; const SEED: u32 = 0; - const ASSET: CurrencyId = Asset::CategoricalOutcome(0, 0); + const ASSET: Assets = Asset::CategoricalOutcome(0, 0); runtime_benchmarks! { { Runtime, orml_tokens } @@ -2179,7 +2179,7 @@ macro_rules! create_common_benchmark_logic { } pub(crate) mod utils { - use crate::{AccountId, AssetManager, Balance, CurrencyId, Runtime, + use crate::{AccountId, AssetManager, Balance, Assets, Runtime, }; use frame_support::assert_ok; use orml_traits::MultiCurrencyExtended; @@ -2191,7 +2191,7 @@ macro_rules! create_common_benchmark_logic { ::Lookup::unlookup(who) } - pub fn set_balance(currency_id: CurrencyId, who: &AccountId, balance: Balance) { + pub fn set_balance(currency_id: Assets, who: &AccountId, balance: Balance) { assert_ok!(>::update_balance( currency_id, who, diff --git a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs index a633132a6..55f0f4c04 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/setup.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. @@ -18,8 +18,7 @@ use crate::{ xcm_config::config::{general_key, zeitgeist}, - AccountId, AssetRegistry, Balance, CurrencyId, ExistentialDeposit, Runtime, RuntimeOrigin, - System, + AccountId, AssetRegistry, Assets, Balance, ExistentialDeposit, Runtime, RuntimeOrigin, System, }; use frame_support::{assert_ok, traits::GenesisBuild}; use orml_traits::asset_registry::AssetMetadata; @@ -31,7 +30,7 @@ use xcm::{ use zeitgeist_primitives::types::{Asset, CustomMetadata}; pub(super) struct ExtBuilder { - balances: Vec<(AccountId, CurrencyId, Balance)>, + balances: Vec<(AccountId, Assets, Balance)>, parachain_id: u32, } @@ -42,7 +41,7 @@ impl Default for ExtBuilder { } impl ExtBuilder { - pub fn set_balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + pub fn set_balances(mut self, balances: Vec<(AccountId, Assets, Balance)>) -> Self { self.balances = balances; self } @@ -54,7 +53,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let native_currency_id = CurrencyId::Ztg; + let native_currency_id = Assets::Ztg; pallet_balances::GenesisConfig:: { balances: self .balances @@ -104,11 +103,11 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Asset = CurrencyId::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Asset = CurrencyId::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Asset = CurrencyId::ForeignAsset(2); -pub const BTC_ID: Asset = CurrencyId::ForeignAsset(3); -pub const ETH_ID: Asset = CurrencyId::ForeignAsset(4); +pub const FOREIGN_ZTG_ID: Asset = Assets::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Asset = Assets::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Asset = Assets::ForeignAsset(2); +pub const BTC_ID: Asset = Assets::ForeignAsset(3); +pub const ETH_ID: Asset = Assets::ForeignAsset(4); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs index 050527635..3c7518fa1 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Centrifuge GmbH (centrifuge.io). // // This file is part of Zeitgeist. @@ -17,8 +17,8 @@ // along with Zeitgeist. If not, see . use crate::{ - parameters::ZeitgeistTreasuryAccount, xcm_config::config::zeitgeist, CurrencyId, DmpQueue, - Runtime, RuntimeOrigin, XcmpQueue, + parameters::ZeitgeistTreasuryAccount, xcm_config::config::zeitgeist, Assets, DmpQueue, Runtime, + RuntimeOrigin, XcmpQueue, }; use frame_support::{traits::GenesisBuild, weights::Weight}; use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; @@ -99,7 +99,7 @@ pub(super) fn relay_ext() -> sp_io::TestExternalities { pub(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { ExtBuilder::default() .set_balances(vec![ - (ALICE, CurrencyId::Ztg, ztg(10)), + (ALICE, Assets::Ztg, ztg(10)), (ALICE, FOREIGN_PARENT_ID, dot(10)), (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, dot(10)), ]) diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs index 7fbcfa385..23b98e5c2 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.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. @@ -26,7 +26,7 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{general_key, zeitgeist, AssetConvert}, - CurrencyId, + Assets, }; use frame_support::assert_err; @@ -42,12 +42,12 @@ fn convert_native() { // The way Ztg is represented relative within the Zeitgeist runtime let ztg_location_inner: MultiLocation = MultiLocation::new(0, X1(general_key(zeitgeist::KEY))); - assert_eq!(>::convert(ztg_location_inner), Ok(CurrencyId::Ztg)); + assert_eq!(>::convert(ztg_location_inner), Ok(Assets::Ztg)); // The canonical way Ztg is represented out in the wild Zeitgeist::execute_with(|| { assert_eq!( - >::convert(CurrencyId::Ztg), + >::convert(Assets::Ztg), Some(foreign_ztg_multilocation()) ) }); @@ -116,6 +116,6 @@ fn convert_unkown_multilocation() { #[test] fn convert_unsupported_currency() { Zeitgeist::execute_with(|| { - assert_eq!(>::convert(CurrencyId::CombinatorialOutcome), None) + assert_eq!(>::convert(Assets::PoolShare(42)), None) }); } diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs index 5441f6517..528740841 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. @@ -26,7 +26,7 @@ use crate::{ test_net::{PolkadotNet, Sibling, TestNet, Zeitgeist}, }, xcm_config::{config::zeitgeist, fees::default_per_second}, - AssetRegistry, Balance, Balances, CurrencyId, RuntimeOrigin, Tokens, XTokens, + AssetRegistry, Assets, Balance, Balances, RuntimeOrigin, Tokens, XTokens, ZeitgeistTreasuryAccount, }; @@ -59,7 +59,7 @@ fn transfer_ztg_to_sibling() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - CurrencyId::Ztg, + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -195,8 +195,8 @@ fn transfer_btc_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret CurrencyId::Ztg as BTC in this context. - CurrencyId::Ztg, + // Target chain will interpret Assets::Ztg as BTC in this context. + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -316,8 +316,8 @@ fn transfer_eth_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret CurrencyId::Ztg as ETH in this context. - CurrencyId::Ztg, + // Target chain will interpret Assets::Ztg as ETH in this context. + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -502,7 +502,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - CurrencyId::Ztg, + Assets::Ztg, transfer_amount, Box::new( MultiLocation::new( diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 5172970ab..86237f7bb 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. @@ -225,7 +225,7 @@ parameter_types! { pub const NeoSwapsPalletId: PalletId = NS_PALLET_ID; // ORML - pub const GetNativeCurrencyId: CurrencyId = Asset::Ztg; + pub const GetNativeCurrencyId: Assets = Asset::Ztg; // Prediction Market parameters /// (Slashable) Bond that is provided for creating an advised market that needs approval. @@ -495,10 +495,9 @@ parameter_type_with_key! { // are cleaned up automatically. In case of scalar outcomes, the market account can have dust. // Unless LPs use `pool_exit_with_exact_asset_amount`, there can be some dust pool shares remaining. // Explicit match arms are used to ensure new asset types are respected. - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Assets| -> Balance { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] @@ -516,7 +515,15 @@ parameter_type_with_key! { #[cfg(not(feature = "parachain"))] Asset::ForeignAsset(_) => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), - Asset::ParimutuelShare(_, _) => ExistentialDeposit::get(), + Asset::ParimutuelShare(_,_) => ExistentialDeposit::get(), + + // The following assets are irrelevant, as they are managed by pallet-assets + Asset::NewParimutuelShare(_,_) + | Asset::NewCategoricalOutcome(_, _) + | Asset::NewScalarOutcome(_,_) + | Asset::NewPoolShare(_) + | Asset::CustomAssetClass(_) + | Asset::CampaignAssetClass(_) => ExistentialDeposit::get(), } }; } diff --git a/runtime/zeitgeist/src/xcm_config/asset_registry.rs b/runtime/zeitgeist/src/xcm_config/asset_registry.rs index 1b2f16f11..bdf5435af 100644 --- a/runtime/zeitgeist/src/xcm_config/asset_registry.rs +++ b/runtime/zeitgeist/src/xcm_config/asset_registry.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, CurrencyId}; +use crate::{Assets, Balance}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(CurrencyId, AssetMetadata), DispatchError> { + ) -> Result<(Assets, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for Cust } fn post_register( - _id: CurrencyId, + _id: Assets, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs index 39ee8bbd7..6eb5389a8 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. @@ -18,9 +18,9 @@ use super::fees::{native_per_second, FixedConversionRateProvider}; use crate::{ - AccountId, AssetManager, AssetRegistry, Balance, CurrencyId, MaxAssetsIntoHolding, - MaxInstructions, ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, - RuntimeCall, RuntimeOrigin, UnitWeightCost, UniversalLocation, UnknownTokens, XcmpQueue, + AccountId, AssetManager, AssetRegistry, Assets, Balance, MaxAssetsIntoHolding, MaxInstructions, + ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, RuntimeCall, + RuntimeOrigin, UnitWeightCost, UniversalLocation, UnknownTokens, XcmpQueue, ZeitgeistTreasuryAccount, }; @@ -52,7 +52,7 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, }; -use xcm_executor::{traits::TransactAsset, Assets, Config}; +use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; pub mod zeitgeist { @@ -156,7 +156,7 @@ impl TakeRevenue for ToTreasury { if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { if let Ok(asset_id) = - >::convert(location) + >::convert(location) { let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); } @@ -206,9 +206,9 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, - CurrencyIdConvert: Convert>, + CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, > AlignedFractionalTransactAsset< @@ -249,8 +249,8 @@ impl< } impl< - AssetRegistry: Inspect, - CurrencyIdConvert: Convert>, + AssetRegistry: Inspect, + CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, > TransactAsset @@ -274,7 +274,7 @@ impl< asset: &MultiAsset, location: &MultiLocation, maybe_context: Option<&XcmContext>, - ) -> Result { + ) -> Result { let asset_adjusted = Self::adjust_fractional_places(asset); TransactAssetDelegate::withdraw_asset(&asset_adjusted, location, maybe_context) } @@ -284,7 +284,7 @@ impl< from: &MultiLocation, to: &MultiLocation, context: &XcmContext, - ) -> Result { + ) -> Result { let asset_adjusted = Self::adjust_fractional_places(asset); TransactAssetDelegate::transfer_asset(&asset_adjusted, from, to, context) } @@ -305,17 +305,17 @@ pub type MultiAssetTransactor = MultiCurrencyAdapter< UnknownTokens, // This means that this adapter should handle any token that `AssetConvert` can convert // using AssetManager and UnknownTokens in all other cases. - IsNativeConcrete, + IsNativeConcrete, // Our chain's account ID type (we can't get away without mentioning it explicitly). AccountId, // Convert an XCM `MultiLocation` into a local account id. LocationToAccountId, // The AssetId that corresponds to the native currency. - CurrencyId, + Assets, // Struct that provides functions to convert `Asset` <=> `MultiLocation`. AssetConvert, // In case of deposit failure, known assets will be placed in treasury. - DepositToAlternative, + DepositToAlternative, >; /// AssetConvert @@ -327,8 +327,8 @@ pub struct AssetConvert; /// Convert our `Asset` type into its `MultiLocation` representation. /// Other chains need to know how this conversion takes place in order to /// handle it on their side. -impl Convert> for AssetConvert { - fn convert(id: CurrencyId) -> Option { +impl Convert> for AssetConvert { + fn convert(id: Assets) -> Option { match id { Asset::Ztg => Some(MultiLocation::new( 1, @@ -346,14 +346,14 @@ impl Convert> for AssetConvert { /// Convert an incoming `MultiLocation` into a `Asset` if possible. /// Here we need to know the canonical representation of all the tokens we handle in order to /// correctly convert their `MultiLocation` representation into our internal `Asset` type. -impl xcm_executor::traits::Convert for AssetConvert { - fn convert(location: MultiLocation) -> Result { +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { match location { MultiLocation { parents: 0, interior: X1(GeneralKey { data, length }) } => { let key = &data[..data.len().min(length as usize)]; if key == zeitgeist::KEY { - return Ok(CurrencyId::Ztg); + return Ok(Assets::Ztg); } Err(location) @@ -366,7 +366,7 @@ impl xcm_executor::traits::Convert for AssetConvert { if para_id == u32::from(ParachainInfo::parachain_id()) { if key == zeitgeist::KEY { - return Ok(CurrencyId::Ztg); + return Ok(Assets::Ztg); } return Err(location); @@ -379,8 +379,8 @@ impl xcm_executor::traits::Convert for AssetConvert { } } -impl Convert> for AssetConvert { - fn convert(asset: MultiAsset) -> Option { +impl Convert> for AssetConvert { + fn convert(asset: MultiAsset) -> Option { if let MultiAsset { id: Concrete(location), .. } = asset { >::convert(location).ok() } else { @@ -389,8 +389,8 @@ impl Convert> for AssetConvert { } } -impl Convert> for AssetConvert { - fn convert(location: MultiLocation) -> Option { +impl Convert> for AssetConvert { + fn convert(location: MultiLocation) -> Option { >::convert(location).ok() } } diff --git a/runtime/zeitgeist/src/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs index 5b186610b..dc482c336 100644 --- a/runtime/zeitgeist/src/xcm_config/fees.rs +++ b/runtime/zeitgeist/src/xcm_config/fees.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. @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, CurrencyId}; +use crate::{Assets, Balance}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -55,7 +55,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = CurrencyId, + AssetId = Assets, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index af6d59b77..aaef33b1d 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. @@ -41,7 +41,7 @@ mod pallet { traits::{Get, Hooks, StorageVersion, Time}, Blake2_128Concat, PalletId, Parameter, }; - use parity_scale_codec::{FullCodec, MaxEncodedLen}; + use parity_scale_codec::{FullCodec, HasCompact, MaxEncodedLen}; use sp_runtime::{ traits::{ AccountIdConversion, AtLeast32Bit, AtLeast32BitUnsigned, CheckedAdd, @@ -83,6 +83,7 @@ mod pallet { type MarketId: AtLeast32Bit + Copy + Default + + HasCompact + MaxEncodedLen + MaybeSerializeDeserialize + Member diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 1a2e876c9..64a9ab6a8 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. @@ -63,8 +63,8 @@ use zeitgeist_primitives::{ }, traits::{DeployPoolApi, DistributeFees}, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, + Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, }, }; use zrml_neo_swaps::BalanceOf; @@ -351,7 +351,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -462,7 +462,7 @@ impl pallet_treasury::Config for Runtime { #[cfg(feature = "parachain")] zrml_prediction_markets::impl_mock_registry! { MockRegistry, - CurrencyId, + Assets, Balance, zeitgeist_primitives::types::CustomMetadata } diff --git a/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs b/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs index 4212d80bf..d93ef6360 100644 --- a/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs +++ b/zrml/orderbook/fuzz/orderbook_v1_full_workflow.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,7 +19,7 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use zeitgeist_primitives::types::{Asset, ScalarPosition, SerdeWrapper}; +use zeitgeist_primitives::types::{Asset, ScalarPosition}; use zrml_orderbook::{ mock::{ExtBuilder, Orderbook, RuntimeOrigin}, types::OrderSide, @@ -94,7 +94,7 @@ fn asset(seed: (u128, u16)) -> Asset { if seed1 % 2 == 0 { ScalarPosition::Long } else { ScalarPosition::Short }; Asset::ScalarOutcome(seed0, scalar_position) } - 2 => Asset::PoolShare(SerdeWrapper(seed0)), + 2 => Asset::PoolShare(seed0), _ => Asset::Ztg, } } diff --git a/zrml/orderbook/src/mock.rs b/zrml/orderbook/src/mock.rs index 2384c3ab9..b1f6b62d4 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. @@ -30,8 +30,8 @@ use zeitgeist_primitives::{ MaxReserves, MinimumPeriod, OrderbookPalletId, PmPalletId, BASE, }, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, + Index, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -100,7 +100,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; diff --git a/zrml/orderbook/src/types.rs b/zrml/orderbook/src/types.rs index b0e1d2b73..182ce6920 100644 --- a/zrml/orderbook/src/types.rs +++ b/zrml/orderbook/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use zeitgeist_primitives::types::Asset; @@ -29,7 +29,7 @@ pub enum OrderSide { } #[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Order { +pub struct Order { pub market_id: MarketId, pub side: OrderSide, pub maker: AccountId, diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index c04387509..bd55125c1 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. // @@ -37,8 +37,8 @@ use zeitgeist_primitives::{ }, traits::DistributeFees, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - Hash, Index, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, + Index, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -168,7 +168,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; diff --git a/zrml/prediction-markets/runtime-api/src/lib.rs b/zrml/prediction-markets/runtime-api/src/lib.rs index a8b3f3ea5..4a488b52b 100644 --- a/zrml/prediction-markets/runtime-api/src/lib.rs +++ b/zrml/prediction-markets/runtime-api/src/lib.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -18,12 +19,12 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::{Codec, MaxEncodedLen}; +use parity_scale_codec::{Codec, HasCompact, MaxEncodedLen}; use zeitgeist_primitives::types::Asset; sp_api::decl_runtime_apis! { pub trait PredictionMarketsApi where - MarketId: Codec + MaxEncodedLen, + MarketId: Codec + HasCompact + MaxEncodedLen, Hash: Codec, { fn market_outcome_share_id(market_id: MarketId, outcome: u16) -> Asset; diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 8a448a1ec..fcb9f71fa 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. @@ -59,8 +59,8 @@ use zeitgeist_primitives::{ }, traits::DeployPoolApi, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, MarketId, Moment, PoolId, SerdeWrapper, UncheckedExtrinsicTest, + AccountIdTest, Amount, Asset, Assets, Balance, BasicCurrencyAdapter, BlockNumber, + BlockTest, Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, }, }; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; @@ -271,7 +271,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -285,7 +285,7 @@ impl orml_tokens::Config for Runtime { #[cfg(feature = "parachain")] crate::orml_asset_registry::impl_mock_registry! { MockRegistry, - CurrencyId, + Assets, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -488,7 +488,7 @@ impl ExtBuilder { #[cfg(feature = "parachain")] orml_tokens::GenesisConfig:: { balances: (0..69) - .map(|idx| (idx, CurrencyId::ForeignAsset(100), INITIAL_BALANCE)) + .map(|idx| (idx, Assets::ForeignAsset(100), INITIAL_BALANCE)) .collect(), } .assimilate_storage(&mut t) @@ -502,7 +502,7 @@ impl ExtBuilder { orml_asset_registry_mock::GenesisConfig { metadata: vec![ ( - CurrencyId::ForeignAsset(100), + Assets::ForeignAsset(100), AssetMetadata { decimals: 18, name: "ACALA USD".as_bytes().to_vec(), @@ -513,7 +513,7 @@ impl ExtBuilder { }, ), ( - CurrencyId::ForeignAsset(420), + Assets::ForeignAsset(420), AssetMetadata { decimals: 18, name: "FANCY_TOKEN".as_bytes().to_vec(), @@ -559,7 +559,7 @@ pub fn set_timestamp_for_on_initialize(time: Moment) { sp_api::mock_impl_runtime_apis! { impl zrml_prediction_markets_runtime_api::PredictionMarketsApi, MarketId, Hash> for Runtime { fn market_outcome_share_id(_: MarketId, _: u16) -> Asset { - Asset::PoolShare(SerdeWrapper(1)) + Asset::PoolShare(1) } } } diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index 3f3e3dee7..5c25f00db 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. @@ -35,8 +35,8 @@ use zeitgeist_primitives::{ }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Asset, Assets, Balance, BasicCurrencyAdapter, BlockNumber, + BlockTest, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -171,7 +171,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = Everything; type RuntimeEvent = (); type ExistentialDeposits = ExistentialDeposits; diff --git a/zrml/swaps/fuzz/pool_exit.rs b/zrml/swaps/fuzz/pool_exit.rs index 5b0c6e7be..7f595412b 100644 --- a/zrml/swaps/fuzz/pool_exit.rs +++ b/zrml/swaps/fuzz/pool_exit.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -23,7 +24,7 @@ use zrml_swaps::mock::{ExtBuilder, RuntimeOrigin, Swaps}; mod utils; use orml_traits::currency::MultiCurrency; use utils::{construct_asset, GeneralPoolData}; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; use zrml_swaps::mock::AssetManager; fuzz_target!(|data: GeneralPoolData| { @@ -41,11 +42,7 @@ fuzz_target!(|data: GeneralPoolData| { let pool_creator = data.pool_creation.origin; let pool_id = data.pool_creation.create_pool(); // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting - let _ = AssetManager::deposit( - Asset::PoolShare(SerdeWrapper(pool_id)), - &pool_creator, - data.pool_amount, - ); + let _ = AssetManager::deposit(Asset::PoolShare(pool_id), &pool_creator, data.pool_amount); let _ = Swaps::pool_exit( RuntimeOrigin::signed(data.origin), pool_id, diff --git a/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs b/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs index 9f983b131..11f3c60f9 100644 --- a/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs +++ b/zrml/swaps/fuzz/pool_exit_with_exact_asset_amount.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -25,7 +26,7 @@ use orml_traits::currency::MultiCurrency; use utils::{construct_asset, ExactAssetAmountData}; use zrml_swaps::mock::AssetManager; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; fuzz_target!(|data: ExactAssetAmountData| { let mut ext = ExtBuilder::default().build(); @@ -43,11 +44,7 @@ fuzz_target!(|data: ExactAssetAmountData| { let pool_creator = data.pool_creation.origin; let pool_id = data.pool_creation.create_pool(); // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting - let _ = AssetManager::deposit( - Asset::PoolShare(SerdeWrapper(pool_id)), - &pool_creator, - data.pool_amount, - ); + let _ = AssetManager::deposit(Asset::PoolShare(pool_id), &pool_creator, data.pool_amount); let _ = Swaps::pool_exit_with_exact_asset_amount( RuntimeOrigin::signed(data.origin), pool_id, diff --git a/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs b/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs index a583da30e..072fd3a42 100644 --- a/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs +++ b/zrml/swaps/fuzz/pool_exit_with_exact_pool_amount.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -23,7 +24,7 @@ use zrml_swaps::mock::{ExtBuilder, RuntimeOrigin, Swaps}; mod utils; use orml_traits::currency::MultiCurrency; use utils::{construct_asset, ExactAmountData}; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; use zrml_swaps::mock::AssetManager; fuzz_target!(|data: ExactAmountData| { @@ -42,11 +43,7 @@ fuzz_target!(|data: ExactAmountData| { let pool_creator = data.pool_creation.origin; let pool_id = data.pool_creation.create_pool(); // to exit a pool, origin also needs to have the pool tokens of the pool that they're exiting - let _ = AssetManager::deposit( - Asset::PoolShare(SerdeWrapper(pool_id)), - &pool_creator, - data.pool_amount, - ); + let _ = AssetManager::deposit(Asset::PoolShare(pool_id), &pool_creator, data.pool_amount); let _ = Swaps::pool_exit_with_exact_pool_amount( RuntimeOrigin::signed(data.origin), pool_id, diff --git a/zrml/swaps/fuzz/utils.rs b/zrml/swaps/fuzz/utils.rs index 6c4272ecf..a44619f83 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,7 +29,7 @@ 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, ScoringRule}, }; use zrml_swaps::mock::{Swaps, DEFAULT_MARKET_ID}; @@ -43,7 +43,7 @@ pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { if seed1 % 2 == 0 { ScalarPosition::Long } else { ScalarPosition::Short }; Asset::ScalarOutcome(seed0, scalar_position) } - 2 => Asset::PoolShare(SerdeWrapper(seed0)), + 2 => Asset::PoolShare(seed0), _ => Asset::Ztg, } } diff --git a/zrml/swaps/rpc/src/lib.rs b/zrml/swaps/rpc/src/lib.rs index 403d1da39..7548b0c2d 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. @@ -27,7 +27,7 @@ use jsonrpsee::{ proc_macros::rpc, types::error::{CallError, ErrorObject}, }; -use parity_scale_codec::{Codec, MaxEncodedLen}; +use parity_scale_codec::{Codec, HasCompact, MaxEncodedLen}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_runtime::{ @@ -35,7 +35,7 @@ use sp_runtime::{ traits::{Block as BlockT, MaybeDisplay, MaybeFromStr, NumberFor}, }; use std::collections::BTreeMap; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; pub use zrml_swaps_runtime_api::SwapsApi as SwapsRuntimeApi; @@ -43,7 +43,7 @@ pub use zrml_swaps_runtime_api::SwapsApi as SwapsRuntimeApi; pub trait SwapsApi where Balance: FromStr + Display + parity_scale_codec::MaxEncodedLen, - MarketId: FromStr + Display + parity_scale_codec::MaxEncodedLen + Ord, + MarketId: FromStr + Display + HasCompact + MaxEncodedLen + Ord, PoolId: FromStr + Display, BlockNumber: Ord + parity_scale_codec::MaxEncodedLen + Display + FromStr, { @@ -52,7 +52,7 @@ where &self, pool_id: PoolId, at: Option, - ) -> RpcResult>>; + ) -> RpcResult>; #[method(name = "swaps_poolAccountId", aliases = ["swaps_poolAccountIdAt"])] async fn pool_account_id(&self, pool_id: PoolId, at: Option) @@ -66,7 +66,7 @@ where asset_out: Asset, with_fees: bool, at: Option, - ) -> RpcResult>; + ) -> RpcResult; #[method(name = "swaps_getSpotPrices")] async fn get_spot_prices( @@ -76,7 +76,7 @@ where asset_out: Asset, with_fees: bool, blocks: Vec, - ) -> RpcResult>>; + ) -> RpcResult>; #[method(name = "swaps_getAllSpotPrices")] async fn get_all_spot_prices( @@ -125,14 +125,22 @@ where C::Api: SwapsRuntimeApi, PoolId: Clone + Codec + MaybeDisplay + MaybeFromStr + Send + 'static, AccountId: Clone + Display + Codec + Send + 'static, - Balance: Codec + MaybeDisplay + MaybeFromStr + MaxEncodedLen + Send + 'static, - MarketId: Clone + Codec + MaybeDisplay + MaybeFromStr + MaxEncodedLen + Ord + Send + 'static, + Balance: Codec + HasCompact + MaybeDisplay + MaybeFromStr + MaxEncodedLen + Send + 'static, + MarketId: Clone + + Codec + + HasCompact + + MaybeDisplay + + MaybeFromStr + + MaxEncodedLen + + Ord + + Send + + 'static, { async fn pool_shares_id( &self, pool_id: PoolId, at: Option<::Hash>, - ) -> RpcResult>> { + ) -> RpcResult> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| //if the block hash is not supplied assume the best block @@ -176,7 +184,7 @@ where asset_out: Asset, with_fees: bool, at: Option<::Hash>, - ) -> RpcResult> { + ) -> RpcResult { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let res = @@ -197,7 +205,7 @@ where asset_out: Asset, with_fees: bool, blocks: Vec>, - ) -> RpcResult>> { + ) -> RpcResult> { let api = self.client.runtime_api(); blocks .into_iter() diff --git a/zrml/swaps/runtime-api/src/lib.rs b/zrml/swaps/runtime-api/src/lib.rs index 437b0a53d..e531ff7f8 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,30 +19,30 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::{Codec, Decode, MaxEncodedLen}; +use parity_scale_codec::{Codec, Decode, HasCompact, MaxEncodedLen}; use sp_runtime::{ traits::{MaybeDisplay, MaybeFromStr}, DispatchError, }; use sp_std::vec::Vec; -use zeitgeist_primitives::types::{Asset, Pool, SerdeWrapper}; +use zeitgeist_primitives::types::{Asset, Pool}; sp_api::decl_runtime_apis! { pub trait SwapsApi where PoolId: Codec, AccountId: Codec, - Balance: Codec + MaybeDisplay + MaybeFromStr + MaxEncodedLen, - MarketId: Codec + MaxEncodedLen, + Balance: Codec + MaybeDisplay + MaybeFromStr + HasCompact + MaxEncodedLen, + MarketId: Codec + HasCompact + MaxEncodedLen, Pool: Decode, { - fn pool_shares_id(pool_id: PoolId) -> Asset>; + 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; + ) -> Balance; fn get_all_spot_prices( pool_id: &PoolId, with_fees: bool diff --git a/zrml/swaps/src/arbitrage.rs b/zrml/swaps/src/arbitrage.rs index 304d8ff0a..021890bcb 100644 --- a/zrml/swaps/src/arbitrage.rs +++ b/zrml/swaps/src/arbitrage.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -19,7 +19,7 @@ use crate::{math::calc_spot_price, root::calc_preimage}; use alloc::collections::btree_map::BTreeMap; -use parity_scale_codec::MaxEncodedLen; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; use sp_runtime::{ traits::{AtLeast32Bit, AtLeast32BitUnsigned}, SaturatedConversion, @@ -38,7 +38,7 @@ const TOLERANCE: Fixed = BASE / 1_000; // 0.001 /// All calculations depend on on-chain, which are passed using the `balances` parameter. pub(crate) trait ArbitrageForCpmm where - MarketId: MaxEncodedLen, + MarketId: HasCompact + MaxEncodedLen, { /// Calculate the total spot price (sum of all spot prices of outcome tokens). /// @@ -78,7 +78,7 @@ where impl ArbitrageForCpmm for Pool where Balance: AtLeast32BitUnsigned + Copy, - MarketId: MaxEncodedLen + AtLeast32Bit + Copy, + MarketId: HasCompact + MaxEncodedLen + AtLeast32Bit + Copy, Pool: ArbitrageForCpmmHelper, { fn calc_total_spot_price( @@ -137,7 +137,7 @@ where trait ArbitrageForCpmmHelper where - MarketId: MaxEncodedLen, + MarketId: HasCompact + MaxEncodedLen, { /// Common code of `Arbitrage::calc_arbitrage_amount_*`. /// @@ -163,7 +163,7 @@ where impl ArbitrageForCpmmHelper for Pool where Balance: AtLeast32BitUnsigned + Copy, - MarketId: MaxEncodedLen + AtLeast32Bit + Copy, + MarketId: HasCompact + MaxEncodedLen + AtLeast32Bit + Copy, { fn calc_arbitrage_amount_common( &self, diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index f63f281ee..3fa287d89 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. @@ -94,7 +94,7 @@ mod pallet { traits::{MarketCommonsPalletApi, Swaps, ZeitgeistAssetManager}, types::{ Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, - ScoringRule, SerdeWrapper, + ScoringRule, }, }; use zrml_liquidity_mining::LiquidityMiningPalletApi; @@ -1714,7 +1714,7 @@ mod pallet { } pub(crate) fn pool_shares_id(pool_id: PoolId) -> Asset> { - Asset::PoolShare(SerdeWrapper(pool_id)) + Asset::PoolShare(pool_id) } pub fn pool_by_id( diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index 948a8ce5f..f3ec41881 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. @@ -49,10 +49,10 @@ use zeitgeist_primitives::{ PmPalletId, SwapsPalletId, BASE, }, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Deadlines, Hash, Index, Market, MarketBonds, MarketCreation, + AccountIdTest, Amount, Asset, Assets, Balance, BasicCurrencyAdapter, BlockNumber, + BlockTest, Deadlines, Hash, Index, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, PoolId, - ScoringRule, SerdeWrapper, UncheckedExtrinsicTest, + ScoringRule, UncheckedExtrinsicTest, }, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -168,7 +168,7 @@ impl orml_currencies::Config for Runtime { } parameter_type_with_key! { - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Assets| -> Balance { match currency_id { &BASE_ASSET => ExistentialDeposit::get(), Asset::Ztg => ExistentialDeposit::get(), @@ -205,7 +205,7 @@ where impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Assets; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -306,16 +306,16 @@ sp_api::mock_impl_runtime_apis! { asset_in: &Asset, asset_out: &Asset, with_fees: bool, - ) -> SerdeWrapper { - SerdeWrapper(Swaps::get_spot_price(pool_id, asset_in, asset_out, with_fees).ok().unwrap_or(0)) + ) -> Balance { + Swaps::get_spot_price(pool_id, asset_in, asset_out, with_fees).ok().unwrap_or(0) } fn pool_account_id(pool_id: &PoolId) -> AccountIdTest { Swaps::pool_account_id(pool_id) } - fn pool_shares_id(pool_id: PoolId) -> Asset> { - Asset::PoolShare(SerdeWrapper(pool_id)) + fn pool_shares_id(pool_id: PoolId) -> Asset { + Asset::PoolShare(pool_id) } fn get_all_spot_prices( From ddaa364f8aaa42a70dee6bcc60dc15115a70d937 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Wed, 24 Jan 2024 15:46:05 +0530 Subject: [PATCH 03/14] New Asset System - Implement asset-router (#1189) * Add new asset types * Add custom assets to runtime * Add market assets to runtime * Add pallet_assets benchmark and weights * Expose MarketAssets call enum * Update todo comment to incluse issue number * Add campaign assets instance to runtime * Cargo fmt * Taplo fmt * Refine asset class types * Use efficient asset types * Add all variants to overarching Asset enum * Make MarketId compactable * Adjust SerdeWrapper Soon to be deprecated * Make Battery Station compileable * Make Zeitgeist compileable * Cleanup code * Remove NewForeignAsset Conversion to compact encoding implies massive migration effort * Implement asset type conversions * Add Currency asset class * Remove deprecated SerdeWrapper * Add Currencies asset class Also renames existing CurrencyId to Assets * Add scale codec index matching tests * Add asset conversion tests * Update docstring * Improve assets module structure * Update license * Create asset-router pallet scaffold * Start implementing all traits * Implement MultiCurrency partially for asset-router * Implement MultiCurrency for asset-router * Implement MultiCurrencyExtended * MultiLockableCurrency * Implement MultiReservableCurrency * Implement NamedMultiReservableCurrency * Fix runtime * Integrate asset-router in runtime * Fix a couple of bugs * Prepare asset-router test environment * Start MultiCurrency test impl * Complete MultiCurrency tests * Add MultiCurrencyExtended tests * Implement MultiReserveableCurrency tests * Implement NamedMultiReserveableCurrency tests * Implement MultiLockableCurrency tests * Improve test structure * Undo unnecessary change * Format code * Implement fungibles::{Create, Destroy, Inspect} * Remove comment * Add tests for Inspect impl * Add tests for Create impl * Add tests for Destroy impl * Make asset types configurable * Use less restricitve traits for pm AssetManager * Make project compilable * Merge branch 'sea212-new-asset-system' into sea212-new-asset-system-part-3 * Update licenses * Repair tests partially * Comment out irrelevant test * Partially satisfy Clippy * Adjust XCM to use Currencies * Adjust XCM to use Currencies (zeitgeist runtime) * Adjust prediction markets tests * Adjust neo-swaps mock * Satisfy Clippy * Format code * Update licenses * Remove pallet-asset benchmark helper from mock * Format code * Repair tests with runtime-benchmarks * Format code * Remove TODO comment * Implement log for unhandled errors in asset-router * Use log target * Improve and prettify readmes * Use assert_noop! in favor of asset_err! Co-authored-by: Malte Kliemann * Make sea212 codeowner of zrml/asset-router * Fix typo * Rename variable * Better abs() overflow handling * Check Bob's balance in MultiCurrency tests * Fix Create test for Currencies * Improve test precision * Verify total issuance via direct pallet invocation * Implement Inspect for Currencies * Satisfy Clippy --------- Co-authored-by: Malte Kliemann --- CODEOWNERS | 1 + Cargo.lock | 23 + Cargo.toml | 3 + README.md | 8 +- primitives/src/assets/campaign_assets.rs | 2 +- primitives/src/assets/currencies.rs | 7 +- primitives/src/assets/custom_assets.rs | 2 +- primitives/src/constants/mock.rs | 19 +- primitives/src/types.rs | 7 +- runtime/battery-station/Cargo.toml | 2 + .../src/integration_tests/xcm/setup.rs | 13 +- .../src/integration_tests/xcm/test_net.rs | 4 +- .../xcm/tests/currency_id_convert.rs | 12 +- .../integration_tests/xcm/tests/transfers.rs | 6 +- runtime/battery-station/src/parameters.rs | 25 +- .../src/xcm_config/asset_registry.rs | 10 +- .../battery-station/src/xcm_config/config.rs | 74 +- .../battery-station/src/xcm_config/fees.rs | 4 +- runtime/common/src/fees.rs | 34 +- runtime/common/src/lib.rs | 36 +- runtime/zeitgeist/Cargo.toml | 2 + .../src/integration_tests/xcm/setup.rs | 15 +- .../src/integration_tests/xcm/test_net.rs | 4 +- .../xcm/tests/currency_id_convert.rs | 12 +- .../integration_tests/xcm/tests/transfers.rs | 8 +- runtime/zeitgeist/src/parameters.rs | 25 +- .../src/xcm_config/asset_registry.rs | 10 +- runtime/zeitgeist/src/xcm_config/config.rs | 74 +- runtime/zeitgeist/src/xcm_config/fees.rs | 4 +- zrml/asset-router/Cargo.toml | 37 + zrml/asset-router/README.md | 11 + zrml/asset-router/src/lib.rs | 806 ++++++++++++++++++ zrml/asset-router/src/mock.rs | 280 ++++++ zrml/asset-router/src/tests/create.rs | 61 ++ zrml/asset-router/src/tests/destroy.rs | 101 +++ zrml/asset-router/src/tests/inspect.rs | 110 +++ zrml/asset-router/src/tests/mod.rs | 44 + zrml/asset-router/src/tests/multi_currency.rs | 166 ++++ .../src/tests/multi_lockable_currency.rs | 97 +++ .../src/tests/multi_reservable_currency.rs | 110 +++ .../tests/named_multi_reservable_currency.rs | 118 +++ zrml/neo-swaps/src/mock.rs | 8 +- zrml/prediction-markets/Cargo.toml | 7 +- zrml/prediction-markets/src/lib.rs | 23 +- zrml/prediction-markets/src/mock.rs | 154 +++- zrml/prediction-markets/src/tests.rs | 58 +- zrml/swaps/Cargo.toml | 4 +- zrml/swaps/src/lib.rs | 16 +- zrml/swaps/src/tests.rs | 4 +- 49 files changed, 2426 insertions(+), 235 deletions(-) create mode 100644 zrml/asset-router/Cargo.toml create mode 100644 zrml/asset-router/README.md create mode 100644 zrml/asset-router/src/lib.rs create mode 100644 zrml/asset-router/src/mock.rs create mode 100644 zrml/asset-router/src/tests/create.rs create mode 100644 zrml/asset-router/src/tests/destroy.rs create mode 100644 zrml/asset-router/src/tests/inspect.rs create mode 100644 zrml/asset-router/src/tests/mod.rs create mode 100644 zrml/asset-router/src/tests/multi_currency.rs create mode 100644 zrml/asset-router/src/tests/multi_lockable_currency.rs create mode 100644 zrml/asset-router/src/tests/multi_reservable_currency.rs create mode 100644 zrml/asset-router/src/tests/named_multi_reservable_currency.rs diff --git a/CODEOWNERS b/CODEOWNERS index 00e47700b..758a6baa0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,6 +8,7 @@ # Ignore everything but mod.rs in /runtime/common/src/weights/ /runtime/common/src/weights/* /runtime/common/src/weights/mod.rs @sea212 +/zrml/asset-router/ @sea212 /zrml/authorized/ @Chralt98 /zrml/court/ @Chralt98 /zrml/global-disputes/ @Chralt98 diff --git a/Cargo.lock b/Cargo.lock index f20131183..c677897cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,7 @@ dependencies = [ "xcm-emulator", "xcm-executor", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -14495,6 +14496,7 @@ dependencies = [ "xcm-emulator", "xcm-executor", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -14531,6 +14533,25 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "zrml-asset-router" +version = "0.4.1" +dependencies = [ + "frame-support", + "frame-system", + "log", + "orml-tokens", + "orml-traits", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "test-case", + "zeitgeist-primitives", +] + [[package]] name = "zrml-authorized" version = "0.4.1" @@ -14736,6 +14757,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-randomness-collective-flip", "pallet-timestamp", @@ -14751,6 +14773,7 @@ dependencies = [ "test-case", "xcm", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", diff --git a/Cargo.toml b/Cargo.toml index 00ca1d25f..192cddebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ default-members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/asset-router", "zrml/court", "zrml/global-disputes", "zrml/liquidity-mining", @@ -29,6 +30,7 @@ members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/asset-router", "zrml/court", "zrml/global-disputes", "zrml/liquidity-mining", @@ -229,6 +231,7 @@ zrml-swaps-rpc = { path = "zrml/swaps/rpc" } # Zeitgeist (wasm) common-runtime = { path = "runtime/common", default-features = false } zeitgeist-primitives = { path = "primitives", default-features = false } +zrml-asset-router = { path = "zrml/asset-router", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } zrml-court = { path = "zrml/court", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } diff --git a/README.md b/README.md index 0f98b4ee0..e8b94e1c0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ _anything_. ## Modules +- [asset-router](./zrml/asset-router) - Routes all asset classes to the + respective pallets and provides a garbage collection for destructible assets. - [authorized](./zrml/authorized) - Offers authorized resolution of disputes. - [court](./zrml/court) - An implementation of a court mechanism used to resolve disputes in a decentralized fashion. @@ -33,9 +35,9 @@ _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) - A naive orderbook implementation that's only + part of Zeitgeist's PoC. Will be replaced by a v2 orderbook that uses 0x-style + hybrid on-chain and off-chain trading. - [parimutuel](./zrml/parimutuel) - A straightforward parimutuel market maker for categorical markets. - [prediction-markets](./zrml/prediction-markets) - The core implementation of diff --git a/primitives/src/assets/campaign_assets.rs b/primitives/src/assets/campaign_assets.rs index 245b60ce4..6f5b8de4b 100644 --- a/primitives/src/assets/campaign_assets.rs +++ b/primitives/src/assets/campaign_assets.rs @@ -25,7 +25,7 @@ use super::*; #[derive( Clone, CompactAs, Copy, Debug, Decode, Default, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo, )] -pub struct CampaignAssetClass(#[codec(compact)] pub(super) CampaignAssetId); +pub struct CampaignAssetClass(#[codec(compact)] pub CampaignAssetId); impl From> for CampaignAssetClass { fn from(value: Compact) -> CampaignAssetClass { diff --git a/primitives/src/assets/currencies.rs b/primitives/src/assets/currencies.rs index 02b4cd86a..8f88865fb 100644 --- a/primitives/src/assets/currencies.rs +++ b/primitives/src/assets/currencies.rs @@ -17,10 +17,13 @@ use super::*; -/// The `CurrencyClass` enum represents all non-ztg currencies. +/// The `CurrencyClass` enum represents all non-ztg currencies +// used in orml-tokens #[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)] +#[derive( + 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 // orml-tokens to pallet-assets is complete diff --git a/primitives/src/assets/custom_assets.rs b/primitives/src/assets/custom_assets.rs index e3264c770..0508a440e 100644 --- a/primitives/src/assets/custom_assets.rs +++ b/primitives/src/assets/custom_assets.rs @@ -23,7 +23,7 @@ use super::*; #[derive( Clone, CompactAs, Copy, Debug, Decode, Default, Eq, Encode, MaxEncodedLen, PartialEq, TypeInfo, )] -pub struct CustomAssetClass(#[codec(compact)] pub(super) CustomAssetId); +pub struct CustomAssetClass(#[codec(compact)] pub CustomAssetId); impl From> for CustomAssetClass { fn from(value: Compact) -> CustomAssetClass { diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 8f8c49b9b..200221c5b 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -21,12 +21,22 @@ pub use super::*; use crate::{ assets::Asset, - types::{Assets, Balance, Moment}, + types::{Assets, Balance, Currencies, Moment}, }; use frame_support::{parameter_types, traits::LockIdentifier, PalletId}; use orml_traits::parameter_type_with_key; use sp_arithmetic::Perbill; +// Assets +parameter_types! { + pub const AssetsAccountDeposit: Balance = 0; + pub const AssetsApprovalDeposit: Balance = 0; + pub const AssetsDeposit: Balance = 0; + pub const AssetsStringLimit: u32 = 256; + pub const AssetsMetadataDepositBase: Balance = 0; + pub const AssetsMetadataDepositPerByte: Balance = 0; +} + // Authorized parameter_types! { pub const AuthorizedPalletId: PalletId = PalletId(*b"zge/atzd"); @@ -164,6 +174,7 @@ parameter_types! { pub const GetNativeCurrencyId: Assets = Asset::Ztg; } +// Will be removed once asset-system is completely integerated parameter_type_with_key! { pub ExistentialDeposits: |currency_id: Assets| -> Balance { match currency_id { @@ -173,6 +184,12 @@ parameter_type_with_key! { }; } +parameter_type_with_key! { + pub ExistentialDepositsNew: |_currency_id: Currencies| -> Balance { + 1 + }; +} + // System parameter_types! { pub const BlockHashCount: u64 = 250; diff --git a/primitives/src/types.rs b/primitives/src/types.rs index ab69b5be2..39ff1a294 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -87,13 +87,13 @@ pub type Assets = Asset; /// Asset type representing campaign assets pub type CampaignAsset = CampaignAssetClass; -// ID type for campaign asset class +// ID type of the campaign asset class pub type CampaignAssetId = u128; /// Asset type representing custom assets pub type CustomAsset = CustomAssetClass; -// ID type for campaign asset class +// ID type of the custom asset class pub type CustomAssetId = u128; // Asset type representing currencies (excluding ZTG) @@ -102,9 +102,6 @@ pub type Currencies = CurrencyClass; /// Asset type representing assets used by prediction markets pub type MarketAsset = MarketAssetClass; -/// ID type for asset classes that anyone can create -pub type AssetId = u128; - /// 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/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index acf436504..5e7bdceb3 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -108,6 +108,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } +zrml-asset-router = { workspace = true } zrml-authorized = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } @@ -382,6 +383,7 @@ try-runtime = [ "orml-xtokens?/try-runtime", # Zeitgeist runtime pallets + "zrml-asset-router/try-runtime", "zrml-authorized/try-runtime", "zrml-court/try-runtime", "zrml-liquidity-mining/try-runtime", diff --git a/runtime/battery-station/src/integration_tests/xcm/setup.rs b/runtime/battery-station/src/integration_tests/xcm/setup.rs index 06481e4b0..24d4be69c 100644 --- a/runtime/battery-station/src/integration_tests/xcm/setup.rs +++ b/runtime/battery-station/src/integration_tests/xcm/setup.rs @@ -27,7 +27,7 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Asset, CustomMetadata}; +use zeitgeist_primitives::types::{Currencies, CustomMetadata}; pub(super) struct ExtBuilder { balances: Vec<(AccountId, Assets, Balance)>, @@ -71,6 +71,9 @@ impl ExtBuilder { .balances .into_iter() .filter(|(_, currency_id, _)| *currency_id != native_currency_id) + .map(|(account_id, currency_id, initial_balance)| { + (account_id, currency_id.try_into().unwrap(), initial_balance) + }) .collect::>(), } .assimilate_storage(&mut t) @@ -103,10 +106,10 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Asset = Assets::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Asset = Assets::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Asset = Assets::ForeignAsset(2); -pub const BTC_ID: Asset = Assets::ForeignAsset(3); +pub const FOREIGN_ZTG_ID: Currencies = Currencies::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Currencies = Currencies::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Currencies = Currencies::ForeignAsset(2); +pub const BTC_ID: Currencies = Currencies::ForeignAsset(3); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/battery-station/src/integration_tests/xcm/test_net.rs b/runtime/battery-station/src/integration_tests/xcm/test_net.rs index c6ce4d520..2ee922bc5 100644 --- a/runtime/battery-station/src/integration_tests/xcm/test_net.rs +++ b/runtime/battery-station/src/integration_tests/xcm/test_net.rs @@ -103,8 +103,8 @@ pub(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { ExtBuilder::default() .set_balances(vec![ (ALICE, Assets::Ztg, ztg(10)), - (ALICE, FOREIGN_PARENT_ID, roc(10)), - (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, roc(1)), + (ALICE, FOREIGN_PARENT_ID.into(), roc(10)), + (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID.into(), roc(1)), ]) .set_parachain_id(parachain_id) .build() diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs index ac8b380ca..a51401686 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -62,18 +62,18 @@ fn convert_any_registered_parent_multilocation() { foreign_parent_multilocation() ); - assert_eq!(>::convert(FOREIGN_PARENT_ID), None); + assert_eq!(>::convert(Assets::from(FOREIGN_PARENT_ID)), None); // Register parent as foreign asset in the Zeitgeist parachain register_foreign_parent(None); assert_eq!( >::convert(foreign_parent_multilocation()), - Ok(FOREIGN_PARENT_ID), + Ok(FOREIGN_PARENT_ID.into()), ); assert_eq!( - >::convert(FOREIGN_PARENT_ID), + >::convert(Assets::from(FOREIGN_PARENT_ID)), Some(foreign_parent_multilocation()) ); }); @@ -87,18 +87,18 @@ fn convert_any_registered_sibling_multilocation() { foreign_sibling_multilocation() ); - assert_eq!(>::convert(FOREIGN_SIBLING_ID), None); + assert_eq!(>::convert(Assets::from(FOREIGN_SIBLING_ID)), None); // Register sibling as foreign asset in the Zeitgeist parachain register_foreign_sibling(None); assert_eq!( >::convert(foreign_sibling_multilocation()), - Ok(FOREIGN_SIBLING_ID), + Ok(FOREIGN_SIBLING_ID.into()), ); assert_eq!( - >::convert(FOREIGN_SIBLING_ID), + >::convert(Assets::from(FOREIGN_SIBLING_ID)), Some(foreign_sibling_multilocation()) ); }); 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 95ec4c5f2..64105768c 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs @@ -126,7 +126,7 @@ fn transfer_ztg_sibling_to_zeitgeist() { assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), bob_initial_balance); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(BOB), - FOREIGN_ZTG_ID, + FOREIGN_ZTG_ID.into(), transfer_amount, Box::new( MultiLocation::new( @@ -250,7 +250,7 @@ fn transfer_btc_zeitgeist_to_sibling() { Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - BTC_ID, + BTC_ID.into(), transfer_amount, Box::new( MultiLocation::new( @@ -328,7 +328,7 @@ fn transfer_roc_to_relay_chain() { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - FOREIGN_PARENT_ID, + FOREIGN_PARENT_ID.into(), transfer_amount, Box::new( MultiLocation::new(1, X1(Junction::AccountId32 { id: BOB.into(), network: None })) diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 6b9b8d160..bc7107d3c 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -497,16 +497,17 @@ parameter_type_with_key! { // are cleaned up automatically. In case of scalar outcomes, the market account can have dust. // Unless LPs use `pool_exit_with_exact_asset_amount`, there can be some dust pool shares remaining. // Explicit match arms are used to ensure new asset types are respected. - pub ExistentialDeposits: |currency_id: Assets| -> Balance { + pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::OldCategoricalOutcome(_,_) => ExistentialDeposit::get(), + Currencies::OldParimutuelShare(_,_) => ExistentialDeposit::get(), + Currencies::OldPoolShare(_) => ExistentialDeposit::get(), + Currencies::OldScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] - Asset::ForeignAsset(id) => { + Currencies::ForeignAsset(id) => { let maybe_metadata = < orml_asset_registry::Pallet as orml_traits::asset_registry::Inspect - >::metadata(&Asset::ForeignAsset(*id)); + >::metadata(&Currencies::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; @@ -515,17 +516,7 @@ parameter_type_with_key! { 1 } #[cfg(not(feature = "parachain"))] - Asset::ForeignAsset(_) => ExistentialDeposit::get(), - Asset::Ztg => ExistentialDeposit::get(), - Asset::ParimutuelShare(_,_) => ExistentialDeposit::get(), - - // The following assets are irrelevant, as they are managed by pallet-assets - Asset::NewParimutuelShare(_,_) - | Asset::NewCategoricalOutcome(_, _) - | Asset::NewScalarOutcome(_,_) - | Asset::NewPoolShare(_) - | Asset::CustomAssetClass(_) - | Asset::CampaignAssetClass(_) => ExistentialDeposit::get(), + Currencies::ForeignAsset(_) => ExistentialDeposit::get(), } }; } diff --git a/runtime/battery-station/src/xcm_config/asset_registry.rs b/runtime/battery-station/src/xcm_config/asset_registry.rs index bdf5435af..20d23e90d 100644 --- a/runtime/battery-station/src/xcm_config/asset_registry.rs +++ b/runtime/battery-station/src/xcm_config/asset_registry.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Assets, Balance}; +use crate::{Balance, Currencies}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(Assets, AssetMetadata), DispatchError> { + ) -> Result<(Currencies, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for CustomAs } fn post_register( - _id: Assets, + _id: Currencies, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs index 09c649ebd..197151614 100644 --- a/runtime/battery-station/src/xcm_config/config.rs +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -51,7 +51,10 @@ use xcm_builder::{ TakeWeightCredit, }; use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; +use zeitgeist_primitives::{ + constants::BalanceFractionalDecimals, + types::{Asset, Currencies}, +}; pub mod battery_station { #[cfg(test)] @@ -204,7 +207,7 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, @@ -217,29 +220,41 @@ impl< > { fn adjust_fractional_places(asset: &MultiAsset) -> MultiAsset { - if let Some(ref asset_id) = CurrencyIdConvert::convert(asset.clone()) { - if let Fungible(amount) = asset.fun { - let mut asset_updated = asset.clone(); - let native_decimals = u32::from(FracDecPlaces::get()); - let metadata = AssetRegistry::metadata(asset_id); - - if let Some(metadata) = metadata { - let decimals = metadata.decimals; - - asset_updated.fun = if decimals > native_decimals { - let power = decimals.saturating_sub(native_decimals); - let adjust_factor = 10u128.saturating_pow(power); - // Floors the adjusted token amount, thus no tokens are generated - Fungible(amount.saturating_div(adjust_factor)) - } else { - let power = native_decimals.saturating_sub(decimals); - let adjust_factor = 10u128.saturating_pow(power); - Fungible(amount.saturating_mul(adjust_factor)) - }; - - return asset_updated; + let (asset_id, amount) = + if let Some(ref asset_id) = CurrencyIdConvert::convert(asset.clone()) { + if let Fungible(amount) = asset.fun { + (*asset_id, amount) + } else { + return asset.clone(); } - } + } else { + return asset.clone(); + }; + + let currency = if let Ok(currency) = Currencies::try_from(asset_id) { + currency + } else { + return asset.clone(); + }; + + let metadata = AssetRegistry::metadata(¤cy); + if let Some(metadata) = metadata { + let mut asset_adjusted = asset.clone(); + let decimals = metadata.decimals; + let native_decimals = u32::from(FracDecPlaces::get()); + + asset_adjusted.fun = if decimals > native_decimals { + let power = decimals.saturating_sub(native_decimals); + let adjust_factor = 10u128.saturating_pow(power); + // Floors the adjusted token amount, thus no tokens are generated + Fungible(amount.saturating_div(adjust_factor)) + } else { + let power = native_decimals.saturating_sub(decimals); + let adjust_factor = 10u128.saturating_pow(power); + Fungible(amount.saturating_mul(adjust_factor)) + }; + + return asset_adjusted; } asset.clone() @@ -247,7 +262,7 @@ impl< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, @@ -335,7 +350,10 @@ impl Convert> for AssetConvert { general_key(battery_station::KEY), ), )), - Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).ok()?, + Asset::ForeignAsset(_) => { + let currency = Currencies::try_from(id).ok()?; + AssetRegistry::multilocation(¤cy).ok()? + } _ => None, } } @@ -370,9 +388,9 @@ impl xcm_executor::traits::Convert for AssetConvert { return Err(location); } - AssetRegistry::location_to_asset_id(location).ok_or(location) + AssetRegistry::location_to_asset_id(location).ok_or(location).map(|a| a.into()) } - _ => AssetRegistry::location_to_asset_id(location).ok_or(location), + _ => AssetRegistry::location_to_asset_id(location).ok_or(location).map(|a| a.into()), } } } diff --git a/runtime/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs index 12e25935a..138ed3183 100644 --- a/runtime/battery-station/src/xcm_config/fees.rs +++ b/runtime/battery-station/src/xcm_config/fees.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Assets, Balance}; +use crate::{Balance, Currencies}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -55,7 +55,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = Assets, + AssetId = Currencies, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index d16e357e3..d5f53a216 100644 --- a/runtime/common/src/fees.rs +++ b/runtime/common/src/fees.rs @@ -96,7 +96,7 @@ macro_rules! impl_foreign_fees { // It does calculate foreign fees by extending transactions to include an optional // `AssetId` that specifies the asset to be used for payment (defaulting to the native // token on `None`), such that for each transaction the asset id can be specified. - // For real ZTG `None` is used and for DOT `Some(Asset::Foreign(0))` is used. + // For real ZTG `None` is used and for DOT `Some(Currencies::Foreign(0))` is used. pub(crate) fn calculate_fee( native_fee: Balance, @@ -119,9 +119,9 @@ macro_rules! impl_foreign_fees { #[cfg(feature = "parachain")] pub(crate) fn get_fee_factor( - currency_id: Assets, + currency: Currencies, ) -> Result { - let metadata = ::metadata(¤cy_id).ok_or( + let metadata = ::metadata(¤cy).ok_or( TransactionValidityError::Invalid(InvalidTransaction::Custom( CustomTxError::NoAssetMetadata as u8, )), @@ -139,12 +139,12 @@ macro_rules! impl_foreign_fees { fn to_asset_balance( native_fee: Balance, - asset_id: TxPaymentAssetId, + currency_id: TxPaymentAssetId, ) -> Result { #[cfg(feature = "parachain")] { - let currency_id = Asset::ForeignAsset(asset_id); - let fee_factor = get_fee_factor(currency_id)?; + let currency = Currencies::ForeignAsset(currency_id); + let fee_factor = get_fee_factor(currency)?; let converted_fee = calculate_fee(native_fee, fee_factor)?; Ok(converted_fee) } @@ -187,7 +187,7 @@ macro_rules! impl_foreign_fees { let converted_fee = TTCBalanceToAssetBalance::to_asset_balance(native_fee, asset_id)? .max(min_converted_fee); - let currency_id = Asset::ForeignAsset(asset_id); + let currency_id = Currencies::ForeignAsset(asset_id); let can_withdraw = >::can_withdraw(currency_id, who, converted_fee); if can_withdraw != WithdrawConsequence::Success { @@ -208,7 +208,7 @@ macro_rules! impl_foreign_fees { let min_converted_fee = if corrected_native_fee.is_zero() { Zero::zero() } else { One::one() }; let asset_id = match paid.asset() { - Asset::ForeignAsset(asset_id) => asset_id, + Currencies::ForeignAsset(asset_id) => asset_id, _ => { return Err(TransactionValidityError::Invalid(InvalidTransaction::Custom( CustomTxError::NonForeignAssetPaid as u8, @@ -332,15 +332,15 @@ macro_rules! fee_tests { t.execute_with(|| { let fee_and_tip_balance = 10 * ExistentialDeposit::get(); let fees_and_tips = >::issue( - Asset::ForeignAsset(0), + Currencies::ForeignAsset(0), fee_and_tip_balance, ); assert!( - Tokens::free_balance(Asset::ForeignAsset(0), &Treasury::account_id()).is_zero() + Tokens::free_balance(Currencies::ForeignAsset(0), &Treasury::account_id()).is_zero() ); DealWithForeignFees::on_unbalanced(fees_and_tips); assert_eq!( - Tokens::free_balance(Asset::ForeignAsset(0), &Treasury::account_id()), + Tokens::free_balance(Currencies::ForeignAsset(0), &Treasury::account_id()), fee_and_tip_balance, ); }); @@ -386,7 +386,7 @@ macro_rules! fee_tests { location: Some(xcm::VersionedMultiLocation::V3(xcm::latest::MultiLocation::parent())), additional: custom_metadata, }; - let dot = Asset::ForeignAsset(0); + let dot = Currencies::ForeignAsset(0); assert_ok!(AssetRegistry::register_asset(RuntimeOrigin::root(), meta.clone(), Some(dot))); @@ -455,7 +455,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Asset::ForeignAsset(dot_asset_id); + let dot = Currencies::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -477,7 +477,7 @@ macro_rules! fee_tests { { // no registering of dot assert_noop!( - get_fee_factor(Asset::ForeignAsset(0)), + get_fee_factor(Currencies::ForeignAsset(0)), TransactionValidityError::Invalid(InvalidTransaction::Custom(2u8)) ); } @@ -506,7 +506,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Asset::ForeignAsset(dot_asset_id); + let dot = Currencies::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -540,7 +540,7 @@ macro_rules! fee_tests { location: None, additional: custom_metadata, }; - let non_location_token = Asset::ForeignAsset(1); + let non_location_token = Currencies::ForeignAsset(1); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -575,7 +575,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Asset::ForeignAsset(dot_asset_id); + let dot = Currencies::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index c588b328a..f6fe2da2b 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -326,6 +326,7 @@ macro_rules! create_runtime { NeoSwaps: zrml_neo_swaps::{Call, Event, Pallet, Storage} = 60, Orderbook: zrml_orderbook::{Call, Event, Pallet, Storage} = 61, Parimutuel: zrml_parimutuel::{Call, Event, Pallet, Storage} = 62, + AssetRouter: zrml_asset_router::{Pallet} = 63, $($additional_pallets)* } @@ -568,7 +569,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl orml_asset_registry::Config for Runtime { - type AssetId = Assets; + type AssetId = Currencies; type AssetProcessor = CustomAssetProcessor; type AuthorityOrigin = AsEnsureOriginWithArg; type Balance = Balance; @@ -579,13 +580,13 @@ macro_rules! impl_config_traits { impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; + type MultiCurrency = AssetRouter; type NativeCurrency = BasicCurrencyAdapter; type WeightInfo = weights::orml_currencies::WeightInfo; } 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 = (); @@ -600,7 +601,7 @@ macro_rules! impl_config_traits { type Amount = Amount; type Balance = Balance; type CurrencyHooks = CurrencyHooks; - type CurrencyId = Assets; + type CurrencyId = Currencies; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -1158,6 +1159,19 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl parachain_info::Config for Runtime {} + 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 MarketAssetType = MarketAsset; + type MarketAssets = MarketAssets; + } + impl zrml_authorized::Config for Runtime { type AuthorizedDisputeResolutionOrigin = EnsureRootOrMoreThanHalfAdvisoryCommittee; type Currency = Balances; @@ -2096,15 +2110,15 @@ macro_rules! create_common_benchmark_logic { pub(crate) mod tokens { use super::utils::{lookup_of_account, set_balance as update_balance}; - use crate::{AccountId, Balance, Assets, Tokens, Runtime}; + use crate::{AccountId, Balance, Tokens, Runtime}; use frame_benchmarking::{account, vec, whitelisted_caller}; use frame_system::RawOrigin; use orml_benchmarking::runtime_benchmarks; use orml_traits::MultiCurrency; - use zeitgeist_primitives::{constants::BASE, types::Asset}; + use zeitgeist_primitives::{constants::BASE, types::Currencies}; const SEED: u32 = 0; - const ASSET: Assets = Asset::CategoricalOutcome(0, 0); + const ASSET: Currencies = Currencies::ForeignAsset(7); runtime_benchmarks! { { Runtime, orml_tokens } @@ -2113,7 +2127,7 @@ macro_rules! create_common_benchmark_logic { let amount: Balance = BASE; let from: AccountId = whitelisted_caller(); - update_balance(ASSET, &from, amount); + update_balance(ASSET.into(), &from, amount); let to: AccountId = account("to", 0, SEED); let to_lookup = lookup_of_account(to.clone()); @@ -2126,7 +2140,7 @@ macro_rules! create_common_benchmark_logic { let amount: Balance = BASE; let from: AccountId = whitelisted_caller(); - update_balance(ASSET, &from, amount); + update_balance(ASSET.into(), &from, amount); let to: AccountId = account("to", 0, SEED); let to_lookup = lookup_of_account(to); @@ -2137,7 +2151,7 @@ macro_rules! create_common_benchmark_logic { transfer_keep_alive { let from: AccountId = whitelisted_caller(); - update_balance(ASSET, &from, 2 * BASE); + update_balance(ASSET.into(), &from, 2 * BASE); let to: AccountId = account("to", 0, SEED); let to_lookup = lookup_of_account(to.clone()); @@ -2149,7 +2163,7 @@ macro_rules! create_common_benchmark_logic { force_transfer { let from: AccountId = account("from", 0, SEED); let from_lookup = lookup_of_account(from.clone()); - update_balance(ASSET, &from, 2 * BASE); + update_balance(ASSET.into(), &from, 2 * BASE); let to: AccountId = account("to", 0, SEED); let to_lookup = lookup_of_account(to.clone()); diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 25d46bd22..32f9c528a 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -107,6 +107,7 @@ xcm-executor = { workspace = true, optional = true } common-runtime = { workspace = true } zeitgeist-primitives = { workspace = true } +zrml-asset-router = { workspace = true } zrml-authorized = { workspace = true } zrml-court = { workspace = true } zrml-global-disputes = { workspace = true, optional = true } @@ -372,6 +373,7 @@ try-runtime = [ "orml-xtokens?/try-runtime", # Zeitgeist runtime pallets + "zrml-asset-router/try-runtime", "zrml-authorized/try-runtime", "zrml-court/try-runtime", "zrml-liquidity-mining/try-runtime", diff --git a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs index 55f0f4c04..bfb31474e 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs @@ -27,7 +27,7 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Asset, CustomMetadata}; +use zeitgeist_primitives::types::{Currencies, CustomMetadata}; pub(super) struct ExtBuilder { balances: Vec<(AccountId, Assets, Balance)>, @@ -71,6 +71,9 @@ impl ExtBuilder { .balances .into_iter() .filter(|(_, currency_id, _)| *currency_id != native_currency_id) + .map(|(account_id, currency_id, initial_balance)| { + (account_id, currency_id.try_into().unwrap(), initial_balance) + }) .collect::>(), } .assimilate_storage(&mut t) @@ -103,11 +106,11 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Asset = Assets::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Asset = Assets::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Asset = Assets::ForeignAsset(2); -pub const BTC_ID: Asset = Assets::ForeignAsset(3); -pub const ETH_ID: Asset = Assets::ForeignAsset(4); +pub const FOREIGN_ZTG_ID: Currencies = Currencies::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Currencies = Currencies::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Currencies = Currencies::ForeignAsset(2); +pub const BTC_ID: Currencies = Currencies::ForeignAsset(3); +pub const ETH_ID: Currencies = Currencies::ForeignAsset(4); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs index 3c7518fa1..c8f212d12 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs @@ -100,8 +100,8 @@ pub(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { ExtBuilder::default() .set_balances(vec![ (ALICE, Assets::Ztg, ztg(10)), - (ALICE, FOREIGN_PARENT_ID, dot(10)), - (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, dot(10)), + (ALICE, FOREIGN_PARENT_ID.into(), dot(10)), + (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID.into(), dot(10)), ]) .set_parachain_id(parachain_id) .build() diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs index 23b98e5c2..a687a5edd 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -61,18 +61,18 @@ fn convert_any_registered_parent_multilocation() { foreign_parent_multilocation() ); - assert_eq!(>::convert(FOREIGN_PARENT_ID), None); + assert_eq!(>::convert(Assets::from(FOREIGN_PARENT_ID)), None); // Register parent as foreign asset in the Zeitgeist parachain register_foreign_parent(None); assert_eq!( >::convert(foreign_parent_multilocation()), - Ok(FOREIGN_PARENT_ID), + Ok(FOREIGN_PARENT_ID.into()), ); assert_eq!( - >::convert(FOREIGN_PARENT_ID), + >::convert(Assets::from(FOREIGN_PARENT_ID)), Some(foreign_parent_multilocation()) ); }); @@ -86,18 +86,18 @@ fn convert_any_registered_sibling_multilocation() { foreign_sibling_multilocation() ); - assert_eq!(>::convert(FOREIGN_SIBLING_ID), None); + assert_eq!(>::convert(Assets::from(FOREIGN_SIBLING_ID)), None); // Register sibling as foreign asset in the Zeitgeist parachain register_foreign_sibling(None); assert_eq!( >::convert(foreign_sibling_multilocation()), - Ok(FOREIGN_SIBLING_ID), + Ok(FOREIGN_SIBLING_ID.into()), ); assert_eq!( - >::convert(FOREIGN_SIBLING_ID), + >::convert(Assets::from(FOREIGN_SIBLING_ID)), Some(foreign_sibling_multilocation()) ); }); diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs index 528740841..31f64b2c2 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs @@ -126,7 +126,7 @@ fn transfer_ztg_sibling_to_zeitgeist() { assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), bob_initial_balance); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(BOB), - FOREIGN_ZTG_ID, + FOREIGN_ZTG_ID.into(), transfer_amount, Box::new( MultiLocation::new( @@ -250,7 +250,7 @@ fn transfer_btc_zeitgeist_to_sibling() { Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - BTC_ID, + BTC_ID.into(), transfer_amount, Box::new( MultiLocation::new( @@ -371,7 +371,7 @@ fn transfer_eth_zeitgeist_to_sibling() { Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - ETH_ID, + ETH_ID.into(), transfer_amount, Box::new( MultiLocation::new( @@ -446,7 +446,7 @@ fn transfer_dot_to_relay_chain() { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - FOREIGN_PARENT_ID, + FOREIGN_PARENT_ID.into(), transfer_amount, Box::new( MultiLocation::new(1, X1(Junction::AccountId32 { id: BOB.into(), network: None })) diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 86237f7bb..6ad4ef44b 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -495,16 +495,17 @@ parameter_type_with_key! { // are cleaned up automatically. In case of scalar outcomes, the market account can have dust. // Unless LPs use `pool_exit_with_exact_asset_amount`, there can be some dust pool shares remaining. // Explicit match arms are used to ensure new asset types are respected. - pub ExistentialDeposits: |currency_id: Assets| -> Balance { + pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::OldCategoricalOutcome(_,_) => ExistentialDeposit::get(), + Currencies::OldParimutuelShare(_,_) => ExistentialDeposit::get(), + Currencies::OldPoolShare(_) => ExistentialDeposit::get(), + Currencies::OldScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] - Asset::ForeignAsset(id) => { + Currencies::ForeignAsset(id) => { let maybe_metadata = < orml_asset_registry::Pallet as orml_traits::asset_registry::Inspect - >::metadata(&Asset::ForeignAsset(*id)); + >::metadata(&Currencies::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; @@ -513,17 +514,7 @@ parameter_type_with_key! { 1 } #[cfg(not(feature = "parachain"))] - Asset::ForeignAsset(_) => ExistentialDeposit::get(), - Asset::Ztg => ExistentialDeposit::get(), - Asset::ParimutuelShare(_,_) => ExistentialDeposit::get(), - - // The following assets are irrelevant, as they are managed by pallet-assets - Asset::NewParimutuelShare(_,_) - | Asset::NewCategoricalOutcome(_, _) - | Asset::NewScalarOutcome(_,_) - | Asset::NewPoolShare(_) - | Asset::CustomAssetClass(_) - | Asset::CampaignAssetClass(_) => ExistentialDeposit::get(), + Currencies::ForeignAsset(_) => ExistentialDeposit::get(), } }; } diff --git a/runtime/zeitgeist/src/xcm_config/asset_registry.rs b/runtime/zeitgeist/src/xcm_config/asset_registry.rs index bdf5435af..20d23e90d 100644 --- a/runtime/zeitgeist/src/xcm_config/asset_registry.rs +++ b/runtime/zeitgeist/src/xcm_config/asset_registry.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Assets, Balance}; +use crate::{Balance, Currencies}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(Assets, AssetMetadata), DispatchError> { + ) -> Result<(Currencies, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for CustomAs } fn post_register( - _id: Assets, + _id: Currencies, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs index 6eb5389a8..40b91c5ea 100644 --- a/runtime/zeitgeist/src/xcm_config/config.rs +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -53,7 +53,10 @@ use xcm_builder::{ TakeWeightCredit, }; use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; +use zeitgeist_primitives::{ + constants::BalanceFractionalDecimals, + types::{Asset, Currencies}, +}; pub mod zeitgeist { #[cfg(test)] @@ -206,7 +209,7 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, @@ -219,29 +222,41 @@ impl< > { fn adjust_fractional_places(asset: &MultiAsset) -> MultiAsset { - if let Some(ref asset_id) = CurrencyIdConvert::convert(asset.clone()) { - if let Fungible(amount) = asset.fun { - let mut asset_updated = asset.clone(); - let native_decimals = u32::from(FracDecPlaces::get()); - let metadata = AssetRegistry::metadata(asset_id); - - if let Some(metadata) = metadata { - let decimals = metadata.decimals; - - asset_updated.fun = if decimals > native_decimals { - let power = decimals.saturating_sub(native_decimals); - let adjust_factor = 10u128.saturating_pow(power); - // Floors the adjusted token amount, thus no tokens are generated - Fungible(amount.saturating_div(adjust_factor)) - } else { - let power = native_decimals.saturating_sub(decimals); - let adjust_factor = 10u128.saturating_pow(power); - Fungible(amount.saturating_mul(adjust_factor)) - }; - - return asset_updated; + let (asset_id, amount) = + if let Some(ref asset_id) = CurrencyIdConvert::convert(asset.clone()) { + if let Fungible(amount) = asset.fun { + (*asset_id, amount) + } else { + return asset.clone(); } - } + } else { + return asset.clone(); + }; + + let currency = if let Ok(currency) = Currencies::try_from(asset_id) { + currency + } else { + return asset.clone(); + }; + + let metadata = AssetRegistry::metadata(¤cy); + if let Some(metadata) = metadata { + let mut asset_adjusted = asset.clone(); + let decimals = metadata.decimals; + let native_decimals = u32::from(FracDecPlaces::get()); + + asset_adjusted.fun = if decimals > native_decimals { + let power = decimals.saturating_sub(native_decimals); + let adjust_factor = 10u128.saturating_pow(power); + // Floors the adjusted token amount, thus no tokens are generated + Fungible(amount.saturating_div(adjust_factor)) + } else { + let power = native_decimals.saturating_sub(decimals); + let adjust_factor = 10u128.saturating_pow(power); + Fungible(amount.saturating_mul(adjust_factor)) + }; + + return asset_adjusted; } asset.clone() @@ -249,7 +264,7 @@ impl< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, @@ -337,7 +352,10 @@ impl Convert> for AssetConvert { general_key(zeitgeist::KEY), ), )), - Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).ok()?, + Asset::ForeignAsset(_) => { + let currency = Currencies::try_from(id).ok()?; + AssetRegistry::multilocation(¤cy).ok()? + } _ => None, } } @@ -372,9 +390,9 @@ impl xcm_executor::traits::Convert for AssetConvert { return Err(location); } - AssetRegistry::location_to_asset_id(location).ok_or(location) + AssetRegistry::location_to_asset_id(location).ok_or(location).map(|a| a.into()) } - _ => AssetRegistry::location_to_asset_id(location).ok_or(location), + _ => AssetRegistry::location_to_asset_id(location).ok_or(location).map(|a| a.into()), } } } diff --git a/runtime/zeitgeist/src/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs index dc482c336..fbad3601a 100644 --- a/runtime/zeitgeist/src/xcm_config/fees.rs +++ b/runtime/zeitgeist/src/xcm_config/fees.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Assets, Balance}; +use crate::{Balance, Currencies}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -55,7 +55,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = Assets, + AssetId = Currencies, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/zrml/asset-router/Cargo.toml b/zrml/asset-router/Cargo.toml new file mode 100644 index 000000000..a58dfd6cd --- /dev/null +++ b/zrml/asset-router/Cargo.toml @@ -0,0 +1,37 @@ +[dependencies] +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { 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 } +zeitgeist-primitives = { workspace = true } + +[dev-dependencies] +orml-tokens = { workspace = true, features = ["default"] } +pallet-assets = { workspace = true, features = ["default"] } +pallet-balances = { workspace = true, features = ["default"] } +sp-io = { workspace = true, features = ["default"] } +test-case = { workspace = true } +zeitgeist-primitives = { workspace = true, features = ["default", "mock"] } + +[features] +default = ["std"] +runtime-benchmarks = ["pallet-assets/runtime-benchmarks"] +std = [ + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "pallet-assets/std", + "parity-scale-codec/std", + "zeitgeist-primitives/std", +] +try-runtime = [] + +[package] +authors = ["Zeitgeist PM "] +edition = "2021" +name = "zrml-asset-router" +version = "0.4.1" diff --git a/zrml/asset-router/README.md b/zrml/asset-router/README.md new file mode 100644 index 000000000..48a4fd617 --- /dev/null +++ b/zrml/asset-router/README.md @@ -0,0 +1,11 @@ +# Asset Router + +The asset router allows to interact with different asset classes using one +overaching asset class. The caller is not required to be aware of which pallet +handles the asset class of the asset in question, as the asset router internally +routes the call to the appropriate pallet as defined in the pallet's +configuration. It implements various ORML `MultiCurrency` traits as well as +various `Fungible` traits, thus it can be used in other pallets that require +those implementation (such as ORML Currencies). The asset router also provides a +garbage collector for destructible assets, that handles asset destruction +whenever on-chain execution time is available. diff --git a/zrml/asset-router/src/lib.rs b/zrml/asset-router/src/lib.rs new file mode 100644 index 000000000..17a2d934a --- /dev/null +++ b/zrml/asset-router/src/lib.rs @@ -0,0 +1,806 @@ +// 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 . + +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use core::{fmt::Debug, marker::PhantomData}; + use frame_support::{ + pallet_prelude::{DispatchError, DispatchResult, Hooks}, + traits::{ + tokens::{ + fungibles::{Create, Destroy, Inspect, Mutate, Transfer}, + DepositConsequence, WithdrawConsequence, + }, + BalanceStatus as Status, + }, + transactional, Parameter, + }; + use orml_traits::{ + arithmetic::Signed, + currency::{ + MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, + NamedMultiReservableCurrency, TransferAll, + }, + BalanceStatus, LockIdentifier, + }; + use parity_scale_codec::{FullCodec, MaxEncodedLen}; + use scale_info::TypeInfo; + use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, Bounded, MaybeSerializeDeserialize, Member, Saturating, Zero, + }, + FixedPointOperand, + }; + + pub trait AssetTraits: + Create + + Destroy + + Inspect + + Transfer + + Mutate + { + } + + impl AssetTraits for G + where + G: Create + + Destroy + + Inspect + + Transfer + + Mutate, + T: Config, + { + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching asset type that contains all assets classes. + type AssetType: Copy + + Debug + + Eq + + From + + From + + From + + From + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + + /// The type that represents balances. + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen + + FixedPointOperand; + + /// Logic that handles campaign assets by providing multiple fungible + /// trait implementations. + type CampaignAssets: AssetTraits; + /// The custom asset type. + type CampaignAssetType: TryFrom + + Copy + + Debug + + Eq + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + + /// Logic that handles currencies by providing multiple currencies + /// trait implementations. + type Currencies: TransferAll + + MultiCurrencyExtended< + Self::AccountId, + CurrencyId = Self::CurrencyType, + Balance = Self::Balance, + > + MultiLockableCurrency + + MultiReservableCurrency + + NamedMultiReservableCurrency; + /// The currency type. + type CurrencyType: TryFrom + + Copy + + Debug + + Eq + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + + /// Logic that handles custom assets by providing multiple fungible + /// trait implementations. + type CustomAssets: AssetTraits; + /// The custom asset type. + type CustomAssetType: TryFrom + + Copy + + Debug + + Eq + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + + /// Logic that handles market assets by providing multiple fungible + /// trait implementations. + type MarketAssets: AssetTraits; + /// The market asset type. + type MarketAssetType: TryFrom + + Copy + + Debug + + Eq + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + } + + const LOG_TARGET: &str = "runtime::asset-router"; + + #[pallet::hooks] + impl Hooks for Pallet {} + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet {} + + impl TransferAll for Pallet { + #[transactional] + fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { + // Only transfers assets maintained in orml-tokens, not implementable for pallet-assets + >::transfer_all(source, dest) + } + } + + #[pallet::error] + pub enum Error { + /// Cannot convert Amount (MultiCurrencyExtended implementation) into Balance type. + AmountIntoBalanceFailed, + /// Asset conversion failed. + UnknownAsset, + /// Operation is not supported for given asset + Unsupported, + } + + /// This macro converts the invoked asset type into the respective + /// 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),*)) + } 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 { + Err(Error::::UnknownAsset) + } + }; + } + + /// This macro delegates a call to Currencies if the asset represents a currency, otherwise + /// 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) { + >::$currency_method(currency, $($args),+) + } else { + Self::log_unsupported($currency_id, stringify!($currency_method)); + $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) { + >::$asset_method(asset, $($args),*) + } else if let Ok(asset) = T::CampaignAssetType::try_from($asset_id) { + T::CampaignAssets::$asset_method(asset, $($args),*) + } else if let Ok(asset) = T::CustomAssetType::try_from($asset_id) { + T::CustomAssets::$asset_method(asset, $($args),*) + } else { + Self::log_unsupported($asset_id, stringify!($asset_method)); + $error + } + }; + } + + impl Pallet { + #[inline] + fn log_unsupported(asset: T::AssetType, function: &str) { + log::warn!(target: LOG_TARGET, "Asset {:?} not supported in function {:?}", asset, function); + } + } + + impl MultiCurrency for Pallet { + type CurrencyId = T::AssetType; + type Balance = T::Balance; + + fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { + let min_balance = route_call!(currency_id, minimum_balance, minimum_balance,); + min_balance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "minimum_balance"); + Self::Balance::zero() + }) + } + + fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { + let total_issuance = route_call!(currency_id, total_issuance, total_issuance,); + total_issuance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "total_issuance"); + Self::Balance::zero() + }) + } + + fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { + let total_balance = route_call!(currency_id, total_balance, balance, who); + total_balance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "total_balance"); + Self::Balance::zero() + }) + } + + 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) + } 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 { + Self::log_unsupported(currency_id, "free_balance"); + Self::Balance::zero() + } + } + + fn ensure_can_withdraw( + currency_id: Self::CurrencyId, + 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) + } 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 { + return Err(Error::::UnknownAsset.into()); + }; + + withdraw_consequence.into_result().map(|_| ()) + } + + fn transfer( + currency_id: Self::CurrencyId, + from: &T::AccountId, + 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(|_| ()) + } 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 { + Err(Error::::UnknownAsset.into()) + } + } + + fn deposit( + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + route_call!(currency_id, deposit, mint_into, who, amount)? + } + + fn withdraw( + currency_id: Self::CurrencyId, + 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(|_| ()) + } 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 { + 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 + } 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 { + Self::log_unsupported(currency_id, "can_slash"); + false + } + } + + fn slash( + currency_id: Self::CurrencyId, + 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) + } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { + T::CampaignAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) + } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { + T::CustomAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) + } else { + Self::log_unsupported(currency_id, "slash"); + amount + } + } + } + + impl MultiCurrencyExtended for Pallet { + type Amount = >::Amount; + + fn update_balance( + currency_id: Self::CurrencyId, + 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) + } else { + Self::withdraw(currency_id, who, by_balance).map(|_| ()) + } + } + } + + impl MultiLockableCurrency for Pallet { + type Moment = T::BlockNumber; + + fn set_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::set_lock( + lock_id, currency, who, amount, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn extend_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::extend_lock( + lock_id, currency, who, amount, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn remove_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::remove_lock( + lock_id, currency, who, + ); + } + + Err(Error::::Unsupported.into()) + } + } + + impl MultiReservableCurrency for Pallet { + fn can_reserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> bool { + only_currency!(currency_id, false, MultiReservableCurrency, can_reserve, who, value) + } + + fn slash_reserved( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + only_currency!(currency_id, value, MultiReservableCurrency, slash_reserved, who, value) + } + + fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { + only_currency!( + currency_id, + Zero::zero(), + MultiReservableCurrency, + reserved_balance, + who + ) + } + + fn reserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + only_currency!( + currency_id, + Err(Error::::Unsupported.into()), + MultiReservableCurrency, + reserve, + who, + value + ) + } + + fn unreserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + only_currency!(currency_id, value, MultiReservableCurrency, unreserve, who, value) + } + + fn repatriate_reserved( + currency_id: Self::CurrencyId, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> Result { + only_currency!( + currency_id, + Err(Error::::Unsupported.into()), + MultiReservableCurrency, + repatriate_reserved, + slashed, + beneficiary, + value, + status + ) + } + } + + impl NamedMultiReservableCurrency for Pallet { + type ReserveIdentifier = + >::ReserveIdentifier; + + fn reserved_balance_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserved_balance_named( + id, currency, who, + ); + } + + Self::log_unsupported(currency_id, "reserved_balance_named"); + Zero::zero() + } + + fn reserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserve_named( + id, currency, who, value + ); + } + + Err(Error::::Unsupported.into()) + } + + fn unreserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::unreserve_named( + id, currency, who, value + ); + } + + Self::log_unsupported(currency_id, "unreserve_named"); + value + } + + fn slash_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::slash_reserved_named( + id, currency, who, value + ); + } + + Self::log_unsupported(currency_id, "slash_reserved_named"); + value + } + + fn repatriate_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::repatriate_reserved_named( + id, currency, slashed, beneficiary, value, status + ); + } + + Err(Error::::Unsupported.into()) + } + } + + // Supertrait of Create and Destroy + impl Inspect for Pallet { + type AssetId = T::AssetType; + type Balance = T::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + route_call!(asset, total_issuance, total_issuance,).unwrap_or(Zero::zero()) + // only_asset!(asset, Zero::zero(), Inspect, total_issuance,) + } + + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + route_call!(asset, minimum_balance, minimum_balance,).unwrap_or(Zero::zero()) + //only_asset!(asset, Zero::zero(), Inspect, minimum_balance,) + } + + fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { + route_call!(asset, total_balance, balance, who).unwrap_or(Zero::zero()) + //only_asset!(asset, Zero::zero(), Inspect, balance, who) + } + + fn reducible_balance( + asset: Self::AssetId, + who: &T::AccountId, + keep_alive: bool, + ) -> Self::Balance { + if T::CurrencyType::try_from(asset).is_ok() { + >::free_balance(asset, who) + } else { + only_asset!(asset, Zero::zero(), Inspect, reducible_balance, who, keep_alive) + } + } + + fn can_deposit( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + mint: bool, + ) -> DepositConsequence { + if T::CurrencyType::try_from(asset).is_err() { + return only_asset!( + asset, + DepositConsequence::UnknownAsset, + Inspect, + can_deposit, + who, + amount, + mint + ); + } + + let total_balance = >::total_balance(asset, who); + let min_balance = >::minimum_balance(asset); + + if total_balance.saturating_add(amount) < min_balance { + DepositConsequence::BelowMinimum + } else { + DepositConsequence::Success + } + } + + fn can_withdraw( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if T::CurrencyType::try_from(asset).is_err() { + return only_asset!( + asset, + WithdrawConsequence::UnknownAsset, + Inspect, + can_withdraw, + who, + amount + ); + } + + let can_withdraw = + >::ensure_can_withdraw(asset, who, amount); + + if let Err(_e) = can_withdraw { + return WithdrawConsequence::NoFunds; + } + + let total_balance = >::total_balance(asset, who); + let min_balance = >::minimum_balance(asset); + let remainder = total_balance.saturating_sub(amount); + + if remainder < min_balance { + WithdrawConsequence::ReducedToZero(remainder) + } else { + WithdrawConsequence::Success + } + } + + fn asset_exists(asset: Self::AssetId) -> bool { + if T::CurrencyType::try_from(asset).is_ok() { + true + } else { + only_asset!(asset, false, Inspect, asset_exists,) + } + } + } + + impl Create for Pallet { + fn create( + id: Self::AssetId, + admin: T::AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Create, + create, + admin, + is_sufficient, + min_balance + ) + } + } + + impl Destroy for Pallet { + fn start_destroy( + id: Self::AssetId, + maybe_check_owner: Option, + ) -> DispatchResult { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Destroy, + start_destroy, + maybe_check_owner + ) + } + + fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Destroy, + destroy_accounts, + max_items + ) + } + + fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Destroy, + destroy_approvals, + max_items + ) + } + + fn finish_destroy(id: Self::AssetId) -> DispatchResult { + only_asset!(id, Err(Error::::Unsupported.into()), Destroy, finish_destroy,) + } + } +} diff --git a/zrml/asset-router/src/mock.rs b/zrml/asset-router/src/mock.rs new file mode 100644 index 000000000..2e95cec23 --- /dev/null +++ b/zrml/asset-router/src/mock.rs @@ -0,0 +1,280 @@ +// 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 . + +#![cfg(test)] + +extern crate alloc; + +use crate::{self as zrml_asset_router}; +use alloc::{vec, vec::Vec}; +use frame_support::{ + construct_runtime, + traits::{AsEnsureOriginWithArg, Everything}, +}; +use frame_system::EnsureSigned; +use orml_traits::parameter_type_with_key; +use parity_scale_codec::Compact; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, ConstU128, ConstU32, IdentityLookup}, +}; +use zeitgeist_primitives::{ + constants::mock::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves, BASE}, + types::{ + AccountIdTest, Amount, Assets, Balance, BlockNumber, BlockTest, CampaignAsset, + CampaignAssetClass, CampaignAssetId, Currencies, CustomAsset, CustomAssetClass, + CustomAssetId, Hash, Index, MarketAsset, UncheckedExtrinsicTest, + }, +}; + +pub(super) const ALICE: AccountIdTest = 0; +pub(super) const BOB: AccountIdTest = 1; +pub(super) const CHARLIE: AccountIdTest = 2; + +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_INTERNAL: MarketAsset = MarketAsset::CategoricalOutcome(7, 8); +pub(super) const CURRENCY: Assets = Assets::ForeignAsset(0); +pub(super) const CURRENCY_INTERNAL: Currencies = Currencies::ForeignAsset(0); + +pub(super) const CAMPAIGN_ASSET_MIN_BALANCE: Balance = 1; +pub(super) const CUSTOM_ASSET_MIN_BALANCE: Balance = 2; +pub(super) const MARKET_ASSET_MIN_BALANCE: Balance = 3; +pub(super) const CURRENCY_MIN_BALANCE: Balance = 4; + +pub(super) const CAMPAIGN_ASSET_INITIAL_AMOUNT: Balance = 10; +pub(super) const CUSTOM_ASSET_INITIAL_AMOUNT: Balance = 20; +pub(super) const MARKET_ASSET_INITIAL_AMOUNT: Balance = 30; +pub(super) const CURRENCY_INITIAL_AMOUNT: Balance = 40; + +pub(super) type AccountId = ::AccountId; +pub(super) type CustomAssetsInstance = pallet_assets::Instance1; +pub(super) type CampaignAssetsInstance = pallet_assets::Instance2; +pub(super) type MarketAssetsInstance = pallet_assets::Instance3; + +construct_runtime!( + pub enum Runtime + where + Block = BlockTest, + NodeBlock = BlockTest, + UncheckedExtrinsic = UncheckedExtrinsicTest, + { + AssetRouter: zrml_asset_router::{Pallet}, + Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + CustomAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + System: frame_system::{Call, Config, Event, Pallet, Storage}, + Tokens: orml_tokens::{Config, Event, Pallet, Storage}, + } +); + +impl crate::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 MarketAssetType = MarketAsset; + type MarketAssets = MarketAssets; +} + +impl frame_system::Config for Runtime { + type AccountData = pallet_balances::AccountData; + type AccountId = AccountIdTest; + type BaseCallFilter = Everything; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockNumber = BlockNumber; + type BlockWeights = (); + type RuntimeCall = RuntimeCall; + type DbWeight = (); + type RuntimeEvent = RuntimeEvent; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = Index; + type Lookup = IdentityLookup; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type RuntimeOrigin = RuntimeOrigin; + type PalletInfo = PalletInfo; + type SS58Prefix = (); + type SystemWeightInfo = (); + type Version = (); + type OnSetCode = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: Currencies| -> Balance { + CURRENCY_MIN_BALANCE + }; +} + +impl orml_tokens::Config for Runtime { + type Amount = Amount; + type Balance = Balance; + type CurrencyId = Currencies; + type DustRemovalWhitelist = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type CurrencyHooks = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +// Required for runtime benchmarks +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() + } + } +} + +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type AssetDeposit = ConstU128<0>; + 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 = EnsureSigned; + type Freezer = (); + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<255>; + type WeightInfo = (); +} + +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type AssetDeposit = ConstU128<0>; + 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 = EnsureSigned; + type Freezer = (); + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<255>; + type WeightInfo = (); +} + +// Required for runtime benchmarks +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 = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type AssetDeposit = ConstU128<0>; + 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 = EnsureSigned; + type Freezer = (); + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<255>; + type WeightInfo = (); +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +pub struct ExtBuilder { + balances: Vec<(AccountIdTest, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![(ALICE, 1_000 * BASE), (BOB, 1_000 * BASE), (CHARLIE, 1_000 * BASE)] } + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: self.balances } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() + } +} diff --git a/zrml/asset-router/src/tests/create.rs b/zrml/asset-router/src/tests/create.rs new file mode 100644 index 000000000..ec89e1777 --- /dev/null +++ b/zrml/asset-router/src/tests/create.rs @@ -0,0 +1,61 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use frame_support::traits::tokens::fungibles::Inspect; + +#[test] +fn create_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert!(AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + }); +} + +#[test] +fn create_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert!(AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + }); +} + +#[test] +fn create_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert!(AssetRouter::asset_exists(MARKET_ASSET)); + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + }); +} + +#[test] +fn create_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + AssetRouter::create(CURRENCY, ALICE, true, CURRENCY_MIN_BALANCE), + Error::::Unsupported + ); + }); +} diff --git a/zrml/asset-router/src/tests/destroy.rs b/zrml/asset-router/src/tests/destroy.rs new file mode 100644 index 000000000..5896d5180 --- /dev/null +++ b/zrml/asset-router/src/tests/destroy.rs @@ -0,0 +1,101 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use frame_support::traits::tokens::fungibles::Inspect; + +fn destroy_test_helper(asset: Assets, initial_amount: ::Balance) { + assert!(AssetRouter::asset_exists(asset)); + assert_ok!(>::deposit( + asset, + &ALICE, + initial_amount + )); + 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)); + assert_ok!(AssetRouter::finish_destroy(asset)); + assert!(!AssetRouter::asset_exists(asset)); +} + +#[test] +fn destroy_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + assert_ok!( + pallet_assets::Call::::approve_transfer { + id: CAMPAIGN_ASSET_INTERNAL.into(), + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + destroy_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn destroy_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + assert_ok!( + pallet_assets::Call::::approve_transfer { + id: CUSTOM_ASSET_INTERNAL.into(), + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + destroy_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn destroy_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + assert_ok!( + pallet_assets::Call::::approve_transfer { + id: MARKET_ASSET_INTERNAL, + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + destroy_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn destroy_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(>::deposit( + CURRENCY, + &ALICE, + CURRENCY_INITIAL_AMOUNT + )); + assert_noop!(AssetRouter::start_destroy(CURRENCY, None), Error::::Unsupported); + assert_noop!(AssetRouter::destroy_accounts(CURRENCY, 100), Error::::Unsupported); + assert_noop!(AssetRouter::destroy_approvals(CURRENCY, 100), Error::::Unsupported); + assert_noop!(AssetRouter::finish_destroy(CURRENCY), Error::::Unsupported); + }); +} diff --git a/zrml/asset-router/src/tests/inspect.rs b/zrml/asset-router/src/tests/inspect.rs new file mode 100644 index 000000000..7f79845f8 --- /dev/null +++ b/zrml/asset-router/src/tests/inspect.rs @@ -0,0 +1,110 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use frame_support::traits::tokens::fungibles::Inspect; + +fn inspect_test_helper(asset: Assets, initial_amount: ::Balance) { + assert!(AssetRouter::asset_exists(asset)); + + assert_ok!(>::deposit( + asset, + &ALICE, + initial_amount + )); + 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); + assert_eq!( + AssetRouter::can_withdraw(asset, &ALICE, initial_amount), + WithdrawConsequence::ReducedToZero(0) + ); + assert_eq!(AssetRouter::can_deposit(asset, &ALICE, 1, true), DepositConsequence::Success); +} + +#[test] +fn inspect_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use orml_traits::MultiCurrency; + + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + assert_eq!( + >::minimum_balance(CAMPAIGN_ASSET), + CAMPAIGN_ASSET_MIN_BALANCE + ); + inspect_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn inspect_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use orml_traits::MultiCurrency; + + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + assert_eq!( + >::minimum_balance(CUSTOM_ASSET), + CUSTOM_ASSET_MIN_BALANCE + ); + inspect_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn inspect_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use orml_traits::MultiCurrency; + + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + assert_eq!( + >::minimum_balance(MARKET_ASSET), + MARKET_ASSET_MIN_BALANCE + ); + inspect_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn inspect_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(AssetRouter::minimum_balance(CURRENCY), CURRENCY_MIN_BALANCE); + inspect_test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT); + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + }); +} diff --git a/zrml/asset-router/src/tests/mod.rs b/zrml/asset-router/src/tests/mod.rs new file mode 100644 index 000000000..11d9da345 --- /dev/null +++ b/zrml/asset-router/src/tests/mod.rs @@ -0,0 +1,44 @@ +// 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 . + +#![cfg(test)] + +use super::{mock::*, Error}; +use frame_support::{ + assert_noop, assert_ok, + dispatch::RawOrigin::Signed, + traits::{ + tokens::{ + fungibles::{Create, Destroy}, + DepositConsequence, WithdrawConsequence, + }, + UnfilteredDispatchable, + }, +}; +use orml_traits::{ + BalanceStatus, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, + NamedMultiReservableCurrency, +}; +use zeitgeist_primitives::types::Assets; + +mod create; +mod destroy; +mod inspect; +mod multi_currency; +mod multi_lockable_currency; +mod multi_reservable_currency; +mod named_multi_reservable_currency; diff --git a/zrml/asset-router/src/tests/multi_currency.rs b/zrml/asset-router/src/tests/multi_currency.rs new file mode 100644 index 000000000..db88aa981 --- /dev/null +++ b/zrml/asset-router/src/tests/multi_currency.rs @@ -0,0 +1,166 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use orml_traits::MultiCurrency; +use test_case::test_case; +use zeitgeist_primitives::types::{Amount, Balance}; + +fn multicurrency_test_helper( + asset: Assets, + initial_amount: ::Balance, + min_balance: ::Balance, +) { + assert_eq!(AssetRouter::minimum_balance(asset), min_balance); + assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); + assert_eq!(AssetRouter::total_issuance(asset), initial_amount); + assert_eq!(AssetRouter::total_balance(asset, &ALICE), initial_amount); + assert_eq!(AssetRouter::free_balance(asset, &ALICE), initial_amount); + assert_ok!(AssetRouter::ensure_can_withdraw(asset, &ALICE, initial_amount)); + assert!(AssetRouter::ensure_can_withdraw(asset, &ALICE, initial_amount + 1).is_err()); + assert_ok!(AssetRouter::transfer(asset, &ALICE, &BOB, min_balance)); + assert_eq!(AssetRouter::free_balance(asset, &BOB), min_balance); + assert_eq!(AssetRouter::free_balance(asset, &ALICE), initial_amount - min_balance); + assert_ok!(AssetRouter::withdraw(asset, &ALICE, 1)); + assert_eq!(AssetRouter::free_balance(asset, &ALICE), initial_amount - min_balance - 1); + assert!(AssetRouter::can_slash(asset, &ALICE, 1)); + assert_eq!(AssetRouter::slash(asset, &ALICE, 1), 0); + assert_eq!(AssetRouter::free_balance(asset, &ALICE), initial_amount - min_balance - 2); + assert_ok!(AssetRouter::update_balance( + asset, + &ALICE, + >::Amount::from(1u8) + - >::Amount::from(2u8) + )); + assert_eq!(AssetRouter::free_balance(asset, &ALICE), initial_amount - min_balance - 3); +} + +#[test] +fn multicurrency_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use frame_support::traits::tokens::fungibles::Inspect; + + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + + multicurrency_test_helper( + CAMPAIGN_ASSET, + CAMPAIGN_ASSET_INITIAL_AMOUNT, + CAMPAIGN_ASSET_MIN_BALANCE, + ); + + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn multicurrency_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use frame_support::traits::tokens::fungibles::Inspect; + + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE)); + + multicurrency_test_helper( + CUSTOM_ASSET, + CUSTOM_ASSET_INITIAL_AMOUNT, + CUSTOM_ASSET_MIN_BALANCE, + ); + + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn multicurrency_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + use frame_support::traits::tokens::fungibles::Inspect; + + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE)); + + multicurrency_test_helper( + MARKET_ASSET, + MARKET_ASSET_INITIAL_AMOUNT, + MARKET_ASSET_MIN_BALANCE, + ); + + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); + }); +} + +#[test] +fn multicurrency_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + use frame_support::traits::tokens::fungibles::Inspect; + + multicurrency_test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); + + assert_eq!( + >::total_issuance(CAMPAIGN_ASSET_INTERNAL), + 0 + ); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); + }); +} + +#[test_case(0, Some(0); "zero")] +#[test_case(Amount::max_value(), Some(Amount::max_value().unsigned_abs() as Balance); "max")] +#[test_case(Amount::min_value(), None; "min")] +#[test_case(Amount::min_value() + 1, Some((Amount::min_value() + 1).unsigned_abs() as Balance); "min_plus_one")] +fn multicurrency_update_balance_handles_overflows_correctly( + update: Amount, + expected: Option, +) { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + + if update.is_negative() { + assert_ok!(AssetRouter::update_balance(CAMPAIGN_ASSET, &ALICE, Amount::max_value())); + } + + if let Some(expected_inner) = expected { + assert_ok!(AssetRouter::update_balance(CAMPAIGN_ASSET, &ALICE, update)); + + if update.is_negative() { + assert_eq!( + AssetRouter::free_balance(CAMPAIGN_ASSET, &ALICE), + Amount::max_value() as Balance - expected_inner + ); + } else { + assert_eq!(AssetRouter::free_balance(CAMPAIGN_ASSET, &ALICE), expected_inner); + } + } else { + assert_noop!( + AssetRouter::update_balance(CAMPAIGN_ASSET, &ALICE, update), + Error::::AmountIntoBalanceFailed + ); + } + }); +} diff --git a/zrml/asset-router/src/tests/multi_lockable_currency.rs b/zrml/asset-router/src/tests/multi_lockable_currency.rs new file mode 100644 index 000000000..587b9a40b --- /dev/null +++ b/zrml/asset-router/src/tests/multi_lockable_currency.rs @@ -0,0 +1,97 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use orml_traits::MultiCurrency; + +fn multi_lockable_currency_unroutable_test_helper(asset: Assets) { + assert_noop!( + AssetRouter::set_lock(Default::default(), asset, &ALICE, 1), + Error::::Unsupported + ); + assert_noop!( + AssetRouter::extend_lock(Default::default(), asset, &ALICE, 1), + Error::::Unsupported + ); + assert_noop!( + AssetRouter::remove_lock(Default::default(), asset, &ALICE), + Error::::Unsupported + ); +} + +#[test] +fn multi_lockable_currency_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_ok!(AssetRouter::set_lock(Default::default(), CURRENCY, &ALICE, 1)); + assert_eq!( + orml_tokens::Accounts::::get::< + u128, + ::CurrencyId, + >(ALICE, Default::default()) + .frozen, + 1 + ); + assert_ok!(AssetRouter::extend_lock(Default::default(), CURRENCY, &ALICE, 2)); + assert_eq!( + orml_tokens::Accounts::::get::< + u128, + ::CurrencyId, + >(ALICE, Default::default()) + .frozen, + 2 + ); + assert_ok!(AssetRouter::remove_lock(Default::default(), CURRENCY, &ALICE)); + assert_eq!( + orml_tokens::Accounts::::get::< + u128, + ::CurrencyId, + >(ALICE, Default::default()) + .frozen, + 0 + ); + }); +} + +#[test] +fn multi_lockable_currency_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + multi_lockable_currency_unroutable_test_helper(CAMPAIGN_ASSET); + }); +} + +#[test] +fn multi_lockable_currency_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + multi_lockable_currency_unroutable_test_helper(CUSTOM_ASSET); + }); +} + +#[test] +fn multi_lockable_currency_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + multi_lockable_currency_unroutable_test_helper(MARKET_ASSET); + }); +} diff --git a/zrml/asset-router/src/tests/multi_reservable_currency.rs b/zrml/asset-router/src/tests/multi_reservable_currency.rs new file mode 100644 index 000000000..9f8e9333f --- /dev/null +++ b/zrml/asset-router/src/tests/multi_reservable_currency.rs @@ -0,0 +1,110 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use orml_traits::MultiCurrency; + +fn multi_reserveable_currency_unroutable_test_helper( + asset: Assets, + initial_amount: ::Balance, +) { + assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); + assert!(!AssetRouter::can_reserve(asset, &ALICE, initial_amount)); + assert_noop!( + AssetRouter::reserve(asset, &ALICE, initial_amount), + Error::::Unsupported + ); + assert_eq!(AssetRouter::reserved_balance(asset, &ALICE), 0); + assert_eq!(AssetRouter::slash_reserved(asset, &ALICE, 1), 1); + assert_noop!( + AssetRouter::repatriate_reserved(asset, &ALICE, &BOB, 1, BalanceStatus::Reserved), + Error::::Unsupported + ); + assert_eq!(AssetRouter::unreserve(asset, &ALICE, 1), 1); +} + +#[test] +fn multi_reserveable_currency_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + + assert!(AssetRouter::can_reserve(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert!(!AssetRouter::can_reserve(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT + 1)); + assert_ok!(AssetRouter::reserve(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_eq!(AssetRouter::reserved_balance(CURRENCY, &ALICE), CURRENCY_INITIAL_AMOUNT); + assert_eq!(AssetRouter::slash_reserved(CURRENCY, &ALICE, 1), 0); + assert_eq!( + AssetRouter::repatriate_reserved( + CURRENCY, + &ALICE, + &BOB, + CURRENCY_MIN_BALANCE, + BalanceStatus::Reserved + ) + .unwrap(), + 0 + ); + assert_eq!(AssetRouter::reserved_balance(CURRENCY, &BOB), CURRENCY_MIN_BALANCE); + assert_eq!( + AssetRouter::reserved_balance(CURRENCY, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 1 + ); + assert_eq!(AssetRouter::unreserve(CURRENCY, &ALICE, 1), 0); + assert_eq!( + AssetRouter::reserved_balance(CURRENCY, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 2 + ); + }); +} + +#[test] +fn multi_reserveable_currency_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + multi_reserveable_currency_unroutable_test_helper( + CAMPAIGN_ASSET, + CAMPAIGN_ASSET_INITIAL_AMOUNT, + ); + }); +} + +#[test] +fn multi_reserveable_currency_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + multi_reserveable_currency_unroutable_test_helper( + CUSTOM_ASSET, + CUSTOM_ASSET_INITIAL_AMOUNT, + ); + }); +} + +#[test] +fn multi_reserveable_currency_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + multi_reserveable_currency_unroutable_test_helper( + MARKET_ASSET, + MARKET_ASSET_INITIAL_AMOUNT, + ); + }); +} diff --git a/zrml/asset-router/src/tests/named_multi_reservable_currency.rs b/zrml/asset-router/src/tests/named_multi_reservable_currency.rs new file mode 100644 index 000000000..b0096c8aa --- /dev/null +++ b/zrml/asset-router/src/tests/named_multi_reservable_currency.rs @@ -0,0 +1,118 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use orml_traits::MultiCurrency; + +fn named_multi_reserveable_currency_unroutable_test_helper( + asset: Assets, + initial_amount: ::Balance, +) { + assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); + assert_noop!( + AssetRouter::reserve_named(&Default::default(), asset, &ALICE, initial_amount), + Error::::Unsupported + ); + assert_eq!(AssetRouter::slash_reserved_named(&Default::default(), asset, &ALICE, 1), 1); + assert_noop!( + AssetRouter::repatriate_reserved_named( + &Default::default(), + asset, + &ALICE, + &BOB, + 1, + BalanceStatus::Reserved + ), + Error::::Unsupported + ); + assert_eq!(AssetRouter::unreserve_named(&Default::default(), asset, &ALICE, 1), 1); +} + +#[test] +fn named_multi_reserveable_currency_routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_ok!(AssetRouter::reserve_named( + &Default::default(), + CURRENCY, + &ALICE, + CURRENCY_INITIAL_AMOUNT + )); + assert_eq!(AssetRouter::reserved_balance(CURRENCY, &ALICE), CURRENCY_INITIAL_AMOUNT); + assert_eq!(AssetRouter::slash_reserved_named(&Default::default(), CURRENCY, &ALICE, 1), 0); + assert_eq!( + AssetRouter::repatriate_reserved_named( + &Default::default(), + CURRENCY, + &ALICE, + &BOB, + CURRENCY_MIN_BALANCE, + BalanceStatus::Reserved + ) + .unwrap(), + 0 + ); + assert_eq!(AssetRouter::reserved_balance(CURRENCY, &BOB), CURRENCY_MIN_BALANCE); + assert_eq!( + AssetRouter::reserved_balance(CURRENCY, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 1 + ); + assert_eq!(AssetRouter::unreserve_named(&Default::default(), CURRENCY, &ALICE, 1), 0); + assert_eq!( + AssetRouter::reserved_balance(CURRENCY, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 2 + ); + }); +} + +#[test] +fn named_multi_reserveable_currency_routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + named_multi_reserveable_currency_unroutable_test_helper( + CAMPAIGN_ASSET, + CAMPAIGN_ASSET_INITIAL_AMOUNT, + ); + }); +} + +#[test] +fn named_multi_reserveable_currency_routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + named_multi_reserveable_currency_unroutable_test_helper( + CUSTOM_ASSET, + CUSTOM_ASSET_INITIAL_AMOUNT, + ); + }); +} + +#[test] +fn named_multi_reserveable_currency_routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + named_multi_reserveable_currency_unroutable_test_helper( + MARKET_ASSET, + MARKET_ASSET_INITIAL_AMOUNT, + ); + }); +} diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 64a9ab6a8..1c689690d 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -63,8 +63,8 @@ use zeitgeist_primitives::{ }, traits::{DeployPoolApi, DistributeFees}, types::{ - AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, - Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, + Currencies, Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, }, }; use zrml_neo_swaps::BalanceOf; @@ -462,7 +462,7 @@ impl pallet_treasury::Config for Runtime { #[cfg(feature = "parachain")] zrml_prediction_markets::impl_mock_registry! { MockRegistry, - Assets, + Currencies, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -501,7 +501,7 @@ impl ExtBuilder { #[cfg(feature = "parachain")] orml_asset_registry_mock::GenesisConfig { metadata: vec![( - FOREIGN_ASSET, + Currencies::try_from(FOREIGN_ASSET).unwrap(), AssetMetadata { decimals: 18, name: "MKL".as_bytes().to_vec(), diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index d88f30b77..230289b06 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -21,6 +21,7 @@ zrml-simple-disputes = { workspace = 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 } @@ -29,6 +30,7 @@ sp-api = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } substrate-fixed = { workspace = true, optional = true } xcm = { workspace = true, optional = true } +zrml-asset-router = { workspace = true, optional = true } zrml-prediction-markets-runtime-api = { workspace = true, optional = true } zrml-rikiddo = { workspace = true, optional = true } zrml-swaps = { workspace = true, optional = true } @@ -41,8 +43,10 @@ zrml-prediction-markets = { workspace = true, features = ["mock", "default"] } [features] default = ["std"] mock = [ + "orml-asset-registry/default", "orml-currencies/default", "orml-tokens/default", + "pallet-assets/default", "pallet-balances", "pallet-randomness-collective-flip/default", "pallet-timestamp/default", @@ -52,11 +56,11 @@ mock = [ "sp-io/default", "substrate-fixed", "zeitgeist-primitives/mock", + "zrml-asset-router/default", "zrml-prediction-markets-runtime-api/default", "zrml-rikiddo/default", "zrml-swaps/default", "xcm/default", - "orml-asset-registry/default", ] parachain = [] runtime-benchmarks = [ @@ -64,6 +68,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "orml-asset-registry?/runtime-benchmarks", + "pallet-assets?/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "zeitgeist-primitives/mock", ] diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index e85cbb49e..3c17c40d3 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. @@ -52,7 +52,10 @@ mod pallet { use frame_system::{ensure_signed, pallet_prelude::OriginFor}; #[cfg(feature = "parachain")] - use {orml_traits::asset_registry::Inspect, zeitgeist_primitives::types::CustomMetadata}; + use { + orml_traits::asset_registry::Inspect, + zeitgeist_primitives::types::{CurrencyClass, CustomMetadata}, + }; use orml_traits::{MultiCurrency, NamedMultiReservableCurrency}; use sp_arithmetic::per_things::{Perbill, Percent}; @@ -64,7 +67,7 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, Swaps, ZeitgeistAssetManager, + DisputeResolutionApi, Swaps, }, types::{ Asset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, Market, @@ -81,6 +84,7 @@ mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); const LOG_TARGET: &str = "runtime::zrml-prediction-markets"; + pub(crate) type AssetOf = Asset>; pub(crate) type BalanceOf = ::Balance; pub(crate) type AccountIdOf = ::AccountId; pub(crate) type NegativeImbalanceOf = @@ -1704,16 +1708,17 @@ mod pallet { type ApproveOrigin: EnsureOrigin; /// Shares of outcome assets and native currency - type AssetManager: ZeitgeistAssetManager< + type AssetManager: MultiCurrency, CurrencyId = AssetOf> + + NamedMultiReservableCurrency< Self::AccountId, Balance = BalanceOf, - CurrencyId = Asset>, + CurrencyId = AssetOf, ReserveIdentifier = [u8; 8], >; #[cfg(feature = "parachain")] type AssetRegistry: Inspect< - AssetId = Asset>, + AssetId = CurrencyClass>, Balance = BalanceOf, CustomMetadata = CustomMetadata, >; @@ -3442,7 +3447,11 @@ mod pallet { Asset::Ztg => true, #[cfg(feature = "parachain")] Asset::ForeignAsset(fa) => { - if let Some(metadata) = T::AssetRegistry::metadata(&Asset::ForeignAsset(fa)) { + if let Some(metadata) = + T::AssetRegistry::metadata(&CurrencyClass::>::ForeignAsset( + fa, + )) + { metadata.additional.allow_as_base_asset } else { return Err(Error::::UnregisteredForeignAsset.into()); diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index fcb9f71fa..512c3763e 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -25,26 +25,29 @@ use crate as prediction_markets; use frame_support::{ construct_runtime, ord_parameter_types, parameter_types, - traits::{Everything, NeverEnsureOrigin, OnFinalize, OnInitialize}, + traits::{AsEnsureOriginWithArg, Everything, NeverEnsureOrigin, OnFinalize, OnInitialize}, }; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; #[cfg(feature = "parachain")] use orml_asset_registry::AssetMetadata; +use parity_scale_codec::Compact; use sp_arithmetic::per_things::Percent; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, ConstU32, IdentityLookup}, DispatchError, DispatchResult, }; use std::cell::RefCell; use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{ constants::mock::{ - AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, + AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AssetsAccountDeposit, + AssetsApprovalDeposit, AssetsDeposit, AssetsMetadataDepositBase, + AssetsMetadataDepositPerByte, AssetsStringLimit, AuthorizedPalletId, BalanceFractionalDecimals, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, - CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDeposits, ExitFee, + CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDepositsNew, ExitFee, GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, MaxApprovals, MaxAssets, MaxCategories, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, @@ -59,8 +62,9 @@ use zeitgeist_primitives::{ }, traits::DeployPoolApi, types::{ - AccountIdTest, Amount, Asset, Assets, Balance, BasicCurrencyAdapter, BlockNumber, - BlockTest, Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, + CampaignAsset, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, Hash, Index, + MarketAsset, MarketId, Moment, PoolId, UncheckedExtrinsicTest, }, }; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; @@ -157,6 +161,10 @@ parameter_types! { pub const DisputeBond: Balance = 109 * CENT; } +type CustomAssetsInstance = pallet_assets::Instance1; +type CampaignAssetsInstance = pallet_assets::Instance2; +type MarketAssetsInstance = pallet_assets::Instance3; + construct_runtime!( pub enum Runtime where @@ -164,11 +172,15 @@ construct_runtime!( NodeBlock = BlockTest, UncheckedExtrinsic = UncheckedExtrinsicTest, { + AssetRouter: zrml_asset_router::{Pallet}, Authorized: zrml_authorized::{Event, Pallet, Storage}, Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event}, + CustomAssets: pallet_assets::::{Call, Pallet, Storage, Event}, Court: zrml_court::{Event, Pallet, Storage}, AssetManager: orml_currencies::{Call, Pallet, Storage}, LiquidityMining: zrml_liquidity_mining::{Config, Event, Pallet}, + MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event}, MarketCommons: zrml_market_commons::{Pallet, Storage}, PredictionMarkets: prediction_markets::{Event, Pallet, Storage}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, @@ -263,7 +275,7 @@ impl frame_system::Config for Runtime { impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; + type MultiCurrency = AssetRouter; type NativeCurrency = BasicCurrencyAdapter; type WeightInfo = (); } @@ -271,10 +283,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 = (); @@ -285,7 +297,7 @@ impl orml_tokens::Config for Runtime { #[cfg(feature = "parachain")] crate::orml_asset_registry::impl_mock_registry! { MockRegistry, - Assets, + Currencies, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -315,6 +327,116 @@ ord_parameter_types! { pub const AuthorizedDisputeResolutionUser: AccountIdTest = ALICE; } +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 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 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 MetadataDepositBase = AssetsMetadataDepositBase; + type MetadataDepositPerByte = AssetsMetadataDepositPerByte; + type RemoveItemsLimit = ConstU32<50>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = AssetsStringLimit; + 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 MarketAssetType = MarketAsset; + type MarketAssets = MarketAssets; +} + impl zrml_authorized::Config for Runtime { type AuthorizedDisputeResolutionOrigin = EnsureSignedBy; @@ -488,7 +610,7 @@ impl ExtBuilder { #[cfg(feature = "parachain")] orml_tokens::GenesisConfig:: { balances: (0..69) - .map(|idx| (idx, Assets::ForeignAsset(100), INITIAL_BALANCE)) + .map(|idx| (idx, Currencies::ForeignAsset(100), INITIAL_BALANCE)) .collect(), } .assimilate_storage(&mut t) @@ -502,7 +624,7 @@ impl ExtBuilder { orml_asset_registry_mock::GenesisConfig { metadata: vec![ ( - Assets::ForeignAsset(100), + Currencies::ForeignAsset(100), AssetMetadata { decimals: 18, name: "ACALA USD".as_bytes().to_vec(), @@ -513,7 +635,7 @@ impl ExtBuilder { }, ), ( - Assets::ForeignAsset(420), + Currencies::ForeignAsset(420), AssetMetadata { decimals: 18, name: "FANCY_TOKEN".as_bytes().to_vec(), @@ -558,8 +680,8 @@ pub fn set_timestamp_for_on_initialize(time: Moment) { sp_api::mock_impl_runtime_apis! { impl zrml_prediction_markets_runtime_api::PredictionMarketsApi, MarketId, Hash> for Runtime { - fn market_outcome_share_id(_: MarketId, _: u16) -> Asset { - Asset::PoolShare(1) + fn market_outcome_share_id(_: MarketId, _: u16) -> Assets { + Assets::PoolShare(1) } } } diff --git a/zrml/prediction-markets/src/tests.rs b/zrml/prediction-markets/src/tests.rs index 0fbe22b84..89cd5a80d 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. @@ -1535,7 +1535,7 @@ fn it_allows_to_buy_a_complete_set() { // Check the outcome balances let assets = PredictionMarkets::outcome_assets(0, &market); for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); + let bal = AssetManager::free_balance(*asset, &BOB); assert_eq!(bal, CENT); } @@ -1817,7 +1817,7 @@ fn it_allows_to_sell_a_complete_set() { // Check the outcome balances let assets = PredictionMarkets::outcome_assets(0, &market); for asset in assets.iter() { - let bal = Tokens::free_balance(*asset, &BOB); + let bal = AssetManager::free_balance(*asset, &BOB); assert_eq!(bal, 0); } @@ -3312,22 +3312,22 @@ fn it_correctly_resolves_a_market_that_was_reported_on() { // Check balance of winning outcome asset. let share_b = Asset::CategoricalOutcome(0, 1); - let share_b_total = Tokens::total_issuance(share_b); + let share_b_total = AssetManager::total_issuance(share_b); assert_eq!(share_b_total, CENT); - let share_b_bal = Tokens::free_balance(share_b, &CHARLIE); + 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 = Tokens::total_issuance(share_a); + let share_a_total = AssetManager::total_issuance(share_a); assert_eq!(share_a_total, CENT); - let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); + let share_a_bal = AssetManager::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); + let share_c_total = AssetManager::total_issuance(share_c); assert_eq!(share_c_total, 0); - let share_c_bal = Tokens::free_balance(share_c, &CHARLIE); + let share_c_bal = AssetManager::free_balance(share_c, &CHARLIE); assert_eq!(share_c_bal, 0); assert!(market.bonds.creation.unwrap().is_settled); @@ -4034,15 +4034,27 @@ fn create_market_and_deploy_assets_results_in_expected_balances_and_pool_params( 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(Asset::CategoricalOutcome(0, 0), &ALICE), 0); + assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 1), &ALICE), 0); + assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 2), &ALICE), 0); + assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 3), &ALICE), 0); + + assert_eq!( + AssetManager::free_balance(Asset::CategoricalOutcome(0, 0), &pool_account), + amount + ); + assert_eq!( + AssetManager::free_balance(Asset::CategoricalOutcome(0, 1), &pool_account), + amount + ); + assert_eq!( + AssetManager::free_balance(Asset::CategoricalOutcome(0, 2), &pool_account), + amount + ); + assert_eq!( + AssetManager::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(); @@ -4449,7 +4461,7 @@ fn full_scalar_market_lifecycle() { 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); + let bal = AssetManager::free_balance(*asset, &CHARLIE); assert_eq!(bal, 100 * BASE); } let market = MarketCommons::market(&0).unwrap(); @@ -4492,7 +4504,7 @@ fn full_scalar_market_lifecycle() { assert_eq!(disputes.len(), 0); // give EVE some shares - assert_ok!(Tokens::transfer( + assert_ok!(AssetManager::transfer( RuntimeOrigin::signed(CHARLIE), EVE, Asset::ScalarOutcome(0, ScalarPosition::Short), @@ -4500,13 +4512,13 @@ fn full_scalar_market_lifecycle() { )); assert_eq!( - Tokens::free_balance(Asset::ScalarOutcome(0, ScalarPosition::Short), &CHARLIE), + AssetManager::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); + let bal = AssetManager::free_balance(*asset, &CHARLIE); assert_eq!(bal, 0); } @@ -6372,7 +6384,7 @@ fn scalar_market_correctly_resolves_common(base_asset: Asset, reported ScoringRule::CPMM, ); assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); - assert_ok!(Tokens::transfer( + assert_ok!(AssetManager::transfer( RuntimeOrigin::signed(CHARLIE), EVE, Asset::ScalarOutcome(0, ScalarPosition::Short), diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 84cdca827..03ea77f38 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -2,6 +2,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +orml-tokens = { workspace = true } orml-traits = { workspace = true } parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } scale-info = { workspace = true, features = ["derive"] } @@ -15,7 +16,6 @@ zrml-rikiddo = { workspace = true } # Mock orml-currencies = { workspace = true, optional = true } -orml-tokens = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } pallet-timestamp = { workspace = true, optional = true } sp-api = { workspace = true, optional = true } @@ -32,7 +32,6 @@ zrml-swaps = { workspace = true, features = ["mock"] } default = ["std"] mock = [ "orml-currencies/default", - "orml-tokens/default", "pallet-balances/default", "pallet-timestamp/default", "sp-api/default", @@ -50,6 +49,7 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "orml-tokens/default", "orml-traits/std", "parity-scale-codec/std", "sp-runtime/std", diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 3fa287d89..5550a4a21 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -91,7 +91,7 @@ mod pallet { }; use zeitgeist_primitives::{ constants::{BASE, CENT}, - traits::{MarketCommonsPalletApi, Swaps, ZeitgeistAssetManager}, + traits::{MarketCommonsPalletApi, Swaps}, types::{ Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, ScoringRule, @@ -872,7 +872,11 @@ mod pallet { >; /// Shares of outcome assets and native currency - type AssetManager: ZeitgeistAssetManager>>; + type AssetManager: MultiReservableCurrency< + Self::AccountId, + // Balance = BalanceOf, + CurrencyId = Asset>, + >; /// The weight information for swap's dispatchable functions. type WeightInfo: WeightInfoZeitgeist; @@ -1183,8 +1187,16 @@ mod pallet { }; // Iterate through every share holder and exchange shares for rewards. + // TODO(#1199): Remove Rikiddo. Since Rikiddo is not used in production, + // No share holders can be assumed here for now. + /* let (total_accounts_num, share_accounts) = T::AssetManager::accounts_by_currency_id(shares_id).unwrap_or((0usize, vec![])); + */ + let (total_accounts_num, share_accounts): ( + usize, + Vec<(T::AccountId, orml_tokens::AccountData>)>, + ) = (0usize, vec![]); let share_accounts_num = share_accounts.len(); for share_holder in share_accounts { diff --git a/zrml/swaps/src/tests.rs b/zrml/swaps/src/tests.rs index 4ee87499e..ba00ca8b6 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. @@ -468,6 +468,7 @@ fn destroy_pool_in_subsidy_phase_returns_subsidy_and_closes_pool() { }); } +/* #[test] fn distribute_pool_share_rewards() { ExtBuilder::default().build().execute_with(|| { @@ -537,6 +538,7 @@ fn distribute_pool_share_rewards() { assert!(winner_payout_acc_balance - total_winning_assets < BASE / 1_000_000); }); } +*/ #[test] fn end_subsidy_phase_distributes_shares_and_outcome_assets() { From e5e44edb3731a1af992e03fa79501b32d7d21dc6 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 9 Feb 2024 14:46:10 +0100 Subject: [PATCH 04/14] New Asset System - Add managed asset destruction (#1201) * Add new asset types * Add custom assets to runtime * Add market assets to runtime * Add pallet_assets benchmark and weights * Expose MarketAssets call enum * Update todo comment to incluse issue number * Add campaign assets instance to runtime * Cargo fmt * Taplo fmt * Refine asset class types * Use efficient asset types * Add all variants to overarching Asset enum * Make MarketId compactable * Adjust SerdeWrapper Soon to be deprecated * Make Battery Station compileable * Make Zeitgeist compileable * Cleanup code * Remove NewForeignAsset Conversion to compact encoding implies massive migration effort * Implement asset type conversions * Add Currency asset class * Remove deprecated SerdeWrapper * Add Currencies asset class Also renames existing CurrencyId to Assets * Add scale codec index matching tests * Add asset conversion tests * Update docstring * Improve assets module structure * Update license * Create asset-router pallet scaffold * Start implementing all traits * Implement MultiCurrency partially for asset-router * Implement MultiCurrency for asset-router * Implement MultiCurrencyExtended * MultiLockableCurrency * Implement MultiReservableCurrency * Implement NamedMultiReservableCurrency * Fix runtime * Integrate asset-router in runtime * Fix a couple of bugs * Prepare asset-router test environment * Start MultiCurrency test impl * Complete MultiCurrency tests * Add MultiCurrencyExtended tests * Implement MultiReserveableCurrency tests * Implement NamedMultiReserveableCurrency tests * Implement MultiLockableCurrency tests * Improve test structure * Undo unnecessary change * Format code * Implement fungibles::{Create, Destroy, Inspect} * Remove comment * Add tests for Inspect impl * Add tests for Create impl * Add tests for Destroy impl * Make asset types configurable * Use less restricitve traits for pm AssetManager * Make project compilable * Create volatile assets in PM * Start adjustment of PM tests * Add ManagedDestroy trait * Implement managed_destroy in asset-router * Implement ManagedDestroy trait * Implement on_idle asset destruction * Finalize managed asset destruction * Finalize managed asset destruction * Handle duplicate destruction attempts * Fix reverse logic * Add managed destroy add asset test * Add managed destroy multi add asset test * Add destroy all assets on_idle tests * Complete managed destroy tests * Update AssetRouter config in runtime * Repair PM mock * Satisfy Clippy * Use modified pallet-assets * Use modified pallet-assets The modified variant provides an additional trait and Config type, that are used to automate asset destruction * Remove comments * Remove empty line * Mostly improve asset destruction code * Finalize on_idle hook and adjust tests * Improve project structure * Rename files * Add tests for custom types * Satisfy CI * Apply suggestions from code review Co-authored-by: Chralt * Use common function for impl ManagedDestroy * Improve AssetInDestruction ordering & add test * Format code * Use require_transactional * Use ensure! * Use AssetIndestructible error and fix tests * Don't execute on_idle if weight not enough to read storage * Use DestroyApprovalWeight for approval destruction Co-authored-by: Malte Kliemann * Reduce available weight to 0 when destroying accounts/approvals errors. Co-authored-by: Malte Kliemann * Use more precise return type for handle_destroy functions * Fix destruction state machine loop condition Co-authored-by: Malte Kliemann * Log warning / test assert when loop safety guard is triggered * Add outer asset destruction loop safety guard * Revert loop condition overwrite * Elaborate on managed asset destruction in readme * Improved weight guards + tests Also reduce the maximum storage proof size required, as it previously was 1/8 of the maximum PoV size per block on Polkadot * Test that approvals are also destroyed * Use constant for min extra computation time in on_idle * Add more tests * Add and use unreachable_non_terminating macro * Refractor code * Satisfy Clippy * Fix unreachable_non_terminating macro --------- Co-authored-by: Chralt Co-authored-by: Malte Kliemann --- Cargo.lock | 2 +- Cargo.toml | 1 + primitives/src/constants/mock.rs | 9 +- primitives/src/lib.rs | 1 + primitives/src/macros.rs | 66 ++ primitives/src/traits.rs | 2 + primitives/src/traits/weights.rs | 60 ++ runtime/battery-station/src/parameters.rs | 11 +- runtime/common/src/lib.rs | 12 +- runtime/zeitgeist/src/parameters.rs | 11 +- zrml/asset-router/Cargo.toml | 1 - zrml/asset-router/README.md | 21 +- zrml/asset-router/src/lib.rs | 912 ++++++------------ zrml/asset-router/src/macros.rs | 103 ++ zrml/asset-router/src/mock.rs | 27 +- zrml/asset-router/src/pallet_impl/create.rs | 37 + zrml/asset-router/src/pallet_impl/destroy.rs | 42 + zrml/asset-router/src/pallet_impl/inspect.rs | 118 +++ .../src/pallet_impl/managed_destroy.rs | 74 ++ zrml/asset-router/src/pallet_impl/mod.rs | 27 + .../pallet_impl/multi_lockable_currency.rs | 66 ++ .../pallet_impl/multi_reserveable_currency.rs | 82 ++ .../src/pallet_impl/multicurrency.rs | 173 ++++ .../src/pallet_impl/multicurrency_extended.rs | 53 + .../named_multi_reserveable_currency.rs | 102 ++ .../src/pallet_impl/transfer_all.rs | 26 + zrml/asset-router/src/tests/create.rs | 8 +- zrml/asset-router/src/tests/custom_types.rs | 86 ++ zrml/asset-router/src/tests/destroy.rs | 16 +- zrml/asset-router/src/tests/inspect.rs | 19 +- .../asset-router/src/tests/managed_destroy.rs | 337 +++++++ zrml/asset-router/src/tests/mod.rs | 5 +- zrml/asset-router/src/tests/multi_currency.rs | 35 +- .../src/tests/multi_lockable_currency.rs | 16 +- .../src/tests/multi_reservable_currency.rs | 28 +- .../tests/named_multi_reservable_currency.rs | 28 +- zrml/asset-router/src/types.rs | 120 +++ zrml/neo-swaps/src/mock.rs | 6 +- zrml/prediction-markets/Cargo.toml | 2 +- zrml/prediction-markets/src/mock.rs | 31 +- 40 files changed, 2050 insertions(+), 726 deletions(-) create mode 100644 primitives/src/macros.rs create mode 100644 primitives/src/traits/weights.rs create mode 100644 zrml/asset-router/src/macros.rs create mode 100644 zrml/asset-router/src/pallet_impl/create.rs create mode 100644 zrml/asset-router/src/pallet_impl/destroy.rs create mode 100644 zrml/asset-router/src/pallet_impl/inspect.rs create mode 100644 zrml/asset-router/src/pallet_impl/managed_destroy.rs create mode 100644 zrml/asset-router/src/pallet_impl/mod.rs create mode 100644 zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs create mode 100644 zrml/asset-router/src/pallet_impl/multi_reserveable_currency.rs create mode 100644 zrml/asset-router/src/pallet_impl/multicurrency.rs create mode 100644 zrml/asset-router/src/pallet_impl/multicurrency_extended.rs create mode 100644 zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs create mode 100644 zrml/asset-router/src/pallet_impl/transfer_all.rs create mode 100644 zrml/asset-router/src/tests/custom_types.rs create mode 100644 zrml/asset-router/src/tests/managed_destroy.rs create mode 100644 zrml/asset-router/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index c677897cd..5315217cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5863,7 +5863,7 @@ dependencies = [ [[package]] name = "pallet-assets" version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.38#bcff60a227d455d95b4712b6cb356ce56b1ff672" +source = "git+https://github.com/zeitgeistpm/substrate.git?branch=polkadot-v0.9.38-assets-managed-destroy#135cfbdb68429e2d860f5054463a2a094908eeb4" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/Cargo.toml b/Cargo.toml index 192cddebf..12bb0f272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,6 +330,7 @@ opt-level = 3 panic = "unwind" [patch."https://github.com/paritytech/substrate"] +pallet-assets = { git = "https://github.com/zeitgeistpm/substrate.git", branch = "polkadot-v0.9.38-assets-managed-destroy" } substrate-wasm-builder = { git = "https://github.com/zeitgeistpm/substrate.git", branch = "polkadot-v0.9.38-fix-new-rustc-build" } [patch."https://github.com/paritytech/polkadot"] diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index 200221c5b..dd6e82d72 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -23,10 +23,17 @@ use crate::{ assets::Asset, types::{Assets, Balance, Currencies, Moment}, }; -use frame_support::{parameter_types, traits::LockIdentifier, PalletId}; +use frame_support::{pallet_prelude::Weight, parameter_types, traits::LockIdentifier, PalletId}; use orml_traits::parameter_type_with_key; use sp_arithmetic::Perbill; +// Asset-Router +parameter_types! { + pub const DestroyAccountWeight: Weight = Weight::from_all(1000); + pub const DestroyApprovalWeight: Weight = Weight::from_all(1000); + pub const DestroyFinishWeight: Weight = Weight::from_all(1000); +} + // Assets parameter_types! { pub const AssetsAccountDeposit: Balance = 0; diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 7b5fce9dc..b326a15de 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -22,6 +22,7 @@ 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 new file mode 100644 index 000000000..f4b4c04b4 --- /dev/null +++ b/primitives/src/macros.rs @@ -0,0 +1,66 @@ +// 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/primitives/src/traits.rs b/primitives/src/traits.rs index bef79bfae..b00c2e0ce 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -23,6 +23,7 @@ mod distribute_fees; mod market_commons_pallet_api; mod market_id; mod swaps; +mod weights; 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 weights::CheckedDivPerComponent; pub use zeitgeist_multi_reservable_currency::ZeitgeistAssetManager; diff --git a/primitives/src/traits/weights.rs b/primitives/src/traits/weights.rs new file mode 100644 index 000000000..8cc25046b --- /dev/null +++ b/primitives/src/traits/weights.rs @@ -0,0 +1,60 @@ +// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2023 Parity Technologies (UK) 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 . + +/// Provides `checked_div_per_component` implementation to determine the +/// smallest division result between two `ref_time` and `proof_size`. +/// To be removed once sp-weights is upgraded to polkadot-v0.9.39 +use frame_support::pallet_prelude::Weight; + +pub trait CheckedDivPerComponent { + /// Calculates how many `other` fit into `self`. + /// + /// Divides each component of `self` against the same component of `other`. Returns the minimum + /// of all those divisions. Returns `None` in case **all** components of `other` are zero. + /// + /// This returns `Some` even if some components of `other` are zero as long as there is at least + /// one non-zero component in `other`. The division for this particular component will then + /// yield the maximum value (e.g u64::MAX). This is because we assume not every operation and + /// hence each `Weight` will necessarily use each resource. + fn checked_div_per_component(self, other: &Self) -> Option; +} + +impl CheckedDivPerComponent for Weight { + fn checked_div_per_component(self, other: &Self) -> Option { + let mut all_zero = true; + let ref_time = match self.ref_time().checked_div(other.ref_time()) { + Some(ref_time) => { + all_zero = false; + ref_time + } + None => u64::MAX, + }; + let proof_size = match self.proof_size().checked_div(other.proof_size()) { + Some(proof_size) => { + all_zero = false; + proof_size + } + None => u64::MAX, + }; + if all_zero { + None + } else { + Some(if ref_time < proof_size { ref_time } else { proof_size }) + } + } +} diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index bc7107d3c..e8b301d7b 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -22,7 +22,7 @@ clippy::arithmetic_side_effects )] -use super::{Runtime, VERSION}; +use super::{CampaignAssetsInstance, Runtime, VERSION}; use frame_support::{ dispatch::DispatchClass, parameter_types, @@ -35,6 +35,7 @@ use frame_support::{ }; use frame_system::limits::{BlockLength, BlockWeights}; use orml_traits::parameter_type_with_key; +use pallet_assets::WeightInfo; use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use sp_runtime::{ traits::{AccountIdConversion, Bounded}, @@ -63,6 +64,14 @@ parameter_types! { } parameter_types! { + // Asset-Router + pub DestroyAccountWeight: Weight = + >::WeightInfo::destroy_accounts(1); + pub DestroyApprovalWeight: Weight = + >::WeightInfo::destroy_approvals(1); + pub DestroyFinishWeight: Weight = + >::WeightInfo::finish_destroy(); + // Assets (Campaign) pub const CampaignAssetsAccountDeposit: Balance = deposit(1, 16); pub const CampaignAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index f6fe2da2b..feafacdca 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -661,6 +661,7 @@ macro_rules! impl_config_traits { type CallbackHandle = (); type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; + type Destroyer = AssetRouter; type Extra = (); type ForceOrigin = EnsureRootOrTwoThirdsTechnicalCommittee; type Freezer = (); @@ -685,6 +686,7 @@ macro_rules! impl_config_traits { type CallbackHandle = (); type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; + type Destroyer = AssetRouter; type Extra = (); type ForceOrigin = EnsureRootOrTwoThirdsCouncil; type Freezer = (); @@ -722,6 +724,7 @@ macro_rules! impl_config_traits { type CallbackHandle = (); type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; + type Destroyer = AssetRouter; type Extra = (); type ForceOrigin = EnsureRootOrAllTechnicalCommittee; type Freezer = (); @@ -1168,6 +1171,9 @@ macro_rules! impl_config_traits { type CampaignAssets = CampaignAssets; type CustomAssetType = CustomAsset; type CustomAssets = CustomAssets; + type DestroyAccountWeight = DestroyAccountWeight; + type DestroyApprovalWeight = DestroyApprovalWeight; + type DestroyFinishWeight = DestroyFinishWeight; type MarketAssetType = MarketAsset; type MarketAssets = MarketAssets; } @@ -1250,6 +1256,9 @@ macro_rules! impl_config_traits { type AdvisoryBond = AdvisoryBond; type AdvisoryBondSlashPercentage = AdvisoryBondSlashPercentage; type ApproveOrigin = EnsureRootOrMoreThanOneThirdAdvisoryCommittee; + type AssetManager = AssetManager; + #[cfg(feature = "parachain")] + type AssetRegistry = AssetRegistry; type Authorized = Authorized; type Currency = Balances; type Court = Court; @@ -1290,9 +1299,6 @@ macro_rules! impl_config_traits { type RejectOrigin = EnsureRootOrMoreThanTwoThirdsAdvisoryCommittee; type RequestEditOrigin = EnsureRootOrMoreThanOneThirdAdvisoryCommittee; type ResolveOrigin = EnsureRoot; - type AssetManager = AssetManager; - #[cfg(feature = "parachain")] - type AssetRegistry = AssetRegistry; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; type Swaps = Swaps; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 6ad4ef44b..0e04a06f1 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -22,7 +22,7 @@ clippy::arithmetic_side_effects )] -use super::{Runtime, VERSION}; +use super::{CampaignAssetsInstance, Runtime, VERSION}; use frame_support::{ dispatch::DispatchClass, parameter_types, @@ -35,6 +35,7 @@ use frame_support::{ }; use frame_system::limits::{BlockLength, BlockWeights}; use orml_traits::parameter_type_with_key; +use pallet_assets::WeightInfo; use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use sp_runtime::{ traits::{AccountIdConversion, Bounded}, @@ -63,6 +64,14 @@ parameter_types! { } parameter_types! { + // Asset-Router + pub DestroyAccountWeight: Weight = + >::WeightInfo::destroy_accounts(1); + pub DestroyApprovalWeight: Weight = + >::WeightInfo::destroy_approvals(1); + pub DestroyFinishWeight: Weight = + >::WeightInfo::finish_destroy(); + // Assets (Campaign) pub const CampaignAssetsAccountDeposit: Balance = deposit(1, 16); pub const CampaignAssetsApprovalDeposit: Balance = ExistentialDeposit::get(); diff --git a/zrml/asset-router/Cargo.toml b/zrml/asset-router/Cargo.toml index a58dfd6cd..787fb5d73 100644 --- a/zrml/asset-router/Cargo.toml +++ b/zrml/asset-router/Cargo.toml @@ -11,7 +11,6 @@ zeitgeist-primitives = { workspace = true } [dev-dependencies] orml-tokens = { workspace = true, features = ["default"] } -pallet-assets = { workspace = true, features = ["default"] } pallet-balances = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } test-case = { workspace = true } diff --git a/zrml/asset-router/README.md b/zrml/asset-router/README.md index 48a4fd617..69dfe0391 100644 --- a/zrml/asset-router/README.md +++ b/zrml/asset-router/README.md @@ -6,6 +6,21 @@ handles the asset class of the asset in question, as the asset router internally routes the call to the appropriate pallet as defined in the pallet's configuration. It implements various ORML `MultiCurrency` traits as well as various `Fungible` traits, thus it can be used in other pallets that require -those implementation (such as ORML Currencies). The asset router also provides a -garbage collector for destructible assets, that handles asset destruction -whenever on-chain execution time is available. +those implementation (such as ORML Currencies). The asset router also provides +managed asset destruction, that handles asset destruction for all the assets +registered through the `ManagedDestroy` interface whenever on-chain execution +time is available. + +## Managed Asset Destruction + +Once an asset was registered for managed destruction, it's assigned a state and +stored in a sorted list within the `DestroyAssets` storage. Whenever weight is +available in a block, this pallet will process as many assets as possible from +that sorted list. To achieve that, it loops through all assets one by one and +for each asset, it runs through a state machine that ensures that every step +necessary to properly destroy an asset is executed and that the states are +updated accordingly. It might occur that the pallet that does the actual +destruction, i.e. that is invoked by the managed destruction routine to destroy +a specific asset (using the `Destroy` interface), throws an error. In that case +an asset is considered as `Indestructible` and stored in the +`IndestructibleAssets` storage, while also logging the incident. diff --git a/zrml/asset-router/src/lib.rs b/zrml/asset-router/src/lib.rs index 17a2d934a..08dbd5d1d 100644 --- a/zrml/asset-router/src/lib.rs +++ b/zrml/asset-router/src/lib.rs @@ -15,32 +15,42 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#![feature(proc_macro_hygiene)] #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +pub use pallet::*; + +#[macro_use] +mod macros; #[cfg(test)] mod mock; +pub mod pallet_impl; #[cfg(test)] mod tests; -pub use pallet::*; +mod types; #[frame_support::pallet] pub mod pallet { - use core::{fmt::Debug, marker::PhantomData}; - use frame_support::{ - pallet_prelude::{DispatchError, DispatchResult, Hooks}, + pub(crate) use super::types::*; + pub(crate) use alloc::{collections::BTreeMap, format}; + pub(crate) use core::{fmt::Debug, marker::PhantomData}; + pub(crate) use frame_support::{ + ensure, log, + pallet_prelude::{DispatchError, DispatchResult, Hooks, StorageValue, ValueQuery, Weight}, + require_transactional, traits::{ tokens::{ fungibles::{Create, Destroy, Inspect, Mutate, Transfer}, DepositConsequence, WithdrawConsequence, }, - BalanceStatus as Status, + BalanceStatus as Status, ConstU32, }, - transactional, Parameter, + BoundedVec, Parameter, }; - use orml_traits::{ + pub(crate) use orml_traits::{ arithmetic::Signed, currency::{ MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, @@ -48,14 +58,25 @@ pub mod pallet { }, BalanceStatus, LockIdentifier, }; + pub(crate) use pallet_assets::ManagedDestroy; use parity_scale_codec::{FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; - use sp_runtime::{ + pub(crate) use sp_runtime::{ traits::{ - AtLeast32BitUnsigned, Bounded, MaybeSerializeDeserialize, Member, Saturating, Zero, + AtLeast32BitUnsigned, Bounded, Get, MaybeSerializeDeserialize, Member, Saturating, Zero, }, - FixedPointOperand, + FixedPointOperand, SaturatedConversion, }; + pub(crate) use zeitgeist_primitives::{ + traits::CheckedDivPerComponent, unreachable_non_terminating, + }; + + pub(crate) const LOG_TARGET: &str = "runtime::asset-router"; + pub(crate) const MAX_ASSET_DESTRUCTIONS_PER_BLOCK: u8 = 128; + pub(crate) const MAX_ASSETS_IN_DESTRUCTION: u32 = 2048; + const MAX_INDESTRUCTIBLE_ASSETS: u32 = 256; + // 1 ms minimum computation time. + pub(crate) const MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT: u64 = 1_000_000_000; pub trait AssetTraits: Create @@ -90,6 +111,7 @@ pub mod pallet { + FullCodec + MaxEncodedLen + MaybeSerializeDeserialize + + Ord + TypeInfo; /// The type that represents balances. @@ -148,6 +170,13 @@ pub mod pallet { + MaybeSerializeDeserialize + TypeInfo; + /// Weight required for destroying one account. + type DestroyAccountWeight: Get; + /// Weight required for destroying one approval. + type DestroyApprovalWeight: Get; + /// Weight required for finishing the asset destruction process. + type DestroyFinishWeight: Get; + /// Logic that handles market assets by providing multiple fungible /// trait implementations. type MarketAssets: AssetTraits; @@ -162,645 +191,328 @@ pub mod pallet { + TypeInfo; } - const LOG_TARGET: &str = "runtime::asset-router"; - - #[pallet::hooks] - impl Hooks for Pallet {} - - #[pallet::pallet] - pub struct Pallet(PhantomData); - - #[pallet::call] - impl Pallet {} + /// Keeps track of assets that have to be destroyed. + #[pallet::storage] + pub(super) type DestroyAssets = StorageValue<_, DestroyAssetsT, ValueQuery>; - impl TransferAll for Pallet { - #[transactional] - fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { - // Only transfers assets maintained in orml-tokens, not implementable for pallet-assets - >::transfer_all(source, dest) - } - } + /// Keeps track of assets that can't be destroyed. + #[pallet::storage] + pub(crate) type IndestructibleAssets = + StorageValue<_, BoundedVec>, ValueQuery>; #[pallet::error] pub enum Error { /// Cannot convert Amount (MultiCurrencyExtended implementation) into Balance type. AmountIntoBalanceFailed, + /// Cannot start managed destruction as the asset was marked as indestructible. + AssetIndestructible, + /// Cannot start managed destruction as a destruction for the asset is in progress. + DestructionInProgress, + /// The vector holding all assets to destroy reached it's boundary. + TooManyManagedDestroys, /// Asset conversion failed. UnknownAsset, /// Operation is not supported for given asset Unsupported, } - /// This macro converts the invoked asset type into the respective - /// 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),*)) - } 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 { - Err(Error::::UnknownAsset) - } - }; - } - - /// This macro delegates a call to Currencies if the asset represents a currency, otherwise - /// 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) { - >::$currency_method(currency, $($args),+) - } else { - Self::log_unsupported($currency_id, stringify!($currency_method)); - $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) { - >::$asset_method(asset, $($args),*) - } else if let Ok(asset) = T::CampaignAssetType::try_from($asset_id) { - T::CampaignAssets::$asset_method(asset, $($args),*) - } else if let Ok(asset) = T::CustomAssetType::try_from($asset_id) { - T::CustomAssets::$asset_method(asset, $($args),*) - } else { - Self::log_unsupported($asset_id, stringify!($asset_method)); - $error - } - }; - } - - impl Pallet { - #[inline] - fn log_unsupported(asset: T::AssetType, function: &str) { - log::warn!(target: LOG_TARGET, "Asset {:?} not supported in function {:?}", asset, function); - } - } - - impl MultiCurrency for Pallet { - type CurrencyId = T::AssetType; - type Balance = T::Balance; - - fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { - let min_balance = route_call!(currency_id, minimum_balance, minimum_balance,); - min_balance.unwrap_or_else(|_b| { - Self::log_unsupported(currency_id, "minimum_balance"); - Self::Balance::zero() - }) - } - - fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { - let total_issuance = route_call!(currency_id, total_issuance, total_issuance,); - total_issuance.unwrap_or_else(|_b| { - Self::log_unsupported(currency_id, "total_issuance"); - Self::Balance::zero() - }) - } - - fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { - let total_balance = route_call!(currency_id, total_balance, balance, who); - total_balance.unwrap_or_else(|_b| { - Self::log_unsupported(currency_id, "total_balance"); - Self::Balance::zero() - }) - } - - 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) - } 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 { - Self::log_unsupported(currency_id, "free_balance"); - Self::Balance::zero() - } - } + #[pallet::pallet] + pub struct Pallet(PhantomData); - fn ensure_can_withdraw( - currency_id: Self::CurrencyId, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::ensure_can_withdraw( - currency, who, amount, - ); - } + #[pallet::hooks] + impl Hooks for Pallet { + fn on_idle(_: T::BlockNumber, mut remaining_weight: Weight) -> Weight { + let max_extra_weight = Self::on_idle_max_extra_weight(); - let withdraw_consequence = if let Ok(asset) = T::MarketAssetType::try_from(currency_id) + if !remaining_weight + .all_gte(max_extra_weight.saturating_add(T::DbWeight::get().reads(1))) { - 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 { - return Err(Error::::UnknownAsset.into()); + return remaining_weight; }; - withdraw_consequence.into_result().map(|_| ()) - } - - fn transfer( - currency_id: Self::CurrencyId, - from: &T::AccountId, - 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(|_| ()) - } 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 { - Err(Error::::UnknownAsset.into()) - } - } - - fn deposit( - currency_id: Self::CurrencyId, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - route_call!(currency_id, deposit, mint_into, who, amount)? - } - - fn withdraw( - currency_id: Self::CurrencyId, - 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(|_| ()) - } 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 { - 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 - } 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 { - Self::log_unsupported(currency_id, "can_slash"); - false - } - } - - fn slash( - currency_id: Self::CurrencyId, - 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) - } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { - T::CampaignAssets::slash(asset, who, amount) - .map(|b| amount.saturating_sub(b)) - .unwrap_or_else(|_| amount) - } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { - T::CustomAssets::slash(asset, who, amount) - .map(|b| amount.saturating_sub(b)) - .unwrap_or_else(|_| amount) - } else { - Self::log_unsupported(currency_id, "slash"); - amount + let mut destroy_assets = DestroyAssets::::get(); + if destroy_assets.len() == 0 { + return remaining_weight.saturating_sub(T::DbWeight::get().reads(1)); } - } - } - impl MultiCurrencyExtended for Pallet { - type Amount = >::Amount; - - fn update_balance( - currency_id: Self::CurrencyId, - 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(()); - } + remaining_weight = remaining_weight + .saturating_sub(T::DbWeight::get().reads_writes(1, 1)) + .saturating_sub(max_extra_weight); - // 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() - }; + remaining_weight = + Self::handle_asset_destruction(remaining_weight, &mut destroy_assets); - 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) - } else { - Self::withdraw(currency_id, who, by_balance).map(|_| ()) - } + DestroyAssets::::put(destroy_assets); + remaining_weight } } - impl MultiLockableCurrency for Pallet { - type Moment = T::BlockNumber; - - fn set_lock( - lock_id: LockIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::set_lock( - lock_id, currency, who, amount, - ); - } - - Err(Error::::Unsupported.into()) - } - - fn extend_lock( - lock_id: LockIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::extend_lock( - lock_id, currency, who, amount, - ); - } - - Err(Error::::Unsupported.into()) - } - - fn remove_lock( - lock_id: LockIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::remove_lock( - lock_id, currency, who, + impl Pallet { + fn handle_asset_destruction( + mut remaining_weight: Weight, + destroy_assets: &mut DestroyAssetsT, + ) -> Weight { + let mut saftey_counter_outer = 0u8; + + 'outer: while let Some(mut asset) = destroy_assets.pop() { + // Only reachable if there is an error in the implementation of pop() for Vec. + unreachable_non_terminating!( + saftey_counter_outer != MAX_ASSET_DESTRUCTIONS_PER_BLOCK, + LOG_TARGET, + break, + "Destruction outer loop iteration guard triggered, iteration: {:?}", + saftey_counter_outer ); - } - - Err(Error::::Unsupported.into()) - } - } - - impl MultiReservableCurrency for Pallet { - fn can_reserve( - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> bool { - only_currency!(currency_id, false, MultiReservableCurrency, can_reserve, who, value) - } - - fn slash_reserved( - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - only_currency!(currency_id, value, MultiReservableCurrency, slash_reserved, who, value) - } - - fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { - only_currency!( - currency_id, - Zero::zero(), - MultiReservableCurrency, - reserved_balance, - who - ) - } - - fn reserve( - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> DispatchResult { - only_currency!( - currency_id, - Err(Error::::Unsupported.into()), - MultiReservableCurrency, - reserve, - who, - value - ) - } - - fn unreserve( - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - only_currency!(currency_id, value, MultiReservableCurrency, unreserve, who, value) - } - - fn repatriate_reserved( - currency_id: Self::CurrencyId, - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: BalanceStatus, - ) -> Result { - only_currency!( - currency_id, - Err(Error::::Unsupported.into()), - MultiReservableCurrency, - repatriate_reserved, - slashed, - beneficiary, - value, - status - ) - } - } - impl NamedMultiReservableCurrency for Pallet { - type ReserveIdentifier = - >::ReserveIdentifier; - - fn reserved_balance_named( - id: &Self::ReserveIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::reserved_balance_named( - id, currency, who, + let safety_counter_inner_max = DESTRUCTION_STATES; + let mut safety_counter_inner = 0u8; + + while *asset.state() != DestructionState::Destroyed + && *asset.state() != DestructionState::Indestructible + && safety_counter_inner < safety_counter_inner_max + { + match asset.state() { + DestructionState::Accounts => { + handle_asset_destruction!( + &mut asset, + remaining_weight, + destroy_assets, + handle_destroy_accounts, + 'outer + ); + } + DestructionState::Approvals => { + handle_asset_destruction!( + &mut asset, + remaining_weight, + destroy_assets, + handle_destroy_approvals, + 'outer + ); + } + DestructionState::Finalization => { + handle_asset_destruction!( + &mut asset, + remaining_weight, + destroy_assets, + handle_destroy_finish, + 'outer + ); + } + // Next two states should never occur. Just remove the asset. + DestructionState::Destroyed => { + unreachable_non_terminating!( + false, + LOG_TARGET, + "Asset {:?} has invalid state", + asset + ); + } + DestructionState::Indestructible => { + unreachable_non_terminating!( + false, + LOG_TARGET, + "Asset {:?} has invalid state", + asset + ); + } + } + + safety_counter_inner = safety_counter_inner.saturating_add(1); + } + + // Only reachable if there is an error in the destruction state machine. + unreachable_non_terminating!( + safety_counter_inner != safety_counter_inner_max, + LOG_TARGET, + "Destruction inner loop iteration guard triggered, asset: {:?}", + asset ); - } - - Self::log_unsupported(currency_id, "reserved_balance_named"); - Zero::zero() - } - fn reserve_named( - id: &Self::ReserveIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> DispatchResult { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::reserve_named( - id, currency, who, value - ); + saftey_counter_outer = saftey_counter_outer.saturating_add(1); } - Err(Error::::Unsupported.into()) + remaining_weight } - fn unreserve_named( - id: &Self::ReserveIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::unreserve_named( - id, currency, who, value - ); + fn handle_destroy_accounts( + asset: &mut AssetInDestruction, + mut remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Accounts { + return Err(DestructionError::WrongState(remaining_weight)); } - - Self::log_unsupported(currency_id, "unreserve_named"); - value - } - - fn slash_reserved_named( - id: &Self::ReserveIdentifier, - currency_id: Self::CurrencyId, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::slash_reserved_named( - id, currency, who, value - ); + let destroy_account_weight = T::DestroyAccountWeight::get(); + + let destroy_account_cap = + match remaining_weight.checked_div_per_component(&destroy_account_weight) { + Some(amount) => amount, + None => return Ok(DestructionOk::Incomplete(remaining_weight)), + }; + + match Self::destroy_accounts(*asset.asset(), destroy_account_cap.saturated_into()) { + Ok(destroyed_accounts) => { + // TODO(#1202): More precise weights + remaining_weight = remaining_weight.saturating_sub( + destroy_account_weight + .saturating_mul(destroyed_accounts.into()) + .max(destroy_account_weight), + ); + + if u64::from(destroyed_accounts) != destroy_account_cap { + asset.transit_state(); + Ok(DestructionOk::Complete(remaining_weight)) + } else { + Ok(DestructionOk::Incomplete(remaining_weight)) + } + } + Err(e) => { + // In this case, it is not known how many accounts have been destroyed prior + // to triggering this error. The only safe handling is consuming all the + // remaining weight. + let _ = Self::mark_asset_as_indestructible( + asset, + remaining_weight, + remaining_weight, + e, + ); + // Play it safe, consume all remaining weight. + Err(DestructionError::Indestructible(Weight::zero())) + } } - - Self::log_unsupported(currency_id, "slash_reserved_named"); - value } - fn repatriate_reserved_named( - id: &Self::ReserveIdentifier, - currency_id: Self::CurrencyId, - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - if let Ok(currency) = T::CurrencyType::try_from(currency_id) { - return >::repatriate_reserved_named( - id, currency, slashed, beneficiary, value, status - ); + fn handle_destroy_approvals( + asset: &mut AssetInDestruction, + mut remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Approvals { + return Err(DestructionError::WrongState(remaining_weight)); } - - Err(Error::::Unsupported.into()) - } - } - - // Supertrait of Create and Destroy - impl Inspect for Pallet { - type AssetId = T::AssetType; - type Balance = T::Balance; - - fn total_issuance(asset: Self::AssetId) -> Self::Balance { - route_call!(asset, total_issuance, total_issuance,).unwrap_or(Zero::zero()) - // only_asset!(asset, Zero::zero(), Inspect, total_issuance,) - } - - fn minimum_balance(asset: Self::AssetId) -> Self::Balance { - route_call!(asset, minimum_balance, minimum_balance,).unwrap_or(Zero::zero()) - //only_asset!(asset, Zero::zero(), Inspect, minimum_balance,) - } - - fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { - route_call!(asset, total_balance, balance, who).unwrap_or(Zero::zero()) - //only_asset!(asset, Zero::zero(), Inspect, balance, who) - } - - fn reducible_balance( - asset: Self::AssetId, - who: &T::AccountId, - keep_alive: bool, - ) -> Self::Balance { - if T::CurrencyType::try_from(asset).is_ok() { - >::free_balance(asset, who) - } else { - only_asset!(asset, Zero::zero(), Inspect, reducible_balance, who, keep_alive) + let destroy_approval_weight = T::DestroyApprovalWeight::get(); + + let destroy_approval_cap = + match remaining_weight.checked_div_per_component(&destroy_approval_weight) { + Some(amount) => amount, + None => return Ok(DestructionOk::Incomplete(remaining_weight)), + }; + + match Self::destroy_approvals(*asset.asset(), destroy_approval_cap.saturated_into()) { + Ok(destroyed_approvals) => { + // TODO(#1202): More precise weights + remaining_weight = remaining_weight.saturating_sub( + destroy_approval_weight + .saturating_mul(destroyed_approvals.into()) + .max(destroy_approval_weight), + ); + + if u64::from(destroyed_approvals) != destroy_approval_cap { + asset.transit_state(); + Ok(DestructionOk::Complete(remaining_weight)) + } else { + Ok(DestructionOk::Incomplete(remaining_weight)) + } + } + Err(e) => { + // In this case, it is not known how many approvals have been destroyed prior + // to triggering this error. The only safe handling is consuming all the + // remaining weight. + let _ = Self::mark_asset_as_indestructible( + asset, + remaining_weight, + remaining_weight, + e, + ); + // Play it safe, consume all remaining weight. + Err(DestructionError::Indestructible(Weight::zero())) + } } } - fn can_deposit( - asset: Self::AssetId, - who: &T::AccountId, - amount: Self::Balance, - mint: bool, - ) -> DepositConsequence { - if T::CurrencyType::try_from(asset).is_err() { - return only_asset!( - asset, - DepositConsequence::UnknownAsset, - Inspect, - can_deposit, - who, - amount, - mint - ); + fn handle_destroy_finish( + asset: &mut AssetInDestruction, + remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Finalization { + return Err(DestructionError::WrongState(remaining_weight)); } - - let total_balance = >::total_balance(asset, who); - let min_balance = >::minimum_balance(asset); - - if total_balance.saturating_add(amount) < min_balance { - DepositConsequence::BelowMinimum - } else { - DepositConsequence::Success + let destroy_finish_weight = T::DestroyFinishWeight::get(); + + if remaining_weight.all_gte(destroy_finish_weight) { + // TODO(#1202): More precise weights + if let Err(e) = Self::finish_destroy(*asset.asset()) { + let remaining_weight_err = Self::mark_asset_as_indestructible( + asset, + remaining_weight, + destroy_finish_weight, + e, + ); + return Err(DestructionError::Indestructible(remaining_weight_err)); + } + + asset.transit_state(); + return Ok(DestructionOk::Complete( + remaining_weight.saturating_sub(destroy_finish_weight), + )); } - } - fn can_withdraw( - asset: Self::AssetId, - who: &T::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - if T::CurrencyType::try_from(asset).is_err() { - return only_asset!( - asset, - WithdrawConsequence::UnknownAsset, - Inspect, - can_withdraw, - who, - amount + Ok(DestructionOk::Incomplete(remaining_weight)) + } + + fn mark_asset_as_indestructible( + asset: &mut AssetInDestruction, + mut remaining_weight: Weight, + max_weight: Weight, + error: DispatchError, + ) -> Weight { + let asset_inner = *asset.asset(); + + log::error!( + target: LOG_TARGET, + "Error during managed asset account destruction of {:?}: {:?}", + asset_inner, + error + ); + + remaining_weight = remaining_weight.saturating_sub(max_weight); + + if let Err(e) = IndestructibleAssets::::try_mutate(|assets| { + let idx = assets.partition_point(|&asset_in_vec| asset_in_vec < asset_inner); + assets.try_insert(idx, asset_inner) + }) { + log::error!( + target: LOG_TARGET, + "Error during storage of indestructible asset {:?}, dropping asset: {:?}", + asset_inner, + e ); } - let can_withdraw = - >::ensure_can_withdraw(asset, who, amount); - - if let Err(_e) = can_withdraw { - return WithdrawConsequence::NoFunds; - } - - let total_balance = >::total_balance(asset, who); - let min_balance = >::minimum_balance(asset); - let remainder = total_balance.saturating_sub(amount); - - if remainder < min_balance { - WithdrawConsequence::ReducedToZero(remainder) - } else { - WithdrawConsequence::Success - } - } - - fn asset_exists(asset: Self::AssetId) -> bool { - if T::CurrencyType::try_from(asset).is_ok() { - true - } else { - only_asset!(asset, false, Inspect, asset_exists,) - } - } - } - - impl Create for Pallet { - fn create( - id: Self::AssetId, - admin: T::AccountId, - is_sufficient: bool, - min_balance: Self::Balance, - ) -> DispatchResult { - only_asset!( - id, - Err(Error::::Unsupported.into()), - Create, - create, - admin, - is_sufficient, - min_balance - ) - } - } - - impl Destroy for Pallet { - fn start_destroy( - id: Self::AssetId, - maybe_check_owner: Option, - ) -> DispatchResult { - only_asset!( - id, - Err(Error::::Unsupported.into()), - Destroy, - start_destroy, - maybe_check_owner + asset.transit_indestructible(); + remaining_weight.saturating_sub(T::DbWeight::get().reads_writes(1, 1)) + } + + fn on_idle_max_extra_weight() -> Weight { + let max_proof_size_destructibles: u64 = + AssetInDestruction::::max_encoded_len() + .saturating_mul(MAX_ASSETS_IN_DESTRUCTION.saturated_into()) + .saturated_into(); + let max_proof_size_indestructibles: u64 = T::AssetType::max_encoded_len() + .saturating_mul(MAX_INDESTRUCTIBLE_ASSETS.saturated_into()) + .saturated_into(); + let max_proof_size_total = + max_proof_size_destructibles.saturating_add(max_proof_size_indestructibles); + + Weight::from_parts( + MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, + // Maximum proof size assuming writes on full storage. + max_proof_size_total, ) } - fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result { - only_asset!( - id, - Err(Error::::Unsupported.into()), - Destroy, - destroy_accounts, - max_items - ) - } - - fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result { - only_asset!( - id, - Err(Error::::Unsupported.into()), - Destroy, - destroy_approvals, - max_items - ) - } - - fn finish_destroy(id: Self::AssetId) -> DispatchResult { - only_asset!(id, Err(Error::::Unsupported.into()), Destroy, finish_destroy,) + #[inline] + pub(crate) fn log_unsupported(asset: T::AssetType, function: &str) { + log::warn!(target: LOG_TARGET, "Asset {:?} not supported in function {:?}", asset, function); } } } diff --git a/zrml/asset-router/src/macros.rs b/zrml/asset-router/src/macros.rs new file mode 100644 index 000000000..68fef365d --- /dev/null +++ b/zrml/asset-router/src/macros.rs @@ -0,0 +1,103 @@ +// 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 converts the invoked asset type into the respective +/// 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),*)) + } 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 { + Err(Error::::UnknownAsset) + } + }; +} + +/// This macro delegates a call to Currencies if the asset represents a currency, otherwise +/// 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) { + >::$currency_method(currency, $($args),+) + } else { + Self::log_unsupported($currency_id, stringify!($currency_method)); + $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) { + >::$asset_method(asset, $($args),*) + } else if let Ok(asset) = T::CampaignAssetType::try_from($asset_id) { + T::CampaignAssets::$asset_method(asset, $($args),*) + } else if let Ok(asset) = T::CustomAssetType::try_from($asset_id) { + T::CustomAssets::$asset_method(asset, $($args),*) + } else { + Self::log_unsupported($asset_id, stringify!($asset_method)); + $error + } + }; +} + +/// This macro handles the single stages of the asset destruction. +macro_rules! handle_asset_destruction { + ($asset:expr, $remaining_weight:expr, $asset_storage:expr, $asset_method:ident, $outer_loop:tt) => { + let state_before = *($asset.state()); + let call_result = Self::$asset_method($asset, $remaining_weight); + match call_result { + Ok(DestructionOk::Incomplete(weight)) => { + // Should be infallible since the asset was just popped and force inserting + // is not possible. + if let Err(e) = $asset_storage.try_insert($asset_storage.len(), *($asset)) { + log::error!( + target: LOG_TARGET, + "Cannot reintroduce asset {:?} into DestroyAssets storage: {:?}", + $asset, + e + ); + } + + $remaining_weight = weight; + break $outer_loop; + }, + Ok(DestructionOk::Complete(weight)) | Err(DestructionError::WrongState(weight)) => { + $remaining_weight = weight; + }, + Err(DestructionError::Indestructible(weight)) => { + $remaining_weight = weight; + + if state_before != DestructionState::Finalization { + break $outer_loop; + } else { + // In case destruction failed during finalization, there is most likely still + // some weight available. + break; + } + } + } + }; +} diff --git a/zrml/asset-router/src/mock.rs b/zrml/asset-router/src/mock.rs index 2e95cec23..1291b4d6b 100644 --- a/zrml/asset-router/src/mock.rs +++ b/zrml/asset-router/src/mock.rs @@ -20,17 +20,19 @@ extern crate alloc; use crate::{self as zrml_asset_router}; -use alloc::{vec, vec::Vec}; +use alloc::{collections::BTreeMap, vec, vec::Vec}; use frame_support::{ construct_runtime, + pallet_prelude::{DispatchResult, Weight}, traits::{AsEnsureOriginWithArg, Everything}, }; use frame_system::EnsureSigned; use orml_traits::parameter_type_with_key; +use pallet_assets::ManagedDestroy; use parity_scale_codec::Compact; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, ConstU128, ConstU32, IdentityLookup}, + traits::{parameter_types, BlakeTwo256, ConstU128, ConstU32, IdentityLookup}, }; use zeitgeist_primitives::{ constants::mock::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves, BASE}, @@ -64,11 +66,17 @@ pub(super) const CUSTOM_ASSET_INITIAL_AMOUNT: Balance = 20; pub(super) const MARKET_ASSET_INITIAL_AMOUNT: Balance = 30; pub(super) const CURRENCY_INITIAL_AMOUNT: Balance = 40; +pub(super) const DESTROY_WEIGHT: Weight = Weight::from_parts(1000, 0); + pub(super) type AccountId = ::AccountId; pub(super) type CustomAssetsInstance = pallet_assets::Instance1; pub(super) type CampaignAssetsInstance = pallet_assets::Instance2; pub(super) type MarketAssetsInstance = pallet_assets::Instance3; +parameter_types! { + pub const DestroyWeight: Weight = DESTROY_WEIGHT; +} + construct_runtime!( pub enum Runtime where @@ -95,6 +103,9 @@ impl crate::Config for Runtime { type CampaignAssets = CampaignAssets; type CustomAssetType = CustomAsset; type CustomAssets = CustomAssets; + type DestroyAccountWeight = DestroyWeight; + type DestroyApprovalWeight = DestroyWeight; + type DestroyFinishWeight = DestroyWeight; type MarketAssetType = MarketAsset; type MarketAssets = MarketAssets; } @@ -175,6 +186,7 @@ impl pallet_assets::Config for Runtime { type Currency = Balances; type Extra = (); type ForceOrigin = EnsureSigned; + type Destroyer = AssetRouter; type Freezer = (); type MetadataDepositBase = ConstU128<0>; type MetadataDepositPerByte = ConstU128<0>; @@ -198,6 +210,7 @@ impl pallet_assets::Config for Runtime { type Currency = Balances; type Extra = (); type ForceOrigin = EnsureSigned; + type Destroyer = AssetRouter; type Freezer = (); type MetadataDepositBase = ConstU128<0>; type MetadataDepositPerByte = ConstU128<0>; @@ -236,6 +249,7 @@ impl pallet_assets::Config for Runtime { type Currency = Balances; type Extra = (); type ForceOrigin = EnsureSigned; + type Destroyer = AssetRouter; type Freezer = (); type MetadataDepositBase = ConstU128<0>; type MetadataDepositPerByte = ConstU128<0>; @@ -257,7 +271,7 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } -pub struct ExtBuilder { +pub(super) struct ExtBuilder { balances: Vec<(AccountIdTest, Balance)>, } @@ -278,3 +292,10 @@ impl ExtBuilder { t.into() } } + +#[frame_support::transactional] +pub(super) fn managed_destroy_multi_transactional( + assets: BTreeMap>, +) -> DispatchResult { + AssetRouter::managed_destroy_multi(assets) +} diff --git a/zrml/asset-router/src/pallet_impl/create.rs b/zrml/asset-router/src/pallet_impl/create.rs new file mode 100644 index 000000000..3e59e1e5d --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/create.rs @@ -0,0 +1,37 @@ +// 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 crate::pallet::*; + +impl Create for Pallet { + fn create( + id: Self::AssetId, + admin: T::AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Create, + create, + admin, + is_sufficient, + min_balance + ) + } +} diff --git a/zrml/asset-router/src/pallet_impl/destroy.rs b/zrml/asset-router/src/pallet_impl/destroy.rs new file mode 100644 index 000000000..7058b2499 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/destroy.rs @@ -0,0 +1,42 @@ +// 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 crate::pallet::*; + +impl Destroy for Pallet { + fn start_destroy(id: Self::AssetId, maybe_check_owner: Option) -> DispatchResult { + only_asset!( + id, + Err(Error::::Unsupported.into()), + Destroy, + start_destroy, + maybe_check_owner + ) + } + + fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result { + only_asset!(id, Err(Error::::Unsupported.into()), Destroy, destroy_accounts, max_items) + } + + fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result { + only_asset!(id, Err(Error::::Unsupported.into()), Destroy, destroy_approvals, max_items) + } + + fn finish_destroy(id: Self::AssetId) -> DispatchResult { + only_asset!(id, Err(Error::::Unsupported.into()), Destroy, finish_destroy,) + } +} diff --git a/zrml/asset-router/src/pallet_impl/inspect.rs b/zrml/asset-router/src/pallet_impl/inspect.rs new file mode 100644 index 000000000..7b0de6537 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/inspect.rs @@ -0,0 +1,118 @@ +// 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 crate::pallet::*; + +// Supertrait of Create and Destroy +impl Inspect for Pallet { + type AssetId = T::AssetType; + type Balance = T::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + route_call!(asset, total_issuance, total_issuance,).unwrap_or(Zero::zero()) + } + + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + route_call!(asset, minimum_balance, minimum_balance,).unwrap_or(Zero::zero()) + } + + fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance { + route_call!(asset, total_balance, balance, who).unwrap_or(Zero::zero()) + } + + fn reducible_balance( + asset: Self::AssetId, + who: &T::AccountId, + keep_alive: bool, + ) -> Self::Balance { + if T::CurrencyType::try_from(asset).is_ok() { + >::free_balance(asset, who) + } else { + only_asset!(asset, Zero::zero(), Inspect, reducible_balance, who, keep_alive) + } + } + + fn can_deposit( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + mint: bool, + ) -> DepositConsequence { + if T::CurrencyType::try_from(asset).is_err() { + return only_asset!( + asset, + DepositConsequence::UnknownAsset, + Inspect, + can_deposit, + who, + amount, + mint + ); + } + + let total_balance = >::total_balance(asset, who); + let min_balance = >::minimum_balance(asset); + + if total_balance.saturating_add(amount) < min_balance { + DepositConsequence::BelowMinimum + } else { + DepositConsequence::Success + } + } + + fn can_withdraw( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if T::CurrencyType::try_from(asset).is_err() { + return only_asset!( + asset, + WithdrawConsequence::UnknownAsset, + Inspect, + can_withdraw, + who, + amount + ); + } + + let can_withdraw = + >::ensure_can_withdraw(asset, who, amount); + + if let Err(_e) = can_withdraw { + return WithdrawConsequence::NoFunds; + } + + let total_balance = >::total_balance(asset, who); + let min_balance = >::minimum_balance(asset); + let remainder = total_balance.saturating_sub(amount); + + if remainder < min_balance { + WithdrawConsequence::ReducedToZero(remainder) + } else { + WithdrawConsequence::Success + } + } + + fn asset_exists(asset: Self::AssetId) -> bool { + if T::CurrencyType::try_from(asset).is_ok() { + true + } else { + only_asset!(asset, false, Inspect, asset_exists,) + } + } +} diff --git a/zrml/asset-router/src/pallet_impl/managed_destroy.rs b/zrml/asset-router/src/pallet_impl/managed_destroy.rs new file mode 100644 index 000000000..6d714ec71 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/managed_destroy.rs @@ -0,0 +1,74 @@ +// 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 crate::pallet::*; + +impl Pallet { + fn add_asset_to_managed_destruction( + destroy_assets: &mut DestroyAssetsT, + asset: T::AssetType, + maybe_check_owner: Option, + ) -> DispatchResult { + ensure!(Self::asset_exists(asset), Error::::UnknownAsset); + frame_support::ensure!(!destroy_assets.is_full(), Error::::TooManyManagedDestroys); + let asset_to_insert = AssetInDestruction::new(asset); + + let idx = match destroy_assets.binary_search(&asset_to_insert) { + Ok(_) => return Err(Error::::DestructionInProgress.into()), + Err(idx) => { + if IndestructibleAssets::::get().binary_search(&asset).is_ok() { + return Err(Error::::AssetIndestructible.into()); + } + + idx + } + }; + + destroy_assets + .try_insert(idx, asset_to_insert) + .map_err(|_| Error::::TooManyManagedDestroys)?; + + Self::start_destroy(asset, maybe_check_owner)?; + Ok(()) + } +} + +impl ManagedDestroy for Pallet { + fn managed_destroy( + asset: Self::AssetId, + maybe_check_owner: Option, + ) -> DispatchResult { + let mut destroy_assets = DestroyAssets::::get(); + Self::add_asset_to_managed_destruction(&mut destroy_assets, asset, maybe_check_owner)?; + DestroyAssets::::put(destroy_assets); + Ok(()) + } + + #[require_transactional] + fn managed_destroy_multi( + assets: BTreeMap>, + ) -> DispatchResult { + let mut destroy_assets = DestroyAssets::::get(); + + for (asset, maybe_check_owner) in assets { + Self::add_asset_to_managed_destruction(&mut destroy_assets, asset, maybe_check_owner)?; + } + + DestroyAssets::::put(destroy_assets); + Ok(()) + } +} diff --git a/zrml/asset-router/src/pallet_impl/mod.rs b/zrml/asset-router/src/pallet_impl/mod.rs new file mode 100644 index 000000000..7634fb6eb --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/mod.rs @@ -0,0 +1,27 @@ +// 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 mod create; +pub mod destroy; +pub mod inspect; +pub mod managed_destroy; +pub mod multi_lockable_currency; +pub mod multi_reserveable_currency; +pub mod multicurrency; +pub mod multicurrency_extended; +pub mod named_multi_reserveable_currency; +pub mod transfer_all; diff --git a/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs new file mode 100644 index 000000000..fb0b08334 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs @@ -0,0 +1,66 @@ +// 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 crate::pallet::*; + +impl MultiLockableCurrency for Pallet { + type Moment = T::BlockNumber; + + fn set_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::set_lock( + lock_id, currency, who, amount, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn extend_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::extend_lock( + lock_id, currency, who, amount, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn remove_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::remove_lock( + lock_id, currency, who, + ); + } + + Err(Error::::Unsupported.into()) + } +} diff --git a/zrml/asset-router/src/pallet_impl/multi_reserveable_currency.rs b/zrml/asset-router/src/pallet_impl/multi_reserveable_currency.rs new file mode 100644 index 000000000..a6bed8360 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multi_reserveable_currency.rs @@ -0,0 +1,82 @@ +// 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 crate::pallet::*; + +impl MultiReservableCurrency for Pallet { + fn can_reserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> bool { + only_currency!(currency_id, false, MultiReservableCurrency, can_reserve, who, value) + } + + fn slash_reserved( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + only_currency!(currency_id, value, MultiReservableCurrency, slash_reserved, who, value) + } + + fn reserved_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { + only_currency!(currency_id, Zero::zero(), MultiReservableCurrency, reserved_balance, who) + } + + fn reserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + only_currency!( + currency_id, + Err(Error::::Unsupported.into()), + MultiReservableCurrency, + reserve, + who, + value + ) + } + + fn unreserve( + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + only_currency!(currency_id, value, MultiReservableCurrency, unreserve, who, value) + } + + fn repatriate_reserved( + currency_id: Self::CurrencyId, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: BalanceStatus, + ) -> Result { + only_currency!( + currency_id, + Err(Error::::Unsupported.into()), + MultiReservableCurrency, + repatriate_reserved, + slashed, + beneficiary, + value, + status + ) + } +} diff --git a/zrml/asset-router/src/pallet_impl/multicurrency.rs b/zrml/asset-router/src/pallet_impl/multicurrency.rs new file mode 100644 index 000000000..b05987584 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multicurrency.rs @@ -0,0 +1,173 @@ +// 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 crate::pallet::*; + +impl MultiCurrency for Pallet { + type CurrencyId = T::AssetType; + type Balance = T::Balance; + + fn minimum_balance(currency_id: Self::CurrencyId) -> Self::Balance { + let min_balance = route_call!(currency_id, minimum_balance, minimum_balance,); + min_balance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "minimum_balance"); + Self::Balance::zero() + }) + } + + fn total_issuance(currency_id: Self::CurrencyId) -> Self::Balance { + let total_issuance = route_call!(currency_id, total_issuance, total_issuance,); + total_issuance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "total_issuance"); + Self::Balance::zero() + }) + } + + fn total_balance(currency_id: Self::CurrencyId, who: &T::AccountId) -> Self::Balance { + let total_balance = route_call!(currency_id, total_balance, balance, who); + total_balance.unwrap_or_else(|_b| { + Self::log_unsupported(currency_id, "total_balance"); + Self::Balance::zero() + }) + } + + 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) + } 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 { + Self::log_unsupported(currency_id, "free_balance"); + Self::Balance::zero() + } + } + + fn ensure_can_withdraw( + currency_id: Self::CurrencyId, + 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) + } 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 { + return Err(Error::::UnknownAsset.into()); + }; + + withdraw_consequence.into_result().map(|_| ()) + } + + fn transfer( + currency_id: Self::CurrencyId, + from: &T::AccountId, + 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(|_| ()) + } 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 { + Err(Error::::UnknownAsset.into()) + } + } + + fn deposit( + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + route_call!(currency_id, deposit, mint_into, who, amount)? + } + + fn withdraw( + currency_id: Self::CurrencyId, + 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(|_| ()) + } 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 { + 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 + } 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 { + Self::log_unsupported(currency_id, "can_slash"); + false + } + } + + fn slash( + currency_id: Self::CurrencyId, + 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) + } else if let Ok(asset) = T::CampaignAssetType::try_from(currency_id) { + T::CampaignAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| amount) + } else if let Ok(asset) = T::CustomAssetType::try_from(currency_id) { + T::CustomAssets::slash(asset, who, amount) + .map(|b| amount.saturating_sub(b)) + .unwrap_or_else(|_| 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 new file mode 100644 index 000000000..fc2bf4f6c --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs @@ -0,0 +1,53 @@ +// 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 crate::pallet::*; + +impl MultiCurrencyExtended for Pallet { + type Amount = >::Amount; + + fn update_balance( + currency_id: Self::CurrencyId, + 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) + } else { + Self::withdraw(currency_id, who, by_balance).map(|_| ()) + } + } +} 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 new file mode 100644 index 000000000..131ad4142 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs @@ -0,0 +1,102 @@ +// 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 crate::pallet::*; + +impl NamedMultiReservableCurrency for Pallet { + type ReserveIdentifier = + >::ReserveIdentifier; + + fn reserved_balance_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserved_balance_named( + id, currency, who, + ); + } + + Self::log_unsupported(currency_id, "reserved_balance_named"); + Zero::zero() + } + + fn reserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::reserve_named( + id, currency, who, value, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn unreserve_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::unreserve_named( + id, currency, who, value, + ); + } + + Self::log_unsupported(currency_id, "unreserve_named"); + value + } + + fn slash_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::slash_reserved_named( + id, currency, who, value + ); + } + + Self::log_unsupported(currency_id, "slash_reserved_named"); + value + } + + fn repatriate_reserved_named( + id: &Self::ReserveIdentifier, + currency_id: Self::CurrencyId, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + if let Ok(currency) = T::CurrencyType::try_from(currency_id) { + return >::repatriate_reserved_named( + id, currency, slashed, beneficiary, value, status + ); + } + + Err(Error::::Unsupported.into()) + } +} diff --git a/zrml/asset-router/src/pallet_impl/transfer_all.rs b/zrml/asset-router/src/pallet_impl/transfer_all.rs new file mode 100644 index 000000000..0dd0f829f --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/transfer_all.rs @@ -0,0 +1,26 @@ +// 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 crate::pallet::*; + +impl TransferAll for Pallet { + #[require_transactional] + fn transfer_all(source: &T::AccountId, dest: &T::AccountId) -> DispatchResult { + // Only transfers assets maintained in orml-tokens, not implementable for pallet-assets + >::transfer_all(source, dest) + } +} diff --git a/zrml/asset-router/src/tests/create.rs b/zrml/asset-router/src/tests/create.rs index ec89e1777..4b81382ac 100644 --- a/zrml/asset-router/src/tests/create.rs +++ b/zrml/asset-router/src/tests/create.rs @@ -21,7 +21,7 @@ use super::*; use frame_support::traits::tokens::fungibles::Inspect; #[test] -fn create_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); assert!(AssetRouter::asset_exists(CAMPAIGN_ASSET)); @@ -31,7 +31,7 @@ fn create_routes_campaign_assets_correctly() { } #[test] -fn create_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); assert!(AssetRouter::asset_exists(CUSTOM_ASSET)); @@ -41,7 +41,7 @@ fn create_routes_custom_assets_correctly() { } #[test] -fn create_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); assert!(AssetRouter::asset_exists(MARKET_ASSET)); @@ -51,7 +51,7 @@ fn create_routes_market_assets_correctly() { } #[test] -fn create_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_noop!( AssetRouter::create(CURRENCY, ALICE, true, CURRENCY_MIN_BALANCE), diff --git a/zrml/asset-router/src/tests/custom_types.rs b/zrml/asset-router/src/tests/custom_types.rs new file mode 100644 index 000000000..0faf7bc68 --- /dev/null +++ b/zrml/asset-router/src/tests/custom_types.rs @@ -0,0 +1,86 @@ +// 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 crate::{AssetInDestruction, DestructionState}; + +type Aid = AssetInDestruction; + +#[test] +fn asset_in_destruction_created_properly() { + let aid = Aid::new(2); + assert_eq!(*aid.asset(), 2); + assert_eq!(*aid.state(), DestructionState::Accounts); +} + +#[test] +fn asset_in_destruction_transitions_states_properly() { + let mut aid = Aid::new(2); + + aid.transit_state(); + assert_eq!(*aid.state(), DestructionState::Approvals); + + aid.transit_state(); + assert_eq!(*aid.state(), DestructionState::Finalization); + + aid.transit_state(); + assert_eq!(*aid.state(), DestructionState::Destroyed); + + aid.transit_state(); + assert_eq!(*aid.state(), DestructionState::Destroyed); +} + +#[test] +fn asset_in_destruction_indestructible_state_works() { + let mut aid = Aid::new(2); + + aid.transit_indestructible(); + assert_eq!(*aid.state(), DestructionState::Indestructible); + + aid.transit_state(); + assert_eq!(*aid.state(), DestructionState::Indestructible); +} + +#[test] +fn asset_in_destruction_ordering_works() { + // Order by destruction state first. + let asset_1 = Aid::new(0); + let mut asset_2 = asset_1; + assert_eq!(asset_2.transit_state(), Some(&DestructionState::Approvals)); + let mut asset_3 = asset_2; + assert_eq!(asset_3.transit_state(), Some(&DestructionState::Finalization)); + let mut asset_4 = asset_3; + assert_eq!(asset_4.transit_state(), Some(&DestructionState::Destroyed)); + let mut asset_5 = asset_1; + asset_5.transit_indestructible(); + + let mut asset_vec = vec![asset_5, asset_4, asset_3, asset_2, asset_1]; + let mut expected = vec![asset_1, asset_2, asset_3, asset_4, asset_5]; + asset_vec.sort(); + assert_eq!(asset_vec, expected); + + // On equal destruction state, order by asset id. + let mut asset_dif_id_1 = Aid::new(1); + asset_dif_id_1.transit_state(); + let mut asset_dif_id_2 = Aid::new(2); + asset_dif_id_2.transit_state(); + + asset_vec.push(asset_dif_id_1); + asset_vec.push(asset_dif_id_2); + asset_vec.sort(); + expected = vec![asset_1, asset_dif_id_2, asset_dif_id_1, asset_2, asset_3, asset_4, asset_5]; + assert_eq!(asset_vec, expected); +} diff --git a/zrml/asset-router/src/tests/destroy.rs b/zrml/asset-router/src/tests/destroy.rs index 5896d5180..b27aff366 100644 --- a/zrml/asset-router/src/tests/destroy.rs +++ b/zrml/asset-router/src/tests/destroy.rs @@ -20,7 +20,7 @@ use super::*; use frame_support::traits::tokens::fungibles::Inspect; -fn destroy_test_helper(asset: Assets, initial_amount: ::Balance) { +fn test_helper(asset: Assets, initial_amount: ::Balance) { assert!(AssetRouter::asset_exists(asset)); assert_ok!(>::deposit( asset, @@ -35,7 +35,7 @@ fn destroy_test_helper(asset: Assets, initial_amount: } #[test] -fn destroy_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); assert_ok!( @@ -47,12 +47,12 @@ fn destroy_routes_campaign_assets_correctly() { .dispatch_bypass_filter(Signed(ALICE).into()) ); - destroy_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); }); } #[test] -fn destroy_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); assert_ok!( @@ -64,12 +64,12 @@ fn destroy_routes_custom_assets_correctly() { .dispatch_bypass_filter(Signed(ALICE).into()) ); - destroy_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); }); } #[test] -fn destroy_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); assert_ok!( @@ -81,12 +81,12 @@ fn destroy_routes_market_assets_correctly() { .dispatch_bypass_filter(Signed(ALICE).into()) ); - destroy_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); }); } #[test] -fn destroy_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(>::deposit( CURRENCY, diff --git a/zrml/asset-router/src/tests/inspect.rs b/zrml/asset-router/src/tests/inspect.rs index 7f79845f8..bf8065013 100644 --- a/zrml/asset-router/src/tests/inspect.rs +++ b/zrml/asset-router/src/tests/inspect.rs @@ -20,9 +20,8 @@ use super::*; use frame_support::traits::tokens::fungibles::Inspect; -fn inspect_test_helper(asset: Assets, initial_amount: ::Balance) { +fn test_helper(asset: Assets, initial_amount: ::Balance) { assert!(AssetRouter::asset_exists(asset)); - assert_ok!(>::deposit( asset, &ALICE, @@ -39,7 +38,7 @@ fn inspect_test_helper(asset: Assets, initial_amount: } #[test] -fn inspect_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use orml_traits::MultiCurrency; @@ -48,7 +47,7 @@ fn inspect_routes_campaign_assets_correctly() { >::minimum_balance(CAMPAIGN_ASSET), CAMPAIGN_ASSET_MIN_BALANCE ); - inspect_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); assert_eq!(>::total_issuance(CURRENCY_INTERNAL), 0); @@ -56,7 +55,7 @@ fn inspect_routes_campaign_assets_correctly() { } #[test] -fn inspect_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use orml_traits::MultiCurrency; @@ -65,7 +64,7 @@ fn inspect_routes_custom_assets_correctly() { >::minimum_balance(CUSTOM_ASSET), CUSTOM_ASSET_MIN_BALANCE ); - inspect_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), 0 @@ -76,7 +75,7 @@ fn inspect_routes_custom_assets_correctly() { } #[test] -fn inspect_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use orml_traits::MultiCurrency; @@ -85,7 +84,7 @@ fn inspect_routes_market_assets_correctly() { >::minimum_balance(MARKET_ASSET), MARKET_ASSET_MIN_BALANCE ); - inspect_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), 0 @@ -96,10 +95,10 @@ fn inspect_routes_market_assets_correctly() { } #[test] -fn inspect_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_eq!(AssetRouter::minimum_balance(CURRENCY), CURRENCY_MIN_BALANCE); - inspect_test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT); + test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), 0 diff --git a/zrml/asset-router/src/tests/managed_destroy.rs b/zrml/asset-router/src/tests/managed_destroy.rs new file mode 100644 index 000000000..b5ef89335 --- /dev/null +++ b/zrml/asset-router/src/tests/managed_destroy.rs @@ -0,0 +1,337 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use crate::{ + AssetInDestruction, DestroyAssets, DestructionState, IndestructibleAssets, Weight, + MAX_ASSET_DESTRUCTIONS_PER_BLOCK, MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, +}; +use frame_support::{ + traits::{tokens::fungibles::Inspect, Get}, + weights::RuntimeDbWeight, + BoundedVec, +}; +use pallet_assets::ManagedDestroy; + +#[test] +fn adds_assets_properly() { + ExtBuilder::default().build().execute_with(|| { + let campaign_asset = AssetInDestruction::new(CAMPAIGN_ASSET); + let custom_asset = AssetInDestruction::new(CUSTOM_ASSET); + + assert_noop!( + AssetRouter::managed_destroy(CAMPAIGN_ASSET, None), + Error::::UnknownAsset + ); + + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::managed_destroy(CAMPAIGN_ASSET, None)); + assert_noop!( + AssetRouter::managed_destroy(CAMPAIGN_ASSET, None), + Error::::DestructionInProgress + ); + assert_eq!(DestroyAssets::::get(), vec![campaign_asset]); + + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::managed_destroy(CUSTOM_ASSET, None)); + let mut expected = vec![campaign_asset, custom_asset]; + expected.sort(); + assert_eq!(DestroyAssets::::get(), expected); + + IndestructibleAssets::::put(BoundedVec::truncate_from(vec![ + CAMPAIGN_ASSET, + CUSTOM_ASSET, + ])); + DestroyAssets::::kill(); + assert_noop!( + AssetRouter::managed_destroy(CAMPAIGN_ASSET, None), + Error::::AssetIndestructible + ); + assert_noop!( + AssetRouter::managed_destroy(CUSTOM_ASSET, None), + Error::::AssetIndestructible + ); + }); +} + +#[test] +fn adds_multi_assets_properly() { + ExtBuilder::default().build().execute_with(|| { + let assets = BTreeMap::from([(CAMPAIGN_ASSET, None), (CUSTOM_ASSET, None)]); + let campaign_asset = AssetInDestruction::new(CAMPAIGN_ASSET); + let custom_asset = AssetInDestruction::new(CUSTOM_ASSET); + + assert_noop!( + managed_destroy_multi_transactional(assets.clone()), + Error::::UnknownAsset + ); + + for (asset, _) in assets.clone() { + assert_ok!(AssetRouter::create(asset, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + } + + assert_ok!(managed_destroy_multi_transactional(assets.clone())); + + for (asset, _) in assets.clone() { + assert_noop!( + AssetRouter::managed_destroy(asset, None), + Error::::DestructionInProgress + ); + } + + assert_noop!( + managed_destroy_multi_transactional(assets.clone()), + Error::::DestructionInProgress + ); + let mut expected = vec![campaign_asset, custom_asset]; + expected.sort(); + assert_eq!(DestroyAssets::::get(), expected); + + IndestructibleAssets::::put(BoundedVec::truncate_from(vec![ + CAMPAIGN_ASSET, + CUSTOM_ASSET, + ])); + DestroyAssets::::kill(); + assert_noop!( + managed_destroy_multi_transactional(assets), + Error::::AssetIndestructible + ); + }); +} + +#[test] +fn destroys_assets_fully_works_properly() { + ExtBuilder::default().build().execute_with(|| { + let assets_raw = [(CAMPAIGN_ASSET, None), (CUSTOM_ASSET, None), (MARKET_ASSET, None)]; + let assets = BTreeMap::from_iter(assets_raw.to_vec()); + + for (asset, _) in &assets_raw[..] { + assert_ok!(AssetRouter::create(*asset, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + } + + assert_ok!( + pallet_assets::Call::::approve_transfer { + id: CAMPAIGN_ASSET_INTERNAL.into(), + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + assert_ok!(managed_destroy_multi_transactional(assets.clone())); + assert_eq!(DestroyAssets::::get().len(), 3); + + let available_weight = (2 * MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT).into(); + let remaining_weight = AssetRouter::on_idle(0, available_weight); + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + assert_eq!(IndestructibleAssets::::get(), vec![]); + assert_eq!(DestroyAssets::::get(), vec![]); + + let mut consumed_weight = available_weight - 3u64 * 3u64 * DESTROY_WEIGHT; + // Consider safety buffer for extra execution time and storage proof size + consumed_weight = consumed_weight + .saturating_sub(Weight::from_parts(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, 45_824)); + assert_eq!(remaining_weight, consumed_weight); + }) +} + +#[test] +fn destroys_assets_partially_properly() { + ExtBuilder::default().build().execute_with(|| { + let assets_raw = [(CAMPAIGN_ASSET, None), (CUSTOM_ASSET, None), (MARKET_ASSET, None)]; + let assets = BTreeMap::from_iter(assets_raw.to_vec()); + + for (asset, _) in &assets_raw[..] { + assert_ok!(AssetRouter::create(*asset, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + } + + assert_ok!(managed_destroy_multi_transactional(assets.clone())); + assert_eq!(DestroyAssets::::get().len(), 3); + + let mut available_weight: Weight = + Weight::from_all(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT) + 2u64 * DESTROY_WEIGHT; + // Make on_idle only partially delete the first asset + let _ = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 3); + + // Now delete each asset one by one by supplying exactly the required weight + available_weight = Weight::from_all(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT) + DESTROY_WEIGHT; + let _ = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 2); + + available_weight = + Weight::from_all(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT) + 3u64 * DESTROY_WEIGHT; + let _ = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 1); + + let _ = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 0); + + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + }) +} + +#[test] +fn properly_handles_indestructible_assets() { + ExtBuilder::default().build().execute_with(|| { + let assets_raw = vec![CAMPAIGN_ASSET, CUSTOM_ASSET, MARKET_ASSET]; + let mut destroy_assets = DestroyAssets::::get(); + let available_weight = (4 * MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT).into(); + + for asset in assets_raw { + destroy_assets.force_push(AssetInDestruction::new(asset)); + } + + destroy_assets.sort(); + + let setup_state = || { + assert_ok!(AssetRouter::create( + *destroy_assets[0].asset(), + ALICE, + true, + CAMPAIGN_ASSET_MIN_BALANCE + )); + assert_ok!(AssetRouter::create( + *destroy_assets[2].asset(), + ALICE, + true, + CAMPAIGN_ASSET_MIN_BALANCE + )); + assert_ok!(AssetRouter::start_destroy(*destroy_assets[0].asset(), None)); + assert_ok!(AssetRouter::start_destroy(*destroy_assets[2].asset(), None)); + }; + + // [1] Asset is indestructible and not in Finalization state, + // i.e. weight consumption bounded but unknown. + setup_state(); + DestroyAssets::::put(destroy_assets.clone()); + assert_eq!(DestroyAssets::::get().len(), 3); + let remaining_weight = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 1); + assert_eq!(remaining_weight, 0.into()); + + // Destroy remaining assets + let _ = AssetRouter::on_idle(0, available_weight); + assert_eq!(DestroyAssets::::get().len(), 0); + assert_eq!(IndestructibleAssets::::get().len(), 1); + + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + + // [2] Asset is indestructible and in Finalization state, + // i.e. weight consumption bounded and known. + DestroyAssets::::kill(); + IndestructibleAssets::::kill(); + setup_state(); + destroy_assets[1].transit_state(); + destroy_assets[1].transit_state(); + DestroyAssets::::put(destroy_assets); + assert_eq!(DestroyAssets::::get().len(), 3); + let remaining_weight = AssetRouter::on_idle(0, available_weight); + let mut consumed_weight = available_weight - 2u32 * 3u32 * DESTROY_WEIGHT - DESTROY_WEIGHT; + // Consider safety buffer for extra execution time and storage proof size + consumed_weight = consumed_weight + .saturating_sub(Weight::from_parts(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, 45_824)); + assert_eq!(remaining_weight, consumed_weight); + assert_eq!(DestroyAssets::::get().len(), 0); + assert_eq!(IndestructibleAssets::::get().len(), 1); + + assert!(!AssetRouter::asset_exists(CAMPAIGN_ASSET)); + assert!(!AssetRouter::asset_exists(CUSTOM_ASSET)); + assert!(!AssetRouter::asset_exists(MARKET_ASSET)); + }) +} + +#[test] +fn does_not_execute_on_insufficient_weight() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::managed_destroy(CAMPAIGN_ASSET, None)); + assert_eq!(DestroyAssets::::get().len(), 1); + + let db_weight: RuntimeDbWeight = ::DbWeight::get(); + let mut available_weight = Weight::from_parts(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, 45_824) + + db_weight.reads(1) + - 1u64.into(); + let mut remaining_weight = AssetRouter::on_idle(0, available_weight); + assert_eq!(available_weight, remaining_weight); + assert_eq!(DestroyAssets::::get().len(), 1); + + available_weight += Weight::from_all(1u64) + db_weight.writes(1); + let mut remaining_weight_expected: Weight = 0u64.into(); + remaining_weight = AssetRouter::on_idle(0, available_weight); + assert_eq!(remaining_weight_expected, remaining_weight); + assert_eq!(DestroyAssets::::get().len(), 1); + + remaining_weight_expected = 1u64.into(); + available_weight += 3u64 * DESTROY_WEIGHT + remaining_weight_expected; + remaining_weight = AssetRouter::on_idle(0, available_weight); + assert_eq!(remaining_weight_expected, remaining_weight); + assert_eq!(DestroyAssets::::get().len(), 0); + }) +} + +#[test] +fn does_skip_and_remove_assets_in_invalid_state() { + ExtBuilder::default().build().execute_with(|| { + let mut campaign_asset = AssetInDestruction::new(CAMPAIGN_ASSET); + campaign_asset.transit_state(); + campaign_asset.transit_state(); + assert_eq!(*campaign_asset.transit_state().unwrap(), DestructionState::Destroyed); + let mut custom_asset = AssetInDestruction::new(CUSTOM_ASSET); + custom_asset.transit_indestructible(); + + let assets_raw = BoundedVec::truncate_from(vec![campaign_asset, custom_asset]); + DestroyAssets::::put(assets_raw); + let db_weight: RuntimeDbWeight = ::DbWeight::get(); + let available_weight = Weight::from_parts(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, 45_824) + + db_weight.reads_writes(1, 1) + + DESTROY_WEIGHT; + + let remaining_weight = AssetRouter::on_idle(0, available_weight); + // No destroy routine was called + assert_eq!(remaining_weight, DESTROY_WEIGHT); + // Asset in invalid states got removed + assert_eq!(DestroyAssets::::get().len(), 0); + }); +} + +#[test] +#[should_panic(expected = "Destruction outer loop iteration guard triggered")] +fn does_trigger_on_idle_outer_loop_safety_guard() { + ExtBuilder::default().build().execute_with(|| { + for asset_num in 0..=MAX_ASSET_DESTRUCTIONS_PER_BLOCK { + let asset = Assets::CampaignAssetClass(asset_num as u128); + assert_ok!(AssetRouter::create(asset, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::managed_destroy(asset, None)); + } + + let db_weight: RuntimeDbWeight = ::DbWeight::get(); + let available_weight = Weight::from_parts(MIN_ON_IDLE_EXTRA_COMPUTATION_WEIGHT, 45_824) + + db_weight.reads(1) + + DESTROY_WEIGHT * 3 * (MAX_ASSET_DESTRUCTIONS_PER_BLOCK + 1) as u64; + + let _ = AssetRouter::on_idle(0, available_weight); + }); +} diff --git a/zrml/asset-router/src/tests/mod.rs b/zrml/asset-router/src/tests/mod.rs index 11d9da345..d59169a37 100644 --- a/zrml/asset-router/src/tests/mod.rs +++ b/zrml/asset-router/src/tests/mod.rs @@ -18,6 +18,7 @@ #![cfg(test)] use super::{mock::*, Error}; +use alloc::collections::BTreeMap; use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin::Signed, @@ -26,7 +27,7 @@ use frame_support::{ fungibles::{Create, Destroy}, DepositConsequence, WithdrawConsequence, }, - UnfilteredDispatchable, + OnIdle, UnfilteredDispatchable, }, }; use orml_traits::{ @@ -36,8 +37,10 @@ use orml_traits::{ use zeitgeist_primitives::types::Assets; mod create; +mod custom_types; mod destroy; mod inspect; +mod managed_destroy; mod multi_currency; mod multi_lockable_currency; mod multi_reservable_currency; diff --git a/zrml/asset-router/src/tests/multi_currency.rs b/zrml/asset-router/src/tests/multi_currency.rs index db88aa981..06f3d0a99 100644 --- a/zrml/asset-router/src/tests/multi_currency.rs +++ b/zrml/asset-router/src/tests/multi_currency.rs @@ -22,7 +22,7 @@ use orml_traits::MultiCurrency; use test_case::test_case; use zeitgeist_primitives::types::{Amount, Balance}; -fn multicurrency_test_helper( +fn test_helper( asset: Assets, initial_amount: ::Balance, min_balance: ::Balance, @@ -52,17 +52,13 @@ fn multicurrency_test_helper( } #[test] -fn multicurrency_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use frame_support::traits::tokens::fungibles::Inspect; assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); - multicurrency_test_helper( - CAMPAIGN_ASSET, - CAMPAIGN_ASSET_INITIAL_AMOUNT, - CAMPAIGN_ASSET_MIN_BALANCE, - ); + test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT, CAMPAIGN_ASSET_MIN_BALANCE); assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); assert_eq!(>::total_issuance(MARKET_ASSET_INTERNAL), 0); @@ -71,17 +67,13 @@ fn multicurrency_routes_campaign_assets_correctly() { } #[test] -fn multicurrency_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use frame_support::traits::tokens::fungibles::Inspect; assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE)); - multicurrency_test_helper( - CUSTOM_ASSET, - CUSTOM_ASSET_INITIAL_AMOUNT, - CUSTOM_ASSET_MIN_BALANCE, - ); + test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT, CUSTOM_ASSET_MIN_BALANCE); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), @@ -93,17 +85,13 @@ fn multicurrency_routes_custom_assets_correctly() { } #[test] -fn multicurrency_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { use frame_support::traits::tokens::fungibles::Inspect; assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE)); - multicurrency_test_helper( - MARKET_ASSET, - MARKET_ASSET_INITIAL_AMOUNT, - MARKET_ASSET_MIN_BALANCE, - ); + test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT, MARKET_ASSET_MIN_BALANCE); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), @@ -115,11 +103,11 @@ fn multicurrency_routes_market_assets_correctly() { } #[test] -fn multicurrency_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { use frame_support::traits::tokens::fungibles::Inspect; - multicurrency_test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); + test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), @@ -134,10 +122,7 @@ fn multicurrency_routes_currencies_correctly() { #[test_case(Amount::max_value(), Some(Amount::max_value().unsigned_abs() as Balance); "max")] #[test_case(Amount::min_value(), None; "min")] #[test_case(Amount::min_value() + 1, Some((Amount::min_value() + 1).unsigned_abs() as Balance); "min_plus_one")] -fn multicurrency_update_balance_handles_overflows_correctly( - update: Amount, - expected: Option, -) { +fn update_balance_handles_overflows_correctly(update: Amount, expected: Option) { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); diff --git a/zrml/asset-router/src/tests/multi_lockable_currency.rs b/zrml/asset-router/src/tests/multi_lockable_currency.rs index 587b9a40b..d69e8e497 100644 --- a/zrml/asset-router/src/tests/multi_lockable_currency.rs +++ b/zrml/asset-router/src/tests/multi_lockable_currency.rs @@ -20,7 +20,7 @@ use super::*; use orml_traits::MultiCurrency; -fn multi_lockable_currency_unroutable_test_helper(asset: Assets) { +fn unroutable_test_helper(asset: Assets) { assert_noop!( AssetRouter::set_lock(Default::default(), asset, &ALICE, 1), Error::::Unsupported @@ -36,7 +36,7 @@ fn multi_lockable_currency_unroutable_test_helper(asset: Assets) { } #[test] -fn multi_lockable_currency_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); assert_ok!(AssetRouter::set_lock(Default::default(), CURRENCY, &ALICE, 1)); @@ -70,28 +70,28 @@ fn multi_lockable_currency_routes_currencies_correctly() { } #[test] -fn multi_lockable_currency_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); - multi_lockable_currency_unroutable_test_helper(CAMPAIGN_ASSET); + unroutable_test_helper(CAMPAIGN_ASSET); }); } #[test] -fn multi_lockable_currency_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); - multi_lockable_currency_unroutable_test_helper(CUSTOM_ASSET); + unroutable_test_helper(CUSTOM_ASSET); }); } #[test] -fn multi_lockable_currency_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); - multi_lockable_currency_unroutable_test_helper(MARKET_ASSET); + unroutable_test_helper(MARKET_ASSET); }); } diff --git a/zrml/asset-router/src/tests/multi_reservable_currency.rs b/zrml/asset-router/src/tests/multi_reservable_currency.rs index 9f8e9333f..449aa1d8a 100644 --- a/zrml/asset-router/src/tests/multi_reservable_currency.rs +++ b/zrml/asset-router/src/tests/multi_reservable_currency.rs @@ -20,10 +20,7 @@ use super::*; use orml_traits::MultiCurrency; -fn multi_reserveable_currency_unroutable_test_helper( - asset: Assets, - initial_amount: ::Balance, -) { +fn unroutable_test_helper(asset: Assets, initial_amount: ::Balance) { assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); assert!(!AssetRouter::can_reserve(asset, &ALICE, initial_amount)); assert_noop!( @@ -40,7 +37,7 @@ fn multi_reserveable_currency_unroutable_test_helper( } #[test] -fn multi_reserveable_currency_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); @@ -74,37 +71,28 @@ fn multi_reserveable_currency_routes_currencies_correctly() { } #[test] -fn multi_reserveable_currency_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); - multi_reserveable_currency_unroutable_test_helper( - CAMPAIGN_ASSET, - CAMPAIGN_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); }); } #[test] -fn multi_reserveable_currency_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); - multi_reserveable_currency_unroutable_test_helper( - CUSTOM_ASSET, - CUSTOM_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); }); } #[test] -fn multi_reserveable_currency_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); - multi_reserveable_currency_unroutable_test_helper( - MARKET_ASSET, - MARKET_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); }); } diff --git a/zrml/asset-router/src/tests/named_multi_reservable_currency.rs b/zrml/asset-router/src/tests/named_multi_reservable_currency.rs index b0096c8aa..1722a9a03 100644 --- a/zrml/asset-router/src/tests/named_multi_reservable_currency.rs +++ b/zrml/asset-router/src/tests/named_multi_reservable_currency.rs @@ -20,10 +20,7 @@ use super::*; use orml_traits::MultiCurrency; -fn named_multi_reserveable_currency_unroutable_test_helper( - asset: Assets, - initial_amount: ::Balance, -) { +fn unroutable_test_helper(asset: Assets, initial_amount: ::Balance) { assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); assert_noop!( AssetRouter::reserve_named(&Default::default(), asset, &ALICE, initial_amount), @@ -45,7 +42,7 @@ fn named_multi_reserveable_currency_unroutable_test_helper( } #[test] -fn named_multi_reserveable_currency_routes_currencies_correctly() { +fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); assert_ok!(AssetRouter::reserve_named( @@ -82,37 +79,28 @@ fn named_multi_reserveable_currency_routes_currencies_correctly() { } #[test] -fn named_multi_reserveable_currency_routes_campaign_assets_correctly() { +fn routes_campaign_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); - named_multi_reserveable_currency_unroutable_test_helper( - CAMPAIGN_ASSET, - CAMPAIGN_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); }); } #[test] -fn named_multi_reserveable_currency_routes_custom_assets_correctly() { +fn routes_custom_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); - named_multi_reserveable_currency_unroutable_test_helper( - CUSTOM_ASSET, - CUSTOM_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); }); } #[test] -fn named_multi_reserveable_currency_routes_market_assets_correctly() { +fn routes_market_assets_correctly() { ExtBuilder::default().build().execute_with(|| { assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); - named_multi_reserveable_currency_unroutable_test_helper( - MARKET_ASSET, - MARKET_ASSET_INITIAL_AMOUNT, - ); + unroutable_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); }); } diff --git a/zrml/asset-router/src/types.rs b/zrml/asset-router/src/types.rs new file mode 100644 index 000000000..91ec53a22 --- /dev/null +++ b/zrml/asset-router/src/types.rs @@ -0,0 +1,120 @@ +// 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 crate::{BoundedVec, Config, ConstU32, Weight, MAX_ASSETS_IN_DESTRUCTION}; +use core::cmp::Ordering; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub(crate) type DestroyAssetsT = BoundedVec< + AssetInDestruction<::AssetType>, + ConstU32<{ MAX_ASSETS_IN_DESTRUCTION }>, +>; + +pub(crate) enum DestructionOk { + Complete(Weight), + Incomplete(Weight), +} + +pub(crate) enum DestructionError { + Indestructible(Weight), + WrongState(Weight), +} + +pub(crate) type DestructionResult = Result; + +#[derive( + Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Decode, Encode, MaxEncodedLen, TypeInfo, +)] +pub(crate) enum DestructionState { + Accounts, + Approvals, + Finalization, + Destroyed, + Indestructible, +} +pub(crate) const DESTRUCTION_STATES: u8 = 5; + +#[derive(Clone, Copy, Encode, Eq, Debug, Decode, MaxEncodedLen, PartialEq, TypeInfo)] +pub(crate) struct AssetInDestruction { + asset: A, + state: DestructionState, +} + +impl PartialOrd for AssetInDestruction +where + A: Eq + Ord + PartialEq + PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +// Ordering for binary search of assets in destruction. +// Prioritize asset state first, then asset. +impl Ord for AssetInDestruction +where + A: Eq + Ord + PartialEq + PartialOrd, +{ + fn cmp(&self, other: &Self) -> Ordering { + match self.state.cmp(&other.state) { + Ordering::Equal => { + // Since asset destruction will always pop from the vector, sorting has to be reverse. + match self.asset.cmp(&other.asset) { + Ordering::Equal => Ordering::Equal, + Ordering::Less => Ordering::Greater, + Ordering::Greater => Ordering::Less, + } + } + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + } + } +} + +impl AssetInDestruction { + pub(crate) fn new(asset: A) -> Self { + AssetInDestruction { asset, state: DestructionState::Accounts } + } + + pub(crate) fn asset(&self) -> &A { + &self.asset + } + + pub(crate) fn state(&self) -> &DestructionState { + &self.state + } + + pub(crate) fn transit_indestructible(&mut self) { + self.state = DestructionState::Indestructible; + } + + // Returns the new state on change, None otherwise + pub(crate) fn transit_state(&mut self) -> Option<&DestructionState> { + let state_before = self.state; + + self.state = match self.state { + DestructionState::Accounts => DestructionState::Approvals, + DestructionState::Approvals => DestructionState::Finalization, + DestructionState::Destroyed => DestructionState::Destroyed, + DestructionState::Finalization => DestructionState::Destroyed, + DestructionState::Indestructible => DestructionState::Indestructible, + }; + + if state_before != self.state { Some(&self.state) } else { None } + } +} diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 1c689690d..c84d8444f 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -41,7 +41,7 @@ use sp_runtime::{ }; use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; #[cfg(feature = "parachain")] -use zeitgeist_primitives::types::Asset; +use zeitgeist_primitives::types::{Asset, Currencies}; use zeitgeist_primitives::{ constants::mock::{ AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, @@ -63,8 +63,8 @@ use zeitgeist_primitives::{ }, traits::{DeployPoolApi, DistributeFees}, types::{ - AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - Currencies, Hash, Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, + Index, MarketId, Moment, PoolId, UncheckedExtrinsicTest, }, }; use zrml_neo_swaps::BalanceOf; diff --git a/zrml/prediction-markets/Cargo.toml b/zrml/prediction-markets/Cargo.toml index 230289b06..7add57212 100644 --- a/zrml/prediction-markets/Cargo.toml +++ b/zrml/prediction-markets/Cargo.toml @@ -47,7 +47,7 @@ mock = [ "orml-currencies/default", "orml-tokens/default", "pallet-assets/default", - "pallet-balances", + "pallet-balances/default", "pallet-randomness-collective-flip/default", "pallet-timestamp/default", "pallet-treasury/default", diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 512c3763e..97aaabacb 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -47,18 +47,19 @@ use zeitgeist_primitives::{ BalanceFractionalDecimals, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, - CorrectionPeriod, CourtPalletId, ExistentialDeposit, ExistentialDepositsNew, 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, - 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, + CorrectionPeriod, CourtPalletId, DestroyAccountWeight, DestroyApprovalWeight, + DestroyFinishWeight, ExistentialDeposit, ExistentialDepositsNew, 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, 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::{ @@ -370,6 +371,7 @@ impl pallet_assets::Config for Runtime { type Extra = (); type ForceOrigin = EnsureRoot; type Freezer = (); + type Destroyer = AssetRouter; type MetadataDepositBase = AssetsMetadataDepositBase; type MetadataDepositPerByte = AssetsMetadataDepositPerByte; type RemoveItemsLimit = ConstU32<50>; @@ -393,6 +395,7 @@ impl pallet_assets::Config for Runtime { type Extra = (); type ForceOrigin = EnsureRoot; type Freezer = (); + type Destroyer = AssetRouter; type MetadataDepositBase = AssetsMetadataDepositBase; type MetadataDepositPerByte = AssetsMetadataDepositPerByte; type RemoveItemsLimit = ConstU32<50>; @@ -416,6 +419,7 @@ impl pallet_assets::Config for Runtime { type Extra = (); type ForceOrigin = EnsureRoot; type Freezer = (); + type Destroyer = AssetRouter; type MetadataDepositBase = AssetsMetadataDepositBase; type MetadataDepositPerByte = AssetsMetadataDepositPerByte; type RemoveItemsLimit = ConstU32<50>; @@ -433,6 +437,9 @@ impl zrml_asset_router::Config for Runtime { type CampaignAssets = CampaignAssets; type CustomAssetType = CustomAsset; type CustomAssets = CustomAssets; + type DestroyAccountWeight = DestroyAccountWeight; + type DestroyApprovalWeight = DestroyApprovalWeight; + type DestroyFinishWeight = DestroyFinishWeight; type MarketAssetType = MarketAsset; type MarketAssets = MarketAssets; } From 9cc0158bd8f8ac4993429deac04c67e7db4c4f35 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 11:51:42 +0100 Subject: [PATCH 05/14] New Asset System - Move lazy migration into asset-router (#1256) * Merge Old* and New* asset variants * Partially integrate lazy migration routing * Integrate lazy migration routing * Fix ExistentialDeposit mapping & Satisfy Clippy * Improve code style Co-authored-by: Chralt * Improve style Co-authored-by: Malte Kliemann * Improve style Co-authored-by: Malte Kliemann * Add frame-support/try-runtime to asset-router * Rename multicurrency* files to multi_currency* * Test old-outcome currency branch * Use reserved_balance_named in tests * Including duplicate asset routing info in readme * Update readme --------- Co-authored-by: Chralt Co-authored-by: Malte Kliemann --- 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 ++---- runtime/battery-station/src/parameters.rs | 8 +- runtime/zeitgeist/src/parameters.rs | 8 +- zrml/asset-router/Cargo.toml | 4 +- zrml/asset-router/README.md | 16 +++ zrml/asset-router/src/macros.rs | 37 ++++-- zrml/asset-router/src/mock.rs | 3 +- zrml/asset-router/src/pallet_impl/inspect.rs | 8 +- zrml/asset-router/src/pallet_impl/mod.rs | 4 +- .../{multicurrency.rs => multi_currency.rs} | 106 +++++++++++++----- .../pallet_impl/multi_currency_extended.rs | 80 +++++++++++++ .../pallet_impl/multi_lockable_currency.rs | 15 +++ .../src/pallet_impl/multicurrency_extended.rs | 53 --------- .../named_multi_reserveable_currency.rs | 28 +++++ zrml/asset-router/src/tests/destroy.rs | 2 +- zrml/asset-router/src/tests/inspect.rs | 6 +- zrml/asset-router/src/tests/multi_currency.rs | 1 + .../src/tests/multi_lockable_currency.rs | 20 ++-- .../src/tests/multi_reservable_currency.rs | 28 ++--- .../tests/named_multi_reservable_currency.rs | 34 ++++-- 24 files changed, 371 insertions(+), 298 deletions(-) rename zrml/asset-router/src/pallet_impl/{multicurrency.rs => multi_currency.rs} (56%) create mode 100644 zrml/asset-router/src/pallet_impl/multi_currency_extended.rs delete mode 100644 zrml/asset-router/src/pallet_impl/multicurrency_extended.rs 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( diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index e8b301d7b..b4697e5a4 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..4096b4d31 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/Cargo.toml b/zrml/asset-router/Cargo.toml index 787fb5d73..30e235325 100644 --- a/zrml/asset-router/Cargo.toml +++ b/zrml/asset-router/Cargo.toml @@ -27,7 +27,9 @@ std = [ "parity-scale-codec/std", "zeitgeist-primitives/std", ] -try-runtime = [] +try-runtime = [ + "frame-support/try-runtime", +] [package] authors = ["Zeitgeist PM "] diff --git a/zrml/asset-router/README.md b/zrml/asset-router/README.md index 69dfe0391..554389cdc 100644 --- a/zrml/asset-router/README.md +++ b/zrml/asset-router/README.md @@ -24,3 +24,19 @@ destruction, i.e. that is invoked by the managed destruction routine to destroy a specific asset (using the `Destroy` interface), throws an error. In that case an asset is considered as `Indestructible` and stored in the `IndestructibleAssets` storage, while also logging the incident. + +## Routing support for duplicate asset types in `CurrencyType` and `MarketAssetType` + +As some asset types within `CurrencyType` and `MarketAssetType` map to the same +asset type in the overarching `AssetType`, it is necessary to apply some +additional logic to determine when a function call with an asset of `AssetType` +should be invoked in `Currencies` and when it should be invoked in +`MarketAssets`. The approach this pallet uses is as follows: + +- Try to convert `AssetType` into `MarketAssetType` +- On success, check if `MarketAssetType` exists. + - If it does, invoke the function in `MarketAssets` + - If it does not, try to convert to `CurrencyType`. + - On success, invoke `Currencies` + - On failure, invoke `MarketAssets` +- On failure, continue trying to convert into other known asset types. diff --git a/zrml/asset-router/src/macros.rs b/zrml/asset-router/src/macros.rs index 68fef365d..663c5ca51 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) } @@ -37,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)); @@ -46,8 +69,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..b9114884e 100644 --- a/zrml/asset-router/src/mock.rs +++ b/zrml/asset-router/src/mock.rs @@ -51,9 +51,10 @@ 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_OLD_OUTCOME: Assets = Assets::CategoricalOutcome(7, 8); pub(super) const CURRENCY_INTERNAL: Currencies = Currencies::ForeignAsset(0); pub(super) const CAMPAIGN_ASSET_MIN_BALANCE: Balance = 1; 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/pallet_impl/mod.rs b/zrml/asset-router/src/pallet_impl/mod.rs index 7634fb6eb..5c3fdeb82 100644 --- a/zrml/asset-router/src/pallet_impl/mod.rs +++ b/zrml/asset-router/src/pallet_impl/mod.rs @@ -19,9 +19,9 @@ pub mod create; pub mod destroy; pub mod inspect; pub mod managed_destroy; +pub mod multi_currency; +pub mod multi_currency_extended; pub mod multi_lockable_currency; pub mod multi_reserveable_currency; -pub mod multicurrency; -pub mod multicurrency_extended; pub mod named_multi_reserveable_currency; pub mod transfer_all; diff --git a/zrml/asset-router/src/pallet_impl/multicurrency.rs b/zrml/asset-router/src/pallet_impl/multi_currency.rs similarity index 56% rename from zrml/asset-router/src/pallet_impl/multicurrency.rs rename to zrml/asset-router/src/pallet_impl/multi_currency.rs index b05987584..77a87d3a8 100644 --- a/zrml/asset-router/src/pallet_impl/multicurrency.rs +++ b/zrml/asset-router/src/pallet_impl/multi_currency.rs @@ -46,14 +46,21 @@ 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 +72,21 @@ 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 +100,21 @@ 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 +133,46 @@ 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 +184,21 @@ 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 +207,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/multi_currency_extended.rs b/zrml/asset-router/src/pallet_impl/multi_currency_extended.rs new file mode 100644 index 000000000..70ea1f499 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multi_currency_extended.rs @@ -0,0 +1,80 @@ +// 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 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; + + fn update_balance( + currency_id: Self::CurrencyId, + who: &T::AccountId, + by_amount: Self::Amount, + ) -> DispatchResult { + if by_amount.is_zero() { + return Ok(()); + } + + 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 { + Err(Error::::UnknownAsset.into()) + } + } +} 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..55e9d5779 100644 --- a/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs +++ b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs @@ -26,6 +26,11 @@ impl MultiLockableCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { + 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, @@ -41,6 +46,11 @@ impl MultiLockableCurrency for Pallet { who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { + 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, @@ -55,6 +65,11 @@ impl MultiLockableCurrency for Pallet { currency_id: Self::CurrencyId, who: &T::AccountId, ) -> DispatchResult { + 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, diff --git a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs b/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs deleted file mode 100644 index fc2bf4f6c..000000000 --- a/zrml/asset-router/src/pallet_impl/multicurrency_extended.rs +++ /dev/null @@ -1,53 +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 . - -use crate::pallet::*; - -impl MultiCurrencyExtended for Pallet { - type Amount = >::Amount; - - fn update_balance( - currency_id: Self::CurrencyId, - 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) - } else { - Self::withdraw(currency_id, who, by_balance).map(|_| ()) - } - } -} 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..c1f70393d 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,6 +26,12 @@ impl NamedMultiReservableCurrency for Pallet { currency_id: Self::CurrencyId, who: &T::AccountId, ) -> Self::Balance { + 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, @@ -42,6 +48,11 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> DispatchResult { + 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, @@ -57,6 +68,12 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> Self::Balance { + 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, @@ -73,6 +90,12 @@ impl NamedMultiReservableCurrency for Pallet { who: &T::AccountId, value: Self::Balance, ) -> Self::Balance { + 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 @@ -91,6 +114,11 @@ impl NamedMultiReservableCurrency for Pallet { value: Self::Balance, status: Status, ) -> Result { + 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 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..e762a3115 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); @@ -98,7 +98,11 @@ fn routes_market_assets_correctly() { fn routes_currencies_correctly() { ExtBuilder::default().build().execute_with(|| { assert_eq!(AssetRouter::minimum_balance(CURRENCY), CURRENCY_MIN_BALANCE); + assert_eq!(AssetRouter::minimum_balance(CURRENCY_OLD_OUTCOME), CURRENCY_MIN_BALANCE); + test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT); + test_helper(CURRENCY_OLD_OUTCOME, CURRENCY_INITIAL_AMOUNT); + assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), 0 diff --git a/zrml/asset-router/src/tests/multi_currency.rs b/zrml/asset-router/src/tests/multi_currency.rs index 06f3d0a99..0e6802305 100644 --- a/zrml/asset-router/src/tests/multi_currency.rs +++ b/zrml/asset-router/src/tests/multi_currency.rs @@ -108,6 +108,7 @@ fn routes_currencies_correctly() { use frame_support::traits::tokens::fungibles::Inspect; test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); + test_helper(CURRENCY_OLD_OUTCOME, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); assert_eq!( >::total_issuance(CAMPAIGN_ASSET_INTERNAL), diff --git a/zrml/asset-router/src/tests/multi_lockable_currency.rs b/zrml/asset-router/src/tests/multi_lockable_currency.rs index d69e8e497..20497d243 100644 --- a/zrml/asset-router/src/tests/multi_lockable_currency.rs +++ b/zrml/asset-router/src/tests/multi_lockable_currency.rs @@ -19,6 +19,7 @@ use super::*; use orml_traits::MultiCurrency; +use test_case::test_case; fn unroutable_test_helper(asset: Assets) { assert_noop!( @@ -35,34 +36,35 @@ fn unroutable_test_helper(asset: Assets) { ); } -#[test] -fn routes_currencies_correctly() { +#[test_case(CURRENCY; "foreign")] +#[test_case(CURRENCY_OLD_OUTCOME; "old_outcome")] +fn routes_currencies_correctly(currency_id: Assets) { ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); - assert_ok!(AssetRouter::set_lock(Default::default(), CURRENCY, &ALICE, 1)); + assert_ok!(AssetRouter::deposit(currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_ok!(AssetRouter::set_lock(Default::default(), currency_id, &ALICE, 1)); assert_eq!( orml_tokens::Accounts::::get::< u128, ::CurrencyId, - >(ALICE, Default::default()) + >(ALICE, currency_id.try_into().unwrap()) .frozen, 1 ); - assert_ok!(AssetRouter::extend_lock(Default::default(), CURRENCY, &ALICE, 2)); + assert_ok!(AssetRouter::extend_lock(Default::default(), currency_id, &ALICE, 2)); assert_eq!( orml_tokens::Accounts::::get::< u128, ::CurrencyId, - >(ALICE, Default::default()) + >(ALICE, currency_id.try_into().unwrap()) .frozen, 2 ); - assert_ok!(AssetRouter::remove_lock(Default::default(), CURRENCY, &ALICE)); + assert_ok!(AssetRouter::remove_lock(Default::default(), currency_id, &ALICE)); assert_eq!( orml_tokens::Accounts::::get::< u128, ::CurrencyId, - >(ALICE, Default::default()) + >(ALICE, currency_id.try_into().unwrap()) .frozen, 0 ); diff --git a/zrml/asset-router/src/tests/multi_reservable_currency.rs b/zrml/asset-router/src/tests/multi_reservable_currency.rs index 449aa1d8a..029ce57d6 100644 --- a/zrml/asset-router/src/tests/multi_reservable_currency.rs +++ b/zrml/asset-router/src/tests/multi_reservable_currency.rs @@ -19,6 +19,7 @@ use super::*; use orml_traits::MultiCurrency; +use test_case::test_case; fn unroutable_test_helper(asset: Assets, initial_amount: ::Balance) { assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); @@ -36,19 +37,20 @@ fn unroutable_test_helper(asset: Assets, initial_amount: ::Balance) { assert_ok!(AssetRouter::deposit(asset, &ALICE, initial_amount)); @@ -39,24 +40,32 @@ fn unroutable_test_helper(asset: Assets, initial_amount: ::Unsupported ); assert_eq!(AssetRouter::unreserve_named(&Default::default(), asset, &ALICE, 1), 1); + assert_eq!(AssetRouter::reserved_balance_named(&Default::default(), asset, &ALICE), 0); } -#[test] -fn routes_currencies_correctly() { +#[test_case(CURRENCY; "foreign")] +#[test_case(CURRENCY_OLD_OUTCOME; "old_outcome")] +fn routes_currencies_correctly(currency_id: Assets) { ExtBuilder::default().build().execute_with(|| { - assert_ok!(AssetRouter::deposit(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_ok!(AssetRouter::deposit(currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); assert_ok!(AssetRouter::reserve_named( &Default::default(), - CURRENCY, + currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT )); - assert_eq!(AssetRouter::reserved_balance(CURRENCY, &ALICE), CURRENCY_INITIAL_AMOUNT); - assert_eq!(AssetRouter::slash_reserved_named(&Default::default(), CURRENCY, &ALICE, 1), 0); + assert_eq!( + AssetRouter::reserved_balance_named(&Default::default(), currency_id, &ALICE), + CURRENCY_INITIAL_AMOUNT + ); + assert_eq!( + AssetRouter::slash_reserved_named(&Default::default(), currency_id, &ALICE, 1), + 0 + ); assert_eq!( AssetRouter::repatriate_reserved_named( &Default::default(), - CURRENCY, + currency_id, &ALICE, &BOB, CURRENCY_MIN_BALANCE, @@ -65,14 +74,17 @@ fn routes_currencies_correctly() { .unwrap(), 0 ); - assert_eq!(AssetRouter::reserved_balance(CURRENCY, &BOB), CURRENCY_MIN_BALANCE); assert_eq!( - AssetRouter::reserved_balance(CURRENCY, &ALICE), + AssetRouter::reserved_balance_named(&Default::default(), currency_id, &BOB), + CURRENCY_MIN_BALANCE + ); + assert_eq!( + AssetRouter::reserved_balance_named(&Default::default(), currency_id, &ALICE), CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 1 ); - assert_eq!(AssetRouter::unreserve_named(&Default::default(), CURRENCY, &ALICE, 1), 0); + assert_eq!(AssetRouter::unreserve_named(&Default::default(), currency_id, &ALICE, 1), 0); assert_eq!( - AssetRouter::reserved_balance(CURRENCY, &ALICE), + AssetRouter::reserved_balance_named(&Default::default(), currency_id, &ALICE), CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 2 ); }); From c6c1f4964a6c558490d2b8e72b2363895d51f48a Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Thu, 22 Feb 2024 16:50:39 +0100 Subject: [PATCH 06/14] New asset system - Merge main (#1258) --- .mergify.yml | 37 +- Cargo.lock | 82 +- Cargo.toml | 7 +- README.md | 8 +- docs/STYLE_GUIDE.md | 104 +- docs/changelog_for_devs.md | 73 + docs/review_checklist.md | 2 + macros/Cargo.toml | 5 + primitives/src/macros.rs => macros/src/lib.rs | 30 +- node/Cargo.toml | 2 +- primitives/Cargo.toml | 4 +- primitives/src/assets.rs | 7 +- primitives/src/assets/all_assets.rs | 13 + primitives/src/assets/currencies.rs | 1 - primitives/src/constants/mock.rs | 8 +- primitives/src/lib.rs | 3 - primitives/src/market.rs | 45 +- primitives/src/math/check_arithm_rslt.rs | 81 - primitives/src/math/checked_ops_res.rs | 108 + primitives/src/math/consts.rs | 37 - primitives/src/math/fixed.rs | 963 ++- primitives/src/math/mod.rs | 6 +- .../swaps/src => primitives/src/math}/root.rs | 13 +- primitives/src/pool.rs | 90 - primitives/src/traits.rs | 2 + .../src/traits/market_commons_pallet_api.rs | 3 - primitives/src/traits/swaps.rs | 88 +- primitives/src/traits/zeitgeist_asset.rs | 37 + primitives/src/types.rs | 3 +- runtime/battery-station/Cargo.toml | 5 +- .../integration_tests/xcm/tests/transfers.rs | 19 +- runtime/battery-station/src/lib.rs | 76 +- runtime/battery-station/src/parameters.rs | 3 + .../battery-station/src/xcm_config/config.rs | 13 +- .../battery-station/src/xcm_config/fees.rs | 7 +- runtime/common/Cargo.toml | 2 +- runtime/common/src/fees.rs | 5 +- runtime/common/src/lib.rs | 83 +- .../src/weights/cumulus_pallet_xcmp_queue.rs | 30 +- runtime/common/src/weights/frame_system.rs | 66 +- runtime/common/src/weights/orml_currencies.rs | 50 +- runtime/common/src/weights/orml_tokens.rs | 44 +- runtime/common/src/weights/pallet_assets.rs | 402 +- .../src/weights/pallet_author_inherent.rs | 22 +- .../src/weights/pallet_author_mapping.rs | 54 +- .../src/weights/pallet_author_slot_filter.rs | 18 +- runtime/common/src/weights/pallet_balances.rs | 42 +- runtime/common/src/weights/pallet_bounties.rs | 77 +- .../common/src/weights/pallet_collective.rs | 202 +- .../common/src/weights/pallet_contracts.rs | 1165 ++- .../common/src/weights/pallet_democracy.rs | 176 +- runtime/common/src/weights/pallet_identity.rs | 204 +- .../common/src/weights/pallet_membership.rs | 101 +- runtime/common/src/weights/pallet_multisig.rs | 94 +- .../src/weights/pallet_parachain_staking.rs | 326 +- runtime/common/src/weights/pallet_preimage.rs | 74 +- runtime/common/src/weights/pallet_proxy.rs | 125 +- .../common/src/weights/pallet_scheduler.rs | 99 +- .../common/src/weights/pallet_timestamp.rs | 24 +- runtime/common/src/weights/pallet_treasury.rs | 67 +- runtime/common/src/weights/pallet_utility.rs | 46 +- runtime/common/src/weights/pallet_vesting.rs | 118 +- runtime/zeitgeist/Cargo.toml | 5 +- .../integration_tests/xcm/tests/transfers.rs | 20 +- runtime/zeitgeist/src/lib.rs | 38 +- runtime/zeitgeist/src/parameters.rs | 3 + runtime/zeitgeist/src/xcm_config/config.rs | 13 +- runtime/zeitgeist/src/xcm_config/fees.rs | 7 +- scripts/benchmarks/configuration.sh | 4 +- scripts/benchmarks/quick_check.sh | 3 +- scripts/update-copyright.sh | 4 +- zrml/asset-router/Cargo.toml | 1 + zrml/asset-router/src/lib.rs | 5 +- zrml/authorized/Cargo.toml | 3 +- zrml/authorized/src/lib.rs | 4 +- zrml/authorized/src/migrations.rs | 58 +- zrml/authorized/src/mock.rs | 9 +- zrml/authorized/src/weights.rs | 70 +- zrml/court/Cargo.toml | 3 +- zrml/court/src/benchmarks.rs | 130 +- zrml/court/src/lib.rs | 251 +- zrml/court/src/mock.rs | 18 +- zrml/court/src/tests.rs | 60 +- zrml/court/src/types.rs | 1 + zrml/court/src/weights.rs | 295 +- zrml/global-disputes/Cargo.toml | 3 +- zrml/global-disputes/src/mock.rs | 10 +- zrml/global-disputes/src/utils.rs | 4 +- zrml/global-disputes/src/weights.rs | 123 +- zrml/liquidity-mining/Cargo.toml | 3 +- zrml/liquidity-mining/src/mock.rs | 8 +- zrml/liquidity-mining/src/tests.rs | 4 +- zrml/liquidity-mining/src/weights.rs | 18 +- zrml/market-commons/Cargo.toml | 4 +- zrml/market-commons/src/lib.rs | 52 +- zrml/market-commons/src/migrations.rs | 60 +- zrml/market-commons/src/mock.rs | 8 +- zrml/market-commons/src/tests.rs | 4 +- zrml/neo-swaps/Cargo.toml | 8 +- zrml/neo-swaps/README.md | 115 +- zrml/neo-swaps/docs/docs.pdf | Bin 204370 -> 204581 bytes zrml/neo-swaps/docs/docs.tex | 52 +- zrml/neo-swaps/src/benchmarking.rs | 370 +- zrml/neo-swaps/src/consts.rs | 30 +- zrml/neo-swaps/src/helpers.rs | 34 + zrml/neo-swaps/src/lib.rs | 306 +- 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/math.rs | 475 +- zrml/neo-swaps/src/migration.rs | 31 + zrml/neo-swaps/src/mock.rs | 150 +- zrml/neo-swaps/src/tests/buy.rs | 131 +- zrml/neo-swaps/src/tests/buy_and_sell.rs | 175 + zrml/neo-swaps/src/tests/deploy_pool.rs | 120 +- zrml/neo-swaps/src/tests/exit.rs | 251 +- zrml/neo-swaps/src/tests/join.rs | 154 +- .../src/tests/liquidity_tree_interactions.rs | 58 + zrml/neo-swaps/src/tests/mod.rs | 39 +- zrml/neo-swaps/src/tests/sell.rs | 182 +- zrml/neo-swaps/src/tests/withdraw_fees.rs | 112 +- .../src/traits/liquidity_shares_manager.rs | 8 +- zrml/neo-swaps/src/traits/pool_operations.rs | 20 +- zrml/neo-swaps/src/types/mod.rs | 4 +- zrml/neo-swaps/src/types/pool.rs | 17 +- zrml/neo-swaps/src/types/solo_lp.rs | 88 - zrml/neo-swaps/src/weights.rs | 207 +- zrml/orderbook/Cargo.toml | 5 +- zrml/orderbook/README.md | 47 +- .../fuzz/orderbook_v1_full_workflow.rs | 56 +- zrml/orderbook/src/benchmarks.rs | 100 +- zrml/orderbook/src/lib.rs | 551 +- zrml/orderbook/src/migrations.rs | 16 + zrml/orderbook/src/mock.rs | 55 +- zrml/orderbook/src/tests.rs | 945 ++- zrml/orderbook/src/types.rs | 15 +- zrml/orderbook/src/weights.rs | 124 +- zrml/parimutuel/Cargo.toml | 3 +- zrml/parimutuel/src/benchmarking.rs | 15 +- zrml/parimutuel/src/lib.rs | 47 +- zrml/parimutuel/src/mock.rs | 6 +- zrml/parimutuel/src/tests/buy.rs | 21 +- zrml/parimutuel/src/tests/claim.rs | 50 +- zrml/parimutuel/src/tests/refund.rs | 13 +- zrml/parimutuel/src/weights.rs | 72 +- zrml/prediction-markets/Cargo.toml | 6 +- .../fuzz/pm_full_workflow.rs | 4 +- .../prediction-markets/runtime-api/Cargo.toml | 2 +- zrml/prediction-markets/src/benchmarks.rs | 501 +- zrml/prediction-markets/src/lib.rs | 1006 +-- zrml/prediction-markets/src/migrations.rs | 405 +- zrml/prediction-markets/src/mock.rs | 87 +- zrml/prediction-markets/src/tests.rs | 6426 ----------------- .../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 + zrml/prediction-markets/src/weights.rs | 853 +-- zrml/rikiddo/Cargo.toml | 4 +- zrml/rikiddo/src/mock.rs | 4 + zrml/simple-disputes/Cargo.toml | 3 +- zrml/simple-disputes/src/lib.rs | 38 +- zrml/simple-disputes/src/mock.rs | 6 +- zrml/simple-disputes/src/tests.rs | 4 +- zrml/styx/Cargo.toml | 3 +- zrml/styx/src/lib.rs | 10 +- zrml/styx/src/mock.rs | 4 + zrml/styx/src/weights.rs | 22 +- zrml/swaps/Cargo.toml | 11 +- zrml/swaps/fuzz/create_pool.rs | 9 +- zrml/swaps/fuzz/utils.rs | 21 +- zrml/swaps/rpc/Cargo.toml | 2 +- zrml/swaps/rpc/src/lib.rs | 43 - zrml/swaps/runtime-api/Cargo.toml | 2 +- zrml/swaps/runtime-api/src/lib.rs | 15 +- zrml/swaps/src/arbitrage.rs | 450 -- zrml/swaps/src/benchmarks.rs | 848 +-- 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 | 2003 +---- zrml/swaps/src/math.rs | 119 +- zrml/swaps/src/migrations.rs | 1 + zrml/swaps/src/mock.rs | 100 +- zrml/swaps/src/tests.rs | 2545 +------ zrml/swaps/src/types/mod.rs | 20 + .../swaps/src/types/pool.rs | 43 +- zrml/swaps/src/utils.rs | 182 +- zrml/swaps/src/weights.rs | 553 +- 225 files changed, 17319 insertions(+), 19839 deletions(-) create mode 100644 macros/Cargo.toml rename primitives/src/macros.rs => macros/src/lib.rs (70%) 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 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 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/buy_and_sell.rs create mode 100644 zrml/neo-swaps/src/tests/liquidity_tree_interactions.rs delete mode 100644 zrml/neo-swaps/src/types/solo_lp.rs create mode 100644 zrml/orderbook/src/migrations.rs 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 delete mode 100644 zrml/swaps/src/arbitrage.rs delete mode 100644 zrml/swaps/src/check_arithm_rslt.rs delete mode 100644 zrml/swaps/src/consts.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/.mergify.yml b/.mergify.yml index ece3525e5..07c49ea76 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 @@ -34,4 +34,37 @@ pull_request_rules: - s:accepted - s:in-progress - s:review-needed + - name: Set in-progress label after changes are pushed + conditions: + - commits[-1].author!=mergify[bot] + - 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] + - commits[-1].date_committer>=0 days 00:01 ago + - queue-position=0 + actions: + label: + add: + - 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 diff --git a/Cargo.lock b/Cargo.lock index 5315217cb..44c60c1df 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.3" dependencies = [ "cfg-if", "common-runtime", @@ -602,6 +602,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-debug-derive", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -1207,7 +1208,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.4.1" +version = "0.4.3" dependencies = [ "cfg-if", "cumulus-pallet-xcmp-queue", @@ -2553,9 +2554,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", @@ -2731,7 +2732,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", ] @@ -3608,8 +3609,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", @@ -14294,9 +14295,13 @@ dependencies = [ "time 0.3.24", ] +[[package]] +name = "zeitgeist-macros" +version = "0.4.3" + [[package]] name = "zeitgeist-node" -version = "0.4.1" +version = "0.4.3" dependencies = [ "battery-station-runtime", "cfg-if", @@ -14388,13 +14393,14 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.1" +version = "0.4.3" dependencies = [ "arbitrary", "fixed", "frame-support", "frame-system", "more-asserts", + "num-traits", "orml-currencies", "orml-tokens", "orml-traits", @@ -14410,7 +14416,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.1" +version = "0.4.3" dependencies = [ "cfg-if", "common-runtime", @@ -14479,6 +14485,7 @@ dependencies = [ "sp-block-builder", "sp-consensus-aura", "sp-core", + "sp-debug-derive", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -14549,13 +14556,15 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", ] [[package]] name = "zrml-authorized" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14571,9 +14580,10 @@ dependencies = [ [[package]] name = "zrml-court" -version = "0.4.1" +version = "0.4.3" dependencies = [ "arrayvec 0.7.4", + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14595,8 +14605,9 @@ dependencies = [ [[package]] name = "zrml-global-disputes" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14615,8 +14626,9 @@ dependencies = [ [[package]] name = "zrml-liquidity-mining" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14633,8 +14645,9 @@ dependencies = [ [[package]] name = "zrml-market-commons" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-support", "frame-system", "pallet-balances", @@ -14644,13 +14657,16 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", + "test-case", "zeitgeist-primitives", ] [[package]] name = "zrml-neo-swaps" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "cfg-if", + "env_logger 0.10.1", "fixed", "frame-benchmarking", "frame-support", @@ -14672,7 +14688,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", @@ -14693,11 +14708,13 @@ dependencies = [ [[package]] name = "zrml-orderbook" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", + "log", "orml-currencies", "orml-tokens", "orml-traits", @@ -14726,8 +14743,9 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14747,8 +14765,9 @@ dependencies = [ [[package]] name = "zrml-prediction-markets" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14769,7 +14788,6 @@ dependencies = [ "sp-arithmetic", "sp-io", "sp-runtime", - "substrate-fixed", "test-case", "xcm", "zeitgeist-primitives", @@ -14801,7 +14819,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets-runtime-api" -version = "0.4.1" +version = "0.4.3" dependencies = [ "parity-scale-codec", "sp-api", @@ -14810,10 +14828,11 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.1" +version = "0.4.3" dependencies = [ "arbitrary", "cfg-if", + "env_logger 0.10.1", "frame-support", "frame-system", "hashbrown 0.12.3", @@ -14843,8 +14862,9 @@ dependencies = [ [[package]] name = "zrml-simple-disputes" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14863,8 +14883,9 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14879,8 +14900,9 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.1" +version = "0.4.3" dependencies = [ + "env_logger 0.10.1", "frame-benchmarking", "frame-support", "frame-system", @@ -14892,16 +14914,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", ] @@ -14922,7 +14944,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.1" +version = "0.4.3" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14935,7 +14957,7 @@ dependencies = [ [[package]] name = "zrml-swaps-runtime-api" -version = "0.4.1" +version = "0.4.3" dependencies = [ "parity-scale-codec", "sp-api", diff --git a/Cargo.toml b/Cargo.toml index 12bb0f272..ed99cccaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] default-members = [ + "macros", "node", "primitives", "runtime/common", @@ -24,6 +25,7 @@ default-members = [ "zrml/styx", ] members = [ + "macros", "node", "primitives", "runtime/common", @@ -193,6 +195,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 } @@ -230,6 +233,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-asset-router = { path = "zrml/asset-router", default-features = false } zrml-authorized = { path = "zrml/authorized", default-features = false } @@ -250,6 +254,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" @@ -262,7 +267,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/README.md b/README.md index e8b94e1c0..66d67ba96 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 @@ -35,9 +35,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/docs/STYLE_GUIDE.md b/docs/STYLE_GUIDE.md index 84e5126ea..4974fb753 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,59 @@ 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. +- 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. +- 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. +- 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/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index e2070d9e9..70afccec1 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -12,6 +12,79 @@ As of 0.3.9, the changelog's format is based on components which query the chain's storage, the extrinsics or the runtime APIs/RPC interface. +## v0.5.0 + +[#1197]: https://github.com/zeitgeistpm/zeitgeist/pull/1197 +[#1178]: https://github.com/zeitgeistpm/zeitgeist/pull/1178 + +### Changes + +- ⚠️ Move the `zeitgeist_primitives::Pool` struct to `zrml_swaps::types::Pool` and change the following fields ([#1197]): + - Remove `market_id` + - Make `swap_fee` non-optional + - Remove `total_subsidy` + - Make `total_weight` non-optional + - Make `weights` non-optional +- ⚠️ Change the type of `liquidity_shares_manager` in `zrml_neo_swaps::types::Pool` from `zrml_neo_swaps::types::SoloLp` to `zrml_neo_swaps::types::LiquidityTree`. Details on the liquidity tree can be found in the `README.md` of zrml-neo-swaps and the documentation of the `LiquidityTree` object ([#1179]). + +### Migrations + +- Closed all CPMM pools. Withdrawals are still allowed. Creating new pools will + be impossible until further updates are deployed. ([#1197]) +- Remove all Rikiddo storage elements. ([#1197]) +- Migrate neo-swaps `Pools` storage. The market creator's liquidity position is translated into a position in the liquidity tree of the same value ([#1178]). + +### Removed + +- ⚠️ Remove the `Disputes` storage element from zrml-prediction-markets. + ([#1197]) +- ⚠️ Remove the following extrinsics from zrml-prediction-markets. ([#1197]): + - `create_cpmm_market_and_deploy_assets` + - `deploy_swap_pool_and_additional_liquidity` + - `deploy_swap_pool_for_market` +- ⚠️ Remove the following config values from zrml-prediction-markets ([#1197]): + - `MaxSubsidyPeriod` + - `MinSubsidyPeriod` + - `Swaps` +- Remove automatic arbitrage for CPMM pools. ([#1197]) +- ⚠️ Remove the following extrinsics from zrml-swaps ([#1197]): + - `admin_clean_up_pool` + - `pool_exit_subsidy` + - `pool_join_subsidy` +- ⚠️ Remove the following config values from zrml-swaps ([#1197]): + - `FixedTypeU` + - `FixedTypeS` + - `LiquidityMining` + - `MarketCommons` + - `MinSubsidy` + - `MinSubsidyPerAccount` + - `RikiddoSigmoidFeeMarketEma` +- ⚠️ Remove `CPMM` and `RikiddoSigmoidFeeMarketEma` from `ScoringRule`. + ([#1197]) +- ⚠️ Remove `Suspended`, `CollectingSubsidy` and `InsufficientSubsidy` from + `MarketStatus`. ([#1197]) + +### Deprecate + +- ⚠️ Deprecate the following storage elements of zrml-prediction-markets (will + be removed in v0.5.1; [#1197]): + - `MarketIdsPerOpenBlock` + - `MarketIdsPerOpenTimeFrame` + - `MarketsCollectingSubsidy` +- ⚠️ Deprecate the following storage elements of zrml-market-commons (will be + removed at an unspecified point in time; [#1197]): + - `MarketPool` +- ⚠️ Deprecate the following storage elements of zrml-swaps (will be removed in + v0.5.1; [#1197]): + - `SubsidyProviders` + - `PoolsCachedForArbitrage` + +## v0.4.3 + +### Removed + +- Remove old storage migrations + ## v0.4.2 [#1127]: https://github.com/zeitgeistpm/zeitgeist/pull/1127 diff --git a/docs/review_checklist.md b/docs/review_checklist.md index 90c94b2ce..3b9220c61 100644 --- a/docs/review_checklist.md +++ b/docs/review_checklist.md @@ -53,6 +53,7 @@ - [ ] The try-runtime passes without any warnings (substrate storage operations often just log a warning instead of failing, so these warnings usually point to problem which could break the storage). +- [ ] Ensure that the [STYLE_GUIDE] is observed. ## Events @@ -85,3 +86,4 @@ Additional info (similar to the remark emitted by anywhere in the chain storage. [docs.zeitgeist.pm]: docs.zeitgeist.pm +[STYLE_GUIDE]: ./STYLE_GUIDE.md diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 000000000..86492935c --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,5 @@ +[package] +authors = ["Zeitgeist PM "] +edition = "2021" +name = "zeitgeist-macros" +version = "0.4.3" diff --git a/primitives/src/macros.rs b/macros/src/lib.rs similarity index 70% rename from primitives/src/macros.rs rename to macros/src/lib.rs index f4b4c04b4..b6ee80362 100644 --- a/primitives/src/macros.rs +++ b/macros/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2024 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -15,10 +15,38 @@ // 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 +/// // 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::>() + } +} + /// 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)*) => { diff --git a/node/Cargo.toml b/node/Cargo.toml index bf169fa24..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.1" +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 bca638965..4a4bca7b9 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 } @@ -21,6 +22,7 @@ typenum = { workspace = true } [features] default = ["std"] mock = [] +runtime-benchmarks = [] std = [ "frame-support/std", "frame-system/std", @@ -37,4 +39,4 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zeitgeist-primitives" -version = "0.4.1" +version = "0.4.3" diff --git a/primitives/src/assets.rs b/primitives/src/assets.rs index 104df8c3a..c187096bd 100644 --- a/primitives/src/assets.rs +++ b/primitives/src/assets.rs @@ -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::{CampaignAssetId, CategoryIndex, CustomAssetId, PoolId}; +#[cfg(feature = "runtime-benchmarks")] +use crate::traits::ZeitgeistAssetEnumerator; +use crate::{ + traits::PoolSharesId, + types::{CampaignAssetId, CategoryIndex, CustomAssetId, PoolId}, +}; use parity_scale_codec::{Compact, CompactAs, Decode, Encode, HasCompact, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs index 7145da0a0..120f02643 100644 --- a/primitives/src/assets/all_assets.rs +++ b/primitives/src/assets/all_assets.rs @@ -80,6 +80,19 @@ pub enum Asset { CustomAssetClass(#[codec(compact)] CustomAssetId), } +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 { diff --git a/primitives/src/assets/currencies.rs b/primitives/src/assets/currencies.rs index 25fac624e..9ec2e0bba 100644 --- a/primitives/src/assets/currencies.rs +++ b/primitives/src/assets/currencies.rs @@ -36,7 +36,6 @@ pub enum CurrencyClass { #[codec(index = 3)] PoolShare(PoolId), - // Type can not be compacted as it is already used uncompacted in the storage #[codec(index = 5)] ForeignAsset(u32), diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index dd6e82d72..b2e19c2bd 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -65,6 +65,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; } @@ -91,6 +92,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"); } @@ -112,13 +114,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"); @@ -144,8 +142,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 b326a15de..e46cd5fbf 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -22,13 +22,10 @@ extern crate alloc; mod assets; pub mod constants; -pub mod macros; mod market; pub mod math; mod max_runtime_usize; mod outcome_report; -mod pool; -mod pool_status; mod proxy_type; pub mod traits; pub mod types; diff --git a/primitives/src/market.rs b/primitives/src/market.rs index 1a9751ca2..cae1eb2bc 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. @@ -353,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}; @@ -439,7 +416,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/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..02e9ec3de --- /dev/null +++ b/primitives/src/math/checked_ops_res.rs @@ -0,0 +1,108 @@ +// 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 num_traits::{checked_pow, One}; +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; +} + +pub trait CheckedPowRes +where + Self: Sized, +{ + fn checked_pow_res(&self, exp: usize) -> 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)) + } +} + +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/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..eff7434d5 100644 --- a/primitives/src/math/fixed.rs +++ b/primitives/src/math/fixed.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -22,176 +22,188 @@ // 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) }) +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(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) -} +/// 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 bmul_floor(a: u128, b: u128) -> Result { - // checked_mul already rounds down - let c0 = a.check_mul_rslt(&b)?; - c0.check_div_rslt(&BASE) -} + /// Calculates the product of `self` and `other` and rounds down. + fn bmul_floor(&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 up. + fn bmul_ceil(&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) +/// 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; + + /// Calculates the fixed point division of `self` by `other` and rounds down. + fn bdiv_floor(&self, other: Self) -> Result; + + /// Calculates the fixed point division of `self` by `other` and rounds up. + fn bdiv_ceil(&self, other: Self) -> Result; } -pub fn bpowi(a: u128, n: u128) -> Result { - let mut z = if n % 2 != 0 { a } else { BASE }; +/// 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; - let mut b = a; - let mut m = n.check_div_rslt(&2)?; + /// Calculates the fixed point product `self * multiplier / divisor` and rounds down. + fn bmul_bdiv_floor(&self, multiplier: Self, divisor: Self) -> Result; - while m != 0 { - b = bmul(b, b)?; + /// Calculates the fixed point product `self * multiplier / divisor` and rounds up. + fn bmul_bdiv_ceil(&self, multiplier: Self, divisor: Self) -> Result; +} - if m % 2 != 0 { - z = bmul(z, b)?; - } +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()?) + } - m = m.check_div_rslt(&2)?; + fn bmul_floor(&self, other: Self) -> Result { + self.checked_mul_res(&other)?.checked_div_res(&ZeitgeistBase::get()?) } - Ok(z) + 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 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)?)?; +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) + } - if remain == 0 { - return Ok(whole_pow); + fn bdiv_floor(&self, other: Self) -> Result { + self.checked_mul_res(&ZeitgeistBase::get()?)?.checked_div_res(&other) } - let partial_result = bpow_approx(base, remain)?; - bmul(whole_pow, partial_result) + 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) + } } -/// Compute an estimate of the power `base ** exp`. +/// Helper function for implementing `FixedMulDiv` in a numerically clean way. /// -/// # 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")); - } - - 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; - } +/// 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()?) + } +} - 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)?; - } +/// 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) } - // 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 bmul_bdiv_floor(&self, multiplier: Self, divisor: Self) -> Result { + bmul_bdiv_common(self, multiplier, divisor, Zero::zero()) } - Ok(sum) + 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) + } } /// Converts a fixed point decimal number into another type. @@ -318,262 +330,489 @@ 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)) + ); + } + + // 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 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 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)] diff --git a/primitives/src/math/mod.rs b/primitives/src/math/mod.rs index 42a27f245..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. // @@ -15,6 +15,6 @@ // 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; +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 4e1222e4f..bb0cea540 100644 --- a/zrml/swaps/src/root.rs +++ b/primitives/src/math/root.rs @@ -1,4 +1,4 @@ -// Copyright 2022 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::{bmul, 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 = bmul(_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 ac4184819..000000000 --- a/primitives/src/pool.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2023-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 crate::{ - constants::MAX_ASSETS, - types::{Asset, PoolStatus}, -}; -use alloc::{collections::BTreeMap, vec::Vec}; -use parity_scale_codec::{Compact, Decode, Encode, HasCompact, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::{RuntimeDebug, SaturatedConversion}; - -#[derive(TypeInfo, Clone, Encode, Eq, Decode, PartialEq, RuntimeDebug)] -pub struct Pool -where - MarketId: MaxEncodedLen + HasCompact, -{ - 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 + HasCompact, -{ - 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 + HasCompact, -{ - 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 b00c2e0ce..02d8d70f1 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -24,6 +24,7 @@ mod market_commons_pallet_api; mod market_id; mod swaps; mod weights; +mod zeitgeist_asset; mod zeitgeist_multi_reservable_currency; pub use complete_set_operations_api::CompleteSetOperationsApi; @@ -34,4 +35,5 @@ pub use market_commons_pallet_api::MarketCommonsPalletApi; pub use market_id::MarketId; pub use swaps::Swaps; pub use weights::CheckedDivPerComponent; +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 59aecfc43..1c8f7ae93 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -85,9 +85,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 43cf3f9de..4d914a6ec 100644 --- a/primitives/src/traits/swaps.rs +++ b/primitives/src/traits/swaps.rs @@ -16,16 +16,15 @@ // 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::{HasCompact, MaxEncodedLen}; pub trait Swaps { - type Balance: MaxEncodedLen; - type MarketId: MaxEncodedLen + HasCompact; + type Asset: MaxEncodedLen; + type Balance: HasCompact + MaxEncodedLen; + // TODO(#1216): Add weight type which implements `Into` and `From` /// Creates an initial active pool. /// @@ -41,16 +40,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 +54,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 +65,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 +86,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 +106,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 +116,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 +131,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 +141,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 39ff1a294..e759f3b65 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -17,8 +17,7 @@ // along with Zeitgeist. If not, see . pub use crate::{ - assets::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, pool::*, - pool_status::PoolStatus, proxy_type::*, + assets::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, }; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 5e7bdceb3..e478ec077 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -38,6 +38,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 } @@ -418,12 +419,14 @@ 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 "] edition = "2021" name = "battery-station-runtime" -version = "0.4.1" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs index 64105768c..149a7f093 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs @@ -177,10 +177,11 @@ fn transfer_btc_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = btc(0); let initial_sovereign_balance = btc(100); let transfer_amount = btc(100); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_btc(None); - + treasury_initial_balance = Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -230,6 +231,13 @@ fn transfer_btc_sibling_to_zeitgeist() { Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) + ) }); } @@ -289,9 +297,12 @@ fn transfer_roc_from_relay_chain() { TestNet::reset(); let transfer_amount: Balance = roc(1); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_foreign_parent(None); + treasury_initial_balance = + Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()); }); RococoNet::execute_with(|| { @@ -311,6 +322,12 @@ fn transfer_roc_from_relay_chain() { let expected = transfer_amount - roc_fee(); let expected_adjusted = adjusted_balance(roc(1), expected); assert_eq!(Tokens::free_balance(FOREIGN_PARENT_ID, &BOB), expected_adjusted); + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(roc(1), roc_fee()) + ) }); } diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index d99e2dc95..84e050e86 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. @@ -58,15 +58,13 @@ use sp_version::NativeVersion; use substrate_fixed::{types::extra::U33, FixedI128, FixedU128}; use zeitgeist_primitives::{constants::*, types::*}; use zrml_prediction_markets::Call::{ - buy_complete_set, create_cpmm_market_and_deploy_assets, create_market, - deploy_swap_pool_and_additional_liquidity, deploy_swap_pool_for_market, dispute, edit_market, - redeem_shares, report, sell_complete_set, + buy_complete_set, create_market, dispute, edit_market, redeem_shares, report, sell_complete_set, }; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; use zrml_swaps::Call::{ - pool_exit, pool_exit_with_exact_asset_amount, pool_exit_with_exact_pool_amount, pool_join, - pool_join_with_exact_asset_amount, pool_join_with_exact_pool_amount, swap_exact_amount_in, - swap_exact_amount_out, + force_pool_exit, pool_exit, pool_exit_with_exact_asset_amount, + pool_exit_with_exact_pool_amount, pool_join, pool_join_with_exact_asset_amount, + pool_join_with_exact_pool_amount, swap_exact_amount_in, swap_exact_amount_out, }; #[cfg(feature = "parachain")] use { @@ -106,10 +104,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: 52, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 25, + transaction_version: 26, state_version: 1, }; @@ -129,22 +127,18 @@ 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 SimpleDisputes dispute mechanism + // Only allow markets using Authorized or Court dispute mechanism create_market { - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - 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), - scoring_rule: ScoringRule::CPMM, + dispute_mechanism: + Some(MarketDisputeMechanism::Authorized) + | Some(MarketDisputeMechanism::Court), .. } => true, redeem_shares { .. } => true, @@ -164,6 +158,8 @@ impl Contains for ContractsCallfilter { swap_exact_amount_out { .. } => true, _ => false, }, + RuntimeCall::Orderbook(_) => true, + RuntimeCall::Parimutuel(_) => true, _ => false, } } @@ -172,37 +168,27 @@ impl Contains for ContractsCallfilter { #[derive(scale_info::TypeInfo)] pub struct IsCallable; -// Currently disables Rikiddo. impl Contains for IsCallable { fn contains(call: &RuntimeCall) -> bool { #[allow(clippy::match_like_matches_macro)] match call { RuntimeCall::SimpleDisputes(_) => false, RuntimeCall::LiquidityMining(_) => false, - RuntimeCall::PredictionMarkets(inner_call) => { - match inner_call { - // Disable Rikiddo and SimpleDisputes markets - create_market { - scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. - } => false, - create_market { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - edit_market { - scoring_rule: ScoringRule::RikiddoSigmoidFeeMarketEma, .. - } => false, - create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - edit_market { - dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), - .. - } => false, - _ => true, - } - } + RuntimeCall::PredictionMarkets(inner_call) => match inner_call { + create_market { + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), + .. + } => false, + edit_market { + dispute_mechanism: Some(MarketDisputeMechanism::SimpleDisputes), + .. + } => false, + _ => true, + }, + RuntimeCall::Swaps(inner_call) => match inner_call { + force_pool_exit { .. } => true, + _ => false, + }, _ => true, } } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index b4697e5a4..e5892fc62 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -170,6 +170,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. @@ -234,6 +236,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: Assets = Asset::Ztg; diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs index 197151614..ef4a1da90 100644 --- a/runtime/battery-station/src/xcm_config/config.rs +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -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, }; @@ -153,13 +153,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/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs index 138ed3183..29ae027ab 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, Currencies}; 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/Cargo.toml b/runtime/common/Cargo.toml index 3663efd37..a72792e79 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -82,7 +82,7 @@ std = [ authors = ["Zeitgeist PM "] edition = "2021" name = "common-runtime" -version = "0.4.1" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index d5f53a216..b3072c061 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/common/src/lib.rs b/runtime/common/src/lib.rs index feafacdca..ba4cf89c0 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -60,11 +60,7 @@ macro_rules! decl_common_types { type Address = sp_runtime::MultiAddress; - #[cfg(feature = "parachain")] - type Migrations = (zrml_prediction_markets::migrations::AddEarlyCloseBonds,); - - #[cfg(not(feature = "parachain"))] - type Migrations = (zrml_prediction_markets::migrations::AddEarlyCloseBonds,); + type Migrations = (); pub type Executive = frame_executive::Executive< Runtime, @@ -376,7 +372,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::*; @@ -391,7 +387,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 = (); @@ -586,7 +583,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 = (); @@ -737,7 +737,6 @@ macro_rules! impl_config_traits { type WeightInfo = weights::pallet_assets::WeightInfo; } - impl pallet_balances::Config for Runtime { type AccountStore = System; type Balance = Balance; @@ -786,7 +785,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; @@ -836,7 +835,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; @@ -927,7 +927,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, @@ -943,26 +946,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, @@ -992,12 +997,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 { .. }) @@ -1119,7 +1118,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; } @@ -1206,6 +1206,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; @@ -1227,7 +1228,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; } @@ -1286,9 +1286,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; @@ -1301,7 +1299,6 @@ macro_rules! impl_config_traits { type ResolveOrigin = EnsureRoot; type SimpleDisputes = SimpleDisputes; type Slash = Treasury; - type Swaps = Swaps; type ValidityBond = ValidityBond; type WeightInfo = zrml_prediction_markets::weights::WeightInfo; } @@ -1351,15 +1348,9 @@ macro_rules! impl_config_traits { } impl zrml_swaps::Config for Runtime { + type Asset = Assets; 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; @@ -1367,11 +1358,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; } @@ -1392,12 +1380,14 @@ 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; } impl zrml_orderbook::Config for Runtime { type AssetManager = AssetManager; + type ExternalFees = MarketCreatorFee; type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; type PalletId = OrderbookPalletId; @@ -1413,7 +1403,7 @@ macro_rules! impl_config_traits { type PalletId = ParimutuelPalletId; type WeightInfo = zrml_parimutuel::weights::WeightInfo; } - } + }; } // Implement runtime apis @@ -1926,13 +1916,6 @@ macro_rules! create_runtime_api { fn pool_shares_id(pool_id: PoolId) -> Asset { Asset::PoolShare(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/common/src/weights/cumulus_pallet_xcmp_queue.rs b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs index 2e0f4aea3..f24d34abb 100644 --- a/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/runtime/common/src/weights/cumulus_pallet_xcmp_queue.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=cumulus_pallet_xcmp_queue // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -56,10 +56,10 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for /// Proof Skipped: XcmpQueue QueueConfig (max_values: Some(1), max_size: None, mode: Measured) fn set_config_with_u32() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `637` - // Minimum execution time: 9_990 nanoseconds. - Weight::from_parts(10_420_000, 637) + // Measured: `175` + // Estimated: `670` + // Minimum execution time: 7_090 nanoseconds. + Weight::from_parts(7_090_000, 670) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -67,10 +67,10 @@ impl cumulus_pallet_xcmp_queue::weights::WeightInfo for /// Proof Skipped: XcmpQueue QueueConfig (max_values: Some(1), max_size: None, mode: Measured) fn set_config_with_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `637` - // Minimum execution time: 10_040 nanoseconds. - Weight::from_parts(10_910_000, 637) + // Measured: `175` + // Estimated: `670` + // Minimum execution time: 5_250 nanoseconds. + Weight::from_parts(5_250_000, 670) .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..a5b68948f 100644 --- a/runtime/common/src/weights/frame_system.rs +++ b/runtime/common/src/weights/frame_system.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=frame_system // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -53,24 +53,20 @@ use frame_support::{ pub struct WeightInfo(PhantomData); impl frame_system::weights::WeightInfo for WeightInfo { /// The range of component `b` is `[0, 3932160]`. - fn remark(b: u32) -> Weight { + fn remark(_b: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_330 nanoseconds. - Weight::from_parts(3_530_000, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(832, 0).saturating_mul(b.into())) + // Minimum execution time: 2_430 nanoseconds. + Weight::from_parts(385_840_000, 0) } /// The range of component `b` is `[0, 3932160]`. - fn remark_with_event(b: u32) -> Weight { + fn remark_with_event(_b: u32) -> 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: 5_830 nanoseconds. + Weight::from_parts(3_189_292_000, 0) } /// Storage: System Digest (r:1 w:1) /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) @@ -80,49 +76,39 @@ impl frame_system::weights::WeightInfo for WeightInfo Weight { + fn set_storage(_i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_570 nanoseconds. - Weight::from_parts(3_640_000, 0) - // Standard Error: 4_801 - .saturating_add(Weight::from_parts(1_148_373, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + // Minimum execution time: 1_910 nanoseconds. + Weight::from_parts(495_902_000, 0).saturating_add(T::DbWeight::get().writes(1000)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1000]`. - fn kill_storage(i: u32) -> Weight { + fn kill_storage(_i: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_430 nanoseconds. - Weight::from_parts(3_640_000, 0) - // Standard Error: 4_580 - .saturating_add(Weight::from_parts(943_402, 0).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + // Minimum execution time: 1_970 nanoseconds. + Weight::from_parts(390_580_000, 0).saturating_add(T::DbWeight::get().writes(1000)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[0, 1000]`. - fn kill_prefix(p: u32) -> Weight { + fn kill_prefix(_p: u32) -> 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())) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) - .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) + // Measured: `6 + p * (69 ±0)` + // Estimated: `69775` + // Minimum execution time: 4_410 nanoseconds. + Weight::from_parts(1_001_496_000, 69775).saturating_add(T::DbWeight::get().writes(1000)) } } diff --git a/runtime/common/src/weights/orml_currencies.rs b/runtime/common/src/weights/orml_currencies.rs index 3288d6395..1cea0ce1c 100644 --- a/runtime/common/src/weights/orml_currencies.rs +++ b/runtime/common/src/weights/orml_currencies.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=orml_currencies // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/orml_weight_template.hbs @@ -49,17 +49,19 @@ use frame_support::{traits::Get, weights::Weight}; /// Weight functions for orml_currencies (automatically generated) pub struct WeightInfo(PhantomData); impl orml_currencies::WeightInfo for WeightInfo { + /// Storage: MarketAssets Asset (r:1 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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 transfer_non_native_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1719` - // Estimated: `7803` - // Minimum execution time: 80_790 nanoseconds. - Weight::from_parts(81_670_000, 7803) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `1862` + // Estimated: `10503` + // Minimum execution time: 36_721 nanoseconds. + Weight::from_parts(36_721_000, 10503) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: System Account (r:1 w:1) @@ -68,11 +70,13 @@ 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: 26_811 nanoseconds. + Weight::from_parts(26_811_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: MarketAssets Asset (r:1 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:1 w:1) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:1 w:1) @@ -81,11 +85,11 @@ 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` - // Estimated: `7723` - // Minimum execution time: 56_660 nanoseconds. - Weight::from_parts(68_620_000, 7723) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `1470` + // Estimated: `10423` + // Minimum execution time: 27_511 nanoseconds. + Weight::from_parts(27_511_000, 10423) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: System Account (r:1 w:1) @@ -94,8 +98,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: 25_150 nanoseconds. + Weight::from_parts(25_150_000, 2607) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,8 +109,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: 23_901 nanoseconds. + Weight::from_parts(23_901_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..6cda35750 100644 --- a/runtime/common/src/weights/orml_tokens.rs +++ b/runtime/common/src/weights/orml_tokens.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=orml_tokens // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/orml_weight_template.hbs @@ -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: `1772` // Estimated: `7803` - // Minimum execution time: 80_511 nanoseconds. - Weight::from_parts(98_030_000, 7803) + // Minimum execution time: 33_181 nanoseconds. + Weight::from_parts(33_181_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: `1772` // Estimated: `7803` - // Minimum execution time: 84_540 nanoseconds. - Weight::from_parts(102_440_000, 7803) + // Minimum execution time: 32_790 nanoseconds. + Weight::from_parts(32_790_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: `1596` // Estimated: `7803` - // Minimum execution time: 66_630 nanoseconds. - Weight::from_parts(80_230_000, 7803) + // Minimum execution time: 26_241 nanoseconds. + Weight::from_parts(26_241_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: `1772` // Estimated: `10410` - // Minimum execution time: 74_130 nanoseconds. - Weight::from_parts(80_780_000, 10410) + // Minimum execution time: 29_471 nanoseconds. + Weight::from_parts(29_471_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: `1394` // Estimated: `7723` - // Minimum execution time: 56_380 nanoseconds. - Weight::from_parts(57_350_000, 7723) + // Minimum execution time: 24_010 nanoseconds. + Weight::from_parts(24_010_000, 7723) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/runtime/common/src/weights/pallet_assets.rs b/runtime/common/src/weights/pallet_assets.rs index 3a30a53f5..7b34409a5 100644 --- a/runtime/common/src/weights/pallet_assets.rs +++ b/runtime/common/src/weights/pallet_assets.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2023-11-01`, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-02-15`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `sea212-PC`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! EXECUTION: `Some(Native)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/debug/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev // --steps=2 -// --repeat=2 +// --repeat=0 // --pallet=pallet_assets // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -52,365 +52,353 @@ use frame_support::{ /// Weight functions for pallet_assets (automatically generated) pub struct WeightInfo(PhantomData); impl pallet_assets::weights::WeightInfo for WeightInfo { - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `218` + // Measured: `285` // Estimated: `5304` - // Minimum execution time: 294_576 nanoseconds. - Weight::from_parts(316_096_000, 5304) + // Minimum execution time: 15_220 nanoseconds. + Weight::from_parts(15_220_000, 5304) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `42` + // Measured: `109` // Estimated: `2697` - // Minimum execution time: 163_914 nanoseconds. - Weight::from_parts(187_984_000, 2697) + // Minimum execution time: 9_330 nanoseconds. + Weight::from_parts(9_330_000, 2697) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: AssetRouter DestroyAssets (r:1 w:1) + /// Proof: AssetRouter DestroyAssets (max_values: Some(1), max_size: Some(40962), added: 41457, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: AssetRouter IndestructibleAssets (r:1 w:0) + /// Proof: AssetRouter IndestructibleAssets (max_values: Some(1), max_size: Some(38914), added: 39409, mode: MaxEncodedLen) fn start_destroy() -> Weight { // Proof Size summary in bytes: - // Measured: `358` - // Estimated: `2697` - // Minimum execution time: 160_353 nanoseconds. - Weight::from_parts(172_194_000, 2697) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `452` + // Estimated: `83563` + // Minimum execution time: 13_051 nanoseconds. + Weight::from_parts(13_051_000, 83563) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:51 w:50) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:51 w:50) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// Storage: System Account (r:50 w:50) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `c` is `[0, 50]`. - fn destroy_accounts(c: u32) -> Weight { + fn destroy_accounts(_c: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `324 + c * (242 ±0)` - // Estimated: `135636 + c * (2589 ±0)` - // Minimum execution time: 224_474 nanoseconds. - Weight::from_parts(225_024_000, 135636) - // Standard Error: 432_869 - .saturating_add(Weight::from_parts(176_285_210, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) - .saturating_add(Weight::from_parts(0, 2589).saturating_mul(c.into())) + // Measured: `376 + c * (241 ±0)` + // Estimated: `265086` + // Minimum execution time: 12_911 nanoseconds. + Weight::from_parts(466_572_000, 265086) + .saturating_add(T::DbWeight::get().reads(102)) + .saturating_add(T::DbWeight::get().writes(101)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Approvals (r:51 w:50) - /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Approvals (r:51 w:50) + /// Proof: CustomAssets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) /// The range of component `a` is `[0, 50]`. - fn destroy_approvals(a: u32) -> Weight { + fn destroy_approvals(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `358 + a * (88 ±0)` - // Estimated: `5332 + a * (2635 ±0)` - // Minimum execution time: 201_885 nanoseconds. - Weight::from_parts(210_474_500, 5332) - // Standard Error: 807_700 - .saturating_add(Weight::from_parts(184_051_830, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 2635).saturating_mul(a.into())) + // Measured: `410 + a * (88 ±0)` + // Estimated: `137082` + // Minimum execution time: 12_480 nanoseconds. + Weight::from_parts(403_251_000, 137082) + .saturating_add(T::DbWeight::get().reads(52)) + .saturating_add(T::DbWeight::get().writes(51)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:0) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:0) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) fn finish_destroy() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `5324` - // Minimum execution time: 184_174 nanoseconds. - Weight::from_parts(207_774_000, 5324) + // Minimum execution time: 9_881 nanoseconds. + Weight::from_parts(9_881_000, 5324) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:1 w:1) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:1 w:1) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `5286` - // Minimum execution time: 303_436 nanoseconds. - Weight::from_parts(316_737_000, 5286) + // Minimum execution time: 15_010 nanoseconds. + Weight::from_parts(15_010_000, 5286) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:1 w:1) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:1 w:1) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `444` + // Measured: `481` // Estimated: `5286` - // Minimum execution time: 388_118 nanoseconds. - Weight::from_parts(402_668_000, 5286) + // Minimum execution time: 17_511 nanoseconds. + Weight::from_parts(17_511_000, 5286) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:2 w:2) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:2 w:2) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `483` + // Measured: `520` // Estimated: `10482` - // Minimum execution time: 566_502 nanoseconds. - Weight::from_parts(587_882_000, 10482) + // Minimum execution time: 23_321 nanoseconds. + Weight::from_parts(23_321_000, 10482) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:2 w:2) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:2 w:2) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// 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: `483` + // Measured: `520` // Estimated: `10482` - // Minimum execution time: 500_120 nanoseconds. - Weight::from_parts(517_041_000, 10482) + // Minimum execution time: 20_681 nanoseconds. + Weight::from_parts(20_681_000, 10482) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:2 w:2) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:2 w:2) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn force_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `483` + // Measured: `520` // Estimated: `10482` - // Minimum execution time: 565_382 nanoseconds. - Weight::from_parts(581_262_000, 10482) + // Minimum execution time: 23_021 nanoseconds. + Weight::from_parts(23_021_000, 10482) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:1 w:1) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:1 w:1) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) fn freeze() -> Weight { // Proof Size summary in bytes: - // Measured: `444` + // Measured: `481` // Estimated: `5286` - // Minimum execution time: 212_584 nanoseconds. - Weight::from_parts(231_845_000, 5286) + // Minimum execution time: 10_620 nanoseconds. + Weight::from_parts(10_620_000, 5286) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Account (r:1 w:1) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:1 w:1) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) fn thaw() -> Weight { // Proof Size summary in bytes: - // Measured: `444` + // Measured: `481` // Estimated: `5286` - // Minimum execution time: 213_705 nanoseconds. - Weight::from_parts(225_135_000, 5286) + // Minimum execution time: 10_370 nanoseconds. + Weight::from_parts(10_370_000, 5286) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) fn freeze_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `358` + // Measured: `410` // Estimated: `2697` - // Minimum execution time: 157_223 nanoseconds. - Weight::from_parts(169_343_000, 2697) + // Minimum execution time: 8_710 nanoseconds. + Weight::from_parts(8_710_000, 2697) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) fn thaw_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `358` + // Measured: `410` // Estimated: `2697` - // Minimum execution time: 158_273 nanoseconds. - Weight::from_parts(170_884_000, 2697) + // Minimum execution time: 8_400 nanoseconds. + Weight::from_parts(8_400_000, 2697) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:0) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:0) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `5324` - // Minimum execution time: 189_044 nanoseconds. - Weight::from_parts(201_334_000, 5324) + // Minimum execution time: 10_600 nanoseconds. + Weight::from_parts(10_600_000, 5324) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `2697` - // Minimum execution time: 166_284 nanoseconds. - Weight::from_parts(180_313_000, 2697) + // Minimum execution time: 9_221 nanoseconds. + Weight::from_parts(9_221_000, 2697) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:1) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:1) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn set_metadata(n: u32, s: u32) -> Weight { + fn set_metadata(_n: u32, _s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `5324` - // Minimum execution time: 282_556 nanoseconds. - Weight::from_parts(287_195_500, 5324) - // Standard Error: 211_675 - .saturating_add(Weight::from_parts(55_910, 0).saturating_mul(n.into())) - // Standard Error: 211_675 - .saturating_add(Weight::from_parts(115_310, 0).saturating_mul(s.into())) + // Minimum execution time: 14_040 nanoseconds. + Weight::from_parts(16_751_500, 5324) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:1) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:1) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `532` + // Measured: `569` // Estimated: `5324` - // Minimum execution time: 295_517 nanoseconds. - Weight::from_parts(310_096_000, 5324) + // Minimum execution time: 14_580 nanoseconds. + Weight::from_parts(14_580_000, 5324) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:1) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:1) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32) -> Weight { + fn force_set_metadata(_n: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `131` + // Measured: `183` // Estimated: `5324` - // Minimum execution time: 186_034 nanoseconds. - Weight::from_parts(189_209_000, 5324) - // Standard Error: 183_142 - .saturating_add(Weight::from_parts(54_710, 0).saturating_mul(n.into())) - // Standard Error: 183_142 - .saturating_add(Weight::from_parts(76_500, 0).saturating_mul(s.into())) + // Minimum execution time: 9_340 nanoseconds. + Weight::from_parts(9_745_000, 5324) + // Standard Error: 6_754 + .saturating_add(Weight::from_parts(9_100, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:0) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Metadata (r:1 w:1) - /// Proof: Assets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:0) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Metadata (r:1 w:1) + /// Proof: CustomAssets Metadata (max_values: None, max_size: Some(152), added: 2627, mode: MaxEncodedLen) fn force_clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `532` + // Measured: `569` // Estimated: `5324` - // Minimum execution time: 291_577 nanoseconds. - Weight::from_parts(308_826_000, 5324) + // Minimum execution time: 15_270 nanoseconds. + Weight::from_parts(15_270_000, 5324) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) fn force_asset_status() -> Weight { // Proof Size summary in bytes: - // Measured: `324` + // Measured: `376` // Estimated: `2697` - // Minimum execution time: 163_124 nanoseconds. - Weight::from_parts(177_773_000, 2697) + // Minimum execution time: 9_671 nanoseconds. + Weight::from_parts(9_671_000, 2697) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Approvals (r:1 w:1) - /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Approvals (r:1 w:1) + /// Proof: CustomAssets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `358` + // Measured: `410` // Estimated: `5332` - // Minimum execution time: 326_807 nanoseconds. - Weight::from_parts(340_847_000, 5332) + // Minimum execution time: 16_250 nanoseconds. + Weight::from_parts(16_250_000, 5332) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Approvals (r:1 w:1) - /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) - /// Storage: Assets Account (r:2 w:2) - /// Proof: Assets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Approvals (r:1 w:1) + /// Proof: CustomAssets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: CustomAssets Account (r:2 w:2) + /// Proof: CustomAssets Account (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn transfer_approved() -> Weight { // Proof Size summary in bytes: - // Measured: `665` + // Measured: `687` // Estimated: `13117` - // Minimum execution time: 739_565 nanoseconds. - Weight::from_parts(755_396_000, 13117) + // Minimum execution time: 31_161 nanoseconds. + Weight::from_parts(31_161_000, 13117) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Approvals (r:1 w:1) - /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Approvals (r:1 w:1) + /// Proof: CustomAssets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `540` + // Measured: `577` // Estimated: `5332` - // Minimum execution time: 356_107 nanoseconds. - Weight::from_parts(379_428_000, 5332) + // Minimum execution time: 16_640 nanoseconds. + Weight::from_parts(16_640_000, 5332) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Assets Asset (r:1 w:1) - /// Proof: Assets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) - /// Storage: Assets Approvals (r:1 w:1) - /// Proof: Assets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) + /// Storage: CustomAssets Asset (r:1 w:1) + /// Proof: CustomAssets Asset (max_values: None, max_size: Some(222), added: 2697, mode: MaxEncodedLen) + /// Storage: CustomAssets Approvals (r:1 w:1) + /// Proof: CustomAssets Approvals (max_values: None, max_size: Some(160), added: 2635, mode: MaxEncodedLen) fn force_cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `540` + // Measured: `577` // Estimated: `5332` - // Minimum execution time: 360_088 nanoseconds. - Weight::from_parts(375_088_000, 5332) + // Minimum execution time: 16_961 nanoseconds. + Weight::from_parts(16_961_000, 5332) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/runtime/common/src/weights/pallet_author_inherent.rs b/runtime/common/src/weights/pallet_author_inherent.rs index 022cd20b5..8bcfce8e4 100644 --- a/runtime/common/src/weights/pallet_author_inherent.rs +++ b/runtime/common/src/weights/pallet_author_inherent.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_author_inherent // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -66,10 +66,10 @@ impl pallet_author_inherent::weights::WeightInfo for We /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: MaxEncodedLen) fn kick_off_authorship_validation() -> Weight { // Proof Size summary in bytes: - // Measured: `572` - // Estimated: `7316` - // Minimum execution time: 37_751 nanoseconds. - Weight::from_parts(45_860_000, 7316) + // Measured: `605` + // Estimated: `7415` + // Minimum execution time: 19_110 nanoseconds. + Weight::from_parts(19_110_000, 7415) .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..1acb378ec 100644 --- a/runtime/common/src/weights/pallet_author_mapping.rs +++ b/runtime/common/src/weights/pallet_author_mapping.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_author_mapping // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -60,10 +60,10 @@ impl pallet_author_mapping::weights::WeightInfo for Wei /// Proof Skipped: AuthorMapping NimbusLookup (max_values: None, max_size: None, mode: Measured) fn add_association() -> Weight { // Proof Size summary in bytes: - // Measured: `462` - // Estimated: `6006` - // Minimum execution time: 42_590 nanoseconds. - Weight::from_parts(44_770_000, 6006) + // Measured: `495` + // Estimated: `6072` + // Minimum execution time: 17_090 nanoseconds. + Weight::from_parts(17_090_000, 6072) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -73,10 +73,10 @@ impl pallet_author_mapping::weights::WeightInfo for Wei /// Proof Skipped: AuthorMapping NimbusLookup (max_values: None, max_size: None, mode: Measured) fn update_association() -> Weight { // Proof Size summary in bytes: - // Measured: `407` - // Estimated: `5764` - // Minimum execution time: 36_420 nanoseconds. - Weight::from_parts(38_180_000, 5764) + // Measured: `440` + // Estimated: `5830` + // Minimum execution time: 12_151 nanoseconds. + Weight::from_parts(12_151_000, 5830) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -88,10 +88,10 @@ impl pallet_author_mapping::weights::WeightInfo for Wei /// Proof Skipped: AuthorMapping NimbusLookup (max_values: None, max_size: None, mode: Measured) fn clear_association() -> Weight { // Proof Size summary in bytes: - // Measured: `583` - // Estimated: `6248` - // Minimum execution time: 49_801 nanoseconds. - Weight::from_parts(51_321_000, 6248) + // Measured: `616` + // Estimated: `6314` + // Minimum execution time: 16_060 nanoseconds. + Weight::from_parts(16_060_000, 6314) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -103,10 +103,10 @@ impl pallet_author_mapping::weights::WeightInfo for Wei /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn remove_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `689` - // Estimated: `8935` - // Minimum execution time: 56_490 nanoseconds. - Weight::from_parts(58_100_000, 8935) + // Measured: `722` + // Estimated: `9001` + // Minimum execution time: 19_871 nanoseconds. + Weight::from_parts(19_871_000, 9001) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -116,10 +116,10 @@ impl pallet_author_mapping::weights::WeightInfo for Wei /// Proof Skipped: AuthorMapping MappingWithDeposit (max_values: None, max_size: None, mode: Measured) fn set_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `513` - // Estimated: `8451` - // Minimum execution time: 43_710 nanoseconds. - Weight::from_parts(45_410_000, 8451) + // Measured: `546` + // Estimated: `8517` + // Minimum execution time: 14_680 nanoseconds. + Weight::from_parts(14_680_000, 8517) .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..3f40a00c9 100644 --- a/runtime/common/src/weights/pallet_author_slot_filter.rs +++ b/runtime/common/src/weights/pallet_author_slot_filter.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_author_slot_filter // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -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: 6_110 nanoseconds. + Weight::from_parts(6_110_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..c26941889 100644 --- a/runtime/common/src/weights/pallet_balances.rs +++ b/runtime/common/src/weights/pallet_balances.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_balances // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -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: 35_071 nanoseconds. + Weight::from_parts(35_071_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: 25_781 nanoseconds. + Weight::from_parts(25_781_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: 22_471 nanoseconds. + Weight::from_parts(22_471_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: 23_950 nanoseconds. + Weight::from_parts(23_950_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: 32_951 nanoseconds. + Weight::from_parts(32_951_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: 26_851 nanoseconds. + Weight::from_parts(26_851_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: 19_170 nanoseconds. + Weight::from_parts(19_170_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..00dd4efae 100644 --- a/runtime/common/src/weights/pallet_bounties.rs +++ b/runtime/common/src/weights/pallet_bounties.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_bounties // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -61,14 +61,12 @@ impl pallet_bounties::weights::WeightInfo for WeightInf /// Storage: Bounties Bounties (r:0 w:1) /// Proof: Bounties Bounties (max_values: None, max_size: Some(181), added: 2656, mode: MaxEncodedLen) /// The range of component `d` is `[0, 8192]`. - fn propose_bounty(d: u32) -> Weight { + fn propose_bounty(_d: u32) -> Weight { // 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: 14_731 nanoseconds. + Weight::from_parts(15_420_000, 3106) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -80,8 +78,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: 7_481 nanoseconds. + Weight::from_parts(7_481_000, 3553) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -91,8 +89,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: 6_461 nanoseconds. + Weight::from_parts(6_461_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,8 +102,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: 21_601 nanoseconds. + Weight::from_parts(21_601_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -117,8 +115,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: 12_561 nanoseconds. + Weight::from_parts(12_561_000, 5263) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -128,8 +126,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: 9_070 nanoseconds. + Weight::from_parts(9_070_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +141,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: 30_990 nanoseconds. + Weight::from_parts(30_990_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -158,8 +156,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: 20_530 nanoseconds. + Weight::from_parts(20_530_000, 7870) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -173,8 +171,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: 25_840 nanoseconds. + Weight::from_parts(25_840_000, 10477) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -184,8 +182,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: 9_451 nanoseconds. + Weight::from_parts(9_451_000, 2656) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -196,18 +194,13 @@ impl pallet_bounties::weights::WeightInfo for WeightInf /// Storage: System Account (r:200 w:200) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `b` is `[0, 100]`. - fn spend_funds(b: u32) -> Weight { + fn spend_funds(_b: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b.into()))) - .saturating_add(Weight::from_parts(0, 7870).saturating_mul(b.into())) + // Measured: `4 + b * (358 ±0)` + // Estimated: `787897` + // Minimum execution time: 2_460 nanoseconds. + Weight::from_parts(1_323_574_000, 787897) + .saturating_add(T::DbWeight::get().reads(301)) + .saturating_add(T::DbWeight::get().writes(301)) } } diff --git a/runtime/common/src/weights/pallet_collective.rs b/runtime/common/src/weights/pallet_collective.rs index 8eae1c505..c39eb29af 100644 --- a/runtime/common/src/weights/pallet_collective.rs +++ b/runtime/common/src/weights/pallet_collective.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_collective // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -65,20 +65,22 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[0, 255]`. 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: 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())) + // Measured: `0 + m * (8264 ±0) + p * (3228 ±0)` + // Estimated: `1204 + m * (15297 ±1_899) + p * (5961 ±744)` + // Minimum execution time: 13_300 nanoseconds. + Weight::from_parts(13_300_000, 1204) + // Standard Error: 3_265_951 + .saturating_add(Weight::from_parts(8_527_799, 0).saturating_mul(m.into())) + // Standard Error: 1_280_765 + .saturating_add(Weight::from_parts(3_328_234, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) - .saturating_add(Weight::from_parts(0, 19751).saturating_mul(m.into())) - .saturating_add(Weight::from_parts(0, 10255).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 15297).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 5961).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Members (r:1 w:0) /// Proof Skipped: AdvisoryCommittee Members (max_values: Some(1), max_size: None, mode: Measured) @@ -86,14 +88,14 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32) -> Weight { // 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())) + // Measured: `99 + m * (32 ±0)` + // Estimated: `595 + m * (32 ±0)` + // Minimum execution time: 12_031 nanoseconds. + Weight::from_parts(11_630_145, 595) + // Standard Error: 84 + .saturating_add(Weight::from_parts(381, 0).saturating_mul(b.into())) + // Standard Error: 874 + .saturating_add(Weight::from_parts(10_090, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } @@ -105,12 +107,12 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `m` is `[1, 100]`. 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())) + // Measured: `99 + m * (32 ±0)` + // Estimated: `3170 + m * (64 ±0)` + // Minimum execution time: 13_900 nanoseconds. + Weight::from_parts(14_051_635, 3170) + // Standard Error: 253 + .saturating_add(Weight::from_parts(13_186, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } @@ -129,37 +131,34 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[1, 255]`. fn propose_proposed(b: u32, m: u32, p: u32) -> Weight { // 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())) + // Measured: `68 + m * (32 ±0) + p * (36 ±0)` + // Estimated: `4305 + m * (160 ±0) + p * (180 ±0)` + // Minimum execution time: 17_221 nanoseconds. + Weight::from_parts(13_531_059, 4305) + // Standard Error: 1_044 + .saturating_add(Weight::from_parts(1_656, 0).saturating_mul(b.into())) + // Standard Error: 10_888 + .saturating_add(Weight::from_parts(19_520, 0).saturating_mul(m.into())) + // Standard Error: 4_201 + .saturating_add(Weight::from_parts(41_586, 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())) - .saturating_add(Weight::from_parts(0, 170).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 180).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Members (r:1 w:0) /// Proof Skipped: AdvisoryCommittee Members (max_values: Some(1), max_size: None, mode: Measured) /// Storage: AdvisoryCommittee Voting (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[5, 100]`. - fn vote(m: u32) -> Weight { + fn vote(_m: u32) -> Weight { // 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())) + // Measured: `1239 + m * (64 ±0)` + // Estimated: `18254` + // Minimum execution time: 15_780 nanoseconds. + Weight::from_parts(19_200_000, 18254) .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())) } /// Storage: AdvisoryCommittee Voting (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Voting (max_values: None, max_size: None, mode: Measured) @@ -173,16 +172,18 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[1, 255]`. fn close_early_disapproved(m: u32, p: u32) -> Weight { // 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())) + // Measured: `283 + m * (64 ±0) + p * (35 ±0)` + // Estimated: `4601 + m * (256 ±0) + p * (140 ±0)` + // Minimum execution time: 19_241 nanoseconds. + Weight::from_parts(16_291_175, 4601) + // Standard Error: 3_698 + .saturating_add(Weight::from_parts(29_114, 0).saturating_mul(m.into())) + // Standard Error: 1_397 + .saturating_add(Weight::from_parts(38_366, 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())) - .saturating_add(Weight::from_parts(0, 136).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 256).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 140).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Voting (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Voting (max_values: None, max_size: None, mode: Measured) @@ -197,21 +198,21 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[1, 255]`. fn close_early_approved(b: u32, m: u32, p: u32) -> Weight { // 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())) + // Measured: `0 + b * (1 ±0) + m * (64 ±0) + p * (39 ±0)` + // Estimated: `4980 + b * (8 ±0) + m * (256 ±0) + p * (160 ±0)` + // Minimum execution time: 25_361 nanoseconds. + Weight::from_parts(16_276_001, 4980) + // Standard Error: 228 + .saturating_add(Weight::from_parts(4_367, 0).saturating_mul(b.into())) + // Standard Error: 2_433 + .saturating_add(Weight::from_parts(45_555, 0).saturating_mul(m.into())) + // Standard Error: 919 + .saturating_add(Weight::from_parts(57_375, 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())) - .saturating_add(Weight::from_parts(0, 248).saturating_mul(m.into())) - .saturating_add(Weight::from_parts(0, 144).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 256).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 160).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Voting (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Voting (max_values: None, max_size: None, mode: Measured) @@ -227,18 +228,18 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[1, 255]`. fn close_disapproved(m: u32, p: u32) -> Weight { // 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())) + // Measured: `303 + m * (64 ±0) + p * (35 ±0)` + // Estimated: `5480 + m * (320 ±0) + p * (175 ±0)` + // Minimum execution time: 20_361 nanoseconds. + Weight::from_parts(17_791_257, 5480) + // Standard Error: 3_779 + .saturating_add(Weight::from_parts(25_307, 0).saturating_mul(m.into())) + // Standard Error: 1_428 + .saturating_add(Weight::from_parts(39_013, 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())) - .saturating_add(Weight::from_parts(0, 170).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 320).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 175).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Voting (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Voting (max_values: None, max_size: None, mode: Measured) @@ -255,21 +256,21 @@ impl pallet_collective::weights::WeightInfo for WeightI /// The range of component `p` is `[1, 255]`. fn close_approved(b: u32, m: u32, p: u32) -> Weight { // 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())) + // Measured: `0 + b * (1 ±0) + m * (64 ±0) + p * (39 ±0)` + // Estimated: `5335 + b * (10 ±0) + m * (320 ±0) + p * (200 ±0)` + // Minimum execution time: 27_001 nanoseconds. + Weight::from_parts(18_839_784, 5335) + // Standard Error: 590 + .saturating_add(Weight::from_parts(4_762, 0).saturating_mul(b.into())) + // Standard Error: 6_281 + .saturating_add(Weight::from_parts(32_260, 0).saturating_mul(m.into())) + // Standard Error: 2_374 + .saturating_add(Weight::from_parts(58_649, 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())) - .saturating_add(Weight::from_parts(0, 310).saturating_mul(m.into())) - .saturating_add(Weight::from_parts(0, 180).saturating_mul(p.into())) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 320).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 200).saturating_mul(p.into())) } /// Storage: AdvisoryCommittee Proposals (r:1 w:1) /// Proof Skipped: AdvisoryCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) @@ -278,16 +279,13 @@ impl pallet_collective::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee ProposalOf (r:0 w:1) /// Proof Skipped: AdvisoryCommittee ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[1, 255]`. - fn disapprove_proposal(p: u32) -> Weight { + fn disapprove_proposal(_p: u32) -> Weight { // 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())) + // Measured: `255 + p * (32 ±0)` + // Estimated: `25749` + // Minimum execution time: 10_500 nanoseconds. + Weight::from_parts(17_421_000, 25749) .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..0dbcbaba4 100644 --- a/runtime/common/src/weights/pallet_contracts.rs +++ b/runtime/common/src/weights/pallet_contracts.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_contracts // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -58,35 +58,30 @@ 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: 3_580 nanoseconds. + Weight::from_parts(3_580_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) /// The range of component `k` is `[0, 1024]`. - fn on_initialize_per_trie_key(k: u32) -> Weight { + fn on_initialize_per_trie_key(_k: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `414 + k * (69 ±0)` + // Measured: `280 + 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: 8_570 nanoseconds. + Weight::from_parts(984_005_000, 0) .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()))) + .saturating_add(T::DbWeight::get().writes(1025)) } /// Storage: Contracts DeletionQueue (r:1 w:1) /// Proof: Contracts DeletionQueue (max_values: Some(1), max_size: Some(16642), added: 17137, mode: Ignored) /// The range of component `q` is `[0, 128]`. - fn on_initialize_per_queue_item(q: u32) -> Weight { + fn on_initialize_per_queue_item(_q: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `214 + q * (33 ±0)` + // Measured: `42 + q * (34 ±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: 2_970 nanoseconds. + Weight::from_parts(98_383_000, 0) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -95,14 +90,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: Contracts CodeStorage (r:0 w:1) /// Proof: Contracts CodeStorage (max_values: None, max_size: Some(126001), added: 128476, mode: Ignored) /// The range of component `c` is `[0, 61717]`. - fn reinstrument(c: u32) -> Weight { + fn reinstrument(_c: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `203 + c * (1 ±0)` + // Measured: `271` // 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: 19_261 nanoseconds. + Weight::from_parts(2_411_872_000, 0) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,14 +110,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 `c` is `[0, 125952]`. - fn call_with_code_per_byte(c: u32) -> Weight { + fn call_with_code_per_byte(_c: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `675` + // Measured: `747` // 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: 179_665 nanoseconds. + Weight::from_parts(2_252_239_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -149,16 +140,16 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, i: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `157` + // Measured: `105` // 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: 2_239_028 nanoseconds. + Weight::from_parts(2_239_028_000, 0) + // Standard Error: 8_881 + .saturating_add(Weight::from_parts(45_107, 0).saturating_mul(c.into())) + // Standard Error: 522 + .saturating_add(Weight::from_parts(258, 0).saturating_mul(i.into())) + // Standard Error: 522 + .saturating_add(Weight::from_parts(264, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(9)) } @@ -180,14 +171,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// The range of component `s` is `[0, 1048576]`. fn instantiate(i: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `433` + // Measured: `446` // Estimated: `0` - // Minimum execution time: 2_556_385 nanoseconds. - Weight::from_parts(712_729_592, 0) - // Standard Error: 48 - .saturating_add(Weight::from_parts(1_946, 0).saturating_mul(i.into())) - // Standard Error: 48 - .saturating_add(Weight::from_parts(2_947, 0).saturating_mul(s.into())) + // Minimum execution time: 1_385_095 nanoseconds. + Weight::from_parts(616_576_000, 0) + // Standard Error: 26 + .saturating_add(Weight::from_parts(735, 0).saturating_mul(i.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(732, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -205,8 +196,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: 102_042 nanoseconds. + Weight::from_parts(102_042_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -219,14 +210,12 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: Contracts OwnerInfoOf (r:0 w:1) /// Proof: Contracts OwnerInfoOf (max_values: None, max_size: Some(88), added: 2563, mode: Ignored) /// The range of component `c` is `[0, 61717]`. - fn upload_code(c: u32) -> Weight { + fn upload_code(_c: u32) -> Weight { // 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: 179_815 nanoseconds. + Weight::from_parts(4_022_303_000, 0) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -242,8 +231,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: 16_090 nanoseconds. + Weight::from_parts(16_090_000, 0) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -257,8 +246,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: 19_411 nanoseconds. + Weight::from_parts(19_411_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -273,14 +262,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, 20]`. - fn seal_caller(r: u32) -> Weight { + fn seal_caller(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `697 + r * (480 ±0)` + // Measured: `695 + 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: 173_905 nanoseconds. + Weight::from_parts(398_440_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -295,16 +282,13 @@ 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, 20]`. - fn seal_is_contract(r: u32) -> Weight { + fn seal_is_contract(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `749 + r * (19218 ±0)` + // Measured: `696 + r * (19211 ±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())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 175_424 nanoseconds. + Weight::from_parts(3_865_098_000, 0) + .saturating_add(T::DbWeight::get().reads(1606)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: System Account (r:1 w:0) @@ -318,16 +302,13 @@ 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, 20]`. - fn seal_code_hash(r: u32) -> Weight { + fn seal_code_hash(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `741 + r * (19539 ±0)` + // Measured: `705 + r * (19531 ±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())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 176_775 nanoseconds. + Weight::from_parts(4_543_177_000, 0) + .saturating_add(T::DbWeight::get().reads(1606)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: System Account (r:1 w:0) @@ -341,14 +322,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, 20]`. - fn seal_own_code_hash(r: u32) -> Weight { + fn seal_own_code_hash(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `704 + r * (480 ±0)` + // Measured: `702 + 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: 174_865 nanoseconds. + Weight::from_parts(402_140_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -363,14 +342,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, 20]`. - fn seal_caller_is_origin(r: u32) -> Weight { + fn seal_caller_is_origin(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `694 + r * (240 ±0)` + // Measured: `692 + 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: 173_064 nanoseconds. + Weight::from_parts(309_308_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -385,14 +362,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, 20]`. - fn seal_address(r: u32) -> Weight { + fn seal_address(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `698 + r * (480 ±0)` + // Measured: `696 + 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: 172_995 nanoseconds. + Weight::from_parts(389_370_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -407,14 +382,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, 20]`. - fn seal_gas_left(r: u32) -> Weight { + fn seal_gas_left(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `699 + r * (480 ±0)` + // Measured: `697 + 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: 173_434 nanoseconds. + Weight::from_parts(400_600_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -429,14 +402,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, 20]`. - fn seal_balance(r: u32) -> Weight { + fn seal_balance(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `873 + r * (480 ±0)` + // Measured: `696 + r * (488 ±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: 172_775 nanoseconds. + Weight::from_parts(1_233_411_000, 0) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -451,14 +422,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, 20]`. - fn seal_value_transferred(r: u32) -> Weight { + fn seal_value_transferred(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `708 + r * (480 ±0)` + // Measured: `706 + 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: 171_744 nanoseconds. + Weight::from_parts(429_791_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -473,14 +442,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, 20]`. - fn seal_minimum_balance(r: u32) -> Weight { + fn seal_minimum_balance(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `706 + r * (480 ±0)` + // Measured: `704 + 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: 174_004 nanoseconds. + Weight::from_parts(398_920_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -495,14 +462,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, 20]`. - fn seal_block_number(r: u32) -> Weight { + fn seal_block_number(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `703 + r * (480 ±0)` + // Measured: `701 + 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: 173_704 nanoseconds. + Weight::from_parts(435_591_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -517,14 +482,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, 20]`. - fn seal_now(r: u32) -> Weight { + fn seal_now(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `694 + r * (480 ±0)` + // Measured: `692 + 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: 172_924 nanoseconds. + Weight::from_parts(398_800_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -541,14 +504,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, 20]`. - fn seal_weight_to_fee(r: u32) -> Weight { + fn seal_weight_to_fee(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `809 + r * (800 ±0)` + // Measured: `703 + r * (805 ±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: 194_355 nanoseconds. + Weight::from_parts(1_186_620_000, 0) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -563,14 +524,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, 20]`. - fn seal_gas(r: u32) -> Weight { + fn seal_gas(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `661 + r * (320 ±0)` + // Measured: `659 + 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: 96_473 nanoseconds. + Weight::from_parts(236_846_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -585,14 +544,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, 20]`. - fn seal_input(r: u32) -> Weight { + fn seal_input(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `696 + r * (480 ±0)` + // Measured: `694 + 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: 173_754 nanoseconds. + Weight::from_parts(388_760_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -607,14 +564,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 `n` is `[0, 1024]`. - fn seal_input_per_kb(n: u32) -> Weight { + fn seal_input_per_kb(_n: u32) -> Weight { // 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: 186_675 nanoseconds. + Weight::from_parts(18_309_830_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -629,14 +584,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: 170_794 nanoseconds. + Weight::from_parts(176_204_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -651,14 +604,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 `n` is `[0, 1024]`. - fn seal_return_per_kb(n: u32) -> Weight { + fn seal_return_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `694` + // Measured: `692` // 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: 171_734 nanoseconds. + Weight::from_parts(445_731_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -677,18 +628,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:3 w:3) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 1]`. - fn seal_terminate(r: u32) -> Weight { + fn seal_terminate(_r: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((6_u64).saturating_mul(r.into()))) + // Minimum execution time: 173_994 nanoseconds. + Weight::from_parts(230_156_000, 0) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(9)) } /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -703,14 +650,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, 20]`. - fn seal_random(r: u32) -> Weight { + fn seal_random(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `775 + r * (800 ±0)` + // Measured: `697 + r * (803 ±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: 171_664 nanoseconds. + Weight::from_parts(1_233_671_000, 0) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -725,14 +670,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, 20]`. - fn seal_deposit_event(r: u32) -> Weight { + fn seal_deposit_event(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `694 + r * (800 ±0)` + // Measured: `692 + 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: 176_445 nanoseconds. + Weight::from_parts(2_172_755_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -750,14 +693,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1630 + t * (2608 ±0) + n * (8 ±0)` + // Measured: `1598 + t * (2600 ±0) + n * (10 ±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: 466_252 nanoseconds. + Weight::from_parts(287_492_500, 0) + // Standard Error: 5_003_245 + .saturating_add(Weight::from_parts(113_174_125, 0).saturating_mul(t.into())) + // Standard Error: 1_250_811 + .saturating_add(Weight::from_parts(11_172_468, 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)) @@ -774,188 +717,146 @@ 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, 20]`. - fn seal_debug_message(r: u32) -> Weight { + fn seal_debug_message(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `693 + r * (800 ±0)` + // Measured: `691 + 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: 102_063 nanoseconds. + Weight::from_parts(343_079_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 10]`. - fn seal_set_storage(r: u32) -> Weight { + fn seal_set_storage(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `720 + r * (23420 ±0)` + // Measured: `694 + r * (23412 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 184_665 nanoseconds. + Weight::from_parts(3_528_681_000, 0) + .saturating_add(T::DbWeight::get().reads(806)) + .saturating_add(T::DbWeight::get().writes(803)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_new_kb(n: u32) -> Weight { + fn seal_set_storage_per_new_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `12402 + n * (12006 ±0)` + // Measured: `1931 + n * (13146 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 254_867 nanoseconds. + Weight::from_parts(651_147_000, 0) + .saturating_add(T::DbWeight::get().reads(87)) + .saturating_add(T::DbWeight::get().writes(85)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_old_kb(n: u32) -> Weight { + fn seal_set_storage_per_old_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `14990 + n * (175775 ±0)` + // Measured: `1931 + n * (177300 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 266_557 nanoseconds. + Weight::from_parts(628_756_000, 0) + .saturating_add(T::DbWeight::get().reads(86)) + .saturating_add(T::DbWeight::get().writes(83)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 10]`. - fn seal_clear_storage(r: u32) -> Weight { + fn seal_clear_storage(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `720 + r * (23100 ±0)` + // Measured: `694 + r * (23092 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 175_885 nanoseconds. + Weight::from_parts(3_409_749_000, 0) + .saturating_add(T::DbWeight::get().reads(806)) + .saturating_add(T::DbWeight::get().writes(803)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_clear_storage_per_kb(n: u32) -> Weight { + fn seal_clear_storage_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `14670 + n * (175775 ±0)` + // Measured: `1435 + n * (177322 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 244_096 nanoseconds. + Weight::from_parts(599_955_000, 0) + .saturating_add(T::DbWeight::get().reads(86)) + .saturating_add(T::DbWeight::get().writes(83)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 10]`. - fn seal_get_storage(r: u32) -> Weight { + fn seal_get_storage(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `730 + r * (23740 ±0)` + // Measured: `703 + r * (23732 ±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())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 175_955 nanoseconds. + Weight::from_parts(2_709_320_000, 0) + .saturating_add(T::DbWeight::get().reads(806)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_get_storage_per_kb(n: u32) -> Weight { + fn seal_get_storage_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `15321 + n * (175775 ±0)` + // Measured: `1764 + n * (177363 ±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())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 235_166 nanoseconds. + Weight::from_parts(610_066_000, 0) + .saturating_add(T::DbWeight::get().reads(86)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 10]`. - fn seal_contains_storage(r: u32) -> Weight { + fn seal_contains_storage(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `723 + r * (23100 ±0)` + // Measured: `697 + r * (23092 ±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())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 177_305 nanoseconds. + Weight::from_parts(2_626_618_000, 0) + .saturating_add(T::DbWeight::get().reads(806)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_contains_storage_per_kb(n: u32) -> Weight { + fn seal_contains_storage_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `14673 + n * (175775 ±0)` + // Measured: `1438 + n * (177322 ±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())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 218_365 nanoseconds. + Weight::from_parts(542_574_000, 0) + .saturating_add(T::DbWeight::get().reads(86)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 10]`. - fn seal_take_storage(r: u32) -> Weight { + fn seal_take_storage(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `731 + r * (23740 ±0)` + // Measured: `704 + r * (23732 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 198_385 nanoseconds. + Weight::from_parts(3_505_881_000, 0) + .saturating_add(T::DbWeight::get().reads(806)) + .saturating_add(T::DbWeight::get().writes(803)) } /// Storage: Skipped Metadata (r:0 w:0) /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Ignored) /// The range of component `n` is `[0, 8]`. - fn seal_take_storage_per_kb(n: u32) -> Weight { + fn seal_take_storage_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `15322 + n * (175775 ±0)` + // Measured: `1765 + n * (177363 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) + // Minimum execution time: 236_026 nanoseconds. + Weight::from_parts(666_147_000, 0) + .saturating_add(T::DbWeight::get().reads(86)) + .saturating_add(T::DbWeight::get().writes(83)) } /// Storage: System Account (r:1602 w:1601) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -968,18 +869,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, 20]`. - fn seal_transfer(r: u32) -> Weight { + fn seal_transfer(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1322 + r * (3601 ±0)` + // Measured: `717 + r * (3631 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) + // Minimum execution time: 176_465 nanoseconds. + Weight::from_parts(13_119_076_000, 0) + .saturating_add(T::DbWeight::get().reads(1607)) + .saturating_add(T::DbWeight::get().writes(1604)) } /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -992,18 +889,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:1602 w:1602) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 20]`. - fn seal_call(r: u32) -> Weight { + fn seal_call(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `948 + r * (20495 ±0)` + // Measured: `718 + r * (20498 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((160_u64).saturating_mul(r.into()))) + // Minimum execution time: 176_234 nanoseconds. + Weight::from_parts(226_800_040_000, 0) + .saturating_add(T::DbWeight::get().reads(3207)) + .saturating_add(T::DbWeight::get().writes(3203)) } /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -1016,18 +909,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:1537 w:1537) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 20]`. - fn seal_delegate_call(r: u32) -> Weight { + fn seal_delegate_call(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + r * (71670 ±0)` + // Measured: `703 + r * (74458 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((75_u64).saturating_mul(r.into()))) + // Minimum execution time: 177_375 nanoseconds. + Weight::from_parts(226_439_247_000, 0) + .saturating_add(T::DbWeight::get().reads(3076)) + .saturating_add(T::DbWeight::get().writes(1538)) } /// Storage: System Account (r:82 w:81) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -1045,12 +934,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: 5_581_492 nanoseconds. + Weight::from_parts(4_935_940_999, 0) + // Standard Error: 11_041_823 + .saturating_add(Weight::from_parts(645_551_000, 0).saturating_mul(t.into())) + // Standard Error: 10_783 + .saturating_add(Weight::from_parts(17_775_676, 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)) @@ -1071,18 +960,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:1602 w:1602) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 20]`. - fn seal_instantiate(r: u32) -> Weight { + fn seal_instantiate(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1512 + r * (25573 ±0)` + // Measured: `742 + r * (25594 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((400_u64).saturating_mul(r.into()))) + // Minimum execution time: 175_805 nanoseconds. + Weight::from_parts(321_382_493_000, 0) + .saturating_add(T::DbWeight::get().reads(8008)) + .saturating_add(T::DbWeight::get().writes(8006)) } /// Storage: System Account (r:82 w:82) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -1101,20 +986,18 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 960]`. /// The range of component `s` is `[0, 960]`. - fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32) -> Weight { + fn seal_instantiate_per_transfer_input_salt_kb(_t: u32, i: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `5505 + t * (68 ±0)` + // Measured: `5156 + 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + // Minimum execution time: 71_057_159 nanoseconds. + Weight::from_parts(71_057_159_000, 0) + // Standard Error: 25_212_820 + .saturating_add(Weight::from_parts(66_641_535, 0).saturating_mul(i.into())) + // Standard Error: 25_212_820 + .saturating_add(Weight::from_parts(66_615_773, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(307)) + .saturating_add(T::DbWeight::get().writes(304)) } /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -1127,14 +1010,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: 173_775 nanoseconds. + Weight::from_parts(189_505_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1149,14 +1030,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 `n` is `[0, 1024]`. - fn seal_hash_sha2_256_per_kb(n: u32) -> Weight { + fn seal_hash_sha2_256_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1493` + // Measured: `1333` // 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: 189_384 nanoseconds. + Weight::from_parts(46_429_658_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1171,14 +1050,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_keccak_256(r: u32) -> Weight { + fn seal_hash_keccak_256(_r: u32) -> Weight { // 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: 171_414 nanoseconds. + Weight::from_parts(208_715_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1193,14 +1070,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 `n` is `[0, 1024]`. - fn seal_hash_keccak_256_per_kb(n: u32) -> Weight { + fn seal_hash_keccak_256_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1495` + // Measured: `1335` // 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: 209_756 nanoseconds. + Weight::from_parts(175_664_912_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1215,14 +1090,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_256(r: u32) -> Weight { + fn seal_hash_blake2_256(_r: u32) -> Weight { // 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: 175_004 nanoseconds. + Weight::from_parts(200_015_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1237,14 +1110,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 `n` is `[0, 1024]`. - fn seal_hash_blake2_256_per_kb(n: u32) -> Weight { + fn seal_hash_blake2_256_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1495` + // Measured: `1335` // 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: 201_325 nanoseconds. + Weight::from_parts(68_755_378_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1259,14 +1130,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: 172_824 nanoseconds. + Weight::from_parts(195_655_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1281,14 +1150,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 `n` is `[0, 1024]`. - fn seal_hash_blake2_128_per_kb(n: u32) -> Weight { + fn seal_hash_blake2_128_per_kb(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1495` + // Measured: `1335` // 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: 202_786 nanoseconds. + Weight::from_parts(68_773_308_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1303,14 +1170,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_ecdsa_recover(r: u32) -> Weight { + fn seal_ecdsa_recover(_r: u32) -> Weight { // 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: 174_755 nanoseconds. + Weight::from_parts(2_769_740_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1325,14 +1190,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_ecdsa_to_eth_address(r: u32) -> Weight { + fn seal_ecdsa_to_eth_address(_r: u32) -> Weight { // 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: 175_395 nanoseconds. + Weight::from_parts(600_975_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1349,18 +1212,14 @@ impl pallet_contracts::weights::WeightInfo for WeightIn /// Storage: System EventTopics (r:1538 w:1538) /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Ignored) /// The range of component `r` is `[0, 20]`. - fn seal_set_code_hash(r: u32) -> Weight { + fn seal_set_code_hash(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + r * (79300 ±0)` + // Measured: `698 + r * (81746 ±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())) - .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)) - .saturating_add(T::DbWeight::get().writes((150_u64).saturating_mul(r.into()))) + // Minimum execution time: 173_474 nanoseconds. + Weight::from_parts(19_254_512_000, 0) + .saturating_add(T::DbWeight::get().reads(4613)) + .saturating_add(T::DbWeight::get().writes(3075)) } /// Storage: System Account (r:1 w:0) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: Ignored) @@ -1373,14 +1232,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, 20]`. - fn seal_reentrance_count(r: u32) -> Weight { + fn seal_reentrance_count(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `689 + r * (240 ±0)` + // Measured: `687 + 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: 182_275 nanoseconds. + Weight::from_parts(312_438_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1395,14 +1252,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, 20]`. - fn seal_account_reentrance_count(r: u32) -> Weight { + fn seal_account_reentrance_count(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1387 + r * (3140 ±0)` + // Measured: `704 + r * (3169 ±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: 174_775 nanoseconds. + Weight::from_parts(433_051_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -1419,535 +1274,429 @@ 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, 20]`. - fn seal_instantiation_nonce(r: u32) -> Weight { + fn seal_instantiation_nonce(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `692 + r * (240 ±0)` + // Measured: `690 + 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: 175_375 nanoseconds. + Weight::from_parts(317_528_000, 0) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// The range of component `r` is `[0, 50]`. - fn instr_i64const(r: u32) -> Weight { + fn instr_i64const(_r: u32) -> Weight { // 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_250 nanoseconds. + Weight::from_parts(14_320_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64load(r: u32) -> Weight { + 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_560 nanoseconds. + Weight::from_parts(273_497_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64store(r: u32) -> Weight { + 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_500 nanoseconds. + Weight::from_parts(147_333_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_select(r: u32) -> Weight { + 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_250 nanoseconds. + Weight::from_parts(65_622_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_if(r: u32) -> Weight { + 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_220 nanoseconds. + Weight::from_parts(71_172_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_br(r: u32) -> Weight { + 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_280 nanoseconds. + Weight::from_parts(35_310_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_br_if(r: u32) -> Weight { + 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_300 nanoseconds. + Weight::from_parts(56_291_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_br_table(r: u32) -> Weight { + 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_210 nanoseconds. + Weight::from_parts(58_811_000, 0) } /// The range of component `e` is `[1, 256]`. - fn instr_br_table_per_entry(e: u32) -> Weight { + 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: 4_120 nanoseconds. + Weight::from_parts(4_860_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_call(r: u32) -> Weight { + 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_320 nanoseconds. + Weight::from_parts(210_196_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_call_indirect(r: u32) -> Weight { + 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_430 nanoseconds. + Weight::from_parts(305_568_000, 0) } /// The range of component `p` is `[0, 128]`. - fn instr_call_indirect_per_param(p: u32) -> Weight { + 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: 9_010 nanoseconds. + Weight::from_parts(42_741_000, 0) } /// The range of component `l` is `[0, 1024]`. - fn instr_call_per_local(l: u32) -> Weight { + 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: 5_600 nanoseconds. + Weight::from_parts(12_971_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_local_get(r: u32) -> Weight { + 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: 1_640 nanoseconds. + Weight::from_parts(23_371_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_local_set(r: u32) -> Weight { + 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: 1_660 nanoseconds. + Weight::from_parts(24_590_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_local_tee(r: u32) -> Weight { + 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: 1_840 nanoseconds. + Weight::from_parts(29_981_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_global_get(r: u32) -> Weight { + 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_460 nanoseconds. + Weight::from_parts(87_812_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_global_set(r: u32) -> Weight { + 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_720 nanoseconds. + Weight::from_parts(85_122_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_memory_current(r: u32) -> Weight { + 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_651 nanoseconds. + Weight::from_parts(52_571_000, 0) } /// The range of component `r` is `[0, 1]`. - fn instr_memory_grow(r: u32) -> Weight { + 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_470 nanoseconds. + Weight::from_parts(138_103_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64clz(r: u32) -> Weight { + 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_230 nanoseconds. + Weight::from_parts(29_451_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64ctz(r: u32) -> Weight { + 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_320 nanoseconds. + Weight::from_parts(29_631_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64popcnt(r: u32) -> Weight { + 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_460 nanoseconds. + Weight::from_parts(30_980_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64eqz(r: u32) -> Weight { + 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_560 nanoseconds. + Weight::from_parts(29_891_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64extendsi32(r: u32) -> Weight { + 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_250 nanoseconds. + Weight::from_parts(29_611_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64extendui32(r: u32) -> Weight { + 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_180 nanoseconds. + Weight::from_parts(33_711_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i32wrapi64(r: u32) -> Weight { + 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_300 nanoseconds. + Weight::from_parts(29_861_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64eq(r: u32) -> Weight { + 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_210 nanoseconds. + Weight::from_parts(46_101_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64ne(r: u32) -> Weight { + 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_230 nanoseconds. + Weight::from_parts(45_991_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64lts(r: u32) -> Weight { + 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_220 nanoseconds. + Weight::from_parts(46_892_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64ltu(r: u32) -> Weight { + 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_250 nanoseconds. + Weight::from_parts(47_132_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64gts(r: u32) -> Weight { + 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_270 nanoseconds. + Weight::from_parts(45_971_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64gtu(r: u32) -> Weight { + 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_330 nanoseconds. + Weight::from_parts(45_902_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64les(r: u32) -> Weight { + 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_220 nanoseconds. + Weight::from_parts(50_231_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64leu(r: u32) -> Weight { + 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_270 nanoseconds. + Weight::from_parts(47_121_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64ges(r: u32) -> Weight { + 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_241 nanoseconds. + Weight::from_parts(45_921_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64geu(r: u32) -> Weight { + 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_210 nanoseconds. + Weight::from_parts(45_921_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64add(r: u32) -> Weight { + 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_310 nanoseconds. + Weight::from_parts(47_321_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64sub(r: u32) -> Weight { + 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_310 nanoseconds. + Weight::from_parts(45_871_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64mul(r: u32) -> Weight { + 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_220 nanoseconds. + Weight::from_parts(46_181_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64divs(r: u32) -> Weight { + 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_370 nanoseconds. + Weight::from_parts(50_561_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64divu(r: u32) -> Weight { + 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_310 nanoseconds. + Weight::from_parts(48_431_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64rems(r: u32) -> Weight { + 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_230 nanoseconds. + Weight::from_parts(50_451_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64remu(r: u32) -> Weight { + 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_450 nanoseconds. + Weight::from_parts(48_111_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64and(r: u32) -> Weight { + 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_230 nanoseconds. + Weight::from_parts(47_251_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64or(r: u32) -> Weight { + 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_240 nanoseconds. + Weight::from_parts(47_301_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64xor(r: u32) -> Weight { + 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_480 nanoseconds. + Weight::from_parts(45_921_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64shl(r: u32) -> Weight { + 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_300 nanoseconds. + Weight::from_parts(46_191_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64shrs(r: u32) -> Weight { + 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_300 nanoseconds. + Weight::from_parts(47_001_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64shru(r: u32) -> Weight { + 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_290 nanoseconds. + Weight::from_parts(47_222_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64rotl(r: u32) -> Weight { + 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_260 nanoseconds. + Weight::from_parts(46_141_000, 0) } /// The range of component `r` is `[0, 50]`. - fn instr_i64rotr(r: u32) -> Weight { + 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_440 nanoseconds. + Weight::from_parts(46_151_000, 0) } } diff --git a/runtime/common/src/weights/pallet_democracy.rs b/runtime/common/src/weights/pallet_democracy.rs index 17b87abdf..a7b22fe78 100644 --- a/runtime/common/src/weights/pallet_democracy.rs +++ b/runtime/common/src/weights/pallet_democracy.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_democracy // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -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: 23_280 nanoseconds. + Weight::from_parts(23_280_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: 18_950 nanoseconds. + Weight::from_parts(18_950_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: 30_630 nanoseconds. + Weight::from_parts(30_630_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: 27_401 nanoseconds. + Weight::from_parts(27_401_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: 12_601 nanoseconds. + Weight::from_parts(12_601_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: 48_421 nanoseconds. + Weight::from_parts(48_421_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: 11_181 nanoseconds. + Weight::from_parts(11_181_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: 2_800 nanoseconds. + Weight::from_parts(2_800_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: 2_740 nanoseconds. + Weight::from_parts(2_740_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: 12_060 nanoseconds. + Weight::from_parts(12_060_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: 14_791 nanoseconds. + Weight::from_parts(14_791_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: 40_661 nanoseconds. + Weight::from_parts(40_661_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: 5_560 nanoseconds. + Weight::from_parts(5_560_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) @@ -234,18 +234,14 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: Democracy ReferendumInfoOf (r:99 w:0) /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(209), added: 2684, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn on_initialize_base(r: u32) -> Weight { + fn on_initialize_base(_r: u32) -> Weight { // 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())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + // Measured: `150 + r * (125 ±0)` + // Estimated: `266714` + // Minimum execution time: 6_850 nanoseconds. + Weight::from_parts(186_554_000, 266714) + .saturating_add(T::DbWeight::get().reads(101)) .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 2684).saturating_mul(r.into())) } /// Storage: Democracy LowestUnbaked (r:1 w:1) /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -260,18 +256,14 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: Democracy ReferendumInfoOf (r:99 w:0) /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(209), added: 2684, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn on_initialize_base_with_launch_period(r: u32) -> Weight { + fn on_initialize_base_with_launch_period(_r: u32) -> Weight { // 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())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + // Measured: `150 + r * (125 ±0)` + // Estimated: `285034` + // Minimum execution time: 8_340 nanoseconds. + Weight::from_parts(186_275_000, 285034) + .saturating_add(T::DbWeight::get().reads(104)) .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 2684).saturating_mul(r.into())) } /// Storage: Democracy VotingOf (r:3 w:3) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) @@ -280,38 +272,28 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: Balances Locks (r:1 w:1) /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn delegate(r: u32) -> Weight { + fn delegate(_r: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) - .saturating_add(Weight::from_parts(0, 2684).saturating_mul(r.into())) + // Measured: `695 + r * (149 ±0)` + // Estimated: `288312` + // Minimum execution time: 21_931 nanoseconds. + Weight::from_parts(266_277_000, 288312) + .saturating_add(T::DbWeight::get().reads(103)) + .saturating_add(T::DbWeight::get().writes(103)) } /// Storage: Democracy VotingOf (r:2 w:2) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) /// Storage: Democracy ReferendumInfoOf (r:99 w:99) /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(209), added: 2684, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. - fn undelegate(r: u32) -> Weight { + fn undelegate(_r: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) - .saturating_add(Weight::from_parts(0, 2684).saturating_mul(r.into())) + // Measured: `497 + r * (147 ±0)` + // Estimated: `278264` + // Minimum execution time: 14_091 nanoseconds. + Weight::from_parts(250_536_000, 278264) + .saturating_add(T::DbWeight::get().reads(101)) + .saturating_add(T::DbWeight::get().writes(101)) } /// Storage: Democracy PublicProps (r:0 w:1) /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) @@ -319,8 +301,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: 2_110 nanoseconds. + Weight::from_parts(2_110_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) @@ -331,10 +313,10 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// The range of component `r` is `[0, 99]`. fn unlock_remove(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `554` + // Measured: `326 + r * (2 ±0)` // Estimated: `12655` - // Minimum execution time: 31_790 nanoseconds. - Weight::from_parts(44_745_537, 12655) + // Minimum execution time: 13_670 nanoseconds. + Weight::from_parts(16_441_000, 12655) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -345,14 +327,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_set(r: u32) -> Weight { + fn unlock_set(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `555 + r * (22 ±0)` + // Measured: `554 + 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: 15_651 nanoseconds. + Weight::from_parts(19_570_000, 12655) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -361,14 +341,12 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: Democracy VotingOf (r:1 w:1) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. - fn remove_vote(r: u32) -> Weight { + fn remove_vote(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `760 + r * (26 ±0)` + // Measured: `445 + r * (28 ±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: 10_660 nanoseconds. + Weight::from_parts(17_171_000, 8958) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -377,14 +355,12 @@ impl pallet_democracy::weights::WeightInfo for WeightIn /// Storage: Democracy VotingOf (r:1 w:1) /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3799), added: 6274, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. - fn remove_other_vote(r: u32) -> Weight { + fn remove_other_vote(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `760 + r * (26 ±0)` + // Measured: `445 + r * (28 ±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: 10_840 nanoseconds. + Weight::from_parts(16_681_000, 8958) .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..6350ca2ad 100644 --- a/runtime/common/src/weights/pallet_identity.rs +++ b/runtime/common/src/weights/pallet_identity.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_identity // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -55,14 +55,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// 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 add_registrar(r: u32) -> Weight { + fn add_registrar(_r: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `64 + r * (57 ±0)` + // Measured: `62 + 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: 7_880 nanoseconds. + Weight::from_parts(8_790_000, 952) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -74,10 +72,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: 14_540 nanoseconds. + Weight::from_parts(14_889_142, 7313) + // Standard Error: 10_405 + .saturating_add(Weight::from_parts(138_210, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -88,19 +86,14 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Storage: Identity SuperOf (r:64 w:64) /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[0, 64]`. - fn set_subs_new(s: u32) -> Weight { + fn set_subs_new(_s: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) - .saturating_add(Weight::from_parts(0, 2589).saturating_mul(s.into())) + // Estimated: `177590` + // Minimum execution time: 6_180 nanoseconds. + Weight::from_parts(106_043_000, 177590) + .saturating_add(T::DbWeight::get().reads(66)) + .saturating_add(T::DbWeight::get().writes(65)) } /// Storage: Identity IdentityOf (r:1 w:0) /// Proof: Identity IdentityOf (max_values: None, max_size: Some(4838), added: 7313, mode: MaxEncodedLen) @@ -109,17 +102,14 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Storage: Identity SuperOf (r:0 w:64) /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `p` is `[0, 64]`. - fn set_subs_old(p: u32) -> Weight { + fn set_subs_old(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `226 + p * (32 ±0)` + // Measured: `101 + p * (33 ±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: 5_530 nanoseconds. + Weight::from_parts(50_692_000, 11894) .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()))) + .saturating_add(T::DbWeight::get().writes(65)) } /// Storage: Identity SubsOf (r:1 w:1) /// Proof: Identity SubsOf (max_values: None, max_size: Some(2106), added: 4581, mode: MaxEncodedLen) @@ -132,18 +122,18 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// The range of component `x` is `[0, 64]`. 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)` + // Measured: `408 + r * (5 ±0) + s * (33 ±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: 22_121 nanoseconds. + Weight::from_parts(12_695_285, 11894) + // Standard Error: 60_892 + .saturating_add(Weight::from_parts(157_714, 0).saturating_mul(r.into())) + // Standard Error: 6_660 + .saturating_add(Weight::from_parts(615_843, 0).saturating_mul(s.into())) + // Standard Error: 6_660 + .saturating_add(Weight::from_parts(127_562, 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)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } /// Storage: Identity Registrars (r:1 w:0) @@ -152,16 +142,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 request_judgement(r: u32, x: u32) -> Weight { + fn request_judgement(_r: u32, x: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `431 + r * (57 ±0) + x * (66 ±0)` + // Measured: `428 + 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: 15_540 nanoseconds. + Weight::from_parts(19_157_142, 8265) + // Standard Error: 135 + .saturating_add(Weight::from_parts(156_500, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -169,58 +157,50 @@ 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)` + // Measured: `429 + 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: 13_150 nanoseconds. + Weight::from_parts(16_887_142, 7313) + // Standard Error: 4_600 + .saturating_add(Weight::from_parts(156_890, 0).saturating_mul(x.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_fee(r: u32) -> Weight { + fn set_fee(_r: u32) -> Weight { // 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: 5_240 nanoseconds. + Weight::from_parts(5_750_000, 952) .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_account_id(r: u32) -> Weight { + fn set_account_id(_r: u32) -> Weight { // 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: 4_740 nanoseconds. + Weight::from_parts(5_200_000, 952) .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_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: 4_800 nanoseconds. + Weight::from_parts(5_030_000, 952) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -230,14 +210,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)` + // Measured: `508 + 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: 12_260 nanoseconds. + Weight::from_parts(11_834_166, 8265) + // Standard Error: 99_592 + .saturating_add(Weight::from_parts(60_833, 0).saturating_mul(r.into())) + // Standard Error: 9_336 + .saturating_add(Weight::from_parts(231_187, 0).saturating_mul(x.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -254,16 +236,16 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// The range of component `x` is `[0, 64]`. fn kill_identity(_r: u32, s: u32, x: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `954 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Measured: `789 + r * (9 ±0) + s * (33 ±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: 30_611 nanoseconds. + Weight::from_parts(29_901_714, 17108) + // Standard Error: 33_592 + .saturating_add(Weight::from_parts(643_239, 0).saturating_mul(s.into())) + // Standard Error: 33_592 + .saturating_add(Weight::from_parts(70_255, 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(3)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) } /// Storage: Identity IdentityOf (r:1 w:0) @@ -273,14 +255,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)` + // Measured: `101 + s * (43 ±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: 14_570 nanoseconds. + Weight::from_parts(19_940_000, 14483) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -289,14 +269,12 @@ impl pallet_identity::weights::WeightInfo for WeightInf /// Storage: Identity SuperOf (r:1 w:1) /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[1, 64]`. - fn rename_sub(s: u32) -> Weight { + fn rename_sub(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `464 + s * (7 ±0)` + // Measured: `297 + s * (8 ±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: 7_751 nanoseconds. + Weight::from_parts(9_820_000, 9902) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -307,14 +285,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 `[1, 64]`. - fn remove_sub(s: u32) -> Weight { + fn remove_sub(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `544 + s * (39 ±0)` + // Measured: `376 + s * (40 ±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: 17_470 nanoseconds. + Weight::from_parts(20_470_000, 14483) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -323,14 +299,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 quit_sub(s: u32) -> Weight { + fn quit_sub(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `469 + s * (42 ±0)` + // Measured: `357 + 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: 11_930 nanoseconds. + Weight::from_parts(15_691_000, 7170) .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..a76c3ed13 100644 --- a/runtime/common/src/weights/pallet_membership.rs +++ b/runtime/common/src/weights/pallet_membership.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_membership // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -61,17 +61,14 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 99]`. - fn add_member(m: u32) -> Weight { + fn add_member(_m: u32) -> Weight { // 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())) + // Measured: `234 + m * (64 ±0)` + // Estimated: `23917` + // Minimum execution time: 11_551 nanoseconds. + Weight::from_parts(13_041_000, 23917) .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())) } /// Storage: AdvisoryCommitteeMembership Members (r:1 w:1) /// Proof: AdvisoryCommitteeMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) @@ -84,17 +81,14 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. - fn remove_member(m: u32) -> Weight { + fn remove_member(_m: u32) -> Weight { // 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())) + // Measured: `340 + m * (64 ±0)` + // Estimated: `24948` + // Minimum execution time: 12_650 nanoseconds. + Weight::from_parts(14_640_000, 24948) .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())) } /// Storage: AdvisoryCommitteeMembership Members (r:1 w:1) /// Proof: AdvisoryCommitteeMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) @@ -107,17 +101,14 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. - fn swap_member(m: u32) -> Weight { + fn swap_member(_m: u32) -> Weight { // 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())) + // Measured: `340 + m * (64 ±0)` + // Estimated: `24948` + // Minimum execution time: 12_840 nanoseconds. + Weight::from_parts(15_381_000, 24948) .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())) } /// Storage: AdvisoryCommitteeMembership Members (r:1 w:1) /// Proof: AdvisoryCommitteeMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) @@ -130,17 +121,14 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn reset_member(m: u32) -> Weight { + fn reset_member(_m: u32) -> Weight { // 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())) + // Measured: `338 + m * (64 ±0)` + // Estimated: `24948` + // Minimum execution time: 12_720 nanoseconds. + Weight::from_parts(21_581_000, 24948) .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())) } /// Storage: AdvisoryCommitteeMembership Members (r:1 w:1) /// Proof: AdvisoryCommitteeMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) @@ -153,17 +141,14 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn change_key(m: u32) -> Weight { + fn change_key(_m: u32) -> Weight { // 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())) + // Measured: `338 + m * (64 ±0)` + // Estimated: `24948` + // Minimum execution time: 13_350 nanoseconds. + Weight::from_parts(15_451_000, 24948) .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())) } /// Storage: AdvisoryCommitteeMembership Members (r:1 w:0) /// Proof: AdvisoryCommitteeMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) @@ -172,31 +157,25 @@ impl pallet_membership::weights::WeightInfo for WeightI /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn set_prime(m: u32) -> Weight { + fn set_prime(_m: u32) -> Weight { // 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())) + // Measured: `134 + m * (32 ±0)` + // Estimated: `7034` + // Minimum execution time: 5_770 nanoseconds. + Weight::from_parts(6_600_000, 7034) .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())) } /// Storage: AdvisoryCommitteeMembership Prime (r:0 w:1) /// Proof: AdvisoryCommitteeMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) /// Storage: AdvisoryCommittee Prime (r:0 w:1) /// Proof Skipped: AdvisoryCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn clear_prime(m: u32) -> Weight { + fn clear_prime(_m: u32) -> Weight { // 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())) - .saturating_add(T::DbWeight::get().writes(2)) + // Minimum execution time: 1_780 nanoseconds. + Weight::from_parts(1_830_000, 0).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..c395df6ec 100644 --- a/runtime/common/src/weights/pallet_multisig.rs +++ b/runtime/common/src/weights/pallet_multisig.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_multisig // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -53,14 +53,12 @@ use frame_support::{ pub struct WeightInfo(PhantomData); impl pallet_multisig::weights::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. - fn as_multi_threshold_1(z: u32) -> Weight { + fn as_multi_threshold_1(_z: u32) -> Weight { // 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: 9_020 nanoseconds. + Weight::from_parts(9_430_000, 0) } /// Storage: Multisig Multisigs (r:1 w:1) /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3350), added: 5825, mode: MaxEncodedLen) @@ -68,14 +66,14 @@ impl pallet_multisig::weights::WeightInfo for WeightInf /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `339 + s * (1 ±0)` + // Measured: `280 + s * (2 ±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: 21_591 nanoseconds. + Weight::from_parts(15_954_265, 5825) + // Standard Error: 16_525 + .saturating_add(Weight::from_parts(56_367, 0).saturating_mul(s.into())) + // Standard Error: 161 + .saturating_add(Weight::from_parts(868, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,14 +83,14 @@ impl pallet_multisig::weights::WeightInfo for WeightInf /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `283` + // Measured: `317` // 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: 15_631 nanoseconds. + Weight::from_parts(11_573_268, 5825) + // Standard Error: 6_160 + .saturating_add(Weight::from_parts(40_577, 0).saturating_mul(s.into())) + // Standard Error: 59 + .saturating_add(Weight::from_parts(809, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,56 +102,50 @@ impl pallet_multisig::weights::WeightInfo for WeightInf /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `425 + s * (33 ±0)` + // Measured: `458 + s * (34 ±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: 25_271 nanoseconds. + Weight::from_parts(18_623_040, 8432) + // Standard Error: 11_046 + .saturating_add(Weight::from_parts(66_479, 0).saturating_mul(s.into())) + // Standard Error: 108 + .saturating_add(Weight::from_parts(787, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// 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 approve_as_multi_create(s: u32) -> Weight { + fn approve_as_multi_create(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `343 + s * (1 ±0)` + // Measured: `280 + s * (2 ±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: 16_990 nanoseconds. + Weight::from_parts(22_170_000, 5825) .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 approve_as_multi_approve(s: u32) -> Weight { + fn approve_as_multi_approve(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `283` + // Measured: `317` // 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: 11_630 nanoseconds. + Weight::from_parts(15_290_000, 5825) .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)` + // Measured: `522 + s * (2 ±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: 16_390 nanoseconds. + Weight::from_parts(20_881_000, 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..ddf1034ec 100644 --- a/runtime/common/src/weights/pallet_parachain_staking.rs +++ b/runtime/common/src/weights/pallet_parachain_staking.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_parachain_staking // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -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: 10_440 nanoseconds. + Weight::from_parts(10_440_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: 13_821 nanoseconds. + Weight::from_parts(13_821_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: 11_971 nanoseconds. + Weight::from_parts(11_971_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: 10_280 nanoseconds. + Weight::from_parts(10_280_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: 7_890 nanoseconds. + Weight::from_parts(7_890_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: 8_791 nanoseconds. + Weight::from_parts(8_791_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: 19_731 nanoseconds. + Weight::from_parts(19_731_000, 1284) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -148,34 +148,28 @@ impl pallet_parachain_staking::weights::WeightInfo for /// Storage: ParachainStaking BottomDelegations (r:0 w:1) /// Proof Skipped: ParachainStaking BottomDelegations (max_values: None, max_size: None, mode: Measured) /// The range of component `x` is `[3, 1000]`. - fn join_candidates(x: u32) -> Weight { - // 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())) + fn join_candidates(_x: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `401 + x * (50 ±0)` + // Estimated: `317949` + // Minimum execution time: 25_120 nanoseconds. + Weight::from_parts(69_512_000, 317949) .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())) } /// Storage: ParachainStaking CandidateInfo (r:1 w:1) /// Proof Skipped: ParachainStaking CandidateInfo (max_values: None, max_size: None, mode: Measured) /// Storage: ParachainStaking CandidatePool (r:1 w:1) /// Proof Skipped: ParachainStaking CandidatePool (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `x` is `[3, 1000]`. - fn schedule_leave_candidates(x: u32) -> Weight { - // 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())) + fn schedule_leave_candidates(_x: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `305 + x * (49 ±0)` + // Estimated: `101662` + // Minimum execution time: 12_730 nanoseconds. + Weight::from_parts(44_090_000, 101662) .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())) } /// Storage: ParachainStaking CandidateInfo (r:1 w:1) /// Proof Skipped: ParachainStaking CandidateInfo (max_values: None, max_size: None, mode: Measured) @@ -196,36 +190,28 @@ impl pallet_parachain_staking::weights::WeightInfo for /// Storage: ParachainStaking Total (r:1 w:1) /// Proof Skipped: ParachainStaking Total (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `x` is `[2, 350]`. - fn execute_leave_candidates(x: u32) -> Weight { - // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(x.into()))) - .saturating_add(Weight::from_parts(0, 12995).saturating_mul(x.into())) + fn execute_leave_candidates(_x: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `319 + x * (595 ±0)` + // Estimated: `4570902` + // Minimum execution time: 41_411 nanoseconds. + Weight::from_parts(6_406_310_000, 4570902) + .saturating_add(T::DbWeight::get().reads(1055)) + .saturating_add(T::DbWeight::get().writes(1055)) } /// Storage: ParachainStaking CandidateInfo (r:1 w:1) /// Proof Skipped: ParachainStaking CandidateInfo (max_values: None, max_size: None, mode: Measured) /// Storage: ParachainStaking CandidatePool (r:1 w:1) /// Proof Skipped: ParachainStaking CandidatePool (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `x` is `[3, 1000]`. - fn cancel_leave_candidates(x: u32) -> Weight { - // 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())) + fn cancel_leave_candidates(_x: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `261 + x * (49 ±0)` + // Estimated: `101574` + // Minimum execution time: 12_311 nanoseconds. + Weight::from_parts(45_751_000, 101574) .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())) } /// Storage: ParachainStaking CandidateInfo (r:1 w:1) /// Proof Skipped: ParachainStaking CandidateInfo (max_values: None, max_size: None, mode: Measured) @@ -235,8 +221,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: 13_030 nanoseconds. + Weight::from_parts(13_030_000, 3712) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -248,8 +234,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: 11_990 nanoseconds. + Weight::from_parts(11_990_000, 3614) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -267,8 +253,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: 25_661 nanoseconds. + Weight::from_parts(25_661_000, 11796) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -278,8 +264,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: 9_441 nanoseconds. + Weight::from_parts(9_441_000, 2691) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -297,8 +283,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: 24_160 nanoseconds. + Weight::from_parts(24_160_000, 11856) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -308,8 +294,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: 9_060 nanoseconds. + Weight::from_parts(9_060_000, 2711) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -331,18 +317,18 @@ impl pallet_parachain_staking::weights::WeightInfo for /// The range of component `y` is `[2, 300]`. fn delegate(x: u32, y: u32) -> Weight { // 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())) + // Measured: `1472 + x * (108 ±0) + y * (53 ±0)` + // Estimated: `22156 + x * (540 ±0) + y * (270 ±0)` + // Minimum execution time: 45_551 nanoseconds. + Weight::from_parts(31_977_214, 22156) + // Standard Error: 5_812 + .saturating_add(Weight::from_parts(134_695, 0).saturating_mul(x.into())) + // Standard Error: 1_891 + .saturating_add(Weight::from_parts(52_098, 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())) - .saturating_add(Weight::from_parts(0, 265).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 540).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 270).saturating_mul(y.into())) } /// Storage: ParachainStaking DelegatorState (r:1 w:1) /// Proof Skipped: ParachainStaking DelegatorState (max_values: None, max_size: None, mode: Measured) @@ -352,8 +338,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: 10_380 nanoseconds. + Weight::from_parts(10_380_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -376,19 +362,14 @@ impl pallet_parachain_staking::weights::WeightInfo for /// 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 `x` is `[2, 100]`. - fn execute_leave_delegators(x: u32) -> Weight { - // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(x.into()))) - .saturating_add(Weight::from_parts(0, 13492).saturating_mul(x.into())) + fn execute_leave_delegators(_x: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + x * (563 ±0)` + // Estimated: `1385383` + // Minimum execution time: 36_731 nanoseconds. + Weight::from_parts(1_198_257_000, 1385383) + .saturating_add(T::DbWeight::get().reads(401)) + .saturating_add(T::DbWeight::get().writes(302)) } /// Storage: ParachainStaking DelegatorState (r:1 w:1) /// Proof Skipped: ParachainStaking DelegatorState (max_values: None, max_size: None, mode: Measured) @@ -398,8 +379,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: 11_720 nanoseconds. + Weight::from_parts(11_720_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -411,8 +392,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: 10_110 nanoseconds. + Weight::from_parts(10_110_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -436,8 +417,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: 32_770 nanoseconds. + Weight::from_parts(32_770_000, 23667) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -449,8 +430,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: 10_041 nanoseconds. + Weight::from_parts(10_041_000, 5302) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -476,8 +457,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: 37_821 nanoseconds. + Weight::from_parts(37_821_000, 28447) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -501,8 +482,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: 32_081 nanoseconds. + Weight::from_parts(32_081_000, 24399) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -514,8 +495,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: 10_330 nanoseconds. + Weight::from_parts(10_330_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -527,8 +508,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: 10_060 nanoseconds. + Weight::from_parts(10_060_000, 5546) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -550,8 +531,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: 18_520 nanoseconds. + Weight::from_parts(18_520_000, 11670) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -560,16 +541,12 @@ impl pallet_parachain_staking::weights::WeightInfo for /// Storage: ParachainStaking TopDelegations (r:1 w:0) /// Proof Skipped: ParachainStaking TopDelegations (max_values: None, max_size: None, mode: Measured) /// The range of component `y` is `[0, 100]`. - fn get_rewardable_delegators(y: u32) -> Weight { - // 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())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(Weight::from_parts(0, 96).saturating_mul(y.into())) + fn get_rewardable_delegators(_y: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `83 + y * (48 ±0)` + // Estimated: `14788` + // Minimum execution time: 3_630 nanoseconds. + Weight::from_parts(6_521_000, 14788).saturating_add(T::DbWeight::get().reads(2)) } /// Storage: ParachainStaking TotalSelected (r:1 w:0) /// Proof Skipped: ParachainStaking TotalSelected (max_values: Some(1), max_size: None, mode: Measured) @@ -591,20 +568,20 @@ impl pallet_parachain_staking::weights::WeightInfo for /// The range of component `y` is `[0, 100]`. fn select_top_candidates(x: u32, y: u32) -> Weight { // 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())) + // Measured: `0 + x * (5124 ±0) + y * (2390 ±0)` + // Estimated: `13898 + x * (27948 ±1_656) + y * (7648 ±828)` + // Minimum execution time: 14_520 nanoseconds. + Weight::from_parts(14_520_000, 13898) + // Standard Error: 1_776_838 + .saturating_add(Weight::from_parts(8_272_708, 0).saturating_mul(x.into())) + // Standard Error: 888_419 + .saturating_add(Weight::from_parts(1_025_784, 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)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(x.into()))) - .saturating_add(Weight::from_parts(0, 26124).saturating_mul(x.into())) - .saturating_add(Weight::from_parts(0, 6816).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 27948).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 7648).saturating_mul(y.into())) } /// Storage: ParachainStaking DelayedPayouts (r:1 w:0) /// Proof Skipped: ParachainStaking DelayedPayouts (max_values: None, max_size: None, mode: Measured) @@ -617,26 +594,21 @@ impl pallet_parachain_staking::weights::WeightInfo for /// Storage: System Account (r:301 w:301) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `y` is `[0, 300]`. - fn pay_one_collator_reward(y: u32) -> Weight { - // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(y.into()))) - .saturating_add(Weight::from_parts(0, 3483).saturating_mul(y.into())) + fn pay_one_collator_reward(_y: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `511 + y * (219 ±0)` + // Estimated: `1062338` + // Minimum execution time: 25_221 nanoseconds. + Weight::from_parts(1_698_988_000, 1062338) + .saturating_add(T::DbWeight::get().reads(306)) + .saturating_add(T::DbWeight::get().writes(303)) } fn base_on_initialize() -> Weight { // 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: 940 nanoseconds. + Weight::from_parts(940_000, 0) } /// Storage: ParachainStaking DelegatorState (r:1 w:0) /// Proof Skipped: ParachainStaking DelegatorState (max_values: None, max_size: None, mode: Measured) @@ -646,18 +618,18 @@ impl pallet_parachain_staking::weights::WeightInfo for /// The range of component `y` is `[0, 100]`. fn set_auto_compound(x: u32, y: u32) -> Weight { // 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())) + // Measured: `251 + x * (35 ±0) + y * (47 ±0)` + // Estimated: `5452 + x * (72 ±0) + y * (96 ±0)` + // Minimum execution time: 13_860 nanoseconds. + Weight::from_parts(10_831_000, 5452) + // Standard Error: 1_962 + .saturating_add(Weight::from_parts(37_266, 0).saturating_mul(x.into())) + // Standard Error: 5_888 + .saturating_add(Weight::from_parts(30_290, 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())) - .saturating_add(Weight::from_parts(0, 98).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 72).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 96).saturating_mul(y.into())) } /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) @@ -682,21 +654,21 @@ impl pallet_parachain_staking::weights::WeightInfo for /// The range of component `z` is `[0, 99]`. fn delegate_with_auto_compound(x: u32, y: u32, z: u32) -> Weight { // 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())) - .saturating_add(T::DbWeight::get().reads(8)) + // Measured: `0 + x * (86 ±0) + y * (33 ±0) + z * (121 ±0)` + // Estimated: `93843 + x * (425 ±15) + y * (78 ±15) + z * (286 ±55)` + // Minimum execution time: 47_191 nanoseconds. + Weight::from_parts(17_010_666, 93843) + // Standard Error: 3_271 + .saturating_add(Weight::from_parts(86_544, 0).saturating_mul(x.into())) + // Standard Error: 3_281 + .saturating_add(Weight::from_parts(42_262, 0).saturating_mul(y.into())) + // Standard Error: 11_566 + .saturating_add(Weight::from_parts(155_865, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(8)) - .saturating_add(Weight::from_parts(0, 367).saturating_mul(x.into())) - .saturating_add(Weight::from_parts(0, 73).saturating_mul(y.into())) - .saturating_add(Weight::from_parts(0, 230).saturating_mul(z.into())) + .saturating_add(Weight::from_parts(0, 425).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 78).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 286).saturating_mul(z.into())) } /// Storage: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) @@ -704,8 +676,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: 8_441 nanoseconds. + Weight::from_parts(8_441_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..1284d4723 100644 --- a/runtime/common/src/weights/pallet_preimage.rs +++ b/runtime/common/src/weights/pallet_preimage.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_preimage // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -57,14 +57,12 @@ impl pallet_preimage::weights::WeightInfo for WeightInf /// Storage: Preimage PreimageFor (r:0 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. - fn note_preimage(s: u32) -> Weight { + fn note_preimage(_s: u32) -> Weight { // 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: 15_300 nanoseconds. + Weight::from_parts(4_487_394_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,14 +71,12 @@ impl pallet_preimage::weights::WeightInfo for WeightInf /// Storage: Preimage PreimageFor (r:0 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. - fn note_requested_preimage(s: u32) -> Weight { + fn note_requested_preimage(_s: u32) -> Weight { // 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: 10_291 nanoseconds. + Weight::from_parts(4_497_135_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -89,14 +85,12 @@ impl pallet_preimage::weights::WeightInfo for WeightInf /// Storage: Preimage PreimageFor (r:0 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. - fn note_no_deposit_preimage(s: u32) -> Weight { + fn note_no_deposit_preimage(_s: u32) -> Weight { // Proof Size summary in bytes: // Measured: `106` // Estimated: `2566` - // Minimum execution time: 22_440 nanoseconds. - Weight::from_parts(22_760_000, 2566) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_008, 0).saturating_mul(s.into())) + // Minimum execution time: 10_140 nanoseconds. + Weight::from_parts(4_552_098_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -108,8 +102,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: 16_501 nanoseconds. + Weight::from_parts(16_501_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -121,8 +115,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: 10_910 nanoseconds. + Weight::from_parts(10_910_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -132,8 +126,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: 9_650 nanoseconds. + Weight::from_parts(9_650_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -143,8 +137,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: 6_730 nanoseconds. + Weight::from_parts(6_730_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,8 +148,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: 9_060 nanoseconds. + Weight::from_parts(9_060_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -165,8 +159,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: 6_050 nanoseconds. + Weight::from_parts(6_050_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +172,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: 10_231 nanoseconds. + Weight::from_parts(10_231_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -189,8 +183,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: 5_771 nanoseconds. + Weight::from_parts(5_771_000, 2566) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -200,8 +194,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: 5_760 nanoseconds. + Weight::from_parts(5_760_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..1708b4214 100644 --- a/runtime/common/src/weights/pallet_proxy.rs +++ b/runtime/common/src/weights/pallet_proxy.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_proxy // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -55,15 +55,12 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo Weight { + fn proxy(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `122 + p * (41 ±0)` + // Measured: `120 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 20_940 nanoseconds. - Weight::from_parts(25_140_189, 3844) - // Standard Error: 11_204 - .saturating_add(Weight::from_parts(82_019, 0).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 9_380 nanoseconds. + Weight::from_parts(10_150_000, 3844).saturating_add(T::DbWeight::get().reads(1)) } /// Storage: Proxy Proxies (r:1 w:0) /// Proof: Proxy Proxies (max_values: None, max_size: Some(1369), added: 3844, mode: MaxEncodedLen) @@ -75,14 +72,14 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: - // Measured: `521 + a * (72 ±0) + p * (41 ±0)` + // Measured: `555 + a * (72 ±0) + p * (39 ±0)` // Estimated: `11287` - // Minimum execution time: 45_610 nanoseconds. - Weight::from_parts(50_961_712, 11287) - // Standard Error: 20_838 - .saturating_add(Weight::from_parts(313_335, 0).saturating_mul(a.into())) - // Standard Error: 21_529 - .saturating_add(Weight::from_parts(82_019, 0).saturating_mul(p.into())) + // Minimum execution time: 19_850 nanoseconds. + Weight::from_parts(18_677_683, 11287) + // Standard Error: 45_508 + .saturating_add(Weight::from_parts(70_500, 0).saturating_mul(a.into())) + // Standard Error: 47_025 + .saturating_add(Weight::from_parts(37_816, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -92,16 +89,14 @@ impl 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)` + // Measured: `439 + 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: 11_780 nanoseconds. + Weight::from_parts(11_858_016, 7443) + // Standard Error: 1_983 + .saturating_add(Weight::from_parts(53_403, 0).saturating_mul(a.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -111,14 +106,16 @@ impl 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)` + // Measured: `439 + a * (72 ±0)` // Estimated: `7443` - // Minimum execution time: 27_740 nanoseconds. - Weight::from_parts(34_981_605, 7443) - // Standard Error: 15_466 - .saturating_add(Weight::from_parts(249_342, 0).saturating_mul(a.into())) + // Minimum execution time: 11_620 nanoseconds. + Weight::from_parts(11_527_516, 7443) + // Standard Error: 13_437 + .saturating_add(Weight::from_parts(57_758, 0).saturating_mul(a.into())) + // Standard Error: 13_885 + .saturating_add(Weight::from_parts(2_983, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -132,42 +129,38 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: - // Measured: `449 + a * (72 ±0) + p * (41 ±0)` + // Measured: `370 + a * (75 ±0) + p * (39 ±0)` // Estimated: `11287` - // Minimum execution time: 39_530 nanoseconds. - Weight::from_parts(51_332_901, 11287) - // Standard Error: 20_753 - .saturating_add(Weight::from_parts(60_767, 0).saturating_mul(a.into())) - // Standard Error: 21_442 - .saturating_add(Weight::from_parts(25_155, 0).saturating_mul(p.into())) + // Minimum execution time: 16_661 nanoseconds. + Weight::from_parts(15_730_483, 11287) + // Standard Error: 5_000 + .saturating_add(Weight::from_parts(82_241, 0).saturating_mul(a.into())) + // Standard Error: 5_167 + .saturating_add(Weight::from_parts(30_016, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// 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 add_proxy(p: u32) -> Weight { + fn add_proxy(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `122 + p * (41 ±0)` + // Measured: `120 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 29_740 nanoseconds. - Weight::from_parts(34_898_386, 3844) - // Standard Error: 16_204 - .saturating_add(Weight::from_parts(93_434, 0).saturating_mul(p.into())) + // Minimum execution time: 11_750 nanoseconds. + Weight::from_parts(12_601_000, 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_proxy(p: u32) -> Weight { + fn remove_proxy(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `122 + p * (41 ±0)` + // Measured: `120 + 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: 11_700 nanoseconds. + Weight::from_parts(13_341_000, 3844) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -176,38 +169,34 @@ impl pallet_proxy::weights::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: - // Measured: `122 + p * (41 ±0)` + // Measured: `120 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 23_490 nanoseconds. - Weight::from_parts(31_470_503, 3844) + // Minimum execution time: 10_170 nanoseconds. + Weight::from_parts(10_740_000, 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 create_pure(p: u32) -> Weight { + fn create_pure(_p: u32) -> Weight { // Proof Size summary in bytes: // Measured: `102` // Estimated: `3844` - // Minimum execution time: 30_780 nanoseconds. - Weight::from_parts(36_804_882, 3844) - // Standard Error: 14_018 - .saturating_add(Weight::from_parts(15_229, 0).saturating_mul(p.into())) + // Minimum execution time: 12_081 nanoseconds. + Weight::from_parts(12_630_000, 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 `[0, 30]`. - fn kill_pure(p: u32) -> Weight { + fn kill_pure(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `163 + p * (41 ±0)` + // Measured: `162 + p * (41 ±0)` // Estimated: `3844` - // Minimum execution time: 25_020 nanoseconds. - Weight::from_parts(29_081_365, 3844) - // Standard Error: 10_456 - .saturating_add(Weight::from_parts(47_688, 0).saturating_mul(p.into())) + // Minimum execution time: 10_850 nanoseconds. + Weight::from_parts(11_240_000, 3844) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/runtime/common/src/weights/pallet_scheduler.rs b/runtime/common/src/weights/pallet_scheduler.rs index 8a2a6cc71..721ef3e42 100644 --- a/runtime/common/src/weights/pallet_scheduler.rs +++ b/runtime/common/src/weights/pallet_scheduler.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for pallet_scheduler //! //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -58,22 +58,20 @@ impl 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: 3_070 nanoseconds. + Weight::from_parts(3_070_000, 503) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) /// The range of component `s` is `[0, 50]`. - fn service_agenda_base(s: u32) -> Weight { + fn service_agenda_base(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `114 + s * (181 ±0)` + // Measured: `3 + s * (183 ±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: 2_030 nanoseconds. + Weight::from_parts(22_911_000, 13142) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,25 +79,22 @@ 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: 3_970 nanoseconds. + Weight::from_parts(3_970_000, 0) } /// Storage: Preimage PreimageFor (r:1 w:1) /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) /// Storage: Preimage StatusFor (r:1 w:1) /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `s` is `[128, 4194304]`. - fn service_task_fetched(s: u32) -> Weight { + fn service_task_fetched(_s: u32) -> Weight { // 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())) + // Measured: `204 + s * (1 ±0)` + // Estimated: `4199553` + // Minimum execution time: 12_151 nanoseconds. + Weight::from_parts(3_027_077_000, 4199553) .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())) } /// Storage: Scheduler Lookup (r:0 w:1) /// Proof: Scheduler Lookup (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) @@ -107,41 +102,39 @@ 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: 4_670 nanoseconds. + Weight::from_parts(4_670_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: 3_670 nanoseconds. + Weight::from_parts(3_670_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: 2_190 nanoseconds. + Weight::from_parts(2_190_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: 1_900 nanoseconds. + Weight::from_parts(1_900_000, 0) } /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) /// The range of component `s` is `[0, 49]`. - fn schedule(s: u32) -> Weight { + fn schedule(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `114 + s * (181 ±0)` + // Measured: `3 + s * (183 ±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: 8_300 nanoseconds. + Weight::from_parts(20_950_000, 13142) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -150,14 +143,12 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn /// Storage: Scheduler Lookup (r:0 w:1) /// Proof: Scheduler Lookup (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) /// The range of component `s` is `[1, 50]`. - fn cancel(s: u32) -> Weight { + fn cancel(_s: u32) -> Weight { // 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: 14_370 nanoseconds. + Weight::from_parts(28_210_000, 13142) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -166,14 +157,12 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) /// The range of component `s` is `[0, 49]`. - fn schedule_named(s: u32) -> Weight { + fn schedule_named(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `297 + s * (189 ±0)` + // Measured: `3 + s * (196 ±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: 9_370 nanoseconds. + Weight::from_parts(24_591_000, 15669) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -182,14 +171,12 @@ impl pallet_scheduler::weights::WeightInfo for WeightIn /// Storage: Scheduler Agenda (r:1 w:1) /// Proof: Scheduler Agenda (max_values: None, max_size: Some(10667), added: 13142, mode: MaxEncodedLen) /// The range of component `s` is `[1, 50]`. - fn cancel_named(s: u32) -> Weight { + fn cancel_named(_s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `321 + s * (189 ±0)` + // Measured: `142 + s * (192 ±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: 11_860 nanoseconds. + Weight::from_parts(32_401_000, 15669) .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..d3e944ee0 100644 --- a/runtime/common/src/weights/pallet_timestamp.rs +++ b/runtime/common/src/weights/pallet_timestamp.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_timestamp // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -58,10 +58,10 @@ impl pallet_timestamp::weights::WeightInfo for WeightIn /// Proof: Aura CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) fn set() -> Weight { // Proof Size summary in bytes: - // Measured: `256` + // Measured: `289` // Estimated: `1006` - // Minimum execution time: 21_870 nanoseconds. - Weight::from_parts(27_420_000, 1006) + // Minimum execution time: 9_970 nanoseconds. + Weight::from_parts(9_970_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: 4_130 nanoseconds. + Weight::from_parts(4_130_000, 0) } } diff --git a/runtime/common/src/weights/pallet_treasury.rs b/runtime/common/src/weights/pallet_treasury.rs index 7ff7a7ce6..52346a667 100644 --- a/runtime/common/src/weights/pallet_treasury.rs +++ b/runtime/common/src/weights/pallet_treasury.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_treasury // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -60,10 +60,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn spend() -> Weight { // Proof Size summary in bytes: - // Measured: `42` + // Measured: `80` // Estimated: `1396` - // Minimum execution time: 23_240 nanoseconds. - Weight::from_parts(27_170_000, 1396) + // Minimum execution time: 11_190 nanoseconds. + Weight::from_parts(11_190_000, 1396) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -73,10 +73,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn propose_spend() -> Weight { // Proof Size summary in bytes: - // Measured: `179` + // Measured: `217` // Estimated: `499` - // Minimum execution time: 36_060 nanoseconds. - Weight::from_parts(37_371_000, 499) + // Minimum execution time: 14_720 nanoseconds. + Weight::from_parts(14_720_000, 499) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -86,10 +86,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn reject_proposal() -> Weight { // Proof Size summary in bytes: - // Measured: `545` + // Measured: `583` // Estimated: `7797` - // Minimum execution time: 56_050 nanoseconds. - Weight::from_parts(57_720_000, 7797) + // Minimum execution time: 21_061 nanoseconds. + Weight::from_parts(21_061_000, 7797) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -98,14 +98,12 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Storage: Treasury Approvals (r:1 w:1) /// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 99]`. - fn approve_proposal(p: u32) -> Weight { + fn approve_proposal(_p: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `500 + p * (8 ±0)` + // Measured: `171 + p * (11 ±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: 7_920 nanoseconds. + Weight::from_parts(10_680_000, 3480) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -113,10 +111,10 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) fn remove_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `127` + // Measured: `165` // Estimated: `897` - // Minimum execution time: 13_300 nanoseconds. - Weight::from_parts(16_380_000, 897) + // Minimum execution time: 6_720 nanoseconds. + Weight::from_parts(6_720_000, 897) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -133,18 +131,13 @@ impl pallet_treasury::weights::WeightInfo for WeightInf /// Storage: Bounties BountyApprovals (r:1 w:1) /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 100]`. - fn on_initialize_proposals(p: u32) -> Weight { + fn on_initialize_proposals(_p: u32) -> Weight { // 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into()))) - .saturating_add(Weight::from_parts(0, 7797).saturating_mul(p.into())) + // Measured: `260 + p * (319 ±0)` + // Estimated: `785123` + // Minimum execution time: 22_080 nanoseconds. + Weight::from_parts(1_354_945_000, 785123) + .saturating_add(T::DbWeight::get().reads(305)) + .saturating_add(T::DbWeight::get().writes(305)) } } diff --git a/runtime/common/src/weights/pallet_utility.rs b/runtime/common/src/weights/pallet_utility.rs index c061aa025..00d14a31e 100644 --- a/runtime/common/src/weights/pallet_utility.rs +++ b/runtime/common/src/weights/pallet_utility.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_utility // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -53,47 +53,41 @@ use frame_support::{ pub struct WeightInfo(PhantomData); impl pallet_utility::weights::WeightInfo for WeightInfo { /// The range of component `c` is `[0, 1000]`. - fn batch(c: u32) -> Weight { + fn batch(_c: u32) -> Weight { // 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: 5_580 nanoseconds. + Weight::from_parts(1_509_538_000, 0) } 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: 4_590 nanoseconds. + Weight::from_parts(4_590_000, 0) } /// The range of component `c` is `[0, 1000]`. - fn batch_all(c: u32) -> Weight { + 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: 5_170 nanoseconds. + Weight::from_parts(1_550_319_000, 0) } 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: 7_120 nanoseconds. + Weight::from_parts(7_120_000, 0) } /// The range of component `c` is `[0, 1000]`. - fn force_batch(c: u32) -> Weight { + 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: 5_660 nanoseconds. + Weight::from_parts(1_517_079_000, 0) } } diff --git a/runtime/common/src/weights/pallet_vesting.rs b/runtime/common/src/weights/pallet_vesting.rs index cf7ad7385..66010c976 100644 --- a/runtime/common/src/weights/pallet_vesting.rs +++ b/runtime/common/src/weights/pallet_vesting.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=pallet_vesting // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/frame_weight_template.hbs @@ -60,14 +60,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `405 + l * (25 ±0) + s * (40 ±0)` + // Measured: `402 + 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: 17_120 nanoseconds. + Weight::from_parts(16_221_925, 7418) + // Standard Error: 1_237 + .saturating_add(Weight::from_parts(49_510, 0).saturating_mul(l.into())) + // Standard Error: 2_245 + .saturating_add(Weight::from_parts(32_074, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -79,14 +79,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `405 + l * (25 ±0) + s * (40 ±0)` + // Measured: `402 + 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: 17_381 nanoseconds. + Weight::from_parts(16_156_777, 7418) + // Standard Error: 1_749 + .saturating_add(Weight::from_parts(42_846, 0).saturating_mul(l.into())) + // Standard Error: 3_175 + .saturating_add(Weight::from_parts(43_722, 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)` + // Measured: `541 + 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: 19_411 nanoseconds. + Weight::from_parts(17_824_333, 10025) + // Standard Error: 13_078 + .saturating_add(Weight::from_parts(56_326, 0).saturating_mul(l.into())) + // Standard Error: 23_735 + .saturating_add(Weight::from_parts(56_666, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -119,14 +121,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[1, 28]`. fn vest_other_unlocked(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `544 + l * (25 ±0) + s * (40 ±0)` + // Measured: `541 + 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: 19_851 nanoseconds. + Weight::from_parts(18_685_370, 10025) + // Standard Error: 3_358 + .saturating_add(Weight::from_parts(44_163, 0).saturating_mul(l.into())) + // Standard Error: 6_094 + .saturating_add(Weight::from_parts(41_629, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -138,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)` + // Measured: `313 + l * (25 ±0) + s * (51 ±0)` // Estimated: `10025` - // Minimum execution time: 63_280 nanoseconds. - Weight::from_parts(79_723_425, 10025) + // Minimum execution time: 25_141 nanoseconds. + Weight::from_parts(25_246_500, 10025) + // Standard Error: 4_754 + .saturating_add(Weight::from_parts(36_622, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -157,12 +161,12 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[0, 27]`. fn force_vested_transfer(l: u32, _s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `754 + l * (25 ±0) + s * (40 ±0)` + // Measured: `452 + l * (25 ±0) + s * (51 ±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: 27_081 nanoseconds. + Weight::from_parts(27_117_000, 12632) + // Standard Error: 4_771 + .saturating_add(Weight::from_parts(50_081, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -176,14 +180,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[2, 28]`. fn not_unlocking_merge_schedules(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `542 + l * (25 ±0) + s * (40 ±0)` + // Measured: `541 + 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: 19_421 nanoseconds. + Weight::from_parts(17_449_692, 10025) + // Standard Error: 1_749 + .saturating_add(Weight::from_parts(69_785, 0).saturating_mul(l.into())) + // Standard Error: 3_297 + .saturating_add(Weight::from_parts(70_403, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -197,14 +201,14 @@ impl pallet_vesting::weights::WeightInfo for WeightInfo /// The range of component `s` is `[2, 28]`. fn unlocking_merge_schedules(l: u32, s: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `542 + l * (25 ±0) + s * (40 ±0)` + // Measured: `541 + 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: 19_241 nanoseconds. + Weight::from_parts(17_301_461, 10025) + // Standard Error: 4_241 + .saturating_add(Weight::from_parts(76_530, 0).saturating_mul(l.into())) + // Standard Error: 7_994 + .saturating_add(Weight::from_parts(69_269, 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 32f9c528a..c346ffceb 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/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 } @@ -407,12 +408,14 @@ 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 "] edition = "2021" name = "zeitgeist-runtime" -version = "0.4.1" +version = "0.4.3" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs index 31f64b2c2..0222c99a3 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs @@ -177,10 +177,11 @@ fn transfer_btc_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = btc(0); let initial_sovereign_balance = btc(100); let transfer_amount = btc(100); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_btc(None); - + treasury_initial_balance = Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -230,6 +231,13 @@ fn transfer_btc_sibling_to_zeitgeist() { Tokens::free_balance(BTC_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(BTC_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) + ) }); } @@ -292,10 +300,11 @@ fn transfer_eth_sibling_to_zeitgeist() { let zeitgeist_alice_initial_balance = eth(0); let initial_sovereign_balance = eth(1); let transfer_amount = eth(1); + let mut treasury_initial_balance = 0; Zeitgeist::execute_with(|| { register_eth(None); - + treasury_initial_balance = Tokens::free_balance(ETH_ID, &ZeitgeistTreasuryAccount::get()); assert_eq!(Tokens::free_balance(ETH_ID, &ALICE), zeitgeist_alice_initial_balance,); }); @@ -351,6 +360,13 @@ fn transfer_eth_sibling_to_zeitgeist() { Tokens::free_balance(ETH_ID, &ALICE), zeitgeist_alice_initial_balance + expected_adjusted, ); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(ETH_ID, &ZeitgeistTreasuryAccount::get()), + // Align decimal fractional places + treasury_initial_balance + adjusted_balance(eth(1), eth_fee()) + ) }); } diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index f9cf35c7b..bd24a80b3 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. @@ -94,10 +94,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: 52, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 25, + transaction_version: 26, state_version: 1, }; @@ -106,7 +106,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 { @@ -123,14 +123,11 @@ impl Contains for IsCallable { set_code as set_code_contracts, }; use pallet_vesting::Call::force_vested_transfer; - - use zeitgeist_primitives::types::{ - MarketDisputeMechanism::{Court, SimpleDisputes}, - ScoringRule::RikiddoSigmoidFeeMarketEma, - }; + use zeitgeist_primitives::types::MarketDisputeMechanism::SimpleDisputes; use zrml_prediction_markets::Call::{ - create_cpmm_market_and_deploy_assets, create_market, edit_market, + admin_move_market_to_closed, admin_move_market_to_resolved, create_market, edit_market, }; + use zrml_swaps::Call::force_pool_exit; #[allow(clippy::match_like_matches_macro)] match runtime_call { @@ -164,27 +161,24 @@ impl Contains for IsCallable { }, // Membership is managed by the respective Membership instance RuntimeCall::Council(set_members { .. }) => false, - RuntimeCall::Court(_) => false, #[cfg(feature = "parachain")] RuntimeCall::DmpQueue(service_overweight { .. }) => false, - RuntimeCall::GlobalDisputes(_) => false, RuntimeCall::LiquidityMining(_) => false, RuntimeCall::PredictionMarkets(inner_call) => { match inner_call { - // Disable Rikiddo markets - create_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, - edit_market { scoring_rule: RikiddoSigmoidFeeMarketEma, .. } => false, - // Disable Court & SimpleDisputes dispute resolution mechanism - create_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, - edit_market { dispute_mechanism: Some(Court | SimpleDisputes), .. } => false, - create_cpmm_market_and_deploy_assets { - dispute_mechanism: Some(Court | SimpleDisputes), - .. - } => false, + // Disable SimpleDisputes dispute resolution mechanism + create_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, + edit_market { dispute_mechanism: Some(SimpleDisputes), .. } => false, + admin_move_market_to_closed { .. } => false, + admin_move_market_to_resolved { .. } => false, _ => true, } } RuntimeCall::SimpleDisputes(_) => false, + RuntimeCall::Swaps(inner_call) => match inner_call { + force_pool_exit { .. } => true, + _ => false, + }, RuntimeCall::System(inner_call) => { match inner_call { // Some "waste" storage will never impact proper operation. diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 4096b4d31..3ba53e6f5 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -168,6 +168,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. @@ -232,6 +234,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: Assets = Asset::Ztg; diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs index 40b91c5ea..6bcc84d60 100644 --- a/runtime/zeitgeist/src/xcm_config/config.rs +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -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, }; @@ -155,13 +155,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/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs index fbad3601a..ba3d7d56a 100644 --- a/runtime/zeitgeist/src/xcm_config/fees.rs +++ b/runtime/zeitgeist/src/xcm_config/fees.rs @@ -20,8 +20,9 @@ use crate::{Balance, Currencies}; 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/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 71ebb67a1..b63463893 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -30,8 +30,8 @@ export ZEITGEIST_PALLETS=( zrml_authorized zrml_court zrml_global_disputes zrml_liquidity_mining zrml_neo_swaps \ zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ ) -export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-1000}" -export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-10}" +export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-20}" +export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-50}" export ZEITGEIST_WEIGHT_TEMPLATE="./misc/weight_template.hbs" export PROFILE="${PROFILE:-production}" diff --git a/scripts/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/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/asset-router/Cargo.toml b/zrml/asset-router/Cargo.toml index 30e235325..3a264a4d2 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; diff --git a/zrml/authorized/Cargo.toml b/zrml/authorized/Cargo.toml index 743f29e8c..8dbfc6e58 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"] } @@ -38,4 +39,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-authorized" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 7d069e9e6..e491fc75e 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -392,7 +392,7 @@ where }, report: None, resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: MarketStatus::Disputed, bonds: MarketBonds::default(), early_close: None, diff --git a/zrml/authorized/src/migrations.rs b/zrml/authorized/src/migrations.rs index 13a374949..906e70698 100644 --- a/zrml/authorized/src/migrations.rs +++ b/zrml/authorized/src/migrations.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,59 +15,3 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . - -// We use these utilities to prevent having to make the swaps pallet a dependency of -// prediciton-markets. The calls are based on the implementation of `StorageVersion`, found here: -// https://github.com/paritytech/substrate/blob/bc7a1e6c19aec92bfa247d8ca68ec63e07061032/frame/support/src/traits/metadata.rs#L168-L230 -// and previous migrations. -mod utility { - use crate::{BalanceOf, Config, MarketIdOf}; - use alloc::vec::Vec; - use frame_support::{ - migration::{get_storage_value, put_storage_value}, - storage::{storage_prefix, unhashed}, - traits::StorageVersion, - Blake2_128Concat, StorageHasher, - }; - use parity_scale_codec::Encode; - use zeitgeist_primitives::types::{Pool, PoolId}; - - #[allow(unused)] - const SWAPS: &[u8] = b"Swaps"; - #[allow(unused)] - const POOLS: &[u8] = b"Pools"; - #[allow(unused)] - fn storage_prefix_of_swaps_pallet() -> [u8; 32] { - storage_prefix(b"Swaps", b":__STORAGE_VERSION__:") - } - #[allow(unused)] - pub fn key_to_hash(key: K) -> Vec - where - H: StorageHasher, - K: Encode, - { - key.using_encoded(H::hash).as_ref().to_vec() - } - #[allow(unused)] - pub fn get_on_chain_storage_version_of_swaps_pallet() -> StorageVersion { - let key = storage_prefix_of_swaps_pallet(); - unhashed::get_or_default(&key) - } - #[allow(unused)] - pub fn put_storage_version_of_swaps_pallet(value: u16) { - let key = storage_prefix_of_swaps_pallet(); - unhashed::put(&key, &StorageVersion::new(value)); - } - #[allow(unused)] - pub fn get_pool(pool_id: PoolId) -> Option, MarketIdOf>> { - let hash = key_to_hash::(pool_id); - let pool_maybe = - get_storage_value::, MarketIdOf>>>(SWAPS, POOLS, &hash); - pool_maybe.unwrap_or(None) - } - #[allow(unused)] - pub fn set_pool(pool_id: PoolId, pool: Pool, MarketIdOf>) { - let hash = key_to_hash::(pool_id); - put_storage_value(SWAPS, POOLS, &hash, Some(pool)); - } -} diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index d3d8daf5a..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; } @@ -201,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/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index 193980e98..5ed9f2c48 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_authorized // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -64,33 +64,31 @@ 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) /// Proof: PredictionMarkets MarketIdsPerDisputeBlock (max_values: None, max_size: Some(1042), added: 3517, mode: MaxEncodedLen) /// The range of component `m` is `[1, 63]`. - fn authorize_market_outcome_first_report(m: u32) -> Weight { + 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: `609 + m * (24 ±0)` + // Estimated: `9194` + // Minimum execution time: 19_500 nanoseconds. + Weight::from_parts(21_110_000, 9194) .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: `610` + // Estimated: `5677` + // Minimum execution time: 15_511 nanoseconds. + Weight::from_parts(15_511_000, 5677) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -98,17 +96,17 @@ 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: 190 nanoseconds. + Weight::from_parts(190_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:1) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn on_resolution_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `217` + // Measured: `250` // Estimated: `2524` - // Minimum execution time: 8_460 nanoseconds. - Weight::from_parts(10_380_000, 2524) + // Minimum execution time: 4_560 nanoseconds. + Weight::from_parts(4_560_000, 2524) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -116,31 +114,31 @@ 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: 930 nanoseconds. + Weight::from_parts(930_000, 0) } /// Storage: Authorized AuthorizedOutcomeReports (r:1 w:0) /// Proof: Authorized AuthorizedOutcomeReports (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn get_auto_resolve_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `217` + // Measured: `250` // 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: 4_200 nanoseconds. + Weight::from_parts(4_200_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. - Weight::from_parts(330_000, 0) + // Minimum execution time: 150 nanoseconds. + Weight::from_parts(150_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: 220 nanoseconds. + Weight::from_parts(220_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 +146,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: 940 nanoseconds. + Weight::from_parts(940_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/court/Cargo.toml b/zrml/court/Cargo.toml index 66e6961b0..4e2c9eec6 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"] } @@ -46,4 +47,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-court" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 9885c7b8e..39a810659 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -27,7 +27,7 @@ use crate::{ types::{CourtParticipantInfo, CourtPoolItem, CourtStatus, Draw, Vote}, AppealInfo, BalanceOf, Call, Config, CourtId, CourtPool, Courts, DelegatedStakesOf, MarketIdToCourtId, MarketOf, Pallet as Court, Pallet, Participants, RequestBlock, - SelectedDraws, VoteItem, + SelectedDraws, VoteItem, YearlyInflation, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::{account, benchmarks, whitelisted_caller}; @@ -77,7 +77,7 @@ where }), resolved_outcome: None, status: MarketStatus::Disputed, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, bonds: MarketBonds { creation: None, oracle: None, @@ -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 @@ -442,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(); @@ -450,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 { @@ -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 { @@ -579,6 +672,7 @@ benchmarks! { >::set_block_number(T::InflationPeriod::get()); let now = >::block_number(); + YearlyInflation::::put(Perbill::from_percent(2)); }: { Court::::handle_inflation(now); } diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 0a5f9a77d..10cf08bcb 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, @@ -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>; @@ -235,6 +239,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>; @@ -289,7 +294,7 @@ mod pallet { #[pallet::type_value] pub fn DefaultYearlyInflation() -> Perbill { - Perbill::from_perthousand(20u32) + Perbill::from_perthousand(0u32) } /// The current inflation rate. @@ -323,6 +328,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 +346,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 +466,18 @@ 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), + /// The inflation rate is too high. + InflationExceedsMaxYearlyInflation, + } + + // 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 +577,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 +627,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 +732,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 +816,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 +894,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 +973,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 +983,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 +996,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 +1021,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 +1073,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 +1101,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 +1118,7 @@ mod pallet { } } } + Ok(()) }; for draw in draws { @@ -1087,7 +1136,7 @@ mod pallet { debug_assert!(false); } - handle_vote(draw); + handle_vote(draw)?; } Self::slash_losers_to_award_winners(court_id, jurors_to_stakes, &winner); @@ -1118,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 }); @@ -1175,7 +1229,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; @@ -1249,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 @@ -1397,10 +1455,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 +1476,7 @@ mod pallet { } } - delegated_stakes + Ok(delegated_stakes) } // Updates the `selections` map for the juror and the lock amount. @@ -1427,7 +1488,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 +1503,7 @@ mod pallet { delegated_stakes.clone(), &delegated_juror, lock, - ); + )?; } SelectionAdd::DelegationWeight => { *weight = weight.saturating_add(1); @@ -1465,7 +1526,7 @@ mod pallet { DelegatedStakesOf::::default(), &delegated_juror, lock, - ); + )?; selections.insert( court_participant.clone(), SelectionValue { weight: 0, slashable: lock, delegated_stakes }, @@ -1483,10 +1544,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 +1562,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 +1595,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 +1658,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 +1698,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 +1717,7 @@ mod pallet { pool.remove(i); } - selections + Ok(selections) } // Converts the `selections` map into a vector of `Draw` structs. @@ -1685,7 +1800,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 +1875,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. @@ -2218,7 +2336,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/court/src/mock.rs b/zrml/court/src/mock.rs index 9f3b14f5a..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. @@ -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, + RequestInterval, VotePeriod, BASE, }, traits::DisputeResolutionApi, types::{ @@ -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 }) } } @@ -149,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; @@ -201,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; } @@ -254,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/court/src/tests.rs b/zrml/court/src/tests.rs index 2fd612fec..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. @@ -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, }, @@ -75,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, @@ -1002,8 +1004,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 +1513,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(), + ); }); } @@ -3040,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]; @@ -3086,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/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, } diff --git a/zrml/court/src/weights.rs b/zrml/court/src/weights.rs index 3842a44cd..85c59de52 100644 --- a/zrml/court/src/weights.rs +++ b/zrml/court/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_court // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -81,14 +81,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Balances Locks (r:1 w:1) /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) /// The range of component `j` is `[0, 999]`. - fn join_court(j: u32) -> Weight { + fn join_court(_j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1096 + j * (72 ±0)` + // Measured: `440 + j * (73 ±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: 18_071 nanoseconds. + Weight::from_parts(63_321_000, 78997) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -102,14 +100,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 * (75 ±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: 31_951 nanoseconds. + Weight::from_parts(11_783_401, 78997) + // Standard Error: 2_169 + .saturating_add(Weight::from_parts(72_269, 0).saturating_mul(j.into())) + // Standard Error: 539_100 + .saturating_add(Weight::from_parts(3_961_250, 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)) @@ -120,14 +118,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court CourtPool (r:1 w:1) /// Proof: Court CourtPool (max_values: Some(1), max_size: Some(72002), added: 72497, mode: MaxEncodedLen) /// The range of component `j` is `[0, 999]`. - fn prepare_exit_court(j: u32) -> Weight { + fn prepare_exit_court(_j: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1034 + j * (72 ±0)` + // Measured: `378 + j * (73 ±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: 12_601 nanoseconds. + Weight::from_parts(52_961_000, 75223) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -137,10 +133,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: `340` // Estimated: `6500` - // Minimum execution time: 37_360 nanoseconds. - Weight::from_parts(43_201_000, 6500) + // Minimum execution time: 16_850 nanoseconds. + Weight::from_parts(16_850_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -150,10 +146,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: `340` // Estimated: `6500` - // Minimum execution time: 35_770 nanoseconds. - Weight::from_parts(40_940_000, 6500) + // Minimum execution time: 15_560 nanoseconds. + Weight::from_parts(15_560_000, 6500) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -162,21 +158,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court SelectedDraws (r:1 w:1) /// Proof: Court SelectedDraws (max_values: None, max_size: Some(149974), added: 152449, mode: MaxEncodedLen) /// The range of component `d` is `[1, 510]`. - fn vote(d: u32) -> Weight { + fn vote(_d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `416 + d * (53 ±0)` + // Measured: `482 + 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: 17_341 nanoseconds. + Weight::from_parts(47_941_000, 155273) .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) @@ -184,21 +178,19 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court SelectedDraws (r:1 w:1) /// Proof: Court SelectedDraws (max_values: None, max_size: Some(149974), added: 152449, mode: MaxEncodedLen) /// The range of component `d` is `[1, 510]`. - fn denounce_vote(d: u32) -> Weight { + 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: `1570 + d * (53 ±0)` + // Estimated: `163667` + // Minimum execution time: 26_670 nanoseconds. + Weight::from_parts(59_282_000, 163667) .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) @@ -206,14 +198,12 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court SelectedDraws (r:1 w:1) /// Proof: Court SelectedDraws (max_values: None, max_size: Some(149974), added: 152449, mode: MaxEncodedLen) /// The range of component `d` is `[1, 510]`. - fn reveal_vote(d: u32) -> Weight { + 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: `2178 + d * (53 ±0)` + // Estimated: `163667` + // Minimum execution time: 30_621 nanoseconds. + Weight::from_parts(60_112_000, 163667) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -222,7 +212,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) @@ -245,20 +235,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: `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())) + // Measured: `0 + j * (141 ±0) + a * (35486 ±0) + r * (16 ±0) + f * (16 ±0)` + // Estimated: `515439 + j * (277 ±105) + a * (281048 ±44_881)` + // Minimum execution time: 2_175_665 nanoseconds. + Weight::from_parts(2_175_665_000, 515439) + // Standard Error: 1_730_902 + .saturating_add(Weight::from_parts(4_467_386, 0).saturating_mul(j.into())) + // Standard Error: 734_954_679 + .saturating_add(Weight::from_parts(1_828_359_258, 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().reads((103_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((104_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 277).saturating_mul(j.into())) + .saturating_add(Weight::from_parts(0, 281048).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) @@ -269,19 +259,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: System Account (r:511 w:510) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `d` is `[5, 510]`. - fn reassign_court_stakes(d: u32) -> Weight { + fn reassign_court_stakes(_d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `911 + 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(d.into()))) - .saturating_add(Weight::from_parts(0, 5333).saturating_mul(d.into())) + // Measured: `520 + d * (588 ±0)` + // Estimated: `2877710` + // Minimum execution time: 61_732 nanoseconds. + Weight::from_parts(9_913_050_000, 2877710) + .saturating_add(T::DbWeight::get().reads(1023)) + .saturating_add(T::DbWeight::get().writes(1022)) } /// Storage: Court YearlyInflation (r:0 w:1) /// Proof: Court YearlyInflation (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -289,8 +274,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: 7_440 nanoseconds. + Weight::from_parts(7_440_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) @@ -299,18 +284,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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 { + fn handle_inflation(_j: u32) -> Weight { // 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())) - .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()))) - .saturating_add(Weight::from_parts(0, 2607).saturating_mul(j.into())) + // Measured: `264 + j * (243 ±0)` + // Estimated: `2679996` + // Minimum execution time: 15_180 nanoseconds. + Weight::from_parts(6_159_567_000, 2679996) + .saturating_add(T::DbWeight::get().reads(1002)) + .saturating_add(T::DbWeight::get().writes(1000)) } /// Storage: Court CourtPool (r:1 w:1) /// Proof: Court CourtPool (max_values: Some(1), max_size: Some(72002), added: 72497, mode: MaxEncodedLen) @@ -321,19 +302,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court Participants (r:240 w:236) /// Proof: Court Participants (max_values: None, max_size: Some(251), added: 2726, mode: MaxEncodedLen) /// The range of component `a` is `[0, 3]`. - fn select_participants(a: u32) -> Weight { + fn select_participants(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `84018 + 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())) - .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: `90768 + a * (22375 ±0)` + // Estimated: `730329` + // Minimum execution time: 1_012_616 nanoseconds. + Weight::from_parts(7_988_843_000, 730329) + .saturating_add(T::DbWeight::get().reads(243)) + .saturating_add(T::DbWeight::get().writes(238)) } /// Storage: Court NextCourtId (r:1 w:1) /// Proof: Court NextCourtId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) @@ -361,18 +337,17 @@ 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)` - // 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())) - .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: `3094 + j * (81 ±0) + r * (17 ±0)` + // Estimated: `142620 + j * (23 ±0)` + // Minimum execution time: 115_813 nanoseconds. + Weight::from_parts(111_223_534, 142620) + // Standard Error: 2_001 + .saturating_add(Weight::from_parts(94_821, 0).saturating_mul(j.into())) + // Standard Error: 31_288 + .saturating_add(Weight::from_parts(26_612, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(29)) + .saturating_add(T::DbWeight::get().writes(31)) + .saturating_add(Weight::from_parts(0, 23).saturating_mul(j.into())) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -383,23 +358,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 { + 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(d.into()))) - .saturating_add(Weight::from_parts(0, 2726).saturating_mul(d.into())) + // Measured: `1482 + d * (255 ±0)` + // Estimated: `1553716` + // Minimum execution time: 22_631 nanoseconds. + Weight::from_parts(1_442_556_000, 1553716) + .saturating_add(T::DbWeight::get().reads(515)) + .saturating_add(T::DbWeight::get().writes(511)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -410,18 +380,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: System Account (r:4 w:4) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `a` is `[0, 4]`. - fn exchange(a: u32) -> Weight { + fn exchange(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `386 + 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())) - .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, 6331).saturating_mul(a.into())) + // Measured: `456 + a * (352 ±0)` + // Estimated: `30663` + // Minimum execution time: 7_140 nanoseconds. + Weight::from_parts(44_891_000, 30663) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(8)) } /// Storage: Court MarketIdToCourtId (r:1 w:0) /// Proof: Court MarketIdToCourtId (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) @@ -429,10 +395,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: `456` // 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: 5_951 nanoseconds. + Weight::from_parts(5_951_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 +409,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: `3222` + // Estimated: `83504` + // Minimum execution time: 19_051 nanoseconds. + Weight::from_parts(19_051_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) @@ -461,14 +427,16 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Court Participants (max_values: None, max_size: Some(251), added: 2726, mode: MaxEncodedLen) /// The range of component `a` is `[0, 4]`. /// The range of component `d` is `[1, 510]`. - fn on_global_dispute(_a: u32, d: u32) -> Weight { + fn on_global_dispute(a: u32, d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `448 + a * (66 ±0) + d * (256 ±0)` + // Measured: `1000 + a * (66 ±0) + d * (255 ±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: 14_930 nanoseconds. + Weight::from_parts(14_930_000, 157788) + // Standard Error: 1_850_182 + .saturating_add(Weight::from_parts(1_407_745, 0).saturating_mul(a.into())) + // Standard Error: 14_511 + .saturating_add(Weight::from_parts(2_752_653, 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)) @@ -484,18 +452,13 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court Courts (r:0 w:1) /// Proof: Court Courts (max_values: None, max_size: Some(349), added: 2824, mode: MaxEncodedLen) /// The range of component `d` is `[1, 510]`. - fn clear(d: u32) -> Weight { + fn clear(_d: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `363 + 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(d.into()))) - .saturating_add(Weight::from_parts(0, 2726).saturating_mul(d.into())) + // Measured: `911 + d * (255 ±0)` + // Estimated: `1545224` + // Minimum execution time: 11_950 nanoseconds. + Weight::from_parts(1_428_246_000, 1545224) + .saturating_add(T::DbWeight::get().reads(512)) + .saturating_add(T::DbWeight::get().writes(512)) } } diff --git a/zrml/global-disputes/Cargo.toml b/zrml/global-disputes/Cargo.toml index 0d089b5d7..8740e5b02 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"] } @@ -45,4 +46,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-global-disputes" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/global-disputes/src/mock.rs b/zrml/global-disputes/src/mock.rs index f8734b2f1..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; } @@ -199,6 +198,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/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/global-disputes/src/weights.rs b/zrml/global-disputes/src/weights.rs index e336c3eeb..894f9b37b 100644 --- a/zrml/global-disputes/src/weights.rs +++ b/zrml/global-disputes/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_global_disputes // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -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: `557 + o * (24 ±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: 23_721 nanoseconds. + Weight::from_parts(23_540_375, 13631) + // Standard Error: 146_250 + .saturating_add(Weight::from_parts(18_062, 0).saturating_mul(o.into())) + // Standard Error: 23_877 + .saturating_add(Weight::from_parts(41_928, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -97,18 +97,18 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `o` is `[1, 10]`. fn unlock_vote_balance_set(l: u32, o: u32) -> Weight { // 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())) + // Measured: `0 + l * (468 ±0) + o * (1600 ±0)` + // Estimated: `10554 + l * (2870 ±0)` + // Minimum execution time: 14_491 nanoseconds. + Weight::from_parts(12_323_777, 10554) + // Standard Error: 1_749 + .saturating_add(Weight::from_parts(1_608_230, 0).saturating_mul(l.into())) + // Standard Error: 9_718 + .saturating_add(Weight::from_parts(216_722, 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)) - .saturating_add(Weight::from_parts(0, 2871).saturating_mul(l.into())) + .saturating_add(Weight::from_parts(0, 2870).saturating_mul(l.into())) } /// Storage: GlobalDisputes Locks (r:1 w:1) /// Proof: GlobalDisputes Locks (max_values: None, max_size: Some(1641), added: 4116, mode: MaxEncodedLen) @@ -122,21 +122,21 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `o` is `[1, 10]`. 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())) + // Measured: `0 + l * (452 ±0) + o * (1600 ±0)` + // Estimated: `10554 + l * (2870 ±0)` + // Minimum execution time: 14_091 nanoseconds. + Weight::from_parts(8_142_111, 10554) + // Standard Error: 1_212 + .saturating_add(Weight::from_parts(1_524_320, 0).saturating_mul(l.into())) + // Standard Error: 6_735 + .saturating_add(Weight::from_parts(594_888, 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)) - .saturating_add(Weight::from_parts(0, 2871).saturating_mul(l.into())) + .saturating_add(Weight::from_parts(0, 2870).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) @@ -144,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: `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: `723 + w * (32 ±0)` + // Estimated: `11501` + // Minimum execution time: 28_091 nanoseconds. + Weight::from_parts(28_311_000, 11501) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -162,19 +160,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: System Account (r:11 w:11) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) /// The range of component `o` is `[1, 10]`. - fn reward_outcome_owner_shared_possession(o: u32) -> Weight { + 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_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())) - .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())) + // Measured: `512 + o * (42 ±0)` + // Estimated: `34418` + // Minimum execution time: 26_261 nanoseconds. + Weight::from_parts(94_423_000, 34418) + .saturating_add(T::DbWeight::get().reads(13)) + .saturating_add(T::DbWeight::get().writes(11)) } /// Storage: GlobalDisputes Outcomes (r:1 w:0) /// Proof: GlobalDisputes Outcomes (max_values: None, max_size: Some(395), added: 2870, mode: MaxEncodedLen) @@ -184,10 +177,10 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) fn reward_outcome_owner_paid_possession() -> Weight { // Proof Size summary in bytes: - // Measured: `537` + // Measured: `570` // Estimated: `10955` - // Minimum execution time: 65_360 nanoseconds. - Weight::from_parts(75_881_000, 10955) + // Minimum execution time: 26_021 nanoseconds. + Weight::from_parts(26_021_000, 10955) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -199,12 +192,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: `428 + 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: 37_701 nanoseconds. + Weight::from_parts(28_303_707, 8611) + // Standard Error: 59_671 + .saturating_add(Weight::from_parts(8_223_646, 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)) @@ -219,12 +212,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: `428 + 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: 36_521 nanoseconds. + Weight::from_parts(47_271_452, 8611) + // Standard Error: 164_967 + .saturating_add(Weight::from_parts(8_146_995, 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..ce3c51820 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"] } @@ -40,4 +41,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-liquidity-mining" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/liquidity-mining/src/mock.rs b/zrml/liquidity-mining/src/mock.rs index 22a76d3e3..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; } @@ -138,6 +137,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/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/liquidity-mining/src/weights.rs b/zrml/liquidity-mining/src/weights.rs index d70c2da17..b4402ebf1 100644 --- a/zrml/liquidity-mining/src/weights.rs +++ b/zrml/liquidity-mining/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_liquidity_mining // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -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: 2_750 nanoseconds. + Weight::from_parts(2_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..215042a49 100644 --- a/zrml/market-commons/Cargo.toml +++ b/zrml/market-commons/Cargo.toml @@ -8,9 +8,11 @@ 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"] } +test-case = { workspace = true } zeitgeist-primitives = { workspace = true, features = ["mock", "default"] } [features] @@ -31,4 +33,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-market-commons" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index aaef33b1d..8abd983b6 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -38,30 +38,30 @@ 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, HasCompact, MaxEncodedLen}; use sp_runtime::{ traits::{ - AccountIdConversion, AtLeast32Bit, AtLeast32BitUnsigned, CheckedAdd, - MaybeSerializeDeserialize, Member, Saturating, + AtLeast32Bit, AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member, Saturating, }, - ArithmeticError, DispatchError, SaturatedConversion, + DispatchError, + }; + 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); - - 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; @@ -89,11 +89,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; } @@ -132,7 +127,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) } @@ -143,7 +138,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; @@ -195,13 +190,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 { @@ -240,8 +228,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..7bcc6e2e5 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. @@ -15,61 +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 alloc::vec::Vec; - use frame_support::{ - storage::{storage_prefix, unhashed}, - traits::StorageVersion, - StorageHasher, - }; - use parity_scale_codec::Encode; - - #[allow(unused)] - pub fn storage_prefix_of_market_common_pallet() -> [u8; 32] { - storage_prefix(b"MarketCommons", b":__STORAGE_VERSION__:") - } - - #[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) - } - - #[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)); - } - - #[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)); - } -} diff --git a/zrml/market-commons/src/mock.rs b/zrml/market-commons/src/mock.rs index 89eebf8b1..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; } @@ -106,6 +105,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/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index 6c7f0716e..b9817d223 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -46,7 +46,7 @@ const MARKET_DUMMY: Market"] edition = "2021" name = "zrml-neo-swaps" -version = "0.4.1" +version = "0.4.3" 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/docs/docs.pdf b/zrml/neo-swaps/docs/docs.pdf index f98e4c2bfa37a5691c36ecc673d53b7cb94130c1..d0bfd0ca6c7160074a7198121b6b94c2a4cc0eeb 100644 GIT binary patch delta 100080 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

_ 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 UqL(Qi10M=EIW-C;B}Gq03VeyUaR2}S delta 99867 zcmV)TK(W82z6{d543H!PGB!DvL7xFAf3;a#kK?u#e&1i=0tG4qV~$AOs15Rvw%N2r z774Obw7@nG9Gf#%SC%Ha%x3!Q^Bo@2mJD`xfub+wRpjA!zH=d+ZXP%3=DSzvXUad` zy}Esqr5lxsvQ)+9?qMU#O4Mb&snS}MvfSJqHt&=F45xP2*?u>E+^RA$Pvdr%f2At< zbHCl8y`N9E+o~#=%(UIfr0Kiu&v$>r(sCnnk>#bLrHfSPyx#08Evl;Kl~a)miN&zW zztSRoXP+i}qNVP!a+TPD$Mg-ARiaXrVV>mW^K2t4p=6QM@;jNALQB2bp{#g$EG?xf zlD~G_OeWI+c~Ktb{j`!%in=JAe~jq{2NFsb{QcdraYxOPL+=FgN7c#1-A`B&-OtUz zdQ7IH<)M>7^<=i#x7nMPJ56`+)9ju;Ja(6{gPly%`eh#afqPfjYre>N!99=QLW zS}$DpQehqoRxv5u=e{&tJa3C4F+*c`G4AXixSXyI;9>v1f5aEl@kB?+vO0O|j!?NH zQ2X7!ADZrw8$Zphg0la-628@%8QPq2G_<(JdjG{&wGYhX#ZMn4M{- z{)6pCH|MtLKv~b$>^8>NtgMBEnWnr09T)VdRG$1}t5sr&DjDFOpi5L$GT5C%d>6i4 zN%#;9ZtB_Knu*&EDU=|Jw?vVOpgJYtKDyLs=W(>M7FSz~9Xp-=1t7DlXx2!!#PnM|c& z0_}5A>3Xg5uv4&be;~MU?1Q6$_bxw<-lY)A zrsI))fAV@@|BkLluOZ_M&N{D?(Y6n}7;y*tIJai9 zY^g_Hmm4mm&S(FJo~NPN`%VYekt&y<=0E+2c)_)a4^Dcg4uq(A zA`MaCM4ID+I)Br)T=VHh4Av8T^P_Q5kU(iV2ElOiVV>r};hj)d_w$78m9GDB}ctah*nS4H5IlKHUe}hAk}=%->qt+N*Hn~TRH68aJEIb1S^&+k3R4JMj^v%1eK31qC%li)smMyXrop$I6%1s> zWkDtne?xZ0hSune6cysTEBV;mKuC8~?Z_8E$Odrx>b-I9l)*pnbi>8mHCK!7`i=hEXAOAs6nnD5N8%10;uOt14Rq!rlRXI^;=% zVOeq*UdKbx$SKoGRLIlQwexkr*2$BLr0%DUe+h~}nd@R1C|#zEP5(WQ(HN5}QOGlA zKes;aLUp-Hs0i#LTZO)yLoD28IPk-#4h)m1R2|kFXmdN-;n~In$WwRmS{yKD8o8j# zc}1R4Y;bw2QppNMY=y=|RcGNc6kVsvAnaw<7Z_RCPA|4YdXd-b{kk~M%B)=O7p`+) ze;M7|)D_Ddu<)%HgQ5tvG$``Lop;W0_!TnUqKxqkig0`|PABFBgC3%eC`GoHg%$3j zY6BG2Djx}+ozm&ESBJEh^w7l?IZnBKGyyrY9@%q z`b$W3IhEIZJU@khP|NgGlFjSnYo1(We^(}?>Y948Lh~2@j8LW$5+vA>kFRr{*2JTL zNO2aK37vUXc*B9mS`t22C8;5+f|O|**|rpgF0U=8#^=yq>8R|E3MM0NufQ4z(o_*o}#sl@`e>_lY z9*~G9vd7t7)6g<4U$0$6B7RqSVt<+4?F7SgMJ;q1URN-9vc}mm2B)K$xYPI!*pAEQ zX`TrwItDnMHFP6SGV1vKSi?~fL32IWDP^TG#n)6G1lge z{p(e(2wS3pX<2v-i){s;h=q5He`)OPGKoX(E#(r=;l538Tn>ROg+FO0!+ndp%z+0T z$T6WcJ=fL$Uq-D!@Pzst3J9Qeu|mQ7AYH%*xq?`gBLs&mm`=_K5}$dz+fTVPgig4^w_ojUsc6n4-?dSl{8am^-OrPM-V0}-aehtW#M6JnIhfa}dnK*t<#(K{ zIYgx4yXIPqNcM1b8@!)if5xafw2sGM~`4VT?cJ;20I(V;w-J8Vub`hym6r(a!&`o^Q%Nd*u#;F-NtM?RBXP8v3J-;(R#JX_x&h27 z%)u2`TBr7DhC>X{=hxjp51qZA(b-?lXuSS#arTElOEF`HaHiz!;(BKI%qT;gP_v8W z?AN(CUoghi{D-^8V)OodA&i=5n`OB9C0~E7@abe+EBYVBK3>puKh|7|x6W^EY>!b2cx^g^_804O}MI zm5f+dX{<=CD~NNV96`Vun3-SP6mgOAy4-HO2v}uw_|#jB@fK72NQVMTm_43xuM8lC z2p}%lQuNjy^I;YwtcJ~p5$38PwRZ?h(L46xF4zd%9B#nf;-9M(O5TG54uOB4{w9ik zpCX7OHYisv_wO)9(T+ho%E}=+jTrIY?~+`DT`@hVUyzHaMtD;~%GcXn+Q%)CV)}e{ z`&uYbSi!YWYSFb)%PfR+pjz98iCv$JbY&l3C8YpnQX5%03!4GP8xSvo3(+$GZeha^ znqrI-1J-jK3vxhxRVGceXwcoabrZvih-vh!ZSj)4e20#wqRg%es+^w;;B?Jk5NiNFXp z;P0;Ub4BN+yL3=Mga1}Y6(xVT3Gq*Zy#nIc`LcLNnaqK|$P_qfzzTl>@q=^SF{pf> z?>-Zb>Zge%F;FlEhrHHeG9NYXlk&X|dts|C; z=YVZXGDVtaN2?_;k|civ190q4qjim1=MdrhNz%kXgy8U7kMU-Y(|VB-vL;l@rq~O*Ni#5?u^kvHB=d}Jj;07j9p_8m@$V!Y@uK_ z)h4~U7^v9@gjf^J>buJ{#6cVg80_kdRw&45QRH{~sFE^ofoER+ChZjt(yKZ)ex3IC zM%6OONH5<)nW%qFDY6dMDFCTXXzw)-4zhFZ_hTI`#S70U;EAMqOz(|BQ}zBPI4cJk zDJ&s4NC{Nw{~MGkgYgcZ2o=TfuOxc{i(C%k&;ud`nLB`0Knf@UhlQ$uS10tTTuw}L zIsI&xTPVo=P{Kkth9=RBdp5mjZNF$i0oJ8N+Y%Y>GVp&+Fo)#39`GcV_X@H|X->oH zDkzRmQJi_AIMuga10|X?2_MKuJccDbMh$t)H$D8DPSotoVzg<&P@3Q>*-0A*+)~ES zF)Io6XMvSEpDSrILDFK9q&ZXVT!*!$VORy#g9g0TbTXZeu%%(gSVy16nkL42wAP}g z$E6umCI){{5bAiqIWdwq*4r*a0VAcjUYC3*3F{z;f)J(9H#r*y5c_`sAiY{U4jeur zoO-wA6Wx||wBM;3I{fw7KR?E2pMzMkYx`}MFq4oi&QE64gIQ1)X*zUwkfzUXRm^rE z7HfMdzttb%w;g)|26(h5U8|WT_xl8)x?v6d)uVqpJ^jy95gS)gm8doaf%z!sJuU`$ ztC{Fx`ZIAcG45h*89X;9HXjwtr@7b|?cXLnQNRy8k63Vs12hJ!f8^P27tcz38G5+M z_i-EGknh%U6XJal_6PRq9e;>{QnV-TGYdV%EZD0d?~|aTOr_DMRs*kSLf z+|x===PIIBn?ACzp@Nz(?)=4_U(}sTm&T+S4Ao-CiX&Wz$&qKG%_pVKaf?n*lDeL@ zD{@4VG-NgGdrn%@tPccIs(Gm<&2;bZ1 z$NseSy;Fig4wD_qyYkWYkYSIM;PY&~y*p>~a3UK1_3e6Muyc)56x!HXyQ)UiG{k>y zJ-prJS9w)S`6orXrUL$4eX`%1V(BRN8fky-3tPrCm>8ce_O=QOumxF1tGV?e>PBN%;}(78 zeqWxES3Ohy_f@vKx?5$nQuAadm~zC{OYd*7$6{f zqq8Oa_u~w*7h`Ale&iYt)D1_HRS@abc_=g79|z%^HnNu9w&Z_>4ySpTz1d;W zwkr7HF4tC(c)F0+7l;*hF?n;e<9ps!<76ySo?;gk`pj6Uc2w%poz?fD8qv2G`S z(pjp0vtDP>t+uo#4)uSTx&WiAyNHMF+`W`0s*Zp}P_|EOXk*BI@l?Drl;$rJn5ckqlY%Qlf}m8SU%X+E`ax^>`-<}mnFqI&o<#(E-*48 zc)M^N6kce<9P7Mwui1HPN4rEJS@*rC3a)aXR|r3!0f{ouW7&UD^4S~y*nH`viCc(o zRQmD^kDkN#LYrVNx7qGcVEVL+U(WOqZXiWtU5HSFlIkJYh?bH0;3e%qQUKTTbQ???UDm8JV+x1Ab1i-Ov1FSE;HRUG2Sm_ETQ_J>_@ zc^5WhFzYG5fu0pPAQfC610g_0SemznK!Wr?!Ik^F_%G~0VsdnTuXD? z$PvElS1^Z^p|UwU??*O!NGiLjP5F@2DwR~N_W+Twgc2VzKw9=Czdbz=S* zW>r2fS2veaMriiFC~E(dSA~ClAX$BXIi)%)X5~9fcro{_dFb}O%4eH$y~-E<{T&uK z_g!Ev-?n~#`Po8j{7Wt;A94Vpw8tAUF;{Cb`1KSy9 z>*f8T*c5(TT{IKj`w>-cIZ}t!n$g9lVzz~SA-8uNZ~EsIbkZhU!x|Ml8k{}YEv~Ei zC5XV~lw@x%ryPs03Fv>iC_ik=dAYIOKKrKoYHi;>mh+;jeN$D;it?jvErx`E>W&ah z30B#sq%3H}glKxsEwP$Ktk4C?g^l|YL-V|>H&uDP^~?zSb6tQ!dBZ}2B&RVofJ3XB z8mP*p^atirbq=iUfo)TBV4#sZvzHP3iK{4a}g7ACk^d;Wja&BWDudvn`crSPIr zR)zn;?HO8TMP2(I^P--?vHV=~6gPAKW7wOcJ%|9w9?H$Fr3B0Vmtux6NDl$Zn)1I$f zdG)zp9r)<(c4#$eY>W$e_H*eR%^T z0=83O(j1M!zlO=Z^+cziL_)W74G6Ac06?9oio|-tnYr|OdT6~WbTBa7C?&l$)YRe~ zq9m}mOiaTN0aA84{cxSyyT|12-h&0^YywimEFYoUlp3q$lPR!B5$<~e@`Df%DzaaS z#lpHE308jqvQY|qQGQH7Q^uLMPr28R+BZ2p;XJD!tcd0JN%muj3>U|MLWveAwyUyP zAPo>;!?0|1p&ZZ_v%|P`<);fq%mRj102tPc)cy!%-E>(MzU4aD9e2Pk!FuP@810nc zQ$mtFSOIgG4lYq@9_e&&+UfRZSbNAgQ-%b4Qi6XarU95$h#D3{-N!UQW39)a6&sK9 z!65_`OFE|1I;MSV8Uq&CTPBX;m4)+1F|-eA-~~nuZTO^&_dx+-^Ti@GY&YO#7Ihjy zDbljFh_b)5Mb-Gnw!sB!NykVd#d&|vs`jN(nsyoE%sm9_-X6HVs|ZUV(zuQwpidDFtN}*?n`802 z=sPSr+7t!P1soOyP(O?~3RwrI(`%dq_}u1#%I5x{KOeK+mOMrY5mBN{FRL)^QeuC^ z#mN^fhd~lMyG{y!iOB4x*o2!PN}Oc4 zc*BS$U<;9_KP-zlMgXnhor!>g|4n~}W6tXW6W-wN-2iQkRU!^(Ne>5s!UciqXb`CS zhK6>6K#s7GAak0~80fo^A^<%F|8y!~HW6=L$C0pNR(=(WL&caGQ$fL}w=tmXbWIU9 z2--2y64;gwq$ROohy!&=Y0nXjBJ@2!bTFJQhXN(xImgCu?)Y_qN=SYjo7;b&=SYZe zZ>15(@(^Jx-#VSf2-C;Jp24ZWX2hQRcWAvyl#K3NI=57VyGctLJYo{xsEja4FNbS; z7c-)FF+;l-Gp~;k=~Ghwl~hjuS*-uvr6`h`gM#gP4e%s7+JN>G2>Q7_AJYJdbhMo* zl8#A_El(%cNln4SbGItJu)a>_;0R3RNe^Ea88H3o{QF@veeX8*~<}8jhOpg$Q zBq6MuP3eHHYP8D|IG?Jf6%1%kX!2vsqr{J#;uD-z`i_03d2-w!=fi&uEn$rH^uZ=c zb0*wS-yCkJXU7fof^eg2+SQqydiBja_0J}9$5SqnsaNj9FR?xgG05oP_FvuF3z%&^ zd>ymLRn8G3y&LSa3GbJH0DA_Gz@t<$VoAuoN$1^Qm*IC7Adn+z`E`7JbtWHQjZfhi zV-KDI2;}p64S_)4Cv|_EVVl)q52q1PdmkvRTKi-xnQgY!%HPosf3hS16~gIy2Qr@Z znmE4n?dujb=9q37=+xeWU#HjHIkk(`a0;Cb3Dxg47Wf7(>W-?{>+=HQK`{8D>o@A0 z=aduOb0Z@s`kWG;HtdLDbN%N_-+Sdw0hit&I1cQk+RP#9~0Ly@P8V|)AB zk7G{>v&VggF9#$|BTN-M*aU$WE@zUE&CM{w#ijY=(a?}Hg)!#1Wt$`Deaetd>lf(Zs2oHhY-%$ z)|}W~yM#mA8d8$&W2e|9B;zxXaUo?Kw;sB|;f9V%2z`H40{a0vJga{Vq9hClQOFAQDcA5&ALGVn%Rhui! zy)S3Jo>*qr<$RjgcfNCdU%|-mgEu;EfWM1xjMMe@^DYC`k1G~oI%lNOuG&jkapIw7 zi$Q-hse`fNp~~xTpI7;^oLPG-vbT+E$OyQG1BG7_pe{)0+ql}83Y9JM_C+^m@Gtp% zZVkVEw07Tiu8L)+95(zo|7@!-?WXd@B1~kdX#ZBl&DJ^j4m-QxJ1-kUha(~?U9@=DY|M@DEFV}y!p=3D}gJ`p>vJSPQ{@;afE5o==zxU*&EGR%}6bw-zM@6v}0p z9x6Z~xC@z~9;!X%X1&S7;mW$+S{e0kX~&niB5uh-vy`JU4SrD-OmB|{b~#!{-Zqw-Z%t_z%+B~MGH zlf1@jyLVmf&i&?_jcZipZ%dlEH^8{(@v!#ZlX_gP;3@PnlkBQ=xsn-Ae!ee0_jI4i zs`4xkvmf&%4EIb69Wu=s$U#zI-ZK3$wlHW?Bf24*T~+0O&ECKIdTChVUsG-$t72QY zbt0j~igTGtkn0M>ErfCu+WT_<1x#To+1qu&_+JPW&A#=$Zk0v7*~fUM#H5PI<2;j8 zN;%=#QMXb7@;8U5M6^t)Hm8aA0$J~H-EHA{FItd|T|6TqH5r2jO_j>r=pT0|o+lh^ z5||caLGV<6(X69i##jJ#W&N(*IB_A(EZOfI?e;#85|=X7qL)nLhZv-&3AH0qrg6(s zo$(_qC`%_8b%k0jWBI+_$+;G@hK6XU7nCQ-c8zhGy}^XY~MLk>+iLT0!Vy_0&uNdA8LhdG{w3oe@BlWmj&eqT*s_TX_g9VCV^q9n{wdoh)}0g zMnaXTI!mPLEh24ZM5+Mt^)hwPM5&Z(?W41C&#V#)N|PF6#FJ~cyEd;}v)u0wdA+uO zg9zHV?VZ{N_Bq>dss8gwX?`X{&Uw}G+`~CSoc%f@LzKBzTIR@(; zx`?N4T~YC70&Fsrue_R#D_N?UoXA)R1IjGMM*ZWi!%7ySSat1vIVxBsYLB|1oDeQCU4fE;E zf6MlM)8v@ZJbiMW1N=<$oB(V;!K+~V@NFIZ)h<$C?~wDrk9APt?yHws4?4wvF6+S; z_gT*f-#2v>&?s(&(FeAuqtkje^da#s^f_0eIfywMyOJN`*q)Izqw*YxIRKt{-`5j* zyo&k`V2EZs^c^$6k;pTEBUd5>j@l$NW)-kR6WG_RK@2(N*{G6>5LGgVD$OjaRGhsI zRq{cdX%D34K&6Ios7%DBXThX@I0_~+<){;W-vQ2nRN4T`IjI>ZHUIeDH^xBjh&aK2>x+~C_#JK8 zpO5Fvd3etJNqA0?RAzEqZ1Kj7>B)L`s5Y)ycO@9tUk`B=k#!r-nP^IsJXIzOYBE=t z1A{%3b*vb|!KOqk^`E#fK&ayN#gH=YUj!SK5r(=I$^q+Eu4U09{NAG>TX_{v)w62` zF3}6~_JCD7+w7CoSd0OGB&f7HDnJkzz?5l{9@HB(cL1Ghg*7>Qe{jnw{ajC(CtJ{xym82@bzO~3WOr2%ED$EyW%#1< zu9;LkcaLL?gr&o(>a(|P6w#3>l;a-Cy_oT_&+z;Mj(5UaQa*Ow_LZNgJA&e~oKo?F&ODkd1Y|(&9 z+|Iq9;WrFl)U(j3X!Sf2sn?8sfkDS66N+`gO;Nv7icMT5bR71$794YH_XE1X{bG$D z)eFq^dTILPx^akqG9-9wrG%}aFf$tP0xCiurYcqW5U|nu;5u0kxT1aQ;qwv$ua~$< zHixzE*@l`YK^gOh-gCL0jeC2sx0#LYvYL(lul_uoW-LE&=id43ZuXE)_IFl1rM6$J1X)ugM#x(A0AY^?BFJl1CVVnbGvXUTV-RtJ<7)=6;v zqc?W6Hiaj$FI(3$#g5(lUDMvjOQr&X<*D#P{3dmr|=ID|Y zkm&|g^-MWI3q7&G$->Ne3sYot{^*bb20%c6XR&gU2=l-wO`c|%2y#G4l|2WxdVgfM zBnLR$Pkf#+XF~X?LeU_-k5^GDU#C;@5V~r#%LPgDN7Su@eCUNfx*#m#caPOk-Xh!=-rn%YCkR+NhVTPQ z0?@-fi7ua{f&&T*hRsB_AP}6ru$#GmZ2viWGKaRWLRkGrL>wz(7dlbv zEU-(S8fFy?GPtqzj_P6HJ<-qigRbJ9h-C{`O`qim0{|8Qhfz0ig%*NgJ+c>n!5O~b zwW2g5QA(pOBPcc!)SUJ!Ezew^I)$3fP`8WSX+LL44_D&fz>t0M0{h}_j(26F43^?AsU zz{8C_BwxGAkFSbDfXDA(MybbtceSs#?2Ci17Oj1qsrWlidq;q2lA^jqm^>Ub#bReHBB^1?Fr8LXB-2+ZdhG-TK7SG(hm%BF5oaffRqhohY0p39B6qlM! zSKaO5Ti4z1^d*Ja@BIa&`3baAiPYfQeL5eaZm@N3P(1;1C?9XB_aMVM>7u#wjQrAc-8+epq z9T%>Zy{_#QCQ?FtmwVjWYhkZncm2~rbwI-n1(!`F^RSK*iRC3KNb+I1GWb=*%4h_g z&`!HHKCa#1U)|3a%N3+R5IgK$_aV}m&exkSVUUYl)_QxApKQE;ucvg5?T(gwG1I-E zAbV^3bV)$47d^tm;EA>42X4K!wB4BJ zR|h#QZGGgDian70RK#E0T%PR4~H-1GsUOh?imQ0uuo+mqF?R69O|Zmp`8YDu3-<+mhQh5`CYq z;3Kt;Ft}fw^;S7sc51Up%9~7{Y(Houj;*prazx1<|NS-q-avsACDD$(wUshOQDAqY z(Wm>M$q7#0Bslr;3Rj=-A0lvkm(=I~zWVcuxbGy924O-HB^c5wncurxC^Fh4gdxFA zvdxmatN)xLQX@@hLx1GU*Em_gPd~w6W_0q=4ZOF5y^2-xdqcw*l8)J^Xlu$`5JZ(c zd(wh3cmJ4<6jUNE`52x&UlbBn7#x4R`r+p4`Z<@0Kt{n-Z|)MI5z|(xLWYUtW|6#1 z`Q-J@Pn8MR&!tERMg|*V?|^tC%39^dZYNWMMf%czEd9r8D}Uhnxxmf)7(ogat+?+M z=Gc!4r4xw+)#a$^WU5tqJDDmb(!arnr0EJibh?3~N#B4zMnIUrUi13{b0`CIrV86Z zq?lNI*`D1*P{qNwl#C0jTIR*LIz!YDV1=_D4OwG4v%B8AaHRGp9Q!?04}e8 z`=&7Dw?nvbf`5#ZB_N);uwT35;)tZ=B>+XG&)r~;m+iqVgMk1pQfg!%)G{vy!Wp85 z03n?9Xh0a#nN{$P;LQtU4FUqgNHgg}S@u>L2zyzTflsk|LI@&)52z=MsdChVuO!#D zl92S?VPfWv5A8}~3meC_k^qJX9CeD^IV#WX&ud#@=zm2O2HZ#~9T!-&%!`3_hNvOH z3THhUu*P&|1%?s4d4a4!z#(N4dOlMYMIf6GYkTod8xq(|q5##hk&OAb+aUdA8VZC&Y#jqbl8P{IGw{R`dO8w#)q2 zA7-oBdY>cszvb zW_v$jBn6GRCp%UmM~q81E1XQJacyfa3XwwCGnm;uSh?`BUTl~1%%48J+htpS`hV;p zoA0ekFFJIX6-^P5MTf1t4d$Hr+q~nHZ6@2RxBE~28=FaPyZ&ud#Tj!%fmmjd-%{TX1RI!DqsdiM$m|}*EVdaSLyvW_#V(Hh} zM}K9T<=OVb1TJNjSuusSUs8*UDt}Z+H%!W7Vy297WGHp(pn2I+@x&PL3Mm-7>?x2; zU}X7_pM+wuW7I2*aa5x_Z9N-ln@(!lZ=Y${el6QIe#44lfI~j zJ(8e-m?>4dbbV+$#fTbn%yt8VbZalw)@drsSL;q!*~la6G*@NO5akeljUa7^5{+>Z z2^}mVi3n3j(3nlSOVC|J6W9`6yQUB*Xf=!4Xxd zra|5q(v(=3LWK%o+HwN{tA87V;-1imf!QrbkSBaQrb^VX){ zeIDj$S>|Y!zAq{rT#Khwf(;}=5DH<0i{kb^n!+?VDU!n4m4j&#rZ9nz<^d>Yf~Ma? zv6d>mEk0wp9jvsa{xH3VR#4D?==(+vhEr&DOikH`f{bE)PTU-di zqc(_RtlCx+;12^>HGiDgg;!(pYH^HLi(|ZcKv6p2)sH$bsU&H|OyC30Opg^GVBSOq z82NfgB8gexj>5uQ6Kqu{ zB`Zyjj@7r*QoHC&?L#WzliO%{ckG)BXIpOxfEEGJ4iodE_uUE&j*oG(SeCr487G2@Hbtg><@*#SBv1^wa56 zlUhHYKE4iYSo%;$(+9gaeGFxWa!C4+W2O%^M*0XG*_S>{pmb3BkgfF5>RmHx3#uiq z2H_H&s%SU=x_`gVAaRtf>wo3>KFhnh%8)S1zYO^Oy#-a8&6n<0jnnyTYtxUmuPVEp z{?1>&^#lEv-Rzy?i)%m9_2%L7%w7a0sngAJ;eXmxm$L2DUz)8Ke)xX9$hNOA5pK}O z++Y3p){C{K_*TCs<8Wv8Fd>j2vpipJ)^2)sK2Sk9FMmR1we*I?GbKQm=pg?N>tufO zbVB#hiH8pCG{X3x^tK{Czz*@j{-|r7_4|q6`Zly@qC_{A>x*^hs}%C^y+g;8Xo58O zX-W*DjQ-Ue`b+iF&qqeU4?ja#oj`BDGK}@Ls>sREAOo?%Lm#U+SLMCD8%tyxK9nO| zHhdiK=6_hH_p>+a 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,11 +105,11 @@ 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())?; - let market_id = >::latest_market_id()?; + let market_id = zrml_market_commons::Pallet::::latest_market_id()?; Ok((caller, market_id)) } @@ -118,11 +121,16 @@ 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::Lmsr, + Some(period), + Some(MarketDisputeMechanism::Court), + )?; Call::::admin_move_market_to_closed { market_id } .dispatch_bypass_filter(T::CloseOrigin::try_successful_origin().unwrap())?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end: u32 = match market.period { MarketPeriod::Timestamp(range) => range.end.saturated_into::(), _ => { @@ -144,8 +152,9 @@ 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), )?; let outcome: OutcomeReport; @@ -162,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::(), _ => { @@ -179,34 +188,24 @@ fn setup_redeem_shares_common( Ok((caller, market_id)) } -fn setup_reported_categorical_market_with_pool( +fn setup_reported_categorical_market( categories: u32, report_outcome: OutcomeReport, -) -> Result<(T::AccountId, MarketIdOf), &'static str> { +) -> Result<(T::AccountId, MarketIdOf), &'static str> +where + T: Config + pallet_timestamp::Config, +{ let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(categories.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, - )?; - - 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, + Some(MarketDisputeMechanism::Court), )?; Call::::admin_move_market_to_closed { market_id } .dispatch_bypass_filter(T::CloseOrigin::try_successful_origin().unwrap())?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let end: u32 = match market.period { MarketPeriod::Timestamp(range) => range.end.saturated_into::(), _ => { @@ -222,6 +221,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 @@ -231,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(); @@ -239,17 +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), @@ -270,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); @@ -297,16 +296,16 @@ benchmarks! { let r in 0..63; let categories = T::MaxCategories::get(); - let (_, market_id) = setup_reported_categorical_market_with_pool::( + let (_, market_id) = setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(0u16), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let report_at = market.report.unwrap().at; let resolves_at = report_at.saturating_add(market.deadlines.dispute_duration); @@ -338,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); @@ -354,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(), @@ -386,12 +385,12 @@ benchmarks! { let categories = T::MaxCategories::get(); let (caller, market_id) = - setup_reported_categorical_market_with_pool::( + setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(2) )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -412,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( @@ -438,8 +437,9 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, + Some(MarketDisputeMechanism::Court), )?; let approve_origin = T::ApproveOrigin::try_successful_origin().unwrap(); @@ -451,8 +451,9 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, + Some(MarketDisputeMechanism::Court), )?; let approve_origin = T::ApproveOrigin::try_successful_origin().unwrap(); @@ -465,8 +466,9 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, + Some(MarketDisputeMechanism::Court), )?; let amount = BASE * 1_000; }: _(RawOrigin::Signed(caller), market_id, amount.saturated_into()) @@ -476,10 +478,10 @@ 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); + let range_end = 200_000; + let period = MarketPeriod::Timestamp(100_000..range_end); for i in 0..m { MarketIdsPerCloseTimeFrame::::try_mutate( @@ -497,21 +499,21 @@ benchmarks! { metadata, MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::CPMM + Some(MarketDisputeMechanism::Court), + ScoringRule::Lmsr ) edit_market { let m in 0..63; let market_type = MarketType::Categorical(T::MaxCategories::get()); - let dispute_mechanism = Some(MarketDisputeMechanism::SimpleDisputes); - let scoring_rule = ScoringRule::CPMM; + let dispute_mechanism = Some(MarketDisputeMechanism::Court); + 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); 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(), @@ -525,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]; @@ -556,103 +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)), - )?; - - 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)), - )?; - - 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(); @@ -665,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(()) })?; @@ -677,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(), )?; @@ -705,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], @@ -715,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(); @@ -739,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 }; }: { @@ -755,38 +660,39 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Advised, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, - Some(MarketPeriod::Timestamp(T::MinSubsidyPeriod::get()..T::MaxSubsidyPeriod::get())), + ScoringRule::Lmsr, + Some(MarketPeriod::Timestamp(100_000..200_000)), + Some(MarketDisputeMechanism::Court), )?; - let market = >::market(&market_id.saturated_into())?; + let market = zrml_market_commons::Pallet::::market(&market_id.saturated_into())?; }: { Pallet::::handle_expired_advised_market(&market_id, market)? } internal_resolve_categorical_reported { let categories = T::MaxCategories::get(); - let (_, market_id) = setup_reported_categorical_market_with_pool::( + let (_, market_id) = setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(1u16), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; }: { Pallet::::on_resolution(&market_id, &market)?; } verify { - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; assert_eq!(market.status, MarketStatus::Resolved); } internal_resolve_categorical_disputed { let categories = T::MaxCategories::get(); let (caller, market_id) = - setup_reported_categorical_market_with_pool::( + setup_reported_categorical_market::( categories.into(), OutcomeReport::Categorical(1u16) )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.dispute_mechanism = Some(MarketDisputeMechanism::Authorized); Ok(()) })?; @@ -801,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); } @@ -815,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); } @@ -829,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, @@ -844,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); } @@ -857,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()) @@ -890,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(); @@ -898,17 +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), @@ -925,16 +805,17 @@ benchmarks! { let m in 0..63; // ensure range.start is now to get the heaviest path - let range_start: MomentOf = >::now(); + let range_start: MomentOf = zrml_market_commons::Pallet::::now(); let range_end: MomentOf = 1_000_000u64.saturated_into(); let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { // ensure sender is oracle to succeed extrinsic call market.oracle = caller.clone(); Ok(()) @@ -943,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::() @@ -971,9 +852,9 @@ 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::()?; + let (caller, oracle, _, metadata) = create_market_common_parameters::(false)?; Call::::create_market { base_asset: Asset::Ztg, creator_fee: Perbill::zero(), @@ -988,10 +869,10 @@ benchmarks! { creation: MarketCreation::Permissionless, market_type: MarketType::Categorical(3), dispute_mechanism: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, } .dispatch_bypass_filter(RawOrigin::Signed(caller.clone()).into())?; - let market_id = >::latest_market_id()?; + let market_id = zrml_market_commons::Pallet::::latest_market_id()?; let close_origin = T::CloseOrigin::try_successful_origin().unwrap(); Pallet::::admin_move_market_to_closed(close_origin, market_id)?; let outcome = OutcomeReport::Categorical(0); @@ -1005,8 +886,9 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(a.saturated_into()), - ScoringRule::CPMM, + ScoringRule::Lmsr, None, + Some(MarketDisputeMechanism::Court), )?; let amount: BalanceOf = LIQUIDITY.saturated_into(); Pallet::::buy_complete_set( @@ -1016,25 +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())), - )?; - 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; @@ -1046,8 +912,9 @@ 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(); } @@ -1057,43 +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(); } @@ -1109,11 +975,12 @@ benchmarks! { let (_, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..range_end)), + Some(MarketDisputeMechanism::Court), )?; // ensure market is reported - >::mutate_market(&market_id, |market| { + zrml_market_commons::Pallet::::mutate_market(&market_id, |market| { market.status = MarketStatus::Reported; Ok(()) })?; @@ -1144,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; @@ -1162,8 +1020,9 @@ 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), )?; for i in 0..o { @@ -1173,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 { @@ -1196,8 +1055,9 @@ 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), )?; for i in 0..o { @@ -1207,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 { @@ -1240,11 +1100,12 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; - let market = >::market(&market_id)?; + let market = zrml_market_commons::Pallet::::market(&market_id)?; let market_creator = market.creator.clone(); for i in 0..o { @@ -1254,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 { @@ -1277,8 +1138,9 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1288,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 @@ -1327,8 +1189,9 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1339,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 @@ -1374,8 +1237,9 @@ benchmarks! { let (caller, market_id) = create_market_common::( MarketCreation::Permissionless, MarketType::Categorical(T::MaxCategories::get()), - ScoringRule::CPMM, + ScoringRule::Lmsr, Some(MarketPeriod::Timestamp(range_start..old_range_end)), + Some(MarketDisputeMechanism::Court), )?; let market_creator = caller.clone(); @@ -1395,16 +1259,42 @@ benchmarks! { let call = Call::::reject_early_close { market_id }; }: { call.dispatch_bypass_filter(close_origin)? } + close_trusted_market { + 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::Lmsr, + Some(MarketPeriod::Timestamp(range_start..range_end)), + None, + )?; + + 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 { + // 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 (caller, oracle, deadlines, metadata) = create_market_common_parameters::()?; - let price = (BASE / 2).saturated_into(); + 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 amount = (10u128 * BASE).saturated_into(); ::AssetManager::deposit( @@ -1426,13 +1316,46 @@ 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() ) + 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 3c17c40d3..a573a78fa 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -50,6 +50,7 @@ mod pallet { transactional, Blake2_128Concat, BoundedVec, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "parachain")] use { @@ -67,13 +68,13 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, Swaps, + DisputeResolutionApi, }, 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}; @@ -83,28 +84,34 @@ 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 AccountIdOf = ::AccountId; pub(crate) type AssetOf = Asset>; 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 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) => { @@ -126,7 +133,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(()) @@ -186,7 +200,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() }); @@ -316,30 +343,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()))] #[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 @@ -350,7 +367,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()) @@ -358,9 +375,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, @@ -411,16 +427,14 @@ 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, #[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); @@ -428,30 +442,16 @@ 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()) + let default_weight: Option = None; + Ok((default_weight, Pays::No).into()) } /// Request an edit to a proposed market. @@ -467,16 +467,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() @@ -492,7 +491,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. @@ -585,92 +585,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 @@ -682,7 +596,7 @@ mod pallet { #[transactional] pub fn create_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -693,7 +607,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, @@ -735,7 +648,7 @@ mod pallet { #[transactional] pub fn edit_market( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, market_id: MarketIdOf, oracle: T::AccountId, period: MarketPeriod>, @@ -745,7 +658,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), @@ -785,183 +697,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 @@ -979,7 +714,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); @@ -1067,7 +802,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 = @@ -1093,15 +836,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. @@ -1114,14 +853,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(), - 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, @@ -1130,7 +863,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() @@ -1138,10 +870,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()) } @@ -1324,12 +1053,15 @@ 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( origin: OriginFor, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -1356,8 +1088,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. @@ -1691,6 +1424,84 @@ 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()))] + #[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 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(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] @@ -1813,10 +1624,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; @@ -1825,10 +1632,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; @@ -1915,9 +1718,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] @@ -2043,6 +1843,16 @@ 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, + /// 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] @@ -2061,11 +1871,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. @@ -2097,15 +1902,11 @@ 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. + RecoveryLimitReached { last_time_frame: TimeFrame, limit_time_frame: TimeFrame }, } #[pallet::hooks] @@ -2114,14 +1915,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, @@ -2144,25 +1937,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, @@ -2207,7 +1981,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!( @@ -2230,36 +2004,6 @@ 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, - >; - - #[pallet::storage] - pub type MarketIdsPerOpenBlock = StorageMap< - _, - Blake2_128Concat, - T::BlockNumber, - BoundedVec, CacheSize>, - ValueQuery, - >; - - #[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< @@ -2305,16 +2049,6 @@ mod pallet { ValueQuery, >; - /// 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, - >; - /// Contains market_ids for which advisor has requested edit. /// Value for given market_id represents the reason for the edit. #[pallet::storage] @@ -2342,10 +2076,15 @@ 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, - base_asset: Asset>, + base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -2394,11 +2133,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)?; @@ -2407,10 +2142,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(); @@ -2479,40 +2211,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, @@ -2568,15 +2270,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.", @@ -2596,7 +2292,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)?; @@ -2618,16 +2321,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); @@ -2788,45 +2485,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); @@ -2857,10 +2515,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) @@ -3103,8 +2757,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)?; @@ -3125,168 +2777,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] { @@ -3320,7 +2810,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); @@ -3368,67 +2876,8 @@ 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>, + base_asset: AssetOf, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, @@ -3441,7 +2890,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, @@ -3468,17 +2917,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 6c20312f8..2893d660c 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,390 +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]) - } - - #[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()); - } - } -} - -// 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 @@ -408,26 +29,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 97aaabacb..2c70692e1 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -38,37 +38,32 @@ 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, AssetsAccountDeposit, AssetsApprovalDeposit, AssetsDeposit, AssetsMetadataDepositBase, - AssetsMetadataDepositPerByte, AssetsStringLimit, AuthorizedPalletId, - BalanceFractionalDecimals, BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, - CloseEarlyDisputeBond, CloseEarlyProtectionBlockPeriod, - CloseEarlyProtectionTimeFramePeriod, CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, - CorrectionPeriod, CourtPalletId, DestroyAccountWeight, DestroyApprovalWeight, - DestroyFinishWeight, ExistentialDeposit, ExistentialDepositsNew, 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, MinAssets, - MinCategories, MinDisputeDuration, MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, - MinSubsidy, MinSubsidyPeriod, MinWeight, MinimumPeriod, OutcomeBond, OutcomeFactor, + AssetsMetadataDepositPerByte, AssetsStringLimit, AuthorizedPalletId, BlockHashCount, + BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, + CloseEarlyProtectionBlockPeriod, CloseEarlyProtectionTimeFramePeriod, + CloseEarlyRequestBond, CloseEarlyTimeFramePeriod, CorrectionPeriod, CourtPalletId, + DestroyAccountWeight, DestroyApprovalWeight, DestroyFinishWeight, ExistentialDeposit, + ExistentialDepositsNew, 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, - SwapsPalletId, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, - MILLISECS_PER_BLOCK, + TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, MILLISECS_PER_BLOCK, }, traits::DeployPoolApi, types::{ AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CampaignAsset, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, Hash, Index, - MarketAsset, MarketId, Moment, PoolId, UncheckedExtrinsicTest, + MarketAsset, MarketId, Moment, UncheckedExtrinsicTest, }, }; -use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; @@ -154,7 +149,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; @@ -185,10 +179,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}, @@ -225,10 +217,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; @@ -242,7 +232,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; } @@ -472,6 +461,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; @@ -494,25 +484,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; @@ -542,29 +516,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 = (); @@ -609,6 +560,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(); @@ -656,7 +610,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 89cd5a80d..000000000 --- a/zrml/prediction-markets/src/tests.rs +++ /dev/null @@ -1,6426 +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, MarketIdsPerOpenBlock, - MarketIdsPerReportBlock, TimeFrame, -}; -use alloc::collections::BTreeMap; -use core::ops::{Range, RangeInclusive}; -use frame_support::{ - assert_err, assert_noop, assert_ok, - dispatch::{DispatchError, DispatchResultWithPostInfo}, - 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::{AccountIdConversion, 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, - }, -}; -use zrml_global_disputes::{ - types::{OutcomeInfo, Possession}, - 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> { - 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::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)] -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::CPMM, - ); - 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::CPMM, - ); - 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::CPMM - )); - 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")] -#[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, - ); - 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_open_and_close_blocks() { - ExtBuilder::default().build().execute_with(|| { - let category_count = 3; - assert_ok!(PredictionMarkets::create_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(22..66), - 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::Block(33..66), - 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::Block(22..33), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 0, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - 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); - }); -} - -#[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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - )); - 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::CPMM, - ); - 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; "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( - market_status: MarketStatus, -) { - ExtBuilder::default().build().execute_with(|| { - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Permissionless, - 0..33, - ScoringRule::CPMM, - ); - 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::CPMM, - ); - - // 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::CPMM, - ); - - 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::CPMM, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = MarketCommons::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::CPMM, - ); - let market_id = 0; - let market = MarketCommons::market(&market_id).unwrap(); - let market_account = MarketCommons::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::CPMM - ), - 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::CPMM - ), - 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::CPMM, - ); - - // 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::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]; - // 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::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]; - // 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::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 + 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::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]; - - 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::CPMM, - ); - - // 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::CPMM, - ); - - // 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::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]; - - 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::CPMM, - ); - - // 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::CPMM, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..66, - ScoringRule::CPMM, - ); - simple_create_categorical_market( - Asset::Ztg, - MarketCreation::Advised, - 22..33, - ScoringRule::CPMM, - ); - 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::CPMM, - ); - 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::CPMM, - ); - 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_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( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Block(0..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(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( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..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(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 - // 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_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..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(0..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(10 * end); - run_to_block(2); // Trigger `on_initialize`; must be at least block #2! - - let market_after_close = MarketCommons::market(&0).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(0).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); - System::assert_has_event(Event::MarketClosed(0).into()); - - let market_after_close = MarketCommons::market(&1).unwrap(); - assert_eq!(market_after_close.status, MarketStatus::Closed); - let pool_after_close = Swaps::pool(1).unwrap(); - assert_eq!(pool_after_close.pool_status, PoolStatus::Closed); - System::assert_has_event(Event::MarketClosed(1).into()); - }); -} - -#[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_cpmm_market_and_deploy_assets( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - ALICE, - MarketPeriod::Timestamp(0..end), - get_deadlines(), - gen_metadata(50), - MarketType::Categorical(category_count), - Some(MarketDisputeMechanism::SimpleDisputes), - 123, - LIQUIDITY, - vec![::MinWeight::get(); category_count.into()], - )); - - // 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::CPMM, - ); - - // 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 = AssetManager::free_balance(*asset, &BOB); - assert_eq!(bal, CENT); - } - - let market_account = MarketCommons::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::CPMM, - ); - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ), - 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::CPMM, - ); - 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::CPMM, - ); - 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_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() { - 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::CPMM, - ); - - 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 = AssetManager::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::CPMM, - ); - 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::CPMM, - ); - 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::CPMM, - ); - - 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::CPMM, - ); - - 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::CPMM, - ); - - 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::CPMM - )); - - 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::CPMM, - ); - 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::CPMM, - ); - 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::CPMM, - ); - 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::CPMM, - ); - - // 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::CPMM, - )); - - // 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::CPMM, - )); - - // 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::CPMM, - )); - - // 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::CPMM, - ); - - // 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::CPMM, - ); - - // 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::CPMM, - ); - - // 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::CPMM, - ); - - // 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::CPMM, - ); - - // 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM - )); - - 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::CPMM, - )); - - // 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::CPMM, - )); - - // 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::CPMM, - ); - - 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::CPMM, - ); - - 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 = AssetManager::total_issuance(share_b); - assert_eq!(share_b_total, CENT); - 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); - let share_a_bal = AssetManager::free_balance(share_a, &CHARLIE); - assert_eq!(share_a_bal, CENT); - - let share_c = Asset::CategoricalOutcome(0, 2); - let share_c_total = AssetManager::total_issuance(share_c); - assert_eq!(share_c_total, 0); - let share_c_bal = AssetManager::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::CPMM, - ); - - 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::CPMM, - )); - - 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::CPMM, - )); - - 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::CollectingSubsidy; "collecting_subsidy")] -#[test_case(MarketStatus::InsufficientSubsidy; "insufficient_subsidy")] -#[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::CPMM, - ); - - 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::CPMM, - )); - 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::CPMM, - ); - - 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 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!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 0), &ALICE), 0); - assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 1), &ALICE), 0); - assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 2), &ALICE), 0); - assert_eq!(AssetManager::free_balance(Asset::CategoricalOutcome(0, 3), &ALICE), 0); - - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(0, 0), &pool_account), - amount - ); - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(0, 1), &pool_account), - amount - ); - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(0, 2), &pool_account), - amount - ); - assert_eq!( - AssetManager::free_balance(Asset::CategoricalOutcome(0, 3), &pool_account), - amount - ); - assert_eq!(AssetManager::free_balance(base_asset, &pool_account), amount); - - let pool = Pools::::get(0).unwrap(); - let assets_expected = vec![ - Asset::CategoricalOutcome(market_id, 0), - Asset::CategoricalOutcome(market_id, 1), - Asset::CategoricalOutcome(market_id, 2), - Asset::CategoricalOutcome(market_id, 3), - base_asset, - ]; - assert_eq!(pool.assets, assets_expected); - assert_eq!(pool.base_asset, base_asset); - assert_eq!(pool.market_id, market_id); - assert_eq!(pool.scoring_rule, ScoringRule::CPMM); - assert_eq!(pool.swap_fee, Some(swap_fee)); - assert_eq!(pool.total_subsidy, None); - assert_eq!(pool.total_subsidy, None); - assert_eq!(pool.total_weight, Some(total_weight)); - let pool_weights = pool.weights.unwrap(); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 0)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 1)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 2)], weight); - assert_eq!(pool_weights[&Asset::CategoricalOutcome(market_id, 3)], weight); - assert_eq!(pool_weights[&base_asset], base_asset_weight); - }; - - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_activates_market_with_sufficient_subsidy() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let min_subsidy = ::MinSubsidy::get(); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(ALICE), 0, min_subsidy)); - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert_eq!(subsidy_queue.len(), 0); - assert_eq!(MarketCommons::market(&0).unwrap().status, MarketStatus::Active); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_blocks_market_with_insufficient_subsidy() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let subsidy = ::MinSubsidy::get() / 3; - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(ALICE), 0, subsidy)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(BOB), 0, subsidy)); - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert_eq!(subsidy_queue.len(), 0); - assert_eq!(MarketCommons::market(&0).unwrap().status, MarketStatus::InsufficientSubsidy); - - // Check that the balances are correctly unreserved. - assert_eq!(AssetManager::reserved_balance(base_asset, &ALICE), 0); - assert_eq!(AssetManager::reserved_balance(base_asset, &BOB), 0); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn process_subsidy_keeps_market_in_subsidy_queue_until_end_of_subsidy_phase() { - let test = |base_asset: Asset| { - let min_sub_period = - ::MinSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - let max_sub_period = - ::MaxSubsidyPeriod::get() / (MILLISECS_PER_BLOCK as u64); - - simple_create_categorical_market( - base_asset, - MarketCreation::Permissionless, - min_sub_period + 42..max_sub_period, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - - // Run to block where 2 markets are ready and process all markets. - run_to_block(min_sub_period); - let subsidy_queue = crate::MarketsCollectingSubsidy::::get(); - assert!(subsidy_queue.len() == 1); - assert!(subsidy_queue[0].market_id == 0); - assert!(MarketCommons::market(&0).unwrap().status == MarketStatus::CollectingSubsidy); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn start_subsidy_creates_pool_and_starts_subsidy() { - let test = |base_asset: Asset| { - // Create advised categorical market using Rikiddo. - simple_create_categorical_market( - base_asset, - MarketCreation::Advised, - 1337..1338, - ScoringRule::RikiddoSigmoidFeeMarketEma, - ); - let market_id = 0; - let mut market = MarketCommons::market(&market_id).unwrap(); - - // Ensure and set correct market status. - assert_err!( - PredictionMarkets::start_subsidy(&market, market_id), - Error::::MarketIsNotCollectingSubsidy - ); - assert_ok!(MarketCommons::mutate_market(&market_id, |market_inner| { - market_inner.status = MarketStatus::CollectingSubsidy; - market = market_inner.clone(); - Ok(()) - })); - - // Pool was created and market was registered for state transition into active. - assert_ok!(PredictionMarkets::start_subsidy(&market, market_id)); - assert_ok!(MarketCommons::market_pool(&market_id)); - let mut inserted = false; - - for market in crate::MarketsCollectingSubsidy::::get() { - if market.market_id == market_id { - inserted = true; - } - } - - assert!(inserted); - }; - ExtBuilder::default().build().execute_with(|| { - test(Asset::Ztg); - }); - #[cfg(feature = "parachain")] - ExtBuilder::default().build().execute_with(|| { - test(Asset::ForeignAsset(100)); - }); -} - -#[test] -fn only_creator_can_edit_market() { - ExtBuilder::default().build().execute_with(|| { - // 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 - ); - }); -} - -#[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::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)); - - // 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::CPMM - )); - 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::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 - // 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::CPMM - ), - 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::CPMM - ), - 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::CPMM - )); - 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::CPMM - )); - - // 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::CPMM - )); - - 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 = AssetManager::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!(AssetManager::transfer( - RuntimeOrigin::signed(CHARLIE), - EVE, - Asset::ScalarOutcome(0, ScalarPosition::Short), - 50 * BASE - )); - - assert_eq!( - AssetManager::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 = AssetManager::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::CPMM, - ); - 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::CPMM, - ); - 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 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. - 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::CPMM, - )); - 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::CPMM, - )); - 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 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() - { - // 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - - 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - 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::CPMM, - )); - - 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::CPMM, - )); - - 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::CPMM - )); - 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::CPMM - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_collecting_subsidy() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(100_000_000..200_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::RikiddoSigmoidFeeMarketEma - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[test] -fn report_fails_on_market_state_insufficient_subsidy() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Timestamp(100_000_000..200_000_000), - get_deadlines(), - gen_metadata(2), - MarketCreation::Advised, - MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), - ScoringRule::RikiddoSigmoidFeeMarketEma - )); - 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(|| { - 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 - )); - assert_noop!( - PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, OutcomeReport::Categorical(1)), - Error::::MarketIsNotClosed, - ); - }); -} - -#[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(|| { - 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::CPMM - )); - 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::CPMM - )); - 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::CPMM, - )); - }); -} - -#[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::CPMM, - )); - }); -} - -#[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::CPMM, - ), - 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::CPMM, - ), - crate::Error::::MarketDurationTooLong, - ); - }); -} - -#[test_case( - MarketCreation::Advised, - ScoringRule::CPMM, - 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::CPMM, - 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_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(|| { - assert_noop!( - PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(1..2), - Deadlines { - grace_period: 1, - oracle_duration: ::MinOracleDuration::get() + 2, - dispute_duration: ::MinDisputeDuration::get() + 3, - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::CPMM, - ), - Error::::NonZeroDisputePeriodOnTrustedMarket - ); - }); -} - -#[test] -fn trusted_market_complete_lifecycle() { - ExtBuilder::default().build().execute_with(|| { - let end = 3; - assert_ok!(PredictionMarkets::create_market( - RuntimeOrigin::signed(ALICE), - Asset::Ztg, - Perbill::zero(), - BOB, - MarketPeriod::Block(0..end), - Deadlines { - grace_period: 0, - oracle_duration: ::MinOracleDuration::get(), - dispute_duration: Zero::zero(), - }, - gen_metadata(0x99), - MarketCreation::Permissionless, - MarketType::Categorical(3), - None, - ScoringRule::CPMM, - )); - let market_id = 0; - assert_ok!(PredictionMarkets::buy_complete_set( - RuntimeOrigin::signed(FRED), - market_id, - BASE - )); - run_to_block(end); - let outcome = OutcomeReport::Categorical(1); - assert_ok!(PredictionMarkets::report( - RuntimeOrigin::signed(BOB), - market_id, - outcome.clone() - )); - let market = MarketCommons::market(&market_id).unwrap(); - assert_eq!(market.status, MarketStatus::Resolved); - assert_eq!(market.report, Some(Report { at: end, by: BOB, outcome: outcome.clone() })); - assert_eq!(market.resolved_outcome, Some(outcome)); - assert_eq!(market.dispute_mechanism, None); - assert!(market.bonds.oracle.unwrap().is_settled); - assert_eq!(market.bonds.outsider, None); - assert_eq!(market.bonds.dispute, None); - assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(FRED), market_id)); - // Ensure that we don't accidentally leave any artifacts. - assert!(MarketIdsPerDisputeBlock::::iter().next().is_none()); - assert!(MarketIdsPerReportBlock::::iter().next().is_none()); - }); -} - -fn deploy_swap_pool( - market: Market>, - market_id: u128, -) -> 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; - simple_create_scalar_market( - base_asset, - MarketCreation::Permissionless, - 0..end, - ScoringRule::CPMM, - ); - assert_ok!(PredictionMarkets::buy_complete_set(RuntimeOrigin::signed(CHARLIE), 0, 100 * BASE)); - assert_ok!(AssetManager::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..73ac62752 --- /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 = AssetManager::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..b892bac20 --- /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 = AssetManager::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!(AssetManager::transfer( + RuntimeOrigin::signed(CHARLIE), + EVE, + Asset::ScalarOutcome(0, ScalarPosition::Short), + 50 * BASE + )); + + assert_eq!( + AssetManager::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 = AssetManager::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..77308d65a --- /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 = AssetManager::total_issuance(share_b); + assert_eq!(share_b_total, CENT); + 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); + let share_a_bal = AssetManager::free_balance(share_a, &CHARLIE); + assert_eq!(share_a_bal, CENT); + + let share_c = Asset::CategoricalOutcome(0, 2); + let share_c_total = AssetManager::total_issuance(share_c); + assert_eq!(share_c_total, 0); + let share_c_bal = AssetManager::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..d7542018a --- /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!(AssetManager::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 + ); + }); +} diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index 9eb2ab2fd..e635c471e 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_prediction_markets // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -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,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_future_pool(a: u32, o: u32) -> Weight; - fn deploy_swap_pool_for_market_open_pool(a: u32) -> Weight; fn start_global_dispute(m: u32, n: u32) -> Weight; fn dispute_authorized() -> Weight; fn handle_expired_advised_market() -> Weight; @@ -69,96 +67,78 @@ 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, 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; - 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) -> 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 close_trusted_market(c: u32) -> Weight; + fn create_market_and_deploy_pool(m: u32, n: u32) -> Weight; + fn manually_close_market(o: 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) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, 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: 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(_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())) - .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: `675 + c * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 20_831 nanoseconds. + Weight::from_parts(21_250_000, 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(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) /// 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: `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())) - .saturating_add(T::DbWeight::get().reads(4)) + fn admin_move_market_to_resolved_scalar_reported(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `721 + r * (16 ±0)` + // Estimated: `10394` + // Minimum execution time: 32_171 nanoseconds. + Weight::from_parts(32_361_000, 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(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) /// 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: `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())) - .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: `677 + r * (16 ±0)` + // Estimated: `10394` + // Minimum execution time: 31_781 nanoseconds. + Weight::from_parts(32_291_000, 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(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) @@ -169,22 +149,18 @@ 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: `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())) - .saturating_add(T::DbWeight::get().reads(8)) + fn admin_move_market_to_resolved_scalar_disputed(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `1280 + r * (16 ±0)` + // Estimated: `22120` + // Minimum execution time: 52_502 nanoseconds. + Weight::from_parts(53_751_000, 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(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) @@ -195,75 +171,64 @@ 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: `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())) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(7)) + fn admin_move_market_to_resolved_categorical_disputed(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `1221 + r * (16 ±0)` + // Estimated: `22120` + // Minimum execution time: 52_571 nanoseconds. + Weight::from_parts(52_712_000, 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(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: 21_791 nanoseconds. + Weight::from_parts(21_791_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())) + fn request_edit(_r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `519` + // Estimated: `6678` + // Minimum execution time: 12_401 nanoseconds. + Weight::from_parts(12_451_000, 6678) .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: MarketAssets Asset (r:64 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:64 w:64) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:64 w:64) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// 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())) - .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())) + fn buy_complete_set(_a: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `663` + // Estimated: `505984` + // Minimum execution time: 39_091 nanoseconds. + Weight::from_parts(438_991_000, 505984) + .saturating_add(T::DbWeight::get().reads(194)) + .saturating_add(T::DbWeight::get().writes(129)) } /// Storage: Timestamp Now (r:1 w:0) /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) @@ -274,106 +239,37 @@ 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)` + // Measured: `155 + m * (17 ±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: 23_641 nanoseconds. + Weight::from_parts(24_920_000, 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) /// 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 { - // 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())) + fn edit_market(_m: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `700 + m * (16 ±0)` + // Estimated: `10706` + // Minimum execution time: 23_060 nanoseconds. + Weight::from_parts(23_201_000, 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(542), added: 3017, 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: 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: `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())) - .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(542), added: 3017, 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 { - // 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())) - .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(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,125 +290,111 @@ 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: `9188 + m * (16 ±0)` + // Estimated: `329717` + // Minimum execution time: 139_494 nanoseconds. + Weight::from_parts(138_828_007, 329717) + // Standard Error: 24_729 + .saturating_add(Weight::from_parts(26_500, 0).saturating_mul(m.into())) + // Standard Error: 24_729 + .saturating_add(Weight::from_parts(9_992, 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: 26_070 nanoseconds. + Weight::from_parts(26_070_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: 21_700 nanoseconds. + Weight::from_parts(21_700_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) - /// 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: `4316` - // Estimated: `15390` - // Minimum execution time: 107_881 nanoseconds. - Weight::from_parts(121_701_000, 15390) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `582` + // Estimated: `6877` + // Minimum execution time: 22_630 nanoseconds. + Weight::from_parts(22_630_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: GlobalDisputes GlobalDisputesInfo (r:1 w:0) /// 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: `4571` - // Estimated: `20785` - // Minimum execution time: 154_130 nanoseconds. - Weight::from_parts(180_720_000, 20785) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `870` + // Estimated: `12272` + // Minimum execution time: 36_260 nanoseconds. + Weight::from_parts(36_260_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(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) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `626` + // Estimated: `6877` + // Minimum execution time: 21_470 nanoseconds. + Weight::from_parts(21_470_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: GlobalDisputes GlobalDisputesInfo (r:1 w:0) /// 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: `893` - // Estimated: `14659` - // Minimum execution time: 101_510 nanoseconds. - Weight::from_parts(124_291_000, 14659) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `929` + // Estimated: `12272` + // Minimum execution time: 37_031 nanoseconds. + Weight::from_parts(37_031_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) @@ -524,28 +406,16 @@ impl WeightInfoZeitgeist for WeightInfo { fn on_initialize_resolve_overhead() -> Weight { // Proof Size summary in bytes: // Measured: `79` - // Estimated: `23164` - // Minimum execution time: 33_631 nanoseconds. - Weight::from_parts(41_000_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: 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())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Estimated: `15090` + // Minimum execution time: 11_000 nanoseconds. + Weight::from_parts(11_000_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(542), added: 3017, mode: MaxEncodedLen) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:1 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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 +424,17 @@ 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) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `2171` + // Estimated: `13576` + // Minimum execution time: 41_521 nanoseconds. + Weight::from_parts(41_521_000, 13576) + .saturating_add(T::DbWeight::get().reads(5)) .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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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,17 +443,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) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `1318` + // Estimated: `21392` + // Minimum execution time: 48_761 nanoseconds. + Weight::from_parts(48_761_000, 21392) + .saturating_add(T::DbWeight::get().reads(8)) .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) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, 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: Balances Reserves (r:1 w:1) @@ -589,302 +459,303 @@ 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, 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())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(5)) + fn reject_market(_c: u32, r: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `608 + c * (16 ±0)` + // Estimated: `10402` + // Minimum execution time: 33_841 nanoseconds. + Weight::from_parts(34_426_000, 10402) + // Standard Error: 109 + .saturating_add(Weight::from_parts(688, 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(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) /// 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 { - // 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())) + fn report_market_with_dispute_mechanism(_m: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + m * (1 ±0)` + // Estimated: `7173` + // Minimum execution time: 19_451 nanoseconds. + Weight::from_parts(19_650_000, 7173) .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) - .saturating_add(T::DbWeight::get().reads(3)) + // Measured: `538` + // Estimated: `6877` + // Minimum execution time: 29_751 nanoseconds. + Weight::from_parts(29_751_000, 6877) + .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: System Account (r:1 w:1) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:64 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:64 w:64) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:64 w:64) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// 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())) - .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(542), added: 3017, 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: `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())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) + fn sell_complete_set(_a: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `895 + a * (161 ±0)` + // Estimated: `505984` + // Minimum execution time: 47_271 nanoseconds. + Weight::from_parts(674_006_000, 505984) + .saturating_add(T::DbWeight::get().reads(194)) + .saturating_add(T::DbWeight::get().writes(129)) } - /// 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(542), added: 3017, mode: MaxEncodedLen) - /// Storage: PredictionMarkets MarketIdsPerOpenTimeFrame (r:1 w:1) - /// Proof: PredictionMarkets MarketIdsPerOpenTimeFrame (max_values: None, max_size: Some(1050), added: 3525, 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) /// 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: `2416 + b * (330 ±0) + f * (334 ±0)` + // Estimated: `7050 + b * (3153 ±0) + f * (3153 ±0)` + // Minimum execution time: 78_562 nanoseconds. + Weight::from_parts(26_970_933, 7050) + // Standard Error: 41_857 + .saturating_add(Weight::from_parts(1_623_200, 0).saturating_mul(b.into())) + // Standard Error: 41_857 + .saturating_add(Weight::from_parts(1_611_866, 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: `2360 + r * (330 ±0) + d * (334 ±0)` + // Estimated: `7034 + r * (3153 ±0) + d * (3153 ±0)` + // Minimum execution time: 76_262 nanoseconds. + Weight::from_parts(26_386_933, 7034) + // Standard Error: 22_805 + .saturating_add(Weight::from_parts(1_558_200, 0).saturating_mul(r.into())) + // Standard Error: 22_805 + .saturating_add(Weight::from_parts(1_570_866, 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())) - } - /// 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: 5_050 nanoseconds. - Weight::from_parts(5_970_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)) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 3153).saturating_mul(d.into())) } /// 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)) + // Measured: `675 + o * (16 ±0)` + // Estimated: `10706` + // Minimum execution time: 21_971 nanoseconds. + Weight::from_parts(21_711_000, 10706) + // Standard Error: 3_024 + .saturating_add(Weight::from_parts(4_126, 0).saturating_mul(o.into())) + // Standard Error: 3_024 + .saturating_add(Weight::from_parts(16_984, 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) + /// 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: `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)) + // Measured: `901 + o * (16 ±0)` + // Estimated: `14430` + // Minimum execution time: 38_301 nanoseconds. + Weight::from_parts(30_845_500, 14430) + // Standard Error: 139_265 + .saturating_add(Weight::from_parts(130_404, 0).saturating_mul(o.into())) + // Standard Error: 139_265 + .saturating_add(Weight::from_parts(118_341, 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: 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 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)) + // Measured: `736 + o * (16 ±0)` + // Estimated: `14430` + // Minimum execution time: 30_801 nanoseconds. + Weight::from_parts(30_206_500, 14430) + // Standard Error: 1_800 + .saturating_add(Weight::from_parts(9_436, 0).saturating_mul(o.into())) + // Standard Error: 1_800 + .saturating_add(Weight::from_parts(11_658, 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: 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 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)) + /// 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: `852 + o * (16 ±0) + n * (16 ±0)` + // Estimated: `14430` + // Minimum execution time: 29_971 nanoseconds. + Weight::from_parts(31_721_000, 14430) + // Standard Error: 2_474 + .saturating_add(Weight::from_parts(14_126, 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: PredictionMarkets MarketIdsPerCloseTimeFrame (r:2 w:2) /// Proof: PredictionMarkets MarketIdsPerCloseTimeFrame (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - 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)) + /// 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: `742 + o * (16 ±0) + n * (16 ±0)` + // Estimated: `10706` + // Minimum execution time: 24_391 nanoseconds. + Weight::from_parts(24_525_500, 10706) + // Standard Error: 948 + .saturating_add(Weight::from_parts(8_642, 0).saturating_mul(o.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: 26_471 nanoseconds. + Weight::from_parts(26_471_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) + /// 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: `674 + c * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 21_180 nanoseconds. + Weight::from_parts(21_531_000, 7181) + .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: MarketAssets Asset (r:64 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `395 + m * (17 ±0)` + // Estimated: `160697 + n * (10414 ±0)` + // Minimum execution time: 91_033 nanoseconds. + Weight::from_parts(78_894_548, 160697) + // Standard Error: 71_935 + .saturating_add(Weight::from_parts(14_756_725, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().reads((4_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, 10414).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) + /// The range of component `o` is `[1, 63]`. + fn manually_close_market(_o: u32) -> Weight { + // Proof Size summary in bytes: + // Measured: `658 + o * (16 ±0)` + // Estimated: `7181` + // Minimum execution time: 20_381 nanoseconds. + Weight::from_parts(20_441_000, 7181) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/zrml/rikiddo/Cargo.toml b/zrml/rikiddo/Cargo.toml index 1f1d0c5e1..8859b1f7e 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", @@ -42,4 +44,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-rikiddo" -version = "0.4.1" +version = "0.4.3" 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 d50e3ca88..e85f005a0 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"] } @@ -41,4 +42,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-simple-disputes" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 859a0f0ad..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. @@ -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, + ); } } @@ -550,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 5c25f00db..e53bd66f4 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -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; } @@ -220,6 +219,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/src/tests.rs b/zrml/simple-disputes/src/tests.rs index f217b9ba7..03ee1e578 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -45,7 +45,7 @@ const DEFAULT_MARKET: MarketOf = Market { deadlines: Deadlines { grace_period: 1_u64, oracle_duration: 1_u64, dispute_duration: 1_u64 }, report: None, resolved_outcome: None, - scoring_rule: ScoringRule::CPMM, + scoring_rule: ScoringRule::Lmsr, status: MarketStatus::Disputed, bonds: MarketBonds { creation: None, diff --git a/zrml/styx/Cargo.toml b/zrml/styx/Cargo.toml index 3454d3e80..95889877d 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"] } @@ -36,4 +37,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-styx" -version = "0.4.1" +version = "0.4.3" 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/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/styx/src/weights.rs b/zrml/styx/src/weights.rs index 16629bd19..6933adeaa 100644 --- a/zrml/styx/src/weights.rs +++ b/zrml/styx/src/weights.rs @@ -19,21 +19,21 @@ //! 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: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --pallet=zrml_styx // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -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: 14_370 nanoseconds. + Weight::from_parts(14_370_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: 5_370 nanoseconds. + Weight::from_parts(5_370_000, 0).saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 03ea77f38..94c2386dd 100644 --- a/zrml/swaps/Cargo.toml +++ b/zrml/swaps/Cargo.toml @@ -6,15 +6,15 @@ orml-tokens = { 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 +env_logger = { workspace = true, optional = true } orml-currencies = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } pallet-timestamp = { workspace = true, optional = true } @@ -26,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] @@ -39,11 +40,13 @@ mock = [ "zeitgeist-primitives/mock", "zrml-market-commons/default", "zrml-swaps-runtime-api/default", + "env_logger/default", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "zeitgeist-primitives/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", @@ -53,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", @@ -66,4 +67,4 @@ try-runtime = [ authors = ["Zeitgeist PM "] edition = "2021" name = "zrml-swaps" -version = "0.4.1" +version = "0.4.3" diff --git a/zrml/swaps/fuzz/create_pool.rs b/zrml/swaps/fuzz/create_pool.rs index 0438e0b8a..932212420 100644 --- a/zrml/swaps/fuzz/create_pool.rs +++ b/zrml/swaps/fuzz/create_pool.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -19,9 +19,9 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use zeitgeist_primitives::{traits::Swaps as SwapsTrait, types::ScoringRule}; +use zeitgeist_primitives::traits::Swaps as SwapsTrait; -use zrml_swaps::mock::{ExtBuilder, Swaps, DEFAULT_MARKET_ID}; +use zrml_swaps::mock::{ExtBuilder, Swaps}; mod utils; use utils::{construct_asset, PoolCreationData}; @@ -32,9 +32,6 @@ fuzz_target!(|data: PoolCreationData| { let _ = Swaps::create_pool( data.origin, data.assets.into_iter().map(construct_asset).collect(), - construct_asset(data.base_asset), - DEFAULT_MARKET_ID, - ScoringRule::CPMM, data.swap_fee, data.amount, data.weights, diff --git a/zrml/swaps/fuzz/utils.rs b/zrml/swaps/fuzz/utils.rs index a44619f83..c3e1084f9 100644 --- a/zrml/swaps/fuzz/utils.rs +++ b/zrml/swaps/fuzz/utils.rs @@ -29,9 +29,9 @@ use zeitgeist_primitives::{ MaxAssets, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, BASE, CENT, }, traits::Swaps as SwapsTrait, - types::{Asset, PoolId, ScalarPosition, ScoringRule}, + types::{Asset, PoolId, ScalarPosition}, }; -use zrml_swaps::mock::{Swaps, DEFAULT_MARKET_ID}; +use zrml_swaps::mock::Swaps; pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { let (module, seed0, seed1) = seed; @@ -48,8 +48,8 @@ pub fn construct_asset(seed: (u8, u128, u16)) -> Asset { } } -fn construct_swap_fee(swap_fee: u128) -> Option { - Some(swap_fee % MaxSwapFee::get()) +fn construct_swap_fee(swap_fee: u128) -> u128 { + swap_fee % MaxSwapFee::get() } #[derive(Debug)] @@ -69,12 +69,9 @@ impl ValidPoolData { match Swaps::create_pool( self.origin, self.assets.into_iter().map(construct_asset).collect(), - construct_asset(self.base_asset), - DEFAULT_MARKET_ID, - ScoringRule::CPMM, construct_swap_fee(self.swap_fee), - Some(self.amount), - Some(self.weights), + self.amount, + self.weights, ) { Ok(pool_id) => pool_id, Err(e) => panic!("Pool creation failed unexpectedly. Error: {:?}", e), @@ -208,7 +205,7 @@ pub struct PoolCreationData { pub origin: u128, pub assets: Vec<(u8, u128, u16)>, pub base_asset: (u8, u128, u16), - pub swap_fee: Option, - pub amount: Option, - pub weights: Option>, + pub swap_fee: u128, + pub amount: u128, + pub weights: Vec, } diff --git a/zrml/swaps/rpc/Cargo.toml b/zrml/swaps/rpc/Cargo.toml index b290453ef..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.1" +version = "0.4.3" diff --git a/zrml/swaps/rpc/src/lib.rs b/zrml/swaps/rpc/src/lib.rs index 7548b0c2d..5591fb2f9 100644 --- a/zrml/swaps/rpc/src/lib.rs +++ b/zrml/swaps/rpc/src/lib.rs @@ -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; 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`]. @@ -224,38 +215,4 @@ where }) .collect() } - - async fn get_all_spot_prices( - &self, - pool_id: PoolId, - with_fees: bool, - blocks: Vec>, - ) -> RpcResult, Vec<(Asset, Balance)>>> { - let api = self.client.runtime_api(); - Ok(blocks - .into_iter() - .map( - |block| -> Result<(NumberFor, Vec<(Asset, Balance)>), CallError> { - let hash = BlockId::number(block); - let prices: Vec<(Asset, Balance)> = api - .get_all_spot_prices(&hash, &pool_id, with_fees) - .map_err(|e| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Unable to get_all_spot_prices: ApiError.", - Some(format!("{:?}", e)), - )) - })? - .map_err(|e| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Unable to get_all_spot_prices: DispatchError.", - Some(format!("{:?}", e)), - )) - })?; - Ok((block, prices)) - }, - ) - .collect::, _>>()?) - } } diff --git a/zrml/swaps/runtime-api/Cargo.toml b/zrml/swaps/runtime-api/Cargo.toml index 31130f1a7..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.1" +version = "0.4.3" diff --git a/zrml/swaps/runtime-api/src/lib.rs b/zrml/swaps/runtime-api/src/lib.rs index e531ff7f8..2045560a2 100644 --- a/zrml/swaps/runtime-api/src/lib.rs +++ b/zrml/swaps/runtime-api/src/lib.rs @@ -19,13 +19,9 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::{Codec, Decode, HasCompact, MaxEncodedLen}; -use sp_runtime::{ - traits::{MaybeDisplay, MaybeFromStr}, - DispatchError, -}; -use sp_std::vec::Vec; -use zeitgeist_primitives::types::{Asset, Pool}; +use parity_scale_codec::{Codec, HasCompact, MaxEncodedLen}; +use sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; +use zeitgeist_primitives::types::Asset; sp_api::decl_runtime_apis! { pub trait SwapsApi where @@ -33,7 +29,6 @@ sp_api::decl_runtime_apis! { AccountId: Codec, Balance: Codec + MaybeDisplay + MaybeFromStr + HasCompact + MaxEncodedLen, MarketId: Codec + HasCompact + MaxEncodedLen, - Pool: Decode, { fn pool_shares_id(pool_id: PoolId) -> Asset; fn pool_account_id(pool_id: &PoolId) -> AccountId; @@ -43,9 +38,5 @@ sp_api::decl_runtime_apis! { asset_out: &Asset, with_fees: bool, ) -> Balance; - 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 021890bcb..000000000 --- a/zrml/swaps/src/arbitrage.rs +++ /dev/null @@ -1,450 +0,0 @@ -// 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 . - -//! 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::{HasCompact, 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: HasCompact + 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: HasCompact + 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: HasCompact + 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: HasCompact + 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 9201ae8d3..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,741 +29,136 @@ use super::*; #[cfg(test)] use crate::Pallet as Swaps; -use crate::{fixed::bmul, 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}, - traits::{MarketCommonsPalletApi, Swaps as _}, - types::{ - Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, - MarketPeriod, MarketStatus, MarketType, OutcomeReport, PoolId, PoolStatus, ScoringRule, - }, + math::fixed::FixedMul, + 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 @@ -772,12 +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 = 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]; @@ -785,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]; @@ -807,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 @@ -839,13 +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 = 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 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]; @@ -853,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]; @@ -875,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/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 5550a4a21..900678e45 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -31,17 +31,14 @@ extern crate alloc; #[macro_use] mod utils; -mod arbitrage; mod benchmarks; -pub mod check_arithm_rslt; -mod consts; 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::*; @@ -49,10 +46,8 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { use crate::{ - arbitrage::ArbitrageForCpmm, - check_arithm_rslt::CheckArithmRslt, events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - fixed::{bdiv, bmul}, + types::{Pool, PoolStatus}, utils::{ pool_exit_with_exact_amount, pool_join_with_exact_amount, swap_exact_amount, PoolExitWithExactAmountParams, PoolJoinWithExactAmountParams, PoolParams, @@ -64,111 +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, StorageMap, StorageValue, ValueQuery}, + traits::{Get, IsType, StorageVersion}, + transactional, Blake2_128Concat, PalletError, PalletId, Parameter, }; + use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use orml_traits::{MultiCurrency, MultiReservableCurrency}; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; + use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{ - traits::AccountIdConversion, ArithmeticError, 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::{BASE, CENT}, - traits::{MarketCommonsPalletApi, Swaps}, - types::{ - Asset, MarketType, OutcomeReport, Pool, PoolId, PoolStatus, ResultWithWeightInfo, - ScoringRule, + constants::CENT, + math::{ + checked_ops_res::{CheckedAddRes, CheckedMulRes}, + fixed::FixedMul, }, - }; - use zrml_liquidity_mining::LiquidityMiningPalletApi; - use zrml_rikiddo::{ - constants::{EMA_LONG, EMA_SHORT}, - traits::RikiddoMVPallet, - types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}, + traits::{PoolSharesId, Swaps}, + types::PoolId, }; /// 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`. @@ -201,134 +129,7 @@ mod pallet { min_assets_out: Vec>, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); - let who_clone = who.clone(); - let pool = Self::pool_by_id(pool_id)?; - // If the pool is still in use, prevent a pool drain. - Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; - let pool_account_id = Pallet::::pool_account_id(&pool_id); - let params = PoolParams { - asset_bounds: min_assets_out, - event: |evt| Self::deposit_event(Event::PoolExit(evt)), - pool_account_id: &pool_account_id, - pool_amount, - pool_id, - pool: &pool, - transfer_asset: |amount, amount_bound, asset| { - Self::ensure_minimum_balance(pool_id, &pool, asset, amount)?; - ensure!(amount >= amount_bound, Error::::LimitOut); - T::LiquidityMining::remove_shares(&who, &pool.market_id, amount); - T::AssetManager::transfer(asset, &pool_account_id, &who, amount)?; - Ok(()) - }, - transfer_pool: || { - Self::burn_pool_shares(pool_id, &who, pool_amount)?; - Ok(()) - }, - fee: |amount: BalanceOf| { - let exit_fee_amount = - bmul(amount.saturated_into(), Self::calc_exit_fee(&pool).saturated_into())? - .saturated_into(); - Ok(exit_fee_amount) - }, - who: who_clone, - }; - crate::utils::pool::<_, _, _, _, T>(params) - } - - /// Pool - Remove subsidty from a pool that uses the Rikiddo scoring rule. - /// - /// Unreserves `pool_amount` of the base currency from being used as subsidy. - /// If `amount` is greater than the amount reserved for subsidy by `origin`, - /// then the whole amount reserved for subsidy will be unreserved. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be unreserved. - /// * `pool_id`: Unique pool identifier. - /// * `amount`: The amount of base currency that should be removed from subsidy. - /// - /// # Weight - /// - /// Complexity: O(1) - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::pool_exit_subsidy())] - #[transactional] - pub fn pool_exit_subsidy( - origin: OriginFor, - #[pallet::compact] pool_id: PoolId, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(amount != Zero::zero(), Error::::ZeroAmount); - - >::try_mutate(pool_id, |pool_opt| { - let pool = pool_opt.as_mut().ok_or(Error::::PoolDoesNotExist)?; - - ensure!( - pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma, - Error::::InvalidScoringRule - ); - let base_asset = pool.base_asset; - let mut real_amount = amount; - let transferred; - - if let Some(subsidy) = >::get(pool_id, &who) { - if amount > subsidy { - real_amount = subsidy; - } - // If the account would be left with less than the minimum subsidy per account, - // then withdraw all their subsidy instead. - if subsidy.saturating_sub(amount) < T::MinSubsidyPerAccount::get() { - real_amount = subsidy; - } - - let missing = T::AssetManager::unreserve(base_asset, &who, real_amount); - transferred = real_amount.saturating_sub(missing); - let zero_balance = BalanceOf::::zero(); - - if missing > zero_balance { - log::warn!( - target: LOG_TARGET, - "Data inconsistency: More subsidy provided than currently \ - reserved. - Pool: {:?}, User: {:?}, Unreserved: {:?}, Previously reserved: {:?}", - pool_id, - who, - transferred, - subsidy - ); - } - - let new_amount = subsidy.saturating_sub(transferred); - let total_subsidy = pool.total_subsidy.ok_or(Error::::PoolMissingSubsidy)?; - - if new_amount > zero_balance && missing == zero_balance { - >::insert(pool_id, &who, new_amount); - pool.total_subsidy = Some( - total_subsidy - .checked_sub(&transferred) - .ok_or(ArithmeticError::Overflow)?, - ); - } else { - >::remove(pool_id, &who); - pool.total_subsidy = Some( - total_subsidy.checked_sub(&subsidy).ok_or(ArithmeticError::Overflow)?, - ); - } - } else { - return Err(Error::::NoSubsidyProvided.into()); - } - - Self::deposit_event(Event::::PoolExitSubsidy( - base_asset, - amount, - CommonPoolEventParams { pool_id, who }, - transferred, - )); - - Ok(()) - }) + Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) } /// Pool - Exit with exact pool amount @@ -354,7 +155,7 @@ mod pallet { pub fn pool_exit_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] asset_amount: BalanceOf, #[pallet::compact] max_pool_amount: BalanceOf, ) -> DispatchResult { @@ -392,7 +193,7 @@ mod pallet { pub fn pool_exit_with_exact_pool_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] min_asset_amount: BalanceOf, ) -> DispatchResult { @@ -406,37 +207,28 @@ 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(), 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(); 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)?; - 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, @@ -444,7 +236,7 @@ mod pallet { pool_id, pool: pool_ref, }; - pool_exit_with_exact_amount::<_, _, _, _, _, T>(params) + pool_exit_with_exact_amount::<_, _, _, _, T>(params) } /// Pool - Join @@ -468,7 +260,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( @@ -480,10 +272,7 @@ mod pallet { let who = ensure_signed(origin)?; ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); let pool = Self::pool_by_id(pool_id)?; - ensure!( - matches!(pool.pool_status, PoolStatus::Initialized | PoolStatus::Active), - Error::::InvalidPoolStatus, - ); + ensure!(pool.status == PoolStatus::Open, Error::::InvalidPoolStatus); let pool_account_id = Pallet::::pool_account_id(&pool_id); let params = PoolParams { @@ -496,7 +285,6 @@ mod pallet { transfer_asset: |amount, amount_bound, asset| { ensure!(amount <= amount_bound, Error::::LimitIn); T::AssetManager::transfer(asset, &who, &pool_account_id, amount)?; - T::LiquidityMining::add_shares(who.clone(), pool.market_id, amount); Ok(()) }, transfer_pool: || Self::mint_pool_shares(pool_id, &who, pool_amount), @@ -508,72 +296,6 @@ mod pallet { Ok(Some(T::WeightInfo::pool_join(pool.assets.len().saturated_into())).into()) } - /// Pool - Add subsidy to a pool that uses the Rikiddo scoring rule. - /// - /// Reserves `pool_amount` of the base currency to be added as subsidy on pool activation. - /// - /// # Arguments - /// - /// * `origin`: Liquidity Provider (LP). The account whose assets should be reserved. - /// * `pool_id`: Unique pool identifier. - /// * `amount`: The amount of base currency that should be added to subsidy. - /// - /// # Weight - /// - /// Complexity: O(1) - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::pool_join_subsidy())] - #[transactional] - pub fn pool_join_subsidy( - origin: OriginFor, - #[pallet::compact] pool_id: PoolId, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(amount != Zero::zero(), Error::::ZeroAmount); - - >::try_mutate(pool_id, |pool_opt| { - let pool = pool_opt.as_mut().ok_or(Error::::PoolDoesNotExist)?; - - ensure!( - pool.scoring_rule == ScoringRule::RikiddoSigmoidFeeMarketEma, - Error::::InvalidScoringRule - ); - let base_asset = pool.base_asset; - T::AssetManager::reserve(base_asset, &who, amount)?; - - let total_subsidy = pool.total_subsidy.ok_or(Error::::PoolMissingSubsidy)?; - >::try_mutate::<_, _, _, DispatchError, _>( - pool_id, - &who, - |user_subsidy| { - if let Some(prev_val) = user_subsidy { - *prev_val = prev_val.saturating_add(amount); - } else { - // If the account adds subsidy for the first time, ensure that it's - // larger than the minimum amount. - ensure!( - amount >= T::MinSubsidyPerAccount::get(), - Error::::InvalidSubsidyAmount - ); - *user_subsidy = Some(amount); - } - - pool.total_subsidy = Some(total_subsidy.saturating_add(amount)); - Ok(()) - }, - )?; - - Self::deposit_event(Event::PoolJoinSubsidy( - base_asset, - amount, - CommonPoolEventParams { pool_id, who }, - )); - - Ok(()) - }) - } - /// Pool - Join with exact asset amount /// /// Joins an asset provided from `origin` to `pool_id`. Differently from `pool_join`, @@ -597,7 +319,7 @@ mod pallet { pub fn pool_join_with_exact_asset_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, #[pallet::compact] asset_amount: BalanceOf, #[pallet::compact] min_pool_amount: BalanceOf, ) -> DispatchResult { @@ -635,7 +357,7 @@ mod pallet { pub fn pool_join_with_exact_pool_amount( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset: Asset>, + asset: AssetOf, #[pallet::compact] pool_amount: BalanceOf, #[pallet::compact] max_asset_amount: BalanceOf, ) -> DispatchResult { @@ -646,32 +368,26 @@ 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(), 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); 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); 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), @@ -679,7 +395,7 @@ mod pallet { pool: &pool, who: who_clone, }; - pool_join_with_exact_amount::<_, _, _, _, T>(params) + pool_join_with_exact_amount::<_, _, _, T>(params) } /// Swap - Exact amount in @@ -700,16 +416,15 @@ mod pallet { /// /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of /// assets if the scoring rule is Rikiddo. - // TODO(#790): Replace with maximum of CPMM and Rikiddo benchmark! #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::swap_exact_amount_in_cpmm())] #[transactional] pub fn swap_exact_amount_in( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, #[pallet::compact] asset_amount_in: BalanceOf, - asset_out: Asset>, + asset_out: AssetOf, min_asset_amount_out: Option>, max_price: Option>, ) -> DispatchResultWithPostInfo { @@ -722,7 +437,6 @@ mod pallet { asset_out, min_asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } @@ -745,16 +459,15 @@ mod pallet { /// /// Complexity: `O(1)` if the scoring rule is CPMM, `O(n)` where `n` is the amount of /// assets if the scoring rule is Rikiddo. - // TODO(#790): Replace with maximum of CPMM and Rikiddo benchmark! #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::swap_exact_amount_out_cpmm())] #[transactional] pub fn swap_exact_amount_out( origin: OriginFor, #[pallet::compact] pool_id: PoolId, - asset_in: Asset>, + asset_in: AssetOf, max_asset_amount_in: Option>, - asset_out: Asset>, + asset_out: AssetOf, #[pallet::compact] asset_amount_out: BalanceOf, max_price: Option>, ) -> DispatchResultWithPostInfo { @@ -767,59 +480,50 @@ mod pallet { asset_out, asset_amount_out, max_price, - false, )?; Ok(Some(weight).into()) } + + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::pool_exit( + min_assets_out.len().min(T::MaxAssets::get().into()) as u32 + ))] + #[transactional] + pub fn force_pool_exit( + origin: OriginFor, + who: T::AccountId, + #[pallet::compact] pool_id: PoolId, + #[pallet::compact] pool_amount: BalanceOf, + min_assets_out: Vec>, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Self::do_pool_exit(who, pool_id, pool_amount, min_assets_out) + } } #[pallet::config] pub trait Config: frame_system::Config { + /// Shares of outcome assets and native currency + type AssetManager: MultiReservableCurrency; + + 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; @@ -842,44 +546,12 @@ mod pallet { /// The minimum amount of assets in a pool. type MinAssets: Get; - /// The minimum amount of subsidy required to state transit a market into active state. - /// Must be greater than 0, but can be arbitrarily close to 0. - #[pallet::constant] - type MinSubsidy: Get>; - - /// The minimum amount of subsidy that each subsidy provider must contribute. - #[pallet::constant] - type MinSubsidyPerAccount: Get>; - #[pallet::constant] type MinWeight: Get; /// The module identifier. #[pallet::constant] type PalletId: Get; - - /// The Rikiddo instance that uses a sigmoid fee and ema of market volume - type RikiddoSigmoidFeeMarketEma: RikiddoMVPallet< - Balance = BalanceOf, - PoolId = PoolId, - FU = Self::FixedTypeU, - Rikiddo = RikiddoSigmoidMV< - Self::FixedTypeU, - Self::FixedTypeS, - FeeSigmoid, - EmaMarketVolume, - >, - >; - - /// Shares of outcome assets and native currency - type AssetManager: MultiReservableCurrency< - Self::AccountId, - // Balance = BalanceOf, - CurrencyId = Asset>, - >; - - /// The weight information for swap's dispatchable functions. - type WeightInfo: WeightInfoZeitgeist; } #[pallet::error] @@ -900,17 +572,12 @@ mod pallet { InsufficientBalance, /// Liquidity provided to new CPMM pool is less than the minimum allowed balance. InsufficientLiquidity, - /// The market was not started since the subsidy goal was not reached. - InsufficientSubsidy, /// Could not create CPMM pool since no amount was specified. InvalidAmountArgument, /// Could not create CPMM pool since no fee was supplied. InvalidFeeArgument, /// Dispatch called on pool with invalid status. InvalidPoolStatus, - /// A function that is only valid for pools with specific scoring rules was called for a - /// pool with another scoring rule. - InvalidScoringRule, /// A function was called for a swaps pool that does not fulfill the state requirement. InvalidStateTransition, /// Could not create CPMM pool since no weights were supplied. @@ -935,9 +602,6 @@ mod pallet { /// The total weight of all assets within a CPMM pool is above a treshhold specified /// by a constant. MaxTotalWeight, - /// It was tried to remove subsidy from a pool which does not have subsidy provided by - /// the address that tried to remove the subsidy. - NoSubsidyProvided, /// The pool in question does not exist. PoolDoesNotExist, /// A pool balance dropped below the allowed minimum. @@ -970,6 +634,20 @@ mod pallet { WinningAssetNotFound, /// Some amount in a transaction equals zero. ZeroAmount, + /// An unexpected error occurred. This is the result of faulty pallet logic and should be + /// reported to the pallet maintainers. + Unexpected(UnexpectedError), + } + + #[derive(Decode, Encode, Eq, PartialEq, PalletError, RuntimeDebug, TypeInfo)] + pub enum UnexpectedError { + StorageOverflow, + } + + impl From for Error { + fn from(error: UnexpectedError) -> Error { + Error::::Unexpected(error) + } } #[pallet::event] @@ -978,18 +656,12 @@ mod pallet { where T: Config, { - /// Buy-burn arbitrage was executed on a CPMM pool. \[pool_id, amount\] - ArbitrageBuyBurn(PoolId, BalanceOf), - /// Mint-sell arbitrage was executed on a CPMM pool. \[pool_id, amount\] - ArbitrageMintSell(PoolId, BalanceOf), - /// Arbitrage was skipped on a CPMM pool. \[pool_id\] - ArbitrageSkipped(PoolId), /// Share holder rewards were distributed. \[pool_id, num_accounts_rewarded, amount\] DistributeShareHolderRewards(PoolId, u64, BalanceOf), /// A new pool has been created. \[CommonPoolEventParams, pool, pool_amount, pool_account\] PoolCreate( CommonPoolEventParams<::AccountId>, - Pool, MarketIdOf>, + PoolOf, BalanceOf, T::AccountId, ), @@ -1000,71 +672,24 @@ mod pallet { /// A pool was opened. \[pool_id\] PoolActive(PoolId), /// Someone has exited a pool. \[PoolAssetsEvent\] - PoolExit( - PoolAssetsEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Someone has (partially) exited a pool by removing subsidy. \[asset, bound, pool_id, who, amount\] - PoolExitSubsidy( - Asset>, - BalanceOf, - CommonPoolEventParams<::AccountId>, - BalanceOf, - ), + PoolExit(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), /// Exits a pool given an exact amount of an asset. \[PoolAssetEvent\] PoolExitWithExactAssetAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Exits a pool given an exact pool's amount. \[PoolAssetEvent\] PoolExitWithExactPoolAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Someone has joined a pool. \[PoolAssetsEvent\] - PoolJoin( - PoolAssetsEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Someone has joined a pool by providing subsidy. \[asset, amount, pool_id, who\] - PoolJoinSubsidy( - Asset>, - BalanceOf, - CommonPoolEventParams<::AccountId>, - ), + PoolJoin(PoolAssetsEvent<::AccountId, AssetOf, BalanceOf>), /// Joins a pool given an exact amount of an asset. \[PoolAssetEvent\] PoolJoinWithExactAssetAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Joins a pool given an exact pool's amount. \[PoolAssetEvent\] PoolJoinWithExactPoolAmount( - PoolAssetEvent< - ::AccountId, - Asset>, - BalanceOf, - >, - ), - /// Total subsidy collected for a pool. \[pool_id, \[(provider, subsidy), ...\], total_subsidy\] - SubsidyCollected( - PoolId, - Vec<(::AccountId, BalanceOf)>, - BalanceOf, + PoolAssetEvent<::AccountId, AssetOf, BalanceOf>, ), /// Pool was manually destroyed. \[pool_id\] PoolDestroyed(PoolId), @@ -1075,29 +700,11 @@ mod pallet { ), /// An exact amount of an asset is entering the pool. \[SwapEvent\] SwapExactAmountIn( - SwapEvent<::AccountId, Asset>, BalanceOf>, + SwapEvent<::AccountId, AssetOf, BalanceOf>, ), /// An exact amount of an asset is leaving the pool. \[SwapEvent\] SwapExactAmountOut( - SwapEvent<::AccountId, Asset>, BalanceOf>, - ), - /// Fees were paid to the market creator. \[market_id , payer, payee, amount, asset\] - MarketCreatorFeesPaid( - MarketIdOf, - ::AccountId, - ::AccountId, - BalanceOf, - Asset>, - ), - /// Fee payment to market creator failed (usually due to existential deposit requirements) - /// \[market_id, payer, payee, amount, asset, error\] - MarketCreatorFeePaymentFailed( - MarketIdOf, - ::AccountId, - ::AccountId, - BalanceOf, - Asset>, - DispatchError, + SwapEvent<::AccountId, AssetOf, BalanceOf>, ), } @@ -1108,449 +715,81 @@ mod pallet { #[pallet::storage] #[pallet::getter(fn pools)] - pub type Pools = StorageMap< - _, - Blake2_128Concat, - PoolId, - Option, MarketIdOf>>, - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn pools_cached_for_arbitrage)] - pub type PoolsCachedForArbitrage = StorageMap<_, Twox64Concat, PoolId, ()>; - - #[pallet::storage] - #[pallet::getter(fn subsidy_providers)] - pub type SubsidyProviders = - StorageDoubleMap<_, Twox64Concat, PoolId, Twox64Concat, T::AccountId, BalanceOf>; + pub(crate) type Pools = + StorageMap<_, Blake2_128Concat, PoolId, PoolOf, OptionQuery>; #[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. - // TODO(#1199): Remove Rikiddo. Since Rikiddo is not used in production, - // No share holders can be assumed here for now. - /* - let (total_accounts_num, share_accounts) = - T::AssetManager::accounts_by_currency_id(shares_id).unwrap_or((0usize, vec![])); - */ - let (total_accounts_num, share_accounts): ( - usize, - Vec<(T::AccountId, orml_tokens::AccountData>)>, - ) = (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 = - bdiv(share_holder_balance.saturated_into(), total_pool_shares.saturated_into()) - .unwrap_or(0); - - // 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); - - // Same for bmul. - let holder_reward = holder_reward_unadjusted.saturating_sub(1); - - let transfer_result = T::AssetManager::transfer( - base_asset, - &pool_account, - &share_holder_account, - holder_reward.saturated_into(), - ); - - // Should be impossible. - if let Err(err) = transfer_result { - let current_amm_holding = - T::AssetManager::free_balance(base_asset, &pool_account); - log::error!( - target: LOG_TARGET, - "The AMM failed to pay out the share holder reward. - market_id: {:?}, pool_id: {:?}, current AMM holding: {:?}, - transfer size: {:?}, to: {:?}, error: {:?}", - pool.market_id, - pool_id, - current_amm_holding, - holder_reward, - share_holder_account, - err, - ); - - if current_amm_holding < holder_reward.saturated_into() { - let _ = T::AssetManager::transfer( - base_asset, - &pool_account, - &share_holder_account, - current_amm_holding.saturated_into(), - ); - } - } - - // We can use the lightweight withdraw here, since pool shares are not reserved. - // We can ignore the result since the balance to transfer is the query result of - // the free balance (always sufficient). - let _ = T::AssetManager::withdraw( - shares_id, - &share_holder_account, - share_holder_balance, - ); - } - - let remaining_pool_funds = T::AssetManager::free_balance(base_asset, &pool_account); - // Transfer winner payout - Infallible since the balance was just read from storage. - let _ = T::AssetManager::transfer( - base_asset, - &pool_account, - winner_payout_account, - remaining_pool_funds, - ); - - Self::deposit_event(Event::::DistributeShareHolderRewards( - pool_id, - share_accounts_num.saturated_into(), - amm_profit, - )); - - T::WeightInfo::distribute_pool_share_rewards( - total_accounts_num.saturated_into(), - share_accounts_num.saturated_into(), - ) - } - - /// Execute arbitrage on as many cached pools until `weight` is spent. - /// - /// Arguments: - /// - /// * `weight`: The maximum amount of weight allowed to spend by this function. - fn execute_arbitrage_all(weight: Weight) -> Weight { - // The time complexity of `apply_cached_pools` is `O(pool_count)`; we calculate the - // minimum number of pools we can handle. - let overhead = T::WeightInfo::apply_to_cached_pools_execute_arbitrage(0); - let extra_weight_per_pool = - T::WeightInfo::apply_to_cached_pools_execute_arbitrage(1).saturating_sub(overhead); - // The division can fail if the benchmark of `apply_to_cached_pools` is not linear in - // the number of pools. This shouldn't ever happen, but if it does, we ensure that - // `pool_count` is zero (this isn't really a runtime error). - let weight_minus_overhead = weight.saturating_sub(overhead); - let max_pool_count_by_ref_time = weight_minus_overhead - .ref_time() - .checked_div(extra_weight_per_pool.ref_time()) - .unwrap_or_else(|| { - debug_assert!( - false, - "Unexpected zero division when calculating arbitrage ref time" - ); - 0_u64 - }); - let max_pool_count_by_proof_size = weight_minus_overhead - .proof_size() - .checked_div(extra_weight_per_pool.proof_size()) - .unwrap_or_else(|| { - debug_assert!( - false, - "Unexpected zero division when calculating arbitrage proof size" - ); - 0_u64 - }); - let max_pool_count = max_pool_count_by_ref_time.min(max_pool_count_by_proof_size); - if max_pool_count == 0_u64 { - return weight; - } - Self::apply_to_cached_pools( - max_pool_count.saturated_into(), - |pool_id| Self::execute_arbitrage(pool_id, ARBITRAGE_MAX_ITERATIONS), - extra_weight_per_pool, - ) - } - - /// Apply a `mutation` to all pools cached for arbitrage (but at most `pool_count` many) - /// and return the actual weight consumed. - /// - /// Arguments: - /// - /// * `pool_count`: The maximum number of pools to apply the `mutation` to. - /// * `mutation`: The `mutation` that is applied to the pools. - /// * `max_weight_per_pool`: The maximum weight consumed by `mutation` per pool. - pub(crate) fn apply_to_cached_pools( - pool_count: u32, - mutation: F, - max_weight_per_pool: Weight, - ) -> Weight - where - F: Fn(PoolId) -> Result, - { - let mut total_weight = T::WeightInfo::apply_to_cached_pools_noop(pool_count); - for (pool_id, _) in PoolsCachedForArbitrage::::drain().take(pool_count as usize) { - // The mutation should never fail, but if it does, we just assume we - // consumed all the weight and rollback the pool. - let _ = with_transaction(|| match mutation(pool_id) { - Err(err) => { - log::warn!( - target: LOG_TARGET, - "Arbitrage unexpectedly failed on pool {:?} with error: {:?}", - pool_id, - err, - ); - total_weight = total_weight.saturating_add(max_weight_per_pool); - TransactionOutcome::Rollback(err.into()) - } - Ok(weight) => { - total_weight = total_weight.saturating_add(weight); - TransactionOutcome::Commit(Ok(())) - } - }); - } - total_weight - } - - /// Execute arbitrage on a single pool. - /// - /// Arguments: - /// - /// * `pool_id`: The id of the pool to arbitrage. - /// * `max_iterations`: The maximum number of iterations allowed in the bisection method. - pub(crate) fn execute_arbitrage( + fn do_pool_exit( + who: T::AccountId, pool_id: PoolId, - max_iterations: usize, - ) -> Result { + pool_amount: BalanceOf, + min_assets_out: Vec>, + ) -> DispatchResult { + ensure!(pool_amount != Zero::zero(), Error::::ZeroAmount); + let who_clone = who.clone(); let pool = Self::pool_by_id(pool_id)?; - let pool_account = Self::pool_account_id(&pool_id); - let balances = pool - .assets - .iter() - .map(|a| (*a, T::AssetManager::free_balance(*a, &pool_account))) - .collect::>(); - let asset_count = pool.assets.len() as u32; - let outcome_tokens = pool.assets.iter().filter(|a| **a != pool.base_asset); - let total_spot_price = pool.calc_total_spot_price(&balances)?; - - if total_spot_price > BASE.saturating_add(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 < BASE.saturating_sub(ARBITRAGE_THRESHOLD) { - let (amount, _) = pool.calc_arbitrage_amount_buy_burn(&balances, max_iterations)?; - // Ensure that executing arbitrage doesn't decrease the outcome asset balances below - // the minimum allowed balance. - let (smallest_outcome, smallest_outcome_balance) = balances - .iter() - .filter(|(k, _)| **k != pool.base_asset) - .min_by(|(_, v1), (_, v2)| v1.cmp(v2)) - .ok_or(Error::::AssetNotInPool)?; - let max_amount = - smallest_outcome_balance.saturating_sub(Self::min_balance(*smallest_outcome)); - let amount = amount.min(max_amount); - T::AssetManager::transfer( - pool.base_asset, - &T::MarketCommons::market_account(pool.market_id), - &pool_account, - amount, - )?; - for t in outcome_tokens { - T::AssetManager::withdraw(*t, &pool_account, amount)?; - } - Self::deposit_event(Event::ArbitrageBuyBurn(pool_id, amount)); - Ok(T::WeightInfo::execute_arbitrage_buy_burn(asset_count)) - } else { - Self::deposit_event(Event::ArbitrageSkipped(pool_id)); - Ok(T::WeightInfo::execute_arbitrage_skipped(asset_count)) - } + // If the pool is still in use, prevent a pool drain. + Self::ensure_minimum_liquidity_shares(pool_id, &pool, pool_amount)?; + let pool_account_id = Pallet::::pool_account_id(&pool_id); + let params = PoolParams { + asset_bounds: min_assets_out, + event: |evt| Self::deposit_event(Event::PoolExit(evt)), + pool_account_id: &pool_account_id, + pool_amount, + pool_id, + pool: &pool, + transfer_asset: |amount, amount_bound, asset| { + Self::ensure_minimum_balance(pool_id, &pool, asset, amount)?; + ensure!(amount >= amount_bound, Error::::LimitOut); + T::AssetManager::transfer(asset, &pool_account_id, &who, amount)?; + Ok(()) + }, + transfer_pool: || { + Self::burn_pool_shares(pool_id, &who, pool_amount)?; + Ok(()) + }, + fee: |amount: BalanceOf| { + let exit_fee_amount = amount.bmul(Self::calc_exit_fee(&pool))?; + Ok(exit_fee_amount) + }, + who: who_clone, + }; + crate::utils::pool::<_, _, _, _, T>(params) } pub fn get_spot_price( pool_id: &PoolId, - asset_in: &Asset>, - asset_out: &Asset>, + asset_in: &AssetOf, + asset_out: &AssetOf, with_fees: bool, ) -> Result, DispatchError> { let pool = Self::pool_by_id(*pool_id)?; ensure!(pool.assets.binary_search(asset_in).is_ok(), Error::::AssetNotInPool); ensure!(pool.assets.binary_search(asset_out).is_ok(), Error::::AssetNotInPool); let pool_account = Self::pool_account_id(pool_id); + let balance_in = T::AssetManager::free_balance(*asset_in, &pool_account); + let balance_out = T::AssetManager::free_balance(*asset_out, &pool_account); + let in_weight = Self::pool_weight_rslt(&pool, asset_in)?; + let out_weight = Self::pool_weight_rslt(&pool, asset_out)?; - if pool.scoring_rule == ScoringRule::CPMM { - let balance_in = T::AssetManager::free_balance(*asset_in, &pool_account); - let balance_out = T::AssetManager::free_balance(*asset_out, &pool_account); - let in_weight = Self::pool_weight_rslt(&pool, asset_in)?; - let out_weight = Self::pool_weight_rslt(&pool, asset_out)?; - - let swap_fee = if with_fees { - let swap_fee = pool.swap_fee.ok_or(Error::::SwapFeeMissing)?; - let market = T::MarketCommons::market(&pool.market_id)?; - market - .creator_fee - .mul_floor(BASE) - .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 Ok(T::RikiddoSigmoidFeeMarketEma::fee(*pool_id)? - .saturating_add(BASE.saturated_into())); - } - - 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 = 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 swap_fee = if with_fees { + pool.swap_fee.saturated_into() } 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()) - } - } + 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] @@ -1559,7 +798,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()) } @@ -1572,10 +811,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)) @@ -1586,10 +822,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); @@ -1602,12 +838,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); @@ -1617,69 +853,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, @@ -1689,13 +862,20 @@ 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(()) } #[inline] pub(crate) fn check_provided_values_len_must_equal_assets_len( - assets: &[Asset>], + assets: &[AssetOf], provided_values: &[U], ) -> Result<(), Error> where @@ -1707,11 +887,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()), } } @@ -1725,13 +903,11 @@ mod pallet { T::AssetManager::deposit(shares_id, to, amount) } - pub(crate) fn pool_shares_id(pool_id: PoolId) -> Asset> { - Asset::PoolShare(pool_id) + pub(crate) fn pool_shares_id(pool_id: PoolId) -> AssetOf { + T::Asset::pool_shares_id(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, { @@ -1741,7 +917,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) @@ -1750,7 +926,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 { @@ -1762,81 +938,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(), } } @@ -1846,8 +957,8 @@ mod pallet { where T: Config, { + type Asset = AssetOf; type Balance = BalanceOf; - type MarketId = MarketIdOf; /// Creates an initial active pool. /// @@ -1858,7 +969,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 @@ -1867,24 +977,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 @@ -1893,116 +997,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(BASE) - .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 <= BASE, 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.check_add_rslt(&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, )); @@ -2011,17 +1047,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)); @@ -2034,7 +1063,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. @@ -2043,195 +1080,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) { - T::AssetManager::unreserve(base_asset, &provider.0, provider.1); - 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 { - T::AssetManager::unreserve(base_asset, &provider_address, subsidy); - 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)?; @@ -2257,7 +1109,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 { @@ -2270,15 +1122,9 @@ 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 - <= bmul( - asset_balance.saturated_into(), - T::MaxOutRatio::get().saturated_into() - )? - .saturated_into(), + asset_amount <= asset_balance.bmul(T::MaxOutRatio::get())?, Error::::MaxOutRatio ); Ok(()) @@ -2288,19 +1134,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)), @@ -2309,7 +1151,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 @@ -2329,7 +1171,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 { @@ -2343,28 +1185,19 @@ 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 = 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(), 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)), @@ -2374,53 +1207,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 @@ -2441,12 +1228,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(), @@ -2455,108 +1241,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 - <= bmul( - balance_in.saturated_into(), - T::MaxInRatio::get().saturated_into() - )? - .saturated_into(), - 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(&cost_after).ok_or(ArithmeticError::Overflow)? - } - 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)?; @@ -2566,7 +1277,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, @@ -2574,33 +1284,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 @@ -2621,119 +1307,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 - <= bmul( - balance_out.saturated_into(), - T::MaxOutRatio::get().saturated_into() - )? - .saturated_into(), - 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(&cost_before).ok_or(ArithmeticError::Overflow)? - } - 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]) @@ -2741,7 +1346,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, @@ -2749,34 +1353,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/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/migrations.rs b/zrml/swaps/src/migrations.rs index b9f09b652..8853c4110 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. diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index f3ec41881..235706cc6 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -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, Assets, Balance, BasicCurrencyAdapter, BlockNumber, - BlockTest, Deadlines, Hash, Index, Market, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, PoolId, - ScoringRule, UncheckedExtrinsicTest, + BlockTest, Hash, Index, MarketId, Moment, PoolId, 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; @@ -283,17 +236,14 @@ 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(); - let mut ext = sp_io::TestExternalities::from(storage); - - ext.execute_with(|| { - MarketCommons::push_market(mock_market(4)).unwrap(); - }); - - ext + sp_io::TestExternalities::from(storage) } } @@ -317,35 +267,5 @@ sp_api::mock_impl_runtime_apis! { fn pool_shares_id(pool_id: PoolId) -> Asset { Asset::PoolShare(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 ba00ca8b6..c12fbd0c1 100644 --- a/zrml/swaps/src/tests.rs +++ b/zrml/swaps/src/tests.rs @@ -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,217 +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(()) })); @@ -684,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( @@ -698,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)] @@ -810,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); @@ -837,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())); @@ -977,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)); @@ -1011,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)); @@ -1045,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; @@ -1085,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; @@ -1109,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( @@ -1149,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)), @@ -1161,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 @@ -1200,7 +754,7 @@ fn pool_exit_decreases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); @@ -1234,7 +788,7 @@ fn pool_exit_decreases_correct_pool_parameters() { fn pool_exit_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_exit( RuntimeOrigin::signed(BOB), DEFAULT_POOL_ID, @@ -1259,7 +813,7 @@ fn pool_exit_emits_correct_events() { fn pool_exit_decreases_correct_pool_parameters_with_exit_fee() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_exit( RuntimeOrigin::signed(BOB), @@ -1295,41 +849,39 @@ fn pool_exit_decreases_correct_pool_parameters_with_exit_fee() { } #[test] -fn pool_exit_decreases_correct_pool_parameters_on_cleaned_up_pool() { - // Test is the same as +fn force_pool_exit_decreases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { + ::ExitFee::set(&0u128); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_ok!(MarketCommons::insert_market_pool(DEFAULT_MARKET_ID, DEFAULT_POOL_ID)); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - assert_ok!(Swaps::admin_clean_up_pool( - RuntimeOrigin::root(), + + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, DEFAULT_POOL_ID, - OutcomeReport::Categorical(65), + _1, + vec!(_1, _1, _1, _1), )); - assert_ok!(Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1),)); System::assert_last_event( Event::PoolExit(PoolAssetsEvent { - assets: vec![ASSET_A, ASSET_D], - bounds: vec![_1, _1], + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![_1, _1, _1, _1], cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: 0 }, - transferred: vec![_1 + 1, _1 + 1], + transferred: vec![_1 + 1, _1 + 1, _1 + 1, _1 + 1], pool_amount: _1, }) .into(), ); assert_all_parameters( - [_25 + 1, _24, _24, _25 + 1], + [_25 + 1, _25 + 1, _25 + 1, _25 + 1], 0, - // Note: Although the asset is deleted from the pool, the assets B/C still remain on the - // pool account. [ DEFAULT_LIQUIDITY - 1, - DEFAULT_LIQUIDITY + _1, - DEFAULT_LIQUIDITY + _1, + DEFAULT_LIQUIDITY - 1, + DEFAULT_LIQUIDITY - 1, DEFAULT_LIQUIDITY - 1, ], DEFAULT_LIQUIDITY, @@ -1338,123 +890,69 @@ fn pool_exit_decreases_correct_pool_parameters_on_cleaned_up_pool() { } #[test] -fn pool_exit_subsidy_unreserves_correct_values() { +fn force_pool_exit_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { - // Events cannot be emitted on block zero... frame_system::Pallet::::set_block_number(1); - - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - - // Add some subsidy - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - let mut reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let mut noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - let mut total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, _25); - assert_eq!(reserved, noted); - assert_eq!(reserved, total_subsidy); - - // Exit 5 subsidy and see if the storage is consistent - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _5)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, noted); - assert_eq!(reserved, total_subsidy); - System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - _5, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - _5, - ) - .into(), - ); - - // Exit the remaining subsidy (in fact, we attempt to exit with more than remaining!) and - // see if the storage is consistent - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - assert!(>::get(DEFAULT_POOL_ID, ALICE).is_none()); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert_eq!(reserved, total_subsidy); + create_initial_pool_with_funds_for_alice(0, true); + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _1, + vec!(1, 2, 3, 4), + )); + let amount = _1 - BASE / 10; // Subtract 10% fees! System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - _25, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - _20, - ) + Event::PoolExit(PoolAssetsEvent { + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![1, 2, 3, 4], + cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: BOB }, + transferred: vec![amount; 4], + pool_amount: _1, + }) .into(), ); - - // Add some subsidy, manually remove some reserved balance (create inconsistency) - // and check if the internal values are adjusted to the inconsistency. - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _25)); - assert_eq!(Currencies::unreserve(ASSET_D, &ALICE, _20), 0); - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _20)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - assert!(>::get(DEFAULT_POOL_ID, ALICE).is_none()); - total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert_eq!(reserved, total_subsidy); }); } #[test] -fn pool_exit_subsidy_fails_if_no_subsidy_is_provided() { +fn force_pool_exit_decreases_correct_pool_parameters_with_exit_fee() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::NoSubsidyProvided - ); - }); -} + frame_system::Pallet::::set_block_number(1); + create_initial_pool_with_funds_for_alice(0, true); -#[test] -fn pool_exit_subsidy_fails_if_amount_is_zero() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, 0), - Error::::ZeroAmount - ); - }); -} + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _10, + vec!(_1, _1, _1, _1), + )); -#[test] -fn pool_exit_subsidy_fails_if_pool_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::PoolDoesNotExist - ); - }); -} + let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); + let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); + assert_eq!(Currencies::free_balance(ASSET_A, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_B, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_C, &BOB), _9); + assert_eq!(Currencies::free_balance(ASSET_D, &BOB), _9); + assert_eq!(Currencies::free_balance(pool_shares_id, &BOB), DEFAULT_LIQUIDITY - _10); + assert_eq!(Currencies::free_balance(ASSET_A, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_B, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_C, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::free_balance(ASSET_D, &pool_account), DEFAULT_LIQUIDITY - _9); + assert_eq!(Currencies::total_issuance(pool_shares_id), DEFAULT_LIQUIDITY - _10); -#[test] -fn pool_exit_subsidy_fails_if_scoring_rule_is_not_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::InvalidScoringRule + System::assert_last_event( + Event::PoolExit(PoolAssetsEvent { + assets: vec![ASSET_A, ASSET_B, ASSET_C, ASSET_D], + bounds: vec![_1, _1, _1, _1], + cpep: CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: BOB }, + transferred: vec![_9, _9, _9, _9], + pool_amount: _10, + }) + .into(), ); - }); + }) } #[test_case(49_999_999_665, 12_272_234_300, 0, 0; "no_fees")] @@ -1472,7 +970,7 @@ fn pool_exit_with_exact_pool_amount_exchanges_correct_values( let asset_amount_joined = _5; ::ExitFee::set(&exit_fee); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -1532,7 +1030,7 @@ fn pool_exit_with_exact_asset_amount_exchanges_correct_values( let asset_amount_joined = _5; ::ExitFee::set(&exit_fee); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -1584,7 +1082,7 @@ fn pool_exit_with_exact_asset_amount_exchanges_correct_values( fn pool_exit_is_not_allowed_with_insufficient_funds() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Alice has no pool shares! assert_noop!( @@ -1601,11 +1099,44 @@ fn pool_exit_is_not_allowed_with_insufficient_funds() { }) } +#[test] +fn force_pool_exit_is_not_allowed_with_insufficient_funds() { + ExtBuilder::default().build().execute_with(|| { + frame_system::Pallet::::set_block_number(1); + create_initial_pool_with_funds_for_alice(0, true); + + // Alice has no pool shares! + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _1, + vec!(0, 0, 0, 0) + ), + Error::::InsufficientBalance, + ); + + // Now Alice has 25 pool shares! + let _ = Currencies::deposit(Swaps::pool_shares_id(DEFAULT_POOL_ID), &ALICE, _25); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _26, + vec!(0, 0, 0, 0) + ), + Error::::InsufficientBalance, + ); + }) +} + #[test] fn pool_join_increases_correct_pool_parameters() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _5, vec!(_25, _25, _25, _25),) @@ -1628,7 +1159,7 @@ fn pool_join_increases_correct_pool_parameters() { fn pool_join_emits_correct_events() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1),)); System::assert_last_event( Event::PoolJoin(PoolAssetsEvent { @@ -1643,159 +1174,6 @@ fn pool_join_emits_correct_events() { }); } -#[test] -fn pool_join_subsidy_reserves_correct_values() { - ExtBuilder::default().build().execute_with(|| { - // Events cannot be emitted on block zero... - frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _20)); - let mut reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let mut noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - assert_eq!(reserved, _20); - assert_eq!(reserved, noted); - assert_eq!(reserved, Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap()); - System::assert_last_event( - Event::PoolJoinSubsidy( - ASSET_D, - _20, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - ) - .into(), - ); - - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _5)); - reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - assert_eq!(reserved, _25); - assert_eq!(reserved, noted); - assert_eq!(reserved, Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap()); - assert_storage_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _5).unwrap_or(()) - ); - System::assert_last_event( - Event::PoolJoinSubsidy( - ASSET_D, - _5, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - ) - .into(), - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_amount_is_zero() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, 0), - Error::::ZeroAmount - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_pool_does_not_exist() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::PoolDoesNotExist - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_scoring_rule_is_not_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); - assert_noop!( - Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, _1), - Error::::InvalidScoringRule - ); - }); -} - -#[test] -fn pool_join_subsidy_fails_if_subsidy_is_below_min_per_account() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - assert_noop!( - Swaps::pool_join_subsidy( - alice_signed(), - 0, - ::MinSubsidyPerAccount::get() - 1 - ), - Error::::InvalidSubsidyAmount, - ); - }); -} - -#[test] -fn pool_join_subsidy_with_small_amount_is_ok_if_account_is_already_a_provider() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let large_amount = ::MinSubsidyPerAccount::get(); - let small_amount = 1; - let total_amount = large_amount + small_amount; - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, large_amount)); - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, small_amount)); - let reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let noted = >::get(DEFAULT_POOL_ID, ALICE).unwrap(); - let total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, total_amount); - assert_eq!(noted, total_amount); - assert_eq!(total_subsidy, total_amount); - }); -} - -#[test] -fn pool_exit_subsidy_unreserves_remaining_subsidy_if_below_min_per_account() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice( - ScoringRule::RikiddoSigmoidFeeMarketEma, - None, - false, - ); - let large_amount = ::MinSubsidyPerAccount::get(); - let small_amount = 1; - assert_ok!(Swaps::pool_join_subsidy(alice_signed(), DEFAULT_POOL_ID, large_amount)); - assert_ok!(Swaps::pool_exit_subsidy(alice_signed(), DEFAULT_POOL_ID, small_amount)); - let reserved = Currencies::reserved_balance(ASSET_D, &ALICE); - let noted = >::get(DEFAULT_POOL_ID, ALICE); - let total_subsidy = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().total_subsidy.unwrap(); - assert_eq!(reserved, 0); - assert!(noted.is_none()); - assert_eq!(total_subsidy, 0); - System::assert_last_event( - Event::PoolExitSubsidy( - ASSET_D, - small_amount, - CommonPoolEventParams { pool_id: DEFAULT_POOL_ID, who: ALICE }, - large_amount, - ) - .into(), - ); - }); -} - #[test_case(_1, 2_490_679_300, 0; "without_swap_fee")] #[test_case(_1, 2_304_521_500, _1_10; "with_swap_fee")] fn pool_join_with_exact_asset_amount_exchanges_correct_values( @@ -1805,7 +1183,7 @@ fn pool_join_with_exact_asset_amount_exchanges_correct_values( ) { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); let bound = 0; let alice_sent = _1; assert_ok!(Swaps::pool_join_with_exact_asset_amount( @@ -1848,7 +1226,7 @@ fn pool_join_with_exact_pool_amount_exchanges_correct_values( ) { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool_with_funds_for_alice(swap_fee, true); let bound = _5; assert_ok!(Swaps::pool_join_with_exact_pool_amount( alice_signed(), @@ -1884,7 +1262,7 @@ fn pool_join_with_exact_pool_amount_exchanges_correct_values( #[test] fn provided_values_len_must_equal_assets_len() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_noop!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _5, vec![]), Error::::ProvidedValuesLenMustEqualAssetsLen @@ -1893,91 +1271,15 @@ fn provided_values_len_must_equal_assets_len() { Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _5, vec![]), Error::::ProvidedValuesLenMustEqualAssetsLen ); - }); -} - -#[test] -fn clean_up_pool_leaves_only_correct_assets() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - assert_ok!(Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - )); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Clean); - assert_eq!(Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap().assets, vec![ASSET_A, ASSET_D]); - System::assert_last_event(Event::PoolCleanedUp(DEFAULT_POOL_ID).into()); - }); -} - -#[test] -fn clean_up_pool_handles_rikiddo_pools_properly() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - - // We need to forcefully close the pool (Rikiddo pools are not allowed to be cleaned - // up when CollectingSubsidy). - assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = PoolStatus::Closed; - Ok(()) - })); - - assert_ok!(Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - )); - - // Rikiddo instance does not exist anymore. - assert_storage_noop!(RikiddoSigmoidFeeMarketEma::clear(DEFAULT_POOL_ID).unwrap_or(())); - }); -} - -#[test_case(PoolStatus::Active; "active")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(PoolStatus::Initialized; "initialized")] -fn clean_up_pool_fails_if_pool_is_not_closed(pool_status: PoolStatus) { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, false); - assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; - Ok(()) - })); - let cat_idx = if let Asset::CategoricalOutcome(_, cidx) = ASSET_A { cidx } else { 0 }; - assert_noop!( - Swaps::clean_up_pool( - &MarketType::Categorical(4), - DEFAULT_POOL_ID, - &OutcomeReport::Categorical(cat_idx), - &Default::default() - ), - Error::::InvalidStateTransition - ); - }); -} - -#[test] -fn clean_up_pool_fails_if_winning_asset_is_not_found() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); - assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); assert_noop!( - Swaps::clean_up_pool( - &MarketType::Categorical(1337), + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, DEFAULT_POOL_ID, - &OutcomeReport::Categorical(1337), - &Default::default() + _5, + vec![] ), - Error::::WinningAssetNotFound + Error::::ProvidedValuesLenMustEqualAssetsLen ); }); } @@ -1988,7 +1290,7 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm() { let asset_bound = Some(_1 / 2); let max_price = Some(_2); frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::swap_exact_amount_in( alice_signed(), DEFAULT_POOL_ID, @@ -2035,12 +1337,9 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm_with_fees() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(BASE / 10), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + BASE / 10, + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -2089,7 +1388,7 @@ fn swap_exact_amount_in_exchanges_correct_values_with_cpmm_with_fees() { fn swap_exact_amount_in_fails_if_no_limit_is_specified() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); assert_noop!( Swaps::swap_exact_amount_in( alice_signed(), @@ -2108,7 +1407,7 @@ fn swap_exact_amount_in_fails_if_no_limit_is_specified() { #[test] fn swap_exact_amount_in_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected amount to receive from trading BASE of A for B. See // swap_exact_amount_in_exchanges_correct_values_with_cpmm for details. let expected_amount = 9900990100; @@ -2130,7 +1429,7 @@ fn swap_exact_amount_in_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm #[test] fn swap_exact_amount_in_fails_if_max_price_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // We're swapping 1:1, but due to slippage the price will exceed _1, so this should raise an // error: assert_noop!( @@ -2148,73 +1447,13 @@ fn swap_exact_amount_in_fails_if_max_price_is_not_satisfied_with_cpmm() { }); } -#[test] -fn swap_exact_amount_in_exchanges_correct_values_with_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, true); - - // Generate funds, add subsidy and start pool. - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, _1); - assert_ok!(Currencies::deposit(ASSET_A, &ALICE, _1)); - - // Check if unsupport trades are catched (base_asset in || asset_in == asset_out). - assert_noop!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - _1, - ASSET_B, - Some(_1 / 2), - Some(_2), - ), - Error::::UnsupportedTrade - ); - assert_noop!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - _1, - ASSET_D, - Some(_1 / 2), - Some(_2), - ), - Error::::UnsupportedTrade - ); - assert_ok!(Currencies::withdraw(ASSET_D, &ALICE, _1)); - - // Check if the trade is executed. - let asset_a_issuance = Currencies::total_issuance(ASSET_A); - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_A, - _1, - ASSET_D, - Some(0), - Some(_20), - )); - - // Check if the balances were updated accordingly. - let asset_a_issuance_after = Currencies::total_issuance(ASSET_A); - let alice_balance_a_after = Currencies::total_balance(ASSET_A, &ALICE); - let alice_balance_d_after = Currencies::total_balance(ASSET_D, &ALICE); - assert_eq!(asset_a_issuance - asset_a_issuance_after, _1); - assert_eq!(alice_balance_a_after, 0); - - // Received base_currency greater than 0.3 and smaller than 0.4 - assert!(alice_balance_d_after > 3 * BASE / 10 && alice_balance_d_after < 4 * BASE / 10); - }); -} - #[test] fn swap_exact_amount_out_exchanges_correct_values_with_cpmm() { let asset_bound = Some(_2); let max_price = Some(_3); ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::swap_exact_amount_out( alice_signed(), DEFAULT_POOL_ID, @@ -2261,12 +1500,9 @@ fn swap_exact_amount_out_exchanges_correct_values_with_cpmm_with_fees() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(BASE / 10), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + BASE / 10, + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); @@ -2313,7 +1549,7 @@ fn swap_exact_amount_out_exchanges_correct_values_with_cpmm_with_fees() { fn swap_exact_amount_out_fails_if_no_limit_is_specified() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); assert_noop!( Swaps::swap_exact_amount_out( alice_signed(), @@ -2332,7 +1568,7 @@ fn swap_exact_amount_out_fails_if_no_limit_is_specified() { #[test] fn swap_exact_amount_out_fails_if_min_asset_amount_out_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected amount of A to swap in for receiving BASE of B. See // swap_exact_amount_out_exchanges_correct_values_with_cpmm for details! let expected_amount = 10101010100; @@ -2354,7 +1590,7 @@ fn swap_exact_amount_out_fails_if_min_asset_amount_out_is_not_satisfied_with_cpm #[test] fn swap_exact_amount_out_fails_if_max_price_is_not_satisfied_with_cpmm() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // We're swapping 1:1, but due to slippage the price will exceed 1, so this should raise an // error: assert_noop!( @@ -2372,65 +1608,6 @@ fn swap_exact_amount_out_fails_if_max_price_is_not_satisfied_with_cpmm() { }); } -#[test] -fn swap_exact_amount_out_exchanges_correct_values_with_rikiddo() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::RikiddoSigmoidFeeMarketEma, None, true); - - // Generate funds, add subsidy and start pool. - subsidize_and_start_rikiddo_pool(DEFAULT_POOL_ID, &ALICE, (BASE * 4) / 10); - - // Check if unsupport trades are catched (base_asset out || asset_in == asset_out). - assert_noop!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_B, - Some(_20), - ASSET_D, - _1, - Some(_20), - ), - Error::::UnsupportedTrade - ); - assert_noop!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - Some(_2), - ASSET_D, - _1, - Some(_2), - ), - Error::::UnsupportedTrade - ); - - // Check if the trade is executed. - let asset_a_issuance = Currencies::total_issuance(ASSET_A); - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - ASSET_D, - Some(_1), - ASSET_A, - _1, - Some(_20), - )); - - // Check if the balances were updated accordingly. - let asset_a_issuance_after = Currencies::total_issuance(ASSET_A); - let alice_balance_a_after = Currencies::total_balance(ASSET_A, &ALICE); - let alice_balance_d_after = Currencies::total_balance(ASSET_D, &ALICE); - assert_eq!(asset_a_issuance_after - asset_a_issuance, _1); - assert_eq!(alice_balance_a_after, _1); - - // Left over base currency must be less than 0.1 - assert!(alice_balance_d_after < BASE / 10); - }); -} - #[test] fn create_pool_fails_on_too_many_assets() { ExtBuilder::default().build().execute_with(|| { @@ -2444,16 +1621,7 @@ fn create_pool_fails_on_too_many_assets() { }); assert_noop!( - Swaps::create_pool( - BOB, - assets.clone(), - *assets.last().unwrap(), - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(weights), - ), + Swaps::create_pool(BOB, assets.clone(), 0, DEFAULT_LIQUIDITY, weights,), Error::::TooManyAssets ); }); @@ -2466,37 +1634,15 @@ fn create_pool_fails_on_too_few_assets() { Swaps::create_pool( BOB, vec!(ASSET_A), - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + DEFAULT_LIQUIDITY, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::TooFewAssets ); }); } -#[test] -fn create_pool_fails_if_base_asset_is_not_in_asset_vector() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Swaps::create_pool( - BOB, - vec!(ASSET_A, ASSET_B, ASSET_C), - ASSET_D, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), - ), - Error::::BaseAssetNotFound - ); - }); -} - #[test] fn create_pool_fails_if_swap_fee_is_too_high() { ExtBuilder::default().build().execute_with(|| { @@ -2508,12 +1654,9 @@ fn create_pool_fails_if_swap_fee_is_too_high() { Swaps::create_pool( BOB, ASSETS.to_vec(), - ASSET_D, - 0, - ScoringRule::CPMM, - Some(::MaxSwapFee::get() + 1), - Some(amount), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + ::MaxSwapFee::get() + 1, + amount, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::SwapFeeTooHigh ); @@ -2521,32 +1664,46 @@ fn create_pool_fails_if_swap_fee_is_too_high() { } #[test] -fn create_pool_fails_if_swap_fee_is_unspecified_for_cpmm() { +fn join_pool_exit_pool_does_not_create_extra_tokens() { ExtBuilder::default().build().execute_with(|| { - let amount = _100; + create_initial_pool_with_funds_for_alice(0, true); + ASSETS.iter().cloned().for_each(|asset| { - let _ = Currencies::deposit(asset, &BOB, amount); + let _ = Currencies::deposit(asset, &CHARLIE, _100); }); - assert_noop!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - ASSET_D, - 0, - ScoringRule::CPMM, - None, - Some(amount), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), - ), - Error::::InvalidFeeArgument - ); + + let amount = 123_456_789_123; // Strange number to force rounding errors! + assert_ok!(Swaps::pool_join( + RuntimeOrigin::signed(CHARLIE), + DEFAULT_POOL_ID, + amount, + vec![_10000, _10000, _10000, _10000] + )); + assert_ok!(Swaps::pool_exit( + RuntimeOrigin::signed(CHARLIE), + DEFAULT_POOL_ID, + amount, + vec![0, 0, 0, 0] + )); + + // Check that the pool retains more tokens than before, and that Charlie loses some tokens + // due to fees. + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + assert_ge!(Currencies::free_balance(ASSET_A, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_B, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_C, &pool_account_id), _100); + assert_ge!(Currencies::free_balance(ASSET_D, &pool_account_id), _100); + assert_le!(Currencies::free_balance(ASSET_A, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_B, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_C, &CHARLIE), _100); + assert_le!(Currencies::free_balance(ASSET_D, &CHARLIE), _100); }); } #[test] -fn join_pool_exit_pool_does_not_create_extra_tokens() { +fn join_pool_force_exit_pool_does_not_create_extra_tokens() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); ASSETS.iter().cloned().for_each(|asset| { let _ = Currencies::deposit(asset, &CHARLIE, _100); @@ -2559,8 +1716,9 @@ fn join_pool_exit_pool_does_not_create_extra_tokens() { amount, vec![_10000, _10000, _10000, _10000] )); - assert_ok!(Swaps::pool_exit( - RuntimeOrigin::signed(CHARLIE), + assert_ok!(Swaps::force_pool_exit( + RuntimeOrigin::signed(DAVE), + CHARLIE, DEFAULT_POOL_ID, amount, vec![0, 0, 0, 0] @@ -2590,17 +1748,14 @@ fn create_pool_fails_on_weight_below_minimum_weight() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!( + DEFAULT_LIQUIDITY, + vec!( DEFAULT_WEIGHT, ::MinWeight::get() - 1, DEFAULT_WEIGHT, DEFAULT_WEIGHT - )), + ), ), Error::::BelowMinimumWeight, ); @@ -2617,17 +1772,14 @@ fn create_pool_fails_on_weight_above_maximum_weight() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!( + DEFAULT_LIQUIDITY, + vec!( DEFAULT_WEIGHT, ::MaxWeight::get() + 1, DEFAULT_WEIGHT, DEFAULT_WEIGHT - )), + ), ), Error::::AboveMaximumWeight, ); @@ -2642,16 +1794,7 @@ fn create_pool_fails_on_total_weight_above_maximum_total_weight() { }); let weight = ::MaxTotalWeight::get() / 4 + 100; assert_noop!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec![weight; 4]), - ), + Swaps::create_pool(BOB, ASSETS.to_vec(), 0, DEFAULT_LIQUIDITY, vec![weight; 4],), Error::::MaxTotalWeight, ); }); @@ -2668,12 +1811,9 @@ fn create_pool_fails_on_insufficient_liquidity() { Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(min_balance - 1), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + min_balance - 1, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), ), Error::::InsufficientLiquidity, ); @@ -2692,12 +1832,9 @@ fn create_pool_succeeds_on_min_liquidity() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(min_balance), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + min_balance, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); assert_all_parameters( [0; 4], @@ -2719,12 +1856,9 @@ fn create_pool_transfers_the_correct_amount_of_tokens() { assert_ok!(Swaps::create_pool( BOB, ASSETS.to_vec(), - BASE_ASSET, 0, - ScoringRule::CPMM, - Some(0), - Some(_1234), - Some(vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT)), + _1234, + vec!(DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT), )); let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); @@ -2749,14 +1883,12 @@ fn close_pool_fails_if_pool_does_not_exist() { }); } -#[test_case(PoolStatus::Closed; "closed")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -fn close_pool_fails_if_pool_is_not_active_or_initialized(pool_status: PoolStatus) { +#[test_case(PoolStatus::Closed)] +fn close_pool_fails_if_pool_is_not_active_or_initialized(status: PoolStatus) { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; + pool.status = status; Ok(()) })); assert_noop!(Swaps::close_pool(0), Error::::InvalidStateTransition); @@ -2767,10 +1899,10 @@ fn close_pool_fails_if_pool_is_not_active_or_initialized(pool_status: PoolStatus fn close_pool_succeeds_and_emits_correct_event_if_pool_exists() { ExtBuilder::default().build().execute_with(|| { frame_system::Pallet::::set_block_number(1); - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Swaps::close_pool(DEFAULT_POOL_ID)); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Closed); + let pool = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap(); + assert_eq!(pool.status, PoolStatus::Closed); System::assert_last_event(Event::PoolClosed(DEFAULT_POOL_ID).into()); }); } @@ -2782,15 +1914,12 @@ fn open_pool_fails_if_pool_does_not_exist() { }); } -#[test_case(PoolStatus::Active; "active")] -#[test_case(PoolStatus::Clean; "clean")] -#[test_case(PoolStatus::CollectingSubsidy; "collecting_subsidy")] -#[test_case(PoolStatus::Closed; "closed")] -fn open_pool_fails_if_pool_is_not_closed(pool_status: PoolStatus) { +#[test_case(PoolStatus::Open)] +fn open_pool_fails_if_pool_is_not_closed(status: PoolStatus) { ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(1), true); + create_initial_pool(1, true); assert_ok!(Swaps::mutate_pool(DEFAULT_POOL_ID, |pool| { - pool.pool_status = pool_status; + pool.status = status; Ok(()) })); assert_noop!(Swaps::open_pool(DEFAULT_POOL_ID), Error::::InvalidStateTransition); @@ -2808,16 +1937,13 @@ fn open_pool_succeeds_and_emits_correct_event_if_pool_exists() { assert_ok!(Swaps::create_pool( BOB, vec![ASSET_D, ASSET_B, ASSET_C, ASSET_A], - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(amount), - Some(vec!(_1, _2, _3, _4)), + amount, + vec!(_1, _2, _3, _4), )); assert_ok!(Swaps::open_pool(DEFAULT_POOL_ID)); - let pool = Swaps::pool(DEFAULT_POOL_ID).unwrap(); - assert_eq!(pool.pool_status, PoolStatus::Active); + let pool = Swaps::pool_by_id(DEFAULT_POOL_ID).unwrap(); + assert_eq!(pool.status, PoolStatus::Open); System::assert_last_event(Event::PoolActive(DEFAULT_POOL_ID).into()); }); } @@ -2825,7 +1951,7 @@ fn open_pool_succeeds_and_emits_correct_event_if_pool_exists() { #[test] fn pool_join_fails_if_max_assets_in_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_noop!( Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1 - 1, _1)), Error::::LimitIn, @@ -2836,7 +1962,7 @@ fn pool_join_fails_if_max_assets_in_is_violated() { #[test] fn pool_join_with_exact_asset_amount_fails_if_min_pool_tokens_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected pool amount when joining with exactly BASE A. let expected_pool_amount = 2490679300; assert_noop!( @@ -2855,7 +1981,7 @@ fn pool_join_with_exact_asset_amount_fails_if_min_pool_tokens_is_violated() { #[test] fn pool_join_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); // Expected asset amount required to joining for BASE pool share. let expected_asset_amount = 40604010000; assert_noop!( @@ -2874,12 +2000,22 @@ fn pool_join_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { #[test] fn pool_exit_fails_if_min_assets_out_is_violated() { ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1, _1))); assert_noop!( Swaps::pool_exit(alice_signed(), DEFAULT_POOL_ID, _1, vec!(_1, _1, _1 + 1, _1)), Error::::LimitOut, ); + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + ALICE, + DEFAULT_POOL_ID, + _1, + vec!(_1, _1, _1 + 1, _1) + ), + Error::::LimitOut, + ); }); } @@ -2887,7 +2023,7 @@ fn pool_exit_fails_if_min_assets_out_is_violated() { fn pool_exit_with_exact_asset_amount_fails_if_min_pool_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&(BASE / 10)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); assert_ok!(Swaps::pool_join_with_exact_asset_amount( alice_signed(), DEFAULT_POOL_ID, @@ -2914,7 +2050,7 @@ fn pool_exit_with_exact_asset_amount_fails_if_min_pool_amount_is_violated() { fn pool_exit_with_exact_pool_amount_fails_if_max_asset_amount_is_violated() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&(BASE / 10)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(0), true); + create_initial_pool_with_funds_for_alice(0, true); let asset_before_join = Currencies::free_balance(ASSET_A, &ALICE); assert_ok!(Swaps::pool_join_with_exact_pool_amount( alice_signed(), @@ -2948,19 +2084,15 @@ fn create_pool_correctly_associates_weights_with_assets() { assert_ok!(Swaps::create_pool( BOB, vec![ASSET_D, ASSET_B, ASSET_C, ASSET_A], - ASSET_A, 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec!(_1, _2, _3, _4)), + DEFAULT_LIQUIDITY, + vec!(_1, _2, _3, _4), )); - let pool = Swaps::pool(0).unwrap(); - let pool_weights = pool.weights.unwrap(); - assert_eq!(pool_weights[&ASSET_A], _4); - assert_eq!(pool_weights[&ASSET_B], _2); - assert_eq!(pool_weights[&ASSET_C], _3); - assert_eq!(pool_weights[&ASSET_D], _1); + let pool = Swaps::pool_by_id(0).unwrap(); + assert_eq!(pool.weights[&ASSET_A], _4); + assert_eq!(pool.weights[&ASSET_B], _2); + assert_eq!(pool.weights[&ASSET_C], _3); + assert_eq!(pool.weights[&ASSET_D], _1); }); } @@ -2972,7 +2104,7 @@ fn single_asset_join_and_exit_are_inverse() { ::ExitFee::set(&0); let asset = ASSET_B; let amount_in = _1; - create_initial_pool(ScoringRule::CPMM, Some(0), true); + create_initial_pool(0, true); assert_ok!(Currencies::deposit(asset, &ALICE, amount_in)); assert_ok!(Swaps::pool_join_with_exact_asset_amount( RuntimeOrigin::signed(ALICE), @@ -3008,7 +2140,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { let amount_out_single_asset_ops = ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0); - create_initial_pool(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool(swap_fee, true); assert_ok!(Currencies::deposit(asset_in, &ALICE, amount_in)); assert_ok!(Swaps::pool_join_with_exact_asset_amount( RuntimeOrigin::signed(ALICE), @@ -3029,7 +2161,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { }); let amount_out_swap = ExtBuilder::default().build().execute_with(|| { - create_initial_pool(ScoringRule::CPMM, Some(swap_fee), true); + create_initial_pool(swap_fee, true); assert_ok!(Currencies::deposit(asset_in, &ALICE, amount_in)); assert_ok!(Swaps::swap_exact_amount_in( RuntimeOrigin::signed(ALICE), @@ -3051,7 +2183,7 @@ fn single_asset_operations_are_equivalent_to_swaps() { fn pool_join_with_uneven_balances() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _50)); assert_ok!(Swaps::pool_join( @@ -3073,7 +2205,7 @@ fn pool_exit_fails_if_balances_drop_too_low() { // We drop the balances below `Swaps::min_balance(...)`, but liquidity remains above // `Swaps::min_balance(...)`. ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::withdraw( @@ -3111,7 +2243,7 @@ fn pool_exit_fails_if_liquidity_drops_too_low() { // We drop the liquidity below `Swaps::min_balance(...)`, but balances remains above // `Swaps::min_balance(...)`. ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's 1000 left of each asset. @@ -3133,11 +2265,84 @@ fn pool_exit_fails_if_liquidity_drops_too_low() { }); } +#[test] +fn force_pool_exit_fails_if_balances_drop_too_low() { + ExtBuilder::default().build().execute_with(|| { + // We drop the balances below `Swaps::min_balance(...)`, but liquidity remains above + // `Swaps::min_balance(...)`. + ::ExitFee::set(&0u128); + create_initial_pool_with_funds_for_alice(1, true); + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + + assert_ok!(Currencies::withdraw( + ASSET_A, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_A) + )); + assert_ok!(Currencies::withdraw( + ASSET_B, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_B) + )); + assert_ok!(Currencies::withdraw( + ASSET_C, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_C) + )); + assert_ok!(Currencies::withdraw( + ASSET_D, + &pool_account_id, + _100 - Swaps::min_balance(ASSET_D) + )); + + // We withdraw 99% of it, leaving 0.01 of each asset, which is below minimum balance. + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _10, + vec![0; 4] + ), + Error::::PoolDrain, + ); + }); +} + +#[test] +fn force_pool_exit_fails_if_liquidity_drops_too_low() { + ExtBuilder::default().build().execute_with(|| { + // We drop the liquidity below `Swaps::min_balance(...)`, but balances remains above + // `Swaps::min_balance(...)`. + ::ExitFee::set(&0u128); + create_initial_pool_with_funds_for_alice(1, true); + let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); + + // There's 1000 left of each asset. + assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_B, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_C, &pool_account_id, _900)); + assert_ok!(Currencies::deposit(ASSET_D, &pool_account_id, _900)); + + // We withdraw too much liquidity but leave enough of each asset. + assert_noop!( + Swaps::force_pool_exit( + RuntimeOrigin::signed(CHARLIE), + BOB, + DEFAULT_POOL_ID, + _100 - Swaps::min_balance(Swaps::pool_shares_id(DEFAULT_POOL_ID)) + 1, + vec![0; 4] + ), + Error::::PoolDrain, + ); + }); +} + #[test] fn swap_exact_amount_in_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3181,7 +2386,7 @@ fn swap_exact_amount_in_fails_if_balances_drop_too_low() { fn swap_exact_amount_out_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3225,7 +2430,7 @@ fn swap_exact_amount_out_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_pool_amount_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3267,7 +2472,7 @@ fn pool_exit_with_exact_pool_amount_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_pool_amount_fails_if_liquidity_drops_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); assert_ok!(Currencies::deposit(ASSET_A, &pool_account_id, _10000)); @@ -3298,7 +2503,7 @@ fn pool_exit_with_exact_pool_amount_fails_if_liquidity_drops_too_low() { fn pool_exit_with_exact_asset_amount_fails_if_balances_drop_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); // There's only very little left of all assets! @@ -3341,7 +2546,7 @@ fn pool_exit_with_exact_asset_amount_fails_if_balances_drop_too_low() { fn pool_exit_with_exact_asset_amount_fails_if_liquidity_drops_too_low() { ExtBuilder::default().build().execute_with(|| { ::ExitFee::set(&0u128); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); + create_initial_pool_with_funds_for_alice(1, true); // Reduce amount of liquidity so that doing the withdraw doesn't cause a `Min*Ratio` error! let pool_shares_id = Swaps::pool_shares_id(DEFAULT_POOL_ID); @@ -3361,995 +2566,48 @@ fn pool_exit_with_exact_asset_amount_fails_if_liquidity_drops_too_low() { }); } -#[test] -fn trading_functions_cache_pool_ids() { - ExtBuilder::default().build().execute_with(|| { - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(1), true); - - assert_ok!(Swaps::pool_join_with_exact_pool_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _2, - u128::MAX, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_join_with_exact_asset_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _2, - 0, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_exit_with_exact_asset_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - u128::MAX, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::pool_exit_with_exact_pool_amount( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - 0, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::swap_exact_amount_in( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - _1, - ASSET_B, - Some(0), - None, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - PoolsCachedForArbitrage::::remove(DEFAULT_POOL_ID); - - assert_ok!(Swaps::swap_exact_amount_out( - RuntimeOrigin::signed(ALICE), - DEFAULT_POOL_ID, - ASSET_A, - Some(u128::MAX), - ASSET_B, - _1, - None, - )); - assert!(PoolsCachedForArbitrage::::contains_key(DEFAULT_POOL_ID)); - }); +fn alice_signed() -> RuntimeOrigin { + RuntimeOrigin::signed(ALICE) } -#[test] -fn on_idle_skips_arbitrage_if_price_does_not_exceed_threshold() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); +fn create_initial_pool(swap_fee: BalanceOf, deposit: bool) { + if deposit { + ASSETS.iter().cloned().for_each(|asset| { + assert_ok!(Currencies::deposit(asset, &BOB, _100)); }); - // Outcome weights sum to the weight of the base asset, and we create no imbalances, so - // total spot price is equal to 1. - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - ASSET_A, - 0, - ScoringRule::CPMM, - Some(0), - Some(DEFAULT_LIQUIDITY), - Some(vec![_3, _1, _1, _1]), - )); - // Force the pool into the cache. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - System::assert_has_event(Event::ArbitrageSkipped(DEFAULT_POOL_ID).into()); - }); + } + let pool_id = Swaps::next_pool_id(); + assert_ok!(Swaps::create_pool( + BOB, + ASSETS.to_vec(), + swap_fee, + DEFAULT_LIQUIDITY, + vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT], + )); + assert_ok!(Swaps::open_pool(pool_id)); } -#[test] -fn on_idle_arbitrages_pools_with_mint_sell() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); +fn create_initial_pool_with_funds_for_alice(swap_fee: BalanceOf, deposit: bool) { + create_initial_pool(swap_fee, deposit); + let _ = Currencies::deposit(ASSET_A, &ALICE, _25); + let _ = Currencies::deposit(ASSET_B, &ALICE, _25); + let _ = Currencies::deposit(ASSET_C, &ALICE, _25); + let _ = Currencies::deposit(ASSET_D, &ALICE, _25); +} - // Withdraw a certain amount of outcome tokens to push the total spot price above 1 - // (ASSET_A is the base asset, all other assets are considered outcomes). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = _25; - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, amount_removed)); +fn assert_all_parameters( + alice_assets: [u128; 4], + alice_pool_assets: u128, + pool_assets: [u128; 4], + total_issuance: u128, +) { + let pai = Swaps::pool_account_id(&DEFAULT_POOL_ID); + let psi = Swaps::pool_shares_id(DEFAULT_POOL_ID); - // Force arbitrage hook. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - - let arbitrage_amount = 49_537_658_690; - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance - arbitrage_amount, - ); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance + arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance + arbitrage_amount); - assert_eq!( - Currencies::free_balance(ASSET_B, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), arbitrage_amount); - System::assert_has_event( - Event::ArbitrageMintSell(DEFAULT_POOL_ID, arbitrage_amount).into(), - ); - }); -} - -#[test] -fn on_idle_arbitrages_pools_with_buy_burn() { - ExtBuilder::default().build().execute_with(|| { - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw a certain amount of base tokens to push the total spot price below 1 (ASSET_A - // is the base asset, all other assets are considered outcomes). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = _25; - assert_ok!(Currencies::withdraw(base_asset, &pool_account_id, amount_removed)); - - // Deposit funds into the prize pool to ensure that the transfers don't fail. - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - let arbitrage_amount = 125_007_629_394; // "Should" be 125_000_000_000. - assert_ok!(Currencies::deposit( - base_asset, - &market_account_id, - arbitrage_amount + SENTINEL_AMOUNT, - )); - - // Force arbitrage hook. - crate::PoolsCachedForArbitrage::::insert(DEFAULT_POOL_ID, ()); - Swaps::on_idle(System::block_number(), Weight::MAX); - - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - assert_eq!(Currencies::free_balance(ASSET_B, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), SENTINEL_AMOUNT); - System::assert_has_event(Event::ArbitrageBuyBurn(DEFAULT_POOL_ID, arbitrage_amount).into()); - }); -} - -#[test] -fn apply_to_cached_pools_only_drains_requested_pools() { - ExtBuilder::default().build().execute_with(|| { - let pool_count = 5; - for pool_id in 0..pool_count { - // Force the pool into the cache. - PoolsCachedForArbitrage::::insert(pool_id, ()); - } - let number_of_pools_to_retain: u32 = 3; - Swaps::apply_to_cached_pools( - pool_count.saturated_into::() - number_of_pools_to_retain, - |_| Ok(Weight::zero()), - Weight::MAX, - ); - assert_eq!( - PoolsCachedForArbitrage::::iter().count(), - number_of_pools_to_retain as usize, - ); - }); -} - -#[test] -fn execute_arbitrage_correctly_observes_min_balance_buy_burn() { - ExtBuilder::default().build().execute_with(|| { - // Static requirement for this test is that base asset and outcome tokens have similar - // minimum balance. - assert!(Swaps::min_balance(ASSET_A) <= 3 * Swaps::min_balance(ASSET_B) / 2); - let min_balance = Swaps::min_balance(ASSET_A); - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw most base tokens to push the total spot price below 1 and most of one of the - // outcome tokens to trigger an arbitrage that would cause minimum balances to be violated. - // The total spot price should be slightly above 1/3. - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let amount_removed = balance - 3 * min_balance / 2; - assert_ok!(Currencies::withdraw(base_asset, &pool_account_id, amount_removed)); - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, amount_removed)); - - // Deposit funds into the prize pool to ensure that the transfers don't fail. - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - let arbitrage_amount = min_balance / 2; - assert_ok!(Currencies::deposit( - base_asset, - &market_account_id, - arbitrage_amount + SENTINEL_AMOUNT, - )); - - assert_ok!(Swaps::execute_arbitrage(DEFAULT_POOL_ID, ARBITRAGE_MAX_ITERATIONS)); - - assert_eq!( - Currencies::free_balance(base_asset, &pool_account_id), - balance + arbitrage_amount - amount_removed, - ); - assert_eq!(Currencies::free_balance(ASSET_B, &pool_account_id), min_balance); - assert_eq!(Currencies::free_balance(ASSET_C, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(ASSET_D, &pool_account_id), balance - arbitrage_amount); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), SENTINEL_AMOUNT); - System::assert_has_event(Event::ArbitrageBuyBurn(DEFAULT_POOL_ID, arbitrage_amount).into()); - }); -} - -#[test] -fn execute_arbitrage_observes_min_balances_mint_sell() { - ExtBuilder::default().build().execute_with(|| { - // Static assumptions for this test are that minimum balances of all assets are similar. - assert!(Swaps::min_balance(ASSET_A) >= Swaps::min_balance(ASSET_B)); - assert_eq!(Swaps::min_balance(ASSET_B), Swaps::min_balance(ASSET_C)); - assert_eq!(Swaps::min_balance(ASSET_B), Swaps::min_balance(ASSET_D)); - let min_balance = Swaps::min_balance(ASSET_A); - frame_system::Pallet::::set_block_number(1); - let assets = ASSETS; - assets.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - let balance = _100; - let base_asset = ASSET_A; - assert_ok!(Swaps::create_pool( - BOB, - assets.into(), - base_asset, - 0, - ScoringRule::CPMM, - Some(0), - Some(balance), - Some(vec![_3, _1, _1, _1]), - )); - - // Withdraw a certain amount of outcome tokens to push the total spot price above 1 - // (`ASSET_A` is the base asset, all other assets are considered outcomes). The exact choice - // of balance for `ASSET_A` ensures that after one iteration, we slightly overshoot the - // target (see below). - let pool_account_id = Swaps::pool_account_id(&DEFAULT_POOL_ID); - assert_ok!(Currencies::withdraw( - ASSET_A, - &pool_account_id, - balance - 39 * min_balance / 30, // little less than 4 / 3 - )); - assert_ok!(Currencies::withdraw(ASSET_B, &pool_account_id, balance - min_balance)); - assert_ok!(Currencies::withdraw(ASSET_C, &pool_account_id, balance - min_balance)); - assert_ok!(Currencies::withdraw(ASSET_D, &pool_account_id, balance - min_balance)); - - // The arbitrage amount of mint-sell should always be so that min_balances are not - // violated (provided the pool starts in valid state), _except_ when the approximation - // overshoots the target. We simulate this by using exactly one iteration. - assert_ok!(Swaps::execute_arbitrage(DEFAULT_POOL_ID, 1)); - - // Using only one iteration means that the arbitrage amount will be slightly more than - // 9 / 30 * min_balance, but that would result in a violation of the minimum balance of the - // base asset, so we instead arbitrage the maximum allowed amount: - let arbitrage_amount = 9 * min_balance / 30; - assert_eq!(Currencies::free_balance(base_asset, &pool_account_id), min_balance); - assert_eq!( - Currencies::free_balance(ASSET_B, &pool_account_id), - min_balance + arbitrage_amount, - ); - assert_eq!( - Currencies::free_balance(ASSET_C, &pool_account_id), - min_balance + arbitrage_amount, - ); - assert_eq!( - Currencies::free_balance(ASSET_D, &pool_account_id), - min_balance + arbitrage_amount, - ); - let market_account_id = MarketCommons::market_account(DEFAULT_MARKET_ID); - assert_eq!(Currencies::free_balance(base_asset, &market_account_id), arbitrage_amount); - System::assert_has_event( - Event::ArbitrageMintSell(DEFAULT_POOL_ID, arbitrage_amount).into(), - ); - }); -} - -#[test_case( - 0, - Perbill::from_parts( - u32::try_from( - 1 + ((::MaxSwapFee::get() * 1_000_000_000 - ) / BASE)).unwrap() - ); "creator_fee_only" - ) -] -#[test_case( - 1 + BASE / 100 * 5, - Perbill::from_parts( - u32::try_from( - (::MaxSwapFee::get() * 1_000_000_000 / 2 - ) / BASE).unwrap() - ); "sum_of_all_fees" - ) -] -fn create_pool_respects_total_fee_limits(swap_fee: u128, creator_fee: Perbill) { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - ASSETS.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _10000)); - }); - assert_err!( - Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - DEFAULT_MARKET_ID, - ScoringRule::CPMM, - Some(swap_fee), - Some(DEFAULT_LIQUIDITY), - Some(vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT]), - ), - Error::::SwapFeeTooHigh - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_charged_correctly( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let ed = ::ExistentialDeposits::get(&BASE_ASSET); - assert_ok!(>::deposit( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - ed - )); - - let market_creator_balance_before = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - let asset_amount_in = _1; - let min_asset_amount_out = Some(_1 / 2); - let max_price = None; - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - true, - &creator_fee, - swap_fee, - asset_amount_in, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - min_asset_amount_out, - max_price, - )); - - let market_creator_balance_after = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - - assert_eq!(market_creator_balance_after - market_creator_balance_before, expected_fee); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_respects_min_amount_out( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let min_asset_amount_out = _1; - // Does not regard market creator fee - let asset_amount_in = calc_in_given_out( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - min_asset_amount_out, - swap_fee, - ) - .unwrap(); - - assert_err!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - Some(min_asset_amount_out), - None, - ), - Error::::LimitOut - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_creator_fee_respects_max_price( - asset_in: Asset, - asset_out: Asset, -) { - let mut max_spot_price = 0; - let asset_amount_in = _1; - - ExtBuilder::default().build().execute_with(|| { - let swap_fee = 0; - - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - None, - Some(u128::MAX), - ),); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_after = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_after = Currencies::free_balance(asset_out, &pool_account); - - // Does not regard market creator fee - max_spot_price = calc_spot_price( - pool_balance_in_after, - DEFAULT_WEIGHT, - pool_balance_out_after, - DEFAULT_WEIGHT, - swap_fee, - ) - .unwrap(); - }); - - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - assert_err!( - Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount_in, - asset_out, - None, - Some(max_spot_price), - ), - Error::::BadLimitPrice - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_in_with_creator_fee_respects_existential_deposit( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - // TODO(#1097) - using 10x ed since otherwise a MathApproximation bug is emitted. - let asset_amount = ::ExistentialDeposits::get(&BASE_ASSET) - .saturating_sub(1) - * 10; - - if asset_amount == 0 { - return; - } - - frame_system::Pallet::::set_block_number(1); - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Currencies::withdraw( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR) - )); - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - true, - &creator_fee, - swap_fee, - asset_amount, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_in( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - asset_amount, - asset_out, - Some(asset_amount / 2), - None, - )); - System::assert_has_event( - Event::MarketCreatorFeePaymentFailed( - DEFAULT_MARKET_ID, - ALICE, - DEFAULT_MARKET_CREATOR, - expected_fee, - BASE_ASSET, - orml_tokens::Error::::ExistentialDeposit.into(), - ) - .into(), - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_charged_correctly( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let ed = ::ExistentialDeposits::get(&BASE_ASSET); - assert_ok!(>::deposit( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - ed - )); - - let market_creator_balance_before = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - let asset_amount_out = _1; - let max_asset_amount_in = Some(_2); - let max_price = None; - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - false, - &creator_fee, - swap_fee, - asset_amount_out, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - max_asset_amount_in, - asset_out, - asset_amount_out, - max_price, - )); - - let market_creator_balance_after = - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR); - - assert_eq!(market_creator_balance_after - market_creator_balance_before, expected_fee); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_respects_max_amount_in( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let max_asset_amount_in = _1; - // Does not regard market creator fee - let asset_amount_out = calc_out_given_in( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - max_asset_amount_in, - swap_fee, - ) - .unwrap(); - - assert_err!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(max_asset_amount_in), - asset_out, - asset_amount_out, - None, - ), - Error::::LimitIn - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_respects_max_price( - asset_in: Asset, - asset_out: Asset, -) { - let mut max_spot_price = 0; - let asset_amount_out = _1; - - ExtBuilder::default().build().execute_with(|| { - let swap_fee = 0; - - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - None, - asset_out, - asset_amount_out, - Some(u128::MAX), - ),); - - let pool_account = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let pool_balance_in_after = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_after = Currencies::free_balance(asset_out, &pool_account); - - // Does not regard market creator fee - max_spot_price = calc_spot_price( - pool_balance_in_after, - DEFAULT_WEIGHT, - pool_balance_out_after, - DEFAULT_WEIGHT, - swap_fee, - ) - .unwrap(); - }); - - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - assert_err!( - Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - None, - asset_out, - asset_amount_out, - Some(max_spot_price), - ), - Error::::BadLimitPrice - ); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_creator_fee_swaps_correct_amount_out( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - - let alice_balance_out_before = Currencies::free_balance(asset_out, &ALICE); - let asset_amount_out = _1; - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(u128::MAX), - asset_out, - asset_amount_out, - None, - )); - - let alice_balance_out_after = Currencies::free_balance(asset_out, &ALICE); - assert_eq!(alice_balance_out_after - alice_balance_out_before, asset_amount_out); - }); -} - -#[test_case(BASE_ASSET, ASSET_B; "base_asset_in")] -#[test_case(ASSET_B, BASE_ASSET; "base_asset_out")] -#[test_case(ASSET_B, ASSET_C; "no_base_asset")] -fn swap_exact_amount_out_with_creator_fee_respects_existential_deposit( - asset_in: Asset, - asset_out: Asset, -) { - ExtBuilder::default().build().execute_with(|| { - let creator_fee = Perbill::from_percent(1); - let swap_fee = 0; - let asset_amount = ::ExistentialDeposits::get(&BASE_ASSET) - .saturating_sub(1); - - if asset_amount == 0 { - return; - } - - frame_system::Pallet::::set_block_number(1); - assert_ok!(set_creator_fee(DEFAULT_MARKET_ID, creator_fee)); - create_initial_pool_with_funds_for_alice(ScoringRule::CPMM, Some(swap_fee), true); - assert_ok!(Currencies::withdraw( - BASE_ASSET, - &DEFAULT_MARKET_CREATOR, - Currencies::free_balance(BASE_ASSET, &DEFAULT_MARKET_CREATOR) - )); - - let expected_fee = expected_creator_fee( - &DEFAULT_POOL_ID, - false, - &creator_fee, - swap_fee, - asset_amount, - asset_in, - asset_out, - ); - - assert_ok!(Swaps::swap_exact_amount_out( - alice_signed(), - DEFAULT_POOL_ID, - asset_in, - Some(2 * asset_amount), - asset_out, - asset_amount, - None, - )); - System::assert_has_event( - Event::MarketCreatorFeePaymentFailed( - DEFAULT_MARKET_ID, - ALICE, - DEFAULT_MARKET_CREATOR, - expected_fee, - BASE_ASSET, - orml_tokens::Error::::ExistentialDeposit.into(), - ) - .into(), - ); - }); -} - -fn alice_signed() -> RuntimeOrigin { - RuntimeOrigin::signed(ALICE) -} - -// Must be called before the swap happens. -fn expected_creator_fee( - pool_id: &PoolId, - swap_in: bool, - creator_fee: &Perbill, - swap_fee: BalanceOf, - asset_amount: BalanceOf, - asset_in: Asset, - asset_out: Asset, -) -> BalanceOf { - let pool_account = Swaps::pool_account_id(pool_id); - let pool_balance_in_before = Currencies::free_balance(asset_in, &pool_account); - let pool_balance_out_before = Currencies::free_balance(asset_out, &pool_account); - let pool_balance_base_out_before = Currencies::free_balance(BASE_ASSET, &pool_account); - - let expected_amount_without_fee = if swap_in { - calc_out_given_in( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - asset_amount, - swap_fee, - ) - .unwrap() - } else { - calc_in_given_out( - pool_balance_in_before, - DEFAULT_WEIGHT, - pool_balance_out_before, - DEFAULT_WEIGHT, - asset_amount, - swap_fee, - ) - .unwrap() - }; - - if asset_in == BASE_ASSET { - if swap_in { - creator_fee.mul_floor(asset_amount) - } else { - creator_fee.mul_floor(expected_amount_without_fee) - } - } else if asset_out == BASE_ASSET { - if swap_in { - creator_fee.mul_floor(expected_amount_without_fee) - } else { - creator_fee.mul_floor(asset_amount) - } - } else if swap_in { - let fee_before_swap = creator_fee.mul_floor(expected_amount_without_fee); - calc_out_given_in( - DEFAULT_LIQUIDITY - expected_amount_without_fee, - DEFAULT_WEIGHT, - pool_balance_base_out_before, - DEFAULT_WEIGHT, - fee_before_swap, - swap_fee, - ) - .unwrap() - } else { - let fee_before_swap = creator_fee.mul_floor(asset_amount); - calc_out_given_in( - DEFAULT_LIQUIDITY - asset_amount - fee_before_swap, - DEFAULT_WEIGHT, - pool_balance_base_out_before, - DEFAULT_WEIGHT, - fee_before_swap, - swap_fee, - ) - .unwrap() - } -} - -fn create_initial_pool( - scoring_rule: ScoringRule, - swap_fee: Option>, - deposit: bool, -) { - if deposit { - ASSETS.iter().cloned().for_each(|asset| { - assert_ok!(Currencies::deposit(asset, &BOB, _100)); - }); - } - - let pool_id = Swaps::next_pool_id(); - assert_ok!(Swaps::create_pool( - BOB, - ASSETS.to_vec(), - BASE_ASSET, - DEFAULT_MARKET_ID, - scoring_rule, - swap_fee, - if scoring_rule == ScoringRule::CPMM { Some(DEFAULT_LIQUIDITY) } else { None }, - if scoring_rule == ScoringRule::CPMM { - Some(vec![DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT, DEFAULT_WEIGHT]) - } else { - None - }, - )); - if scoring_rule == ScoringRule::CPMM { - assert_ok!(Swaps::open_pool(pool_id)); - } -} - -fn create_initial_pool_with_funds_for_alice( - scoring_rule: ScoringRule, - swap_fee: Option>, - deposit: bool, -) { - create_initial_pool(scoring_rule, swap_fee, deposit); - let _ = Currencies::deposit(ASSET_A, &ALICE, _25); - let _ = Currencies::deposit(ASSET_B, &ALICE, _25); - let _ = Currencies::deposit(ASSET_C, &ALICE, _25); - let _ = Currencies::deposit(ASSET_D, &ALICE, _25); -} - -fn assert_all_parameters( - alice_assets: [u128; 4], - alice_pool_assets: u128, - pool_assets: [u128; 4], - total_issuance: u128, -) { - let pai = Swaps::pool_account_id(&DEFAULT_POOL_ID); - let psi = Swaps::pool_shares_id(DEFAULT_POOL_ID); - - assert_eq!(Currencies::free_balance(ASSET_A, &ALICE), alice_assets[0]); - assert_eq!(Currencies::free_balance(ASSET_B, &ALICE), alice_assets[1]); - assert_eq!(Currencies::free_balance(ASSET_C, &ALICE), alice_assets[2]); - assert_eq!(Currencies::free_balance(ASSET_D, &ALICE), alice_assets[3]); + assert_eq!(Currencies::free_balance(ASSET_A, &ALICE), alice_assets[0]); + assert_eq!(Currencies::free_balance(ASSET_B, &ALICE), alice_assets[1]); + assert_eq!(Currencies::free_balance(ASSET_C, &ALICE), alice_assets[2]); + assert_eq!(Currencies::free_balance(ASSET_D, &ALICE), alice_assets[3]); assert_eq!(Currencies::free_balance(psi, &ALICE), alice_pool_assets); @@ -4359,22 +2617,3 @@ fn assert_all_parameters( assert_eq!(Currencies::free_balance(ASSET_D, &pai), pool_assets[3]); assert_eq!(Currencies::total_issuance(psi), total_issuance); } - -fn set_creator_fee(market_id: MarketId, fee: Perbill) -> DispatchResult { - MarketCommons::mutate_market(&market_id, |market: &mut MarketOf| { - market.creator_fee = fee; - Ok(()) - }) -} - -// Subsidize and start a Rikiddo pool. Extra is the amount of additional base asset added to who. -fn subsidize_and_start_rikiddo_pool( - pool_id: PoolId, - who: &::AccountId, - extra: crate::BalanceOf, -) { - let min_subsidy = ::MinSubsidy::get(); - assert_ok!(Currencies::deposit(ASSET_D, who, min_subsidy + extra)); - assert_ok!(Swaps::pool_join_subsidy(RuntimeOrigin::signed(*who), pool_id, min_subsidy)); - assert!(Swaps::end_subsidy_phase(pool_id).unwrap().result); -} diff --git a/zrml/swaps/src/types/mod.rs b/zrml/swaps/src/types/mod.rs new file mode 100644 index 000000000..2bf0149cb --- /dev/null +++ b/zrml/swaps/src/types/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +mod pool; + +pub(crate) use pool::*; diff --git a/primitives/src/pool_status.rs b/zrml/swaps/src/types/pool.rs similarity index 54% rename from primitives/src/pool_status.rs rename to zrml/swaps/src/types/pool.rs index 1d9873968..79fe8c234 100644 --- a/primitives/src/pool_status.rs +++ b/zrml/swaps/src/types/pool.rs @@ -1,3 +1,4 @@ +// Copyright 2023-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -15,6 +16,40 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +use frame_support::storage::{bounded_btree_map::BoundedBTreeMap, bounded_vec::BoundedVec}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Get, RuntimeDebug}; +use zeitgeist_primitives::constants::MAX_ASSETS; + +pub struct MaxAssets; + +impl Get for MaxAssets { + fn get() -> u32 { + MAX_ASSETS as u32 + } +} + +// TODO(#1213): Replace `u128` with `PoolWeight` type which implements `Into` and +// `From`! Or just replace it with `Balance`. +#[derive(TypeInfo, Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug)] +pub struct Pool { + pub assets: BoundedVec, + pub status: PoolStatus, + pub swap_fee: Balance, + pub total_weight: u128, + pub weights: BoundedBTreeMap, +} + +impl Pool +where + Asset: Ord, +{ + pub fn bound(&self, asset: &Asset) -> bool { + self.weights.get(asset).is_some() + } +} + /// The status of a pool. Closely related to the lifecycle of a market. #[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] @@ -33,13 +68,7 @@ )] pub enum PoolStatus { /// Shares can be normally negotiated. - Active, - /// No trading is allowed. The pool is waiting to be subsidized. - CollectingSubsidy, + Open, /// No trading/adding liquidity is allowed. Closed, - /// The pool has been cleaned up, usually after the corresponding market has been resolved. - Clean, - /// The pool has just been created. - Initialized, } diff --git a/zrml/swaps/src/utils.rs b/zrml/swaps/src/utils.rs index afe06ca3c..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. @@ -23,35 +23,33 @@ // . use crate::{ - check_arithm_rslt::CheckArithmRslt, events::{CommonPoolEventParams, PoolAssetEvent, PoolAssetsEvent, SwapEvent}, - fixed::{bdiv, bmul}, - 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, SaturatedConversion, +use sp_runtime::{traits::Zero, DispatchError}; +use zeitgeist_primitives::{ + math::{ + checked_ops_res::CheckedSubRes, + fixed::{FixedDiv, FixedMul}, + }, + types::PoolId, }; -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. -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); @@ -67,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, @@ -80,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); @@ -105,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, @@ -120,27 +114,26 @@ 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); - 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)?; @@ -149,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, @@ -160,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)?; @@ -190,76 +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 - <= bdiv(asset_amount_in.saturated_into(), asset_amount_out.saturated_into())? - .saturated_into(), - 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, @@ -274,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> @@ -315,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 b3d827065..d2078e41c 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: `2023-10-12`, STEPS: `10`, REPEAT: `1000`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-02-15`, STEPS: `2`, REPEAT: `0`, 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: `zafoi`, CPU: `AMD Ryzen 9 5900X 12-Core Processor` +//! 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=2 +// --repeat=0 // --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,10 @@ 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(542), added: 3017, 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: `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())) - .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) - /// 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) - .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: `3255 + 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())) - .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: 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())) - .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: `805 + 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())) - .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: `662 + 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())) - .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_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())) - .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: `765 + 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())) - .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: `563 + 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())) - .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: `406 + 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())) - .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: MarketAssets Asset (r:66 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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) @@ -306,279 +76,167 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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 `a` is `[2, 65]`. - fn pool_exit(a: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `1015 + 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())) - .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 { + fn pool_exit(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `2459` - // Estimated: `11279` - // Minimum execution time: 52_520 nanoseconds. - Weight::from_parts(65_610_000, 11279) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `845 + a * (176 ±0)` + // Estimated: `529717` + // Minimum execution time: 56_181 nanoseconds. + Weight::from_parts(821_990_000, 529717) + .saturating_add(T::DbWeight::get().reads(200)) + .saturating_add(T::DbWeight::get().writes(132)) } /// 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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `5392` - // Estimated: `19045` - // Minimum execution time: 116_341 nanoseconds. - Weight::from_parts(144_710_000, 19045) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `5655` + // Estimated: `24373` + // Minimum execution time: 55_472 nanoseconds. + Weight::from_parts(55_472_000, 24373) + .saturating_add(T::DbWeight::get().reads(8)) + .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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `5392` - // Estimated: `19045` - // Minimum execution time: 116_160 nanoseconds. - Weight::from_parts(145_430_000, 19045) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `5655` + // Estimated: `24373` + // Minimum execution time: 54_581 nanoseconds. + Weight::from_parts(54_581_000, 24373) + .saturating_add(T::DbWeight::get().reads(8)) + .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: MarketAssets Asset (r:66 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// The range of component `a` is `[2, 65]`. - fn pool_join(a: u32) -> Weight { + fn pool_join(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `876 + 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())) - .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: `2357` - // Estimated: `11279` - // Minimum execution time: 53_771 nanoseconds. - Weight::from_parts(67_210_000, 11279) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `710 + a * (289 ±0)` + // Estimated: `527110` + // Minimum execution time: 46_761 nanoseconds. + Weight::from_parts(663_186_000, 527110) + .saturating_add(T::DbWeight::get().reads(199)) + .saturating_add(T::DbWeight::get().writes(132)) } /// 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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `5947` - // Estimated: `16438` - // Minimum execution time: 98_810 nanoseconds. - Weight::from_parts(122_971_000, 16438) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `6338` + // Estimated: `21766` + // Minimum execution time: 50_152 nanoseconds. + Weight::from_parts(50_152_000, 21766) + .saturating_add(T::DbWeight::get().reads(7)) + .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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `5947` - // Estimated: `16438` - // Minimum execution time: 99_091 nanoseconds. - Weight::from_parts(124_120_000, 16438) - .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: 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())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `6338` + // Estimated: `21766` + // Minimum execution time: 49_132 nanoseconds. + Weight::from_parts(49_132_000, 21766) + .saturating_add(T::DbWeight::get().reads(7)) + .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(542), added: 3017, mode: MaxEncodedLen) + /// Proof: Swaps Pools (max_values: None, max_size: Some(3579), added: 6054, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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: `5489` - // Estimated: `22142` - // Minimum execution time: 180_701 nanoseconds. - Weight::from_parts(224_470_000, 22142) - .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) - /// 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: `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())) - .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: `5253` + // Estimated: `24453` + // Minimum execution time: 81_022 nanoseconds. + Weight::from_parts(81_022_000, 24453) + .saturating_add(T::DbWeight::get().reads(8)) + .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: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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(542), added: 3017, 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) - .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(542), added: 3017, 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: `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())) - .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: `5253` + // Estimated: `24453` + // Minimum execution time: 79_882 nanoseconds. + Weight::from_parts(79_882_000, 24453) + .saturating_add(T::DbWeight::get().reads(8)) + .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 { + fn open_pool(_a: u32) -> Weight { // 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())) + // Measured: `167 + a * (54 ±0)` + // Estimated: `6054` + // Minimum execution time: 8_391 nanoseconds. + Weight::from_parts(20_700_000, 6054) .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 { + fn close_pool(_a: u32) -> Weight { // 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())) + // Measured: `167 + a * (54 ±0)` + // Estimated: `6054` + // Minimum execution time: 7_530 nanoseconds. + Weight::from_parts(14_680_000, 6054) .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: MarketAssets Asset (r:65 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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) @@ -586,18 +244,13 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Tokens TotalIssuance (r:65 w:65) /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) /// The range of component `a` is `[2, 65]`. - fn destroy_pool(a: u32) -> Weight { + fn destroy_pool(_a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `576 + 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())) - .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)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - .saturating_add(Weight::from_parts(0, 5116).saturating_mul(a.into())) + // Measured: `655 + a * (215 ±0)` + // Estimated: `516701` + // Minimum execution time: 36_371 nanoseconds. + Weight::from_parts(640_556_000, 516701) + .saturating_add(T::DbWeight::get().reads(197)) + .saturating_add(T::DbWeight::get().writes(132)) } } From 7860c709bf8f518b7ed47cea5fbb0a7e4492306b Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Mon, 18 Mar 2024 12:28:55 +0100 Subject: [PATCH 07/14] New Asset System - Add Sub Asset Classes and extended Market functionality (#1281) * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Correct ParimutuelAssetClass docstring * Add more docstrings to Market impl * Add tests for MarketTransitionApi * Implement try_into failure tests for assets * Fix typo * Add docstring to public func of Market struct --- Cargo.lock | 1 + macros/src/lib.rs | 8 +- primitives/Cargo.toml | 1 + primitives/src/assets.rs | 2 + primitives/src/assets/all_assets.rs | 37 +- primitives/src/assets/campaign_assets.rs | 2 +- primitives/src/assets/custom_assets.rs | 2 +- primitives/src/assets/market_assets.rs | 4 +- primitives/src/assets/subsets/base_assets.rs | 48 +++ primitives/src/assets/subsets/mod.rs | 26 ++ primitives/src/assets/subsets/parimutuel.rs | 38 +++ primitives/src/assets/subsets/xcm_assets.rs | 43 +++ primitives/src/assets/tests/conversion.rs | 173 +++++++++- primitives/src/assets/tests/scale_codec.rs | 96 ++++-- primitives/src/constants/mock.rs | 2 +- primitives/src/market.rs | 273 +++++++++++++-- primitives/src/traits.rs | 2 + primitives/src/traits/dispute_api.rs | 6 +- .../src/traits/market_commons_pallet_api.rs | 6 +- .../src/traits/market_transition_api.rs | 322 ++++++++++++++++++ primitives/src/types.rs | 28 +- zrml/asset-router/src/lib.rs | 2 +- zrml/asset-router/src/mock.rs | 4 +- .../src/pallet_impl/managed_destroy.rs | 1 - .../asset-router/src/tests/managed_destroy.rs | 2 +- 25 files changed, 1045 insertions(+), 84 deletions(-) create mode 100644 primitives/src/assets/subsets/base_assets.rs create mode 100644 primitives/src/assets/subsets/mod.rs create mode 100644 primitives/src/assets/subsets/parimutuel.rs create mode 100644 primitives/src/assets/subsets/xcm_assets.rs create mode 100644 primitives/src/traits/market_transition_api.rs diff --git a/Cargo.lock b/Cargo.lock index 44c60c1df..66be058e2 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/macros/src/lib.rs b/macros/src/lib.rs index b6ee80362..6b4609b19 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -50,7 +50,7 @@ macro_rules! create_b_tree_map { #[macro_export] macro_rules! unreachable_non_terminating { ($condition: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); + let message = alloc::format!($message, $($message_args)*); #[cfg(test)] assert!($condition, "{}", message); @@ -60,7 +60,7 @@ macro_rules! unreachable_non_terminating { } }; ($condition: expr, $log_target: ident, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); + let message = alloc::format!($message, $($message_args)*); #[cfg(test)] assert!($condition, "{}", message); @@ -70,7 +70,7 @@ macro_rules! unreachable_non_terminating { } }; ($condition: expr, $extra_code: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); + let message = alloc::format!($message, $($message_args)*); #[cfg(test)] assert!($condition, "{}", message); @@ -81,7 +81,7 @@ macro_rules! unreachable_non_terminating { } }; ($condition: expr, $log_target: ident, $extra_code: expr, $message: literal, $($message_args: tt)*) => { - let message = format!($message, $($message_args)*); + let message = alloc::format!($message, $($message_args)*); #[cfg(test)] assert!($condition, "{}", message); 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/assets.rs b/primitives/src/assets.rs index c187096bd..7de237bb8 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, ParimutuelAssetClass, XcmAssetClass}; 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..012486af9 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,32 @@ 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), + } + } +} + +impl From> for Asset { + fn from(value: ParimutuelAssetClass) -> Self { + match value { + ParimutuelAssetClass::::Share(market_id, cat_id) => { + Self::ParimutuelShare(market_id, cat_id) + } + } + } +} + +impl From for Asset { + fn from(value: XcmAssetClass) -> Self { + match value { + XcmAssetClass::Ztg => Self::Ztg, + XcmAssetClass::ForeignAsset(id) => Self::ForeignAsset(id), + } + } +} 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/market_assets.rs b/primitives/src/assets/market_assets.rs index 0789eaa27..7573cf74a 100644 --- a/primitives/src/assets/market_assets.rs +++ b/primitives/src/assets/market_assets.rs @@ -25,7 +25,9 @@ use super::*; /// * `MI`: Market Id #[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)] +#[derive( + Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, PartialEq, PartialOrd, Ord, TypeInfo, +)] pub enum MarketAssetClass { #[codec(index = 0)] CategoricalOutcome(MI, CategoryIndex), diff --git a/primitives/src/assets/subsets/base_assets.rs b/primitives/src/assets/subsets/base_assets.rs new file mode 100644 index 000000000..041d0b10c --- /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 `BaseAssetClass` 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/subsets/mod.rs b/primitives/src/assets/subsets/mod.rs new file mode 100644 index 000000000..eafe22f04 --- /dev/null +++ b/primitives/src/assets/subsets/mod.rs @@ -0,0 +1,26 @@ +// 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::*; + +pub use base_assets::BaseAssetClass; +pub use parimutuel::ParimutuelAssetClass; +pub use xcm_assets::XcmAssetClass; + +mod base_assets; +mod parimutuel; +mod xcm_assets; diff --git a/primitives/src/assets/subsets/parimutuel.rs b/primitives/src/assets/subsets/parimutuel.rs new file mode 100644 index 000000000..26d412a4e --- /dev/null +++ b/primitives/src/assets/subsets/parimutuel.rs @@ -0,0 +1,38 @@ +// 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 `ParimutuelAssetClass` enum represents all assets that are specific to parimutuel 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(()), + } + } +} diff --git a/primitives/src/assets/subsets/xcm_assets.rs b/primitives/src/assets/subsets/xcm_assets.rs new file mode 100644 index 000000000..bbad9de08 --- /dev/null +++ b/primitives/src/assets/subsets/xcm_assets.rs @@ -0,0 +1,43 @@ +// 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 `XcmAssetClass` enum represents all assets that can be transferred via XCM. +#[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 XcmAssetClass { + #[codec(index = 4)] + #[default] + Ztg, + + #[codec(index = 5)] + ForeignAsset(u32), +} + +impl TryFrom> for XcmAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::Ztg => Ok(Self::Ztg), + Asset::::ForeignAsset(id) => Ok(Self::ForeignAsset(id)), + _ => Err(()), + } + } +} diff --git a/primitives/src/assets/tests/conversion.rs b/primitives/src/assets/tests/conversion.rs index eaa8c0006..49829978c 100644 --- a/primitives/src/assets/tests/conversion.rs +++ b/primitives/src/assets/tests/conversion.rs @@ -49,6 +49,14 @@ fn from_all_assets_to_market_assets( assert_eq!(new_asset, new_asset_converted); } +#[test_case(Asset::::Ztg; "ztg")] +#[test_case(Asset::::ForeignAsset(7); "foreign_asset")] +#[test_case(Asset::::CampaignAsset(7); "campaign_asset")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_market_assets_fails(asset: Asset) { + assert!(MarketAssetClass::::try_from(asset).is_err()); +} + #[test_case( MarketAssetClass::::CategoricalOutcome(7, 8), Asset::::CategoricalOutcome(7, 8); @@ -108,6 +116,13 @@ fn from_all_assets_to_currencies(old_asset: Asset, new_asset: Currency assert_eq!(new_asset, new_asset_converted); } +#[test_case(Asset::::Ztg; "ztg")] +#[test_case(Asset::::CampaignAsset(7); "campaign_asset")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_currencies_fails(asset: Asset) { + assert!(CurrencyClass::::try_from(asset).is_err()); +} + #[test_case( CurrencyClass::::CategoricalOutcome(7, 8), Asset::::CategoricalOutcome(7, 8); @@ -141,17 +156,28 @@ 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(); assert_eq!(new_asset, new_asset_converted); } +#[test_case(Asset::::CategoricalOutcome(7, 8); "categorical_outcome")] +#[test_case(Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome")] +#[test_case(Asset::::PoolShare(7); "pool_share")] +#[test_case(Asset::::Ztg; "ztg")] +#[test_case(Asset::::ForeignAsset(7); "foreign_asset")] +#[test_case(Asset::::ParimutuelShare(7, 8); "parimutuel_share")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_campaign_assets_fails(asset: Asset) { + assert!(CampaignAssetClass::try_from(asset).is_err()); +} + #[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,17 +185,156 @@ 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(); assert_eq!(new_asset, new_asset_converted); } +#[test_case(Asset::::CategoricalOutcome(7, 8); "categorical_outcome")] +#[test_case(Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome")] +#[test_case(Asset::::PoolShare(7); "pool_share")] +#[test_case(Asset::::Ztg; "ztg")] +#[test_case(Asset::::ForeignAsset(7); "foreign_asset")] +#[test_case(Asset::::ParimutuelShare(7, 8); "parimutuel_share")] +#[test_case(Asset::::CampaignAsset(7); "campaign_asset")] +fn from_all_assets_to_custom_assets_fails(asset: Asset) { + assert!(CustomAssetClass::try_from(asset).is_err()); +} + #[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(Asset::::CategoricalOutcome(7, 8); "categorical_outcome")] +#[test_case(Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome")] +#[test_case(Asset::::PoolShare(7); "pool_share")] +#[test_case(Asset::::ParimutuelShare(7, 8); "parimutuel_share")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_base_assets_fails(asset: Asset) { + assert!(BaseAssetClass::try_from(asset).is_err()); +} + +#[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); +} + +// Assets <> 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(Asset::::CategoricalOutcome(7, 8); "categorical_outcome")] +#[test_case(Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome")] +#[test_case(Asset::::PoolShare(7); "pool_share")] +#[test_case(Asset::::Ztg; "ztg")] +#[test_case(Asset::::ForeignAsset(7); "foreign_asset")] +#[test_case(Asset::::CampaignAsset(7); "campaign_asset")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_parimutuel_assets_fails(asset: Asset) { + assert!(ParimutuelAssetClass::::try_from(asset).is_err()); +} + +#[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); +} + +// Assets <> XcmAssetClass +#[test_case( + Asset::::ForeignAsset(7), + XcmAssetClass::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + Asset::::Ztg, + XcmAssetClass::Ztg; + "ztg" +)] +fn from_all_assets_to_xcm_assets(old_asset: Asset, new_asset: XcmAssetClass) { + let new_asset_converted: XcmAssetClass = old_asset.try_into().unwrap(); + assert_eq!(new_asset, new_asset_converted); +} + +#[test_case(Asset::::CategoricalOutcome(7, 8); "categorical_outcome")] +#[test_case(Asset::::ScalarOutcome(7, ScalarPosition::Long); "scalar_outcome")] +#[test_case(Asset::::PoolShare(7); "pool_share")] +#[test_case(Asset::::CampaignAsset(7); "campaign_asset")] +#[test_case(Asset::::CustomAsset(7); "custom_asset")] +fn from_all_assets_to_xcm_assets_fails(asset: Asset) { + assert!(XcmAssetClass::try_from(asset).is_err()); +} + +#[test_case( + XcmAssetClass::ForeignAsset(7), + Asset::::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + XcmAssetClass::Ztg, + Asset::::Ztg; + "ztg" +)] +fn from_xcm_assets_to_all_assets(old_asset: XcmAssetClass, 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..050da1db5 100644 --- a/primitives/src/assets/tests/scale_codec.rs +++ b/primitives/src/assets/tests/scale_codec.rs @@ -20,69 +20,127 @@ use super::*; use test_case::test_case; -// Assets <> MarketAssetClass +// 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 <> 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); +} + +// Assets <> XcmAssetClass +#[test_case( + Asset::::ForeignAsset(7), + XcmAssetClass::ForeignAsset(7); + "foreign_asset" +)] +#[test_case( + Asset::::Ztg, + XcmAssetClass::Ztg; + "ztg" +)] +fn index_matching_works_for_xcm_assets(old_asset: Asset, new_asset: XcmAssetClass) { + 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); } diff --git a/primitives/src/constants/mock.rs b/primitives/src/constants/mock.rs index b2e19c2bd..651b02ec4 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -189,7 +189,7 @@ parameter_type_with_key! { parameter_type_with_key! { pub ExistentialDepositsNew: |_currency_id: Currencies| -> Balance { - 1 + 2 }; } diff --git a/primitives/src/market.rs b/primitives/src/market.rs index cae1eb2bc..e11645ca9 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 alloc::vec::Vec; +use crate::types::{MarketAssetClass, OutcomeReport, ScalarPosition}; +use alloc::{vec, 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; @@ -69,6 +69,10 @@ pub struct Market { } impl Market { + /// Returns the `ResolutionMechanism` of market, currently either: + /// - `RedeemTokens`, which implies that the module that handles the state transitions of + /// a market is also responsible to provide means for redeeming rewards + /// - `Noop`, which implies that another module provides the means for redeeming rewards pub fn resolution_mechanism(&self) -> ResolutionMechanism { match self.scoring_rule { ScoringRule::Lmsr | ScoringRule::Orderbook => ResolutionMechanism::RedeemTokens, @@ -76,9 +80,121 @@ impl Market { } } + /// Returns whether the market is redeemable, i.e. reward payout is managed within + /// the same module that controls the state transitions of the underlying 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(_)) + } + } + } + + /// Returns a `Vec` of all outcomes for `market_id`. + 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::::ParimutuelShare(market_id, i)) + } + }; + } + + assets + } + MarketType::Scalar(_) => { + vec![ + MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Short), + ] + } + } + } + + /// Tries to convert the reported outcome for `market_id` into an asset, + /// returns `None` if not possible. Cases where `None` is returned are: + /// - The reported outcome does not exist + /// - The reported outcome does not have a corresponding asset type + pub fn report_into_asset( + &self, + market_id: MI, + ) -> Option> { + let outcome = if let Some(ref report) = self.report { + &report.outcome + } else { + return None; + }; + + self.outcome_report_into_asset(market_id, outcome) + } + + /// Tries to convert the resolved outcome for `market_id` into an asset, + /// returns `None` if not possible. Cases where `None` is returned are: + /// - The resolved outcome does not exist + /// - The resolved outcome does not have a corresponding asset type + pub fn resolved_outcome_into_asset( + &self, + market_id: MI, + ) -> Option> { + let outcome = self.resolved_outcome.as_ref()?; + self.outcome_report_into_asset(market_id, outcome) + } + + /// Tries to convert a `outcome_report` for `market_id` into an asset, + /// returns `None` if not possible. + fn outcome_report_into_asset( + &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::::ParimutuelShare(market_id, *idx)) + } + }, + OutcomeReport::Scalar(_) => None, + } + } } /// Tracks the status of a bond. @@ -139,32 +255,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 +435,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 +520,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::ParimutuelShare(0, 0), MarketAsset::ParimutuelShare(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::ParimutuelShare(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_asset(0), expected); + assert_eq!(market.report_into_asset(0), expected); + } + #[test] fn max_encoded_len_market_type() { // `MarketType::Scalar` is the largest enum variant. diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 02d8d70f1..d01d0a8c4 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -22,6 +22,7 @@ mod dispute_api; mod distribute_fees; mod market_commons_pallet_api; mod market_id; +mod market_transition_api; mod swaps; mod weights; mod zeitgeist_asset; @@ -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/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/primitives/src/traits/market_transition_api.rs b/primitives/src/traits/market_transition_api.rs new file mode 100644 index 000000000..46a613eaa --- /dev/null +++ b/primitives/src/traits/market_transition_api.rs @@ -0,0 +1,322 @@ +// 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 crate::types::ResultWithWeightInfo; +use frame_support::pallet_prelude::{DispatchResult, Weight}; + +/// API that is used to catch market state transitions. +pub trait MarketTransitionApi { + fn on_proposal(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_activation(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_closure(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_report(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_dispute(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_resolution(_market_id: &MI) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(8)] +#[allow(clippy::let_and_return)] +/// Implementation returns on first error or after successful execution of all elements. +impl MarketTransitionApi for Tuple { + fn on_proposal(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_proposal(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } + fn on_activation(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_activation(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } + fn on_closure(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_closure(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } + fn on_report(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_report(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } + fn on_dispute(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_dispute(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } + fn on_resolution(market_id: &MI) -> ResultWithWeightInfo { + let mut collective_result = ResultWithWeightInfo::new(Ok(()), Weight::zero()); + for_tuples!( #( + let result = Tuple::on_resolution(market_id); + collective_result.result = result.result; + collective_result.weight = collective_result.weight.saturating_add(result.weight); + if collective_result.result.is_err() { + return collective_result; + } + )* ); + collective_result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::pallet_prelude::DispatchError; + + const DEFAULT_ERROR: DispatchResult = Err(DispatchError::Other("unimportant")); + const ONE: Weight = Weight::from_all(1); + const TWO: Weight = Weight::from_all(2); + const THREE: Weight = Weight::from_all(3); + + struct ExecutionPath; + impl MarketTransitionApi for ExecutionPath { + fn on_proposal(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_proposal"); + } + fn on_activation(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_activation"); + } + fn on_closure(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_closure"); + } + fn on_report(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_report"); + } + fn on_dispute(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_dispute"); + } + fn on_resolution(_market_id: &u128) -> ResultWithWeightInfo { + panic!("on_resolution"); + } + } + + struct SuccessPath; + impl MarketTransitionApi for SuccessPath { + fn on_proposal(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + fn on_activation(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + fn on_closure(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + fn on_report(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + fn on_dispute(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + fn on_resolution(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(Ok(()), ONE) + } + } + + struct FailurePath; + impl MarketTransitionApi for FailurePath { + fn on_proposal(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + fn on_activation(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + fn on_closure(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + fn on_report(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + fn on_dispute(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + fn on_resolution(_market_id: &u128) -> ResultWithWeightInfo { + ResultWithWeightInfo::new(DEFAULT_ERROR, TWO) + } + } + + #[test] + #[should_panic(expected = "on_proposal")] + fn correct_execution_path_for_tuples_on_proposal() { + <(ExecutionPath,)>::on_proposal(&0); + } + + #[test] + #[should_panic(expected = "on_activation")] + fn correct_execution_path_for_tuples_on_activation() { + <(ExecutionPath,)>::on_activation(&0); + } + + #[test] + #[should_panic(expected = "on_closure")] + fn correct_execution_path_for_tuples_on_closure() { + <(ExecutionPath,)>::on_closure(&0); + } + + #[test] + #[should_panic(expected = "on_report")] + fn correct_execution_path_for_tuples_on_report() { + <(ExecutionPath,)>::on_report(&0); + } + + #[test] + #[should_panic(expected = "on_dispute")] + fn correct_execution_path_for_tuples_on_dispute() { + <(ExecutionPath,)>::on_dispute(&0); + } + + #[test] + #[should_panic(expected = "on_resolution")] + fn correct_execution_path_for_tuples_on_resolution() { + <(ExecutionPath,)>::on_resolution(&0); + } + + #[test] + fn provides_correct_result_on_proposal() { + let mut result = <(SuccessPath,)>::on_proposal(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_proposal(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_proposal(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } + + #[test] + fn provides_correct_result_on_activation() { + let mut result = <(SuccessPath,)>::on_activation(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_activation(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_activation(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } + + #[test] + fn provides_correct_result_on_closure() { + let mut result = <(SuccessPath,)>::on_closure(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_closure(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_closure(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } + + #[test] + fn provides_correct_result_on_report() { + let mut result = <(SuccessPath,)>::on_report(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_report(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_report(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } + + #[test] + fn provides_correct_result_on_dispute() { + let mut result = <(SuccessPath,)>::on_dispute(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_dispute(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_dispute(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } + + #[test] + fn provides_correct_result_on_resolution() { + let mut result = <(SuccessPath,)>::on_resolution(&0); + assert_eq!(result.result, Ok(())); + assert_eq!(result.weight, ONE); + + result = <(SuccessPath, FailurePath)>::on_resolution(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, THREE); + + result = <(FailurePath, SuccessPath)>::on_resolution(&0); + assert_eq!(result.result, DEFAULT_ERROR); + assert_eq!(result.weight, TWO); + } +} diff --git a/primitives/src/types.rs b/primitives/src/types.rs index e759f3b65..76290a56d 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -77,30 +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 campaign assets +/// Asset type representing base assets used by prediction markets. +pub type BaseAsset = BaseAssetClass; + +/// 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, @@ -147,6 +153,12 @@ pub struct ResultWithWeightInfo { pub weight: Weight, } +impl ResultWithWeightInfo { + pub fn new(result: R, weight: Weight) -> Self { + ResultWithWeightInfo { result, weight } + } +} + #[derive( Clone, Copy, diff --git a/zrml/asset-router/src/lib.rs b/zrml/asset-router/src/lib.rs index 50ce4b38e..d2f7a630f 100644 --- a/zrml/asset-router/src/lib.rs +++ b/zrml/asset-router/src/lib.rs @@ -35,7 +35,7 @@ mod types; #[frame_support::pallet] pub mod pallet { pub(crate) use super::types::*; - pub(crate) use alloc::{collections::BTreeMap, format}; + pub(crate) use alloc::collections::BTreeMap; pub(crate) use core::{fmt::Debug, marker::PhantomData}; pub(crate) use frame_support::{ ensure, log, diff --git a/zrml/asset-router/src/mock.rs b/zrml/asset-router/src/mock.rs index b9114884e..e6da9d38b 100644 --- a/zrml/asset-router/src/mock.rs +++ b/zrml/asset-router/src/mock.rs @@ -47,9 +47,9 @@ pub(super) const ALICE: AccountIdTest = 0; pub(super) const BOB: AccountIdTest = 1; pub(super) const CHARLIE: AccountIdTest = 2; -pub(super) const CAMPAIGN_ASSET: Assets = Assets::CampaignAssetClass(0); +pub(super) const CAMPAIGN_ASSET: Assets = Assets::CampaignAsset(0); pub(super) const CAMPAIGN_ASSET_INTERNAL: CampaignAssetClass = CampaignAssetClass(0); -pub(super) const CUSTOM_ASSET: Assets = Assets::CustomAssetClass(0); +pub(super) const CUSTOM_ASSET: Assets = Assets::CustomAsset(0); pub(super) const CUSTOM_ASSET_INTERNAL: CustomAssetClass = CustomAssetClass(0); pub(super) const MARKET_ASSET: Assets = Assets::CategoricalOutcome(7, 8); pub(super) const MARKET_ASSET_INTERNAL: MarketAsset = MarketAsset::CategoricalOutcome(7, 8); diff --git a/zrml/asset-router/src/pallet_impl/managed_destroy.rs b/zrml/asset-router/src/pallet_impl/managed_destroy.rs index 6d714ec71..fd22ac6b0 100644 --- a/zrml/asset-router/src/pallet_impl/managed_destroy.rs +++ b/zrml/asset-router/src/pallet_impl/managed_destroy.rs @@ -58,7 +58,6 @@ impl ManagedDestroy for Pallet { Ok(()) } - #[require_transactional] fn managed_destroy_multi( assets: BTreeMap>, ) -> DispatchResult { diff --git a/zrml/asset-router/src/tests/managed_destroy.rs b/zrml/asset-router/src/tests/managed_destroy.rs index b5ef89335..1c2700ffb 100644 --- a/zrml/asset-router/src/tests/managed_destroy.rs +++ b/zrml/asset-router/src/tests/managed_destroy.rs @@ -322,7 +322,7 @@ fn does_skip_and_remove_assets_in_invalid_state() { fn does_trigger_on_idle_outer_loop_safety_guard() { ExtBuilder::default().build().execute_with(|| { for asset_num in 0..=MAX_ASSET_DESTRUCTIONS_PER_BLOCK { - let asset = Assets::CampaignAssetClass(asset_num as u128); + let asset = Assets::CampaignAsset(asset_num as u128); assert_ok!(AssetRouter::create(asset, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); assert_ok!(AssetRouter::managed_destroy(asset, None)); } From 52fba8a530c6d56175b7b50572b257b39c88cbf7 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 22 Mar 2024 09:33:14 +0100 Subject: [PATCH 08/14] New Asset System - Integrate into Prediction Markets and Parimutuel (#1282) * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Apply suggestions from code review Co-authored-by: Malte Kliemann * Use workspace for impl-trait-for-tuples * Add on_activation and on_resolution weight to parimutuel * Fix typo * Prepare OnStateTransition tests * Add OnStateTransition tests * Update zrml/parimutuel/src/lib.rs Co-authored-by: Chralt --------- Co-authored-by: Malte Kliemann Co-authored-by: Chralt --- Cargo.lock | 3 + Cargo.toml | 1 + primitives/Cargo.toml | 2 +- runtime/battery-station/src/lib.rs | 21 ++ runtime/common/src/lib.rs | 10 +- runtime/zeitgeist/src/lib.rs | 21 ++ zrml/market-commons/src/lib.rs | 13 +- zrml/market-commons/src/tests.rs | 11 +- zrml/parimutuel/Cargo.toml | 5 + zrml/parimutuel/src/benchmarking.rs | 81 +++++- zrml/parimutuel/src/lib.rs | 252 +++++++++++++++--- zrml/parimutuel/src/mock.rs | 186 +++++++++++-- zrml/parimutuel/src/tests/assets.rs | 199 ++++++++++++++ zrml/parimutuel/src/tests/buy.rs | 67 ++--- zrml/parimutuel/src/tests/claim.rs | 103 ++++--- zrml/parimutuel/src/tests/mod.rs | 1 + zrml/parimutuel/src/tests/refund.rs | 51 ++-- zrml/parimutuel/src/utils.rs | 4 +- zrml/parimutuel/src/weights.rs | 35 +++ zrml/prediction-markets/Cargo.toml | 5 +- .../fuzz/pm_full_workflow.rs | 4 +- zrml/prediction-markets/src/benchmarks.rs | 18 +- zrml/prediction-markets/src/lib.rs | 195 +++++++++----- zrml/prediction-markets/src/mock.rs | 76 +++++- .../src/tests/admin_move_market_to_closed.rs | 10 +- .../tests/admin_move_market_to_resolved.rs | 11 +- .../src/tests/approve_market.rs | 28 +- .../src/tests/buy_complete_set.rs | 32 ++- ...lose_trusted_market.rs => close_market.rs} | 21 +- .../src/tests/create_market.rs | 74 +++-- .../tests/create_market_and_deploy_pool.rs | 2 +- zrml/prediction-markets/src/tests/dispute.rs | 45 +++- .../src/tests/dispute_early_close.rs | 16 +- .../src/tests/edit_market.rs | 18 +- .../src/tests/integration.rs | 74 ++--- .../src/tests/manually_close_market.rs | 8 +- zrml/prediction-markets/src/tests/mod.rs | 47 +++- .../src/tests/on_initialize.rs | 2 +- .../src/tests/on_market_close.rs | 30 ++- .../src/tests/on_resolution.rs | 146 +++++++--- .../src/tests/redeem_shares.rs | 57 ++-- .../src/tests/reject_early_close.rs | 10 +- .../src/tests/reject_market.rs | 25 +- zrml/prediction-markets/src/tests/report.rs | 66 ++--- .../src/tests/request_edit.rs | 6 +- .../src/tests/schedule_early_close.rs | 12 +- .../src/tests/sell_complete_set.rs | 35 ++- .../src/tests/start_global_dispute.rs | 2 +- 48 files changed, 1587 insertions(+), 554 deletions(-) create mode 100644 zrml/parimutuel/src/tests/assets.rs rename zrml/prediction-markets/src/tests/{close_trusted_market.rs => close_market.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index 66be058e2..b5f6d5206 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14753,6 +14753,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-timestamp", "parity-scale-codec", @@ -14760,7 +14761,9 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", + "zrml-asset-router", "zrml-market-commons", ] diff --git a/Cargo.toml b/Cargo.toml index ed99cccaa..d8f33423d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -265,6 +265,7 @@ url = "2.2.2" arbitrary = { version = "1.3.0", default-features = false } arrayvec = { version = "0.7.4", default-features = false } cfg-if = { version = "1.0.0" } +impl-trait-for-tuples = { version = "0.2.2" } 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 = "v21.1.1", default-features = false } diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 0d28af56c..6befd4ee1 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -3,7 +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" +impl-trait-for-tuples = { workspace = true } num-traits = { workspace = true } orml-currencies = { workspace = true } orml-tokens = { workspace = true } 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/common/src/lib.rs b/runtime/common/src/lib.rs index ba4cf89c0..d23e3c007 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1255,6 +1255,8 @@ macro_rules! impl_config_traits { impl zrml_prediction_markets::Config for Runtime { type AdvisoryBond = AdvisoryBond; type AdvisoryBondSlashPercentage = AdvisoryBondSlashPercentage; + type AssetCreator = AssetRouter; + type AssetDestroyer = AssetRouter; type ApproveOrigin = EnsureRootOrMoreThanOneThirdAdvisoryCommittee; type AssetManager = AssetManager; #[cfg(feature = "parachain")] @@ -1289,6 +1291,8 @@ macro_rules! impl_config_traits { type MinCategories = MinCategories; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; + // Can be a tuple of hooks + type OnStateTransition = (Parimutuel,); type OracleBond = OracleBond; type OutsiderBond = OutsiderBond; type PalletId = PmPalletId; @@ -1395,12 +1399,14 @@ macro_rules! impl_config_traits { } impl zrml_parimutuel::Config for Runtime { + type AssetCreator = AssetRouter; + type AssetDestroyer = AssetRouter; + type AssetManager = AssetManager; type ExternalFees = MarketCreatorFee; - type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; - type AssetManager = AssetManager; type MinBetSize = MinBetSize; type PalletId = ParimutuelPalletId; + type RuntimeEvent = RuntimeEvent; type WeightInfo = zrml_parimutuel::weights::WeightInfo; } }; diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index bd24a80b3..c89fcecb0 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, 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/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index b9817d223..254a220e4 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -27,14 +27,14 @@ use sp_runtime::{DispatchError, Perbill}; use zeitgeist_primitives::{ traits::MarketCommonsPalletApi, types::{ - AccountIdTest, Asset, Balance, BlockNumber, Deadlines, Market, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, + AccountIdTest, Balance, BaseAsset, BlockNumber, Deadlines, Market, MarketBonds, + MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, Moment, ScoringRule, }, }; -const MARKET_DUMMY: Market> = Market { - base_asset: Asset::Ztg, +const MARKET_DUMMY: Market = Market { + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), creator: 0, @@ -346,8 +346,7 @@ fn market_counter_interacts_correctly_with_push_market_and_remove_market() { fn market_mock( id: AccountIdTest, -) -> zeitgeist_primitives::types::Market> -{ +) -> zeitgeist_primitives::types::Market { let mut market = MARKET_DUMMY; market.oracle = id; market diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index 52e1b0d8a..bddb5f38f 100644 --- a/zrml/parimutuel/Cargo.toml +++ b/zrml/parimutuel/Cargo.toml @@ -3,9 +3,11 @@ 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 } +zeitgeist-macros = { workspace = true } zeitgeist-primitives = { workspace = true } zrml-market-commons = { workspace = true } @@ -17,6 +19,7 @@ pallet-balances = { workspace = true, features = ["default"] } pallet-timestamp = { workspace = true, features = ["default"] } sp-io = { workspace = true, features = ["default"] } zeitgeist-primitives = { workspace = true, features = ["mock", "default"] } +zrml-asset-router = { workspace = true, features = ["default"] } test-case = { workspace = true } @@ -26,12 +29,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/benchmarking.rs b/zrml/parimutuel/src/benchmarking.rs index 2aec79bbb..1962d31a5 100644 --- a/zrml/parimutuel/src/benchmarking.rs +++ b/zrml/parimutuel/src/benchmarking.rs @@ -23,11 +23,17 @@ use crate::{utils::*, Pallet as Parimutuel, *}; use frame_benchmarking::v2::*; -use frame_support::traits::Get; +use frame_support::{ + assert_ok, + traits::{fungibles::Inspect, Get}, +}; use frame_system::RawOrigin; use orml_traits::MultiCurrency; use sp_runtime::{SaturatedConversion, Saturating}; -use zeitgeist_primitives::types::{Asset, MarketStatus, MarketType, OutcomeReport}; +use zeitgeist_primitives::{ + traits::MarketTransitionApi, + types::{MarketStatus, MarketType, OutcomeReport}, +}; use zrml_market_commons::MarketCommonsPalletApi; fn setup_market(market_type: MarketType) -> MarketIdOf { @@ -42,12 +48,12 @@ fn setup_market(market_type: MarketType) -> MarketIdOf { fn buy_asset( market_id: MarketIdOf, - asset: AssetOf, + asset: ParimutuelShareOf, buyer: &T::AccountId, 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(); } @@ -62,10 +68,10 @@ mod benchmarks { let market_id = setup_market::(MarketType::Categorical(64u16)); let amount = T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); - let asset = Asset::ParimutuelShare(market_id, 0u16); + let asset = ParimutuelShareOf::::Share(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); @@ -75,15 +81,16 @@ mod benchmarks { fn claim_rewards() { // max category index is worst case let market_id = setup_market::(MarketType::Categorical(64u16)); + assert_ok!(Parimutuel::::on_activation(&market_id).result); let winner = whitelisted_caller(); - let winner_asset = Asset::ParimutuelShare(market_id, 0u16); + let winner_asset = ParimutuelShareOf::::Share(market_id, 0u16); 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_asset = ParimutuelShareOf::::Share(market_id, 1u16); let loser_amount = T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); buy_asset::(market_id, loser_asset, &loser, loser_amount); @@ -102,17 +109,18 @@ mod benchmarks { fn claim_refunds() { // max category index is worst case let market_id = setup_market::(MarketType::Categorical(64u16)); + assert_ok!(Parimutuel::::on_activation(&market_id).result); let loser_0 = whitelisted_caller(); let loser_0_index = 0u16; - let loser_0_asset = Asset::ParimutuelShare(market_id, loser_0_index); + let loser_0_asset = ParimutuelShareOf::::Share(market_id, loser_0_index); 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_asset = ParimutuelShareOf::::Share(market_id, loser_1_index); let loser_1_amount = T::MinBetSize::get().saturating_mul(10u128.saturated_into::>()); buy_asset::(market_id, loser_1_asset, &loser_1, loser_1_amount); @@ -123,8 +131,8 @@ mod benchmarks { let resolved_outcome = OutcomeReport::Categorical(resolved_index); assert_ne!(resolved_index, loser_0_index); assert_ne!(resolved_index, loser_1_index); - let resolved_asset = Asset::ParimutuelShare(market_id, resolved_index); - let resolved_issuance_asset = T::AssetManager::total_issuance(resolved_asset); + let resolved_asset = ParimutuelShareOf::::Share(market_id, resolved_index); + let resolved_issuance_asset = T::AssetManager::total_issuance(resolved_asset.into()); assert!(resolved_issuance_asset.is_zero()); market.resolved_outcome = Some(resolved_outcome); Ok(()) @@ -134,6 +142,55 @@ mod benchmarks { claim_refunds(RawOrigin::Signed(loser_0), loser_0_asset); } + #[benchmark] + fn on_activation() { + let market_id = setup_market::(MarketType::Categorical(64u16)); + + #[block] + { + Parimutuel::::on_activation(&market_id); + } + + for asset_idx in 0..64 { + let asset = ParimutuelShareOf::::Share(Zero::zero(), asset_idx).into(); + assert!(T::AssetCreator::asset_exists(asset)); + } + } + + #[benchmark] + fn on_resolution() { + let market_id = setup_market::(MarketType::Categorical(64u16)); + assert_ok!(Parimutuel::::on_activation(&market_id).result); + + for asset_idx in 0..64 { + let asset = ParimutuelShareOf::::Share(Zero::zero(), asset_idx).into(); + assert!(T::AssetCreator::asset_exists(asset)); + } + + T::MarketCommons::mutate_market(&market_id, |market| { + market.status = MarketStatus::Resolved; + let resolved_outcome = OutcomeReport::Categorical(0u16); + market.resolved_outcome = Some(resolved_outcome); + Ok(()) + })?; + + #[block] + { + Parimutuel::::on_resolution(&market_id); + } + + #[cfg(test)] + { + use frame_support::{pallet_prelude::Weight, traits::OnIdle}; + + crate::mock::AssetRouter::on_idle(Zero::zero(), Weight::MAX); + for asset_idx in 0..64 { + let asset = ParimutuelShareOf::::Share(Zero::zero(), asset_idx).into(); + assert!(!T::AssetCreator::asset_exists(asset)); + } + } + } + impl_benchmark_test_suite!( Parimutuel, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 3bbcbd441..098e07460 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,16 @@ 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, Inspect}, + Get, IsType, StorageVersion, + }, PalletId, RuntimeDebug, }; use frame_system::{ @@ -42,25 +48,36 @@ mod pallet { pallet_prelude::{BlockNumberFor, OriginFor}, }; use orml_traits::MultiCurrency; + use pallet_assets::ManagedDestroy; use sp_runtime::{ traits::{AccountIdConversion, CheckedSub, Zero}, DispatchResult, }; + use zeitgeist_macros::unreachable_non_terminating; use zeitgeist_primitives::{ math::fixed::FixedMulDiv, - traits::DistributeFees, - types::{Asset, Market, MarketStatus, MarketType, OutcomeReport, ScoringRule}, + traits::{DistributeFees, MarketTransitionApi}, + types::{ + Asset, BaseAsset, Market, MarketAssetClass, MarketStatus, MarketType, OutcomeReport, + ParimutuelAssetClass, ResultWithWeightInfo, 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 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 = Asset>, + Asset = AssetOf, AccountId = AccountIdOf, Balance = BalanceOf, MarketId = MarketIdOf, @@ -91,6 +108,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; @@ -98,7 +116,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)] @@ -140,38 +158,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), } @@ -201,7 +232,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)?; @@ -236,7 +267,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)?; @@ -291,29 +325,36 @@ 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); + Ok(()) } + MarketType::Scalar(_) => 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; 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,23 +365,23 @@ 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!( 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)?; - T::AssetManager::deposit(asset, &who, amount_minus_fees)?; + T::AssetManager::transfer(base_asset.into(), &who, &pot_account, 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, }); @@ -396,7 +437,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 +446,27 @@ 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, + )?; + + if outcome_total == winning_balance { + let destroy_result = T::AssetDestroyer::managed_destroy(winning_asset, None); + unreachable_non_terminating!( + destroy_result.is_ok(), + LOG_TARGET, + "Can't destroy winning outcome asset {:?}: {:?}", + winning_asset, + destroy_result.err() + ); + } Self::deposit_event(Event::RewardsClaimed { market_id, @@ -422,10 +480,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)?; @@ -434,9 +494,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 \ @@ -446,10 +507,10 @@ 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, &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,16 +520,129 @@ 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, + )?; + + if T::AssetCreator::total_issuance(refund_asset_general).is_zero() { + let destroy_result = T::AssetDestroyer::managed_destroy(refund_asset_general, None); + unreachable_non_terminating!( + destroy_result.is_ok(), + LOG_TARGET, + "Can't destroy losing outcome asset {:?}: {:?}", + refund_asset_general, + destroy_result + ); + } Self::deposit_event(Event::BalanceRefunded { market_id, - asset: refund_asset, + asset: refund_asset_general, refunded_balance: refund_balance, sender: who.clone(), }); Ok(()) } + + fn get_assets_to_destroy( + market_id: &MarketIdOf, + market: &MarketOf, + filter: F, + ) -> BTreeMap, Option> + where + F: Copy + FnOnce(MarketAssetClass>) -> bool, + { + BTreeMap::, Option>::from_iter( + market + .outcome_assets(*market_id) + .into_iter() + .filter(|asset| filter(*asset)) + .map(|asset| (AssetOf::::from(asset), None)), + ) + } + } + + impl MarketTransitionApi> for Pallet { + fn on_activation(market_id: &MarketIdOf) -> ResultWithWeightInfo { + let market_result = T::MarketCommons::market(market_id); + + let market = match market_result { + Ok(market) if market.scoring_rule == ScoringRule::Parimutuel => market, + Err(e) => { + return ResultWithWeightInfo::new(Err(e), T::DbWeight::get().reads(1)); + } + _ => { + return ResultWithWeightInfo::new(Ok(()), T::DbWeight::get().reads(1)); + } + }; + + for outcome in market.outcome_assets(*market_id) { + let admin = Self::pot_account(*market_id); + let is_sufficient = true; + let min_balance = 1u8; + if let Err(e) = T::AssetCreator::create( + outcome.into(), + admin, + is_sufficient, + min_balance.into(), + ) { + return ResultWithWeightInfo::new(Err(e), T::WeightInfo::on_activation()); + } + } + + ResultWithWeightInfo::new(Ok(()), T::WeightInfo::on_activation()) + } + + fn on_resolution(market_id: &MarketIdOf) -> ResultWithWeightInfo { + let market_result = T::MarketCommons::market(market_id); + + let market = match market_result { + Ok(market) if market.scoring_rule == ScoringRule::Parimutuel => market, + Err(e) => { + return ResultWithWeightInfo::new(Err(e), T::DbWeight::get().reads(1)); + } + _ => { + return ResultWithWeightInfo::new(Ok(()), T::DbWeight::get().reads(1)); + } + }; + + let winning_asset_option = market.resolved_outcome_into_asset(*market_id); + let winning_asset = if let Some(winning_asset) = winning_asset_option { + winning_asset + } else { + unreachable_non_terminating!( + winning_asset_option.is_some(), + LOG_TARGET, + "Resolved market with id {:?} does not have a resolved outcome", + market_id, + ); + return ResultWithWeightInfo::new(Ok(()), T::DbWeight::get().reads(1)); + }; + + let outcome_total = T::AssetManager::total_issuance(winning_asset.into()); + let assets_to_destroy = if outcome_total.is_zero() { + Self::get_assets_to_destroy(market_id, &market, |asset| { + T::AssetCreator::total_issuance(asset.into()).is_zero() + }) + } else { + Self::get_assets_to_destroy(market_id, &market, |asset| asset != winning_asset) + }; + + let destroy_result = + T::AssetDestroyer::managed_destroy_multi(assets_to_destroy.clone()); + unreachable_non_terminating!( + destroy_result.is_ok(), + LOG_TARGET, + "Can't destroy losing outcome assets {:?}: {:?}", + assets_to_destroy, + destroy_result + ); + + ResultWithWeightInfo::new(Ok(()), T::WeightInfo::on_resolution()) + } } } diff --git a/zrml/parimutuel/src/mock.rs b/zrml/parimutuel/src/mock.rs index 9468a4187..20751b709 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,27 @@ 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 AssetCreator = AssetRouter; + 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 +152,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 +264,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 +274,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 +285,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/assets.rs b/zrml/parimutuel/src/tests/assets.rs new file mode 100644 index 000000000..dc3ab9531 --- /dev/null +++ b/zrml/parimutuel/src/tests/assets.rs @@ -0,0 +1,199 @@ +// 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 crate::{mock::*, utils::*, *}; +use frame_support::{ + assert_ok, + pallet_prelude::Weight, + traits::{ + fungibles::{Create, Inspect}, + OnIdle, + }, +}; +use zeitgeist_primitives::{ + traits::MarketTransitionApi, + types::{Asset, MarketStatus, OutcomeReport, ParimutuelAsset}, +}; +use zrml_market_commons::Markets; + +use frame_support::{ + pallet_prelude::DispatchError, + storage::{with_transaction, TransactionOutcome}, +}; + +#[test] +fn created_after_market_activation() { + 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()); + assert_ok!(Parimutuel::on_activation(&market_id).result); + for asset in market.outcome_assets(market_id) { + assert!(::AssetCreator::asset_exists(asset.into())); + } + }); +} + +#[test] +fn destroyed_after_claim() { + 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)); + ::AssetDestroyer::on_idle(System::block_number(), Weight::MAX); + assert!(!AssetRouter::asset_exists(winner_asset.into())); + }); +} + +#[test] +fn destroyed_losing_after_resolution_with_winner() { + 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); + assert_ok!(Parimutuel::on_activation(&market_id).result); + + 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 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::on_resolution(&market_id).result); + ::AssetDestroyer::on_idle(System::block_number(), Weight::MAX); + assert!(::AssetCreator::asset_exists(winner_asset.into())); + + for asset in market + .outcome_assets(market_id) + .iter() + .filter(|a| Asset::from(**a) != Asset::from(winner_asset)) + { + assert!( + !::AssetCreator::asset_exists((*asset).into()), + "Asset {:?} still exists after destruction", + asset + ); + } + }); +} + +#[test] +#[should_panic(expected = "Resolved market with id 0 does not have a resolved outcome")] +fn no_resolved_outcome_is_catched() { + 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 = None; + Markets::::insert(market_id, market.clone()); + + let _ = with_transaction(|| { + let _ = Parimutuel::on_resolution(&market_id); + TransactionOutcome::Commit(Ok::<(), DispatchError>(())) + }); + }); +} + +#[test] +fn destroyed_after_resolution_without_winner() { + 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); + assert_ok!(Parimutuel::on_activation(&market_id).result); + + let losing_asset = ParimutuelAsset::Share(market_id, 1u16); + let losing_amount = 20 * ::MinBetSize::get(); + assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), losing_asset, losing_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::on_resolution(&market_id).result); + ::AssetDestroyer::on_idle(System::block_number(), Weight::MAX); + assert!(::AssetCreator::asset_exists(losing_asset.into())); + + for asset in market + .outcome_assets(market_id) + .iter() + .filter(|a| Asset::from(**a) != Asset::from(losing_asset)) + { + assert!( + !::AssetCreator::asset_exists((*asset).into()), + "Asset {:?} still exists after destruction", + asset + ); + } + }); +} + +#[test] +fn destroyed_after_refund() { + 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); + assert_ok!(Parimutuel::on_activation(&market_id).result); + + let losing_asset = ParimutuelAsset::Share(market_id, 1u16); + let losing_amount = 20 * ::MinBetSize::get(); + assert_ok!(Parimutuel::buy(RuntimeOrigin::signed(ALICE), losing_asset, losing_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::on_resolution(&market_id).result); + + ::AssetDestroyer::on_idle(System::block_number(), Weight::MAX); + assert!(::AssetCreator::asset_exists(losing_asset.into())); + assert_ok!(Parimutuel::claim_refunds(RuntimeOrigin::signed(ALICE), losing_asset)); + ::AssetDestroyer::on_idle(System::block_number(), Weight::MAX); + assert!(!::AssetCreator::asset_exists(losing_asset.into())); + }); +} diff --git a/zrml/parimutuel/src/tests/buy.rs b/zrml/parimutuel/src/tests/buy.rs index 1ca6b13b5..52dc03e9e 100644 --- a/zrml/parimutuel/src/tests/buy.rs +++ b/zrml/parimutuel/src/tests/buy.rs @@ -15,14 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -#![cfg(test)] - use crate::{mock::*, utils::*, *}; 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 +31,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 +40,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 +62,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 +75,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 +103,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 +125,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 +144,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 +162,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 +182,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 +196,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..adb3d9181 100644 --- a/zrml/parimutuel/src/tests/claim.rs +++ b/zrml/parimutuel/src/tests/claim.rs @@ -15,15 +15,16 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -#![cfg(test)] - use crate::{mock::*, utils::*, *}; use core::ops::RangeInclusive; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::fungibles::Create}; 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::{ + traits::MarketTransitionApi, + types::{MarketStatus, MarketType, OutcomeReport, ParimutuelAsset, ScoringRule}, +}; use zrml_market_commons::{Error as MError, Markets}; #[test] @@ -34,11 +35,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 +57,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 +75,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,52 +106,66 @@ 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 ); }); @@ -245,11 +262,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 +292,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)); @@ -304,12 +321,13 @@ fn claim_refunds_works() { market.market_type = MarketType::Categorical(10u16); market.status = MarketStatus::Active; Markets::::insert(market_id, market); + assert_ok!(Parimutuel::on_activation(&market_id).result); - 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 +348,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/mod.rs b/zrml/parimutuel/src/tests/mod.rs index e11635e0d..f3b72e27e 100644 --- a/zrml/parimutuel/src/tests/mod.rs +++ b/zrml/parimutuel/src/tests/mod.rs @@ -17,6 +17,7 @@ #![cfg(test)] +mod assets; mod buy; mod claim; mod refund; diff --git a/zrml/parimutuel/src/tests/refund.rs b/zrml/parimutuel/src/tests/refund.rs index 14e5c7a48..6397cf5c0 100644 --- a/zrml/parimutuel/src/tests/refund.rs +++ b/zrml/parimutuel/src/tests/refund.rs @@ -15,35 +15,16 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -#![cfg(test)] - 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::{ + traits::MarketTransitionApi, + 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 +38,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 +59,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 +77,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 +95,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 +112,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 +121,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 @@ -156,8 +137,9 @@ fn refund_fails_if_refundable_balance_is_zero() { market.market_type = MarketType::Categorical(10u16); market.status = MarketStatus::Active; Markets::::insert(market_id, market); + assert_ok!(Parimutuel::on_activation(&market_id).result); - 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 @@ -185,8 +167,9 @@ fn refund_emits_event() { market.market_type = MarketType::Categorical(10u16); market.status = MarketStatus::Active; Markets::::insert(market_id, market); + assert_ok!(Parimutuel::on_activation(&market_id).result); - 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 +178,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 +186,7 @@ fn refund_emits_event() { System::assert_last_event( Event::BalanceRefunded { market_id, - asset, + asset: asset.into(), refunded_balance: amount_minus_fees, sender: ALICE, } 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, diff --git a/zrml/parimutuel/src/weights.rs b/zrml/parimutuel/src/weights.rs index f803c5a48..95abcf850 100644 --- a/zrml/parimutuel/src/weights.rs +++ b/zrml/parimutuel/src/weights.rs @@ -52,6 +52,8 @@ pub trait WeightInfoZeitgeist { fn buy() -> Weight; fn claim_rewards() -> Weight; fn claim_refunds() -> Weight; + fn on_activation() -> Weight; + fn on_resolution() -> Weight; } /// Weight functions for zrml_parimutuel (automatically generated) @@ -114,4 +116,37 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } + + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(676), added: 3151, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:64 w:64) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) + fn on_activation() -> Weight { + // Proof Size summary in bytes: + // Measured: `1187` + // Estimated: `175951` + // Minimum execution time: 159_784 nanoseconds. + Weight::from_parts(161_183_000, 175951) + .saturating_add(T::DbWeight::get().reads(65)) + .saturating_add(T::DbWeight::get().writes(64)) + } + /// Storage: MarketCommons Markets (r:1 w:0) + /// Proof: MarketCommons Markets (max_values: None, max_size: Some(676), added: 3151, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:64 w:64) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) + /// Storage: AssetRouter DestroyAssets (r:1 w:1) + /// Proof: AssetRouter DestroyAssets (max_values: Some(1), max_size: Some(40962), added: 41457, mode: MaxEncodedLen) + /// Storage: Tokens TotalIssuance (r:64 w:0) + /// Proof: Tokens TotalIssuance (max_values: None, max_size: Some(43), added: 2518, mode: MaxEncodedLen) + /// Storage: AssetRouter IndestructibleAssets (r:1 w:0) + /// Proof: AssetRouter IndestructibleAssets (max_values: Some(1), max_size: Some(38914), added: 39409, mode: MaxEncodedLen) + fn on_resolution() -> Weight { + // Proof Size summary in bytes: + // Measured: `18377` + // Estimated: `417969` + // Minimum execution time: 367_119 nanoseconds. + Weight::from_parts(370_038_000, 417969) + .saturating_add(T::DbWeight::get().reads(131)) + .saturating_add(T::DbWeight::get().writes(65)) + } } 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/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..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, 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; @@ -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 a573a78fa..9e6d729c6 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,17 +44,20 @@ mod pallet { require_transactional, storage::{with_transaction, TransactionOutcome}, traits::{ - 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, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; + use pallet_assets::ManagedDestroy; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "parachain")] use { - orml_traits::asset_registry::Inspect, + orml_traits::asset_registry::Inspect as RegistryInspect, zeitgeist_primitives::types::{CurrencyClass, CustomMetadata}, }; @@ -68,13 +71,13 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, + DisputeResolutionApi, MarketTransitionApi, }, 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}; @@ -89,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; @@ -103,7 +105,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - AssetOf, + BaseAsset, >; pub(crate) type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; @@ -443,11 +445,27 @@ mod pallet { Error::::MarketEditRequestAlreadyInProgress ); m.status = new_status; + + if m.is_redeemable() { + for outcome in m.outcome_assets(market_id) { + let admin = Self::market_account(market_id); + let is_sufficient = true; + let min_balance = 1u8; + T::AssetCreator::create( + outcome.into(), + admin, + is_sufficient, + min_balance.into(), + )?; + } + } + Ok(()) })?; Self::unreserve_creation_bond(&market_id)?; + T::OnStateTransition::on_activation(&market_id).result?; Self::deposit_event(Event::MarketApproved(market_id, new_status)); // The ApproveOrigin should not pay fees for providing this service let default_weight: Option = None; @@ -523,7 +541,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()) } @@ -581,6 +599,7 @@ mod pallet { Ok(()) })?; + T::OnStateTransition::on_dispute(&market_id).result?; Self::deposit_event(Event::MarketDisputed(market_id, MarketStatus::Disputed, who)); Ok((Some(weight)).into()) } @@ -596,7 +615,7 @@ mod pallet { #[transactional] pub fn create_market( origin: OriginFor, - base_asset: AssetOf, + base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, period: MarketPeriod>, @@ -648,7 +667,7 @@ mod pallet { #[transactional] pub fn edit_market( origin: OriginFor, - base_asset: AssetOf, + base_asset: BaseAsset, market_id: MarketIdOf, oracle: T::AccountId, period: MarketPeriod>, @@ -734,7 +753,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, ); @@ -788,7 +807,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, ); @@ -814,18 +833,25 @@ 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, )?; + // 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, @@ -936,7 +962,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()) } @@ -1034,6 +1060,7 @@ mod pallet { m.status = MarketStatus::Disputed; Ok(()) })?; + T::OnStateTransition::on_dispute(&market_id).result?; } // global disputes uses DisputeResolution API to control its resolution @@ -1061,7 +1088,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>, @@ -1518,7 +1545,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, @@ -1528,7 +1561,7 @@ mod pallet { >; #[cfg(feature = "parachain")] - type AssetRegistry: Inspect< + type AssetRegistry: RegistryInspect< AssetId = CurrencyClass>, Balance = BalanceOf, CustomMetadata = CustomMetadata, @@ -1693,6 +1726,9 @@ mod pallet { /// The origin that is allowed to reject pending advised markets. type RejectOrigin: EnsureOrigin; + /// Additional handler during state transitions. + type OnStateTransition: MarketTransitionApi>; + /// The base amount of currency that must be bonded to ensure the oracle reports /// in a timely manner. #[pallet::constant] @@ -1911,7 +1947,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(); @@ -2084,7 +2119,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>, @@ -2134,32 +2169,33 @@ mod pallet { let market_id = >::push_market(market.clone())?; let market_account = Self::market_account(market_id); + match market.status { + MarketStatus::Active => { + if market.is_redeemable() { + 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.into(), + admin, + is_sufficient, + min_balance.into(), + )?; + } + } - let ids_amount: u32 = Self::insert_auto_close(&market_id)?; + T::OnStateTransition::on_activation(&market_id).result? + } + MarketStatus::Proposed => T::OnStateTransition::on_proposal(&market_id).result?, + _ => (), + } + let ids_amount: u32 = Self::insert_auto_close(&market_id)?; Self::deposit_event(Event::MarketCreated(market_id, market_account, market)); - 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)?; @@ -2274,25 +2310,25 @@ 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.", ); - 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: {:?}.", @@ -2302,7 +2338,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)); @@ -2318,18 +2354,18 @@ 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); + 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)); @@ -2514,9 +2550,12 @@ mod pallet { market.status = MarketStatus::Closed; Ok(()) })?; - let mut total_weight = T::DbWeight::get().reads_writes(1, 1); + + let mut total_weight = T::DbWeight::get().reads_writes(1, 2); + let on_state_transition_result = T::OnStateTransition::on_closure(market_id); + on_state_transition_result.result?; + total_weight = total_weight.saturating_add(on_state_transition_result.weight); Self::deposit_event(Event::MarketClosed(*market_id)); - total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); Ok(total_weight) } @@ -2761,20 +2800,44 @@ 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. + 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(()) })?; + let winning_outcome = updated_market.resolved_outcome_into_asset(*market_id); + if market.is_redeemable() { + 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), 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); + } + } + + let on_state_transition_result = T::OnStateTransition::on_resolution(market_id); + on_state_transition_result.result?; + total_weight = total_weight.saturating_add(on_state_transition_result.weight); + total_weight = + total_weight.saturating_add(Self::calculate_internal_resolve_weight(market)); + Self::deposit_event(Event::MarketResolved( *market_id, MarketStatus::Resolved, resolved_outcome, )); - Ok(total_weight.saturating_add(Self::calculate_internal_resolve_weight(market))) + Ok(total_weight) } /// The reserve ID of the prediction-markets pallet. @@ -2877,7 +2940,7 @@ mod pallet { } fn construct_market( - base_asset: AssetOf, + base_asset: BaseAsset, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, @@ -2893,12 +2956,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 @@ -2906,7 +2972,8 @@ mod pallet { return Err(Error::::UnregisteredForeignAsset.into()); } } - _ => false, + #[cfg(not(feature = "parachain"))] + BaseAsset::ForeignAsset(_) => false, }; ensure!(creator_fee <= T::MaxCreatorFee::get(), Error::::FeeTooHigh); @@ -3010,6 +3077,7 @@ mod pallet { Ok(()) })?; + T::OnStateTransition::on_report(&market_id).result?; let market = >::market(&market_id)?; let block_after_dispute_duration = report.at.saturating_add(market.deadlines.dispute_duration); @@ -3038,6 +3106,7 @@ mod pallet { market.status = MarketStatus::Reported; Ok(()) })?; + T::OnStateTransition::on_report(&market_id).result?; let market = >::market(&market_id)?; Self::on_resolution(&market_id, &market)?; Ok(Some(T::WeightInfo::report_trusted_market()).into()) diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index 2c70692e1..d30a2a962 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -24,8 +24,14 @@ 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, + storage::unhashed::put, + traits::{ + AsEnsureOriginWithArg, Everything, GenesisBuild, NeverEnsureOrigin, OnFinalize, OnIdle, + OnInitialize, + }, }; use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; #[cfg(feature = "parachain")] @@ -57,14 +63,21 @@ use zeitgeist_primitives::{ OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, SimpleDisputesPalletId, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, MILLISECS_PER_BLOCK, }, - traits::DeployPoolApi, + traits::{DeployPoolApi, MarketTransitionApi}, 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, ResultWithWeightInfo, UncheckedExtrinsicTest, }, }; +pub(super) const ON_PROPOSAL_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x00]; +pub(super) const ON_ACTIVATION_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x01]; +pub(super) const ON_CLOSURE_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x02]; +pub(super) const ON_REPORT_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x03]; +pub(super) const ON_DISPUTE_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x04]; +pub(super) const ON_RESOLUTION_STORAGE: [u8; 4] = [0x09, 0x09, 0x00, 0x05]; + pub const ALICE: AccountIdTest = 0; pub const BOB: AccountIdTest = 1; pub const CHARLIE: AccountIdTest = 2; @@ -88,6 +101,36 @@ pub struct DeployPoolArgs { swap_fee: Balance, } +// It just writes true to specific memory locations depending on the hook that's invoked. +pub struct StateTransitionMock; + +impl MarketTransitionApi for StateTransitionMock { + fn on_proposal(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_PROPOSAL_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_activation(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_ACTIVATION_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_closure(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_CLOSURE_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_report(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_REPORT_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_dispute(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_DISPUTE_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } + fn on_resolution(_market_id: &MarketId) -> ResultWithWeightInfo { + put(&ON_RESOLUTION_STORAGE, &true); + ResultWithWeightInfo::new(Ok(()), Weight::zero()) + } +} + thread_local! { pub static DEPLOY_POOL_CALL_DATA: RefCell> = RefCell::new(vec![]); pub static DEPLOY_POOL_RETURN_VALUE: RefCell = RefCell::new(Ok(())); @@ -192,6 +235,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; @@ -221,6 +267,7 @@ impl crate::Config for Runtime { type MinCategories = MinCategories; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; + type OnStateTransition = (StateTransitionMock,); type OracleBond = OracleBond; type OutsiderBond = OutsiderBond; type PalletId = PmPalletId; @@ -229,7 +276,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; @@ -563,11 +609,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) @@ -576,11 +633,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![ @@ -628,6 +687,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/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..c2bf82081 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,28 @@ 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)); + }); +} + +#[test] +fn does_trigger_market_transition_api() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + simple_create_categorical_market( + BaseAsset::Ztg, + MarketCreation::Advised, + 1..2, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::approve_market(RuntimeOrigin::signed(SUDO), 0)); + assert!(StateTransitionMock::on_activation_triggered()); }); } diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 73ac62752..8a5ee2140 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, @@ -42,26 +42,29 @@ 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); } - 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_market.rs similarity index 90% rename from zrml/prediction-markets/src/tests/close_trusted_market.rs rename to zrml/prediction-markets/src/tests/close_market.rs index 8fcdcb2f9..9e666d01a 100644 --- a/zrml/prediction-markets/src/tests/close_trusted_market.rs +++ b/zrml/prediction-markets/src/tests/close_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), @@ -164,3 +164,18 @@ fn close_trusted_market_fails_if_invalid_market_state(status: MarketStatus) { ); }); } + +#[test] +fn does_trigger_market_transition_api_permissionless() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + simple_create_categorical_market( + BaseAsset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + assert_ok!(PredictionMarkets::close_market(&0)); + assert!(StateTransitionMock::on_closure_triggered()); + }); +} diff --git a/zrml/prediction-markets/src/tests/create_market.rs b/zrml/prediction-markets/src/tests/create_market.rs index 151ab1407..a59aa0fa6 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, @@ -618,3 +618,31 @@ fn create_scalar_market_deposits_the_correct_event() { System::assert_last_event(Event::MarketCreated(0, market_account, market).into()); }); } + +#[test] +fn does_trigger_market_transition_api() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + simple_create_categorical_market( + BaseAsset::Ztg, + MarketCreation::Advised, + 1..2, + ScoringRule::Lmsr, + ); + assert!(StateTransitionMock::on_proposal_triggered()); + }); +} + +#[test] +fn does_trigger_market_transition_api_permissionless() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + simple_create_categorical_market( + BaseAsset::Ztg, + MarketCreation::Permissionless, + 1..2, + ScoringRule::Lmsr, + ); + assert!(StateTransitionMock::on_activation_triggered()); + }); +} 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..b06000d78 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, @@ -309,3 +309,34 @@ fn dispute_fails_unless_reported_or_disputed_market(status: MarketStatus) { ); }); } + +#[test] +fn does_trigger_market_transition_api() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + let end = 2; + simple_create_categorical_market( + BaseAsset::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!(StateTransitionMock::on_dispute_triggered()); + }); +} 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..a303f6bc4 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, @@ -199,13 +202,13 @@ 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(); set_timestamp_for_on_initialize(100_000_000); let report_at = 2; @@ -259,13 +262,13 @@ 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); } // 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..e4d159553 100644 --- a/zrml/prediction-markets/src/tests/mod.rs +++ b/zrml/prediction-markets/src/tests/mod.rs @@ -22,7 +22,7 @@ mod admin_move_market_to_closed; mod admin_move_market_to_resolved; mod approve_market; mod buy_complete_set; -mod close_trusted_market; +mod close_market; mod create_market; mod create_market_and_deploy_pool; mod dispute; @@ -42,19 +42,19 @@ 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 frame_support::{ + assert_noop, assert_ok, storage::unhashed::get_or, 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, + Asset, BaseAsset, Deadlines, MarketCreation, MarketDisputeMechanism, MarketId, + MarketPeriod, MarketStatus, MarketType, MultiHash, OutcomeReport, ScoringRule, }, }; use zrml_court::types::VoteItem; @@ -62,6 +62,35 @@ use zrml_market_commons::MarketCommonsPalletApi; const SENTINEL_AMOUNT: u128 = BASE; +impl StateTransitionMock { + pub(super) fn on_proposal_triggered() -> bool { + get_or(&ON_PROPOSAL_STORAGE, false) + } + pub(super) fn on_activation_triggered() -> bool { + get_or(&ON_ACTIVATION_STORAGE, false) + } + pub(super) fn on_closure_triggered() -> bool { + get_or(&ON_CLOSURE_STORAGE, false) + } + pub(super) fn on_report_triggered() -> bool { + get_or(&ON_REPORT_STORAGE, false) + } + pub(super) fn on_dispute_triggered() -> bool { + get_or(&ON_DISPUTE_STORAGE, false) + } + pub(super) fn on_resolution_triggered() -> bool { + get_or(&ON_RESOLUTION_STORAGE, false) + } + pub(super) fn ensure_empty_state() { + assert!(!StateTransitionMock::on_proposal_triggered()); + assert!(!StateTransitionMock::on_activation_triggered()); + assert!(!StateTransitionMock::on_closure_triggered()); + assert!(!StateTransitionMock::on_report_triggered()); + assert!(!StateTransitionMock::on_dispute_triggered()); + assert!(!StateTransitionMock::on_resolution_triggered()); + } +} + fn get_deadlines() -> Deadlines<::BlockNumber> { Deadlines { grace_period: 1_u32.into(), @@ -78,7 +107,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 +128,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 77308d65a..4f640ed33 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, @@ -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); @@ -87,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, @@ -233,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; @@ -469,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)); }); } @@ -481,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( @@ -515,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)); }); } @@ -527,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( @@ -561,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)); }); } @@ -574,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( @@ -612,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)); }); } @@ -625,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( @@ -664,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)); }); } @@ -677,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( @@ -724,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)); }); } @@ -737,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( @@ -782,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)); }); } @@ -795,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( @@ -854,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)); }); } @@ -867,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( @@ -926,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)); }); } @@ -940,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), @@ -987,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( @@ -1021,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)); }); } @@ -1033,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( @@ -1092,10 +1124,42 @@ 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)); + }); +} + +#[test] +fn does_trigger_market_transition_api() { + ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + let end = 3; + assert_ok!(PredictionMarkets::create_market( + RuntimeOrigin::signed(ALICE), + BaseAsset::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, + )); + run_to_block(end); + let outcome = OutcomeReport::Categorical(1); + assert_ok!(PredictionMarkets::report(RuntimeOrigin::signed(BOB), 0, outcome.clone())); + assert!(StateTransitionMock::on_resolution_triggered()); }); } diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index d7542018a..50800a488 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,14 +179,15 @@ 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)); - 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/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..63d4df739 100644 --- a/zrml/prediction-markets/src/tests/report.rs +++ b/zrml/prediction-markets/src/tests/report.rs @@ -18,6 +18,7 @@ use super::*; +use test_case::test_case; use zeitgeist_primitives::{constants::MILLISECS_PER_BLOCK, types::OutcomeReport}; // TODO(#1239) MarketDoesNotExist @@ -32,7 +33,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 +79,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 +105,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 +147,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 +188,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 +211,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 +234,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 +257,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 +295,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 +318,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 +341,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 +364,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), @@ -385,35 +386,38 @@ fn report_fails_on_market_state_resolved() { }); } -#[test] -fn report_fails_if_reporter_is_not_the_oracle() { +#[test_case(Some(MarketDisputeMechanism::SimpleDisputes); "with_dispute_mechanism")] +#[test_case(None; "without_dispute_mechanism")] +fn does_trigger_market_transition_api(dispute_mechanism: Option) { ExtBuilder::default().build().execute_with(|| { + StateTransitionMock::ensure_empty_state(); + let mut deadlines = get_deadlines(); + + if dispute_mechanism.is_none() { + deadlines.dispute_duration = Zero::zero(); + } + assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(ALICE), - Asset::Ztg, + BaseAsset::Ztg, Perbill::zero(), BOB, - MarketPeriod::Timestamp(0..100_000_000), - get_deadlines(), + MarketPeriod::Block(0..2), + deadlines, gen_metadata(2), MarketCreation::Permissionless, MarketType::Categorical(2), - Some(MarketDisputeMechanism::SimpleDisputes), + dispute_mechanism, 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, - ); + run_to_block(2 + market.deadlines.grace_period + market.deadlines.oracle_duration + 1); + assert_ok!(PredictionMarkets::report( + RuntimeOrigin::signed(BOB), + 0, + OutcomeReport::Categorical(1) + )); + assert!(StateTransitionMock::on_report_triggered()); }); } 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..45e539ae7 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, @@ -50,23 +50,26 @@ 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); } - 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), From 3f37e361bdf662ca1a06d10dad478f19a41be747 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Fri, 22 Mar 2024 12:04:38 +0100 Subject: [PATCH 09/14] New Asset System - Integrate into remaining pallets (#1284) * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Integrate new asset system into remaining pallets * Update zrml/neo-swaps/src/tests/join.rs Co-authored-by: Malte Kliemann --------- Co-authored-by: Malte Kliemann --- Cargo.lock | 5 + zrml/authorized/src/lib.rs | 8 +- zrml/authorized/src/mock.rs | 4 +- zrml/liquidity-mining/src/tests.rs | 6 +- zrml/neo-swaps/Cargo.toml | 8 +- zrml/neo-swaps/src/benchmarking.rs | 2 +- zrml/neo-swaps/src/lib.rs | 6 +- zrml/neo-swaps/src/macros.rs | 6 +- zrml/neo-swaps/src/mock.rs | 168 +++++++++++++++++++++--- zrml/neo-swaps/src/tests/deploy_pool.rs | 52 ++++---- zrml/neo-swaps/src/tests/join.rs | 2 +- zrml/neo-swaps/src/tests/mod.rs | 16 +-- zrml/neo-swaps/src/tests/sell.rs | 2 +- zrml/simple-disputes/src/lib.rs | 6 +- zrml/simple-disputes/src/mock.rs | 4 +- zrml/simple-disputes/src/tests.rs | 4 +- zrml/swaps/src/lib.rs | 4 +- 17 files changed, 224 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5f6d5206..190de72c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14678,6 +14678,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-randomness-collective-flip", "pallet-timestamp", @@ -14694,6 +14695,7 @@ dependencies = [ "xcm", "xcm-builder", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -14719,6 +14721,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-timestamp", "parity-scale-codec", @@ -14726,7 +14729,9 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", + "zrml-asset-router", "zrml-market-commons", "zrml-orderbook", ] diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index e491fc75e..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::{ - Asset, AuthorityReport, GlobalDisputeItem, Market, MarketDisputeMechanism, + AuthorityReport, BaseAsset, 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/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/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, diff --git a/zrml/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 2ac64b8e1..73f25bf98 100644 --- a/zrml/neo-swaps/Cargo.toml +++ b/zrml/neo-swaps/Cargo.toml @@ -19,6 +19,7 @@ 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 } @@ -29,6 +30,7 @@ sp-api = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } xcm = { workspace = true, optional = true } xcm-builder = { workspace = true, optional = true } +zrml-asset-router = { workspace = true, optional = true } zrml-authorized = { workspace = true, optional = true } zrml-court = { workspace = true, optional = true } zrml-global-disputes = { workspace = true, optional = true } @@ -63,11 +65,13 @@ mock = [ "orml-asset-registry/default", "orml-currencies/default", "orml-tokens/default", + "pallet-assets/default", "pallet-balances/default", "pallet-timestamp/default", "sp-api/default", "sp-io/default", "zrml-court/std", + "zrml-asset-router/std", "zrml-authorized/std", "zrml-liquidity-mining/std", "zrml-simple-disputes/std", @@ -83,8 +87,10 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", + "pallet-assets?/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "zrml-prediction-markets/runtime-benchmarks", ] std = [ "frame-benchmarking?/std", diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index fde8b8c01..aaabd5465 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -181,7 +181,7 @@ where T: Config, { let market = Market { - base_asset, + base_asset: base_asset.try_into().unwrap(), creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), creator: caller.clone(), diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 6b17e91a1..331606fbc 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -871,7 +871,7 @@ mod pallet { let pool = Pool { account_id: pool_account_id.clone(), reserves: reserves.clone(), - collateral, + collateral: collateral.into(), liquidity_parameter, liquidity_shares_manager: LiquidityTree::new(who.clone(), amount)?, swap_fee, @@ -882,7 +882,7 @@ mod pallet { pool.collateral, &who, &pool.account_id, - T::MultiCurrency::minimum_balance(collateral), + T::MultiCurrency::minimum_balance(collateral.into()), )?; Pools::::insert(market_id, pool); Self::deposit_event(Event::::PoolDeployed { @@ -890,7 +890,7 @@ mod pallet { market_id, account_id: pool_account_id, reserves, - collateral, + collateral: collateral.into(), liquidity_parameter, pool_shares_amount: amount, swap_fee, diff --git a/zrml/neo-swaps/src/macros.rs b/zrml/neo-swaps/src/macros.rs index e14242861..231a5eca1 100644 --- a/zrml/neo-swaps/src/macros.rs +++ b/zrml/neo-swaps/src/macros.rs @@ -50,6 +50,7 @@ macro_rules! create_b_tree_map { /// their expected stake. /// - `total_fees`: The sum of all fees (both lazy and distributed) in the pool's liquidity tree. #[macro_export] +#[cfg(test)] macro_rules! assert_pool_state { ( $market_id:expr, @@ -106,6 +107,7 @@ macro_rules! assert_pool_state { /// Asserts that `account` has the specified `balances` of `assets`. #[macro_export] +#[cfg(test)] macro_rules! assert_balances { ($account:expr, $assets:expr, $balances:expr $(,)?) => { assert_eq!( @@ -114,7 +116,7 @@ macro_rules! assert_balances { "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); + let actual_balance = AssetManager::free_balance(asset.try_into().unwrap(), &$account); assert_eq!( actual_balance, expected_balance, "assert_balances: Balance mismatch for asset {:?}", @@ -126,6 +128,7 @@ macro_rules! assert_balances { /// Asserts that `account` has the specified `balance` of `asset`. #[macro_export] +#[cfg(test)] macro_rules! assert_balance { ($account:expr, $asset:expr, $balance:expr $(,)?) => { assert_balances!($account, [$asset], [$balance]); @@ -134,6 +137,7 @@ macro_rules! assert_balance { /// Asserts that `abs(left - right) < precision`. #[macro_export] +#[cfg(test)] macro_rules! assert_approx { ($left:expr, $right:expr, $precision:expr $(,)?) => { match (&$left, &$right, &$precision) { diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 49a4fe44a..eae7a1789 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -28,29 +28,31 @@ use crate::{consts::*, AssetOf, MarketIdOf}; use core::marker::PhantomData; use frame_support::{ construct_runtime, ord_parameter_types, parameter_types, - traits::{Contains, Everything, NeverEnsureOrigin}, + traits::{AsEnsureOriginWithArg, Contains, Everything, NeverEnsureOrigin}, }; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; #[cfg(feature = "parachain")] use orml_asset_registry::AssetMetadata; use orml_traits::MultiCurrency; +use parity_scale_codec::Compact; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Get, IdentityLookup, Zero}, + traits::{BlakeTwo256, ConstU32, Get, IdentityLookup, Zero}, DispatchResult, Percent, SaturatedConversion, }; -#[cfg(feature = "parachain")] -use zeitgeist_primitives::types::{Asset, Currencies}; use zeitgeist_primitives::{ constants::mock::{ - AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AuthorizedPalletId, - BlockHashCount, BlocksPerYear, CloseEarlyBlockPeriod, CloseEarlyDisputeBond, + AddOutcomePeriod, AggregationPeriod, AppealBond, AppealPeriod, AssetsAccountDeposit, + AssetsApprovalDeposit, AssetsDeposit, AssetsMetadataDepositBase, + AssetsMetadataDepositPerByte, AssetsStringLimit, AuthorizedPalletId, 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, + DestroyAccountWeight, DestroyApprovalWeight, DestroyFinishWeight, ExistentialDeposit, + ExistentialDepositsNew, 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, @@ -61,8 +63,9 @@ use zeitgeist_primitives::{ math::fixed::FixedMul, traits::{DeployPoolApi, 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, }, }; use zrml_neo_swaps::BalanceOf; @@ -78,7 +81,7 @@ pub const SUDO: AccountIdTest = 123456; pub const EXTERNAL_FEES: Balance = CENT; #[cfg(feature = "parachain")] -pub const FOREIGN_ASSET: Asset = Asset::ForeignAsset(1); +pub const FOREIGN_ASSET: Assets = Assets::ForeignAsset(1); parameter_types! { pub const FeeAccount: AccountIdTest = FEE_ACCOUNT; @@ -154,6 +157,10 @@ impl Contains for DustRemovalWhitelist { } } +pub(super) type CustomAssetsInstance = pallet_assets::Instance1; +pub(super) type CampaignAssetsInstance = pallet_assets::Instance2; +pub(super) type MarketAssetsInstance = pallet_assets::Instance3; + construct_runtime!( pub enum Runtime where @@ -162,11 +169,15 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { NeoSwaps: zrml_neo_swaps::{Call, Event, Pallet}, + AssetManager: orml_currencies::{Call, Pallet, Storage}, + AssetRouter: zrml_asset_router::{Pallet}, Authorized: zrml_authorized::{Event, Pallet, Storage}, Balances: pallet_balances::{Call, Config, Event, Pallet, Storage}, + CampaignAssets: pallet_assets::::{Call, Pallet, Storage, Event}, Court: zrml_court::{Event, Pallet, Storage}, - AssetManager: orml_currencies::{Call, Pallet, Storage}, + CustomAssets: pallet_assets::::{Call, Pallet, Storage, Event}, LiquidityMining: zrml_liquidity_mining::{Config, Event, Pallet}, + MarketAssets: pallet_assets::::{Call, Pallet, Storage, Event}, MarketCommons: zrml_market_commons::{Pallet, Storage}, PredictionMarkets: zrml_prediction_markets::{Event, Pallet, Storage}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, @@ -191,12 +202,130 @@ impl crate::Config for Runtime { type WeightInfo = zrml_neo_swaps::weights::WeightInfo; } +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 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 pallet_randomness_collective_flip::Config for Runtime {} impl zrml_prediction_markets::Config for Runtime { type AdvisoryBond = AdvisoryBond; type AdvisoryBondSlashPercentage = AdvisoryBondSlashPercentage; type ApproveOrigin = EnsureSignedBy; + type AssetCreator = AssetRouter; + type AssetDestroyer = AssetRouter; #[cfg(feature = "parachain")] type AssetRegistry = MockRegistry; type Authorized = Authorized; @@ -228,6 +357,7 @@ impl zrml_prediction_markets::Config for Runtime { type MinCategories = MinCategories; type MaxEditReasonLen = MaxEditReasonLen; type MaxRejectReasonLen = MaxRejectReasonLen; + type OnStateTransition = (); type OracleBond = OracleBond; type OutsiderBond = OutsiderBond; type PalletId = PmPalletId; @@ -318,7 +448,7 @@ impl frame_system::Config for Runtime { impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; + type MultiCurrency = AssetRouter; type NativeCurrency = BasicCurrencyAdapter; type WeightInfo = (); } @@ -326,10 +456,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 = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; - type ExistentialDeposits = ExistentialDeposits; + type ExistentialDeposits = ExistentialDepositsNew; type MaxLocks = MaxLocks; type MaxReserves = MaxReserves; type CurrencyHooks = (); @@ -444,7 +574,7 @@ impl ExtBuilder { { use frame_support::traits::GenesisBuild; orml_tokens::GenesisConfig:: { - balances: vec![(ALICE, FOREIGN_ASSET, 100_000_000_001 * _1)], + balances: vec![(ALICE, FOREIGN_ASSET.try_into().unwrap(), 100_000_000_001 * _1)], } .assimilate_storage(&mut t) .unwrap(); diff --git a/zrml/neo-swaps/src/tests/deploy_pool.rs b/zrml/neo-swaps/src/tests/deploy_pool.rs index 689dd76c0..8b3f017a7 100644 --- a/zrml/neo-swaps/src/tests/deploy_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_pool.rs @@ -90,6 +90,7 @@ fn deploy_pool_works_with_scalar_marktes() { ExtBuilder::default().build().execute_with(|| { let alice_before = AssetManager::free_balance(BASE_ASSET, &ALICE); let amount = _100; + let expected_amounts = [amount, 101755598229]; let spot_prices = vec![_1_6, _5_6 + 1]; let swap_fee = CENT; let market_id: MarketId = 0; @@ -97,14 +98,6 @@ fn deploy_pool_works_with_scalar_marktes() { Asset::ScalarOutcome(market_id, ScalarPosition::Long), Asset::ScalarOutcome(market_id, ScalarPosition::Short), ]; - // Deploy some funds in the pool account to ensure that rogue funds don't screw up price - // calculatings. - let rogue_funds = _100; - assert_ok!(AssetManager::deposit( - assets[0], - &Pallet::::pool_account_id(&market_id), - rogue_funds - )); let _ = create_market_and_deploy_pool( ALICE, BASE_ASSET, @@ -114,8 +107,31 @@ fn deploy_pool_works_with_scalar_marktes() { swap_fee, ); let pool = Pools::::get(market_id).unwrap(); + let mut reserves = BTreeMap::new(); + reserves.insert(assets[0], expected_amounts[0]); + reserves.insert(assets[1], expected_amounts[1]); + System::assert_last_event( + Event::PoolDeployed { + who: ALICE, + market_id, + account_id: pool.account_id, + reserves, + collateral: pool.collateral, + liquidity_parameter: pool.liquidity_parameter, + pool_shares_amount: amount, + swap_fee, + } + .into(), + ); + // Deploy some funds in the pool account to ensure that rogue funds don't screw up price + // calculatings. + let rogue_funds = _100; + assert_ok!(AssetManager::deposit( + assets[0], + &Pallet::::pool_account_id(&market_id), + rogue_funds + )); let expected_liquidity = 558110626551; - let expected_amounts = [amount, 101755598229]; let buffer = AssetManager::minimum_balance(pool.collateral); assert_eq!(pool.assets(), assets); assert_approx!(pool.liquidity_parameter, expected_liquidity, 1_000); @@ -145,22 +161,6 @@ fn deploy_pool_works_with_scalar_marktes() { let price_sum = pool.assets().iter().map(|&a| pool.calculate_spot_price(a).unwrap()).sum::(); assert_eq!(price_sum, _1); - let mut reserves = BTreeMap::new(); - reserves.insert(assets[0], expected_amounts[0]); - reserves.insert(assets[1], expected_amounts[1]); - System::assert_last_event( - Event::PoolDeployed { - who: ALICE, - market_id, - account_id: pool.account_id, - reserves, - collateral: pool.collateral, - liquidity_parameter: pool.liquidity_parameter, - pool_shares_amount: amount, - swap_fee, - } - .into(), - ); }); } @@ -430,7 +430,7 @@ fn deploy_pool_fails_on_insufficient_funds() { vec![_3_4, _1_4], CENT ), - orml_tokens::Error::::BalanceTooLow + pallet_assets::Error::::BalanceLow ); }); } diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index 76c940460..f1dde0de8 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -215,7 +215,7 @@ fn join_fails_on_insufficient_funds() { _100, vec![u128::MAX, u128::MAX] ), - orml_tokens::Error::::BalanceTooLow + pallet_assets::Error::::NoAccount ); }); } diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index efbf95964..529c1083b 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -34,20 +34,20 @@ use zeitgeist_primitives::{ constants::CENT, math::fixed::{FixedDiv, FixedMul}, types::{ - AccountIdTest, Asset, Deadlines, MarketCreation, MarketId, MarketPeriod, MarketStatus, - MarketType, MultiHash, ScalarPosition, ScoringRule, + AccountIdTest, Asset, Assets, Deadlines, MarketCreation, MarketId, MarketPeriod, + MarketStatus, MarketType, MultiHash, ScalarPosition, ScoringRule, }, }; use zrml_market_commons::{MarketCommonsPalletApi, Markets}; #[cfg(not(feature = "parachain"))] -const BASE_ASSET: Asset = Asset::Ztg; +const BASE_ASSET: Assets = Assets::Ztg; #[cfg(feature = "parachain")] -const BASE_ASSET: Asset = FOREIGN_ASSET; +const BASE_ASSET: Assets = FOREIGN_ASSET; fn create_market( creator: AccountIdTest, - base_asset: Asset, + base_asset: Assets, market_type: MarketType, scoring_rule: ScoringRule, ) -> MarketId { @@ -56,7 +56,7 @@ fn create_market( metadata[1] = 0x30; assert_ok!(PredictionMarkets::create_market( RuntimeOrigin::signed(creator), - base_asset, + base_asset.try_into().unwrap(), Perbill::zero(), EVE, MarketPeriod::Block(0..2), @@ -76,7 +76,7 @@ fn create_market( fn create_market_and_deploy_pool( creator: AccountIdOf, - base_asset: Asset, + base_asset: Assets, market_type: MarketType, amount: BalanceOf, spot_prices: Vec>, @@ -104,7 +104,7 @@ fn deposit_complete_set( amount: BalanceOf, ) { let market = MarketCommons::market(&market_id).unwrap(); - assert_ok!(AssetManager::deposit(market.base_asset, &account, amount)); + assert_ok!(AssetManager::deposit(market.base_asset.into(), &account, amount)); assert_ok!(::CompleteSetOperations::buy_complete_set( RuntimeOrigin::signed(account), market_id, diff --git a/zrml/neo-swaps/src/tests/sell.rs b/zrml/neo-swaps/src/tests/sell.rs index ae6aa501f..9308de914 100644 --- a/zrml/neo-swaps/src/tests/sell.rs +++ b/zrml/neo-swaps/src/tests/sell.rs @@ -352,7 +352,7 @@ fn sell_fails_on_insufficient_funds() { amount_in, u128::MAX, ), - orml_tokens::Error::::BalanceTooLow, + pallet_assets::Error::::BalanceLow, ); }); } 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(), 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, diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 900678e45..42c9c4fab 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -65,7 +65,7 @@ mod pallet { transactional, Blake2_128Concat, PalletError, PalletId, Parameter, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; - use orml_traits::{MultiCurrency, MultiReservableCurrency}; + use orml_traits::MultiCurrency; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; @@ -504,7 +504,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// Shares of outcome assets and native currency - type AssetManager: MultiReservableCurrency; + type AssetManager: MultiCurrency; type Asset: Parameter + Member From 7fd695ef0a94e4d326de1c1b77f924bce19093a9 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Mon, 25 Mar 2024 10:28:57 +0100 Subject: [PATCH 10/14] New Asset System - Integrate into Orderbook/Court/GlobalDisputes (#1283) * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Implement support for non-reservable assets in orderbook * Format * Fix unfillable / unremovable order bug Co-authored-by: Chralt * Check reserved balanced named * Log when reserve is less than expected --------- Co-authored-by: Chralt --- Cargo.toml | 2 +- zrml/court/src/benchmarks.rs | 4 +- zrml/court/src/lib.rs | 4 +- zrml/court/src/mock.rs | 4 +- zrml/court/src/tests.rs | 8 +- zrml/global-disputes/src/mock.rs | 4 +- zrml/global-disputes/src/utils.rs | 4 +- zrml/orderbook/Cargo.toml | 14 +- zrml/orderbook/src/benchmarks.rs | 4 +- zrml/orderbook/src/lib.rs | 200 ++++++++++++--------- zrml/orderbook/src/mock.rs | 162 +++++++++++++++-- zrml/orderbook/src/tests.rs | 281 ++++++++++++++++++++++-------- zrml/orderbook/src/utils.rs | 10 +- 13 files changed, 508 insertions(+), 193 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8f33423d..d056ef428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -265,8 +265,8 @@ url = "2.2.2" arbitrary = { version = "1.3.0", default-features = false } arrayvec = { version = "0.7.4", default-features = false } cfg-if = { version = "1.0.0" } -impl-trait-for-tuples = { version = "0.2.2" } fixed = { version = "=1.15.0", default-features = false, features = ["num-traits"] } +impl-trait-for-tuples = { version = "0.2.2" } # 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 = "v21.1.1", default-features = false } # Hashbrown works in no_std by default and default features are used in Rikiddo diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 39a810659..95624c244 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -41,7 +41,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ traits::{DisputeApi, DisputeResolutionApi}, types::{ - Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + BaseAsset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, }, }; @@ -54,7 +54,7 @@ where T: Config, { Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: account("creator", 0, 0), 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/court/src/mock.rs b/zrml/court/src/mock.rs index 7407eac52..02843c728 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -40,7 +40,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, }, }; @@ -101,7 +101,7 @@ impl DisputeResolutionApi for MockResolution { Self::Balance, Self::BlockNumber, Self::Moment, - Asset, + BaseAsset, >, ) -> Result { Ok(Weight::zero()) diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 4592413e3..8e2bc5288 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -54,9 +54,9 @@ use zeitgeist_primitives::{ }, traits::DisputeApi, types::{ - AccountIdTest, Asset, Deadlines, GlobalDisputeItem, Market, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, - ScoringRule, + AccountIdTest, BaseAsset, Deadlines, GlobalDisputeItem, Market, MarketBonds, + MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, + OutcomeReport, Report, ScoringRule, }, }; use zrml_market_commons::{Error as MError, MarketCommonsPalletApi}; @@ -64,7 +64,7 @@ use zrml_market_commons::{Error as MError, MarketCommonsPalletApi}; const ORACLE_REPORT: OutcomeReport = OutcomeReport::Scalar(u128::MAX); const DEFAULT_MARKET: MarketOf = Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: 0, diff --git a/zrml/global-disputes/src/mock.rs b/zrml/global-disputes/src/mock.rs index c49705d5f..89be0f994 100644 --- a/zrml/global-disputes/src/mock.rs +++ b/zrml/global-disputes/src/mock.rs @@ -36,7 +36,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, }, }; @@ -80,7 +80,7 @@ impl DisputeResolutionApi for NoopResolution { Self::Balance, Self::BlockNumber, Self::Moment, - Asset, + BaseAsset, >, ) -> Result { Ok(Weight::zero()) diff --git a/zrml/global-disputes/src/utils.rs b/zrml/global-disputes/src/utils.rs index e282ce2fd..d7fa8841d 100644 --- a/zrml/global-disputes/src/utils.rs +++ b/zrml/global-disputes/src/utils.rs @@ -24,7 +24,7 @@ type MarketOf = zeitgeist_primitives::types::Market< BalanceOf, ::BlockNumber, MomentOf, - zeitgeist_primitives::types::Asset>, + zeitgeist_primitives::types::BaseAsset, >; pub(crate) fn market_mock() -> MarketOf @@ -36,7 +36,7 @@ where use zeitgeist_primitives::types::ScoringRule; zeitgeist_primitives::types::Market { - base_asset: zeitgeist_primitives::types::Asset::Ztg, + base_asset: zeitgeist_primitives::types::BaseAsset::Ztg, creation: zeitgeist_primitives::types::MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: T::GlobalDisputesPalletId::get().into_account_truncating(), diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index dd4963def..775eb2249 100644 --- a/zrml/orderbook/Cargo.toml +++ b/zrml/orderbook/Cargo.toml @@ -8,37 +8,43 @@ orml-traits = { 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 } # Mock env_logger = { 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-timestamp = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } +zrml-asset-router = { workspace = true, optional = true } zrml-market-commons = { workspace = true, optional = true } [dev-dependencies] test-case = { workspace = true } -zrml-orderbook = { workspace = true, features = ["mock", "default"] } +zrml-orderbook = { workspace = true, features = ["default", "mock"] } [features] default = ["std"] mock = [ + "env_logger/default", + "orml-currencies/default", "orml-tokens/default", + "pallet-assets/default", "pallet-balances/default", "pallet-timestamp/default", - "zrml-market-commons/default", - "orml-currencies/default", "sp-io/default", "zeitgeist-primitives/mock", - "env_logger/default", + "zrml-asset-router", + "zrml-market-commons/default", ] 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/orderbook/src/benchmarks.rs b/zrml/orderbook/src/benchmarks.rs index b3075b678..d1d6818aa 100644 --- a/zrml/orderbook/src/benchmarks.rs +++ b/zrml/orderbook/src/benchmarks.rs @@ -48,7 +48,7 @@ fn order_common_parameters( &'static str, > { let market = market_mock::(); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); 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(); @@ -84,7 +84,7 @@ benchmarks! { 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 maker_asset = T::MarketCommons::market(&market_id).unwrap().base_asset.into(); let caller = generate_funded_account::(None, maker_asset)?; }: fill_order(RawOrigin::Signed(caller), order_id, None) diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index d6ab8b7c9..27bdb2b93 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; use crate::{types::*, weights::*}; -use alloc::{vec, vec::Vec}; +use alloc::vec; use core::marker::PhantomData; use frame_support::{ ensure, @@ -33,16 +33,19 @@ use frame_support::{ transactional, PalletId, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::OriginFor}; -use orml_traits::{BalanceStatus, MultiCurrency, NamedMultiReservableCurrency}; +use orml_traits::{ + BalanceStatus, MultiCurrency, MultiReservableCurrency, NamedMultiReservableCurrency, +}; pub use pallet::*; -use sp_runtime::traits::{Get, Zero}; +use sp_runtime::traits::{AccountIdConversion, Get, Zero}; +use zeitgeist_macros::unreachable_non_terminating; use zeitgeist_primitives::{ math::{ checked_ops_res::{CheckedAddRes, CheckedSubRes}, fixed::FixedMulDiv, }, traits::{DistributeFees, MarketCommonsPalletApi}, - types::{Asset, Market, MarketStatus, MarketType, ScalarPosition, ScoringRule}, + types::{Asset, BaseAsset, MarketStatus, ScoringRule}, }; #[cfg(feature = "runtime-benchmarks")] @@ -103,15 +106,7 @@ mod pallet { <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type AccountIdOf = ::AccountId; pub(crate) type OrderOf = Order, BalanceOf, MarketIdOf>; - pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; pub(crate) type AssetOf = Asset>; - pub(crate) type MarketOf = Market< - AccountIdOf, - BalanceOf, - ::BlockNumber, - MomentOf, - AssetOf, - >; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -246,30 +241,6 @@ mod pallet { } 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( @@ -282,6 +253,17 @@ mod pallet { Ok(()) } + 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(()) + } + /// Calculates the amount that the taker is going to get from the maker's amount. fn get_taker_fill( order_data: &OrderOf, @@ -298,15 +280,16 @@ mod pallet { maker_fill.bmul_bdiv_floor(taker_full_fill, maker_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(()) + /// The order account for a specific order id. + #[inline] + pub(crate) fn order_account(order_id: OrderId) -> AccountIdOf { + T::PalletId::get().into_sub_account_truncating(order_id) + } + + /// The reserve ID of the order book pallet. + #[inline] + pub(crate) fn reserve_id() -> [u8; 8] { + T::PalletId::get().0 } fn do_remove_order(order_id: OrderId, who: AccountIdOf) -> DispatchResult { @@ -314,24 +297,30 @@ mod pallet { 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, - ); + let order_account = Self::order_account(order_id); + let asset = order_data.maker_asset; + let amount = order_data.maker_amount; + + if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), asset, &maker) + .is_zero() + { + let missing = + T::AssetManager::unreserve_named(&Self::reserve_id(), asset, maker, amount); + + unreachable_non_terminating!( + missing.is_zero(), + LOG_TARGET, + "Could not unreserve all of the amount. reserve_id: {:?}, asset: {:?} who: \ + {:?}, amount: {:?}, missing: {:?}", + Self::reserve_id(), + asset, + maker, + amount, + missing, + ); + } else { + T::AssetManager::transfer(asset, &order_account, maker, amount)?; + } >::remove(order_id); @@ -343,21 +332,26 @@ mod pallet { /// Charge the external fees from `taker` and return the adjusted maker fill. fn charge_external_fees( order_data: &OrderOf, - base_asset: AssetOf, + base_asset: BaseAsset, maker_fill: BalanceOf, taker: &AccountIdOf, taker_fill: BalanceOf, ) -> Result, DispatchError> { - let maker_asset_is_base = order_data.maker_asset == base_asset; + let maker_asset_is_base = order_data.maker_asset == base_asset.into(); let base_asset_fill = if maker_asset_is_base { taker_fill } else { - debug_assert!(order_data.taker_asset == base_asset); + unreachable_non_terminating!( + order_data.taker_asset == base_asset.into(), + LOG_TARGET, + "Order {:?} does not contain a base asset", + order_data + ); maker_fill }; let fee_amount = T::ExternalFees::distribute( order_data.market_id, - base_asset, + base_asset.into(), taker, base_asset_fill, ); @@ -379,9 +373,10 @@ mod pallet { ) -> DispatchResult { let mut order_data = >::get(order_id).ok_or(Error::::OrderDoesNotExist)?; let market = T::MarketCommons::market(&order_data.market_id)?; - debug_assert!( + unreachable_non_terminating!( market.scoring_rule == ScoringRule::Orderbook, - "The call to place_order already ensured the scoring rule order book." + LOG_TARGET, + "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; @@ -401,17 +396,36 @@ mod pallet { // 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 let taker_fill = Self::get_taker_fill(&order_data, maker_fill)?; + let order_account = Self::order_account(order_id); + + if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), maker_asset, &maker) + .is_zero() + { + let missing = T::AssetManager::repatriate_reserved_named( + &Self::reserve_id(), + maker_asset, + &maker, + &taker, + taker_fill, + BalanceStatus::Free, + )?; + + unreachable_non_terminating!( + missing.is_zero(), + LOG_TARGET, + "Could not repatriate all of the amount. reserve_id: {:?}, asset: {:?} who: \ + {:?}, amount: {:?}, missing: {:?}", + Self::reserve_id(), + maker_asset, + maker, + taker_fill, + missing, + ); + } else { + T::AssetManager::transfer(maker_asset, &order_account, &taker, taker_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, @@ -464,18 +478,21 @@ mod pallet { 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 { + let outcome_asset = if maker_asset == base_asset.into() { taker_asset } else { - ensure!(taker_asset == base_asset, Error::::MarketBaseAssetNotPresent); + ensure!(taker_asset == base_asset.into(), Error::::MarketBaseAssetNotPresent); maker_asset }; - ensure!( - market_assets.binary_search(&outcome_asset).is_ok(), - Error::::InvalidOutcomeAsset - ); + + let outcome_asset_converted = + outcome_asset.try_into().map_err(|_| Error::::InvalidOutcomeAsset)?; + let market_assets = market.outcome_assets(market_id); + market_assets + .binary_search(&outcome_asset_converted) + .map_err(|_| Error::::InvalidOutcomeAsset)?; + ensure!( maker_amount >= T::AssetManager::minimum_balance(maker_asset), Error::::BelowMinimumBalance @@ -489,7 +506,18 @@ mod pallet { 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)?; + // reserving the maker_asset is preferred (depends on other pallet support) + if T::AssetManager::can_reserve(maker_asset, &who, maker_amount) { + T::AssetManager::reserve_named( + &Self::reserve_id(), + maker_asset, + &who, + maker_amount, + )?; + } else { + let order_account = Self::order_account(order_id); + T::AssetManager::transfer(maker_asset, &who, &order_account, maker_amount)?; + } let order = Order { market_id, diff --git a/zrml/orderbook/src/mock.rs b/zrml/orderbook/src/mock.rs index 699b9e1dc..d94ec22a4 100644 --- a/zrml/orderbook/src/mock.rs +++ b/zrml/orderbook/src/mock.rs @@ -18,25 +18,35 @@ #![cfg(feature = "mock")] -use crate as orderbook_v1; +use crate as orderbook; use crate::{AssetOf, BalanceOf, MarketIdOf}; use core::marker::PhantomData; -use frame_support::{construct_runtime, pallet_prelude::Get, parameter_types, traits::Everything}; +use frame_support::{ + construct_runtime, + pallet_prelude::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, Zero}, + traits::{BlakeTwo256, ConstU32, IdentityLookup, Zero}, Perbill, SaturatedConversion, }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposit, ExistentialDeposits, GetNativeCurrencyId, MaxLocks, - MaxReserves, MinimumPeriod, OrderbookPalletId, BASE, + AssetsAccountDeposit, AssetsApprovalDeposit, AssetsDeposit, AssetsMetadataDepositBase, + AssetsMetadataDepositPerByte, AssetsStringLimit, BlockHashCount, DestroyAccountWeight, + DestroyApprovalWeight, DestroyFinishWeight, ExistentialDeposit, ExistentialDepositsNew, + GetNativeCurrencyId, MaxLocks, MaxReserves, MinimumPeriod, OrderbookPalletId, 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, }, }; @@ -51,6 +61,10 @@ parameter_types! { pub const FeeAccount: AccountIdTest = MARKET_CREATOR; } +type CustomAssetsInstance = pallet_assets::Instance1; +type CampaignAssetsInstance = pallet_assets::Instance2; +type MarketAssetsInstance = pallet_assets::Instance3; + pub fn calculate_fee(amount: BalanceOf) -> BalanceOf { Perbill::from_rational(1u64, 100u64).mul_floor(amount.saturated_into::>()) } @@ -87,13 +101,17 @@ construct_runtime!( NodeBlock = BlockTest, UncheckedExtrinsic = UncheckedExtrinsicTest, { + AssetManager: orml_currencies::{Call, 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}, - Orderbook: orderbook_v1::{Call, Event, Pallet}, + Orderbook: orderbook::{Call, Event, Pallet}, System: frame_system::{Call, Config, Event, Pallet, Storage}, - Tokens: orml_tokens::{Config, Event, Pallet, Storage}, - AssetManager: orml_currencies::{Call, Pallet, Storage}, Timestamp: pallet_timestamp::{Pallet}, + Tokens: orml_tokens::{Config, Event, Pallet, Storage}, } ); @@ -103,7 +121,7 @@ impl crate::Config for Runtime { type RuntimeEvent = RuntimeEvent; type MarketCommons = MarketCommons; type PalletId = OrderbookPalletId; - type WeightInfo = orderbook_v1::weights::WeightInfo; + type WeightInfo = orderbook::weights::WeightInfo; } impl frame_system::Config for Runtime { @@ -135,7 +153,7 @@ impl frame_system::Config for Runtime { impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; - type MultiCurrency = Tokens; + type MultiCurrency = AssetRouter; type NativeCurrency = BasicCurrencyAdapter; type WeightInfo = (); } @@ -143,10 +161,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 = (); @@ -154,6 +172,106 @@ impl orml_tokens::Config for Runtime { type WeightInfo = (); } +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; @@ -173,6 +291,22 @@ impl pallet_timestamp::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; diff --git a/zrml/orderbook/src/tests.rs b/zrml/orderbook/src/tests.rs index e5b46e42d..aa546c519 100644 --- a/zrml/orderbook/src/tests.rs +++ b/zrml/orderbook/src/tests.rs @@ -17,7 +17,7 @@ // along with Zeitgeist. If not, see . use crate::{mock::*, utils::market_mock, Error, Event, Order, Orders}; -use frame_support::{assert_noop, assert_ok}; +use frame_support::{assert_noop, assert_ok, traits::fungibles::Create}; use orml_tokens::Error as AError; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use pallet_balances::Error as BError; @@ -25,7 +25,7 @@ use sp_runtime::{Perbill, Perquintill}; use test_case::test_case; use zeitgeist_primitives::{ constants::BASE, - types::{Asset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, + types::{Asset, BaseAsset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, }; use zrml_market_commons::{Error as MError, MarketCommonsPalletApi, Markets}; @@ -45,7 +45,7 @@ fn place_order_fails_with_wrong_scoring_rule(scoring_rule: ScoringRule) { Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - market.base_asset, + market.base_asset.into(), 10 * BASE, Asset::CategoricalOutcome(market_id, 2), 25 * BASE, @@ -74,7 +74,7 @@ fn place_order_fails_if_market_status_not_active(status: MarketStatus) { Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - market.base_asset, + market.base_asset.into(), 10 * BASE, Asset::CategoricalOutcome(0, 2), 25 * BASE, @@ -95,7 +95,7 @@ fn fill_order_fails_if_market_status_not_active(status: MarketStatus) { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -128,7 +128,7 @@ fn fill_order_fails_if_amount_too_high_for_order() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -157,7 +157,7 @@ fn fill_order_fails_if_amount_is_below_minimum_balance() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -190,7 +190,7 @@ fn place_order_fails_if_amount_is_below_minimum_balance() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); assert_noop!( @@ -226,7 +226,7 @@ fn fill_order_fails_if_balance_too_low() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -259,7 +259,7 @@ fn fill_order_fails_if_partial_fill_near_full_fill_not_allowed() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -274,7 +274,7 @@ fn fill_order_fails_if_partial_fill_near_full_fill_not_allowed() { taker_amount, )); - AssetManager::deposit(taker_asset, &BOB, taker_amount).unwrap(); + assert_ok!(AssetManager::deposit(taker_asset, &BOB, taker_amount)); assert_noop!( Orderbook::fill_order( @@ -294,7 +294,7 @@ fn fill_order_removes_order() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -324,7 +324,7 @@ fn fill_order_partially_fills_order() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let order_id = 0u128; @@ -370,6 +370,37 @@ fn fill_order_partially_fills_order() { }); } +#[test] +fn fill_order_does_work_with_reserves_after_funding_order_account() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let mut market = market_mock::(); + market.base_asset = BaseAsset::Ztg; + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset.into(); + let taker_asset = Asset::CategoricalOutcome(0, 2); + let taker_amount = 10 * BASE; + let maker_amount = 250 * BASE; + + assert_ok!(AssetManager::deposit(maker_asset, &ALICE, maker_amount)); + assert_ok!(AssetManager::deposit(taker_asset, &BOB, taker_amount)); + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + let reserved_funds = AssetManager::reserved_balance(maker_asset, &ALICE); + assert_eq!(reserved_funds, maker_amount); + assert_ok!(AssetManager::deposit(maker_asset, &Orderbook::order_account(0), BASE)); + assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(BOB), 0, None)); + }); +} + #[test] fn place_order_fails_if_market_base_asset_not_present() { ExtBuilder::default().build().execute_with(|| { @@ -403,7 +434,7 @@ fn place_order_fails_if_invalid_outcome_asset() { assert_eq!(market.market_type, MarketType::Categorical(64u16)); let maker_asset = Asset::ScalarOutcome(0, ScalarPosition::Long); - let taker_asset = market.base_asset; + let taker_asset = market.base_asset.into(); assert_noop!( Orderbook::place_order( @@ -449,7 +480,7 @@ fn place_order_fails_if_maker_has_insufficient_funds() { Markets::::insert(market_id, market.clone()); let maker = ALICE; - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let alice_free_maker_amount = AssetManager::free_balance(maker_asset, &maker); @@ -485,11 +516,16 @@ fn it_fails_order_does_not_exist() { }); } -#[test] -fn it_places_orders() { +#[test_case(true; "with_reservable_asset")] +#[test_case(false; "with_non_reservable_asset")] +fn it_places_orders(reservable_maker_asset: bool) { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; - let market = market_mock::(); + let mut market = market_mock::(); + if !reservable_maker_asset { + market.base_asset = BaseAsset::CampaignAsset(0); + assert_ok!(AssetRouter::create(market.base_asset.into(), ALICE, true, 1)); + } Markets::::insert(market_id, market.clone()); let taker_asset_0 = Asset::CategoricalOutcome(0, 2); @@ -497,18 +533,24 @@ fn it_places_orders() { let taker_amount = 10 * BASE; let maker_amount = 250 * BASE; - assert_ok!(AssetManager::deposit(market.base_asset, &ALICE, maker_amount)); + assert_ok!(AssetManager::deposit(market.base_asset.into(), &ALICE, maker_amount)); assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, - market.base_asset, + market.base_asset.into(), maker_amount, taker_asset_0, taker_amount, )); - let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); + let reserved_funds = if reservable_maker_asset { + AssetManager::reserved_balance(market.base_asset.into(), &ALICE) + } else { + let order_account = Orderbook::order_account(0); + assert_eq!(AssetManager::free_balance(market.base_asset.into(), &BOB), 0); + AssetManager::free_balance(market.base_asset.into(), &order_account) + }; assert_eq!(reserved_funds, maker_amount); let maker_asset = Asset::CategoricalOutcome(0, 1); @@ -522,7 +564,7 @@ fn it_places_orders() { market_id, maker_asset, maker_amount, - market.base_asset, + market.base_asset.into(), taker_amount, )); @@ -539,7 +581,7 @@ fn it_fills_order_fully_maker_outcome_asset() { Markets::::insert(market_id, market.clone()); let maker_asset = Asset::CategoricalOutcome(0, 1); - let taker_asset = market.base_asset; + let taker_asset = market.base_asset.into(); let maker_amount = 100 * BASE; let taker_amount = 500 * BASE; @@ -589,26 +631,33 @@ fn it_fills_order_fully_maker_outcome_asset() { assert_eq!(alice_maker_asset_free, INITIAL_BALANCE); assert_eq!(alice_taker_asset_free, maker_amount); - let bob_taker_asset_free = AssetManager::free_balance(market.base_asset, &BOB); + let bob_taker_asset_free = AssetManager::free_balance(market.base_asset.into(), &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_order_fully_maker_base_asset() { +#[test_case(true; "with_reservable_asset")] +#[test_case(false; "with_non_reservable_asset")] +fn it_fills_order_fully_maker_base_asset(reservable_maker_asset: bool) { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; - let market = market_mock::(); + let mut market = market_mock::(); + let taker_amount = 10 * BASE; + let maker_amount = 50 * BASE; + + if !reservable_maker_asset { + market.base_asset = BaseAsset::CampaignAsset(0); + assert_ok!(AssetRouter::create(market.base_asset.into(), BOB, true, 1)); + assert_ok!(AssetManager::deposit(market.base_asset.into(), &BOB, maker_amount)); + } + Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 1); - let taker_amount = 10 * BASE; - let maker_amount = 50 * BASE; - assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, @@ -618,8 +667,14 @@ fn it_fills_order_fully_maker_base_asset() { taker_amount, )); - let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); - assert_eq!(reserved_bob, maker_amount); + let reserved_funds = if reservable_maker_asset { + AssetManager::reserved_balance(market.base_asset.into(), &BOB) + } else { + let order_account = Orderbook::order_account(0); + assert_eq!(AssetManager::free_balance(market.base_asset.into(), &BOB), 0); + AssetManager::free_balance(market.base_asset.into(), &order_account) + }; + assert_eq!(reserved_funds, maker_amount); let order_id = 0u128; assert_ok!(AssetManager::deposit(taker_asset, &ALICE, taker_amount)); @@ -645,31 +700,48 @@ fn it_fills_order_fully_maker_base_asset() { 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); + + if reservable_maker_asset { + assert_eq!(alice_maker_asset_free, INITIAL_BALANCE + maker_amount_minus_fees); + } else { + assert_eq!(alice_maker_asset_free, 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); + + if reservable_maker_asset { + assert_eq!(bob_bal, INITIAL_BALANCE - maker_amount); + } else { + assert_eq!(bob_bal, 0); + } + assert_eq!(bob_shares, taker_amount); }); } -#[test] -fn it_fills_order_partially_maker_base_asset() { +#[test_case(true; "with_reservable_asset")] +#[test_case(false; "with_non_reservable_asset")] +fn it_fills_order_partially_maker_base_asset(reservable_maker_asset: bool) { ExtBuilder::default().build().execute_with(|| { let market_id = 0u128; - let market = market_mock::(); + let mut market = market_mock::(); + let maker_amount = 500 * BASE; + let taker_amount = 100 * BASE; + + if !reservable_maker_asset { + market.base_asset = BaseAsset::CampaignAsset(0); + assert_ok!(AssetRouter::create(market.base_asset.into(), BOB, true, 1)); + } + Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 1); - let maker_amount = 500 * BASE; - let taker_amount = 100 * BASE; - assert_ok!(AssetManager::deposit(maker_asset, &BOB, maker_amount)); - assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(BOB), market_id, @@ -679,8 +751,14 @@ fn it_fills_order_partially_maker_base_asset() { taker_amount, )); - let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); - assert_eq!(reserved_bob, maker_amount); + let reserved_funds = if reservable_maker_asset { + AssetManager::reserved_balance(market.base_asset.into(), &BOB) + } else { + let order_account = Orderbook::order_account(0); + assert_eq!(AssetManager::free_balance(market.base_asset.into(), &BOB), 0); + AssetManager::free_balance(market.base_asset.into(), &order_account) + }; + assert_eq!(reserved_funds, maker_amount); let order_id = 0u128; assert_ok!(AssetManager::deposit(taker_asset, &ALICE, taker_amount)); @@ -736,16 +814,34 @@ fn it_fills_order_partially_maker_base_asset() { 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); + + if reservable_maker_asset { + assert_eq!(alice_maker_asset_free, INITIAL_BALANCE + filled_maker_amount_minus_fees); + } else { + assert_eq!(alice_maker_asset_free, 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); + + if reservable_maker_asset { + assert_eq!(bob_maker_asset_free, INITIAL_BALANCE); + } else { + assert_eq!(bob_maker_asset_free, 0); + } + assert_eq!(bob_taker_asset_free, alice_portion); - let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); - assert_eq!(reserved_bob, unfilled_maker_amount); + if reservable_maker_asset { + let reserved_bob = AssetManager::reserved_balance(maker_asset, &BOB); + assert_eq!(reserved_bob, unfilled_maker_amount); + } else { + let order_account = Orderbook::order_account(0); + let remaining = AssetManager::free_balance(maker_asset, &order_account); + assert_eq!(remaining, unfilled_maker_amount); + } }); } @@ -757,7 +853,7 @@ fn it_fills_order_partially_maker_outcome_asset() { Markets::::insert(market_id, market.clone()); let maker_asset = Asset::CategoricalOutcome(0, 1); - let taker_asset = market.base_asset; + let taker_asset = market.base_asset.into(); let maker_amount = 100 * BASE; let taker_amount = 500 * BASE; @@ -779,7 +875,7 @@ fn it_fills_order_partially_maker_outcome_asset() { let order_id = 0u128; let market_creator_free_balance_before = - AssetManager::free_balance(market.base_asset, &MARKET_CREATOR); + AssetManager::free_balance(market.base_asset.into(), &MARKET_CREATOR); // instead of buying 500 of the base asset, Alice buys 70 shares let alice_portion = 70 * BASE; @@ -787,7 +883,7 @@ fn it_fills_order_partially_maker_outcome_asset() { assert_ok!(Orderbook::fill_order(RuntimeOrigin::signed(ALICE), order_id, portion,)); let market_creator_free_balance_after = - AssetManager::free_balance(market.base_asset, &MARKET_CREATOR); + AssetManager::free_balance(market.base_asset.into(), &MARKET_CREATOR); assert_eq!( market_creator_free_balance_after - market_creator_free_balance_before, calculate_fee::(70 * BASE) @@ -846,19 +942,25 @@ fn it_fills_order_partially_maker_outcome_asset() { }); } -#[test] -fn it_removes_order() { +#[test_case(true; "with_reservable_asset")] +#[test_case(false; "with_non_reservable_asset")] +fn it_removes_order(reservable_maker_asset: bool) { 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 mut market = market_mock::(); let taker_amount = 25 * BASE; let maker_amount = 10 * BASE; + if !reservable_maker_asset { + market.base_asset = BaseAsset::CampaignAsset(0); + assert_ok!(AssetRouter::create(market.base_asset.into(), ALICE, true, 1)); + assert_ok!(AssetManager::deposit(market.base_asset.into(), &ALICE, maker_amount)); + } + + Markets::::insert(market_id, market.clone()); + let maker_asset = market.base_asset.into(); + let taker_asset = Asset::CategoricalOutcome(0, 2); + assert_ok!(Orderbook::place_order( RuntimeOrigin::signed(ALICE), market_id, @@ -868,7 +970,13 @@ fn it_removes_order() { taker_amount, )); - let reserved_funds = AssetManager::reserved_balance(market.base_asset, &ALICE); + let reserved_funds = if reservable_maker_asset { + AssetManager::reserved_balance(market.base_asset.into(), &ALICE) + } else { + let order_account = Orderbook::order_account(0); + assert_eq!(AssetManager::free_balance(market.base_asset.into(), &BOB), 0); + AssetManager::free_balance(market.base_asset.into(), &order_account) + }; assert_eq!(reserved_funds, maker_amount); let order_id = 0u128; @@ -878,12 +986,16 @@ fn it_removes_order() { Order { market_id, maker: ALICE, maker_asset, maker_amount, taker_asset, taker_amount } ); - 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 = AssetManager::reserved_balance(market.base_asset, &ALICE); + let reserved_funds = if reservable_maker_asset { + AssetManager::reserved_balance(market.base_asset.into(), &ALICE) + } else { + let order_account = Orderbook::order_account(0); + let alice_balance = AssetManager::free_balance(market.base_asset.into(), &ALICE); + assert_eq!(alice_balance, maker_amount); + AssetManager::free_balance(market.base_asset.into(), &order_account) + }; assert_eq!(reserved_funds, 0); assert!(Orders::::get(order_id).is_none()); @@ -897,7 +1009,7 @@ fn remove_order_emits_event() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let taker_amount = 25 * BASE; @@ -927,7 +1039,7 @@ fn remove_order_fails_if_not_order_creator() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let taker_amount = 25 * BASE; @@ -951,6 +1063,39 @@ fn remove_order_fails_if_not_order_creator() { }); } +#[test] +fn remove_order_does_work_with_reserves_after_funding_order_account() { + ExtBuilder::default().build().execute_with(|| { + let market_id = 0u128; + let mut market = market_mock::(); + market.base_asset = BaseAsset::Ztg; + Markets::::insert(market_id, market.clone()); + + let maker_asset = market.base_asset.into(); + let taker_asset = Asset::CategoricalOutcome(0, 2); + let taker_amount = 10 * BASE; + let maker_amount = 250 * BASE; + + assert_ok!(AssetManager::deposit(maker_asset, &ALICE, maker_amount)); + assert_ok!(AssetManager::deposit(taker_asset, &BOB, taker_amount)); + assert_ok!(Orderbook::place_order( + RuntimeOrigin::signed(ALICE), + market_id, + maker_asset, + maker_amount, + taker_asset, + taker_amount, + )); + + let mut reserved_funds = AssetManager::reserved_balance(maker_asset, &ALICE); + assert_eq!(reserved_funds, maker_amount); + assert_ok!(AssetManager::deposit(maker_asset, &Orderbook::order_account(0), BASE)); + assert_ok!(Orderbook::remove_order(RuntimeOrigin::signed(ALICE), 0)); + reserved_funds = AssetManager::reserved_balance(maker_asset, &ALICE); + assert_eq!(reserved_funds, 0); + }); +} + #[test] fn place_order_emits_event() { ExtBuilder::default().build().execute_with(|| { @@ -958,7 +1103,7 @@ fn place_order_emits_event() { let market = market_mock::(); Markets::::insert(market_id, market.clone()); - let maker_asset = market.base_asset; + let maker_asset = market.base_asset.into(); let taker_asset = Asset::CategoricalOutcome(0, 2); let taker_amount = 25 * BASE; diff --git a/zrml/orderbook/src/utils.rs b/zrml/orderbook/src/utils.rs index 3ec2fb88b..468d0f388 100644 --- a/zrml/orderbook/src/utils.rs +++ b/zrml/orderbook/src/utils.rs @@ -20,16 +20,18 @@ use crate::*; use sp_runtime::traits::AccountIdConversion; use zeitgeist_primitives::types::{ - Asset, Deadlines, Market, MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, - MarketType, ScoringRule, + BaseAsset, Deadlines, Market, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, ScoringRule, }; +type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; + type MarketOf = Market< ::AccountId, BalanceOf, ::BlockNumber, MomentOf, - Asset>, + BaseAsset, >; pub(crate) fn market_mock() -> MarketOf @@ -37,7 +39,7 @@ where T: crate::Config, { Market { - base_asset: Asset::Ztg, + base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), creator: T::PalletId::get().into_account_truncating(), From 7a751ec326e2a7ba22d1af8d62199ea988d1b972 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 26 Mar 2024 19:55:35 +0100 Subject: [PATCH 11/14] New Asset System - Integrate into XCM (#1287) * 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 * Remove migrations (#1180) * Filter admin functions for main-net (#1190) filter admin functions for main-net * Add debug assertions for slashes and reserves (#1188) * add debug assertions for missing slashes * place debug_assert for unreserves * Add some verify checks to court (#1187) add some verify checks to court * Bypass battery stations contracts call filter for court, parimutuel, order book markets (#1185) update contracts call filter * Fix failing court benchmark (#1191) * fix court assertion for resolve_at * remove unnecessary variable * mirror mock and actual impl for DisputeResolution * 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 * Modify court events for indexer (#1182) * modify events for indexer * gracefully handle panicers * handle binary search by key error * improve style * Ensure MinBetSize after fee (#1193) * handle own existential deposit errors * use require_transactional * correct benchmark and test min bet size amounts * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Remove court and global disputes from call filter for the main-net (#1226) * remove from call filter * update copyright * 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 * 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> * 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> * Update style guide to streamline reviews (#1228) * 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. * Set inflation to more than zero for a full benchmark of handle_inflation (#1234) Update benchmarks.rs * 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 * 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 * Utilize Merigify's Merge Queue (#1243) ci(Mergify): configuration update Signed-off-by: Harald Heckmann * 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> * Avoid mergify dequeue (#1245) * Avoid mergify deque * Set review needed only shortly after Mergify commit * 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> * Remove migrations and dead code (#1241) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * 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> * 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> * Fix Rust and Discord badge (#1247) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Fix neo-swaps doc strings (#1250) * Improve `SellExecuted` documentation * Clean up math in comments and doc strings * Fix formatting * 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 * Merge Old* and New* asset variants * Partially integrate lazy migration routing * Integrate lazy migration routing * Fix ExistentialDeposit mapping & Satisfy Clippy * Remove primitives/macros * Filter certain asset destroy calls (they're managed) * Integrate asset destruction into prediction markets * Add BaseAssetClass * Update prediction-markets & market-commons * Update authorized * Update liquidity-mining * Update simple-disputes * Update parimutuels (wip) * Move functions into market struct and properly delete assets * Add ParimutuelAssetClass * Add parimutuel.rs * Remove duplicate code * Adjust asset type and managed destroy after refund in parimutuels * Add MarketTransitionApi This will allow prediction-markets to signal other pallets that state transitions happened * Add MarketTransitionApi * Implement MarketTransitionApi for Parimutuels * Partially implement asset creation/destruction in parimutuels * Add all asset creation / destruction tests to parimutuels * Adjust Court * Update global-disputes * Update swaps * Update neo-swaps * Integrate OnStateTransition hooks into prediction-markets * Use proper state transition weights * Make cargo check succeed * Partially update orderbook * Update orderbook * Update orderbook * Finalize orderbook update * Improve style * Add XcmAssetClass * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Implement support for non-reservable assets in orderbook * Integrate new asset system into Battery Station XCM * Integrate new asset system into Zeitgeist XCM * Fix conditional import * Format * Fix unfillable / unremovable order bug Co-authored-by: Chralt * Make compileable * Resolve one merge conflict * Format * Satisfy Clippy --------- Signed-off-by: Harald Heckmann Co-authored-by: Chralt Co-authored-by: Malte Kliemann Co-authored-by: Chralt98 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- primitives/src/types.rs | 5 +- .../src/integration_tests/xcm/setup.rs | 10 +- .../xcm/tests/currency_id_convert.rs | 154 ++++++++++++------ .../integration_tests/xcm/tests/transfers.rs | 64 ++++---- runtime/battery-station/src/parameters.rs | 2 +- .../src/xcm_config/asset_registry.rs | 10 +- .../battery-station/src/xcm_config/config.rs | 32 ++-- .../battery-station/src/xcm_config/fees.rs | 4 +- runtime/common/src/fees.rs | 20 +-- runtime/common/src/lib.rs | 4 +- .../src/integration_tests/xcm/setup.rs | 12 +- .../xcm/tests/currency_id_convert.rs | 154 ++++++++++++------ .../integration_tests/xcm/tests/transfers.rs | 85 +++++----- runtime/zeitgeist/src/parameters.rs | 2 +- .../src/xcm_config/asset_registry.rs | 10 +- runtime/zeitgeist/src/xcm_config/config.rs | 32 ++-- runtime/zeitgeist/src/xcm_config/fees.rs | 4 +- zrml/neo-swaps/src/mock.rs | 2 +- zrml/neo-swaps/src/tests/join.rs | 1 + zrml/orderbook/src/lib.rs | 2 +- zrml/prediction-markets/src/lib.rs | 9 +- zrml/prediction-markets/src/mock.rs | 7 +- 22 files changed, 379 insertions(+), 246 deletions(-) diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 76290a56d..68ce0b1c1 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -104,9 +104,12 @@ pub type Currencies = CurrencyClass; /// Asset type representing assets used by prediction markets. pub type MarketAsset = MarketAssetClass; -/// Asset type representing types used in parimutuel markets. +/// Asset type representing assets used in parimutuel markets. pub type ParimutuelAsset = ParimutuelAssetClass; +/// Asset type representing assets that can be transferred via XCM. +pub type XcmAsset = XcmAssetClass; + /// 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/runtime/battery-station/src/integration_tests/xcm/setup.rs b/runtime/battery-station/src/integration_tests/xcm/setup.rs index 24d4be69c..092184ade 100644 --- a/runtime/battery-station/src/integration_tests/xcm/setup.rs +++ b/runtime/battery-station/src/integration_tests/xcm/setup.rs @@ -27,7 +27,7 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Currencies, CustomMetadata}; +use zeitgeist_primitives::types::{CustomMetadata, XcmAsset}; pub(super) struct ExtBuilder { balances: Vec<(AccountId, Assets, Balance)>, @@ -106,10 +106,10 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Currencies = Currencies::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Currencies = Currencies::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Currencies = Currencies::ForeignAsset(2); -pub const BTC_ID: Currencies = Currencies::ForeignAsset(3); +pub const FOREIGN_ZTG_ID: XcmAsset = XcmAsset::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: XcmAsset = XcmAsset::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: XcmAsset = XcmAsset::ForeignAsset(2); +pub const BTC_ID: XcmAsset = XcmAsset::ForeignAsset(3); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs index a51401686..e28c94407 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -26,82 +26,97 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{battery_station, general_key, AssetConvert}, - Assets, + Assets, CustomMetadata, ScalarPosition, XcmAsset, }; - +use core::fmt::Debug; use frame_support::assert_err; use sp_runtime::traits::Convert as C2; +use test_case::test_case; use xcm::latest::{Junction::*, Junctions::*, MultiLocation}; use xcm_emulator::TestExt; use xcm_executor::traits::Convert as C1; -#[test] -fn convert_native() { +fn convert_common_native(expected: T) +where + T: Copy + Debug + PartialEq, + AssetConvert: C1 + C2>, +{ assert_eq!(battery_station::KEY.to_vec(), vec![0, 1]); // The way Ztg is represented relative within the Zeitgeist runtime let ztg_location_inner: MultiLocation = MultiLocation::new(0, X1(general_key(battery_station::KEY))); - assert_eq!(>::convert(ztg_location_inner), Ok(Assets::Ztg)); + assert_eq!(>::convert(ztg_location_inner), Ok(expected)); // The canonical way Ztg is represented out in the wild Zeitgeist::execute_with(|| { - assert_eq!( - >::convert(Assets::Ztg), - Some(foreign_ztg_multilocation()) - ) + assert_eq!(>::convert(expected), Some(foreign_ztg_multilocation())) }); } -#[test] -fn convert_any_registered_parent_multilocation() { +fn convert_common_non_native( + expected: T, + multilocation: MultiLocation, + register: fn(Option), +) where + T: Copy + Debug + PartialEq, + AssetConvert: C1 + C2>, +{ Zeitgeist::execute_with(|| { - assert_err!( - >::convert(foreign_parent_multilocation()), - foreign_parent_multilocation() - ); - - assert_eq!(>::convert(Assets::from(FOREIGN_PARENT_ID)), None); - + assert_err!(>::convert(multilocation), multilocation); + assert_eq!(>::convert(expected), None); // Register parent as foreign asset in the Zeitgeist parachain - register_foreign_parent(None); - - assert_eq!( - >::convert(foreign_parent_multilocation()), - Ok(FOREIGN_PARENT_ID.into()), - ); - - assert_eq!( - >::convert(Assets::from(FOREIGN_PARENT_ID)), - Some(foreign_parent_multilocation()) - ); + register(None); + assert_eq!(>::convert(multilocation), Ok(expected)); + assert_eq!(>::convert(expected), Some(multilocation)); }); } #[test] -fn convert_any_registered_sibling_multilocation() { - Zeitgeist::execute_with(|| { - assert_err!( - >::convert(foreign_sibling_multilocation()), - foreign_sibling_multilocation() - ); +fn convert_native_assets() { + convert_common_native(Assets::Ztg); +} - assert_eq!(>::convert(Assets::from(FOREIGN_SIBLING_ID)), None); +#[test] +fn convert_native_xcm_assets() { + convert_common_native(XcmAsset::Ztg); +} - // Register sibling as foreign asset in the Zeitgeist parachain - register_foreign_sibling(None); +#[test] +fn convert_any_registered_parent_multilocation_assets() { + convert_common_non_native( + Assets::from(FOREIGN_PARENT_ID), + foreign_parent_multilocation(), + register_foreign_parent, + ); +} - assert_eq!( - >::convert(foreign_sibling_multilocation()), - Ok(FOREIGN_SIBLING_ID.into()), - ); +#[test] +fn convert_any_registered_parent_multilocation_xcm_assets() { + convert_common_non_native( + XcmAsset::try_from(Assets::from(FOREIGN_PARENT_ID)).unwrap(), + foreign_parent_multilocation(), + register_foreign_parent, + ); +} - assert_eq!( - >::convert(Assets::from(FOREIGN_SIBLING_ID)), - Some(foreign_sibling_multilocation()) - ); - }); +#[test] +fn convert_any_registered_sibling_multilocation_assets() { + convert_common_non_native( + Assets::from(FOREIGN_SIBLING_ID), + foreign_sibling_multilocation(), + register_foreign_sibling, + ); +} + +#[test] +fn convert_any_registered_sibling_multilocation_xcm_assets() { + convert_common_non_native( + XcmAsset::try_from(Assets::from(FOREIGN_SIBLING_ID)).unwrap(), + foreign_sibling_multilocation(), + register_foreign_sibling, + ); } #[test] @@ -110,13 +125,46 @@ fn convert_unkown_multilocation() { MultiLocation::new(1, X2(Parachain(battery_station::ID), general_key(&[42]))); Zeitgeist::execute_with(|| { - assert!(>::convert(unknown_location).is_err()); + assert!(>::convert(unknown_location).is_err()); }); } -#[test] -fn convert_unsupported_currency() { - Zeitgeist::execute_with(|| { - assert_eq!(>::convert(Assets::PoolShare(42)), None) - }); +#[test_case( + Assets::CategoricalOutcome(7, 8); + "assets_categorical" +)] +#[test_case( + Assets::ScalarOutcome(7, ScalarPosition::Long); + "assets_scalar" +)] +#[test_case( + Assets::PoolShare(7); + "assets_pool_share" +)] +#[test_case( + Assets::ForeignAsset(7); + "assets_foreign" +)] +#[test_case( + Assets::ParimutuelShare(7, 8); + "assets_parimutuel_share" +)] +#[test_case( + Assets::CampaignAsset(7); + "assets_campaign_asset" +)] +#[test_case( + Assets::CustomAsset(7); + "assets_custom_asset" +)] +#[test_case( + XcmAsset::ForeignAsset(7); + "xcm_assets_foreign" +)] +fn convert_unsupported_asset(asset: T) +where + T: Copy + Debug + PartialEq, + AssetConvert: C2>, +{ + Zeitgeist::execute_with(|| assert_eq!(>::convert(asset), None)); } 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 149a7f093..109a388dd 100644 --- a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs @@ -26,7 +26,7 @@ use crate::{ test_net::{RococoNet, Sibling, TestNet, Zeitgeist}, }, xcm_config::{config::battery_station, fees::default_per_second}, - AssetRegistry, Assets, Balance, Balances, RuntimeOrigin, Tokens, XTokens, + AssetManager, AssetRegistry, Balance, Balances, RuntimeOrigin, XTokens, ZeitgeistTreasuryAccount, }; @@ -36,7 +36,7 @@ use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, WeightLimi use xcm_emulator::TestExt; use zeitgeist_primitives::{ constants::{BalanceFractionalDecimals, BASE}, - types::{CustomMetadata, XcmMetadata}, + types::{CustomMetadata, XcmAsset, XcmMetadata}, }; #[test] @@ -49,8 +49,8 @@ fn transfer_ztg_to_sibling() { Sibling::execute_with(|| { treasury_initial_balance = - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), 0); + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), 0); register_foreign_ztg(None); }); @@ -59,7 +59,7 @@ fn transfer_ztg_to_sibling() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - Assets::Ztg, + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -82,14 +82,14 @@ fn transfer_ztg_to_sibling() { }); Sibling::execute_with(|| { - let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB); + let current_balance = AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB); // Verify that BOB now has (amount transferred - fee) assert_eq!(current_balance, transfer_amount - ztg_fee()); // Verify that fees (of foreign currency) have been put into treasury assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()), treasury_initial_balance + ztg_fee() ) }); @@ -123,10 +123,10 @@ fn transfer_ztg_sibling_to_zeitgeist() { Sibling::execute_with(|| { assert_eq!(Balances::free_balance(zeitgeist_parachain_account()), 0); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), bob_initial_balance); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), bob_initial_balance); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(BOB), - FOREIGN_ZTG_ID.into(), + FOREIGN_ZTG_ID, transfer_amount, Box::new( MultiLocation::new( @@ -143,7 +143,7 @@ fn transfer_ztg_sibling_to_zeitgeist() { // Confirm that Bobs's balance is initial balance - amount transferred assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), bob_initial_balance - transfer_amount ); }); @@ -181,8 +181,12 @@ fn transfer_btc_sibling_to_zeitgeist() { 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,); + treasury_initial_balance = + AssetManager::free_balance(BTC_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!( + AssetManager::free_balance(BTC_ID.into(), &ALICE), + zeitgeist_alice_initial_balance, + ); }); Sibling::execute_with(|| { @@ -196,8 +200,8 @@ fn transfer_btc_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret Assets::Ztg as BTC in this context. - Assets::Ztg, + // Target chain will interpret XcmAsset::Ztg as BTC in this context. + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -228,13 +232,13 @@ fn transfer_btc_sibling_to_zeitgeist() { // Verify that remote Alice now has initial balance + amount transferred - fee assert_eq!( - Tokens::free_balance(BTC_ID, &ALICE), + AssetManager::free_balance(BTC_ID.into(), &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()), + AssetManager::free_balance(BTC_ID.into(), &ZeitgeistTreasuryAccount::get()), // Align decimal fractional places treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) ) @@ -252,13 +256,13 @@ fn transfer_btc_zeitgeist_to_sibling() { transfer_btc_sibling_to_zeitgeist(); Sibling::execute_with(|| { - assert_eq!(Tokens::free_balance(BTC_ID, &BOB), sibling_bob_initial_balance,); + assert_eq!(AssetManager::free_balance(BTC_ID.into(), &BOB), sibling_bob_initial_balance,); }); Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - BTC_ID.into(), + BTC_ID, transfer_amount, Box::new( MultiLocation::new( @@ -274,7 +278,7 @@ fn transfer_btc_zeitgeist_to_sibling() { )); // Confirm that Alice's balance is initial_balance - amount_transferred - assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), 0); + assert_eq!(AssetManager::free_balance(BTC_ID.into(), &ALICE), 0); }); Sibling::execute_with(|| { @@ -302,7 +306,7 @@ fn transfer_roc_from_relay_chain() { Zeitgeist::execute_with(|| { register_foreign_parent(None); treasury_initial_balance = - Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()); + AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ZeitgeistTreasuryAccount::get()); }); RococoNet::execute_with(|| { @@ -321,10 +325,10 @@ fn transfer_roc_from_relay_chain() { Zeitgeist::execute_with(|| { 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); + assert_eq!(AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &BOB), expected_adjusted); // Verify that fees (of foreign currency) have been put into treasury assert_eq!( - Tokens::free_balance(FOREIGN_PARENT_ID, &ZeitgeistTreasuryAccount::get()), + AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ZeitgeistTreasuryAccount::get()), // Align decimal fractional places treasury_initial_balance + adjusted_balance(roc(1), roc_fee()) ) @@ -340,12 +344,12 @@ fn transfer_roc_to_relay_chain() { transfer_roc_from_relay_chain(); Zeitgeist::execute_with(|| { - let initial_balance = Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE); + let initial_balance = AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ALICE); assert!(initial_balance >= transfer_amount); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - FOREIGN_PARENT_ID.into(), + FOREIGN_PARENT_ID, transfer_amount, Box::new( MultiLocation::new(1, X1(Junction::AccountId32 { id: BOB.into(), network: None })) @@ -355,7 +359,7 @@ fn transfer_roc_to_relay_chain() { )); assert_eq!( - Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE), + AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ALICE), initial_balance - transfer_amount_local ) }); @@ -377,8 +381,8 @@ fn transfer_ztg_to_sibling_with_custom_fee() { Sibling::execute_with(|| { treasury_initial_balance = - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), 0); + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), 0); register_foreign_ztg(None); let custom_metadata = CustomMetadata { @@ -401,7 +405,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - Assets::Ztg, + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -424,7 +428,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { }); Sibling::execute_with(|| { - let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB); + let current_balance = AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB); let custom_fee = calc_fee(default_per_second(10) * 10); // Verify that BOB now has (amount transferred - fee) @@ -432,7 +436,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { // Verify that fees (of foreign currency) have been put into treasury assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()), treasury_initial_balance + custom_fee ) }); diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index e5892fc62..e3f43ce33 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -519,7 +519,7 @@ parameter_type_with_key! { Currencies::ForeignAsset(id) => { let maybe_metadata = < orml_asset_registry::Pallet as orml_traits::asset_registry::Inspect - >::metadata(&Currencies::ForeignAsset(*id)); + >::metadata(&XcmAsset::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; diff --git a/runtime/battery-station/src/xcm_config/asset_registry.rs b/runtime/battery-station/src/xcm_config/asset_registry.rs index 20d23e90d..9deb85aa0 100644 --- a/runtime/battery-station/src/xcm_config/asset_registry.rs +++ b/runtime/battery-station/src/xcm_config/asset_registry.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, Currencies}; +use crate::{Balance, XcmAsset}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(Currencies, AssetMetadata), DispatchError> { + ) -> Result<(XcmAsset, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for Cust } fn post_register( - _id: Currencies, + _id: XcmAsset, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs index ef4a1da90..c68d81f3d 100644 --- a/runtime/battery-station/src/xcm_config/config.rs +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -51,10 +51,7 @@ use xcm_builder::{ TakeWeightCredit, }; use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; -use zeitgeist_primitives::{ - constants::BalanceFractionalDecimals, - types::{Asset, Currencies}, -}; +use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::XcmAsset}; pub mod battery_station { #[cfg(test)] @@ -214,7 +211,7 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, @@ -238,7 +235,7 @@ impl< return asset.clone(); }; - let currency = if let Ok(currency) = Currencies::try_from(asset_id) { + let currency = if let Ok(currency) = XcmAsset::try_from(asset_id) { currency } else { return asset.clone(); @@ -269,7 +266,7 @@ impl< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, @@ -350,22 +347,28 @@ pub struct AssetConvert; impl Convert> for AssetConvert { fn convert(id: Assets) -> Option { match id { - Asset::Ztg => Some(MultiLocation::new( + Assets::Ztg => Some(MultiLocation::new( 1, X2( Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(battery_station::KEY), ), )), - Asset::ForeignAsset(_) => { - let currency = Currencies::try_from(id).ok()?; - AssetRegistry::multilocation(¤cy).ok()? + Assets::ForeignAsset(_) => { + let asset = XcmAsset::try_from(id).ok()?; + AssetRegistry::multilocation(&asset).ok()? } _ => None, } } } +impl Convert> for AssetConvert { + fn convert(id: XcmAsset) -> Option { + >>::convert(id.into()) + } +} + /// Convert an incoming `MultiLocation` into a `Asset` if possible. /// Here we need to know the canonical representation of all the tokens we handle in order to /// correctly convert their `MultiLocation` representation into our internal `Asset` type. @@ -402,6 +405,13 @@ impl xcm_executor::traits::Convert for AssetConvert { } } +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { + >::convert(location) + .and_then(|asset| asset.try_into().map_err(|_| location)) + } +} + impl Convert> for AssetConvert { fn convert(asset: MultiAsset) -> Option { if let MultiAsset { id: Concrete(location), .. } = asset { diff --git a/runtime/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs index 29ae027ab..bc6178eee 100644 --- a/runtime/battery-station/src/xcm_config/fees.rs +++ b/runtime/battery-station/src/xcm_config/fees.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, Currencies}; +use crate::{Balance, XcmAsset}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -56,7 +56,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = Currencies, + AssetId = XcmAsset, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index b3072c061..ff2f0a0ea 100644 --- a/runtime/common/src/fees.rs +++ b/runtime/common/src/fees.rs @@ -95,7 +95,7 @@ macro_rules! impl_foreign_fees { // It does calculate foreign fees by extending transactions to include an optional // `AssetId` that specifies the asset to be used for payment (defaulting to the native // token on `None`), such that for each transaction the asset id can be specified. - // For real ZTG `None` is used and for DOT `Some(Currencies::Foreign(0))` is used. + // For real ZTG `None` is used and for DOT `Some(Currencies::ForeignAsset(0))` is used. pub(crate) fn calculate_fee( native_fee: Balance, @@ -118,7 +118,7 @@ macro_rules! impl_foreign_fees { #[cfg(feature = "parachain")] pub(crate) fn get_fee_factor( - currency: Currencies, + currency: XcmAsset, ) -> Result { let metadata = ::metadata(¤cy).ok_or( TransactionValidityError::Invalid(InvalidTransaction::Custom( @@ -142,7 +142,7 @@ macro_rules! impl_foreign_fees { ) -> Result { #[cfg(feature = "parachain")] { - let currency = Currencies::ForeignAsset(currency_id); + let currency = XcmAsset::ForeignAsset(currency_id); let fee_factor = get_fee_factor(currency)?; let converted_fee = calculate_fee(native_fee, fee_factor)?; Ok(converted_fee) @@ -387,7 +387,7 @@ macro_rules! fee_tests { }; let dot = Currencies::ForeignAsset(0); - assert_ok!(AssetRegistry::register_asset(RuntimeOrigin::root(), meta.clone(), Some(dot))); + assert_ok!(AssetRegistry::register_asset(RuntimeOrigin::root(), meta.clone(), Some(Assets::from(dot).try_into().unwrap()))); assert_ok!(>::deposit(dot, &Treasury::account_id(), BASE)); @@ -454,7 +454,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Currencies::ForeignAsset(dot_asset_id); + let dot = XcmAsset::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -476,7 +476,7 @@ macro_rules! fee_tests { { // no registering of dot assert_noop!( - get_fee_factor(Currencies::ForeignAsset(0)), + get_fee_factor(XcmAsset::ForeignAsset(0)), TransactionValidityError::Invalid(InvalidTransaction::Custom(2u8)) ); } @@ -505,7 +505,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Currencies::ForeignAsset(dot_asset_id); + let dot = XcmAsset::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -539,12 +539,12 @@ macro_rules! fee_tests { location: None, additional: custom_metadata, }; - let non_location_token = Currencies::ForeignAsset(1); + let non_location_token = XcmAsset::ForeignAsset(1); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), meta, - Some(non_location_token) + Some(Assets::from(non_location_token).try_into().unwrap()) )); assert_eq!(get_fee_factor(non_location_token).unwrap(), 10_393); @@ -579,7 +579,7 @@ macro_rules! fee_tests { assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), meta, - Some(dot) + Some(Assets::from(dot).try_into().unwrap()) )); let fees_and_tips = >::issue(dot, 0); diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index d23e3c007..dca2c2a76 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -566,7 +566,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl orml_asset_registry::Config for Runtime { - type AssetId = Currencies; + type AssetId = XcmAsset; type AssetProcessor = CustomAssetProcessor; type AuthorityOrigin = AsEnsureOriginWithArg; type Balance = Balance; @@ -621,7 +621,7 @@ macro_rules! impl_config_traits { type AccountIdToMultiLocation = AccountIdToMultiLocation; type Balance = Balance; type BaseXcmWeight = BaseXcmWeight; - type CurrencyId = Assets; + type CurrencyId = XcmAsset; type CurrencyIdConvert = AssetConvert; type RuntimeEvent = RuntimeEvent; type MaxAssetsForTransfer = MaxAssetsForTransfer; diff --git a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs index bfb31474e..525a2e369 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs @@ -27,7 +27,7 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Currencies, CustomMetadata}; +use zeitgeist_primitives::types::{CustomMetadata, XcmAsset}; pub(super) struct ExtBuilder { balances: Vec<(AccountId, Assets, Balance)>, @@ -106,11 +106,11 @@ pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const PARA_ID_SIBLING: u32 = 3000; /// IDs that are used to represent tokens from other chains -pub const FOREIGN_ZTG_ID: Currencies = Currencies::ForeignAsset(0); -pub const FOREIGN_PARENT_ID: Currencies = Currencies::ForeignAsset(1); -pub const FOREIGN_SIBLING_ID: Currencies = Currencies::ForeignAsset(2); -pub const BTC_ID: Currencies = Currencies::ForeignAsset(3); -pub const ETH_ID: Currencies = Currencies::ForeignAsset(4); +pub const FOREIGN_ZTG_ID: XcmAsset = XcmAsset::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: XcmAsset = XcmAsset::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: XcmAsset = XcmAsset::ForeignAsset(2); +pub const BTC_ID: XcmAsset = XcmAsset::ForeignAsset(3); +pub const ETH_ID: XcmAsset = XcmAsset::ForeignAsset(4); #[inline] pub(super) const fn ztg(amount: Balance) -> Balance { diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs index a687a5edd..d23021334 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -26,81 +26,96 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{general_key, zeitgeist, AssetConvert}, - Assets, + Assets, CustomMetadata, ScalarPosition, XcmAsset, }; - +use core::fmt::Debug; use frame_support::assert_err; use sp_runtime::traits::Convert as C2; +use test_case::test_case; use xcm::latest::{Junction::*, Junctions::*, MultiLocation}; use xcm_emulator::TestExt; use xcm_executor::traits::Convert as C1; -#[test] -fn convert_native() { +fn convert_common_native(expected: T) +where + T: Copy + Debug + PartialEq, + AssetConvert: C1 + C2>, +{ assert_eq!(zeitgeist::KEY.to_vec(), vec![0, 1]); // The way Ztg is represented relative within the Zeitgeist runtime let ztg_location_inner: MultiLocation = MultiLocation::new(0, X1(general_key(zeitgeist::KEY))); - assert_eq!(>::convert(ztg_location_inner), Ok(Assets::Ztg)); + assert_eq!(>::convert(ztg_location_inner), Ok(expected)); // The canonical way Ztg is represented out in the wild Zeitgeist::execute_with(|| { - assert_eq!( - >::convert(Assets::Ztg), - Some(foreign_ztg_multilocation()) - ) + assert_eq!(>::convert(expected), Some(foreign_ztg_multilocation())) }); } -#[test] -fn convert_any_registered_parent_multilocation() { +fn convert_common_non_native( + expected: T, + multilocation: MultiLocation, + register: fn(Option), +) where + T: Copy + Debug + PartialEq, + AssetConvert: C1 + C2>, +{ Zeitgeist::execute_with(|| { - assert_err!( - >::convert(foreign_parent_multilocation()), - foreign_parent_multilocation() - ); - - assert_eq!(>::convert(Assets::from(FOREIGN_PARENT_ID)), None); - + assert_err!(>::convert(multilocation), multilocation); + assert_eq!(>::convert(expected), None); // Register parent as foreign asset in the Zeitgeist parachain - register_foreign_parent(None); - - assert_eq!( - >::convert(foreign_parent_multilocation()), - Ok(FOREIGN_PARENT_ID.into()), - ); - - assert_eq!( - >::convert(Assets::from(FOREIGN_PARENT_ID)), - Some(foreign_parent_multilocation()) - ); + register(None); + assert_eq!(>::convert(multilocation), Ok(expected)); + assert_eq!(>::convert(expected), Some(multilocation)); }); } #[test] -fn convert_any_registered_sibling_multilocation() { - Zeitgeist::execute_with(|| { - assert_err!( - >::convert(foreign_sibling_multilocation()), - foreign_sibling_multilocation() - ); +fn convert_native_assets() { + convert_common_native(Assets::Ztg); +} - assert_eq!(>::convert(Assets::from(FOREIGN_SIBLING_ID)), None); +#[test] +fn convert_native_xcm_assets() { + convert_common_native(XcmAsset::Ztg); +} - // Register sibling as foreign asset in the Zeitgeist parachain - register_foreign_sibling(None); +#[test] +fn convert_any_registered_parent_multilocation_assets() { + convert_common_non_native( + Assets::from(FOREIGN_PARENT_ID), + foreign_parent_multilocation(), + register_foreign_parent, + ); +} - assert_eq!( - >::convert(foreign_sibling_multilocation()), - Ok(FOREIGN_SIBLING_ID.into()), - ); +#[test] +fn convert_any_registered_parent_multilocation_xcm_assets() { + convert_common_non_native( + XcmAsset::try_from(Assets::from(FOREIGN_PARENT_ID)).unwrap(), + foreign_parent_multilocation(), + register_foreign_parent, + ); +} - assert_eq!( - >::convert(Assets::from(FOREIGN_SIBLING_ID)), - Some(foreign_sibling_multilocation()) - ); - }); +#[test] +fn convert_any_registered_sibling_multilocation_assets() { + convert_common_non_native( + Assets::from(FOREIGN_SIBLING_ID), + foreign_sibling_multilocation(), + register_foreign_sibling, + ); +} + +#[test] +fn convert_any_registered_sibling_multilocation_xcm_assets() { + convert_common_non_native( + XcmAsset::try_from(Assets::from(FOREIGN_SIBLING_ID)).unwrap(), + foreign_sibling_multilocation(), + register_foreign_sibling, + ); } #[test] @@ -109,13 +124,46 @@ fn convert_unkown_multilocation() { MultiLocation::new(1, X2(Parachain(zeitgeist::ID), general_key(&[42]))); Zeitgeist::execute_with(|| { - assert!(>::convert(unknown_location).is_err()); + assert!(>::convert(unknown_location).is_err()); }); } -#[test] -fn convert_unsupported_currency() { - Zeitgeist::execute_with(|| { - assert_eq!(>::convert(Assets::PoolShare(42)), None) - }); +#[test_case( + Assets::CategoricalOutcome(7, 8); + "assets_categorical" +)] +#[test_case( + Assets::ScalarOutcome(7, ScalarPosition::Long); + "assets_scalar" +)] +#[test_case( + Assets::PoolShare(7); + "assets_pool_share" +)] +#[test_case( + Assets::ForeignAsset(7); + "assets_foreign" +)] +#[test_case( + Assets::ParimutuelShare(7, 8); + "assets_parimutuel_share" +)] +#[test_case( + Assets::CampaignAsset(7); + "assets_campaign_asset" +)] +#[test_case( + Assets::CustomAsset(7); + "assets_custom_asset" +)] +#[test_case( + XcmAsset::ForeignAsset(7); + "xcm_assets_foreign" +)] +fn convert_unsupported_asset(asset: T) +where + T: Copy + Debug + PartialEq, + AssetConvert: C2>, +{ + Zeitgeist::execute_with(|| assert_eq!(>::convert(asset), None)); } diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs index 0222c99a3..bb5d66e3a 100644 --- a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs @@ -26,7 +26,7 @@ use crate::{ test_net::{PolkadotNet, Sibling, TestNet, Zeitgeist}, }, xcm_config::{config::zeitgeist, fees::default_per_second}, - AssetRegistry, Assets, Balance, Balances, RuntimeOrigin, Tokens, XTokens, + AssetManager, AssetRegistry, Balance, Balances, RuntimeOrigin, XTokens, ZeitgeistTreasuryAccount, }; @@ -36,7 +36,7 @@ use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, WeightLimi use xcm_emulator::TestExt; use zeitgeist_primitives::{ constants::{BalanceFractionalDecimals, BASE}, - types::{CustomMetadata, XcmMetadata}, + types::{CustomMetadata, XcmAsset, XcmMetadata}, }; #[test] @@ -49,8 +49,8 @@ fn transfer_ztg_to_sibling() { Sibling::execute_with(|| { treasury_initial_balance = - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), 0); + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), 0); register_foreign_ztg(None); }); @@ -59,7 +59,7 @@ fn transfer_ztg_to_sibling() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - Assets::Ztg, + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -82,14 +82,14 @@ fn transfer_ztg_to_sibling() { }); Sibling::execute_with(|| { - let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB); + let current_balance = AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB); // Verify that BOB now has (amount transferred - fee) assert_eq!(current_balance, transfer_amount - ztg_fee()); // Verify that fees (of foreign currency) have been put into treasury assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()), treasury_initial_balance + ztg_fee() ) }); @@ -123,10 +123,10 @@ fn transfer_ztg_sibling_to_zeitgeist() { Sibling::execute_with(|| { assert_eq!(Balances::free_balance(zeitgeist_parachain_account()), 0); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), bob_initial_balance); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), bob_initial_balance); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(BOB), - FOREIGN_ZTG_ID.into(), + FOREIGN_ZTG_ID, transfer_amount, Box::new( MultiLocation::new( @@ -143,7 +143,7 @@ fn transfer_ztg_sibling_to_zeitgeist() { // Confirm that Bobs's balance is initial balance - amount transferred assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), bob_initial_balance - transfer_amount ); }); @@ -181,8 +181,12 @@ fn transfer_btc_sibling_to_zeitgeist() { 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,); + treasury_initial_balance = + AssetManager::free_balance(BTC_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!( + AssetManager::free_balance(BTC_ID.into(), &ALICE), + zeitgeist_alice_initial_balance, + ); }); Sibling::execute_with(|| { @@ -196,8 +200,8 @@ fn transfer_btc_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret Assets::Ztg as BTC in this context. - Assets::Ztg, + // Target chain will interpret XcmAsset::Ztg as BTC in this context. + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -228,13 +232,13 @@ fn transfer_btc_sibling_to_zeitgeist() { // Verify that remote Alice now has initial balance + amount transferred - fee assert_eq!( - Tokens::free_balance(BTC_ID, &ALICE), + AssetManager::free_balance(BTC_ID.into(), &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()), + AssetManager::free_balance(BTC_ID.into(), &ZeitgeistTreasuryAccount::get()), // Align decimal fractional places treasury_initial_balance + adjusted_balance(btc(1), btc_fee()) ) @@ -252,13 +256,13 @@ fn transfer_btc_zeitgeist_to_sibling() { transfer_btc_sibling_to_zeitgeist(); Sibling::execute_with(|| { - assert_eq!(Tokens::free_balance(BTC_ID, &BOB), sibling_bob_initial_balance,); + assert_eq!(AssetManager::free_balance(BTC_ID.into(), &BOB), sibling_bob_initial_balance,); }); Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - BTC_ID.into(), + BTC_ID, transfer_amount, Box::new( MultiLocation::new( @@ -274,7 +278,7 @@ fn transfer_btc_zeitgeist_to_sibling() { )); // Confirm that Alice's balance is initial_balance - amount_transferred - assert_eq!(Tokens::free_balance(BTC_ID, &ALICE), 0); + assert_eq!(AssetManager::free_balance(BTC_ID.into(), &ALICE), 0); }); Sibling::execute_with(|| { @@ -304,8 +308,12 @@ fn transfer_eth_sibling_to_zeitgeist() { 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,); + treasury_initial_balance = + AssetManager::free_balance(ETH_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!( + AssetManager::free_balance(ETH_ID.into(), &ALICE), + zeitgeist_alice_initial_balance, + ); }); Sibling::execute_with(|| { @@ -325,8 +333,8 @@ fn transfer_eth_sibling_to_zeitgeist() { )); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - // Target chain will interpret Assets::Ztg as ETH in this context. - Assets::Ztg, + // Target chain will interpret XcmAsset::Ztg as ETH in this context. + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -357,13 +365,13 @@ fn transfer_eth_sibling_to_zeitgeist() { // Verify that remote Alice now has initial balance + amount transferred - fee assert_eq!( - Tokens::free_balance(ETH_ID, &ALICE), + AssetManager::free_balance(ETH_ID.into(), &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()), + AssetManager::free_balance(ETH_ID.into(), &ZeitgeistTreasuryAccount::get()), // Align decimal fractional places treasury_initial_balance + adjusted_balance(eth(1), eth_fee()) ) @@ -381,13 +389,13 @@ fn transfer_eth_zeitgeist_to_sibling() { transfer_eth_sibling_to_zeitgeist(); Sibling::execute_with(|| { - assert_eq!(Tokens::free_balance(ETH_ID, &BOB), sibling_bob_initial_balance,); + assert_eq!(AssetManager::free_balance(ETH_ID.into(), &BOB), sibling_bob_initial_balance,); }); Zeitgeist::execute_with(|| { assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - ETH_ID.into(), + ETH_ID, transfer_amount, Box::new( MultiLocation::new( @@ -403,7 +411,7 @@ fn transfer_eth_zeitgeist_to_sibling() { )); // Confirm that Alice's balance is initial_balance - amount_transferred - assert_eq!(Tokens::free_balance(ETH_ID, &ALICE), 0); + assert_eq!(AssetManager::free_balance(ETH_ID.into(), &ALICE), 0); }); Sibling::execute_with(|| { @@ -445,7 +453,10 @@ fn transfer_dot_from_relay_chain() { }); Zeitgeist::execute_with(|| { - assert_eq!(Tokens::free_balance(FOREIGN_PARENT_ID, &BOB), transfer_amount - dot_fee()); + assert_eq!( + AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &BOB), + transfer_amount - dot_fee() + ); }); } @@ -457,12 +468,12 @@ fn transfer_dot_to_relay_chain() { transfer_dot_from_relay_chain(); Zeitgeist::execute_with(|| { - let initial_balance = Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE); + let initial_balance = AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ALICE); assert!(initial_balance >= transfer_amount); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - FOREIGN_PARENT_ID.into(), + FOREIGN_PARENT_ID, transfer_amount, Box::new( MultiLocation::new(1, X1(Junction::AccountId32 { id: BOB.into(), network: None })) @@ -472,7 +483,7 @@ fn transfer_dot_to_relay_chain() { )); assert_eq!( - Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE), + AssetManager::free_balance(FOREIGN_PARENT_ID.into(), &ALICE), initial_balance - transfer_amount ) }); @@ -494,8 +505,8 @@ fn transfer_ztg_to_sibling_with_custom_fee() { Sibling::execute_with(|| { treasury_initial_balance = - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); - assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB), 0); + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()); + assert_eq!(AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB), 0); register_foreign_ztg(None); let custom_metadata = CustomMetadata { @@ -518,7 +529,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { assert_eq!(Balances::free_balance(sibling_parachain_account()), 0); assert_ok!(XTokens::transfer( RuntimeOrigin::signed(ALICE), - Assets::Ztg, + XcmAsset::Ztg, transfer_amount, Box::new( MultiLocation::new( @@ -541,7 +552,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { }); Sibling::execute_with(|| { - let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB); + let current_balance = AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &BOB); let custom_fee = calc_fee(default_per_second(10) * 10); // Verify that BOB now has (amount transferred - fee) @@ -552,7 +563,7 @@ fn transfer_ztg_to_sibling_with_custom_fee() { // Verify that fees (of foreign currency) have been put into treasury assert_eq!( - Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + AssetManager::free_balance(FOREIGN_ZTG_ID.into(), &ZeitgeistTreasuryAccount::get()), treasury_initial_balance + custom_fee ) }); diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 3ba53e6f5..38ad05703 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -517,7 +517,7 @@ parameter_type_with_key! { Currencies::ForeignAsset(id) => { let maybe_metadata = < orml_asset_registry::Pallet as orml_traits::asset_registry::Inspect - >::metadata(&Currencies::ForeignAsset(*id)); + >::metadata(&XcmAsset::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; diff --git a/runtime/zeitgeist/src/xcm_config/asset_registry.rs b/runtime/zeitgeist/src/xcm_config/asset_registry.rs index 20d23e90d..9deb85aa0 100644 --- a/runtime/zeitgeist/src/xcm_config/asset_registry.rs +++ b/runtime/zeitgeist/src/xcm_config/asset_registry.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, Currencies}; +use crate::{Balance, XcmAsset}; use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -29,11 +29,11 @@ use zeitgeist_primitives::types::CustomMetadata; /// Only pre check is to ensure an asset id was passed. pub struct CustomAssetProcessor; -impl AssetProcessor> for CustomAssetProcessor { +impl AssetProcessor> for CustomAssetProcessor { fn pre_register( - id: Option, + id: Option, metadata: AssetMetadata, - ) -> Result<(Currencies, AssetMetadata), DispatchError> { + ) -> Result<(XcmAsset, AssetMetadata), DispatchError> { match id { Some(id) => Ok((id, metadata)), None => Err(DispatchError::Other("asset-registry: AssetId is required")), @@ -41,7 +41,7 @@ impl AssetProcessor> for Cust } fn post_register( - _id: Currencies, + _id: XcmAsset, _asset_metadata: AssetMetadata, ) -> Result<(), DispatchError> { Ok(()) diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs index 6bcc84d60..27d6c1e54 100644 --- a/runtime/zeitgeist/src/xcm_config/config.rs +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -53,10 +53,7 @@ use xcm_builder::{ TakeWeightCredit, }; use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; -use zeitgeist_primitives::{ - constants::BalanceFractionalDecimals, - types::{Asset, Currencies}, -}; +use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::XcmAsset}; pub mod zeitgeist { #[cfg(test)] @@ -216,7 +213,7 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, @@ -240,7 +237,7 @@ impl< return asset.clone(); }; - let currency = if let Ok(currency) = Currencies::try_from(asset_id) { + let currency = if let Ok(currency) = XcmAsset::try_from(asset_id) { currency } else { return asset.clone(); @@ -271,7 +268,7 @@ impl< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, @@ -352,22 +349,28 @@ pub struct AssetConvert; impl Convert> for AssetConvert { fn convert(id: Assets) -> Option { match id { - Asset::Ztg => Some(MultiLocation::new( + Assets::Ztg => Some(MultiLocation::new( 1, X2( Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(zeitgeist::KEY), ), )), - Asset::ForeignAsset(_) => { - let currency = Currencies::try_from(id).ok()?; - AssetRegistry::multilocation(¤cy).ok()? + Assets::ForeignAsset(_) => { + let asset = XcmAsset::try_from(id).ok()?; + AssetRegistry::multilocation(&asset).ok()? } _ => None, } } } +impl Convert> for AssetConvert { + fn convert(id: XcmAsset) -> Option { + >>::convert(id.into()) + } +} + /// Convert an incoming `MultiLocation` into a `Asset` if possible. /// Here we need to know the canonical representation of all the tokens we handle in order to /// correctly convert their `MultiLocation` representation into our internal `Asset` type. @@ -404,6 +407,13 @@ impl xcm_executor::traits::Convert for AssetConvert { } } +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { + >::convert(location) + .and_then(|asset| asset.try_into().map_err(|_| location)) + } +} + impl Convert> for AssetConvert { fn convert(asset: MultiAsset) -> Option { if let MultiAsset { id: Concrete(location), .. } = asset { diff --git a/runtime/zeitgeist/src/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs index ba3d7d56a..0b2f18661 100644 --- a/runtime/zeitgeist/src/xcm_config/fees.rs +++ b/runtime/zeitgeist/src/xcm_config/fees.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{Balance, Currencies}; +use crate::{Balance, XcmAsset}; use core::marker::PhantomData; use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}; use xcm::latest::MultiLocation; @@ -56,7 +56,7 @@ pub struct FixedConversionRateProvider(PhantomData impl< AssetRegistry: orml_traits::asset_registry::Inspect< - AssetId = Currencies, + AssetId = XcmAsset, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index eae7a1789..1ef50ebe8 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -543,7 +543,7 @@ impl pallet_treasury::Config for Runtime { #[cfg(feature = "parachain")] zrml_prediction_markets::impl_mock_registry! { MockRegistry, - Currencies, + zeitgeist_primitives::types::XcmAsset, Balance, zeitgeist_primitives::types::CustomMetadata } diff --git a/zrml/neo-swaps/src/tests/join.rs b/zrml/neo-swaps/src/tests/join.rs index f1dde0de8..7107bf1ac 100644 --- a/zrml/neo-swaps/src/tests/join.rs +++ b/zrml/neo-swaps/src/tests/join.rs @@ -208,6 +208,7 @@ fn join_fails_on_insufficient_funds() { vec![_1_2, _1_2], CENT, ); + assert_noop!( NeoSwaps::join( RuntimeOrigin::signed(ALICE), diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index 27bdb2b93..cde97ae57 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -301,7 +301,7 @@ mod pallet { let asset = order_data.maker_asset; let amount = order_data.maker_amount; - if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), asset, &maker) + if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), asset, maker) .is_zero() { let missing = diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 9e6d729c6..e4d2c405c 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -58,7 +58,7 @@ mod pallet { #[cfg(feature = "parachain")] use { orml_traits::asset_registry::Inspect as RegistryInspect, - zeitgeist_primitives::types::{CurrencyClass, CustomMetadata}, + zeitgeist_primitives::types::{CustomMetadata, XcmAsset}, }; use orml_traits::{MultiCurrency, NamedMultiReservableCurrency}; @@ -1562,7 +1562,7 @@ mod pallet { #[cfg(feature = "parachain")] type AssetRegistry: RegistryInspect< - AssetId = CurrencyClass>, + AssetId = XcmAsset, Balance = BalanceOf, CustomMetadata = CustomMetadata, >; @@ -2962,10 +2962,7 @@ mod pallet { BaseAsset::Ztg => true, #[cfg(feature = "parachain")] BaseAsset::ForeignAsset(id) => { - if let Some(metadata) = - T::AssetRegistry::metadata(&CurrencyClass::>::ForeignAsset( - id, - )) + if let Some(metadata) = T::AssetRegistry::metadata(&XcmAsset::ForeignAsset(id)) { metadata.additional.allow_as_base_asset } else { diff --git a/zrml/prediction-markets/src/mock.rs b/zrml/prediction-markets/src/mock.rs index d30a2a962..edfc86a3f 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -68,6 +68,7 @@ use zeitgeist_primitives::{ AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CampaignAsset, CampaignAssetClass, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, Hash, Index, MarketAsset, MarketId, Moment, ResultWithWeightInfo, UncheckedExtrinsicTest, + XcmAsset, }, }; @@ -333,7 +334,7 @@ impl orml_tokens::Config for Runtime { #[cfg(feature = "parachain")] crate::orml_asset_registry::impl_mock_registry! { MockRegistry, - Currencies, + XcmAsset, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -644,7 +645,7 @@ impl ExtBuilder { orml_asset_registry_mock::GenesisConfig { metadata: vec![ ( - Currencies::ForeignAsset(100), + XcmAsset::ForeignAsset(100), AssetMetadata { decimals: 18, name: "ACALA USD".as_bytes().to_vec(), @@ -655,7 +656,7 @@ impl ExtBuilder { }, ), ( - Currencies::ForeignAsset(420), + XcmAsset::ForeignAsset(420), AssetMetadata { decimals: 18, name: "FANCY_TOKEN".as_bytes().to_vec(), From 6b1aef6b5e2a72b44324db7fd704916ff280cf5b Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 2 Apr 2024 14:46:28 +0530 Subject: [PATCH 12/14] New Asset System - Enable fee payment for CampaignAssets (#1289) * 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 * Remove migrations (#1180) * Filter admin functions for main-net (#1190) filter admin functions for main-net * Add debug assertions for slashes and reserves (#1188) * add debug assertions for missing slashes * place debug_assert for unreserves * Add some verify checks to court (#1187) add some verify checks to court * Bypass battery stations contracts call filter for court, parimutuel, order book markets (#1185) update contracts call filter * Fix failing court benchmark (#1191) * fix court assertion for resolve_at * remove unnecessary variable * mirror mock and actual impl for DisputeResolution * 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 * Modify court events for indexer (#1182) * modify events for indexer * gracefully handle panicers * handle binary search by key error * improve style * Ensure MinBetSize after fee (#1193) * handle own existential deposit errors * use require_transactional * correct benchmark and test min bet size amounts * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Remove court and global disputes from call filter for the main-net (#1226) * remove from call filter * update copyright * 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 * 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> * 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> * Update style guide to streamline reviews (#1228) * 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. * Set inflation to more than zero for a full benchmark of handle_inflation (#1234) Update benchmarks.rs * 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 * 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 * Utilize Merigify's Merge Queue (#1243) ci(Mergify): configuration update Signed-off-by: Harald Heckmann * 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> * Avoid mergify dequeue (#1245) * Avoid mergify deque * Set review needed only shortly after Mergify commit * 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> * Remove migrations and dead code (#1241) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * 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> * 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> * Fix Rust and Discord badge (#1247) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Fix neo-swaps doc strings (#1250) * Improve `SellExecuted` documentation * Clean up math in comments and doc strings * Fix formatting * 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 * Merge Old* and New* asset variants * Partially integrate lazy migration routing * Integrate lazy migration routing * Fix ExistentialDeposit mapping & Satisfy Clippy * Remove primitives/macros * Filter certain asset destroy calls (they're managed) * Integrate asset destruction into prediction markets * Add BaseAssetClass * Update prediction-markets & market-commons * Update authorized * Update liquidity-mining * Update simple-disputes * Update parimutuels (wip) * Move functions into market struct and properly delete assets * Add ParimutuelAssetClass * Add parimutuel.rs * Remove duplicate code * Adjust asset type and managed destroy after refund in parimutuels * Add MarketTransitionApi This will allow prediction-markets to signal other pallets that state transitions happened * Add MarketTransitionApi * Implement MarketTransitionApi for Parimutuels * Partially implement asset creation/destruction in parimutuels * Add all asset creation / destruction tests to parimutuels * Adjust Court * Update global-disputes * Update swaps * Update neo-swaps * Integrate OnStateTransition hooks into prediction-markets * Use proper state transition weights * Make cargo check succeed * Partially update orderbook * Update orderbook * Update orderbook * Finalize orderbook update * Improve style * Add XcmAssetClass * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Implement support for non-reservable assets in orderbook * Integrate new asset system into Battery Station XCM * Integrate new asset system into Zeitgeist XCM * Implement Unbalanced for asset-router * Enable fee payment for campaign assets * Fix conditional import * Format * Fix unfillable / unremovable order bug Co-authored-by: Chralt * Add tests for Unbalanced impl * Implement fee charging tests * Undo unnecessray change * Undo last commit * Better case-distinction and explicitly drop campaign asset fee Co-authored-by: Chralt * Use is_foreign_asset() * Avoid panic * Format * Fix CI --------- Signed-off-by: Harald Heckmann Co-authored-by: Chralt Co-authored-by: Malte Kliemann Co-authored-by: Chralt98 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- primitives/src/assets/currencies.rs | 6 + runtime/battery-station/src/parameters.rs | 2 + runtime/common/src/fees.rs | 428 ++++++++++++------ runtime/common/src/lib.rs | 4 +- runtime/zeitgeist/src/parameters.rs | 2 + zrml/asset-router/src/lib.rs | 9 +- zrml/asset-router/src/macros.rs | 25 + zrml/asset-router/src/mock.rs | 8 +- zrml/asset-router/src/pallet_impl/inspect.rs | 4 +- zrml/asset-router/src/pallet_impl/mod.rs | 1 + .../src/pallet_impl/unbalanced.rs | 67 +++ zrml/asset-router/src/tests/mod.rs | 1 + zrml/asset-router/src/tests/unbalanced.rs | 125 +++++ zrml/orderbook/src/lib.rs | 3 +- 14 files changed, 531 insertions(+), 154 deletions(-) create mode 100644 zrml/asset-router/src/pallet_impl/unbalanced.rs create mode 100644 zrml/asset-router/src/tests/unbalanced.rs diff --git a/primitives/src/assets/currencies.rs b/primitives/src/assets/currencies.rs index 9ec2e0bba..d29359d43 100644 --- a/primitives/src/assets/currencies.rs +++ b/primitives/src/assets/currencies.rs @@ -43,6 +43,12 @@ pub enum CurrencyClass { ParimutuelShare(MI, CategoryIndex), } +impl CurrencyClass { + pub fn is_foreign_asset(&self) -> bool { + matches!(self, Self::ForeignAsset(_)) + } +} + impl Default for CurrencyClass { fn default() -> Self { Self::ForeignAsset(u32::default()) diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index e3f43ce33..d5b65e90d 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -402,6 +402,8 @@ parameter_types! { .build_or_panic(); // Transaction payment + /// A fee multiplier applied to the calculated fee factor for `CampaignAsset` + pub const CampaignAssetFeeMultiplier: u32 = 100; /// A fee mulitplier for Operational extrinsics to compute “virtual tip” /// to boost their priority. pub const OperationalFeeMultiplier: u8 = 5; diff --git a/runtime/common/src/fees.rs b/runtime/common/src/fees.rs index ff2f0a0ea..6facbdbb1 100644 --- a/runtime/common/src/fees.rs +++ b/runtime/common/src/fees.rs @@ -40,9 +40,8 @@ macro_rules! impl_fee_types { } pub struct DealWithForeignFees; - - impl OnUnbalanced> for DealWithForeignFees { - fn on_unbalanced(fees_and_tips: CreditOf) { + impl OnUnbalanced> for DealWithForeignFees { + fn on_unbalanced(fees_and_tips: CreditOf) { // We have to manage the mint / burn ratio on the Zeitgeist chain, // but we do not have the responsibility and necessary knowledge to // manage the mint / burn ratio for any other chain. @@ -51,7 +50,7 @@ macro_rules! impl_fee_types { // on_unbalanced is not implemented for other currencies than the native currency // https://github.com/paritytech/substrate/blob/85415fb3a452dba12ff564e6b093048eed4c5aad/frame/treasury/src/lib.rs#L618-L627 // https://github.com/paritytech/substrate/blob/5ea6d95309aaccfa399c5f72e5a14a4b7c6c4ca1/frame/treasury/src/lib.rs#L490 - let res = >::resolve( + let res = AssetRouter::resolve( &TreasuryPalletId::get().into_account_truncating(), fees_and_tips, ); @@ -64,6 +63,8 @@ macro_rules! impl_fee_types { #[macro_export] macro_rules! impl_foreign_fees { () => { + #[cfg(feature = "parachain")] + use frame_support::ensure; use frame_support::{ pallet_prelude::InvalidTransaction, traits::{ @@ -81,7 +82,10 @@ macro_rules! impl_foreign_fees { }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::{Convert, DispatchInfoOf, PostDispatchInfoOf}; - use zeitgeist_primitives::{math::fixed::FixedMul, types::TxPaymentAssetId}; + use zeitgeist_primitives::{ + math::fixed::{FixedDiv, FixedMul}, + types::Assets, + }; #[repr(u8)] pub enum CustomTxError { @@ -90,6 +94,7 @@ macro_rules! impl_foreign_fees { NoAssetMetadata = 2, NoFeeFactor = 3, NonForeignAssetPaid = 4, + InvalidAssetType = 5, } // It does calculate foreign fees by extending transactions to include an optional @@ -116,15 +121,64 @@ macro_rules! impl_foreign_fees { Ok(converted_fee) } - #[cfg(feature = "parachain")] - pub(crate) fn get_fee_factor( - currency: XcmAsset, + fn get_fee_factor_campaign_asset( + campaign_asset: CampaignAsset, ) -> Result { - let metadata = ::metadata(¤cy).ok_or( + let ztg_supply = Balances::total_issuance(); + let campaign_asset_supply = AssetManager::total_issuance(campaign_asset.into()); + let fee_multiplier = Balance::from(CampaignAssetFeeMultiplier::get()); + + let ztg_div_campaign_supply = ztg_supply.checked_div(campaign_asset_supply).ok_or( TransactionValidityError::Invalid(InvalidTransaction::Custom( - CustomTxError::NoAssetMetadata as u8, + CustomTxError::FeeConversionArith as u8, )), )?; + + // Use neutral fee multiplier if the ZTG supply is 100x greater than the campaign + // asset supply. + if ztg_div_campaign_supply >= fee_multiplier { + Ok(BASE) + } else { + campaign_asset_supply.saturating_mul(fee_multiplier).bdiv(ztg_supply).map_err( + |_| { + TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::FeeConversionArith as u8, + )) + }, + ) + } + } + + #[cfg(not(feature = "parachain"))] + fn get_fee_factor_foreign_asset( + _foreign_asset: Currencies, + ) -> Result { + Err(TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::NoForeignAssetsOnStandaloneChain as u8, + ))) + } + + #[cfg(feature = "parachain")] + fn get_fee_factor_foreign_asset( + foreign_asset: Currencies, + ) -> Result { + ensure!( + foreign_asset.is_foreign_asset(), + TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::InvalidAssetType as u8, + )) + ); + let metadata_asset: XcmAsset = + Assets::from(foreign_asset).try_into().map_err(|_| { + TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::InvalidAssetType as u8, + )) + })?; + + let metadata = ::metadata(&metadata_asset) + .ok_or(TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::NoAssetMetadata as u8, + )))?; let fee_factor = metadata.additional.xcm.fee_factor.ok_or(TransactionValidityError::Invalid( InvalidTransaction::Custom(CustomTxError::NoFeeFactor as u8), @@ -132,47 +186,54 @@ macro_rules! impl_foreign_fees { Ok(fee_factor) } + pub(crate) fn get_fee_factor(asset: Assets) -> Result { + if let Ok(campaign_asset) = CampaignAsset::try_from(asset) { + return get_fee_factor_campaign_asset(campaign_asset); + } else if let Ok(currency) = Currencies::try_from(asset) { + return get_fee_factor_foreign_asset(currency); + } + + Err(TransactionValidityError::Invalid(InvalidTransaction::Custom( + CustomTxError::InvalidAssetType as u8, + ))) + } + pub struct TTCBalanceToAssetBalance; - impl BalanceConversion for TTCBalanceToAssetBalance { + impl BalanceConversion for TTCBalanceToAssetBalance { type Error = TransactionValidityError; fn to_asset_balance( native_fee: Balance, - currency_id: TxPaymentAssetId, + asset: Assets, ) -> Result { - #[cfg(feature = "parachain")] - { - let currency = XcmAsset::ForeignAsset(currency_id); - let fee_factor = get_fee_factor(currency)?; - let converted_fee = calculate_fee(native_fee, fee_factor)?; - Ok(converted_fee) - } - #[cfg(not(feature = "parachain"))] - { - Err(TransactionValidityError::Invalid(InvalidTransaction::Custom( - CustomTxError::NoForeignAssetsOnStandaloneChain as u8, - ))) - } + let fee_factor = get_fee_factor(asset)?; + let converted_fee = calculate_fee(native_fee, fee_factor)?; + Ok(converted_fee) } } pub struct TTCHandleCredit; - impl HandleCredit for TTCHandleCredit { - fn handle_credit(final_fee: CreditOf) { - // Handle the final fee and tip, e.g. by transferring to the treasury. - DealWithForeignFees::on_unbalanced(final_fee); + impl HandleCredit for TTCHandleCredit { + fn handle_credit(final_fee: CreditOf) { + let asset = final_fee.asset(); + + if CampaignAsset::try_from(asset).is_ok() { + drop(final_fee); + } else if Currencies::try_from(asset).is_ok() { + DealWithForeignFees::on_unbalanced(final_fee); + } } } - pub struct TokensTxCharger; - impl pallet_asset_tx_payment::OnChargeAssetTransaction for TokensTxCharger { - type AssetId = TxPaymentAssetId; + pub struct TxCharger; + impl pallet_asset_tx_payment::OnChargeAssetTransaction for TxCharger { + type AssetId = Assets; type Balance = Balance; - type LiquidityInfo = CreditOf; + type LiquidityInfo = CreditOf; fn withdraw_fee( who: &AccountId, - call: &RuntimeCall, + _call: &RuntimeCall, _dispatch_info: &DispatchInfoOf, asset_id: Self::AssetId, native_fee: Self::Balance, @@ -186,13 +247,13 @@ macro_rules! impl_foreign_fees { let converted_fee = TTCBalanceToAssetBalance::to_asset_balance(native_fee, asset_id)? .max(min_converted_fee); - let currency_id = Currencies::ForeignAsset(asset_id); + let can_withdraw = - >::can_withdraw(currency_id, who, converted_fee); + >::can_withdraw(asset_id, who, converted_fee); if can_withdraw != WithdrawConsequence::Success { return Err(InvalidTransaction::Payment.into()); } - >::withdraw(currency_id, who, converted_fee) + >::withdraw(asset_id, who, converted_fee) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) } @@ -201,35 +262,23 @@ macro_rules! impl_foreign_fees { _dispatch_info: &DispatchInfoOf, _post_info: &PostDispatchInfoOf, corrected_native_fee: Self::Balance, - tip: Self::Balance, + _tip: Self::Balance, paid: Self::LiquidityInfo, ) -> Result<(), TransactionValidityError> { let min_converted_fee = if corrected_native_fee.is_zero() { Zero::zero() } else { One::one() }; - let asset_id = match paid.asset() { - Currencies::ForeignAsset(asset_id) => asset_id, - _ => { - return Err(TransactionValidityError::Invalid(InvalidTransaction::Custom( - CustomTxError::NonForeignAssetPaid as u8, - ))); - } - }; + + let asset = paid.asset(); // Convert the corrected fee and tip into the asset used for payment. let converted_fee = - TTCBalanceToAssetBalance::to_asset_balance(corrected_native_fee, asset_id)? + TTCBalanceToAssetBalance::to_asset_balance(corrected_native_fee, asset)? .max(min_converted_fee); - let converted_tip = TTCBalanceToAssetBalance::to_asset_balance(tip, asset_id)?; - // Calculate how much refund we should return. - let (final_fee, refund) = paid.split(converted_fee); // Refund to the account that paid the fees. If this fails, the account might have dropped // below the existential balance. In that case we don't refund anything. - let _ = >::resolve(who, refund); - - // Handle the final fee and tip, e.g. by transferring to the treasury. - // Note: The `corrected_native_fee` already includes the `tip`. + let (final_fee, refund) = paid.split(converted_fee); + let _ = AssetRouter::resolve(who, refund); TTCHandleCredit::handle_credit(final_fee); - Ok(()) } } @@ -287,7 +336,12 @@ macro_rules! impl_market_creator_fees { macro_rules! fee_tests { () => { use crate::*; - use frame_support::{assert_noop, assert_ok, dispatch::DispatchClass, weights::Weight}; + use frame_support::{ + assert_noop, assert_ok, + dispatch::DispatchClass, + traits::{fungible::Unbalanced, fungibles::Create}, + weights::Weight, + }; use orml_traits::MultiCurrency; use pallet_asset_tx_payment::OnChargeAssetTransaction; use sp_core::H256; @@ -330,21 +384,126 @@ macro_rules! fee_tests { frame_system::GenesisConfig::default().build_storage::().unwrap().into(); t.execute_with(|| { let fee_and_tip_balance = 10 * ExistentialDeposit::get(); - let fees_and_tips = >::issue( - Currencies::ForeignAsset(0), - fee_and_tip_balance, - ); + let fees_and_tips = AssetRouter::issue(Asset::ForeignAsset(0), fee_and_tip_balance); assert!( - Tokens::free_balance(Currencies::ForeignAsset(0), &Treasury::account_id()).is_zero() + AssetRouter::free_balance(Asset::ForeignAsset(0), &Treasury::account_id()) + .is_zero() ); DealWithForeignFees::on_unbalanced(fees_and_tips); assert_eq!( - Tokens::free_balance(Currencies::ForeignAsset(0), &Treasury::account_id()), + AssetRouter::free_balance(Asset::ForeignAsset(0), &Treasury::account_id()), fee_and_tip_balance, ); }); } + #[test] + fn fee_payment_campaign_assets_withdraws_correct_amount() { + let mut t: sp_io::TestExternalities = + frame_system::GenesisConfig::default().build_storage::().unwrap().into(); + t.execute_with(|| { + let asset = Asset::CampaignAsset(0); + let alice = AccountId::from([0u8; 32]); + let initial_balance: Balance = 1_000_000_000_000; + let native_fee: Balance = 1_000_000; + let fee_multiplier: Balance = CampaignAssetFeeMultiplier::get().into(); + + let ztg_supply = initial_balance * fee_multiplier - 1; + let fee_factor = + initial_balance.saturating_mul(fee_multiplier).bdiv(ztg_supply).unwrap(); + let expected_fee = calculate_fee(native_fee, fee_factor).unwrap(); + let mock_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + + Balances::set_total_issuance(ztg_supply); + assert_ok!(AssetRouter::create(asset, alice.clone(), true, 1)); + assert_ok!(AssetManager::deposit(asset, &alice, initial_balance)); + + assert_eq!( + TxCharger::withdraw_fee( + &alice, + &mock_call, + &Default::default(), + asset, + native_fee, + 0 + ) + .unwrap() + .peek(), + expected_fee + ); + assert_eq!( + AssetManager::total_balance(asset, &alice), + initial_balance - expected_fee + ); + }); + } + + fn campaign_asset_throttled_fee_common() -> CreditOf { + let asset = Asset::CampaignAsset(0); + let alice = AccountId::from([0u8; 32]); + let initial_balance: Balance = 1_000_000_000_000; + let native_fee: Balance = 1_000_000; + let fee_multiplier: Balance = CampaignAssetFeeMultiplier::get().into(); + + let ztg_supply = initial_balance.bmul(fee_multiplier * initial_balance + 1).unwrap(); + let mock_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); + + Balances::set_total_issuance(ztg_supply); + assert_ok!(AssetRouter::create(asset, alice.clone(), true, 1)); + assert_ok!(AssetManager::deposit(asset, &alice, initial_balance)); + + let withdrawn = TxCharger::withdraw_fee( + &alice, + &mock_call, + &Default::default(), + asset, + native_fee, + 0, + ) + .unwrap(); + assert_eq!(withdrawn.peek(), native_fee); + assert_eq!(AssetManager::total_balance(asset, &alice), initial_balance - native_fee); + + withdrawn + } + + #[test] + fn fee_payment_campaign_assets_withdraws_correct_amount_throttled() { + let mut t: sp_io::TestExternalities = + frame_system::GenesisConfig::default().build_storage::().unwrap().into(); + t.execute_with(|| { + let _ = campaign_asset_throttled_fee_common(); + }); + } + + #[test] + fn fee_payment_campaign_assets_corrects_reimburses_and_burns_fees_properly() { + let mut t: sp_io::TestExternalities = + frame_system::GenesisConfig::default().build_storage::().unwrap().into(); + t.execute_with(|| { + let asset = Asset::CampaignAsset(0); + let withdrawn = campaign_asset_throttled_fee_common(); + let amount = withdrawn.peek(); + let native_fee_adjusted: Balance = 1_000_000 / 2; + let alice = AccountId::from([0u8; 32]); + let initial_balance: Balance = 1_000_000_000_000; + let fee_multiplier = get_fee_factor(asset).unwrap(); + let fee = calculate_fee(native_fee_adjusted, fee_multiplier).unwrap(); + let expected = initial_balance - fee; + + assert_ok!(TxCharger::correct_and_deposit_fee( + &alice, + &Default::default(), + &Default::default(), + native_fee_adjusted, + 0, + withdrawn + )); + assert_eq!(AssetManager::total_balance(asset, &alice), expected); + assert_eq!(AssetManager::total_issuance(Asset::CampaignAsset(0)), expected); + }); + } + #[test] fn fee_multiplier_can_grow_from_zero() { let minimum_multiplier = MinimumMultiplier::get(); @@ -370,65 +529,59 @@ macro_rules! fee_tests { .unwrap() .into(); t.execute_with(|| { - { - let alice = AccountId::from([0u8; 32]); - let fee_factor = 143_120_520; - let custom_metadata = CustomMetadata { - xcm: XcmMetadata { fee_factor: Some(fee_factor) }, - ..Default::default() - }; - let meta: AssetMetadata = AssetMetadata { - decimals: 10, - name: "Polkadot".into(), - symbol: "DOT".into(), - existential_deposit: ExistentialDeposit::get(), - location: Some(xcm::VersionedMultiLocation::V3(xcm::latest::MultiLocation::parent())), - additional: custom_metadata, - }; - let dot = Currencies::ForeignAsset(0); - - assert_ok!(AssetRegistry::register_asset(RuntimeOrigin::root(), meta.clone(), Some(Assets::from(dot).try_into().unwrap()))); - - - assert_ok!(>::deposit(dot, &Treasury::account_id(), BASE)); - - let mock_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); - let mock_dispatch_info = frame_support::dispatch::DispatchInfo { - weight: frame_support::dispatch::Weight::zero(), - class: DispatchClass::Normal, - pays_fee: frame_support::dispatch::Pays::Yes, - }; - let mock_post_info = frame_support::dispatch::PostDispatchInfo { - actual_weight: Some(frame_support::dispatch::Weight::zero()), - pays_fee: frame_support::dispatch::Pays::Yes, - }; - - let free_balance_treasury_before = Tokens::free_balance(dot, &Treasury::account_id()); - let free_balance_alice_before = Tokens::free_balance(dot, &alice); - let corrected_native_fee = BASE; - let paid = >::issue(dot, 2 * BASE); - let paid_balance = paid.peek(); - let tip = 0u128; - assert_ok!(>::correct_and_deposit_fee( - &alice, - &mock_dispatch_info, - &mock_post_info, - corrected_native_fee, - tip, - paid, - )); - - let treasury_gain = Tokens::free_balance(dot, &Treasury::account_id()) - free_balance_treasury_before; - let alice_gain = Tokens::free_balance(dot, &alice) - free_balance_alice_before; - - let decimals = meta.decimals; - let base = 10u128.checked_pow(decimals).unwrap(); - - let dot_fee = ((corrected_native_fee * fee_factor) + (base / 2)) / base; - assert_eq!(dot_fee, treasury_gain); - assert_eq!(143_120_520, treasury_gain); - assert_eq!(paid_balance - treasury_gain, alice_gain); - } + let alice = AccountId::from([0u8; 32]); + let fee_factor = 143_120_520; + let custom_metadata = CustomMetadata { + xcm: XcmMetadata { fee_factor: Some(fee_factor) }, + ..Default::default() + }; + let meta: AssetMetadata = AssetMetadata { + decimals: 10, + name: "Polkadot".into(), + symbol: "DOT".into(), + existential_deposit: ExistentialDeposit::get(), + location: Some(xcm::VersionedMultiLocation::V3( + xcm::latest::MultiLocation::parent(), + )), + additional: custom_metadata, + }; + let dot = Asset::ForeignAsset(0); + + assert_ok!(AssetRegistry::register_asset( + RuntimeOrigin::root(), + meta.clone(), + Some(dot.try_into().unwrap()) + )); + assert_ok!(AssetManager::deposit(dot, &Treasury::account_id(), BASE)); + + let free_balance_treasury_before = + AssetManager::free_balance(dot, &Treasury::account_id()); + let free_balance_alice_before = AssetManager::free_balance(dot, &alice); + let corrected_native_fee = BASE; + let paid = AssetRouter::issue(dot, 2 * BASE); + let paid_balance = paid.peek(); + let tip = 0u128; + assert_ok!(TxCharger::correct_and_deposit_fee( + &alice, + &Default::default(), + &Default::default(), + corrected_native_fee, + tip, + paid, + )); + + let treasury_gain = AssetManager::free_balance(dot, &Treasury::account_id()) + - free_balance_treasury_before; + let alice_gain = + AssetManager::free_balance(dot, &alice) - free_balance_alice_before; + + let decimals = meta.decimals; + let base = 10u128.checked_pow(decimals).unwrap(); + + let dot_fee = ((corrected_native_fee * fee_factor) + (base / 2)) / base; + assert_eq!(dot_fee, treasury_gain); + assert_eq!(143_120_520, treasury_gain); + assert_eq!(paid_balance - treasury_gain, alice_gain); }); } @@ -462,7 +615,7 @@ macro_rules! fee_tests { Some(dot) )); - assert_eq!(get_fee_factor(dot).unwrap(), 143_120_520u128); + assert_eq!(get_fee_factor(dot.into()).unwrap(), 143_120_520u128); }); } @@ -476,7 +629,7 @@ macro_rules! fee_tests { { // no registering of dot assert_noop!( - get_fee_factor(XcmAsset::ForeignAsset(0)), + get_fee_factor(Asset::ForeignAsset(0)), TransactionValidityError::Invalid(InvalidTransaction::Custom(2u8)) ); } @@ -514,7 +667,7 @@ macro_rules! fee_tests { )); assert_noop!( - get_fee_factor(dot), + get_fee_factor(dot.into()), TransactionValidityError::Invalid(InvalidTransaction::Custom(3u8)) ); }); @@ -547,7 +700,7 @@ macro_rules! fee_tests { Some(Assets::from(non_location_token).try_into().unwrap()) )); - assert_eq!(get_fee_factor(non_location_token).unwrap(), 10_393); + assert_eq!(get_fee_factor(non_location_token.into()).unwrap(), 10_393); }); } @@ -574,34 +727,25 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Currencies::ForeignAsset(dot_asset_id); + let dot = Asset::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), meta, - Some(Assets::from(dot).try_into().unwrap()) + Some(dot.try_into().unwrap()) )); - let fees_and_tips = >::issue(dot, 0); - assert_ok!(>::deposit( - dot, - &Treasury::account_id(), - BASE - )); + let fees_and_tips = AssetRouter::issue(dot, 0); + assert_ok!(AssetManager::deposit(dot, &Treasury::account_id(), BASE)); let mock_call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] }); - let mock_dispatch_info = frame_support::dispatch::DispatchInfo { - weight: frame_support::dispatch::Weight::zero(), - class: DispatchClass::Normal, - pays_fee: frame_support::dispatch::Pays::Yes, - }; assert_eq!( - >::withdraw_fee( + TxCharger::withdraw_fee( &Treasury::account_id(), &mock_call, - &mock_dispatch_info, - dot_asset_id, + &Default::default(), + dot, BASE / 2, 0, ) @@ -612,5 +756,5 @@ macro_rules! fee_tests { }); } } - } + }; } diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index dca2c2a76..8e7bcd84b 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -1090,8 +1090,8 @@ macro_rules! impl_config_traits { impl pallet_asset_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Fungibles = Tokens; - type OnChargeAssetTransaction = TokensTxCharger; + type Fungibles = AssetRouter; + type OnChargeAssetTransaction = TxCharger; } impl pallet_transaction_payment::Config for Runtime { diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 38ad05703..13c1ab58c 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -400,6 +400,8 @@ parameter_types! { .build_or_panic(); // Transaction payment + /// A fee multiplier applied to the calculated fee factor for `CampaignAsset` + pub const CampaignAssetFeeMultiplier: u32 = 100; /// A fee mulitplier for Operational extrinsics to compute “virtual tip” /// to boost their priority. pub const OperationalFeeMultiplier: u8 = 5; diff --git a/zrml/asset-router/src/lib.rs b/zrml/asset-router/src/lib.rs index d2f7a630f..ec97b3937 100644 --- a/zrml/asset-router/src/lib.rs +++ b/zrml/asset-router/src/lib.rs @@ -43,7 +43,7 @@ pub mod pallet { require_transactional, traits::{ tokens::{ - fungibles::{Create, Destroy, Inspect, Mutate, Transfer}, + fungibles::{Create, Destroy, Inspect, Mutate, Transfer, Unbalanced}, DepositConsequence, WithdrawConsequence, }, BalanceStatus as Status, ConstU32, @@ -83,6 +83,7 @@ pub mod pallet { + Inspect + Transfer + Mutate + + Unbalanced { } @@ -92,7 +93,8 @@ pub mod pallet { + Destroy + Inspect + Transfer - + Mutate, + + Mutate + + Unbalanced, T: Config, { } @@ -145,7 +147,8 @@ pub mod pallet { Balance = Self::Balance, > + MultiLockableCurrency + MultiReservableCurrency - + NamedMultiReservableCurrency; + + NamedMultiReservableCurrency + + Unbalanced; /// The currency type. type CurrencyType: TryFrom + Copy diff --git a/zrml/asset-router/src/macros.rs b/zrml/asset-router/src/macros.rs index 663c5ca51..06dff530c 100644 --- a/zrml/asset-router/src/macros.rs +++ b/zrml/asset-router/src/macros.rs @@ -42,6 +42,31 @@ macro_rules! route_call { }; } +macro_rules! route_call_with_trait { + ($currency_id:expr, $trait:ident, $method:ident, $($args:expr),*) => { + 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(>::$method(asset, $($args),*)) + } else { + if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + Ok(>::$method(currency, $($args),*)) + } else { + Ok(>::$method(asset, $($args),*)) + } + } + } else if let Ok(asset) = T::CampaignAssetType::try_from($currency_id) { + Ok(>::$method(asset, $($args),*)) + } else if let Ok(asset) = T::CustomAssetType::try_from($currency_id) { + Ok(>::$method(asset, $($args),*)) + } else if let Ok(currency) = T::CurrencyType::try_from($currency_id) { + Ok(>::$method(currency, $($args),*)) + } else { + Err(Error::::UnknownAsset) + } + }; +} + /// This macro delegates a call to Currencies if the asset represents a currency, otherwise /// it returns an error. macro_rules! only_currency { diff --git a/zrml/asset-router/src/mock.rs b/zrml/asset-router/src/mock.rs index e6da9d38b..716142b96 100644 --- a/zrml/asset-router/src/mock.rs +++ b/zrml/asset-router/src/mock.rs @@ -57,10 +57,10 @@ pub(super) const CURRENCY: Assets = Assets::ForeignAsset(0); pub(super) const CURRENCY_OLD_OUTCOME: Assets = Assets::CategoricalOutcome(7, 8); pub(super) const CURRENCY_INTERNAL: Currencies = Currencies::ForeignAsset(0); -pub(super) const CAMPAIGN_ASSET_MIN_BALANCE: Balance = 1; -pub(super) const CUSTOM_ASSET_MIN_BALANCE: Balance = 2; -pub(super) const MARKET_ASSET_MIN_BALANCE: Balance = 3; -pub(super) const CURRENCY_MIN_BALANCE: Balance = 4; +pub(super) const CAMPAIGN_ASSET_MIN_BALANCE: Balance = 2; +pub(super) const CUSTOM_ASSET_MIN_BALANCE: Balance = 3; +pub(super) const MARKET_ASSET_MIN_BALANCE: Balance = 4; +pub(super) const CURRENCY_MIN_BALANCE: Balance = 5; pub(super) const CAMPAIGN_ASSET_INITIAL_AMOUNT: Balance = 10; pub(super) const CUSTOM_ASSET_INITIAL_AMOUNT: Balance = 20; diff --git a/zrml/asset-router/src/pallet_impl/inspect.rs b/zrml/asset-router/src/pallet_impl/inspect.rs index e4c2f50d7..1c19338d4 100644 --- a/zrml/asset-router/src/pallet_impl/inspect.rs +++ b/zrml/asset-router/src/pallet_impl/inspect.rs @@ -110,7 +110,9 @@ impl Inspect for Pallet { fn asset_exists(asset: Self::AssetId) -> bool { if let Ok(currency) = T::CurrencyType::try_from(asset) { - if T::Currencies::total_issuance(currency) > Zero::zero() { + if >::total_issuance(currency) + > Zero::zero() + { true } else { only_asset!(asset, false, Inspect, asset_exists,) diff --git a/zrml/asset-router/src/pallet_impl/mod.rs b/zrml/asset-router/src/pallet_impl/mod.rs index 5c3fdeb82..d0a94a1c7 100644 --- a/zrml/asset-router/src/pallet_impl/mod.rs +++ b/zrml/asset-router/src/pallet_impl/mod.rs @@ -25,3 +25,4 @@ pub mod multi_lockable_currency; pub mod multi_reserveable_currency; pub mod named_multi_reserveable_currency; pub mod transfer_all; +pub mod unbalanced; diff --git a/zrml/asset-router/src/pallet_impl/unbalanced.rs b/zrml/asset-router/src/pallet_impl/unbalanced.rs new file mode 100644 index 000000000..fc91c047a --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/unbalanced.rs @@ -0,0 +1,67 @@ +// 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 crate::pallet::*; +use frame_support::traits::tokens::fungibles::Unbalanced; + +impl Unbalanced for Pallet { + fn set_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + route_call_with_trait!(asset, Unbalanced, set_balance, who, amount)? + } + + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) { + let _ = route_call_with_trait!(asset, Unbalanced, set_total_issuance, amount); + } + + fn decrease_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Result { + route_call_with_trait!(asset, Unbalanced, decrease_balance, who, amount)? + } + + fn decrease_balance_at_most( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Self::Balance { + route_call_with_trait!(asset, Unbalanced, decrease_balance_at_most, who, amount) + .unwrap_or(Zero::zero()) + } + + fn increase_balance( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Result { + route_call_with_trait!(asset, Unbalanced, increase_balance, who, amount)? + } + + fn increase_balance_at_most( + asset: Self::AssetId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Self::Balance { + route_call_with_trait!(asset, Unbalanced, increase_balance_at_most, who, amount) + .unwrap_or(Zero::zero()) + } +} diff --git a/zrml/asset-router/src/tests/mod.rs b/zrml/asset-router/src/tests/mod.rs index d59169a37..48a23763d 100644 --- a/zrml/asset-router/src/tests/mod.rs +++ b/zrml/asset-router/src/tests/mod.rs @@ -45,3 +45,4 @@ mod multi_currency; mod multi_lockable_currency; mod multi_reservable_currency; mod named_multi_reservable_currency; +mod unbalanced; diff --git a/zrml/asset-router/src/tests/unbalanced.rs b/zrml/asset-router/src/tests/unbalanced.rs new file mode 100644 index 000000000..91b78ffad --- /dev/null +++ b/zrml/asset-router/src/tests/unbalanced.rs @@ -0,0 +1,125 @@ +// 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 . + +#![cfg(test)] + +use super::*; +use frame_support::traits::tokens::fungibles::Unbalanced; +use orml_traits::MultiCurrency; + +fn test_helper( + asset: Assets, + initial_amount: ::Balance, + min_balance: ::Balance, +) { + assert_eq!(AssetRouter::total_balance(asset, &ALICE), initial_amount); + assert_ok!(AssetRouter::increase_balance(asset, &ALICE, 1)); + assert_eq!(AssetRouter::total_balance(asset, &ALICE), initial_amount + 1); + assert_ok!(AssetRouter::decrease_balance(asset, &ALICE, 1)); + assert_eq!(AssetRouter::total_balance(asset, &ALICE), initial_amount); + assert_eq!(AssetRouter::increase_balance_at_most(asset, &ALICE, 1), 1); + assert_eq!(AssetRouter::total_balance(asset, &ALICE), initial_amount + 1); + let to_decrease = initial_amount + 2 - min_balance; + assert_eq!( + AssetRouter::decrease_balance_at_most(asset, &ALICE, to_decrease), + initial_amount + 1 + ); + assert_eq!(AssetRouter::total_balance(asset, &ALICE), 0); + AssetRouter::set_total_issuance(asset, 1337); + assert_eq!(AssetRouter::total_issuance(asset), 1337); +} + +#[test] +fn routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::deposit(CAMPAIGN_ASSET, &ALICE, CAMPAIGN_ASSET_INITIAL_AMOUNT)); + + test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT, CAMPAIGN_ASSET_MIN_BALANCE); + + assert_eq!(AssetRouter::total_issuance(CUSTOM_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(MARKET_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(CURRENCY), 0); + }); +} + +#[test] +#[should_panic] +fn campaign_assets_panic_on_set_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE)); + let _ = AssetRouter::set_balance(CAMPAIGN_ASSET, &ALICE, 42); + }); +} + +#[test] +fn routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::deposit(CUSTOM_ASSET, &ALICE, CUSTOM_ASSET_INITIAL_AMOUNT)); + + test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT, CUSTOM_ASSET_MIN_BALANCE); + + assert_eq!(AssetRouter::total_issuance(CAMPAIGN_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(MARKET_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(CURRENCY), 0); + }); +} + +#[test] +#[should_panic] +fn custom_assets_panic_on_set_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE)); + let _ = AssetRouter::set_balance(CUSTOM_ASSET, &ALICE, 42); + }); +} + +#[test] +fn routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE)); + assert_ok!(AssetRouter::deposit(MARKET_ASSET, &ALICE, MARKET_ASSET_INITIAL_AMOUNT)); + + test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT, MARKET_ASSET_MIN_BALANCE); + + assert_eq!(AssetRouter::total_issuance(CAMPAIGN_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(CUSTOM_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(CURRENCY), 0); + }); +} + +#[test] +#[should_panic] +fn market_assets_panic_on_set_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE)); + let _ = AssetRouter::set_balance(MARKET_ASSET, &ALICE, 42); + }); +} + +#[test] +fn routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::set_balance(CURRENCY, &ALICE, CURRENCY_INITIAL_AMOUNT)); + test_helper(CURRENCY, CURRENCY_INITIAL_AMOUNT, CURRENCY_MIN_BALANCE); + + assert_eq!(AssetRouter::total_issuance(CAMPAIGN_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(CUSTOM_ASSET), 0); + assert_eq!(AssetRouter::total_issuance(MARKET_ASSET), 0); + }); +} diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index cde97ae57..924d24072 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -301,8 +301,7 @@ mod pallet { let asset = order_data.maker_asset; let amount = order_data.maker_amount; - if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), asset, maker) - .is_zero() + if !T::AssetManager::reserved_balance_named(&Self::reserve_id(), asset, maker).is_zero() { let missing = T::AssetManager::unreserve_named(&Self::reserve_id(), asset, maker, amount); From ad86d4514008e6df0c88037e515c98555f4867a0 Mon Sep 17 00:00:00 2001 From: Harald Heckmann Date: Tue, 2 Apr 2024 16:43:31 +0530 Subject: [PATCH 13/14] New Asset System - Prepare merge into main (#1292) * 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 * Remove migrations (#1180) * Filter admin functions for main-net (#1190) filter admin functions for main-net * Add debug assertions for slashes and reserves (#1188) * add debug assertions for missing slashes * place debug_assert for unreserves * Add some verify checks to court (#1187) add some verify checks to court * Bypass battery stations contracts call filter for court, parimutuel, order book markets (#1185) update contracts call filter * Fix failing court benchmark (#1191) * fix court assertion for resolve_at * remove unnecessary variable * mirror mock and actual impl for DisputeResolution * 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 * Modify court events for indexer (#1182) * modify events for indexer * gracefully handle panicers * handle binary search by key error * improve style * Ensure MinBetSize after fee (#1193) * handle own existential deposit errors * use require_transactional * correct benchmark and test min bet size amounts * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Remove court and global disputes from call filter for the main-net (#1226) * remove from call filter * update copyright * 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 * 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> * 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> * Update style guide to streamline reviews (#1228) * 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. * Set inflation to more than zero for a full benchmark of handle_inflation (#1234) Update benchmarks.rs * 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 * 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 * Utilize Merigify's Merge Queue (#1243) ci(Mergify): configuration update Signed-off-by: Harald Heckmann * 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> * Avoid mergify dequeue (#1245) * Avoid mergify deque * Set review needed only shortly after Mergify commit * 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> * Remove migrations and dead code (#1241) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * 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> * 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> * Fix Rust and Discord badge (#1247) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Fix neo-swaps doc strings (#1250) * Improve `SellExecuted` documentation * Clean up math in comments and doc strings * Fix formatting * 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 * Merge Old* and New* asset variants * Partially integrate lazy migration routing * Integrate lazy migration routing * Fix ExistentialDeposit mapping & Satisfy Clippy * 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 * 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 * Remove primitives/macros * Filter certain asset destroy calls (they're managed) * Remove unchecked modulo operations from production (#1254) * Remove unchecked modulo operations from production * Update copyright * 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 * Integrate asset destruction into prediction markets * Add BaseAssetClass * Update prediction-markets & market-commons * Update authorized * Update liquidity-mining * Update simple-disputes * Update parimutuels (wip) * Merge release v0.5.0 into main (#1262) * Update versions (#1227) * Update weights (#1232) * Update copyright years * Revert Court handle_inflation() weight * Move functions into market struct and properly delete assets * Add ParimutuelAssetClass * Add parimutuel.rs * Remove duplicate code * Adjust asset type and managed destroy after refund in parimutuels * Add MarketTransitionApi This will allow prediction-markets to signal other pallets that state transitions happened * Add MarketTransitionApi * Implement MarketTransitionApi for Parimutuels * Only run copyright CI when merging into `main` (#1263) * Bring README up to date (#1264) * Update proxy types (#1259) * Replace `Swaps` with `NeoSwaps` proxy types * Add `withdraw_fees` to liquidity-related proxies * Update toolchain to fix codecov and fuzz errors (#1269) * Update toolchain * Fix formatting and linter errors * Update copyright notices * Fix copyright notice * Use GitHub action to free up space (#1271) * Use GitHub action to freee up space * Try deleting tool-cache as well * Add missing free disk calls; don't remove tool-cache * Remove unused dependencies (#1270) * License checker: Replace incorrect `push` with `append` (#1268) Replace incorrect `push` with `append` Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * Partially implement asset creation/destruction in parimutuels * Add all asset creation / destruction tests to parimutuels * Adjust Court * Update global-disputes * Update swaps * Update neo-swaps * Integrate OnStateTransition hooks into prediction-markets * Use proper state transition weights * Make cargo check succeed * Partially update orderbook * Update orderbook * Update orderbook * Finalize orderbook update * Improve style * Add XcmAssetClass * Add sub asset classes, extend Market, provide market transition trait * Update asset-router * Integrate asset system into prediction-market, market-commons and parimutuel - Market commons now uses the BaseAsset class for base assets - Prediction markets now creates and destroys MarketAssets only if market.is_redeemable - Prediction markets now calls OnStateTransition when transitioning it's state - Parimutuel markets now implements StateTransitionApi - Parimutuel markets now properly creates and destroys ParimutuelShares * Implement support for non-reservable assets in orderbook * Update Codeowners (#1286) * Integrate new asset system into Battery Station XCM * Integrate new asset system into Zeitgeist XCM * Implement Unbalanced for asset-router * Enable fee payment for campaign assets * Fix conditional import * Format * Fix unfillable / unremovable order bug Co-authored-by: Chralt * Add tests for Unbalanced impl * Implement fee charging tests * Undo unnecessray change * Undo last commit * Add previous stake information after rejoin (#1285) * update joined_at after rejoin * Add additional fields to CourtPoolItem struct, refactor * complete tests * add migration * revert benchmark verify check * remove migration comment * satisfy clippy * implement different approach * adopt uneligble stake and index info * update benchmark * apply review comments * correct current period index calculation * apply review suggestions * Update zrml/court/src/tests.rs Co-authored-by: Harald Heckmann * shorten test * add try-runtime checks --------- Co-authored-by: Harald Heckmann * Update asset-router readme * Extended changelog for devs * Remove deprecated ExistentialDeposits type --------- Signed-off-by: Harald Heckmann Co-authored-by: Chralt Co-authored-by: Malte Kliemann Co-authored-by: Chralt98 Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .github/workflows/copyright.yml | 34 + .github/workflows/coverage.yml | 23 +- .github/workflows/integration-tests.yml | 269 + .github/workflows/migration.yml | 5 + .github/workflows/rust.yml | 78 +- .github/workflows/try-runtime.yml | 10 + .gitignore | 4 +- CODEOWNERS | 3 + Cargo.lock | 50 +- GH-banner.jpg | Bin 684061 -> 0 bytes GH-banner.svg | 21 + HEADER_GPL3 | 2 +- README.md | 75 +- docs/changelog_for_devs.md | 105 +- 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 +- macros/Cargo.toml | 2 +- node/Cargo.toml | 2 +- node/src/cli.rs | 4 +- node/src/service.rs | 6 +- package-lock.json | 6 + primitives/Cargo.toml | 2 +- primitives/src/constants/mock.rs | 14 +- primitives/src/math/checked_ops_res.rs | 21 +- primitives/src/math/fixed.rs | 44 +- primitives/src/math/root.rs | 3 +- primitives/src/traits/swaps.rs | 114 +- runtime/battery-station/Cargo.toml | 2 +- runtime/battery-station/src/lib.rs | 4 +- runtime/battery-station/src/parameters.rs | 4 - runtime/common/Cargo.toml | 2 +- runtime/common/src/fees.rs | 9 + runtime/common/src/lib.rs | 25 +- .../src/weights/cumulus_pallet_xcmp_queue.rs | 32 +- runtime/common/src/weights/frame_system.rs | 68 +- runtime/common/src/weights/orml_currencies.rs | 52 +- runtime/common/src/weights/orml_tokens.rs | 46 +- .../src/weights/pallet_author_inherent.rs | 24 +- .../src/weights/pallet_author_mapping.rs | 56 +- .../src/weights/pallet_author_slot_filter.rs | 20 +- runtime/common/src/weights/pallet_balances.rs | 44 +- runtime/common/src/weights/pallet_bounties.rs | 79 +- .../common/src/weights/pallet_collective.rs | 208 +- .../common/src/weights/pallet_contracts.rs | 1167 ++-- .../common/src/weights/pallet_democracy.rs | 178 +- runtime/common/src/weights/pallet_identity.rs | 194 +- .../common/src/weights/pallet_membership.rs | 103 +- runtime/common/src/weights/pallet_multisig.rs | 96 +- .../src/weights/pallet_parachain_staking.rs | 328 +- runtime/common/src/weights/pallet_preimage.rs | 76 +- runtime/common/src/weights/pallet_proxy.rs | 119 +- .../common/src/weights/pallet_scheduler.rs | 101 +- .../common/src/weights/pallet_timestamp.rs | 26 +- runtime/common/src/weights/pallet_treasury.rs | 69 +- runtime/common/src/weights/pallet_utility.rs | 48 +- runtime/common/src/weights/pallet_vesting.rs | 118 +- runtime/zeitgeist/Cargo.toml | 2 +- runtime/zeitgeist/src/lib.rs | 4 +- runtime/zeitgeist/src/parameters.rs | 4 - rust-toolchain | 2 +- .../src/check_license/copyright.py | 2 +- zrml/asset-router/README.md | 27 +- zrml/authorized/Cargo.toml | 2 +- zrml/authorized/src/mock_storage.rs | 4 +- zrml/authorized/src/weights.rs | 50 +- zrml/court/Cargo.toml | 3 +- zrml/court/src/benchmarks.rs | 12 +- zrml/court/src/lib.rs | 224 +- zrml/court/src/migrations.rs | 239 +- zrml/court/src/mock_storage.rs | 3 +- zrml/court/src/tests.rs | 318 +- zrml/court/src/types.rs | 9 +- zrml/court/src/weights.rs | 253 +- zrml/global-disputes/Cargo.toml | 2 +- zrml/global-disputes/src/tests.rs | 14 +- zrml/global-disputes/src/weights.rs | 101 +- zrml/liquidity-mining/Cargo.toml | 2 +- zrml/liquidity-mining/src/lib.rs | 7 +- zrml/liquidity-mining/src/weights.rs | 12 +- zrml/market-commons/Cargo.toml | 2 +- zrml/neo-swaps/Cargo.toml | 6 +- zrml/neo-swaps/src/mock.rs | 4 +- zrml/neo-swaps/src/weights.rs | 153 +- zrml/orderbook/Cargo.toml | 2 +- zrml/orderbook/src/mock.rs | 4 +- zrml/orderbook/src/weights.rs | 38 +- zrml/parimutuel/Cargo.toml | 2 +- zrml/parimutuel/src/mock.rs | 4 +- zrml/parimutuel/src/weights.rs | 38 +- zrml/prediction-markets/Cargo.toml | 6 +- .../prediction-markets/runtime-api/Cargo.toml | 2 +- zrml/prediction-markets/src/mock.rs | 8 +- zrml/prediction-markets/src/weights.rs | 338 +- zrml/rikiddo/Cargo.toml | 2 +- .../src/tests/rikiddo_sigmoid_mv/cost.rs | 5 +- .../src/tests/rikiddo_sigmoid_mv/misc.rs | 5 +- .../src/tests/rikiddo_sigmoid_mv/price.rs | 4 +- zrml/simple-disputes/Cargo.toml | 2 +- zrml/simple-disputes/src/mock.rs | 4 +- zrml/styx/Cargo.toml | 2 +- zrml/styx/src/benchmarks.rs | 6 +- zrml/styx/src/weights.rs | 16 +- zrml/swaps/Cargo.toml | 4 +- zrml/swaps/README.md | 35 +- zrml/swaps/rpc/Cargo.toml | 2 +- zrml/swaps/runtime-api/Cargo.toml | 2 +- zrml/swaps/src/benchmarks.rs | 18 +- zrml/swaps/src/fixed.rs | 10 +- zrml/swaps/src/lib.rs | 1261 ++-- zrml/swaps/src/math.rs | 10 +- zrml/swaps/src/mock.rs | 10 +- zrml/swaps/src/tests.rs | 73 +- zrml/swaps/src/types/pool.rs | 6 +- zrml/swaps/src/utils.rs | 12 +- zrml/swaps/src/weights.rs | 155 +- 139 files changed, 12286 insertions(+), 6859 deletions(-) create mode 100644 .github/workflows/copyright.yml create mode 100644 .github/workflows/integration-tests.yml delete mode 100644 GH-banner.jpg create mode 100644 GH-banner.svg 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/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/coverage.yml b/.github/workflows/coverage.yml index 8a43c0520..8affb0d66 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,27 +22,18 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install rust toolchain run: rustup show - uses: actions-rs/install@v0.1 with: crate: grcov - use-tool-cache: true - - # No disk space: https://github.com/zeitgeistpm/zeitgeist/actions/runs/5085081984/jobs/9144298675?pr=1006 - # Workaround: https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 - - name: Free up disk space on GitHub hosted runners - run: | - # Ensure context is GitHub hosted runner - # https://docs.github.com/en/actions/learn-github-actions/contexts#runner-context - if [[ "${{ runner.name }}" == "GitHub Actions"* ]]; then - echo "Freeing up space in GitHub hosted runner" - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - fi + use-tool-cache: false - name: Cache Dependencies uses: Swatinem/rust-cache@v1 @@ -58,4 +49,4 @@ jobs: fail_ci_if_error: true flags: tests verbose: true - name: unit-tests \ No newline at end of file + name: unit-tests 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/.github/workflows/migration.yml b/.github/workflows/migration.yml index 967dc6510..6387503c2 100644 --- a/.github/workflows/migration.yml +++ b/.github/workflows/migration.yml @@ -17,4 +17,9 @@ jobs: - name: Install build tools run: ./scripts/init.sh + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + - run: ./scripts/runtime-upgrade/test_runtime_upgrade.sh ${{ github.event.inputs.block }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4d52a85ec..a814b4d88 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: | @@ -80,6 +55,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install build tools run: ./scripts/init.sh @@ -101,6 +81,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install build tools run: ./scripts/init.sh @@ -118,22 +103,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install build tools run: ./scripts/init.sh - - # No disk space: https://github.com/zeitgeistpm/zeitgeist/actions/runs/5085081984/jobs/9144298675?pr=1006 - # Workaround: https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 - - name: Free up disk space on GitHub hosted runners - run: | - # Ensure context is GitHub hosted runner - # https://docs.github.com/en/actions/learn-github-actions/contexts#runner-context - if [[ "${{ runner.name }}" == "GitHub Actions"* ]]; then - echo "Freeing up space in GitHub hosted runner" - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - fi - name: Cache Dependencies uses: Swatinem/rust-cache@v1 @@ -153,22 +129,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install build tools run: ./scripts/init.sh - - # No disk space: https://github.com/zeitgeistpm/zeitgeist/actions/runs/5085081984/jobs/9144298675?pr=1006 - # Workaround: https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 - - name: Free up disk space on GitHub hosted runners - run: | - # Ensure context is GitHub hosted runner - # https://docs.github.com/en/actions/learn-github-actions/contexts#runner-context - if [[ "${{ runner.name }}" == "GitHub Actions"* ]]; then - echo "Freeing up space in GitHub hosted runner" - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - fi - name: Cache Dependencies uses: Swatinem/rust-cache@v1 @@ -188,6 +155,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + - name: Install build tools run: ./scripts/init.sh diff --git a/.github/workflows/try-runtime.yml b/.github/workflows/try-runtime.yml index 676368afa..0d5d4b99d 100644 --- a/.github/workflows/try-runtime.yml +++ b/.github/workflows/try-runtime.yml @@ -13,6 +13,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + - name: Install build tools run: ./scripts/init.sh @@ -27,6 +32,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Free up disk space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + - name: Install build tools run: ./scripts/init.sh 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/CODEOWNERS b/CODEOWNERS index 758a6baa0..03f736322 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -12,7 +12,10 @@ /zrml/authorized/ @Chralt98 /zrml/court/ @Chralt98 /zrml/global-disputes/ @Chralt98 +/zrml/market-commons/ @maltekliemann /zrml/neo-swaps/ @maltekliemann +/zrml/orderbook/ @Chralt98 +/zrml/parimutuel/ @Chralt98 /zrml/prediction-markets/ @maltekliemann /zrml/rikiddo/ @sea212 /zrml/simple-disputes/ @Chralt98 diff --git a/Cargo.lock b/Cargo.lock index 190de72c1..824c2949e 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", @@ -1208,7 +1208,7 @@ dependencies = [ [[package]] name = "common-runtime" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "cumulus-pallet-xcmp-queue", @@ -14297,11 +14297,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", @@ -14393,7 +14393,7 @@ dependencies = [ [[package]] name = "zeitgeist-primitives" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arbitrary", "fixed", @@ -14417,7 +14417,7 @@ dependencies = [ [[package]] name = "zeitgeist-runtime" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "common-runtime", @@ -14563,7 +14563,7 @@ dependencies = [ [[package]] name = "zrml-authorized" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14581,7 +14581,7 @@ dependencies = [ [[package]] name = "zrml-court" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arrayvec 0.7.4", "env_logger 0.10.1", @@ -14600,13 +14600,14 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", "zrml-market-commons", ] [[package]] name = "zrml-global-disputes" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14627,7 +14628,7 @@ dependencies = [ [[package]] name = "zrml-liquidity-mining" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14646,7 +14647,7 @@ dependencies = [ [[package]] name = "zrml-market-commons" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-support", @@ -14664,7 +14665,7 @@ dependencies = [ [[package]] name = "zrml-neo-swaps" -version = "0.4.3" +version = "0.5.0" dependencies = [ "cfg-if", "env_logger 0.10.1", @@ -14704,14 +14705,12 @@ dependencies = [ "zrml-neo-swaps", "zrml-prediction-markets", "zrml-prediction-markets-runtime-api", - "zrml-rikiddo", "zrml-simple-disputes", - "zrml-swaps", ] [[package]] name = "zrml-orderbook" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14749,7 +14748,7 @@ dependencies = [ [[package]] name = "zrml-parimutuel" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14774,7 +14773,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14808,9 +14807,7 @@ dependencies = [ "zrml-market-commons", "zrml-prediction-markets", "zrml-prediction-markets-runtime-api", - "zrml-rikiddo", "zrml-simple-disputes", - "zrml-swaps", ] [[package]] @@ -14828,7 +14825,7 @@ dependencies = [ [[package]] name = "zrml-prediction-markets-runtime-api" -version = "0.4.3" +version = "0.5.0" dependencies = [ "parity-scale-codec", "sp-api", @@ -14837,7 +14834,7 @@ dependencies = [ [[package]] name = "zrml-rikiddo" -version = "0.4.3" +version = "0.5.0" dependencies = [ "arbitrary", "cfg-if", @@ -14871,7 +14868,7 @@ dependencies = [ [[package]] name = "zrml-simple-disputes" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14892,7 +14889,7 @@ dependencies = [ [[package]] name = "zrml-styx" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14909,7 +14906,7 @@ dependencies = [ [[package]] name = "zrml-swaps" -version = "0.4.3" +version = "0.5.0" dependencies = [ "env_logger 0.10.1", "frame-benchmarking", @@ -14931,7 +14928,6 @@ dependencies = [ "test-case", "zeitgeist-macros", "zeitgeist-primitives", - "zrml-liquidity-mining", "zrml-market-commons", "zrml-swaps", "zrml-swaps-runtime-api", @@ -14953,7 +14949,7 @@ dependencies = [ [[package]] name = "zrml-swaps-rpc" -version = "0.4.3" +version = "0.5.0" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -14966,7 +14962,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/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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