diff --git a/CODEOWNERS b/CODEOWNERS index 059ede2ac..03f736322 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 2fee7efbf..824c2949e 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", @@ -619,6 +620,7 @@ dependencies = [ "xcm-emulator", "xcm-executor", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -1215,6 +1217,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "pallet-asset-tx-payment", + "pallet-assets", "pallet-author-inherent", "pallet-author-mapping", "pallet-author-slot-filter", @@ -5858,6 +5861,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-assets" +version = "4.0.0-dev" +source = "git+https://github.com/zeitgeistpm/substrate.git?branch=polkadot-v0.9.38-assets-managed-destroy#135cfbdb68429e2d860f5054463a2a094908eeb4" +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" @@ -14381,6 +14399,7 @@ dependencies = [ "fixed", "frame-support", "frame-system", + "impl-trait-for-tuples", "more-asserts", "num-traits", "orml-currencies", @@ -14428,6 +14447,7 @@ dependencies = [ "orml-xcm-support", "orml-xtokens", "pallet-asset-tx-payment", + "pallet-assets", "pallet-aura", "pallet-author-inherent", "pallet-author-mapping", @@ -14484,6 +14504,7 @@ dependencies = [ "xcm-emulator", "xcm-executor", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -14520,6 +14541,26 @@ 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-macros", + "zeitgeist-primitives", +] + [[package]] name = "zrml-authorized" version = "0.5.0" @@ -14638,6 +14679,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-randomness-collective-flip", "pallet-timestamp", @@ -14654,6 +14696,7 @@ dependencies = [ "xcm", "xcm-builder", "zeitgeist-primitives", + "zrml-asset-router", "zrml-authorized", "zrml-court", "zrml-global-disputes", @@ -14677,6 +14720,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-timestamp", "parity-scale-codec", @@ -14684,7 +14728,9 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", + "zrml-asset-router", "zrml-market-commons", "zrml-orderbook", ] @@ -14711,6 +14757,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-timestamp", "parity-scale-codec", @@ -14718,7 +14765,9 @@ dependencies = [ "sp-io", "sp-runtime", "test-case", + "zeitgeist-macros", "zeitgeist-primitives", + "zrml-asset-router", "zrml-market-commons", ] @@ -14735,6 +14784,7 @@ dependencies = [ "orml-currencies", "orml-tokens", "orml-traits", + "pallet-assets", "pallet-balances", "pallet-randomness-collective-flip", "pallet-timestamp", @@ -14749,6 +14799,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 ccb25afea..d056ef428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ default-members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/asset-router", "zrml/court", "zrml/global-disputes", "zrml/liquidity-mining", @@ -31,6 +32,7 @@ members = [ "runtime/battery-station", "runtime/zeitgeist", "zrml/authorized", + "zrml/asset-router", "zrml/court", "zrml/global-disputes", "zrml/liquidity-mining", @@ -163,6 +165,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 } @@ -232,6 +235,7 @@ zrml-swaps-rpc = { path = "zrml/swaps/rpc" } 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 } zrml-court = { path = "zrml/court", default-features = false } zrml-global-disputes = { path = "zrml/global-disputes", default-features = false } @@ -262,6 +266,7 @@ arbitrary = { version = "1.3.0", default-features = false } 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"] } +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 @@ -331,6 +336,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/README.md b/README.md index 7350560e5..1fa93686c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ decentralized court. ## 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. diff --git a/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index 70afccec1..7828f07d9 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -12,27 +12,114 @@ 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.1 + +[#1197]: https://github.com/zeitgeistpm/zeitgeist/pull/1197 +[pallet-asset]: + https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/assets + +### Added + +- [#1197] New asset classes: + - `CampaignAssetClass` - Can be registered by gov and council and be used in + markets and to pay transaction fees. + - `CustomAssetClass` - Allows any user to register their custom assets (can't + be used in markets yet). + - `MarketAssetClass` - Contains all asset types used in markets. + - `CurrencyClass` - Contains all non-ztg currencies, currently only + `ForeignAsset`. Temporarily also contains outcome asset types as they are + being lazily migrated to `MarketAssetClass` + - Subclasses (they are composites of multiple types from potentially various + asset classes): + - `BaseAssetClass` - Contains all assets that can be used as base asset / + collateral in prediction markets. + - `XcmAssetClass` - Contains all assets that can be transferred via XCM + (used in XCM related pallets like XTokens). + - `ParimutuelAssetClass` - Contains all assets that can be used in + parimutuel markets. + - All asset classes can be converted into the overarching asset class `Assets` + (that contains all asset types) by using `Into` or simply decoding their + scale codec representation into the `Assets` type. + - `Assets` provides `TryInto` into all other asset classes, which fails if the + asset type is not existent in the asset class. +- [#1197] Added [pallet-asset], which is a Substrate pallet that provides fine + grained control over asset creation, destruction, management (mint, burn, + freeze, admin account) and much more. It is used for `CampaignAssetClass`, + `CustomAssetClass` and `MarketAssetClass`. +- [#1197] Added zrml-asset-router (AssetRouter). This pallet is an abstraction + layer over multiple pallets (like orml-tokens and pallet-assets) that handles + routing calls, managing asset destruction and the lazy migrating market assets + from `CurrencyClass` to `MarketAssetClass`. It does not have any dispatchable + calls or events, but custom errors that might be relayed to the dispatchable + calls of the pallets that it uses within it's routing mechanism. + `orml-currencies` (AssetManager) is ought to be used when interacting with the + chain via transactions and can be used when developing pallets. In the latter + case, some functionalities can only be used when directly interacting with + zrml-asset-router. +- [#1197] Campaign assets have to be created and destroyed by gov or the + council. Custom assets have to be created and destroyed via transactions. + Market assets are automatically created and destroyed. In all non automatic + cases, destroying is achieved by calling `start_destroy`. +- [#1197] Transaction fee payment is now possible with campaign assets. The fee + is calculated as follows (with `CampaignAssetFeeMultiplier = 100`): + +```rust +if ztg_supply / campaign_asset_supply >= 100 { + return native_fee; +} else { + return native_fee * campaign_asset_supply * 100 / ztg_supply; +} +``` + +### Changed + +- [#1197] `Assets` does not contain the `CombinatorialOutcome` asset type + anymore, but has been extended by all existing asset types. +- [#1197] The transaction fee asset type has been changed from `u32` to + `Assets`. +- [#1197] The prediction market base asset type has been changed in the `Market` + storage and market creation dispatchable calls to `BaseAssetClass`. +- [#1197] The asset type for XCM has been changed to `XcmAssetClass`. It is used + in `orml-xtokens` (XTokens) and `orml-asset-registry` (AssetRegistry). + +### Removed + +- [#1197] `SerdeWrapper` has been removed. + +### Deprecated + +- [#1197] Market outcome asset types are no longer handled by `orml-tokens` + (Tokens), except for existing markets which still used market asset types + within `CurrencyClass`. `pallet-assets` (MarketAssets) now handles market + outcome asset types from the `MarketAssetClass`. + ## v0.5.0 [#1197]: https://github.com/zeitgeistpm/zeitgeist/pull/1197 [#1178]: https://github.com/zeitgeistpm/zeitgeist/pull/1178 -### Changes +### Changed -- ⚠️ 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]). +- ⚠️ 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]). +- 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 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 7f346a898..07ded556b 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 = { workspace = true } num-traits = { workspace = true } orml-currencies = { workspace = true } orml-tokens = { workspace = true } diff --git a/primitives/src/asset.rs b/primitives/src/assets.rs similarity index 55% rename from primitives/src/asset.rs rename to primitives/src/assets.rs index 40c2ea37c..7de237bb8 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/assets.rs @@ -20,56 +20,26 @@ use crate::traits::ZeitgeistAssetEnumerator; use crate::{ traits::PoolSharesId, - types::{CategoryIndex, PoolId, SerdeWrapper}, + types::{CampaignAssetId, CategoryIndex, CustomAssetId, PoolId}, }; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::{Compact, CompactAs, Decode, Encode, HasCompact, MaxEncodedLen}; use scale_info::TypeInfo; -/// The `Asset` enum represents all types of assets available in the Zeitgeist -/// system. -/// -/// # 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), -} +pub use all_assets::Asset; +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}; -impl PoolSharesId> for Asset { - fn pool_shares_id(pool_id: SerdeWrapper) -> Self { - Self::PoolShare(pool_id) - } -} - -#[cfg(feature = "runtime-benchmarks")] -impl ZeitgeistAssetEnumerator for Asset { - fn create_asset_id(t: MI) -> Self { - Asset::CategoricalOutcome(t, 0) - } -} +mod all_assets; +mod campaign_assets; +mod currencies; +mod custom_assets; +mod market_assets; +mod subsets; +#[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 diff --git a/primitives/src/assets/all_assets.rs b/primitives/src/assets/all_assets.rs new file mode 100644 index 000000000..012486af9 --- /dev/null +++ b/primitives/src/assets/all_assets.rs @@ -0,0 +1,170 @@ +// 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), + + #[codec(index = 7)] + CampaignAsset(#[codec(compact)] CampaignAssetId), + + #[codec(index = 8)] + CustomAsset(#[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 { + MarketAssetClass::::CategoricalOutcome(market_id, cat_id) => { + Self::CategoricalOutcome(market_id, cat_id) + } + MarketAssetClass::::ScalarOutcome(market_id, scalar_pos) => { + Self::ScalarOutcome(market_id, scalar_pos) + } + MarketAssetClass::::ParimutuelShare(market_id, cat_id) => { + Self::ParimutuelShare(market_id, cat_id) + } + MarketAssetClass::::PoolShare(pool_id) => Self::PoolShare(pool_id), + } + } +} + +impl From for Asset { + fn from(value: CampaignAssetClass) -> Self { + Self::CampaignAsset(value.0) + } +} + +impl From for Asset { + fn from(value: CustomAssetClass) -> Self { + Self::CustomAsset(value.0) + } +} + +impl From> for Asset { + fn from(value: CurrencyClass) -> Self { + match value { + CurrencyClass::::CategoricalOutcome(market_id, cat_id) => { + Self::CategoricalOutcome(market_id, cat_id) + } + CurrencyClass::::ScalarOutcome(market_id, scalar_pos) => { + Self::ScalarOutcome(market_id, scalar_pos) + } + CurrencyClass::::ParimutuelShare(market_id, cat_id) => { + Self::ParimutuelShare(market_id, cat_id) + } + CurrencyClass::::PoolShare(pool_id) => Self::PoolShare(pool_id), + CurrencyClass::::ForeignAsset(asset_id) => Self::ForeignAsset(asset_id), + } + } +} + +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 new file mode 100644 index 000000000..dcf912a77 --- /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 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::::CampaignAsset(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..d29359d43 --- /dev/null +++ b/primitives/src/assets/currencies.rs @@ -0,0 +1,77 @@ +// 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 +// 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, Ord, PartialEq, PartialOrd, TypeInfo, +)] +pub enum CurrencyClass { + // All Outcome and Share variants will be removed once the lazy migration from + // orml-tokens to pallet-assets is complete + #[codec(index = 0)] + CategoricalOutcome(MI, CategoryIndex), + + #[codec(index = 1)] + ScalarOutcome(MI, ScalarPosition), + + #[codec(index = 3)] + PoolShare(PoolId), + + #[codec(index = 5)] + ForeignAsset(u32), + + #[codec(index = 6)] + 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()) + } +} + +impl TryFrom> for CurrencyClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::CategoricalOutcome(market_id, cat_id) => { + Ok(Self::CategoricalOutcome(market_id, cat_id)) + } + Asset::::ScalarOutcome(market_id, scalar_pos) => { + Ok(Self::ScalarOutcome(market_id, scalar_pos)) + } + Asset::::ParimutuelShare(market_id, cat_id) => { + Ok(Self::ParimutuelShare(market_id, cat_id)) + } + Asset::::PoolShare(pool_id) => Ok(Self::PoolShare(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..b5f8339ec --- /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 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::::CustomAsset(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..7573cf74a --- /dev/null +++ b/primitives/src/assets/market_assets.rs @@ -0,0 +1,63 @@ +// 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, PartialOrd, Ord, TypeInfo, +)] +pub enum MarketAssetClass { + #[codec(index = 0)] + CategoricalOutcome(MI, CategoryIndex), + + #[codec(index = 1)] + ScalarOutcome(MI, ScalarPosition), + + #[codec(index = 3)] + PoolShare(PoolId), + + #[codec(index = 6)] + ParimutuelShare(MI, CategoryIndex), +} + +impl TryFrom> for MarketAssetClass { + type Error = (); + + fn try_from(value: Asset) -> Result { + match value { + Asset::::CategoricalOutcome(market_id, cat_id) => { + Ok(Self::CategoricalOutcome(market_id, cat_id)) + } + Asset::::ScalarOutcome(market_id, scalar_pos) => { + Ok(Self::ScalarOutcome(market_id, scalar_pos)) + } + Asset::::ParimutuelShare(market_id, cat_id) => { + Ok(Self::ParimutuelShare(market_id, cat_id)) + } + Asset::::PoolShare(pool_id) => Ok(Self::PoolShare(pool_id)), + _ => Err(()), + } + } +} 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.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..49829978c --- /dev/null +++ b/primitives/src/assets/tests/conversion.rs @@ -0,0 +1,358 @@ +// 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::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + MarketAssetClass::::PoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::ParimutuelShare(7, 8), + MarketAssetClass::::ParimutuelShare(7, 8); + "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(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); + "categorical_outcome" +)] +#[test_case( + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long), + Asset::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + MarketAssetClass::::PoolShare(7), + Asset::::PoolShare(7); + "pool_share" +)] +#[test_case( + MarketAssetClass::::ParimutuelShare(7, 8), + Asset::::ParimutuelShare(7, 8); + "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::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + CurrencyClass::::PoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::ParimutuelShare(7, 8), + CurrencyClass::::ParimutuelShare(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(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); + "categorical_outcome" +)] +#[test_case( + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long), + Asset::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + CurrencyClass::::PoolShare(7), + Asset::::PoolShare(7); + "pool_share" +)] +#[test_case( + CurrencyClass::::ParimutuelShare(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::::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::::CampaignAsset(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::::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::::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); +} + +// 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..050da1db5 --- /dev/null +++ b/primitives/src/assets/tests/scale_codec.rs @@ -0,0 +1,146 @@ +// 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 <> 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), + CurrencyClass::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + CurrencyClass::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + CurrencyClass::::PoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::ParimutuelShare(7, 8), + CurrencyClass::::ParimutuelShare(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); +} + +// Assets <> MarketAssetClass +#[test_case( + Asset::::CategoricalOutcome(7, 8), + MarketAssetClass::::CategoricalOutcome(7, 8); + "categorical_outcome" +)] +#[test_case( + Asset::::ScalarOutcome(7, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(7, ScalarPosition::Long); + "scalar_outcome" +)] +#[test_case( + Asset::::PoolShare(7), + MarketAssetClass::::PoolShare(7); + "pool_share" +)] +#[test_case( + Asset::::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::::ParimutuelShare(7, 8), + ParimutuelAssetClass::Share(7, 8); + "parimutuel_share" +)] +fn index_matching_works_for_parimutuel_assets( + old_asset: Asset, + 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(); + 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 11627c67c..5b2ab65c2 100644 --- a/primitives/src/constants/mock.rs +++ b/primitives/src/constants/mock.rs @@ -20,13 +20,30 @@ pub use super::*; use crate::{ - asset::Asset, - types::{Balance, CurrencyId, Moment}, + 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; + 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"); @@ -155,16 +172,15 @@ 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: Currencies| -> Balance {2}; } parameter_type_with_key! { - pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance { - match currency_id { - Asset::Ztg => ExistentialDeposit::get(), - _ => 10 - } - }; + pub ExistentialDepositsAssets: |_asset_id: Assets| -> Balance {2}; } // System diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 3c571da82..e46cd5fbf 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -20,13 +20,12 @@ extern crate alloc; -mod asset; +mod assets; pub mod constants; mod market; pub mod math; mod max_runtime_usize; mod outcome_report; mod proxy_type; -mod serde_wrapper; pub mod traits; pub mod types; 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/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 00fce342a..d01d0a8c4 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -22,7 +22,9 @@ 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; mod zeitgeist_multi_reservable_currency; @@ -32,6 +34,8 @@ 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::*; pub use zeitgeist_multi_reservable_currency::ZeitgeistAssetManager; diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index 7c7accf1c..6ba9db4fc 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. @@ -20,11 +20,11 @@ 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; -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 @@ -34,7 +34,7 @@ type MarketOfDisputeApi = Market< ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + BaseAsset, >; type GlobalDisputeItemOfDisputeApi = @@ -45,7 +45,7 @@ pub trait DisputeApi { type Balance; type NegativeImbalance; type BlockNumber; - type MarketId: MaxEncodedLen; + type MarketId: MaxEncodedLen + HasCompact; type Moment; type Origin; @@ -150,14 +150,14 @@ type MarketOfDisputeResolutionApi = Market< ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + BaseAsset, >; 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 251365157..12c1ad7ef 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -18,24 +18,24 @@ #![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}, 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 // 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 @@ -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/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/traits/swaps.rs b/primitives/src/traits/swaps.rs index cff467ac9..415c163bd 100644 --- a/primitives/src/traits/swaps.rs +++ b/primitives/src/traits/swaps.rs @@ -18,12 +18,12 @@ use crate::types::PoolId; use alloc::vec::Vec; -use frame_support::dispatch::{DispatchError, Weight}; -use sp_runtime::DispatchResult; +use frame_support::dispatch::{DispatchError, DispatchResult, Weight}; +use parity_scale_codec::{HasCompact, MaxEncodedLen}; pub trait Swaps { - type Asset; - type Balance; + type Asset: MaxEncodedLen; + type Balance: HasCompact + MaxEncodedLen; /// Creates a new pool. /// 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/primitives/src/types.rs b/primitives/src/types.rs index 417a71cc5..68ce0b1c1 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -17,8 +17,7 @@ // along with Zeitgeist. If not, see . pub use crate::{ - asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, - serde_wrapper::*, + assets::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, }; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; @@ -78,10 +77,38 @@ impl<'a> Arbitrary<'a> for MultiHash { } } -/// ORML adapter +/// ORML adapter. pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; -pub type CurrencyId = Asset; +/// ID type for any asset class. +pub type Assets = Asset; + +/// 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. +pub type CampaignAssetId = u128; + +/// Asset type representing custom assets. +pub type CustomAsset = CustomAssetClass; + +// ID type of the custom 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; + +/// 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. @@ -129,6 +156,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/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index 6031e0c83..bdb4d7d0d 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 } @@ -108,6 +109,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 } @@ -183,6 +185,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", @@ -232,6 +235,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", @@ -353,6 +357,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", @@ -379,6 +384,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 6086541ee..092184ade 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; @@ -28,10 +27,10 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Asset, CustomMetadata}; +use zeitgeist_primitives::types::{CustomMetadata, XcmAsset}; 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 @@ -72,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) @@ -104,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 = 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: 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/test_net.rs b/runtime/battery-station/src/integration_tests/xcm/test_net.rs index 7e1becfd9..2ee922bc5 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,9 +102,9 @@ 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, FOREIGN_PARENT_ID, roc(10)), - (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, roc(1)), + (ALICE, Assets::Ztg, ztg(10)), + (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 539ee4afa..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 @@ -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,82 +26,97 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{battery_station, general_key, AssetConvert}, - CurrencyId, + 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(CurrencyId::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(CurrencyId::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(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), - ); - - assert_eq!( - >::convert(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(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), - ); +#[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(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(CurrencyId::CombinatorialOutcome), 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 20d7350d3..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, Balance, Balances, CurrencyId, 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), - CurrencyId::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,7 +123,7 @@ 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, @@ -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 CurrencyId::Ztg as BTC in this context. - CurrencyId::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,7 +256,7 @@ 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(|| { @@ -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,7 +344,7 @@ 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( @@ -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), - CurrencyId::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/lib.rs b/runtime/battery-station/src/lib.rs index 7b3415104..34c2c6b8f 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,17 @@ 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_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}; #[cfg(feature = "std")] use sp_version::NativeVersion; @@ -64,7 +69,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, @@ -168,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/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index a7175708e..e0490cd20 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,46 @@ 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(); + /// 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; @@ -198,7 +239,7 @@ parameter_types! { pub const MaxLiquidityTreeDepth: u32 = 9u32; // 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. @@ -357,6 +398,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; @@ -464,17 +507,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: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), - Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::CategoricalOutcome(_, _) => ExistentialDeposit::get(), + Currencies::ParimutuelShare(_, _) => ExistentialDeposit::get(), + Currencies::PoolShare(_) => ExistentialDeposit::get(), + Currencies::ScalarOutcome(_, _) => 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(&XcmAsset::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; @@ -483,9 +526,7 @@ parameter_type_with_key! { 1 } #[cfg(not(feature = "parachain"))] - Asset::ForeignAsset(_) => ExistentialDeposit::get(), - Asset::Ztg => ExistentialDeposit::get(), - Asset::ParimutuelShare(_,_) => 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 1b2f16f11..9deb85aa0 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::{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<(CurrencyId, 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: CurrencyId, + _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 e472ce980..c68d81f3d 100644 --- a/runtime/battery-station/src/xcm_config/config.rs +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -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,8 +50,8 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, }; -use xcm_executor::{traits::TransactAsset, Assets, Config}; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; +use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; +use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::XcmAsset}; pub mod battery_station { #[cfg(test)] @@ -155,7 +155,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 adj_am = AlignedFractionalMultiAssetTransactor::adjust_fractional_places(&revenue).fun; @@ -211,9 +211,9 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, - CurrencyIdConvert: Convert>, + CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, > AlignedFractionalTransactAsset< @@ -224,29 +224,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) = XcmAsset::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() @@ -254,8 +266,8 @@ impl< } impl< - AssetRegistry: Inspect, - CurrencyIdConvert: Convert>, + AssetRegistry: Inspect, + CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, > TransactAsset @@ -279,7 +291,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) } @@ -289,7 +301,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) } @@ -310,17 +322,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 @@ -332,33 +344,42 @@ 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( + Assets::Ztg => Some(MultiLocation::new( 1, X2( Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(battery_station::KEY), ), )), - Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).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. -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) @@ -371,21 +392,28 @@ 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); } - 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()), } } } -impl Convert> for AssetConvert { - fn convert(asset: MultiAsset) -> Option { +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 { >::convert(location).ok() } else { @@ -394,8 +422,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 fd9dced37..bc6178eee 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::{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 = CurrencyId, + AssetId = XcmAsset, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index c23e89d33..c4e210c2a 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/fees.rs b/runtime/common/src/fees.rs index daf99543a..22562d1c8 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. // @@ -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,19 +50,30 @@ 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, ); debug_assert!(res.is_ok()); } } + + /// Disregards the fees. + pub struct DealWithCampaignFees; + impl OnUnbalanced> for DealWithCampaignFees { + fn on_unbalanced(_fees_and_tips: CreditOf) { + // Handled by type OnDropCredit + return; + } + } }; } #[macro_export] macro_rules! impl_foreign_fees { () => { + #[cfg(feature = "parachain")] + use frame_support::ensure; use frame_support::{ pallet_prelude::InvalidTransaction, traits::{ @@ -81,7 +91,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,12 +103,13 @@ 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 // `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::ForeignAsset(0))` is used. pub(crate) fn calculate_fee( native_fee: Balance, @@ -116,15 +130,64 @@ macro_rules! impl_foreign_fees { Ok(converted_fee) } - #[cfg(feature = "parachain")] - pub(crate) fn get_fee_factor( - currency_id: CurrencyId, + fn get_fee_factor_campaign_asset( + campaign_asset: CampaignAsset, ) -> Result { - let metadata = ::metadata(¤cy_id).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 +195,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, - asset_id: TxPaymentAssetId, + asset: Assets, ) -> Result { - #[cfg(feature = "parachain")] - { - let currency_id = Asset::ForeignAsset(asset_id); - let fee_factor = get_fee_factor(currency_id)?; - 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 +256,13 @@ 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 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 +271,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() { - Asset::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 +345,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 +393,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( - Asset::ForeignAsset(0), - fee_and_tip_balance, - ); + let fees_and_tips = AssetRouter::issue(Asset::ForeignAsset(0), fee_and_tip_balance); assert!( - Tokens::free_balance(Asset::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(Asset::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 +538,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 = Asset::ForeignAsset(0); - - assert_ok!(AssetRegistry::register_asset(RuntimeOrigin::root(), meta.clone(), Some(dot))); - - - 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); }); } @@ -454,7 +616,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Asset::ForeignAsset(dot_asset_id); + let dot = XcmAsset::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -462,7 +624,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); }); } @@ -505,7 +667,7 @@ macro_rules! fee_tests { additional: custom_metadata, }; let dot_asset_id = 0u32; - let dot = Asset::ForeignAsset(dot_asset_id); + let dot = XcmAsset::ForeignAsset(dot_asset_id); assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), @@ -514,7 +676,7 @@ macro_rules! fee_tests { )); assert_noop!( - get_fee_factor(dot), + get_fee_factor(dot.into()), TransactionValidityError::Invalid(InvalidTransaction::Custom(3u8)) ); }); @@ -539,15 +701,15 @@ macro_rules! fee_tests { location: None, additional: custom_metadata, }; - let non_location_token = Asset::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); + assert_eq!(get_fee_factor(non_location_token.into()).unwrap(), 10_393); }); } @@ -579,29 +741,20 @@ macro_rules! fee_tests { assert_ok!(AssetRegistry::register_asset( RuntimeOrigin::root(), meta, - Some(dot) + 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 +765,5 @@ macro_rules! fee_tests { }); } } - } + }; } diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 3084e4a71..402d6cb74 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -90,6 +90,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; @@ -281,6 +286,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, + CustomAssets: 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, @@ -315,6 +323,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)* } @@ -558,7 +567,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "parachain")] impl orml_asset_registry::Config for Runtime { - type AssetId = CurrencyId; + type AssetId = XcmAsset; type AssetProcessor = CustomAssetProcessor; type AuthorityOrigin = AsEnsureOriginWithArg; type Balance = Balance; @@ -569,14 +578,14 @@ 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 + orml_traits::currency::MutationHooks for CurrencyHooks { type OnDust = orml_tokens::TransferDust; @@ -593,7 +602,7 @@ macro_rules! impl_config_traits { type Amount = Amount; type Balance = Balance; type CurrencyHooks = CurrencyHooks; - type CurrencyId = CurrencyId; + type CurrencyId = Currencies; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -613,7 +622,7 @@ macro_rules! impl_config_traits { type AccountIdToMultiLocation = AccountIdToMultiLocation; type Balance = Balance; type BaseXcmWeight = BaseXcmWeight; - type CurrencyId = CurrencyId; + type CurrencyId = XcmAsset; type CurrencyIdConvert = AssetConvert; type RuntimeEvent = RuntimeEvent; type MaxAssetsForTransfer = MaxAssetsForTransfer; @@ -626,6 +635,109 @@ macro_rules! impl_config_traits { type XcmExecutor = xcm_executor::XcmExecutor; } + // 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 = CustomAssetsApprovalDeposit; + type AssetAccountDeposit = CustomAssetsAccountDeposit; + type AssetDeposit = CustomAssetsDeposit; + 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 Destroyer = AssetRouter; + 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 = CampaignAsset; + type AssetIdParameter = Compact; + type Balance = Balance; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetsBenchmarkHelper; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Destroyer = AssetRouter; + 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 Destroyer = AssetRouter; + 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; @@ -983,8 +1095,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 { @@ -1055,6 +1167,22 @@ 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 DestroyAccountWeight = DestroyAccountWeight; + type DestroyApprovalWeight = DestroyApprovalWeight; + type DestroyFinishWeight = DestroyFinishWeight; + type MarketAssetType = MarketAsset; + type MarketAssets = MarketAssets; + } + impl zrml_authorized::Config for Runtime { type AuthorizedDisputeResolutionOrigin = EnsureRootOrMoreThanHalfAdvisoryCommittee; type Currency = Balances; @@ -1132,7 +1260,12 @@ 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")] + type AssetRegistry = AssetRegistry; type Authorized = Authorized; type Currency = Balances; type Court = Court; @@ -1163,6 +1296,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; @@ -1171,9 +1306,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 ValidityBond = ValidityBond; @@ -1225,7 +1357,7 @@ macro_rules! impl_config_traits { } impl zrml_swaps::Config for Runtime { - type Asset = Asset; + type Asset = Assets; type RuntimeEvent = RuntimeEvent; type ExitFee = ExitFee; type MinAssets = MinAssets; @@ -1270,12 +1402,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; } }; @@ -1366,6 +1500,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, CustomAssets); list_benchmark!(list, extra, pallet_balances, Balances); list_benchmark!(list, extra, pallet_bounties, Bounties); list_benchmark!(list, extra, pallet_collective, AdvisoryCommittee); @@ -1470,6 +1605,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, CustomAssets); add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_bounties, Bounties); add_benchmark!(params, batches, pallet_collective, AdvisoryCommittee); @@ -1778,16 +1914,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) } } @@ -1857,7 +1993,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}; @@ -1871,8 +2007,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 } @@ -1972,15 +2108,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, CurrencyId, 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: CurrencyId = Asset::CategoricalOutcome(0, 0); + const ASSET: Currencies = Currencies::ForeignAsset(7); runtime_benchmarks! { { Runtime, orml_tokens } @@ -1989,7 +2125,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()); @@ -2002,7 +2138,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); @@ -2013,7 +2149,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()); @@ -2025,7 +2161,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()); @@ -2055,7 +2191,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; @@ -2067,7 +2203,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/common/src/weights/mod.rs b/runtime/common/src/weights/mod.rs index 826493ca0..27fae644e 100644 --- a/runtime/common/src/weights/mod.rs +++ b/runtime/common/src/weights/mod.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. @@ -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..095468d8c --- /dev/null +++ b/runtime/common/src/weights/pallet_assets.rs @@ -0,0 +1,405 @@ +// 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 . + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2024-02-15`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! 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/release/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=2 +// --repeat=0 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=native +// --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: 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: `285` + // Estimated: `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: 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: `109` + // Estimated: `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: 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: `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: 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 { + // Proof Size summary in bytes: + // 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: 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 { + // Proof Size summary in bytes: + // 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: 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: `376` + // Estimated: `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: 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: `376` + // Estimated: `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: 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: `481` + // Estimated: `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: 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: `520` + // Estimated: `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: 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: `520` + // Estimated: `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: 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: `520` + // Estimated: `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: 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: `481` + // Estimated: `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: 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: `481` + // Estimated: `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: 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: `410` + // Estimated: `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: 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: `410` + // Estimated: `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: 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: `376` + // Estimated: `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: 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: `376` + // Estimated: `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: 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 { + // Proof Size summary in bytes: + // Measured: `376` + // Estimated: `5324` + // 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: 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: `569` + // Estimated: `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: 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 { + // Proof Size summary in bytes: + // Measured: `183` + // Estimated: `5324` + // 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: 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: `569` + // Estimated: `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: 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: `376` + // Estimated: `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: 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: `410` + // Estimated: `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: 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: `687` + // Estimated: `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: 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: `577` + // Estimated: `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: 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: `577` + // Estimated: `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/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index 59d4edf57..949e46a7c 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 } @@ -107,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 } @@ -183,6 +185,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 +234,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", @@ -343,6 +347,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", @@ -369,6 +374,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 a633132a6..525a2e369 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; @@ -28,10 +27,10 @@ use xcm::{ latest::{Junction::Parachain, Junctions::X2, MultiLocation}, VersionedMultiLocation, }; -use zeitgeist_primitives::types::{Asset, CustomMetadata}; +use zeitgeist_primitives::types::{CustomMetadata, XcmAsset}; 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 @@ -72,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) @@ -104,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 = 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: 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/test_net.rs b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs index 050527635..c8f212d12 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,9 +99,9 @@ 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, FOREIGN_PARENT_ID, dot(10)), - (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, dot(10)), + (ALICE, Assets::Ztg, ztg(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 7fbcfa385..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 @@ -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,81 +26,96 @@ use crate::{ test_net::Zeitgeist, }, xcm_config::config::{general_key, zeitgeist, AssetConvert}, - CurrencyId, + 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(CurrencyId::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(CurrencyId::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(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), - ); - - assert_eq!( - >::convert(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(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), - ); +#[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(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(CurrencyId::CombinatorialOutcome), 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 2638f76f5..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, Balance, Balances, CurrencyId, 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), - CurrencyId::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,7 +123,7 @@ 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, @@ -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 CurrencyId::Ztg as BTC in this context. - CurrencyId::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,7 +256,7 @@ 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(|| { @@ -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 CurrencyId::Ztg as ETH in this context. - CurrencyId::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,7 +389,7 @@ 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(|| { @@ -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,7 +468,7 @@ 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( @@ -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), - CurrencyId::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/lib.rs b/runtime/zeitgeist/src/lib.rs index a3eb21547..b0790d71d 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, @@ -112,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::{ @@ -145,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/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index d3e171c22..4af6be894 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,44 @@ 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(); + /// 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; @@ -198,7 +237,7 @@ parameter_types! { pub const MaxLiquidityTreeDepth: u32 = 9u32; // 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. @@ -357,6 +396,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; @@ -464,17 +505,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: CurrencyId| -> Balance { + pub ExistentialDeposits: |currency_id: Currencies| -> Balance { match currency_id { - Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), - Asset::CombinatorialOutcome => ExistentialDeposit::get(), - Asset::PoolShare(_) => ExistentialDeposit::get(), - Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), + Currencies::CategoricalOutcome(_, _) => ExistentialDeposit::get(), + Currencies::ParimutuelShare(_, _) => ExistentialDeposit::get(), + Currencies::PoolShare(_) => ExistentialDeposit::get(), + Currencies::ScalarOutcome(_, _) => 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(&XcmAsset::ForeignAsset(*id)); if let Some(metadata) = maybe_metadata { return metadata.existential_deposit; @@ -483,9 +524,7 @@ parameter_type_with_key! { 1 } #[cfg(not(feature = "parachain"))] - Asset::ForeignAsset(_) => ExistentialDeposit::get(), - Asset::Ztg => ExistentialDeposit::get(), - Asset::ParimutuelShare(_, _) => 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 1b2f16f11..9deb85aa0 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::{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<(CurrencyId, 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: CurrencyId, + _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 f9424eaea..27d6c1e54 100644 --- a/runtime/zeitgeist/src/xcm_config/config.rs +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -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,8 +52,8 @@ use xcm_builder::{ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, TakeWeightCredit, }; -use xcm_executor::{traits::TransactAsset, Assets, Config}; -use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::Asset}; +use xcm_executor::{traits::TransactAsset, Assets as ExecutorAssets, Config}; +use zeitgeist_primitives::{constants::BalanceFractionalDecimals, types::XcmAsset}; pub mod zeitgeist { #[cfg(test)] @@ -157,7 +157,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 adj_am = AlignedFractionalMultiAssetTransactor::adjust_fractional_places(&revenue).fun; @@ -213,9 +213,9 @@ pub struct AlignedFractionalTransactAsset< } impl< - AssetRegistry: Inspect, + AssetRegistry: Inspect, FracDecPlaces: Get, - CurrencyIdConvert: Convert>, + CurrencyIdConvert: Convert>, TransactAssetDelegate: TransactAsset, > AlignedFractionalTransactAsset< @@ -226,29 +226,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) = XcmAsset::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() @@ -256,8 +268,8 @@ impl< } impl< - AssetRegistry: Inspect, - CurrencyIdConvert: Convert>, + AssetRegistry: Inspect, + CurrencyIdConvert: Convert>, FracDecPlaces: Get, TransactAssetDelegate: TransactAsset, > TransactAsset @@ -281,7 +293,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) } @@ -291,7 +303,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) } @@ -312,17 +324,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 @@ -334,33 +346,42 @@ 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( + Assets::Ztg => Some(MultiLocation::new( 1, X2( Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(zeitgeist::KEY), ), )), - Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).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. -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) @@ -373,21 +394,28 @@ 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); } - 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()), } } } -impl Convert> for AssetConvert { - fn convert(asset: MultiAsset) -> Option { +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 { >::convert(location).ok() } else { @@ -396,8 +424,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 a40ce9177..0b2f18661 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::{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 = CurrencyId, + AssetId = XcmAsset, Balance = Balance, CustomMetadata = CustomMetadata, >, diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 6d7a293b1..b63463893 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}" diff --git a/zrml/asset-router/Cargo.toml b/zrml/asset-router/Cargo.toml new file mode 100644 index 000000000..3a264a4d2 --- /dev/null +++ b/zrml/asset-router/Cargo.toml @@ -0,0 +1,39 @@ +[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-macros = { workspace = true } +zeitgeist-primitives = { workspace = true } + +[dev-dependencies] +orml-tokens = { 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 = [ + "frame-support/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..34908131e --- /dev/null +++ b/zrml/asset-router/README.md @@ -0,0 +1,61 @@ +# 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. + +## Overview + +The asset router 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 +managed asset destruction, that handles asset destruction for all the assets +registered through the `ManagedDestroy` interface whenever on-chain execution +time is available. Finally, the asset router also provides a lazy migration +mechanism for asset types from the `CurrencyType` to the `MarketAssetType`. + +### Terminology + +- _Asset Type_: An asset type is used to represent assets that share the same + purpose. +- _Asset Class_: An asset class is an overarching collection of multible asset + types that share common properties. +- _Tokens_: Tokens are a countable number of instantiations of a specific asset + type that can be moved between accounts. +- _Lazy Migration_: A lazy migration migrates data and the control over the data + from a source to a destination over a prolonged amount of time, usually per + request of the data or after expiry of the data. +- _Managed Asset Destruction_: A mechanism to automatically destroy an asset + type. + +### 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. + +### Lazy migration from `CurrencyClass` to `MarketAssetClass` + +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/lib.rs b/zrml/asset-router/src/lib.rs new file mode 100644 index 000000000..ec97b3937 --- /dev/null +++ b/zrml/asset-router/src/lib.rs @@ -0,0 +1,520 @@ +// 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 . + +#![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; +mod types; + +#[frame_support::pallet] +pub mod pallet { + pub(crate) use super::types::*; + pub(crate) use alloc::collections::BTreeMap; + 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, Unbalanced}, + DepositConsequence, WithdrawConsequence, + }, + BalanceStatus as Status, ConstU32, + }, + BoundedVec, Parameter, + }; + pub(crate) use orml_traits::{ + arithmetic::Signed, + currency::{ + MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, + NamedMultiReservableCurrency, TransferAll, + }, + BalanceStatus, LockIdentifier, + }; + pub(crate) use pallet_assets::ManagedDestroy; + use parity_scale_codec::{FullCodec, MaxEncodedLen}; + use scale_info::TypeInfo; + pub(crate) use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, Bounded, Get, MaybeSerializeDeserialize, Member, Saturating, Zero, + }, + FixedPointOperand, SaturatedConversion, + }; + 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; + 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 + + Destroy + + Inspect + + Transfer + + Mutate + + Unbalanced + { + } + + impl AssetTraits for G + where + G: Create + + Destroy + + Inspect + + Transfer + + Mutate + + Unbalanced, + 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 + + Ord + + 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 + + Unbalanced; + /// 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; + + /// 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; + /// The market asset type. + type MarketAssetType: TryFrom + + Copy + + Debug + + Eq + + FullCodec + + MaxEncodedLen + + MaybeSerializeDeserialize + + TypeInfo; + } + + /// Keeps track of assets that have to be destroyed. + #[pallet::storage] + pub(super) type DestroyAssets = StorageValue<_, DestroyAssetsT, ValueQuery>; + + /// 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, + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[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(); + + if !remaining_weight + .all_gte(max_extra_weight.saturating_add(T::DbWeight::get().reads(1))) + { + return remaining_weight; + }; + + let mut destroy_assets = DestroyAssets::::get(); + if destroy_assets.len() == 0 { + return remaining_weight.saturating_sub(T::DbWeight::get().reads(1)); + } + + remaining_weight = remaining_weight + .saturating_sub(T::DbWeight::get().reads_writes(1, 1)) + .saturating_sub(max_extra_weight); + + remaining_weight = + Self::handle_asset_destruction(remaining_weight, &mut destroy_assets); + + DestroyAssets::::put(destroy_assets); + remaining_weight + } + } + + 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 + ); + + 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 + ); + + saftey_counter_outer = saftey_counter_outer.saturating_add(1); + } + + remaining_weight + } + + fn handle_destroy_accounts( + asset: &mut AssetInDestruction, + mut remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Accounts { + return Err(DestructionError::WrongState(remaining_weight)); + } + 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())) + } + } + } + + fn handle_destroy_approvals( + asset: &mut AssetInDestruction, + mut remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Approvals { + return Err(DestructionError::WrongState(remaining_weight)); + } + 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 handle_destroy_finish( + asset: &mut AssetInDestruction, + remaining_weight: Weight, + ) -> DestructionResult { + if *asset.state() != DestructionState::Finalization { + return Err(DestructionError::WrongState(remaining_weight)); + } + 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), + )); + } + + 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 + ); + } + + 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, + ) + } + + #[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..06dff530c --- /dev/null +++ b/zrml/asset-router/src/macros.rs @@ -0,0 +1,151 @@ +// 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(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) + } + }; +} + +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 { + ($currency_id:expr, $error:expr, $currency_trait:ident, $currency_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) { + 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)); + $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 new file mode 100644 index 000000000..716142b96 --- /dev/null +++ b/zrml/asset-router/src/mock.rs @@ -0,0 +1,302 @@ +// 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::{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::{parameter_types, 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::CampaignAsset(0); +pub(super) const CAMPAIGN_ASSET_INTERNAL: CampaignAssetClass = CampaignAssetClass(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); +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 = 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; +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 + 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 DestroyAccountWeight = DestroyWeight; + type DestroyApprovalWeight = DestroyWeight; + type DestroyFinishWeight = DestroyWeight; + 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 Destroyer = AssetRouter; + 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 Destroyer = AssetRouter; + 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 Destroyer = AssetRouter; + 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(super) 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() + } +} + +#[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..1c19338d4 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/inspect.rs @@ -0,0 +1,124 @@ +// 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 let Ok(currency) = T::CurrencyType::try_from(asset) { + if >::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/managed_destroy.rs b/zrml/asset-router/src/pallet_impl/managed_destroy.rs new file mode 100644 index 000000000..fd22ac6b0 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/managed_destroy.rs @@ -0,0 +1,73 @@ +// 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(()) + } + + 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..d0a94a1c7 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/mod.rs @@ -0,0 +1,28 @@ +// 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_currency; +pub mod multi_currency_extended; +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/multi_currency.rs b/zrml/asset-router/src/pallet_impl/multi_currency.rs new file mode 100644 index 000000000..77a87d3a8 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multi_currency.rs @@ -0,0 +1,217 @@ +// 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(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() + } + } + + fn ensure_can_withdraw( + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let withdraw_consequence = 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::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()); + }; + + withdraw_consequence.into_result().map(|_| ()) + } + + fn transfer( + currency_id: Self::CurrencyId, + from: &T::AccountId, + to: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + 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()) + } + } + + 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(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(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 + } + } + + fn slash( + currency_id: Self::CurrencyId, + who: &T::AccountId, + amount: Self::Balance, + ) -> Self::Balance { + if let Ok(asset) = T::MarketAssetType::try_from(currency_id) { + // Route "pre new asset system" market assets to `CurrencyType` + if T::MarketAssets::asset_exists(asset) { + // 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)) + .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 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 new file mode 100644 index 000000000..55e9d5779 --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/multi_lockable_currency.rs @@ -0,0 +1,81 @@ +// 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(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, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn extend_lock( + lock_id: LockIdentifier, + currency_id: Self::CurrencyId, + 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, + ); + } + + Err(Error::::Unsupported.into()) + } + + fn remove_lock( + lock_id: LockIdentifier, + 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, + ); + } + + 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/named_multi_reserveable_currency.rs b/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs new file mode 100644 index 000000000..c1f70393d --- /dev/null +++ b/zrml/asset-router/src/pallet_impl/named_multi_reserveable_currency.rs @@ -0,0 +1,130 @@ +// 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(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, + ); + } + + 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(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, + ); + } + + 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(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, + ); + } + + 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(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 + ); + } + + 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(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 + ); + } + + 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/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/create.rs b/zrml/asset-router/src/tests/create.rs new file mode 100644 index 000000000..4b81382ac --- /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 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 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 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 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/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 new file mode 100644 index 000000000..cca618b9b --- /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 test_helper(asset: Assets, initial_amount: ::Balance) { + 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)); + assert_ok!(AssetRouter::finish_destroy(asset)); + assert!(!AssetRouter::asset_exists(asset)); +} + +#[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!( + pallet_assets::Call::::approve_transfer { + id: CAMPAIGN_ASSET_INTERNAL.into(), + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + }); +} + +#[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!( + pallet_assets::Call::::approve_transfer { + id: CUSTOM_ASSET_INTERNAL.into(), + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + }); +} + +#[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!( + pallet_assets::Call::::approve_transfer { + id: MARKET_ASSET_INTERNAL, + delegate: BOB, + amount: 1 + } + .dispatch_bypass_filter(Signed(ALICE).into()) + ); + + test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn 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..e762a3115 --- /dev/null +++ b/zrml/asset-router/src/tests/inspect.rs @@ -0,0 +1,113 @@ +// 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 test_helper(asset: Assets, initial_amount: ::Balance) { + 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); + 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 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 + ); + 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 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 + ); + 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 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 + ); + 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 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 + ); + assert_eq!(>::total_issuance(CUSTOM_ASSET_INTERNAL), 0); + assert_eq!(>::total_issuance(MARKET_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..1c2700ffb --- /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::CampaignAsset(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 new file mode 100644 index 000000000..48a23763d --- /dev/null +++ b/zrml/asset-router/src/tests/mod.rs @@ -0,0 +1,48 @@ +// 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 alloc::collections::BTreeMap; +use frame_support::{ + assert_noop, assert_ok, + dispatch::RawOrigin::Signed, + traits::{ + tokens::{ + fungibles::{Create, Destroy}, + DepositConsequence, WithdrawConsequence, + }, + OnIdle, UnfilteredDispatchable, + }, +}; +use orml_traits::{ + BalanceStatus, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, + NamedMultiReservableCurrency, +}; +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; +mod named_multi_reservable_currency; +mod unbalanced; 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..0e6802305 --- /dev/null +++ b/zrml/asset-router/src/tests/multi_currency.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 . + +#![cfg(test)] + +use super::*; +use orml_traits::MultiCurrency; +use test_case::test_case; +use zeitgeist_primitives::types::{Amount, Balance}; + +fn 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 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)); + + 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 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)); + + 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 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)); + + 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 routes_currencies_correctly() { + ExtBuilder::default().build().execute_with(|| { + 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), + 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 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..20497d243 --- /dev/null +++ b/zrml/asset-router/src/tests/multi_lockable_currency.rs @@ -0,0 +1,99 @@ +// 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; + +fn 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_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_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, currency_id.try_into().unwrap()) + .frozen, + 1 + ); + assert_ok!(AssetRouter::extend_lock(Default::default(), currency_id, &ALICE, 2)); + assert_eq!( + orml_tokens::Accounts::::get::< + u128, + ::CurrencyId, + >(ALICE, currency_id.try_into().unwrap()) + .frozen, + 2 + ); + assert_ok!(AssetRouter::remove_lock(Default::default(), currency_id, &ALICE)); + assert_eq!( + orml_tokens::Accounts::::get::< + u128, + ::CurrencyId, + >(ALICE, currency_id.try_into().unwrap()) + .frozen, + 0 + ); + }); +} + +#[test] +fn routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CAMPAIGN_ASSET); + }); +} + +#[test] +fn routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CUSTOM_ASSET); + }); +} + +#[test] +fn routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + 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..029ce57d6 --- /dev/null +++ b/zrml/asset-router/src/tests/multi_reservable_currency.rs @@ -0,0 +1,100 @@ +// 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; + +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!( + 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_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_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); + + assert!(AssetRouter::can_reserve(currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert!(!AssetRouter::can_reserve(currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT + 1)); + assert_ok!(AssetRouter::reserve(currency_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_eq!(AssetRouter::reserved_balance(currency_id, &ALICE), CURRENCY_INITIAL_AMOUNT); + assert_eq!(AssetRouter::slash_reserved(currency_id, &ALICE, 1), 0); + assert_eq!( + AssetRouter::repatriate_reserved( + currency_id, + &ALICE, + &BOB, + CURRENCY_MIN_BALANCE, + BalanceStatus::Reserved + ) + .unwrap(), + 0 + ); + assert_eq!(AssetRouter::reserved_balance(currency_id, &BOB), CURRENCY_MIN_BALANCE); + assert_eq!( + AssetRouter::reserved_balance(currency_id, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 1 + ); + assert_eq!(AssetRouter::unreserve(currency_id, &ALICE, 1), 0); + assert_eq!( + AssetRouter::reserved_balance(currency_id, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 2 + ); + }); +} + +#[test] +fn routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + 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..94f05ef5c --- /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; +use test_case::test_case; + +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), + 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); + assert_eq!(AssetRouter::reserved_balance_named(&Default::default(), asset, &ALICE), 0); +} + +#[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_id, &ALICE, CURRENCY_INITIAL_AMOUNT)); + assert_ok!(AssetRouter::reserve_named( + &Default::default(), + currency_id, + &ALICE, + CURRENCY_INITIAL_AMOUNT + )); + 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_id, + &ALICE, + &BOB, + CURRENCY_MIN_BALANCE, + BalanceStatus::Reserved + ) + .unwrap(), + 0 + ); + assert_eq!( + 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_id, &ALICE, 1), 0); + assert_eq!( + AssetRouter::reserved_balance_named(&Default::default(), currency_id, &ALICE), + CURRENCY_INITIAL_AMOUNT - CURRENCY_MIN_BALANCE - 2 + ); + }); +} + +#[test] +fn routes_campaign_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CAMPAIGN_ASSET, ALICE, true, CAMPAIGN_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CAMPAIGN_ASSET, CAMPAIGN_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn routes_custom_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(CUSTOM_ASSET, ALICE, true, CUSTOM_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(CUSTOM_ASSET, CUSTOM_ASSET_INITIAL_AMOUNT); + }); +} + +#[test] +fn routes_market_assets_correctly() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(AssetRouter::create(MARKET_ASSET, ALICE, true, MARKET_ASSET_MIN_BALANCE,)); + + unroutable_test_helper(MARKET_ASSET, MARKET_ASSET_INITIAL_AMOUNT); + }); +} 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/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/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/authorized/src/weights.rs b/zrml/authorized/src/weights.rs index 93ba4256a..97bcaf1b3 100644 --- a/zrml/authorized/src/weights.rs +++ b/zrml/authorized/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_authorized // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -87,7 +87,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `577` + // Measured: `610` // Estimated: `5677` // Minimum execution time: 35_410 nanoseconds. Weight::from_parts(36_251_000, 5677) @@ -105,7 +105,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: 10_110 nanoseconds. Weight::from_parts(12_690_000, 2524) @@ -123,7 +123,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: 9_200 nanoseconds. Weight::from_parts(9_520_000, 2524).saturating_add(T::DbWeight::get().reads(1)) diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index d58c9a610..603cd619e 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 70810ac77..6199490c3 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -29,7 +29,6 @@ use crate::{ }; use alloc::{ collections::{BTreeMap, BTreeSet}, - format, vec::Vec, }; use core::marker::PhantomData; @@ -66,7 +65,7 @@ use zeitgeist_primitives::{ math::checked_ops_res::{CheckedAddRes, CheckedRemRes, CheckedSubRes}, traits::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}, types::{ - Asset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, + BaseAsset, GlobalDisputeItem, Market, MarketDisputeMechanism, MarketStatus, OutcomeReport, ResultWithWeightInfo, }, }; @@ -226,7 +225,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 eafb1d38e..9a2373003 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/court/src/weights.rs b/zrml/court/src/weights.rs index 021007cd0..e36156997 100644 --- a/zrml/court/src/weights.rs +++ b/zrml/court/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_court // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -137,7 +137,7 @@ 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: `307` + // Measured: `340` // Estimated: `6500` // Minimum execution time: 42_610 nanoseconds. Weight::from_parts(44_230_000, 6500) @@ -150,7 +150,7 @@ 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: `307` + // Measured: `340` // Estimated: `6500` // Minimum execution time: 37_370 nanoseconds. Weight::from_parts(43_080_000, 6500) @@ -295,18 +295,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: Court YearlyInflation (r:1 w:0) /// Proof: Court YearlyInflation (max_values: Some(1), max_size: Some(4), added: 499, 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: 33_280 nanoseconds. - Weight::from_parts(34_480_000, 72996) - // Standard Error: 9_174 - .saturating_add(Weight::from_parts(19_968_654, 0).saturating_mul(j.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(j.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(j.into()))) - .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) @@ -425,7 +421,7 @@ 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: `423` + // Measured: `456` // Estimated: `5339` // Minimum execution time: 12_750 nanoseconds. Weight::from_parts(15_520_000, 5339).saturating_add(T::DbWeight::get().reads(2)) @@ -442,7 +438,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) fn has_failed() -> Weight { // Proof Size summary in bytes: - // Measured: `3189` + // Measured: `3222` // Estimated: `83504` // Minimum execution time: 38_700 nanoseconds. Weight::from_parts(45_630_000, 83504).saturating_add(T::DbWeight::get().reads(5)) 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/global-disputes/src/weights.rs b/zrml/global-disputes/src/weights.rs index 329d7710a..6ccc35602 100644 --- a/zrml/global-disputes/src/weights.rs +++ b/zrml/global-disputes/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_global_disputes // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -108,7 +108,7 @@ impl WeightInfoZeitgeist for WeightInfo { .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) @@ -133,7 +133,7 @@ impl WeightInfoZeitgeist for WeightInfo { .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(678), added: 3153, mode: MaxEncodedLen) @@ -146,7 +146,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `w` is `[1, 10]`. fn add_vote_outcome(_w: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `690 + w * (32 ±0)` + // Measured: `723 + w * (32 ±0)` // Estimated: `11501` // Minimum execution time: 69_591 nanoseconds. Weight::from_parts(88_511_398, 11501) @@ -182,7 +182,7 @@ 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: 74_690 nanoseconds. Weight::from_parts(90_080_000, 10955) 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/liquidity-mining/src/weights.rs b/zrml/liquidity-mining/src/weights.rs index 27fe33018..6cfe59345 100644 --- a/zrml/liquidity-mining/src/weights.rs +++ b/zrml/liquidity-mining/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_liquidity_mining // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index 5fb809959..209d0eab4 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -41,7 +41,7 @@ mod pallet { traits::{Hooks, StorageVersion, Time}, Blake2_128Concat, Parameter, }; - use parity_scale_codec::{FullCodec, MaxEncodedLen}; + use parity_scale_codec::{FullCodec, HasCompact, MaxEncodedLen}; use sp_runtime::{ traits::{ AtLeast32Bit, AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member, Saturating, @@ -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; @@ -83,6 +82,7 @@ mod pallet { type MarketId: AtLeast32Bit + Copy + Default + + HasCompact + MaxEncodedLen + MaybeSerializeDeserialize + Member 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/neo-swaps/Cargo.toml b/zrml/neo-swaps/Cargo.toml index 77cd8d3ee..4e457f224 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 } @@ -59,11 +61,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", @@ -79,8 +83,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..c8ea923a6 100644 --- a/zrml/neo-swaps/src/macros.rs +++ b/zrml/neo-swaps/src/macros.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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 a6d519fd8..28de6bf34 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; 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, + ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, + GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, + MaxApprovals, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, MaxDisputeDuration, + MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, MaxLiquidityTreeDepth, MaxLocks, MaxMarketLifetime, MaxOracleDuration, MaxOwners, MaxRejectReasonLen, MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinCategories, MinDisputeDuration, MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, @@ -61,8 +63,9 @@ use zeitgeist_primitives::{ math::fixed::FixedMul, traits::{DeployPoolApi, DistributeFees}, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - 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,7 +456,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Currencies; type DustRemovalWhitelist = DustRemovalWhitelist; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -413,7 +543,7 @@ impl pallet_treasury::Config for Runtime { #[cfg(feature = "parachain")] zrml_prediction_markets::impl_mock_registry! { MockRegistry, - CurrencyId, + zeitgeist_primitives::types::XcmAsset, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -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(); @@ -454,7 +584,7 @@ impl ExtBuilder { }; orml_asset_registry_mock::GenesisConfig { metadata: vec![( - FOREIGN_ASSET, + FOREIGN_ASSET.try_into().unwrap(), AssetMetadata { decimals: 18, name: "MKL".as_bytes().to_vec(), 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..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), @@ -215,7 +216,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..de5fd367e 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index 327cacd9b..62c46a7d7 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_neo_swaps // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -68,6 +68,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) /// Storage: System Account (r:3 w:3) /// Proof: System Account (max_values: None, max_size: Some(132), added: 2607, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:129 w:129) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: Tokens TotalIssuance (r:128 w:128) @@ -91,6 +93,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, 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:3 w:3) @@ -116,6 +120,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) @@ -139,6 +145,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) @@ -162,6 +170,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) @@ -185,6 +195,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:0) @@ -221,6 +233,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: NeoSwaps Pools (r:1 w:1) /// Proof: NeoSwaps Pools (max_values: None, max_size: Some(144745), added: 147220, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:128 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Tokens Accounts (r:256 w:256) /// Proof: Tokens Accounts (max_values: None, max_size: Some(123), added: 2598, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) diff --git a/zrml/orderbook/Cargo.toml b/zrml/orderbook/Cargo.toml index 9639b9545..e6ad5bdc3 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/fuzz/orderbook_v1_full_workflow.rs b/zrml/orderbook/fuzz/orderbook_v1_full_workflow.rs index 807eabd2f..a23fa0330 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}; #[cfg(feature = "arbitrary")] @@ -92,7 +92,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/benchmarks.rs b/zrml/orderbook/src/benchmarks.rs index b3075b678..751f990aa 100644 --- a/zrml/orderbook/src/benchmarks.rs +++ b/zrml/orderbook/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. @@ -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..924d24072 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,29 @@ 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 +331,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 +372,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 +395,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 +477,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 +505,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 1909806c6..03bde6194 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, ExistentialDeposits, + GetNativeCurrencyId, MaxLocks, MaxReserves, MinimumPeriod, OrderbookPalletId, BASE, }, traits::DistributeFees, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - 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,7 +161,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Currencies; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -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/types.rs b/zrml/orderbook/src/types.rs index b47673c64..981d11fbb 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; @@ -23,7 +23,7 @@ use zeitgeist_primitives::types::Asset; pub type OrderId = u128; #[derive(Clone, Encode, Eq, Decode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Order { +pub struct Order { pub market_id: MarketId, pub maker: AccountId, pub maker_asset: Asset, diff --git a/zrml/orderbook/src/utils.rs b/zrml/orderbook/src/utils.rs index 3ec2fb88b..08fa91ede 100644 --- a/zrml/orderbook/src/utils.rs +++ b/zrml/orderbook/src/utils.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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(), diff --git a/zrml/orderbook/src/weights.rs b/zrml/orderbook/src/weights.rs index d0a6a4c1a..7c6ecc1dd 100644 --- a/zrml/orderbook/src/weights.rs +++ b/zrml/orderbook/src/weights.rs @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_orderbook // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -74,6 +74,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// Proof: Orderbook Orders (max_values: None, max_size: Some(142), added: 2617, mode: MaxEncodedLen) /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:1 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) /// Proof: Balances Reserves (max_values: None, max_size: Some(1249), added: 3724, mode: MaxEncodedLen) /// Storage: System Account (r:1 w:1) @@ -91,6 +93,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:1 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// Storage: Orderbook NextOrderId (r:1 w:1) /// Proof: Orderbook NextOrderId (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// Storage: Balances Reserves (r:1 w:1) diff --git a/zrml/parimutuel/Cargo.toml b/zrml/parimutuel/Cargo.toml index 2aaed4d75..92cd83486 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..b45816e8c 100644 --- a/zrml/parimutuel/src/benchmarking.rs +++ b/zrml/parimutuel/src/benchmarking.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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 e4cfcfaac..e0775e43a 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, ExistentialDeposits, GetNativeCurrencyId, + MaxReserves, MinBetSize, MinimumPeriod, ParimutuelPalletId, BASE, }, traits::DistributeFees, types::{ - AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, - 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,7 +274,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Currencies; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -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..454cec9af 100644 --- a/zrml/parimutuel/src/tests/mod.rs +++ b/zrml/parimutuel/src/tests/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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..c153885b6 100644 --- a/zrml/parimutuel/src/utils.rs +++ b/zrml/parimutuel/src/utils.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Forecasting Technologies LTD. +// Copyright 2023-2024 Forecasting Technologies LTD. // // This file is part of Zeitgeist. // @@ -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 d22df535a..a6e9ca6c0 100644 --- a/zrml/parimutuel/src/weights.rs +++ b/zrml/parimutuel/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_parimutuel // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -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) @@ -61,6 +63,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: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) @@ -76,6 +80,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:1 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:1 w:1) @@ -93,6 +99,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: MarketAssets Asset (r:2 w:0) + /// Proof: MarketAssets Asset (max_values: None, max_size: Some(225), added: 2700, mode: MaxEncodedLen) /// 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:1 w:1) @@ -108,4 +116,37 @@ impl WeightInfoZeitgeist for WeightInfo { .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(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 d981a5e99..707b7c1c1 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 } @@ -29,6 +30,7 @@ pallet-treasury = { workspace = true, optional = true } sp-api = { workspace = true, optional = true } sp-io = { 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 } [dev-dependencies] @@ -39,9 +41,12 @@ zrml-prediction-markets = { workspace = true, features = ["mock", "default"] } [features] default = ["std"] mock = [ + "env_logger/default", + "orml-asset-registry/default", "orml-currencies/default", "orml-tokens/default", - "pallet-balances", + "pallet-assets/default", + "pallet-balances/default", "pallet-randomness-collective-flip/default", "pallet-timestamp/default", "pallet-treasury/default", @@ -49,10 +54,9 @@ mock = [ "sp-api/default", "sp-io/default", "zeitgeist-primitives/mock", + "zrml-asset-router/default", "zrml-prediction-markets-runtime-api/default", "xcm/default", - "orml-asset-registry/default", - "env_logger/default", ] parachain = [] runtime-benchmarks = [ @@ -60,6 +64,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", ] @@ -69,6 +74,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/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/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 fd5766af5..e4d2c405c 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,16 +44,22 @@ 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, zeitgeist_primitives::types::CustomMetadata}; + use { + orml_traits::asset_registry::Inspect as RegistryInspect, + zeitgeist_primitives::types::{CustomMetadata, XcmAsset}, + }; use orml_traits::{MultiCurrency, NamedMultiReservableCurrency}; use sp_arithmetic::per_things::{Perbill, Percent}; @@ -65,13 +71,13 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, ZeitgeistAssetManager, + 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}; @@ -86,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; @@ -100,7 +105,7 @@ mod pallet { BalanceOf, ::BlockNumber, MomentOf, - AssetOf, + BaseAsset, >; pub(crate) type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; @@ -440,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; @@ -520,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()) } @@ -578,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()) } @@ -593,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>, @@ -645,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>, @@ -731,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, ); @@ -785,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, ); @@ -811,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, @@ -933,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()) } @@ -1031,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 @@ -1058,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>, @@ -1515,17 +1545,24 @@ mod pallet { /// The origin that is allowed to approve / reject pending advised markets. type ApproveOrigin: EnsureOrigin; - /// Shares of outcome assets and native currency - type AssetManager: ZeitgeistAssetManager< + /// 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, Balance = BalanceOf, - CurrencyId = Asset>, + CurrencyId = AssetOf, ReserveIdentifier = [u8; 8], >; #[cfg(feature = "parachain")] - type AssetRegistry: Inspect< - AssetId = Asset>, + type AssetRegistry: RegistryInspect< + AssetId = XcmAsset, Balance = BalanceOf, CustomMetadata = CustomMetadata, >; @@ -1689,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] @@ -1907,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(); @@ -2080,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>, @@ -2130,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)?; @@ -2270,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: {:?}.", @@ -2298,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)); @@ -2314,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)); @@ -2510,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) } @@ -2757,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. @@ -2873,7 +2940,7 @@ mod pallet { } fn construct_market( - base_asset: AssetOf, + base_asset: BaseAsset, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, @@ -2889,16 +2956,21 @@ 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) => { - if let Some(metadata) = T::AssetRegistry::metadata(&Asset::ForeignAsset(fa)) { + BaseAsset::ForeignAsset(id) => { + if let Some(metadata) = T::AssetRegistry::metadata(&XcmAsset::ForeignAsset(id)) + { metadata.additional.allow_as_base_asset } else { return Err(Error::::UnregisteredForeignAsset.into()); } } - _ => false, + #[cfg(not(feature = "parachain"))] + BaseAsset::ForeignAsset(_) => false, }; ensure!(creator_fee <= T::MaxCreatorFee::get(), Error::::FeeTooHigh); @@ -3002,6 +3074,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); @@ -3030,6 +3103,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 813687480..3f0ef4230 100644 --- a/zrml/prediction-markets/src/mock.rs +++ b/zrml/prediction-markets/src/mock.rs @@ -24,43 +24,61 @@ use crate as prediction_markets; use frame_support::{ - construct_runtime, ord_parameter_types, parameter_types, - traits::{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, 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 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, MaxCategories, MaxCourtParticipants, MaxCreatorFee, - MaxDelegations, MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, - MaxGracePeriod, MaxMarketLifetime, MaxOracleDuration, MaxOwners, MaxRejectReasonLen, - MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinCategories, MinDisputeDuration, - MinJurorStake, MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, OutcomeBond, - OutcomeFactor, OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, - SimpleDisputesPalletId, TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, - MILLISECS_PER_BLOCK, + DestroyAccountWeight, DestroyApprovalWeight, DestroyFinishWeight, ExistentialDeposit, + ExistentialDeposits, GdVotingPeriod, GetNativeCurrencyId, GlobalDisputeLockId, + GlobalDisputesPalletId, InflationPeriod, LiquidityMiningPalletId, LockId, MaxAppeals, + MaxApprovals, MaxCategories, MaxCourtParticipants, MaxCreatorFee, MaxDelegations, + MaxDisputeDuration, MaxDisputes, MaxEditReasonLen, MaxGlobalDisputeVotes, MaxGracePeriod, + MaxMarketLifetime, MaxOracleDuration, MaxOwners, MaxRejectReasonLen, MaxReserves, + MaxSelectedDraws, MaxYearlyInflation, MinCategories, MinDisputeDuration, MinJurorStake, + MinOracleDuration, MinOutcomeVoteAmount, MinimumPeriod, OutcomeBond, OutcomeFactor, + OutsiderBond, PmPalletId, RemoveKeysLimit, RequestInterval, SimpleDisputesPalletId, + TreasuryPalletId, VotePeriod, VotingOutcomeFee, BASE, CENT, MILLISECS_PER_BLOCK, }, - traits::DeployPoolApi, + traits::{DeployPoolApi, MarketTransitionApi}, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, MarketId, Moment, SerdeWrapper, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, + CampaignAsset, CampaignAssetClass, CampaignAssetId, Currencies, CustomAsset, CustomAssetId, + Hash, Index, MarketAsset, MarketId, Moment, ResultWithWeightInfo, UncheckedExtrinsicTest, + XcmAsset, }, }; +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; @@ -84,6 +102,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> = const { RefCell::new(vec![]) }; pub static DEPLOY_POOL_RETURN_VALUE: RefCell = const { RefCell::new(Ok(())) }; @@ -152,6 +200,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 @@ -159,11 +211,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}, @@ -180,6 +236,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; @@ -209,6 +268,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; @@ -217,7 +277,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; @@ -253,7 +312,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 = (); } @@ -261,7 +320,7 @@ impl orml_currencies::Config for Runtime { impl orml_tokens::Config for Runtime { type Amount = Amount; type Balance = Balance; - type CurrencyId = CurrencyId; + type CurrencyId = Currencies; type DustRemovalWhitelist = Everything; type RuntimeEvent = RuntimeEvent; type ExistentialDeposits = ExistentialDeposits; @@ -275,7 +334,7 @@ impl orml_tokens::Config for Runtime { #[cfg(feature = "parachain")] crate::orml_asset_registry::impl_mock_registry! { MockRegistry, - CurrencyId, + XcmAsset, Balance, zeitgeist_primitives::types::CustomMetadata } @@ -305,6 +364,122 @@ 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 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 zrml_authorized::Config for Runtime { type AuthorizedDisputeResolutionOrigin = EnsureSignedBy; @@ -435,29 +610,42 @@ 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) - .map(|idx| (idx, CurrencyId::ForeignAsset(100), INITIAL_BALANCE)) + .map(|idx| (idx, Currencies::ForeignAsset(100), INITIAL_BALANCE)) .collect(), } .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![ ( - CurrencyId::ForeignAsset(100), + XcmAsset::ForeignAsset(100), AssetMetadata { decimals: 18, name: "ACALA USD".as_bytes().to_vec(), @@ -468,7 +656,7 @@ impl ExtBuilder { }, ), ( - CurrencyId::ForeignAsset(420), + XcmAsset::ForeignAsset(420), AssetMetadata { decimals: 18, name: "FANCY_TOKEN".as_bytes().to_vec(), @@ -500,6 +688,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); } } @@ -516,8 +705,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(SerdeWrapper(1)) + fn market_outcome_share_id(_: MarketId, _: u16) -> Assets { + Assets::PoolShare(1) } } } 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 c9b2f3311..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 = Tokens::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 7640e4acb..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 = Tokens::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; @@ -245,7 +248,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), @@ -253,19 +256,19 @@ 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).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 c9ec16c84..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, @@ -62,22 +62,21 @@ 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); - assert_eq!(share_a_total, CENT); - let share_a_bal = Tokens::free_balance(share_a, &CHARLIE); - assert_eq!(share_a_bal, CENT); + let share_a_total = AssetManager::total_issuance(share_a); + assert_eq!(share_a_total, 0); + let share_a_bal = AssetManager::free_balance(share_a, &CHARLIE); + assert_eq!(share_a_bal, 0); 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); @@ -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 a76618a4d..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, @@ -138,7 +150,7 @@ fn scalar_market_correctly_resolves_common(base_asset: AssetOf, reporte ScoringRule::Lmsr, ); 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), @@ -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), diff --git a/zrml/prediction-markets/src/weights.rs b/zrml/prediction-markets/src/weights.rs index e4c3e7a97..0281b617e 100644 --- a/zrml/prediction-markets/src/weights.rs +++ b/zrml/prediction-markets/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_prediction_markets // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -218,6 +218,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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) @@ -250,7 +252,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[0, 63]`. 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: 55_550 nanoseconds. Weight::from_parts(74_259_340, 8263) @@ -270,7 +272,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `m` is `[0, 63]`. fn edit_market(_m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `735 + m * (16 ±0)` + // Measured: `700 + m * (16 ±0)` // Estimated: `10706` // Minimum execution time: 53_490 nanoseconds. Weight::from_parts(75_146_182, 10706) @@ -423,6 +425,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: 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) @@ -440,6 +444,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: MarketCommons Markets (r:1 w:0) /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) + /// Storage: 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) @@ -511,6 +517,8 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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) @@ -590,7 +598,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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)` + // Measured: `675 + o * (16 ±0)` // Estimated: `10706` // Minimum execution time: 51_230 nanoseconds. Weight::from_parts(61_363_456, 10706) @@ -613,7 +621,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[0, 63]`. fn schedule_early_close_after_dispute(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `970 + o * (16 ±0)` + // Measured: `901 + o * (16 ±0)` // Estimated: `14430` // Minimum execution time: 96_271 nanoseconds. Weight::from_parts(119_475_237, 14430) @@ -636,7 +644,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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)` + // Measured: `736 + o * (16 ±0)` // Estimated: `14430` // Minimum execution time: 74_650 nanoseconds. Weight::from_parts(92_510_070, 14430) @@ -659,7 +667,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `n` is `[0, 63]`. fn dispute_early_close(o: u32, n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `924 + o * (16 ±0) + n * (16 ±0)` + // Measured: `852 + o * (16 ±0) + n * (16 ±0)` // Estimated: `14430` // Minimum execution time: 71_610 nanoseconds. Weight::from_parts(86_696_361, 14430) @@ -680,7 +688,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// 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: `814 + o * (16 ±0) + n * (16 ±0)` + // Measured: `742 + o * (16 ±0) + n * (16 ±0)` // Estimated: `10706` // Minimum execution time: 55_950 nanoseconds. Weight::from_parts(70_714_615, 10706) @@ -758,10 +766,12 @@ impl WeightInfoZeitgeist for WeightInfo { .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 7714).saturating_mul(n.into())) } - /// Storage: MarketCommons Markets (r:1 w:1) - /// Proof: MarketCommons Markets (max_values: None, max_size: Some(678), added: 3153, mode: MaxEncodedLen) /// Storage: Timestamp Now (r:1 w:0) /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: 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) /// The range of component `o` is `[1, 63]`. 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 468589c52..1fc79d844 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -30,13 +30,13 @@ use sp_runtime::{ }; use zeitgeist_primitives::{ constants::mock::{ - BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxDisputes, MaxReserves, + BlockHashCount, ExistentialDepositsAssets, GetNativeCurrencyId, MaxDisputes, MaxReserves, MinimumPeriod, OutcomeBond, OutcomeFactor, SimpleDisputesPalletId, BASE, }, traits::DisputeResolutionApi, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, + 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()) @@ -171,10 +171,10 @@ 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; + type ExistentialDeposits = ExistentialDepositsAssets; type MaxLocks = (); type MaxReserves = MaxReserves; type CurrencyHooks = (); 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/styx/src/weights.rs b/zrml/styx/src/weights.rs index 63715de60..4594eb18b 100644 --- a/zrml/styx/src/weights.rs +++ b/zrml/styx/src/weights.rs @@ -21,11 +21,11 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev //! DATE: `2024-01-15`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `zeitgeist-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: `Some(Wasm)`, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! 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 @@ -33,7 +33,7 @@ // --repeat=20 // --pallet=zrml_styx // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs diff --git a/zrml/swaps/Cargo.toml b/zrml/swaps/Cargo.toml index 97435a048..3cc8ac8be 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"] } @@ -14,7 +15,6 @@ zeitgeist-primitives = { workspace = true } env_logger = { workspace = true, optional = true } 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", @@ -52,6 +51,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/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 eaf3f17fe..c3e1084f9 100644 --- a/zrml/swaps/fuzz/utils.rs +++ b/zrml/swaps/fuzz/utils.rs @@ -29,7 +29,7 @@ use zeitgeist_primitives::{ MaxAssets, MaxSwapFee, MaxTotalWeight, MaxWeight, MinAssets, MinWeight, BASE, CENT, }, traits::Swaps as SwapsTrait, - types::{Asset, PoolId, ScalarPosition, SerdeWrapper}, + types::{Asset, PoolId, ScalarPosition}, }; use zrml_swaps::mock::Swaps; @@ -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 4cad2388f..5591fb2f9 100644 --- a/zrml/swaps/rpc/src/lib.rs +++ b/zrml/swaps/rpc/src/lib.rs @@ -27,14 +27,14 @@ 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::{ generic::BlockId, traits::{Block as BlockT, MaybeDisplay, MaybeFromStr, NumberFor}, }; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; pub use zrml_swaps_runtime_api::SwapsApi as SwapsRuntimeApi; @@ -42,7 +42,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, { @@ -51,7 +51,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) @@ -65,7 +65,7 @@ where asset_out: Asset, with_fees: bool, at: Option, - ) -> RpcResult>; + ) -> RpcResult; #[method(name = "swaps_getSpotPrices")] async fn get_spot_prices( @@ -75,7 +75,7 @@ where asset_out: Asset, with_fees: bool, blocks: Vec, - ) -> RpcResult>>; + ) -> RpcResult>; } /// A struct that implements the [`SwapsApi`]. @@ -116,14 +116,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 @@ -167,7 +175,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 = @@ -188,7 +196,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 b76ca8038..2045560a2 100644 --- a/zrml/swaps/runtime-api/src/lib.rs +++ b/zrml/swaps/runtime-api/src/lib.rs @@ -19,26 +19,24 @@ #![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 sp_runtime::traits::{MaybeDisplay, MaybeFromStr}; -use zeitgeist_primitives::types::{Asset, SerdeWrapper}; +use zeitgeist_primitives::types::Asset; 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, { - 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; } } diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index 33cee6776..17e2e0132 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -80,8 +80,8 @@ mod pallet { checked_ops_res::{CheckedAddRes, CheckedMulRes}, fixed::FixedMul, }, - traits::{PoolSharesId, Swaps, ZeitgeistAssetManager}, - types::{PoolId, SerdeWrapper}, + traits::{PoolSharesId, Swaps}, + types::PoolId, }; /// The current storage version. @@ -359,7 +359,7 @@ mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type AssetManager: ZeitgeistAssetManager; + type AssetManager: MultiCurrency; type Asset: Parameter + Member @@ -368,7 +368,7 @@ mod pallet { + MaybeSerializeDeserialize + Ord + TypeInfo - + PoolSharesId>; + + PoolSharesId; type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -1092,7 +1092,7 @@ mod pallet { } pub(crate) fn pool_shares_id(pool_id: PoolId) -> AssetOf { - T::Asset::pool_shares_id(SerdeWrapper(pool_id)) + T::Asset::pool_shares_id(pool_id) } pub fn pool_by_id(pool_id: PoolId) -> Result, DispatchError> diff --git a/zrml/swaps/src/mock.rs b/zrml/swaps/src/mock.rs index 9d6a592be..de0c3fa27 100644 --- a/zrml/swaps/src/mock.rs +++ b/zrml/swaps/src/mock.rs @@ -45,8 +45,8 @@ use zeitgeist_primitives::{ BASE, }, 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, }, }; @@ -144,7 +144,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(), @@ -181,7 +181,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; @@ -254,16 +254,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) } } } diff --git a/zrml/swaps/src/weights.rs b/zrml/swaps/src/weights.rs index 37e4ccd80..24ff4837f 100644 --- a/zrml/swaps/src/weights.rs +++ b/zrml/swaps/src/weights.rs @@ -67,6 +67,8 @@ pub struct WeightInfo(PhantomData); impl WeightInfoZeitgeist for WeightInfo { /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -90,6 +92,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -107,6 +111,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -124,6 +130,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -145,6 +153,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -160,6 +170,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -175,6 +187,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -190,6 +204,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:0) /// 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) @@ -208,7 +224,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn open_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `168 + a * (54 ±0)` + // Measured: `167 + a * (54 ±0)` // Estimated: `6054` // Minimum execution time: 18_030 nanoseconds. Weight::from_parts(19_889_995, 6054) @@ -222,7 +238,7 @@ impl WeightInfoZeitgeist for WeightInfo { /// The range of component `a` is `[2, 65]`. fn close_pool(a: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `168 + a * (54 ±0)` + // Measured: `167 + a * (54 ±0)` // Estimated: `6054` // Minimum execution time: 17_140 nanoseconds. Weight::from_parts(19_467_788, 6054) @@ -233,6 +249,8 @@ impl WeightInfoZeitgeist for WeightInfo { } /// Storage: Swaps Pools (r:1 w:1) /// 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)