diff --git a/primitives/src/market.rs b/primitives/src/market.rs index cae1eb2bc..33db98444 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,7 @@ pub struct Market { pub early_close: Option>, } -impl Market { +impl Market { pub fn resolution_mechanism(&self) -> ResolutionMechanism { match self.scoring_rule { ScoringRule::Lmsr | ScoringRule::Orderbook => ResolutionMechanism::RedeemTokens, @@ -139,7 +141,7 @@ impl Default for MarketBonds { } } -impl Market { +impl Market { // Returns the number of outcomes for a market. pub fn outcomes(&self) -> u16 { match self.market_type { @@ -165,16 +167,18 @@ impl Market { } } -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()) @@ -347,7 +351,7 @@ pub enum ResolutionMechanism { mod tests { use crate::{market::*, types::Asset}; use test_case::test_case; - type Market = crate::market::Market>; + type Market = crate::market::Market, u32>; #[test_case( MarketType::Categorical(6), @@ -403,6 +407,7 @@ mod tests { expected: bool, ) { let market = Market { + market_id: 9, base_asset: Asset::Ztg, creator: 1, creation: MarketCreation::Permissionless, diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 00fce342a..5b437452b 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -20,18 +20,20 @@ mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; mod distribute_fees; +mod market_builder; mod market_commons_pallet_api; mod market_id; mod swaps; mod zeitgeist_asset; mod zeitgeist_multi_reservable_currency; -pub use complete_set_operations_api::CompleteSetOperationsApi; -pub use deploy_pool_api::DeployPoolApi; -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 swaps::Swaps; +pub use complete_set_operations_api::*; +pub use deploy_pool_api::*; +pub use dispute_api::*; +pub use distribute_fees::*; +pub use market_builder::*; +pub use market_commons_pallet_api::*; +pub use market_id::*; +pub use swaps::*; pub use zeitgeist_asset::*; -pub use zeitgeist_multi_reservable_currency::ZeitgeistAssetManager; +pub use zeitgeist_multi_reservable_currency::*; diff --git a/primitives/src/traits/dispute_api.rs b/primitives/src/traits/dispute_api.rs index 7c7accf1c..b5ec88d5d 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. @@ -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, Asset<::MarketId>, + ::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, Asset<::MarketId>, + ::MarketId, >; pub trait DisputeResolutionApi { 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 251365157..dac3402f9 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::{Asset, Market, PoolId}; +use crate::{ + traits::MarketBuilderTrait, + types::{Asset, Market, PoolId}, +}; use frame_support::{ dispatch::{fmt::Debug, DispatchError, DispatchResult}, pallet_prelude::{MaybeSerializeDeserialize, Member}, @@ -30,12 +33,14 @@ 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< +type AssetOf = Asset<::MarketId>; +pub type MarketOf = Market< ::AccountId, ::Balance, ::BlockNumber, ::Moment, - Asset<::MarketId>, + AssetOf, + ::MarketId, >; /// Abstraction over storage operations for markets @@ -78,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, + AssetOf, + Self::MarketId, + >; + /// Removes a market from the storage. fn remove_market(market_id: &Self::MarketId) -> DispatchResult; diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 417a71cc5..616357787 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -16,161 +16,12 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -pub use crate::{ - asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, - serde_wrapper::*, -}; -#[cfg(feature = "arbitrary")] -use arbitrary::{Arbitrary, Result, Unstructured}; -use frame_support::dispatch::Weight; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_runtime::{ - generic, - traits::{BlakeTwo256, IdentifyAccount, Verify}, - MultiSignature, OpaqueExtrinsic, -}; - -/// Signed counter-part of Balance -pub type Amount = i128; - -/// Some way of identifying an account on the chain. We intentionally make it equivalent -/// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... -pub type AccountIndex = u64; - -/// Balance of an account. -pub type Balance = u128; - -/// Block type. -pub type Block = generic::Block; - -/// An index to a block. -pub type BlockNumber = u64; - -/// The index of the category for a `CategoricalOutcome` asset. -pub type CategoryIndex = u16; - -/// Multihash for digest sizes up to 384 bit. -/// The multicodec encoding the hash algorithm uses only 1 byte, -/// effecitvely limiting the number of available hash types. -/// HashType (1B) + DigestSize (1B) + Hash (48B). -#[derive(TypeInfo, Clone, Debug, Decode, Encode, Eq, PartialEq)] -pub enum MultiHash { - Sha3_384([u8; 50]), -} - -// Implementation for the fuzzer -#[cfg(feature = "arbitrary")] -impl<'a> Arbitrary<'a> for MultiHash { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let mut rand_bytes = <[u8; 50] as Arbitrary<'a>>::arbitrary(u)?; - rand_bytes[0] = 0x15; - rand_bytes[1] = 0x30; - Ok(MultiHash::Sha3_384(rand_bytes)) - } - - fn size_hint(_depth: usize) -> (usize, Option) { - (50, Some(50)) - } -} - -/// ORML adapter -pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; - -pub type CurrencyId = Asset; - -/// The asset id specifically used for pallet_assets_tx_payment for -/// paying transaction fees in different assets. -/// Since the polkadot extension and wallets can't handle custom asset ids other than just u32, -/// we are using a u32 as on the asset-hubs here. -pub type TxPaymentAssetId = u32; - -/// Index of a transaction in the chain. -pub type Index = u64; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// Block header type as expected by this runtime. -pub type Header = generic::Header; - -/// Digest item type. -pub type DigestItem = generic::DigestItem; - -/// The market identifier type. -pub type MarketId = u128; - -/// Time -pub type Moment = u64; - -/// The identifier type for pools. -pub type PoolId = u128; - -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - -// Tests - -pub type AccountIdTest = u128; - -#[cfg(feature = "std")] -pub type BlockTest = frame_system::mocking::MockBlock; - -#[cfg(feature = "std")] -pub type UncheckedExtrinsicTest = frame_system::mocking::MockUncheckedExtrinsic; - -#[derive(sp_runtime::RuntimeDebug, Clone, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct ResultWithWeightInfo { - pub result: R, - pub weight: Weight, -} - -#[derive( - Clone, - Copy, - Debug, - Decode, - Default, - Encode, - Eq, - MaxEncodedLen, - Ord, - PartialEq, - PartialOrd, - TypeInfo, -)] -/// Custom XC asset metadata -pub struct CustomMetadata { - /// XCM-related metadata. - pub xcm: XcmMetadata, - - /// Whether an asset can be used as base_asset in pools. - pub allow_as_base_asset: bool, -} - -#[derive( - Clone, - Copy, - Debug, - Decode, - Default, - Encode, - Eq, - MaxEncodedLen, - Ord, - PartialEq, - PartialOrd, - TypeInfo, -)] -pub struct XcmMetadata { - /// The factor used to determine the fee. - /// It is multiplied by the fee that would have been paid in native currency, so it represents - /// the ratio `native_price / other_asset_price`. It is a fixed point decimal number containing - /// as many fractional decimals as the asset it is used for contains. - /// Should be updated regularly. - pub fee_factor: Option, -} +pub mod multi_hash; +pub mod result_with_weight_info; +pub mod type_aliases; +pub mod xcm_metadata; + +pub use multi_hash::*; +pub use result_with_weight_info::*; +pub use type_aliases::*; +pub use xcm_metadata::*; diff --git a/primitives/src/types/multi_hash.rs b/primitives/src/types/multi_hash.rs new file mode 100644 index 000000000..628bf2134 --- /dev/null +++ b/primitives/src/types/multi_hash.rs @@ -0,0 +1,50 @@ +// 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 . + +pub use crate::{ + asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, + serde_wrapper::*, +}; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Result, Unstructured}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; + +/// Multihash for digest sizes up to 384 bit. +/// The multicodec encoding the hash algorithm uses only 1 byte, +/// effecitvely limiting the number of available hash types. +/// HashType (1B) + DigestSize (1B) + Hash (48B). +#[derive(TypeInfo, Clone, Debug, Decode, Encode, Eq, PartialEq)] +pub enum MultiHash { + Sha3_384([u8; 50]), +} + +// Implementation for the fuzzer +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for MultiHash { + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let mut rand_bytes = <[u8; 50] as Arbitrary<'a>>::arbitrary(u)?; + rand_bytes[0] = 0x15; + rand_bytes[1] = 0x30; + Ok(MultiHash::Sha3_384(rand_bytes)) + } + + fn size_hint(_depth: usize) -> (usize, Option) { + (50, Some(50)) + } +} diff --git a/primitives/src/types/result_with_weight_info.rs b/primitives/src/types/result_with_weight_info.rs new file mode 100644 index 000000000..34ac8b493 --- /dev/null +++ b/primitives/src/types/result_with_weight_info.rs @@ -0,0 +1,31 @@ +// 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 . + +pub use crate::{ + asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, + serde_wrapper::*, +}; +use frame_support::dispatch::Weight; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; + +#[derive(sp_runtime::RuntimeDebug, Clone, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct ResultWithWeightInfo { + pub result: R, + pub weight: Weight, +} diff --git a/primitives/src/types/type_aliases.rs b/primitives/src/types/type_aliases.rs new file mode 100644 index 000000000..7365ca2d9 --- /dev/null +++ b/primitives/src/types/type_aliases.rs @@ -0,0 +1,95 @@ +// 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 . + +pub use crate::{ + asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, + serde_wrapper::*, +}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiSignature, OpaqueExtrinsic, +}; + +/// Signed counter-part of Balance +pub type Amount = i128; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u64; + +/// Balance of an account. +pub type Balance = u128; + +/// Block type. +pub type Block = generic::Block; + +/// An index to a block. +pub type BlockNumber = u64; + +/// The index of the category for a `CategoricalOutcome` asset. +pub type CategoryIndex = u16; + +/// ORML adapter +pub type BasicCurrencyAdapter = orml_currencies::BasicCurrencyAdapter; + +pub type CurrencyId = Asset; + +/// The asset id specifically used for pallet_assets_tx_payment for +/// paying transaction fees in different assets. +/// Since the polkadot extension and wallets can't handle custom asset ids other than just u32, +/// we are using a u32 as on the asset-hubs here. +pub type TxPaymentAssetId = u32; + +/// Index of a transaction in the chain. +pub type Index = u64; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// Block header type as expected by this runtime. +pub type Header = generic::Header; + +/// Digest item type. +pub type DigestItem = generic::DigestItem; + +/// The market identifier type. +pub type MarketId = u128; + +/// Time +pub type Moment = u64; + +/// The identifier type for pools. +pub type PoolId = u128; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +// Tests + +pub type AccountIdTest = u128; + +#[cfg(feature = "std")] +pub type BlockTest = frame_system::mocking::MockBlock; + +#[cfg(feature = "std")] +pub type UncheckedExtrinsicTest = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/primitives/src/types/xcm_metadata.rs b/primitives/src/types/xcm_metadata.rs new file mode 100644 index 000000000..75480c8b3 --- /dev/null +++ b/primitives/src/types/xcm_metadata.rs @@ -0,0 +1,71 @@ +// Copyright 2022-2024 Forecasting Technologies LTD. +// Copyright 2021-2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::types::Balance; +pub use crate::{ + asset::*, market::*, max_runtime_usize::*, outcome_report::OutcomeReport, proxy_type::*, + serde_wrapper::*, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +#[derive( + Clone, + Copy, + Debug, + Decode, + Default, + Encode, + Eq, + MaxEncodedLen, + Ord, + PartialEq, + PartialOrd, + TypeInfo, +)] +/// Custom XC asset metadata +pub struct CustomMetadata { + /// XCM-related metadata. + pub xcm: XcmMetadata, + + /// Whether an asset can be used as base_asset in pools. + pub allow_as_base_asset: bool, +} + +#[derive( + Clone, + Copy, + Debug, + Decode, + Default, + Encode, + Eq, + MaxEncodedLen, + Ord, + PartialEq, + PartialOrd, + TypeInfo, +)] +pub struct XcmMetadata { + /// The factor used to determine the fee. + /// It is multiplied by the fee that would have been paid in native currency, so it represents + /// the ratio `native_price / other_asset_price`. It is a fixed point decimal number containing + /// as many fractional decimals as the asset it is used for contains. + /// Should be updated regularly. + pub fee_factor: Option, +} diff --git a/zrml/authorized/src/lib.rs b/zrml/authorized/src/lib.rs index e491fc75e..6270b9b4e 100644 --- a/zrml/authorized/src/lib.rs +++ b/zrml/authorized/src/lib.rs @@ -73,6 +73,7 @@ mod pallet { ::BlockNumber, MomentOf, Asset>, + MarketIdOf, >; #[pallet::call] @@ -371,11 +372,12 @@ where use frame_support::traits::Get; use sp_runtime::{traits::AccountIdConversion, Perbill}; use zeitgeist_primitives::types::{ - Asset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, - MarketStatus, MarketType, ScoringRule, + Asset, Deadlines, Market, MarketBonds, MarketCreation, MarketDisputeMechanism, + MarketPeriod, MarketStatus, MarketType, ScoringRule, }; - zeitgeist_primitives::types::Market { + Market { + market_id: Default::default(), base_asset: Asset::Ztg, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), diff --git a/zrml/authorized/src/mock.rs b/zrml/authorized/src/mock.rs index 556f969c2..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, Asset, Balance, 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, - Asset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/court/src/benchmarks.rs b/zrml/court/src/benchmarks.rs index 39a810659..9e65841d5 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: Asset::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 10cf08bcb..899e344d9 100644 --- a/zrml/court/src/lib.rs +++ b/zrml/court/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Forecasting Technologies LTD. +// Copyright 2022-2024 Forecasting Technologies LTD. // Copyright 2021-2022 Zeitgeist PM LLC. // // This file is part of Zeitgeist. @@ -220,6 +220,7 @@ mod pallet { ::BlockNumber, MomentOf, Asset>, + 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 7407eac52..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, Asset, Balance, 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, - Asset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/court/src/tests.rs b/zrml/court/src/tests.rs index 4592413e3..da30e2141 100644 --- a/zrml/court/src/tests.rs +++ b/zrml/court/src/tests.rs @@ -64,6 +64,7 @@ use zrml_market_commons::{Error as MError, MarketCommonsPalletApi}; const ORACLE_REPORT: OutcomeReport = OutcomeReport::Scalar(u128::MAX); const DEFAULT_MARKET: MarketOf = Market { + market_id: 0, base_asset: Asset::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 c49705d5f..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, Asset, Balance, 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, - Asset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/global-disputes/src/utils.rs b/zrml/global-disputes/src/utils.rs index e282ce2fd..967cb5303 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::Asset>, + 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::Asset::Ztg, creation: zeitgeist_primitives::types::MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(), diff --git a/zrml/liquidity-mining/src/tests.rs b/zrml/liquidity-mining/src/tests.rs index 420f0dae9..19d94980c 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: Asset::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 5fb809959..9d05179c2 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,7 +51,8 @@ mod pallet { }; use zeitgeist_primitives::{ math::checked_ops_res::CheckedAddRes, - types::{Asset, Market, PoolId}, + traits::MarketBuilderTrait, + types::{Asset, Deadlines, EarlyClose, Market, MarketBonds, MarketPeriod, PoolId, Report}, }; /// The current storage version. @@ -60,10 +62,21 @@ mod pallet { pub(crate) type AssetOf = Asset>; pub(crate) type BalanceOf = ::Balance; pub(crate) type BlockNumberOf = ::BlockNumber; - pub(crate) type MarketOf = - Market, BalanceOf, BlockNumberOf, MomentOf, AssetOf>; - pub type MarketIdOf = ::MarketId; - pub type MomentOf = <::Timestamp as frame_support::traits::Time>::Moment; + pub(crate) type MarketIdOf = ::MarketId; + pub(crate) type MarketOf = Market< + AccountIdOf, + BalanceOf, + BlockNumberOf, + MomentOf, + AssetOf, + 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 +118,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 +139,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 +190,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, + AssetOf, + 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/tests.rs b/zrml/market-commons/src/tests.rs index b9817d223..793559b44 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, Asset, Balance, BlockNumber, Deadlines, Market, MarketBonds, MarketCreation, - MarketDisputeMechanism, MarketId, MarketPeriod, MarketStatus, MarketType, Moment, - ScoringRule, + Asset, Deadlines, MarketBonds, MarketCreation, MarketDisputeMechanism, MarketPeriod, + MarketStatus, MarketType, ScoringRule, }, }; -const MARKET_DUMMY: Market> = Market { - base_asset: Asset::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::Lmsr, - 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(Asset::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::Lmsr) + .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,29 +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..dcf28a2c4 --- /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, AssetOf, 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::{ + 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, + AssetOf, + 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: AssetOf, + 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 fde8b8c01..e68d38f94 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -181,6 +181,7 @@ where T: Config, { let market = Market { + market_id: 0u8.into(), base_asset, creation: MarketCreation::Permissionless, creator_fee: Perbill::zero(), @@ -198,8 +199,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/orderbook/src/lib.rs b/zrml/orderbook/src/lib.rs index d6ab8b7c9..876e64bf3 100644 --- a/zrml/orderbook/src/lib.rs +++ b/zrml/orderbook/src/lib.rs @@ -111,6 +111,7 @@ mod pallet { ::BlockNumber, MomentOf, AssetOf, + MarketIdOf, >; #[pallet::pallet] diff --git a/zrml/orderbook/src/utils.rs b/zrml/orderbook/src/utils.rs index 3ec2fb88b..0dc465645 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. // @@ -24,19 +24,12 @@ use zeitgeist_primitives::types::{ MarketType, ScoringRule, }; -type MarketOf = Market< - ::AccountId, - BalanceOf, - ::BlockNumber, - MomentOf, - Asset>, ->; - pub(crate) fn market_mock() -> MarketOf where T: crate::Config, { Market { + market_id: Default::default(), base_asset: Asset::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 3bbcbd441..0b6554426 100644 --- a/zrml/parimutuel/src/lib.rs +++ b/zrml/parimutuel/src/lib.rs @@ -97,8 +97,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, Asset>>; + pub(crate) type MarketOf = Market< + AccountIdOf, + BalanceOf, + BlockNumberFor, + MomentOf, + Asset>, + MarketIdOf, + >; #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/zrml/parimutuel/src/utils.rs b/zrml/parimutuel/src/utils.rs index 3ac1f28cb..35b0422f6 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. // @@ -28,6 +28,7 @@ where }; zeitgeist_primitives::types::Market { + market_id: Default::default(), base_asset: Asset::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 fd5766af5..851b6f1df 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -65,7 +65,7 @@ mod pallet { constants::MILLISECS_PER_BLOCK, traits::{ CompleteSetOperationsApi, DeployPoolApi, DisputeApi, DisputeMaxWeightApi, - DisputeResolutionApi, ZeitgeistAssetManager, + DisputeResolutionApi, MarketBuilderTrait, ZeitgeistAssetManager, }, types::{ Asset, Bond, Deadlines, EarlyClose, EarlyCloseState, GlobalDisputeItem, Market, @@ -76,7 +76,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); @@ -90,7 +90,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>; @@ -98,16 +100,18 @@ mod pallet { pub(crate) type MarketOf = Market< AccountIdOf, BalanceOf, - ::BlockNumber, + BlockNumberOf, MomentOf, AssetOf, + 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 { @@ -596,8 +600,8 @@ mod pallet { base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -648,8 +652,8 @@ mod pallet { base_asset: AssetOf, market_id: MarketIdOf, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, market_type: MarketType, dispute_mechanism: Option, @@ -665,7 +669,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, @@ -681,6 +686,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(()) @@ -1061,8 +1067,8 @@ mod pallet { base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, market_type: MarketType, dispute_mechanism: Option, @@ -1139,25 +1145,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); @@ -2083,8 +2085,8 @@ mod pallet { base_asset: AssetOf, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -2104,7 +2106,8 @@ mod pallet { }, }; - let market = Self::construct_market( + let market_builder = Self::construct_market( + None, base_asset, who.clone(), creator_fee, @@ -2128,7 +2131,8 @@ 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); let ids_amount: u32 = Self::insert_auto_close(&market_id)?; @@ -2386,9 +2390,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. @@ -2423,7 +2425,7 @@ mod pallet { } fn ensure_market_deadlines_are_valid( - deadlines: &Deadlines, + deadlines: &DeadlinesOf, trusted: bool, ) -> DispatchResult { ensure!( @@ -2873,12 +2875,13 @@ mod pallet { } fn construct_market( + market_id: Option>, base_asset: AssetOf, creator: T::AccountId, creator_fee: Perbill, oracle: T::AccountId, - period: MarketPeriod>, - deadlines: Deadlines, + period: MarketPeriodOf, + deadlines: DeadlinesOf, metadata: MultiHash, creation: MarketCreation, market_type: MarketType, @@ -2887,7 +2890,7 @@ mod pallet { report: Option>, resolved_outcome: Option, bonds: MarketBondsOf, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { let valid_base_asset = match base_asset { Asset::Ztg => true, #[cfg(feature = "parachain")] @@ -2913,24 +2916,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/simple-disputes/src/lib.rs b/zrml/simple-disputes/src/lib.rs index c0466b4c7..c82889ca9 100644 --- a/zrml/simple-disputes/src/lib.rs +++ b/zrml/simple-disputes/src/lib.rs @@ -116,6 +116,7 @@ mod pallet { ::BlockNumber, MomentOf, Asset>, + 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: Asset::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 468589c52..4d1d40cdb 100644 --- a/zrml/simple-disputes/src/mock.rs +++ b/zrml/simple-disputes/src/mock.rs @@ -33,10 +33,10 @@ use zeitgeist_primitives::{ BlockHashCount, ExistentialDeposits, GetNativeCurrencyId, MaxDisputes, MaxReserves, MinimumPeriod, OutcomeBond, OutcomeFactor, SimpleDisputesPalletId, BASE, }, - traits::DisputeResolutionApi, + traits::{DisputeResolutionApi, MarketOfDisputeResolutionApi}, types::{ - AccountIdTest, Amount, Asset, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, - CurrencyId, Hash, Index, Market, MarketId, Moment, UncheckedExtrinsicTest, + AccountIdTest, Amount, Balance, BasicCurrencyAdapter, BlockNumber, BlockTest, CurrencyId, + 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, - Asset, - >, + _market: &MarketOfDisputeResolutionApi, ) -> Result { Ok(Weight::zero()) } diff --git a/zrml/simple-disputes/src/tests.rs b/zrml/simple-disputes/src/tests.rs index 03ee1e578..f4b90915f 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: Asset::Ztg, creation: MarketCreation::Permissionless, creator_fee: sp_runtime::Perbill::zero(),