diff --git a/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index e69789cb9..e12a38733 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -14,10 +14,15 @@ APIs/RPC interface. ## v0.5.2 +[#1310]: https://github.com/zeitgeistpm/zeitgeist/pull/1310 [#1307]: https://github.com/zeitgeistpm/zeitgeist/pull/1307 ### Added +- ⚠️ [#1310] Add `market_id` field to `Market` struct. +- [#1310] Add `MarketBuilderTrait`, which is used to define + `MarketCommonsPalletApi::build_market`, which should be used for creating + markets in the future. - [#1307] New hybrid router for managing the trade execution using the `neo-swaps` automated market maker and order book @@ -33,6 +38,10 @@ APIs/RPC interface. For details, please refer to the `README.md` and the in-file documentation. +### Deprectaed + +- [#1310] `MarketCommonsPalletApi::push_market` is now deprecated. + ## v0.5.1 [#1295]: https://github.com/zeitgeistpm/zeitgeist/pull/1295 diff --git a/primitives/src/market.rs b/primitives/src/market.rs index 8280aa570..46b1deef8 100644 --- a/primitives/src/market.rs +++ b/primitives/src/market.rs @@ -31,8 +31,10 @@ use sp_runtime::RuntimeDebug; /// * `BN`: Block number /// * `M`: Moment (time moment) /// * `A`: Asset +/// * `MI`: Market ID #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct Market { +pub struct Market { + pub market_id: MI, /// Base asset of the market. pub base_asset: A, /// Creator of this market. @@ -68,7 +70,10 @@ pub struct Market { pub early_close: Option>, } -impl Market { +impl Market +where + MI: Copy + HasCompact + MaxEncodedLen, +{ /// 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 @@ -111,21 +116,17 @@ impl Market { } /// Returns a `Vec` of all outcomes for `market_id`. - pub fn outcome_assets( - &self, - market_id: MI, - ) -> Vec> { + pub fn outcome_assets(&self) -> Vec> { match self.market_type { MarketType::Categorical(categories) => { let mut assets = Vec::new(); for i in 0..categories { match self.scoring_rule { - ScoringRule::AmmCdaHybrid => { - assets.push(MarketAssetClass::::CategoricalOutcome(market_id, i)) - } + ScoringRule::AmmCdaHybrid => assets + .push(MarketAssetClass::::CategoricalOutcome(self.market_id, i)), ScoringRule::Parimutuel => { - assets.push(MarketAssetClass::::ParimutuelShare(market_id, i)) + assets.push(MarketAssetClass::::ParimutuelShare(self.market_id, i)) } }; } @@ -134,8 +135,8 @@ impl Market { } MarketType::Scalar(_) => { vec![ - MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Long), - MarketAssetClass::::ScalarOutcome(market_id, ScalarPosition::Short), + MarketAssetClass::::ScalarOutcome(self.market_id, ScalarPosition::Long), + MarketAssetClass::::ScalarOutcome(self.market_id, ScalarPosition::Short), ] } } @@ -145,45 +146,38 @@ impl Market { /// 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> { + pub fn report_into_asset(&self) -> Option> { let outcome = if let Some(ref report) = self.report { &report.outcome } else { return None; }; - self.outcome_report_into_asset(market_id, outcome) + self.outcome_report_into_asset(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> { + pub fn resolved_outcome_into_asset(&self) -> Option> { let outcome = self.resolved_outcome.as_ref()?; - self.outcome_report_into_asset(market_id, outcome) + self.outcome_report_into_asset(outcome) } /// Tries to convert a `outcome_report` for `market_id` into an asset, /// returns `None` if not possible. - fn outcome_report_into_asset( + fn outcome_report_into_asset( &self, - market_id: MI, outcome_report: &OutcomeReport, ) -> Option> { match outcome_report { OutcomeReport::Categorical(idx) => match self.scoring_rule { ScoringRule::AmmCdaHybrid => { - Some(MarketAssetClass::::CategoricalOutcome(market_id, *idx)) + Some(MarketAssetClass::::CategoricalOutcome(self.market_id, *idx)) } ScoringRule::Parimutuel => { - Some(MarketAssetClass::::ParimutuelShare(market_id, *idx)) + Some(MarketAssetClass::::ParimutuelShare(self.market_id, *idx)) } }, OutcomeReport::Scalar(_) => None, @@ -249,16 +243,18 @@ impl Default for MarketBonds { } } -impl MaxEncodedLen for Market +impl MaxEncodedLen for Market where AI: MaxEncodedLen, BA: MaxEncodedLen, BN: MaxEncodedLen, M: MaxEncodedLen, A: MaxEncodedLen, + MI: MaxEncodedLen, { fn max_encoded_len() -> usize { AI::max_encoded_len() + .saturating_add(MI::max_encoded_len()) .saturating_add(A::max_encoded_len()) .saturating_add(MarketCreation::max_encoded_len()) .saturating_add(Perbill::max_encoded_len()) @@ -433,7 +429,8 @@ mod tests { types::{Asset, MarketAsset}, }; use test_case::test_case; - type Market = crate::market::Market>; + type MarketId = u128; + type Market = crate::market::Market, MarketId>; #[test_case( MarketType::Categorical(6), @@ -489,13 +486,14 @@ mod tests { expected: bool, ) { let market = Market { + market_id: 9, base_asset: Asset::Ztg, creator: 1, creation: MarketCreation::Permissionless, creator_fee: Default::default(), oracle: 3, metadata: vec![4u8; 5], - market_type, // : MarketType::Categorical(6), + market_type, period: MarketPeriod::Block(7..8), deadlines: Deadlines { grace_period: 1_u32, @@ -528,7 +526,10 @@ mod tests { #[test_case( MarketType::Scalar(12..=34), ScoringRule::AmmCdaHybrid, - vec![MarketAsset::ScalarOutcome(0, ScalarPosition::Long), MarketAsset::ScalarOutcome(0, ScalarPosition::Short)]; + vec![ + MarketAsset::ScalarOutcome(0, ScalarPosition::Long), + MarketAsset::ScalarOutcome(0, ScalarPosition::Short), + ]; "scalar_market" )] fn provides_correct_list_of_assets( @@ -537,6 +538,7 @@ mod tests { expected: Vec, ) { let market = Market { + market_id: 0, base_asset: Asset::Ztg, creator: 1, creation: MarketCreation::Permissionless, @@ -558,7 +560,7 @@ mod tests { bonds: MarketBonds::default(), early_close: None, }; - assert_eq!(market.outcome_assets(0), expected); + assert_eq!(market.outcome_assets(), expected); } #[test_case( @@ -595,6 +597,7 @@ mod tests { }); let market = Market { + market_id: 0, base_asset: Asset::Ztg, creator: 1, creation: MarketCreation::Permissionless, @@ -616,8 +619,8 @@ mod tests { bonds: MarketBonds::default(), early_close: None, }; - assert_eq!(market.resolved_outcome_into_asset(0), expected); - assert_eq!(market.report_into_asset(0), expected); + assert_eq!(market.resolved_outcome_into_asset(), expected); + assert_eq!(market.report_into_asset(), expected); } #[test] diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index f3650674d..7d7bde094 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -22,6 +22,7 @@ mod dispute_api; mod distribute_fees; mod hybrid_router_amm_api; mod hybrid_router_orderbook_api; +mod market_builder; mod market_commons_pallet_api; mod market_id; mod market_transition_api; @@ -29,15 +30,16 @@ mod swaps; mod weights; mod zeitgeist_asset; -pub use complete_set_operations_api::CompleteSetOperationsApi; -pub use deploy_pool_api::DeployPoolApi; -pub use dispute_api::{DisputeApi, DisputeMaxWeightApi, DisputeResolutionApi}; -pub use distribute_fees::DistributeFees; -pub use hybrid_router_amm_api::HybridRouterAmmApi; -pub use hybrid_router_orderbook_api::HybridRouterOrderbookApi; -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 complete_set_operations_api::*; +pub use deploy_pool_api::*; +pub use dispute_api::*; +pub use distribute_fees::*; +pub use hybrid_router_amm_api::*; +pub use hybrid_router_orderbook_api::*; +pub use market_builder::*; +pub use market_commons_pallet_api::*; +pub use market_id::*; +pub use market_transition_api::*; +pub use swaps::*; +pub use weights::*; pub use zeitgeist_asset::*; diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index 6ba9db4fc..8ef6b952b 100644 --- a/primitives/src/traits/dispute_api.rs +++ b/primitives/src/traits/dispute_api.rs @@ -29,12 +29,13 @@ use sp_runtime::DispatchError; // Abstraction of the market type, which is not a part of `DisputeApi` because Rust doesn't support // type aliases in traits. -type MarketOfDisputeApi = Market< +pub type MarketOfDisputeApi = Market< ::AccountId, ::Balance, ::BlockNumber, ::Moment, BaseAsset, + ::MarketId, >; type GlobalDisputeItemOfDisputeApi = @@ -145,12 +146,13 @@ pub trait DisputeMaxWeightApi { fn clear_max_weight() -> Weight; } -type MarketOfDisputeResolutionApi = Market< +pub type MarketOfDisputeResolutionApi = Market< ::AccountId, ::Balance, ::BlockNumber, ::Moment, BaseAsset, + ::MarketId, >; pub trait DisputeResolutionApi { diff --git a/primitives/src/traits/hybrid_router_amm_api.rs b/primitives/src/traits/hybrid_router_amm_api.rs index 304185c3d..50e6a7b05 100644 --- a/primitives/src/traits/hybrid_router_amm_api.rs +++ b/primitives/src/traits/hybrid_router_amm_api.rs @@ -19,10 +19,10 @@ use crate::hybrid_router_api_types::{AmmSoftFail, AmmTrade, ApiError}; use frame_support::dispatch::DispatchError; /// A type alias for the return struct of AMM buy and sell. -pub type AmmTradeOf = AmmTrade<::Balance>; +type AmmTradeOf = AmmTrade<::Balance>; /// A type alias for the error type of the AMM part of the hybrid router. -pub type ApiErrorOf = ApiError; +type ApiErrorOf = ApiError; /// Trait for handling the AMM part of the hybrid router. pub trait HybridRouterAmmApi { diff --git a/primitives/src/traits/hybrid_router_orderbook_api.rs b/primitives/src/traits/hybrid_router_orderbook_api.rs index cd68710d8..9af727631 100644 --- a/primitives/src/traits/hybrid_router_orderbook_api.rs +++ b/primitives/src/traits/hybrid_router_orderbook_api.rs @@ -20,13 +20,13 @@ use frame_support::dispatch::DispatchError; use crate::hybrid_router_api_types::{ApiError, OrderbookSoftFail, OrderbookTrade}; /// A type alias for the return struct of orderbook trades. -pub type OrderbookTradeOf = OrderbookTrade< +type OrderbookTradeOf = OrderbookTrade< ::AccountId, ::Balance, >; /// A type alias for the error type of the orderbook part of the hybrid router. -pub type ApiErrorOf = ApiError; +type ApiErrorOf = ApiError; /// Trait for handling the order book part of the hybrid router. pub trait HybridRouterOrderbookApi { diff --git a/primitives/src/traits/market_builder.rs b/primitives/src/traits/market_builder.rs new file mode 100644 index 000000000..7f682c8d7 --- /dev/null +++ b/primitives/src/traits/market_builder.rs @@ -0,0 +1,61 @@ +// 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::{ + Deadlines, EarlyClose, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, OutcomeReport, Report, ScoringRule, +}; +use alloc::vec::Vec; +use sp_runtime::{DispatchError, Perbill}; + +macro_rules! builder_methods { + ($($field:ident: $type:ty),* $(,)?) => { + $(fn $field(&mut self, $field: $type) -> &mut Self;)* + } +} + +/// Mutably referenced builder struct for the `Market` object. The `build` call is pass-by-value, so +/// the usual calling pattern is: +/// +/// ```ignore +/// let builder = MarketBuilderImpl::new(); +/// builder.field1(value1).field2(value2); +/// builder.clone().build() +/// ``` +pub trait MarketBuilderTrait { + fn build(self) -> Result, DispatchError>; + + builder_methods! { + market_id: MI, + base_asset: A, + creator: AI, + creation: MarketCreation, + creator_fee: Perbill, + oracle: AI, + metadata: Vec, + market_type: MarketType, + period: MarketPeriod, + deadlines: Deadlines, + scoring_rule: ScoringRule, + status: MarketStatus, + report: Option>, + resolved_outcome: Option, + dispute_mechanism: Option, + bonds: MarketBonds, + early_close: Option>, + } +} diff --git a/primitives/src/traits/market_commons_pallet_api.rs b/primitives/src/traits/market_commons_pallet_api.rs index 12c1ad7ef..3b5293d38 100644 --- a/primitives/src/traits/market_commons_pallet_api.rs +++ b/primitives/src/traits/market_commons_pallet_api.rs @@ -18,7 +18,10 @@ #![allow(clippy::type_complexity)] -use crate::types::{BaseAsset, Market, PoolId}; +use crate::{ + traits::MarketBuilderTrait, + types::{BaseAsset, Market, PoolId}, +}; use frame_support::{ dispatch::{fmt::Debug, DispatchError, DispatchResult}, pallet_prelude::{MaybeSerializeDeserialize, Member}, @@ -36,6 +39,7 @@ pub type MarketOf = Market< ::BlockNumber, ::Moment, BaseAsset, + ::MarketId, >; /// Abstraction over storage operations for markets @@ -79,9 +83,32 @@ pub trait MarketCommonsPalletApi { where F: FnOnce(&mut MarketOf) -> DispatchResult; - /// Pushes a new market into the storage, returning its related auto-incremented ID. + /// Add a `market` to the API's list of markets, overwrite its `market_id` field with a new ID + /// and return the market's new ID. + /// + /// Deprecated since v0.5.1. For testing purposes only; use `build_market` in production. fn push_market(market: MarketOf) -> Result; + /// Equips a market with a market ID, writes the market to storage and then returns the ID and + /// the built market. + /// + /// This function is the only public means by which new IDs are issued. The market's `market_id` + /// field is expected to be `None`. If that's not the case, this function will raise an error to + /// avoid double-writes, which are always the result of an incorrect issuance process for market + /// IDs. + fn build_market( + market_builder: U, + ) -> Result<(Self::MarketId, MarketOf), DispatchError> + where + U: MarketBuilderTrait< + Self::AccountId, + Self::Balance, + Self::BlockNumber, + Self::Moment, + BaseAsset, + Self::MarketId, + >; + /// Removes a market from the storage. fn remove_market(market_id: &Self::MarketId) -> DispatchResult; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 19193ef5b..eff5c92c0 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -55,7 +55,7 @@ macro_rules! decl_common_types { use orml_traits::MultiCurrency; use sp_runtime::{generic, DispatchError, DispatchResult, SaturatedConversion}; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; - use zrml_market_commons::migrations::MigrateScoringRuleAmmCdaHybrid; + use zrml_market_commons::migrations::MigrateScoringRuleAmmCdaHybridAndMarketId; use zrml_neo_swaps::migration::MigratePoolReservesToBoundedBTreeMap; pub type Block = generic::Block; @@ -63,8 +63,8 @@ macro_rules! decl_common_types { type Address = sp_runtime::MultiAddress; type Migrations = ( + MigrateScoringRuleAmmCdaHybridAndMarketId, MigratePoolReservesToBoundedBTreeMap, - MigrateScoringRuleAmmCdaHybrid, ); pub type Executive = frame_executive::Executive< diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index 730e04660..1e5644cb6 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -73,6 +73,7 @@ mod pallet { ::BlockNumber, MomentOf, BaseAsset, + MarketIdOf, >; #[pallet::call] @@ -371,11 +372,12 @@ where use frame_support::traits::Get; use sp_runtime::{traits::AccountIdConversion, Perbill}; use zeitgeist_primitives::types::{ - BaseAsset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, - MarketStatus, MarketType, ScoringRule, + BaseAsset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, ScoringRule, }; - zeitgeist_primitives::types::Market { + Market { + market_id: Default::default(), base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 9aabd5c4f..5669d8096 100644 --- a/zrml/authorized/src/mock.rs +++ b/zrml/authorized/src/mock.rs @@ -36,10 +36,10 @@ use zeitgeist_primitives::{ constants::mock::{ AuthorizedPalletId, BlockHashCount, CorrectionPeriod, MaxReserves, MinimumPeriod, BASE, }, - traits::DisputeResolutionApi, + traits::{DisputeResolutionApi, MarketOfDisputeResolutionApi}, types::{ - AccountIdTest, Balance, BaseAsset, BlockNumber, BlockTest, Hash, Index, Market, MarketId, - Moment, UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, + UncheckedExtrinsicTest, }, }; @@ -80,13 +80,7 @@ impl DisputeResolutionApi for MockResolution { fn resolve( _market_id: &Self::MarketId, - _market: &Market< - Self::AccountId, - Self::Balance, - Self::BlockNumber, - Self::Moment, - BaseAsset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 050f8e926..f463facfc 100644 --- a/zrml/court/src/benchmarks.rs +++ b/zrml/court/src/benchmarks.rs @@ -54,6 +54,7 @@ where T: Config, { Market { + market_id: 0u8.into(), base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/court/src/lib.rs b/zrml/court/src/lib.rs index 2db5d5bbb..d3c83f8c2 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -225,6 +225,7 @@ mod pallet { ::BlockNumber, MomentOf, BaseAsset, + MarketIdOf, >; pub(crate) type HashOf = ::Hash; pub(crate) type AccountIdLookupOf = diff --git a/zrml/court/src/mock.rs b/zrml/court/src/mock.rs index 02843c728..6af67d134 100644 --- a/zrml/court/src/mock.rs +++ b/zrml/court/src/mock.rs @@ -38,10 +38,10 @@ use zeitgeist_primitives::{ MaxReserves, MaxSelectedDraws, MaxYearlyInflation, MinJurorStake, MinimumPeriod, RequestInterval, VotePeriod, BASE, }, - traits::DisputeResolutionApi, + traits::{DisputeResolutionApi, MarketOfDisputeResolutionApi}, types::{ - AccountIdTest, Balance, BaseAsset, BlockNumber, BlockTest, Hash, Index, Market, MarketId, - Moment, UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, + UncheckedExtrinsicTest, }, }; @@ -96,13 +96,7 @@ impl DisputeResolutionApi for MockResolution { fn resolve( _market_id: &Self::MarketId, - _market: &Market< - Self::AccountId, - Self::Balance, - Self::BlockNumber, - Self::Moment, - BaseAsset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 175d33360..b0c80d6dc 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -66,6 +66,7 @@ type BlockNumberOf = ::BlockNumber; const ORACLE_REPORT: OutcomeReport = OutcomeReport::Scalar(u128::MAX); const DEFAULT_MARKET: MarketOf = Market { + market_id: 0, base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/global-disputes/src/mock.rs b/zrml/global-disputes/src/mock.rs index 89be0f994..25132468a 100644 --- a/zrml/global-disputes/src/mock.rs +++ b/zrml/global-disputes/src/mock.rs @@ -34,10 +34,10 @@ use zeitgeist_primitives::{ GlobalDisputesPalletId, MaxReserves, MinOutcomeVoteAmount, MinimumPeriod, RemoveKeysLimit, VotingOutcomeFee, BASE, }, - traits::DisputeResolutionApi, + traits::{DisputeResolutionApi, MarketOfDisputeResolutionApi}, types::{ - AccountIdTest, Balance, BaseAsset, BlockNumber, BlockTest, Hash, Index, Market, MarketId, - Moment, UncheckedExtrinsicTest, + AccountIdTest, Balance, BlockNumber, BlockTest, Hash, Index, MarketId, Moment, + UncheckedExtrinsicTest, }, }; @@ -75,13 +75,7 @@ impl DisputeResolutionApi for NoopResolution { fn resolve( _market_id: &Self::MarketId, - _market: &Market< - Self::AccountId, - Self::Balance, - Self::BlockNumber, - Self::Moment, - BaseAsset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/global-disputes/src/utils.rs b/zrml/global-disputes/src/utils.rs index 71fb19c0f..dabebc965 100644 --- a/zrml/global-disputes/src/utils.rs +++ b/zrml/global-disputes/src/utils.rs @@ -25,6 +25,7 @@ type MarketOf = zeitgeist_primitives::types::Market< ::BlockNumber, MomentOf, zeitgeist_primitives::types::BaseAsset, + MarketIdOf, >; pub(crate) fn market_mock() -> MarketOf @@ -36,6 +37,7 @@ where use zeitgeist_primitives::types::ScoringRule; zeitgeist_primitives::types::Market { + market_id: Default::default(), base_asset: zeitgeist_primitives::types::BaseAsset::Ztg, creation: zeitgeist_primitives::types::MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/hybrid-router/src/benchmarking.rs b/zrml/hybrid-router/src/benchmarking.rs index d3f3189bc..24e7f5c7d 100644 --- a/zrml/hybrid-router/src/benchmarking.rs +++ b/zrml/hybrid-router/src/benchmarking.rs @@ -77,6 +77,7 @@ where T: Config, { let market = Market { + market_id: 0u8.into(), base_asset, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), diff --git a/zrml/hybrid-router/src/lib.rs b/zrml/hybrid-router/src/lib.rs index 768bf26c5..1155ee832 100644 --- a/zrml/hybrid-router/src/lib.rs +++ b/zrml/hybrid-router/src/lib.rs @@ -591,7 +591,7 @@ mod pallet { Error::::MaxOrdersExceeded ); let market = T::MarketCommons::market(&market_id)?; - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); ensure!(asset_count as usize == assets.len(), Error::::AssetCountMismatch); let (asset_in, asset_out) = match tx_type { diff --git a/zrml/hybrid-router/src/utils.rs b/zrml/hybrid-router/src/utils.rs index 5aeef4ca1..0dcc0e1ed 100644 --- a/zrml/hybrid-router/src/utils.rs +++ b/zrml/hybrid-router/src/utils.rs @@ -17,7 +17,7 @@ #![cfg(all(feature = "mock", test))] -use crate::{AccountIdOf, BalanceOf}; +use crate::{AccountIdOf, BalanceOf, MarketIdOf}; use frame_system::pallet_prelude::BlockNumberFor; use zeitgeist_primitives::{ traits::MarketCommonsPalletApi, @@ -27,7 +27,7 @@ use zeitgeist_primitives::{ pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; pub(crate) type MarketOf = - Market, BalanceOf, BlockNumberFor, MomentOf, BaseAsset>; + Market, BalanceOf, BlockNumberFor, MomentOf, BaseAsset, MarketIdOf>; pub(crate) fn market_mock(creator: T::AccountId) -> MarketOf where @@ -43,6 +43,7 @@ where }; zeitgeist_primitives::types::Market { + market_id: 0u8.into(), base_asset: BaseAssetClass::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), diff --git a/zrml/liquidity-mining/src/tests.rs b/zrml/liquidity-mining/src/tests.rs index 795280861..68363ff8c 100644 --- a/zrml/liquidity-mining/src/tests.rs +++ b/zrml/liquidity-mining/src/tests.rs @@ -203,6 +203,7 @@ fn create_default_market(market_id: u128, period: Range) { Markets::::insert( market_id, Market { + market_id: Default::default(), base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/market-commons/src/lib.rs b/zrml/market-commons/src/lib.rs index 5bb088856..3b2e1af6c 100644 --- a/zrml/market-commons/src/lib.rs +++ b/zrml/market-commons/src/lib.rs @@ -24,6 +24,7 @@ extern crate alloc; pub mod migrations; mod mock; mod tests; +pub mod types; pub use pallet::*; pub use zeitgeist_primitives::traits::MarketCommonsPalletApi; @@ -50,19 +51,33 @@ mod pallet { }; use zeitgeist_primitives::{ math::checked_ops_res::CheckedAddRes, - types::{BaseAsset, Market, PoolId}, + traits::MarketBuilderTrait, + types::{ + BaseAsset, Deadlines, EarlyClose, Market, MarketBonds, MarketPeriod, PoolId, Report, + }, }; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(11); - 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; + pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type BalanceOf = ::Balance; + pub(crate) type BlockNumberOf = ::BlockNumber; + pub(crate) type MarketIdOf = ::MarketId; + pub(crate) type MarketOf = Market< + AccountIdOf, + BalanceOf, + BlockNumberOf, + MomentOf, + BaseAsset, + MarketIdOf, + >; + pub(crate) type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; + pub(crate) type DeadlinesOf = Deadlines>; + pub(crate) type EarlyCloseOf = EarlyClose, MomentOf>; + pub(crate) type MarketBondsOf = MarketBonds, BalanceOf>; + pub(crate) type MarketPeriodOf = MarketPeriod, MomentOf>; + pub(crate) type ReportOf = Report, BlockNumberOf>; #[pallet::call] impl Pallet {} @@ -105,6 +120,8 @@ mod pallet { NoReport, /// There's a pool registered for this market already. PoolAlreadyExists, + /// Unexpectedly failed to build a market due to missing data. + IncompleteMarketBuilder, } #[pallet::hooks] @@ -124,7 +141,7 @@ mod pallet { // on the storage so next following calls will return yet another incremented number. // // Returns `Err` if `MarketId` addition overflows. - pub fn next_market_id() -> Result { + fn next_market_id() -> Result { let id = MarketCounter::::get(); let new_counter = id.checked_add_res(&1u8.into())?; >::put(new_counter); @@ -175,12 +192,33 @@ mod pallet { }) } - fn push_market(market: MarketOf) -> Result { + fn push_market(mut market: MarketOf) -> Result { let market_id = Self::next_market_id()?; - >::insert(market_id, market); + market.market_id = market_id; + Markets::::insert(market_id, market.clone()); Ok(market_id) } + fn build_market( + mut market_builder: U, + ) -> Result<(Self::MarketId, MarketOf), DispatchError> + where + U: MarketBuilderTrait< + Self::AccountId, + Self::Balance, + Self::BlockNumber, + Self::Moment, + BaseAsset, + Self::MarketId, + >, + { + let market_id = Self::next_market_id()?; + market_builder.market_id(market_id); + let market = market_builder.build()?; + >::insert(market_id, market.clone()); + Ok((market_id, market)) + } + fn remove_market(market_id: &Self::MarketId) -> DispatchResult { if !>::contains_key(market_id) { return Err(Error::::MarketDoesNotExist.into()); diff --git a/zrml/market-commons/src/migrations.rs b/zrml/market-commons/src/migrations.rs index c44406dd7..2569b4a79 100644 --- a/zrml/market-commons/src/migrations.rs +++ b/zrml/market-commons/src/migrations.rs @@ -16,15 +16,13 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{ - AccountIdOf, BalanceOf, BlockNumberOf, Config, MarketIdOf, MomentOf, Pallet as MarketCommons, -}; +use crate::{AccountIdOf, BalanceOf, BlockNumberOf, Config, MomentOf, Pallet as MarketCommons}; use alloc::vec::Vec; use core::marker::PhantomData; use frame_support::{ + dispatch::Weight, log, - pallet_prelude::{Blake2_128Concat, StorageVersion, Weight}, - traits::{Get, OnRuntimeUpgrade}, + traits::{Get, OnRuntimeUpgrade, StorageVersion}, }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -36,10 +34,14 @@ use zeitgeist_primitives::types::{ #[cfg(feature = "try-runtime")] use { - alloc::collections::BTreeMap, frame_support::migration::storage_key_iter, - zeitgeist_primitives::types::MarketId, + crate::MarketIdOf, + alloc::{collections::BTreeMap, format}, + frame_support::migration::storage_key_iter, }; +#[cfg(any(feature = "try-runtime", feature = "test"))] +use frame_support::Blake2_128Concat; + #[cfg(any(feature = "try-runtime", test))] const MARKET_COMMONS: &[u8] = b"MarketCommons"; #[cfg(any(feature = "try-runtime", test))] @@ -78,14 +80,16 @@ pub enum OldScoringRule { const MARKET_COMMONS_REQUIRED_STORAGE_VERSION: u16 = 10; const MARKET_COMMONS_NEXT_STORAGE_VERSION: u16 = 11; +#[cfg(feature = "try-runtime")] #[frame_support::storage_alias] pub(crate) type Markets = StorageMap, Blake2_128Concat, MarketIdOf, OldMarketOf>; -pub struct MigrateScoringRuleAmmCdaHybrid(PhantomData); +pub struct MigrateScoringRuleAmmCdaHybridAndMarketId(PhantomData); -/// Migrates AMM and CDA markets to the new combined scoring rule. -impl OnRuntimeUpgrade for MigrateScoringRuleAmmCdaHybrid +/// Migrates AMM and CDA markets to the new combined scoring rule and adds the market's ID to the +/// struct. +impl OnRuntimeUpgrade for MigrateScoringRuleAmmCdaHybridAndMarketId where T: Config, { @@ -94,17 +98,17 @@ where let market_commons_version = StorageVersion::get::>(); if market_commons_version != MARKET_COMMONS_REQUIRED_STORAGE_VERSION { log::info!( - "MigrateScoringRuleAmmCdaHybrid: market-commons version is {:?}, but {:?} is \ - required", + "MigrateScoringRuleAmmCdaHybridAndMarketId: market-commons version is {:?}, but \ + {:?} is required", market_commons_version, MARKET_COMMONS_REQUIRED_STORAGE_VERSION, ); return total_weight; } - log::info!("MigrateScoringRuleAmmCdaHybrid: Starting..."); + log::info!("MigrateScoringRuleAmmCdaHybridAndMarketId: Starting..."); let mut translated = 0u64; - crate::Markets::::translate::, _>(|_, old_market| { + crate::Markets::::translate::, _>(|market_id, old_market| { translated.saturating_inc(); let scoring_rule = match old_market.scoring_rule { OldScoringRule::Lmsr => ScoringRule::AmmCdaHybrid, @@ -112,6 +116,7 @@ where OldScoringRule::Parimutuel => ScoringRule::Parimutuel, }; let new_market = Market { + market_id, base_asset: old_market.base_asset, creator: old_market.creator, creation: old_market.creation, @@ -131,13 +136,13 @@ where }; Some(new_market) }); - log::info!("MigrateScoringRuleAmmCdaHybrid: Upgraded {} markets.", translated); + log::info!("MigrateScoringRuleAmmCdaHybridAndMarketId: Upgraded {} markets.", translated); total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(translated, translated)); StorageVersion::new(MARKET_COMMONS_NEXT_STORAGE_VERSION).put::>(); total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); - log::info!("MigrateScoringRuleAmmCdaHybrid: Done!"); + log::info!("MigrateScoringRuleAmmCdaHybridAndMarketId: Done!"); total_weight } @@ -165,13 +170,34 @@ where #[cfg(feature = "try-runtime")] fn post_upgrade(previous_state: Vec) -> Result<(), &'static str> { - let old_markets: BTreeMap> = + let old_markets: BTreeMap, OldMarketOf> = Decode::decode(&mut &previous_state[..]).unwrap(); let old_market_count = old_markets.len(); - let new_market_count = Markets::::iter().count(); + let new_market_count = crate::Markets::::iter().count(); assert_eq!(old_market_count, new_market_count); + for (market_id, new_market) in crate::Markets::::iter() { + let old_market = old_markets + .get(&market_id) + .expect(&format!("Market {:?} not found", market_id)[..]); + assert_eq!(market_id, new_market.market_id); + assert_eq!(old_market.base_asset, new_market.base_asset); + assert_eq!(old_market.creator, new_market.creator); + assert_eq!(old_market.creation, new_market.creation); + assert_eq!(old_market.creator_fee, new_market.creator_fee); + assert_eq!(old_market.oracle, new_market.oracle); + assert_eq!(old_market.metadata, new_market.metadata); + assert_eq!(old_market.market_type, new_market.market_type); + assert_eq!(old_market.period, new_market.period); + assert_eq!(old_market.deadlines, new_market.deadlines); + assert_eq!(old_market.status, new_market.status); + assert_eq!(old_market.report, new_market.report); + assert_eq!(old_market.resolved_outcome, new_market.resolved_outcome); + assert_eq!(old_market.dispute_mechanism, new_market.dispute_mechanism); + assert_eq!(old_market.bonds, new_market.bonds); + assert_eq!(old_market.early_close, new_market.early_close); + } log::info!( - "MigrateScoringRuleAmmCdaHybrid: Market counter post-upgrade is {}!", + "MigrateScoringRuleAmmCdaHybridAndMarketId: Post-upgrade market count is {}!", new_market_count ); Ok(()) @@ -181,18 +207,24 @@ where #[cfg(test)] mod tests { use super::*; - use crate::mock::{ExtBuilder, Runtime}; + use crate::{ + mock::{ExtBuilder, Runtime}, + MarketOf, + }; use alloc::fmt::Debug; - use frame_support::{migration::put_storage_value, storage_root, StorageHasher}; + use frame_support::{ + migration::put_storage_value, storage_root, Blake2_128Concat, StorageHasher, + }; + use parity_scale_codec::Encode; use sp_runtime::{Perbill, StateVersion}; use test_case::test_case; - use zeitgeist_primitives::types::{BaseAssetClass, Bond, MarketId}; + use zeitgeist_primitives::types::{BaseAssetClass, Bond, EarlyCloseState, MarketId}; #[test] fn on_runtime_upgrade_increments_the_storage_version() { ExtBuilder::default().build().execute_with(|| { set_up_version(); - MigrateScoringRuleAmmCdaHybrid::::on_runtime_upgrade(); + MigrateScoringRuleAmmCdaHybridAndMarketId::::on_runtime_upgrade(); assert_eq!( StorageVersion::get::>(), MARKET_COMMONS_NEXT_STORAGE_VERSION @@ -209,73 +241,17 @@ mod tests { ) { ExtBuilder::default().build().execute_with(|| { set_up_version(); - let base_asset = BaseAssetClass::Ztg; - let creator = 0; - let creation = MarketCreation::Permissionless; - let creator_fee = Perbill::from_rational(1u32, 1_000u32); - let oracle = 2; - let metadata = vec![0x03; 50]; - let market_type = MarketType::Categorical(4); - let period = MarketPeriod::Block(5..6); - let deadlines = Deadlines { grace_period: 7, oracle_duration: 8, dispute_duration: 9 }; - let report = Some(Report { at: 13, by: 14, outcome: OutcomeReport::Categorical(10) }); - let resolved_outcome = None; - let dispute_mechanism = Some(MarketDisputeMechanism::Court); - let bonds = MarketBonds { - creation: Some(Bond::new(11, 12)), - oracle: None, - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - }; - let status = MarketStatus::Active; - let early_close = None; - let old_market = OldMarketOf:: { - base_asset, - creator, - creation: creation.clone(), - creator_fee, - oracle, - metadata: metadata.clone(), - market_type: market_type.clone(), - period: period.clone(), - deadlines, - scoring_rule: old_scoring_rule, - status, - report: report.clone(), - resolved_outcome: resolved_outcome.clone(), - dispute_mechanism: dispute_mechanism.clone(), - bonds: bonds.clone(), - early_close: early_close.clone(), - }; - let new_market = Market { - base_asset, - creator, - creation, - creator_fee, - oracle, - metadata, - market_type, - period, - deadlines, - scoring_rule: new_scoring_rule, - status, - report, - resolved_outcome, - dispute_mechanism, - bonds, - early_close, - }; + let (old_markets, new_markets) = + construct_old_new_tuple(old_scoring_rule, new_scoring_rule); populate_test_data::>( MARKET_COMMONS, MARKETS, - vec![old_market], + old_markets, ); - MigrateScoringRuleAmmCdaHybrid::::on_runtime_upgrade(); - - let actual = crate::Markets::::get(0).unwrap(); - assert_eq!(actual, new_market); + MigrateScoringRuleAmmCdaHybridAndMarketId::::on_runtime_upgrade(); + assert_eq!(crate::Markets::::get(0).unwrap(), new_markets[0]); + assert_eq!(crate::Markets::::get(1).unwrap(), new_markets[1]); + assert_eq!(crate::Markets::::get(2).unwrap(), new_markets[2]); }); } @@ -285,6 +261,7 @@ mod tests { StorageVersion::new(MARKET_COMMONS_NEXT_STORAGE_VERSION) .put::>(); let market = Market { + market_id: 7, base_asset: BaseAssetClass::Ztg, creator: 1, creation: MarketCreation::Permissionless, @@ -311,7 +288,7 @@ mod tests { }; crate::Markets::::insert(333, market); let tmp = storage_root(StateVersion::V1); - MigrateScoringRuleAmmCdaHybrid::::on_runtime_upgrade(); + MigrateScoringRuleAmmCdaHybridAndMarketId::::on_runtime_upgrade(); assert_eq!(tmp, storage_root(StateVersion::V1)); }); } @@ -321,6 +298,82 @@ mod tests { .put::>(); } + fn construct_old_new_tuple( + old_scoring_rule: OldScoringRule, + new_scoring_rule: ScoringRule, + ) -> (Vec>, Vec>) { + let base_asset = BaseAsset::Ztg; + let creation = MarketCreation::Advised; + let creator_fee = Perbill::from_rational(2u32, 3u32); + let oracle = 4; + let metadata = vec![5; 50]; + let market_type = MarketType::Scalar(6..=7); + let period = MarketPeriod::Block(8..9); + let deadlines = Deadlines { grace_period: 10, oracle_duration: 11, dispute_duration: 12 }; + let status = MarketStatus::Resolved; + let report = Some(Report { at: 13, by: 14, outcome: OutcomeReport::Categorical(15) }); + let resolved_outcome = Some(OutcomeReport::Categorical(16)); + let dispute_mechanism = Some(MarketDisputeMechanism::Court); + let bonds = MarketBonds { + creation: Some(Bond { who: 17, value: 18, is_settled: true }), + oracle: Some(Bond { who: 19, value: 20, is_settled: true }), + outsider: Some(Bond { who: 21, value: 22, is_settled: true }), + dispute: Some(Bond { who: 23, value: 24, is_settled: true }), + close_request: Some(Bond { who: 25, value: 26, is_settled: true }), + close_dispute: Some(Bond { who: 27, value: 28, is_settled: true }), + }; + let early_close = Some(EarlyClose { + old: MarketPeriod::Block(29..30), + new: MarketPeriod::Block(31..32), + state: EarlyCloseState::Disputed, + }); + let sentinels = (0..3).map(|i| 10 - i); + let old_markets = sentinels + .clone() + .map(|sentinel| OldMarket { + base_asset, + creator: sentinel, + creation: creation.clone(), + creator_fee, + oracle, + metadata: metadata.clone(), + market_type: market_type.clone(), + period: period.clone(), + deadlines, + scoring_rule: old_scoring_rule, + status, + report: report.clone(), + resolved_outcome: resolved_outcome.clone(), + dispute_mechanism: dispute_mechanism.clone(), + bonds: bonds.clone(), + early_close: early_close.clone(), + }) + .collect(); + let new_markets = sentinels + .enumerate() + .map(|(index, sentinel)| Market { + market_id: index as u128, + base_asset, + creator: sentinel, + creation: creation.clone(), + creator_fee, + oracle, + metadata: metadata.clone(), + market_type: market_type.clone(), + period: period.clone(), + deadlines, + scoring_rule: new_scoring_rule, + status, + report: report.clone(), + resolved_outcome: resolved_outcome.clone(), + dispute_mechanism: dispute_mechanism.clone(), + bonds: bonds.clone(), + early_close: early_close.clone(), + }) + .collect(); + (old_markets, new_markets) + } + #[allow(unused)] fn populate_test_data(pallet: &[u8], prefix: &[u8], data: Vec) where diff --git a/zrml/market-commons/src/tests.rs b/zrml/market-commons/src/tests.rs index d827609ab..a7d26af88 100644 --- a/zrml/market-commons/src/tests.rs +++ b/zrml/market-commons/src/tests.rs @@ -20,54 +20,70 @@ use crate::{ mock::{ExtBuilder, MarketCommons, Runtime}, - MarketCounter, Markets, + types::MarketBuilder, + AccountIdOf, MarketCounter, Markets, }; use frame_support::{assert_err, assert_noop, assert_ok}; use sp_runtime::{DispatchError, Perbill}; use zeitgeist_primitives::{ - traits::MarketCommonsPalletApi, + traits::{MarketBuilderTrait, MarketCommonsPalletApi}, types::{ - AccountIdTest, Balance, BaseAsset, BlockNumber, Deadlines, Market, MarketBonds, - MarketCreation, MarketDisputeMechanism, MarketPeriod, MarketStatus, MarketType, Moment, - ScoringRule, + BaseAsset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, ScoringRule, }, }; -const MARKET_DUMMY: Market = Market { - base_asset: BaseAsset::Ztg, - creation: MarketCreation::Permissionless, - creator_fee: Perbill::zero(), - creator: 0, - market_type: MarketType::Scalar(0..=100), - dispute_mechanism: Some(MarketDisputeMechanism::Authorized), - metadata: vec![], - oracle: 0, - period: MarketPeriod::Block(0..100), - deadlines: Deadlines { grace_period: 1_u64, oracle_duration: 1_u64, dispute_duration: 1_u64 }, - report: None, - resolved_outcome: None, - scoring_rule: ScoringRule::AmmCdaHybrid, - status: MarketStatus::Disputed, - bonds: MarketBonds { - creation: None, - oracle: None, - outsider: None, - dispute: None, - close_dispute: None, - close_request: None, - }, - early_close: None, -}; +// Creates a sample market builder. We use the `oracle` field to tell markets apart from each other. +fn create_market_builder(oracle: AccountIdOf) -> MarketBuilder { + let mut market_builder = MarketBuilder::new(); + market_builder + .base_asset(BaseAsset::Ztg) + .creation(MarketCreation::Permissionless) + .creator_fee(Perbill::zero()) + .creator(0) + .market_type(MarketType::Scalar(0..=100)) + .dispute_mechanism(Some(MarketDisputeMechanism::Authorized)) + .metadata(vec![]) + .oracle(oracle) + .period(MarketPeriod::Block(0..100)) + .deadlines(Deadlines { + grace_period: 1_u64, + oracle_duration: 1_u64, + dispute_duration: 1_u64, + }) + .report(None) + .resolved_outcome(None) + .scoring_rule(ScoringRule::AmmCdaHybrid) + .status(MarketStatus::Disputed) + .bonds(MarketBonds { + creation: None, + oracle: None, + outsider: None, + dispute: None, + close_dispute: None, + close_request: None, + }) + .early_close(None); + market_builder +} #[test] -fn latest_market_id_interacts_correctly_with_push_market() { +fn build_market_interacts_correct_with_latest_market_id_and_returns_correct_values() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(market_mock(0))); - assert_eq!(MarketCommons::latest_market_id().unwrap(), 0); - assert_ok!(MarketCommons::push_market(market_mock(1))); - assert_eq!(MarketCommons::latest_market_id().unwrap(), 1); - assert_ok!(MarketCommons::push_market(market_mock(1))); - assert_eq!(MarketCommons::latest_market_id().unwrap(), 2); + let mut builder = create_market_builder(3); + let (market_id, market) = MarketCommons::build_market(builder.clone()).unwrap(); + assert_eq!(market_id, 0); + assert_eq!(market, builder.market_id(market_id).clone().build().unwrap()); + + let mut builder = create_market_builder(6); + let (market_id, market) = MarketCommons::build_market(builder.clone()).unwrap(); + assert_eq!(market_id, 1); + assert_eq!(market, builder.market_id(market_id).clone().build().unwrap()); + + let mut builder = create_market_builder(9); + let (market_id, market) = MarketCommons::build_market(builder.clone()).unwrap(); + assert_eq!(market_id, 2); + assert_eq!(market, builder.market_id(market_id).clone().build().unwrap()); }); } @@ -82,11 +98,11 @@ fn latest_market_id_fails_if_there_are_no_markets() { } #[test] -fn market_interacts_correctly_with_push_market() { +fn market_interacts_correctly_with_build_market() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(market_mock(0))); - assert_ok!(MarketCommons::push_market(market_mock(1))); - assert_ok!(MarketCommons::push_market(market_mock(2))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(1))); + assert_ok!(MarketCommons::build_market(create_market_builder(2))); assert_eq!(MarketCommons::market(&0).unwrap().oracle, 0); assert_eq!(MarketCommons::market(&1).unwrap().oracle, 1); assert_eq!(MarketCommons::market(&2).unwrap().oracle, 2); @@ -94,11 +110,11 @@ fn market_interacts_correctly_with_push_market() { } #[test] -fn markets_storage_map_interacts_correctly_with_push_market() { +fn markets_storage_map_interacts_correctly_with_build_market() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(market_mock(0))); - assert_ok!(MarketCommons::push_market(market_mock(1))); - assert_ok!(MarketCommons::push_market(market_mock(2))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(1))); + assert_ok!(MarketCommons::build_market(create_market_builder(2))); assert_eq!(>::get(0).unwrap().oracle, 0); assert_eq!(>::get(1).unwrap().oracle, 1); assert_eq!(>::get(2).unwrap().oracle, 2); @@ -109,9 +125,9 @@ fn markets_storage_map_interacts_correctly_with_push_market() { fn market_fails_if_market_does_not_exist() { ExtBuilder::default().build().execute_with(|| { assert_noop!(MarketCommons::market(&0), crate::Error::::MarketDoesNotExist); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_noop!(MarketCommons::market(&3), crate::Error::::MarketDoesNotExist); }); } @@ -119,7 +135,7 @@ fn market_fails_if_market_does_not_exist() { #[test] fn mutate_market_succeeds_if_closure_succeeds() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(market_mock(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::mutate_market(&0, |market| { market.oracle = 1; Ok(()) @@ -135,9 +151,9 @@ fn mutate_market_fails_if_market_does_not_exist() { MarketCommons::mutate_market(&0, |_| Ok(())), crate::Error::::MarketDoesNotExist ); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_noop!( MarketCommons::mutate_market(&3, |_| Ok(())), crate::Error::::MarketDoesNotExist @@ -149,7 +165,7 @@ fn mutate_market_fails_if_market_does_not_exist() { fn mutate_market_is_noop_if_closure_fails() { ExtBuilder::default().build().execute_with(|| { let err = DispatchError::Other("foo"); - assert_ok!(MarketCommons::push_market(market_mock(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_noop!( // We change the market to check that `mutate_market` is no-op when it errors. MarketCommons::mutate_market(&0, |market| { @@ -162,11 +178,11 @@ fn mutate_market_is_noop_if_closure_fails() { } #[test] -fn remove_market_correctly_interacts_with_push_market() { +fn remove_market_correctly_interacts_with_build_market() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(market_mock(0))); - assert_ok!(MarketCommons::push_market(market_mock(1))); - assert_ok!(MarketCommons::push_market(market_mock(2))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(1))); + assert_ok!(MarketCommons::build_market(create_market_builder(2))); assert_ok!(MarketCommons::remove_market(&1)); assert_eq!(MarketCommons::market(&0).unwrap().oracle, 0); @@ -189,9 +205,9 @@ fn remove_market_correctly_interacts_with_push_market() { fn remove_market_fails_if_market_does_not_exist() { ExtBuilder::default().build().execute_with(|| { assert_noop!(MarketCommons::remove_market(&0), crate::Error::::MarketDoesNotExist); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_noop!(MarketCommons::remove_market(&3), crate::Error::::MarketDoesNotExist); }); } @@ -203,9 +219,9 @@ fn insert_market_pool_fails_if_market_does_not_exist() { MarketCommons::insert_market_pool(0, 15), crate::Error::::MarketDoesNotExist ); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_noop!( MarketCommons::insert_market_pool(3, 12), crate::Error::::MarketDoesNotExist @@ -216,7 +232,7 @@ fn insert_market_pool_fails_if_market_does_not_exist() { #[test] fn insert_market_pool_fails_if_market_has_a_pool() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::insert_market_pool(0, 15)); assert_noop!( MarketCommons::insert_market_pool(0, 14), @@ -228,9 +244,9 @@ fn insert_market_pool_fails_if_market_has_a_pool() { #[test] fn market_pool_correctly_interacts_with_insert_market_pool() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::insert_market_pool(0, 15)); assert_ok!(MarketCommons::insert_market_pool(1, 14)); assert_ok!(MarketCommons::insert_market_pool(2, 13)); @@ -247,9 +263,9 @@ fn market_pool_fails_if_market_has_no_pool() { MarketCommons::market_pool(&0), crate::Error::::MarketPoolDoesNotExist ); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::insert_market_pool(0, 15)); assert_ok!(MarketCommons::insert_market_pool(1, 14)); assert_ok!(MarketCommons::insert_market_pool(2, 13)); @@ -263,9 +279,9 @@ fn market_pool_fails_if_market_has_no_pool() { #[test] fn remove_market_pool_correctly_interacts_with_insert_market_pool() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::insert_market_pool(0, 15)); assert_ok!(MarketCommons::insert_market_pool(1, 14)); assert_ok!(MarketCommons::insert_market_pool(2, 13)); @@ -312,9 +328,9 @@ fn remove_market_pool_fails_if_market_has_no_pool() { MarketCommons::remove_market_pool(&0), crate::Error::::MarketPoolDoesNotExist ); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_ok!(MarketCommons::insert_market_pool(0, 15)); assert_ok!(MarketCommons::insert_market_pool(1, 14)); assert_ok!(MarketCommons::insert_market_pool(2, 13)); @@ -326,28 +342,20 @@ fn remove_market_pool_fails_if_market_has_no_pool() { } #[test] -fn market_counter_interacts_correctly_with_push_market_and_remove_market() { +fn market_counter_interacts_correctly_with_build_market_and_remove_market() { ExtBuilder::default().build().execute_with(|| { assert_eq!(>::get(), 0); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_eq!(>::get(), 1); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_eq!(>::get(), 2); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_eq!(>::get(), 3); assert_ok!(MarketCommons::remove_market(&1)); assert_eq!(>::get(), 3); assert_ok!(MarketCommons::remove_market(&2)); assert_eq!(>::get(), 3); - assert_ok!(MarketCommons::push_market(MARKET_DUMMY)); + assert_ok!(MarketCommons::build_market(create_market_builder(0))); assert_eq!(>::get(), 4); }); } - -fn market_mock( - id: AccountIdTest, -) -> zeitgeist_primitives::types::Market { - let mut market = MARKET_DUMMY; - market.oracle = id; - market -} diff --git a/zrml/market-commons/src/types/market_builder.rs b/zrml/market-commons/src/types/market_builder.rs new file mode 100644 index 000000000..c121f19e0 --- /dev/null +++ b/zrml/market-commons/src/types/market_builder.rs @@ -0,0 +1,167 @@ +// 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::{ + AccountIdOf, BalanceOf, BlockNumberOf, Config, DeadlinesOf, EarlyCloseOf, Error, MarketBondsOf, + MarketIdOf, MarketOf, MarketPeriodOf, MomentOf, ReportOf, +}; +use alloc::vec::Vec; +use sp_runtime::{DispatchError, Perbill}; +use zeitgeist_primitives::{ + traits::MarketBuilderTrait, + types::{ + BaseAsset, Market, MarketCreation, MarketDisputeMechanism, MarketStatus, MarketType, + OutcomeReport, ScoringRule, + }, +}; + +/// Fully-fledged mutably referenced builder struct for `Market`. +#[derive(Clone)] +pub struct MarketBuilder +where + T: Config, +{ + market_id: Option>, + base_asset: Option, + creator: Option>, + creation: Option, + creator_fee: Option, + oracle: Option>, + metadata: Option>, + market_type: Option, + period: Option>, + deadlines: Option>, + scoring_rule: Option, + status: Option, + report: Option>>, + resolved_outcome: Option>, + dispute_mechanism: Option>, + bonds: Option>, + early_close: Option>>, +} + +impl MarketBuilder +where + T: Config, +{ + pub fn new() -> Self { + MarketBuilder { + market_id: None, + base_asset: None, + creator: None, + creation: None, + creator_fee: None, + oracle: None, + metadata: None, + market_type: None, + period: None, + deadlines: None, + scoring_rule: None, + status: None, + report: None, + resolved_outcome: None, + dispute_mechanism: None, + bonds: None, + early_close: None, + } + } +} + +impl Default for MarketBuilder +where + T: Config, +{ + fn default() -> Self { + Self::new() + } +} + +/// Implements setter methods for a mutably referenced builder struct. Fields are specified using +/// the pattern `{ field: type, ... }`. +macro_rules! impl_builder_methods { + ($($field:ident: $type:ty),* $(,)?) => { + $( + fn $field(&mut self, $field: $type) -> &mut Self { + self.$field = Some($field); + self + } + )* + } +} + +/// Unwraps `opt` and throws `IncompleteMarketBuilder` in case of failure. +fn ok_or_incomplete(opt: Option) -> Result +where + T: Config, +{ + opt.ok_or(Error::::IncompleteMarketBuilder.into()) +} + +impl + MarketBuilderTrait< + AccountIdOf, + BalanceOf, + BlockNumberOf, + MomentOf, + BaseAsset, + MarketIdOf, + > for MarketBuilder +where + T: Config, +{ + fn build(self) -> Result, DispatchError> { + Ok(Market { + market_id: ok_or_incomplete::(self.market_id)?, + base_asset: ok_or_incomplete::(self.base_asset)?, + creator: ok_or_incomplete::(self.creator)?, + creation: ok_or_incomplete::(self.creation)?, + creator_fee: ok_or_incomplete::(self.creator_fee)?, + oracle: ok_or_incomplete::(self.oracle)?, + metadata: ok_or_incomplete::(self.metadata)?, + market_type: ok_or_incomplete::(self.market_type)?, + period: ok_or_incomplete::(self.period)?, + deadlines: ok_or_incomplete::(self.deadlines)?, + scoring_rule: ok_or_incomplete::(self.scoring_rule)?, + status: ok_or_incomplete::(self.status)?, + report: ok_or_incomplete::(self.report)?, + resolved_outcome: ok_or_incomplete::(self.resolved_outcome)?, + dispute_mechanism: ok_or_incomplete::(self.dispute_mechanism)?, + bonds: ok_or_incomplete::(self.bonds)?, + early_close: ok_or_incomplete::(self.early_close)?, + }) + } + + impl_builder_methods! { + market_id: MarketIdOf, + base_asset: BaseAsset, + creator: AccountIdOf, + creation: MarketCreation, + creator_fee: Perbill, + oracle: AccountIdOf, + metadata: Vec, + market_type: MarketType, + period: MarketPeriodOf, + deadlines: DeadlinesOf, + scoring_rule: ScoringRule, + status: MarketStatus, + report: Option>, + resolved_outcome: Option, + dispute_mechanism: Option, + bonds: MarketBondsOf, + early_close: Option>, + } +} diff --git a/zrml/market-commons/src/types/mod.rs b/zrml/market-commons/src/types/mod.rs new file mode 100644 index 000000000..699f70ab2 --- /dev/null +++ b/zrml/market-commons/src/types/mod.rs @@ -0,0 +1,20 @@ +// 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 . + +mod market_builder; + +pub use market_builder::*; diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index a241993bd..ad55064b0 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -180,6 +180,7 @@ where T: Config, { let market = Market { + market_id: 0u8.into(), base_asset: base_asset.try_into().unwrap(), creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), @@ -197,8 +198,7 @@ where bonds: Default::default(), early_close: None, }; - let maybe_market_id = T::MarketCommons::push_market(market); - maybe_market_id.unwrap() + T::MarketCommons::push_market(market).unwrap() } fn create_spot_prices(asset_count: u16) -> Vec> { diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 67e6689db..6519c130c 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -71,7 +71,7 @@ mod pallet { fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, }, traits::{CompleteSetOperationsApi, DeployPoolApi, DistributeFees, HybridRouterAmmApi}, - types::{Asset, MarketStatus, MarketType, ScalarPosition, ScoringRule}, + types::{Asset, MarketStatus, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -886,11 +886,10 @@ mod pallet { Error::::LiquidityTooLow ); let pool_account_id = Self::pool_account_id(&market_id); - let assets = Self::outcomes(market_id)?; let mut reserves = BTreeMap::new(); - for (&amount_in, &asset) in amounts_in.iter().zip(assets.iter()) { - T::MultiCurrency::transfer(asset, &who, &pool_account_id, amount_in)?; - let _ = reserves.insert(asset, amount_in); + for (&amount_in, &asset) in amounts_in.iter().zip(market.outcome_assets().iter()) { + T::MultiCurrency::transfer(asset.into(), &who, &pool_account_id, amount_in)?; + let _ = reserves.insert(asset.into(), amount_in); } let collateral = market.base_asset; let pool = Pool { @@ -953,26 +952,6 @@ mod pallet { Ok(FeeDistribution { remaining, swap_fees, external_fees }) } - // TODO(#1218): Carbon copy of a function in prediction-markets. To be removed later. - fn outcomes(market_id: MarketIdOf) -> Result>, DispatchError> { - let market = T::MarketCommons::market(&market_id)?; - Ok(match market.market_type { - 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), - ] - } - }) - } - pub(crate) fn try_mutate_pool( market_id: &MarketIdOf, mutator: F, diff --git a/zrml/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index f1d0f8058..e2cc1c892 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -488,7 +488,7 @@ mod pallet { let outcome_asset_converted = outcome_asset.try_into().map_err(|_| Error::::InvalidOutcomeAsset)?; - let market_assets = market.outcome_assets(market_id); + let market_assets = market.outcome_assets(); market_assets .binary_search(&outcome_asset_converted) .map_err(|_| Error::::InvalidOutcomeAsset)?; diff --git a/zrml/orderbook/src/utils.rs b/zrml/orderbook/src/utils.rs index 3a5c98490..6504fa01f 100644 --- a/zrml/orderbook/src/utils.rs +++ b/zrml/orderbook/src/utils.rs @@ -32,6 +32,7 @@ type MarketOf = Market< ::BlockNumber, MomentOf, BaseAsset, + MarketIdOf, >; pub(crate) fn market_mock() -> MarketOf @@ -39,6 +40,7 @@ where T: crate::Config, { Market { + market_id: Default::default(), base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/parimutuel/src/lib.rs b/zrml/parimutuel/src/lib.rs index 098e07460..c04b70900 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -115,8 +115,14 @@ mod pallet { pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type MomentOf = <::MarketCommons as MarketCommonsPalletApi>::Moment; - pub(crate) type MarketOf = - Market, BalanceOf, BlockNumberFor, MomentOf, BaseAsset>; + pub(crate) type MarketOf = Market< + AccountIdOf, + BalanceOf, + BlockNumberFor, + MomentOf, + BaseAsset, + MarketIdOf, + >; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -549,7 +555,6 @@ mod pallet { } fn get_assets_to_destroy( - market_id: &MarketIdOf, market: &MarketOf, filter: F, ) -> BTreeMap, Option> @@ -558,7 +563,7 @@ mod pallet { { BTreeMap::, Option>::from_iter( market - .outcome_assets(*market_id) + .outcome_assets() .into_iter() .filter(|asset| filter(*asset)) .map(|asset| (AssetOf::::from(asset), None)), @@ -580,7 +585,7 @@ mod pallet { } }; - for outcome in market.outcome_assets(*market_id) { + for outcome in market.outcome_assets() { let admin = Self::pot_account(*market_id); let is_sufficient = true; let min_balance = 1u8; @@ -610,7 +615,7 @@ mod pallet { } }; - let winning_asset_option = market.resolved_outcome_into_asset(*market_id); + let winning_asset_option = market.resolved_outcome_into_asset(); let winning_asset = if let Some(winning_asset) = winning_asset_option { winning_asset } else { @@ -625,11 +630,11 @@ mod pallet { 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| { + Self::get_assets_to_destroy(&market, |asset| { T::AssetCreator::total_issuance(asset.into()).is_zero() }) } else { - Self::get_assets_to_destroy(market_id, &market, |asset| asset != winning_asset) + Self::get_assets_to_destroy(&market, |asset| asset != winning_asset) }; let destroy_result = diff --git a/zrml/parimutuel/src/tests/assets.rs b/zrml/parimutuel/src/tests/assets.rs index dc3ab9531..86a76a0db 100644 --- a/zrml/parimutuel/src/tests/assets.rs +++ b/zrml/parimutuel/src/tests/assets.rs @@ -43,7 +43,7 @@ fn created_after_market_activation() { 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) { + for asset in market.outcome_assets() { assert!(::AssetCreator::asset_exists(asset.into())); } }); @@ -95,10 +95,8 @@ fn destroyed_losing_after_resolution_with_winner() { ::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)) + for asset in + market.outcome_assets().iter().filter(|a| Asset::from(**a) != Asset::from(winner_asset)) { assert!( !::AssetCreator::asset_exists((*asset).into()), @@ -157,10 +155,8 @@ fn destroyed_after_resolution_without_winner() { ::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)) + for asset in + market.outcome_assets().iter().filter(|a| Asset::from(**a) != Asset::from(losing_asset)) { assert!( !::AssetCreator::asset_exists((*asset).into()), diff --git a/zrml/parimutuel/src/utils.rs b/zrml/parimutuel/src/utils.rs index c153885b6..b8a99edcd 100644 --- a/zrml/parimutuel/src/utils.rs +++ b/zrml/parimutuel/src/utils.rs @@ -28,6 +28,7 @@ where }; zeitgeist_primitives::types::Market { + market_id: Default::default(), base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index dbd77245f..abefb9275 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -71,7 +71,7 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, MarketTransitionApi, + DisputeResolutionApi, MarketBuilderTrait, MarketTransitionApi, }, types::{ Asset, BaseAsset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, @@ -82,7 +82,7 @@ mod pallet { }; use zrml_global_disputes::{types::InitialItem, GlobalDisputesPalletApi}; use zrml_liquidity_mining::LiquidityMiningPalletApi; - use zrml_market_commons::MarketCommonsPalletApi; + use zrml_market_commons::{types::MarketBuilder, MarketCommonsPalletApi}; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); @@ -95,7 +95,9 @@ mod pallet { pub(crate) type AccountIdOf = ::AccountId; pub(crate) type AssetOf = Asset>; pub(crate) type BalanceOf = ::Balance; + pub(crate) type BlockNumberOf = ::BlockNumber; pub(crate) type CacheSize = ConstU32<64>; + pub(crate) type DeadlinesOf = Deadlines>; pub(crate) type EditReason = BoundedVec::MaxEditReasonLen>; pub(crate) type InitialItemOf = InitialItem, BalanceOf>; pub(crate) type MarketBondsOf = MarketBonds, BalanceOf>; @@ -103,16 +105,18 @@ mod pallet { pub(crate) type MarketOf = Market< AccountIdOf, BalanceOf, - ::BlockNumber, + BlockNumberOf, MomentOf, BaseAsset, + MarketIdOf, >; + pub(crate) type MarketPeriodOf = MarketPeriod, MomentOf>; pub(crate) type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; pub(crate) type NegativeImbalanceOf = <::Currency as Currency>>::NegativeImbalance; pub(crate) type RejectReason = BoundedVec::MaxRejectReasonLen>; - pub(crate) type ReportOf = Report, ::BlockNumber>; + pub(crate) type ReportOf = Report, BlockNumberOf>; pub(crate) type TimeFrame = u64; macro_rules! impl_unreserve_bond { @@ -447,7 +451,7 @@ mod pallet { m.status = new_status; if m.is_redeemable() { - for outcome in m.outcome_assets(market_id) { + for outcome in m.outcome_assets() { let admin = Self::market_account(market_id); let is_sufficient = true; let min_balance = 1u8; @@ -541,7 +545,7 @@ mod pallet { let sender = ensure_signed(origin)?; Self::do_buy_complete_set(sender, market_id, amount)?; let market = >::market(&market_id)?; - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); let assets_len: u32 = assets.len().saturated_into(); Ok(Some(T::WeightInfo::buy_complete_set(assets_len)).into()) } @@ -618,8 +622,8 @@ mod pallet { base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -670,8 +674,8 @@ mod pallet { base_asset: BaseAsset, market_id: MarketIdOf, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, market_type: MarketType, dispute_mechanism: Option, @@ -687,7 +691,8 @@ mod pallet { ensure!(old_market.status == MarketStatus::Proposed, Error::::InvalidMarketStatus); Self::clear_auto_close(&market_id)?; - let edited_market = Self::construct_market( + let market_builder = Self::construct_market( + Some(market_id), base_asset, old_market.creator, old_market.creator_fee, @@ -703,6 +708,7 @@ mod pallet { old_market.resolved_outcome, old_market.bonds, )?; + let edited_market = market_builder.clone().build()?; >::mutate_market(&market_id, |market| { *market = edited_market.clone(); Ok(()) @@ -962,7 +968,7 @@ mod pallet { let sender = ensure_signed(origin)?; Self::do_sell_complete_set(sender, market_id, amount)?; let market = >::market(&market_id)?; - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); let assets_len: u32 = assets.len().saturated_into(); Ok(Some(T::WeightInfo::sell_complete_set(assets_len)).into()) } @@ -1091,8 +1097,8 @@ mod pallet { base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, market_type: MarketType, dispute_mechanism: Option, @@ -1169,25 +1175,21 @@ mod pallet { let now_block = >::block_number(); let now_time = >::now(); - let get_new_period = |block_period, - time_frame_period| - -> Result< - MarketPeriod>, - DispatchError, - > { - match &market.period { - MarketPeriod::Block(range) => { - let close_time = now_block.saturating_add(block_period); - ensure!(close_time < range.end, Error::::EarlyCloseRequestTooLate); - Ok(MarketPeriod::Block(range.start..close_time)) - } - MarketPeriod::Timestamp(range) => { - let close_time = now_time.saturating_add(time_frame_period); - ensure!(close_time < range.end, Error::::EarlyCloseRequestTooLate); - Ok(MarketPeriod::Timestamp(range.start..close_time)) + let get_new_period = + |block_period, time_frame_period| -> Result, DispatchError> { + match &market.period { + MarketPeriod::Block(range) => { + let close_time = now_block.saturating_add(block_period); + ensure!(close_time < range.end, Error::::EarlyCloseRequestTooLate); + Ok(MarketPeriod::Block(range.start..close_time)) + } + MarketPeriod::Timestamp(range) => { + let close_time = now_time.saturating_add(time_frame_period); + ensure!(close_time < range.end, Error::::EarlyCloseRequestTooLate); + Ok(MarketPeriod::Timestamp(range.start..close_time)) + } } - } - }; + }; let new_period = if let Some(p) = &market.early_close { ensure!(is_authorized, Error::::OnlyAuthorizedCanScheduleEarlyClose); @@ -2119,8 +2121,8 @@ mod pallet { base_asset: BaseAsset, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -2140,7 +2142,8 @@ mod pallet { }, }; - let market = Self::construct_market( + let market_builder = Self::construct_market( + None, base_asset, who.clone(), creator_fee, @@ -2164,12 +2167,13 @@ mod pallet { bonds.total_amount_bonded(&who), )?; - let market_id = >::push_market(market.clone())?; + let (market_id, market) = + >::build_market(market_builder)?; 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) { + for outcome in market.outcome_assets() { let admin = market_account.clone(); let is_sufficient = true; let min_balance = 1u8; @@ -2311,7 +2315,7 @@ mod pallet { "Market account does not have sufficient reserves.", ); - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); // verify first. for asset in assets.iter() { @@ -2360,7 +2364,7 @@ mod pallet { let market_account = Self::market_account(market_id); T::AssetManager::transfer(market.base_asset.into(), &who, &market_account, amount)?; - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); for asset in assets.iter() { T::AssetManager::deposit((*asset).into(), &who, amount)?; } @@ -2423,9 +2427,7 @@ mod pallet { Ok(()) } - fn ensure_market_period_is_valid( - period: &MarketPeriod>, - ) -> DispatchResult { + fn ensure_market_period_is_valid(period: &MarketPeriodOf) -> DispatchResult { // The start of the market is allowed to be in the past (this results in the market // being active immediately), but the market's end must be at least one block/time // frame in the future. @@ -2460,7 +2462,7 @@ mod pallet { } fn ensure_market_deadlines_are_valid( - deadlines: &Deadlines, + deadlines: &DeadlinesOf, trusted: bool, ) -> DispatchResult { ensure!( @@ -2806,13 +2808,13 @@ mod pallet { Ok(()) })?; - let winning_outcome = updated_market.resolved_outcome_into_asset(*market_id); + let winning_outcome = updated_market.resolved_outcome_into_asset(); 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) + .outcome_assets() .into_iter() .filter(|outcome| *outcome != winning_outcome_inner) .map(|asset| (AssetOf::::from(asset), None)), @@ -2937,12 +2939,13 @@ mod pallet { } fn construct_market( + market_id: Option>, base_asset: BaseAsset, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -2951,7 +2954,7 @@ mod pallet { report: Option>, resolved_outcome: Option, bonds: MarketBondsOf, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { let valid_base_asset = match base_asset { BaseAsset::CampaignAsset(idx) => { T::AssetCreator::asset_exists(BaseAsset::CampaignAsset(idx).into()) @@ -2982,24 +2985,28 @@ mod pallet { MarketCreation::Permissionless => MarketStatus::Active, MarketCreation::Advised => MarketStatus::Proposed, }; - Ok(Market { - base_asset, - creation, - creator_fee, - creator, - market_type, - dispute_mechanism, - metadata: Vec::from(multihash), - oracle, - period, - deadlines, - report, - resolved_outcome, - status, - scoring_rule, - bonds, - early_close: None, - }) + let mut market_builder = MarketBuilder::new(); + market_builder + .base_asset(base_asset) + .creator(creator) + .creator_fee(creator_fee) + .oracle(oracle) + .period(period) + .deadlines(deadlines) + .metadata(Vec::from(multihash)) + .creation(creation) + .market_type(market_type) + .dispute_mechanism(dispute_mechanism) + .status(status) + .scoring_rule(scoring_rule) + .report(report) + .resolved_outcome(resolved_outcome) + .bonds(bonds) + .early_close(None); + if let Some(market_id) = market_id { + market_builder.market_id(market_id); + } + Ok(market_builder) } fn report_market_with_dispute_mechanism( diff --git a/zrml/prediction-markets/src/tests/buy_complete_set.rs b/zrml/prediction-markets/src/tests/buy_complete_set.rs index 52b1864b7..68b551cd1 100644 --- a/zrml/prediction-markets/src/tests/buy_complete_set.rs +++ b/zrml/prediction-markets/src/tests/buy_complete_set.rs @@ -40,7 +40,7 @@ fn buy_complete_set_works() { let market = MarketCommons::market(&market_id).unwrap(); - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); for asset in assets.iter() { let bal = AssetManager::free_balance((*asset).into(), &who); assert_eq!(bal, amount); diff --git a/zrml/prediction-markets/src/tests/integration.rs b/zrml/prediction-markets/src/tests/integration.rs index 7b14426df..4ea7749ff 100644 --- a/zrml/prediction-markets/src/tests/integration.rs +++ b/zrml/prediction-markets/src/tests/integration.rs @@ -205,7 +205,7 @@ fn full_scalar_market_lifecycle() { // check balances let market = &MarketCommons::market(&0).unwrap(); - let assets = market.outcome_assets(0); + let assets = market.outcome_assets(); assert_eq!(assets.len(), 2); for asset in assets.iter() { let bal = AssetManager::free_balance((*asset).into(), &CHARLIE); diff --git a/zrml/prediction-markets/src/tests/redeem_shares.rs b/zrml/prediction-markets/src/tests/redeem_shares.rs index 1159399e5..c71921add 100644 --- a/zrml/prediction-markets/src/tests/redeem_shares.rs +++ b/zrml/prediction-markets/src/tests/redeem_shares.rs @@ -185,7 +185,7 @@ fn scalar_market_correctly_resolves_common(base_asset: BaseAsset, reported_value assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(CHARLIE), 0)); assert_ok!(PredictionMarkets::redeem_shares(RuntimeOrigin::signed(EVE), 0)); let market = &MarketCommons::market(&0).unwrap(); - let assets = market.outcome_assets(0); + let assets = market.outcome_assets(); for asset in assets.iter() { assert_eq!(AssetManager::free_balance((*asset).into(), &CHARLIE), 0); assert_eq!(AssetManager::free_balance((*asset).into(), &EVE), 0); diff --git a/zrml/prediction-markets/src/tests/sell_complete_set.rs b/zrml/prediction-markets/src/tests/sell_complete_set.rs index 1463f81bf..a2ed720b2 100644 --- a/zrml/prediction-markets/src/tests/sell_complete_set.rs +++ b/zrml/prediction-markets/src/tests/sell_complete_set.rs @@ -49,7 +49,7 @@ fn sell_complete_set_works(scoring_rule: ScoringRule) { )); let market = MarketCommons::market(&market_id).unwrap(); - let assets = market.outcome_assets(market_id); + let assets = market.outcome_assets(); for asset in assets.iter() { let bal = AssetManager::free_balance((*asset).into(), &who); assert_eq!(bal, expected_amount); diff --git a/zrml/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index 6be6e0bb6..ba02d764b 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -116,6 +116,7 @@ mod pallet { ::BlockNumber, MomentOf, BaseAsset, + MarketIdOf, >; pub(crate) type DisputesOf = BoundedVec< MarketDispute< @@ -556,6 +557,7 @@ where use zeitgeist_primitives::types::{MarketBonds, ScoringRule}; zeitgeist_primitives::types::Market { + market_id: Default::default(), base_asset: BaseAsset::Ztg, creation: zeitgeist_primitives::types::MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/simple-disputes/src/mock.rs b/zrml/simple-disputes/src/mock.rs index 1fc79d844..b74e497f0 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -33,10 +33,10 @@ use zeitgeist_primitives::{ BlockHashCount, ExistentialDepositsAssets, GetNativeCurrencyId, MaxDisputes, MaxReserves, MinimumPeriod, OutcomeBond, OutcomeFactor, SimpleDisputesPalletId, BASE, }, - traits::DisputeResolutionApi, + traits::{DisputeResolutionApi, MarketOfDisputeResolutionApi}, types::{ - AccountIdTest, Amount, Assets, Balance, BaseAsset, BasicCurrencyAdapter, BlockNumber, - BlockTest, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Assets, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, Hash, + Index, MarketId, Moment, UncheckedExtrinsicTest, }, }; @@ -83,13 +83,7 @@ impl DisputeResolutionApi for NoopResolution { fn resolve( _market_id: &Self::MarketId, - _market: &Market< - Self::AccountId, - Self::Balance, - Self::BlockNumber, - Self::Moment, - BaseAsset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index 545d54741..d8d51098c 100644 --- a/zrml/simple-disputes/src/tests.rs +++ b/zrml/simple-disputes/src/tests.rs @@ -33,6 +33,7 @@ use zeitgeist_primitives::{ }; const DEFAULT_MARKET: MarketOf = Market { + market_id: 0, base_asset: BaseAsset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(),