diff --git a/Cargo.toml b/Cargo.toml index eac6a6327..13d473b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -291,6 +291,15 @@ rand_chacha = { version = "0.3.1", default-features = false } serde = { version = "1.0.198", default-features = false } typenum = { version = "1.17.0", default-features = false } +[profile.test] +overflow-checks = true + +[profile.test.package."*"] +overflow-checks = true + +[profile.dev] +overflow-checks = true + [profile.dev.package] blake2 = { opt-level = 3 } blake2b_simd = { opt-level = 3 } @@ -336,17 +345,28 @@ x25519-dalek = { opt-level = 3 } yamux = { opt-level = 3 } zeroize = { opt-level = 3 } +[profile.dev.package."*"] +overflow-checks = true + [profile.production] codegen-units = 1 incremental = false inherits = "release" lto = true +overflow-checks = true + +[profile.production.package."*"] +overflow-checks = true [profile.release] opt-level = 3 +overflow-checks = true # Zeitgeist runtime requires unwinding. panic = "unwind" +[profile.release.package."*"] +overflow-checks = true + # xcm-emulator incompatible block number type fixed # Commits: diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index dbd22367d..df684894e 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -48,14 +48,14 @@ use serde::{Deserialize, Serialize}; pub enum Asset { CategoricalOutcome(MarketId, CategoryIndex), ScalarOutcome(MarketId, ScalarPosition), - CombinatorialToken(CombinatorialId), + CombinatorialOutcomeLegacy, // Here to avoid having to migrate all holdings on the chain. PoolShare(PoolId), #[default] Ztg, ForeignAsset(u32), ParimutuelShare(MarketId, CategoryIndex), + CombinatorialToken(CombinatorialId), } -// TODO Needs storage migration #[cfg(feature = "runtime-benchmarks")] impl ZeitgeistAssetEnumerator for Asset { diff --git a/primitives/src/math/checked_ops_res.rs b/primitives/src/math/checked_ops_res.rs index 13cd420a6..38e971031 100644 --- a/primitives/src/math/checked_ops_res.rs +++ b/primitives/src/math/checked_ops_res.rs @@ -64,6 +64,13 @@ where fn checked_rem_res(&self, other: &Self) -> Result; } +pub trait CheckedIncRes +where + Self: Sized, +{ + fn checked_inc_res(&self) -> Result; +} + impl CheckedAddRes for T where T: CheckedAdd, @@ -123,3 +130,13 @@ where self.checked_rem(other).ok_or(DispatchError::Arithmetic(ArithmeticError::DivisionByZero)) } } + +impl CheckedIncRes for T +where + T: CheckedAdd + From, +{ + #[inline] + fn checked_inc_res(&self) -> Result { + self.checked_add(&1u8.into()).ok_or(DispatchError::Arithmetic(ArithmeticError::Overflow)) + } +} diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index cd36b113e..d141610f1 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -18,6 +18,7 @@ mod combinatorial_tokens_api; mod combinatorial_tokens_benchmark_helper; +mod combinatorial_tokens_fuel; mod combinatorial_tokens_unsafe_api; mod complete_set_operations_api; mod deploy_pool_api; @@ -36,6 +37,7 @@ mod zeitgeist_asset; pub use combinatorial_tokens_api::*; pub use combinatorial_tokens_benchmark_helper::*; +pub use combinatorial_tokens_fuel::*; pub use combinatorial_tokens_unsafe_api::*; pub use complete_set_operations_api::*; pub use deploy_pool_api::*; diff --git a/primitives/src/traits/combinatorial_tokens_api.rs b/primitives/src/traits/combinatorial_tokens_api.rs index 33230d703..c9953feb4 100644 --- a/primitives/src/traits/combinatorial_tokens_api.rs +++ b/primitives/src/traits/combinatorial_tokens_api.rs @@ -15,8 +15,11 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::types::SplitPositionDispatchInfo; +use crate::{traits::CombinatorialTokensFuel, types::SplitPositionDispatchInfo}; use alloc::vec::Vec; +use core::fmt::Debug; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; use sp_runtime::DispatchError; pub trait CombinatorialTokensApi { @@ -24,6 +27,7 @@ pub trait CombinatorialTokensApi { type Balance; type CombinatorialId; type MarketId; + type Fuel: Clone + CombinatorialTokensFuel + Debug + Decode + Encode + Eq + TypeInfo; fn split_position( who: Self::AccountId, @@ -31,6 +35,6 @@ pub trait CombinatorialTokensApi { market_id: Self::MarketId, partition: Vec>, amount: Self::Balance, - force_max_work: bool, + force_max_work: Self::Fuel, ) -> Result, DispatchError>; } diff --git a/primitives/src/traits/combinatorial_tokens_fuel.rs b/primitives/src/traits/combinatorial_tokens_fuel.rs new file mode 100644 index 000000000..8fc952a23 --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_fuel.rs @@ -0,0 +1,10 @@ +/// A trait for keeping track of a certain amount of work to be done. +pub trait CombinatorialTokensFuel { + /// Creates a `Fuel` object from a `total` value which indicates the total amount of work to be + /// done. This is usually done for benchmarking purposes. + fn from_total(total: u32) -> Self; + + /// Returns a `u32` which indicates the total amount of work to be done. Must be `O(1)` to avoid + /// excessive calculation if this call is used when calculating extrinsic weight. + fn total(&self) -> u32; +} diff --git a/primitives/src/traits/futarchy_oracle.rs b/primitives/src/traits/futarchy_oracle.rs index 0a4530a6d..37b5b3b17 100644 --- a/primitives/src/traits/futarchy_oracle.rs +++ b/primitives/src/traits/futarchy_oracle.rs @@ -18,7 +18,12 @@ use frame_support::pallet_prelude::Weight; pub trait FutarchyOracle { + type BlockNumber; + /// Evaluates the query at the current block and returns the weight consumed and a `bool` /// indicating whether the query evaluated positively. fn evaluate(&self) -> (Weight, bool); + + /// Updates the oracle's data and returns the weight consumed. + fn update(&mut self, now: Self::BlockNumber) -> Weight; } diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index 3c1757791..cf2d83d1d 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -165,9 +165,10 @@ parameter_types! { /// can lead to extrinsic with very big weight: see delegate for instance. pub const MaxVotes: u32 = 100; /// The maximum number of public proposals that can exist at any time. - pub const MaxProposals: u32 = 100; + pub const DemocracyMaxProposals: u32 = 100; // Futarchy + pub const FutarchyMaxProposals: u32 = 4; pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; // Hybrid Router parameters @@ -456,6 +457,7 @@ parameter_type_with_key! { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), Asset::CombinatorialToken(_) => ExistentialDeposit::get(), + Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 2b26b806a..09d672b33 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -84,7 +84,7 @@ macro_rules! decl_common_types { generic, DispatchError, DispatchResult, RuntimeDebug, SaturatedConversion, }; use zeitgeist_primitives::traits::{DeployPoolApi, DistributeFees, MarketCommonsPalletApi}; - use zrml_combinatorial_tokens::types::CryptographicIdManager; + use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; use zrml_neo_swaps::types::DecisionMarketOracle; #[cfg(feature = "try-runtime")] @@ -815,7 +815,7 @@ macro_rules! impl_config_traits { type PalletsOrigin = OriginCaller; type MaxVotes = MaxVotes; type WeightInfo = weights::pallet_democracy::WeightInfo; - type MaxProposals = MaxProposals; + type MaxProposals = DemocracyMaxProposals; type Preimages = Preimage; type MaxBlacklisted = ConstU32<100>; type MaxDeposits = ConstU32<100>; @@ -1160,6 +1160,7 @@ macro_rules! impl_config_traits { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = PredictionMarketsCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; @@ -1198,11 +1199,11 @@ macro_rules! impl_config_traits { impl zrml_futarchy::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = DecisionMarketBenchmarkHelper; + type MaxProposals = FutarchyMaxProposals; type MinDuration = MinDuration; type Oracle = DecisionMarketOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = Scheduler; - type SubmitOrigin = EnsureRoot; type WeightInfo = zrml_futarchy::weights::WeightInfo; } @@ -2286,7 +2287,7 @@ macro_rules! create_common_tests { }; use zrml_futarchy::types::Proposal; use zrml_market_commons::types::MarketBuilder; - use zrml_neo_swaps::types::DecisionMarketOracle; + use zrml_neo_swaps::types::{DecisionMarketOracle, DecisionMarketOracleScoreboard}; #[test] fn futarchy_schedules_and_executes_call() { @@ -2360,10 +2361,13 @@ macro_rules! create_common_tests { }; let call = Preimage::bound(RuntimeCall::from(remark_dispatched_as)).unwrap(); + let scoreboard = + DecisionMarketOracleScoreboard::new(40_000, 10_000, one / 7, one); let oracle = DecisionMarketOracle::new( market_id, Asset::CategoricalOutcome(market_id, 0), Asset::CategoricalOutcome(market_id, 1), + scoreboard, ); let when = duration + 10; let proposal = Proposal { when, call, oracle }; diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 8f89dc422..0888ba680 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -165,9 +165,10 @@ parameter_types! { /// can lead to extrinsic with very big weight: see delegate for instance. pub const MaxVotes: u32 = 100; /// The maximum number of public proposals that can exist at any time. - pub const MaxProposals: u32 = 100; + pub const DemocracyMaxProposals: u32 = 100; // Futarchy + pub const FutarchyMaxProposals: u32 = 4; pub const MinDuration: BlockNumber = 7 * BLOCKS_PER_DAY; // Hybrid Router parameters @@ -456,6 +457,7 @@ parameter_type_with_key! { match currency_id { Asset::CategoricalOutcome(_,_) => ExistentialDeposit::get(), Asset::CombinatorialToken(_) => ExistentialDeposit::get(), + Asset::CombinatorialOutcomeLegacy => ExistentialDeposit::get(), Asset::PoolShare(_) => ExistentialDeposit::get(), Asset::ScalarOutcome(_,_) => ExistentialDeposit::get(), #[cfg(feature = "parachain")] diff --git a/zrml/combinatorial-tokens/src/benchmarking.rs b/zrml/combinatorial-tokens/src/benchmarking.rs index 901821fb1..4d2bf4093 100644 --- a/zrml/combinatorial-tokens/src/benchmarking.rs +++ b/zrml/combinatorial-tokens/src/benchmarking.rs @@ -26,7 +26,7 @@ use orml_traits::MultiCurrency; use sp_runtime::{traits::Zero, Perbill}; use zeitgeist_primitives::{ math::fixed::{BaseProvider, ZeitgeistBase}, - traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + traits::{CombinatorialTokensBenchmarkHelper, CombinatorialTokensFuel, MarketCommonsPalletApi}, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; @@ -65,10 +65,11 @@ mod benchmarks { use super::*; #[benchmark] - fn split_position_vertical_sans_parent(n: Linear<2, 32>) { + fn split_position_vertical_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); @@ -92,7 +93,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -103,7 +104,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -129,10 +130,11 @@ mod benchmarks { } #[benchmark] - fn split_position_vertical_with_parent(n: Linear<2, 32>) { + fn split_position_vertical_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let parent_market_id = create_market::(alice.clone(), 2); @@ -142,7 +144,7 @@ mod benchmarks { parent_collection_id, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -167,7 +169,7 @@ mod benchmarks { child_market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -178,7 +180,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -204,11 +206,12 @@ mod benchmarks { } #[benchmark] - fn split_position_horizontal(n: Linear<2, 32>) { + fn split_position_horizontal(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); let asset_count = position_count + 1; + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); @@ -230,7 +233,7 @@ mod benchmarks { parent_collection_id, market_id, asset_in_index_set, - false, + T::Fuel::from_total(total), ) .unwrap(); T::MultiCurrency::deposit(asset_in, &alice, amount).unwrap(); @@ -242,7 +245,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let collection_ids: Vec<_> = partition @@ -253,7 +256,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -279,10 +282,11 @@ mod benchmarks { } #[benchmark] - fn merge_position_vertical_sans_parent(n: Linear<2, 32>) { + fn merge_position_vertical_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); @@ -304,7 +308,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -322,7 +326,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -338,10 +342,11 @@ mod benchmarks { } #[benchmark] - fn merge_position_vertical_with_parent(n: Linear<2, 32>) { + fn merge_position_vertical_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); + let total = m; let parent_collection_id = None; let parent_market_id = create_market::(alice.clone(), 2); @@ -351,7 +356,7 @@ mod benchmarks { parent_collection_id, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -375,7 +380,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -392,7 +397,7 @@ mod benchmarks { child_market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -408,11 +413,12 @@ mod benchmarks { } #[benchmark] - fn merge_position_horizontal(n: Linear<2, 32>) { + fn merge_position_horizontal(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let position_count: usize = n.try_into().unwrap(); let asset_count = position_count + 1; + let total = m; let parent_collection_id = None; let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); @@ -435,7 +441,7 @@ mod benchmarks { parent_collection_id, market_id, index_set, - false, + T::Fuel::from_total(total), ) .unwrap() }) @@ -452,7 +458,7 @@ mod benchmarks { market_id, partition.clone(), amount, - true, + T::Fuel::from_total(total), ); let mut asset_out_index_set = vec![true; asset_count]; @@ -461,7 +467,7 @@ mod benchmarks { parent_collection_id, market_id, asset_out_index_set, - false, + T::Fuel::from_total(total), ) .unwrap(); let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { @@ -477,11 +483,12 @@ mod benchmarks { } #[benchmark] - fn redeem_position_sans_parent(n: Linear<2, 32>) { + fn redeem_position_sans_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let n_u16: u16 = n.try_into().unwrap(); let asset_count = n_u16 + 1; + let total = m; // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` // loop in `redeem_position`. @@ -499,7 +506,7 @@ mod benchmarks { parent_collection_id, market_id, index_set.clone(), - false, + T::Fuel::from_total(total), ) .unwrap(); let amount = ZeitgeistBase::get().unwrap(); @@ -512,7 +519,7 @@ mod benchmarks { parent_collection_id, market_id, index_set.clone(), - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { @@ -529,11 +536,12 @@ mod benchmarks { } #[benchmark] - fn redeem_position_with_parent(n: Linear<2, 32>) { + fn redeem_position_with_parent(n: Linear<2, 32>, m: Linear<32, 64>) { let alice: T::AccountId = whitelisted_caller(); let n_u16: u16 = n.try_into().unwrap(); let asset_count = n_u16 + 1; + let total = m; // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` // loop in `redeem_position`. @@ -545,7 +553,7 @@ mod benchmarks { None, parent_market_id, vec![false, true], - false, + T::Fuel::from_total(total), ) .unwrap(); let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); @@ -555,7 +563,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set.clone(), - false, + T::Fuel::from_total(total), ) .unwrap(); let amount = ZeitgeistBase::get().unwrap(); @@ -570,7 +578,7 @@ mod benchmarks { Some(cid_01), child_market_id, index_set.clone(), - true, + T::Fuel::from_total(total), ); let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 7feb249b3..7e9a7ae8d 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -42,7 +42,7 @@ mod pallet { traits::CombinatorialIdManager, types::TransmutationType, weights::WeightInfoZeitgeist, }; use alloc::{vec, vec::Vec}; - use core::marker::PhantomData; + use core::{fmt::Debug, marker::PhantomData}; use frame_support::{ ensure, pallet_prelude::{DispatchResultWithPostInfo, IsType, StorageVersion}, @@ -53,6 +53,8 @@ mod pallet { pallet_prelude::{BlockNumberFor, OriginFor}, }; use orml_traits::MultiCurrency; + use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, Get, Zero}, DispatchError, DispatchResult, SaturatedConversion, @@ -60,7 +62,8 @@ mod pallet { use zeitgeist_primitives::{ math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, traits::{ - CombinatorialTokensApi, CombinatorialTokensUnsafeApi, MarketCommonsPalletApi, PayoutApi, + CombinatorialTokensApi, CombinatorialTokensFuel, CombinatorialTokensUnsafeApi, + MarketCommonsPalletApi, PayoutApi, }, types::{Asset, CombinatorialId, SplitPositionDispatchInfo}, }; @@ -80,8 +83,19 @@ mod pallet { Asset = AssetOf, MarketId = MarketIdOf, CombinatorialId = CombinatorialId, + Fuel = Self::Fuel, >; + type Fuel: Clone + + CombinatorialTokensFuel + + Debug + + Decode + + Encode + + Eq + + MaxEncodedLen + + PartialEq + + TypeInfo; + type MarketCommons: MarketCommonsPalletApi>; type MultiCurrency: MultiCurrency>; @@ -106,6 +120,8 @@ mod pallet { <::MultiCurrency as MultiCurrency>>::Balance; pub(crate) type CombinatorialIdOf = <::CombinatorialIdManager as CombinatorialIdManager>::CombinatorialId; + pub(crate) type FuelOf = + <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type SplitPositionDispatchInfoOf = @@ -197,21 +213,27 @@ mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight( - T::WeightInfo::split_position_vertical_sans_parent(partition.len().saturated_into()) - .max(T::WeightInfo::split_position_vertical_with_parent( - partition.len().saturated_into(), - )) - .max(T::WeightInfo::split_position_horizontal(partition.len().saturated_into())) + T::WeightInfo::split_position_vertical_sans_parent( + partition.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::split_position_vertical_with_parent( + partition.len().saturated_into(), + fuel.total(), + )) + .max(T::WeightInfo::split_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + )) )] #[transactional] pub fn split_position( origin: OriginFor, - // TODO Abstract this into a separate type. parent_collection_id: Option>, market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -221,7 +243,7 @@ mod pallet { market_id, partition, amount, - force_max_work, + fuel, )?; DispatchResultWithPostInfo::Ok(post_dispatch_info) @@ -229,11 +251,18 @@ mod pallet { #[pallet::call_index(1)] #[pallet::weight( - T::WeightInfo::merge_position_vertical_sans_parent(partition.len().saturated_into()) - .max(T::WeightInfo::merge_position_vertical_with_parent( - partition.len().saturated_into(), - )) - .max(T::WeightInfo::merge_position_horizontal(partition.len().saturated_into())) + T::WeightInfo::merge_position_vertical_sans_parent( + partition.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::merge_position_vertical_with_parent( + partition.len().saturated_into(), + fuel.total(), + )) + .max(T::WeightInfo::merge_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + )) )] #[transactional] pub fn merge_position( @@ -242,23 +271,22 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_merge_position( - who, - parent_collection_id, - market_id, - partition, - amount, - force_max_work, - ) + Self::do_merge_position(who, parent_collection_id, market_id, partition, amount, fuel) } #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()) - .max(T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into())) + T::WeightInfo::redeem_position_with_parent( + index_set.len().saturated_into(), + fuel.total(), + ) + .max(T::WeightInfo::redeem_position_sans_parent( + index_set.len().saturated_into(), + fuel.total() + )) )] #[transactional] pub fn redeem_position( @@ -266,16 +294,10 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_redeem_position( - who, - parent_collection_id, - market_id, - index_set, - force_max_work, - ) + Self::do_redeem_position(who, parent_collection_id, market_id, index_set, fuel) } } @@ -287,13 +309,13 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { let (transmutation_type, position) = Self::transmutation_asset( parent_collection_id, market_id, partition.clone(), - force_max_work, + fuel.clone(), )?; // Destroy the token to be split. @@ -301,12 +323,13 @@ mod pallet { TransmutationType::VerticalWithParent => { // Split combinatorial token into higher level position. // This will fail if the market has a different collateral than the previous - // markets. FIXME A cleaner error message would be nice though... + // markets. T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; T::WeightInfo::split_position_vertical_with_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::VerticalSansParent => { @@ -317,6 +340,7 @@ mod pallet { T::WeightInfo::split_position_vertical_sans_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::Horizontal => { @@ -324,7 +348,10 @@ mod pallet { T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - T::WeightInfo::split_position_horizontal(partition.len().saturated_into()) + T::WeightInfo::split_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + ) } }; @@ -337,7 +364,7 @@ mod pallet { parent_collection_id, market_id, index_set, - force_max_work, + fuel.clone(), ) }) .collect::, _>>()?; @@ -379,13 +406,13 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let (transmutation_type, position) = Self::transmutation_asset( parent_collection_id, market_id, partition.clone(), - force_max_work, + fuel.clone(), )?; // Destroy the old tokens. @@ -397,7 +424,7 @@ mod pallet { parent_collection_id, market_id, index_set, - force_max_work, + fuel.clone(), ) }) .collect::, _>>()?; @@ -414,6 +441,7 @@ mod pallet { T::WeightInfo::merge_position_vertical_with_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::VerticalSansParent => { @@ -423,13 +451,17 @@ mod pallet { T::WeightInfo::merge_position_vertical_sans_parent( partition.len().saturated_into(), + fuel.total(), ) } TransmutationType::Horizontal => { // Horizontal merge. T::MultiCurrency::deposit(position, &who, amount)?; - T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()) + T::WeightInfo::merge_position_horizontal( + partition.len().saturated_into(), + fuel.total(), + ) } }; @@ -451,7 +483,7 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResultWithPostInfo { let payout_vector = T::Payout::payout_vector(market_id).ok_or(Error::::PayoutVectorNotFound)?; @@ -480,7 +512,7 @@ mod pallet { parent_collection_id, market_id, index_set.clone(), - force_max_work, + fuel.clone(), )?; let amount = T::MultiCurrency::free_balance(position, &who); ensure!(!amount.is_zero(), Error::::NoTokensFound); @@ -494,8 +526,10 @@ mod pallet { let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, total_payout)?; - let weight = - T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into()); + let weight = T::WeightInfo::redeem_position_with_parent( + index_set.len().saturated_into(), + fuel.total(), + ); (weight, position) } else { @@ -506,8 +540,10 @@ mod pallet { total_payout, )?; - let weight = - T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into()); + let weight = T::WeightInfo::redeem_position_sans_parent( + index_set.len().saturated_into(), + fuel.total(), + ); (weight, collateral_token) }; @@ -563,7 +599,7 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, partition: Vec>, - force_max_work: bool, + fuel: FuelOf, ) -> Result<(TransmutationType, AssetOf), DispatchError> { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; @@ -586,7 +622,7 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, - force_max_work, + fuel, )?; (TransmutationType::Horizontal, position) @@ -599,13 +635,13 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { T::CombinatorialIdManager::get_collection_id( parent_collection_id, market_id, index_set, - force_max_work, + fuel, ) .ok_or(Error::::InvalidCollectionId.into()) } @@ -628,13 +664,13 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - force_max_work: bool, + fuel: FuelOf, ) -> Result, DispatchError> { let collection_id = Self::collection_id_from_parent_collection( parent_collection_id, market_id, index_set, - force_max_work, + fuel, )?; Self::position_from_collection_id(market_id, collection_id) @@ -649,6 +685,7 @@ mod pallet { type Balance = BalanceOf; type CombinatorialId = CombinatorialIdOf; type MarketId = MarketIdOf; + type Fuel = <::CombinatorialIdManager as CombinatorialIdManager>::Fuel; fn split_position( who: Self::AccountId, @@ -656,16 +693,9 @@ mod pallet { market_id: Self::MarketId, partition: Vec>, amount: Self::Balance, - force_max_work: bool, + fuel: Self::Fuel, ) -> Result, DispatchError> { - Self::do_split_position( - who, - parent_collection_id, - market_id, - partition, - amount, - force_max_work, - ) + Self::do_split_position(who, parent_collection_id, market_id, partition, amount, fuel) } } diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index ede07e829..ff39e3832 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -16,7 +16,11 @@ // along with Zeitgeist. If not, see . use crate as zrml_combinatorial_tokens; -use crate::{mock::types::MockPayout, types::CryptographicIdManager, weights::WeightInfo}; +use crate::{ + mock::types::MockPayout, + types::{cryptographic_id_manager::Fuel, CryptographicIdManager}, + weights::WeightInfo, +}; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -49,6 +53,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = BenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = Currencies; type Payout = MockPayout; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index 83a5162bd..7b6b5880a 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -42,7 +42,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(Asset::Ztg), _99); assert_eq!(alice.free_balance(ct_001), _1); @@ -55,7 +55,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(Asset::Ztg), _100); assert_eq!(alice.free_balance(ct_001), 0); @@ -96,7 +96,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition.clone(), parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -113,7 +113,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition.clone(), child_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -128,7 +128,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition, child_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -143,7 +143,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition, parent_amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); @@ -225,7 +225,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, - false, + Fuel::new(16, false), )); // Split C into C&(U|V) and C&(W|X). @@ -235,7 +235,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, - false, + Fuel::new(16, false), )); // Split A|B into into (A|B)&(U|V) and (A|B)&(W|X). @@ -245,7 +245,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -265,7 +265,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -285,7 +285,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -305,7 +305,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1, amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -337,7 +337,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -345,7 +345,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); let ct_001 = CombinatorialToken([ @@ -372,7 +372,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0], vec![B0, B0, B1]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 2 * amount); @@ -398,7 +398,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -441,7 +441,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B0, B0, B1, B1], vec![B1, B1, B0, B0]], child_amount_first_pass, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -449,7 +449,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0]], child_amount_first_pass, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount_first_pass); @@ -469,7 +469,7 @@ fn split_vertical_followed_by_horizontal_split_with_parent() { child_market_id, vec![vec![B1, B0, B0, B0], vec![B0, B1, B0, B0], vec![B0, B0, B1, B1]], child_amount_second_pass, - false, + Fuel::new(16, false), )); let total_child_amount = child_amount_first_pass + child_amount_second_pass; @@ -507,7 +507,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::split_position( @@ -516,7 +516,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_ok!(CombinatorialTokens::merge_position( @@ -525,7 +525,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), _1); diff --git a/zrml/combinatorial-tokens/src/tests/merge_position.rs b/zrml/combinatorial-tokens/src/tests/merge_position.rs index 077685f29..853036df4 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -50,7 +50,7 @@ fn merge_position_works_no_parent( market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), 0); @@ -111,7 +111,7 @@ fn merge_position_works_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001), amount); @@ -160,7 +160,7 @@ fn merge_position_horizontal_works() { market_id, vec![vec![B0, B1, B0], vec![B1, B0, B0]], amount, - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_110), amount); @@ -181,7 +181,7 @@ fn merge_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -204,7 +204,7 @@ fn merge_position_fails_on_invalid_partition_length() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -227,7 +227,7 @@ fn merge_position_fails_on_trivial_partition_member() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -250,7 +250,7 @@ fn merge_position_fails_on_overlapping_partition_members() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -272,7 +272,7 @@ fn merge_position_fails_on_insufficient_funds() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -294,7 +294,7 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); diff --git a/zrml/combinatorial-tokens/src/tests/mod.rs b/zrml/combinatorial-tokens/src/tests/mod.rs index ebcb3d751..8b1fb8469 100644 --- a/zrml/combinatorial-tokens/src/tests/mod.rs +++ b/zrml/combinatorial-tokens/src/tests/mod.rs @@ -28,6 +28,7 @@ use crate::{ runtime::{CombinatorialTokens, Currencies, MarketCommons, Runtime, RuntimeOrigin, System}, types::MockPayout, }, + types::cryptographic_id_manager::Fuel, Error, Event, Pallet, }; use frame_support::{assert_noop, assert_ok}; diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index 64a0d9944..ffabc600d 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -25,7 +25,13 @@ fn redeem_position_fails_on_no_payout_vector() { let market_id = 0; MockPayout::set_return_value(None); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, vec![], false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + vec![], + Fuel::new(16, false) + ), Error::::PayoutVectorNotFound ); assert!(MockPayout::called_once_with(market_id)); @@ -38,7 +44,13 @@ fn redeem_position_fails_on_market_not_found() { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![_1_2, _1_2])); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![], false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + 0, + vec![], + Fuel::new(16, false) + ), zrml_market_commons::Error::::MarketDoesNotExist ); }); @@ -46,14 +58,20 @@ fn redeem_position_fails_on_market_not_found() { #[test_case(vec![B0, B1, B0, B1]; "incorrect_len")] #[test_case(vec![B0, B0, B0]; "all_zero")] -#[test_case(vec![B0, B0, B0]; "all_one")] +#[test_case(vec![B1, B1, B1]; "all_one")] fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); MockPayout::set_return_value(Some(vec![_1_3, _1_3, _1_3])); let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::InvalidIndexSet ); }); @@ -67,7 +85,13 @@ fn redeem_position_fails_if_tokens_have_to_value() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B1, B0, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::TokenHasNoValue ); }); @@ -81,7 +105,13 @@ fn redeem_position_fails_if_user_holds_no_winning_tokens() { let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); let index_set = vec![B0, B1, B0, B1]; assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), + CombinatorialTokens::redeem_position( + alice.signed(), + None, + market_id, + index_set, + Fuel::new(16, false) + ), Error::::NoTokensFound, ); }); @@ -109,7 +139,7 @@ fn redeem_position_works_sans_parent() { parent_collection_id, market_id, index_set.clone(), - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_110), 0); @@ -166,7 +196,7 @@ fn redeem_position_works_with_parent() { parent_collection_id, market_id, index_set.clone(), - false, + Fuel::new(16, false), )); assert_eq!(alice.free_balance(ct_001_0101), 0); diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 88873e02b..1b16a7ab4 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -34,7 +34,7 @@ fn split_position_works_vertical_no_parent() { market_id, partition.clone(), amount, - false, + Fuel::new(16, false), )); let ct_001 = CombinatorialToken([ @@ -90,7 +90,7 @@ fn split_position_works_vertical_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, - false, + Fuel::new(16, false), )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -107,7 +107,7 @@ fn split_position_works_vertical_with_parent() { child_market_id, partition.clone(), child_amount, - false, + Fuel::new(16, false), )); // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the @@ -183,7 +183,7 @@ fn split_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -206,7 +206,7 @@ fn split_position_fails_on_invalid_partition_length() { market_id, partition, _1, - false, + Fuel::new(16, false), ), Error::::InvalidPartition ); @@ -229,7 +229,7 @@ fn split_position_fails_on_empty_partition_member() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -252,7 +252,7 @@ fn split_position_fails_on_overlapping_partition_members() { market_id, partition, _1, - false, + Fuel::new(16, false), ), Error::::InvalidPartition ); @@ -274,7 +274,7 @@ fn split_position_fails_on_trivial_partition() { market_id, partition, _1, - false + Fuel::new(16, false) ), Error::::InvalidPartition ); @@ -296,7 +296,7 @@ fn split_position_fails_on_insufficient_funds_native_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_currencies::Error::::BalanceTooLow ); @@ -318,7 +318,7 @@ fn split_position_fails_on_insufficient_funds_foreign_token_no_parent() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_currencies::Error::::BalanceTooLow ); @@ -351,7 +351,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -363,7 +363,7 @@ fn split_position_vertical_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B1, B0], vec![B0, B1, B0, B1]], _99, - false, + Fuel::new(16, false), )); }); } @@ -388,7 +388,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _100, - false, + Fuel::new(16, false), ), orml_tokens::Error::::BalanceTooLow ); @@ -400,7 +400,7 @@ fn split_position_horizontal_fails_on_insufficient_funds_combinatorial_token() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], _99, - false, + Fuel::new(16, false), )); }); } diff --git a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs index 547d2f9d8..abf23ffff 100644 --- a/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs +++ b/zrml/combinatorial-tokens/src/traits/combinatorial_id_manager.rs @@ -28,13 +28,18 @@ pub trait CombinatorialIdManager { type Asset; type MarketId; type CombinatorialId; + type Fuel; - // TODO Replace `Vec` with a more effective bit mask type. + /// Calculate the collection ID obtained when splitting `parent_collection_id` over the market + /// given by `market_id` and the `index_set`. + /// + /// The `fuel` parameter specifies how much work the function will do and can be used for + /// benchmarking purposes. fn get_collection_id( parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, - force_max_work: bool, + fuel: Self::Fuel, ) -> Option; fn get_position_id( diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs index f5f5d0a6f..236e2b821 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/decompressor/mod.rs @@ -26,19 +26,20 @@ mod tests; +use crate::types::cryptographic_id_manager::Fuel; use ark_bn254::{g1::G1Affine, Fq}; use ark_ff::{BigInteger, PrimeField}; use core::ops::Neg; use sp_runtime::traits::{One, Zero}; -use zeitgeist_primitives::types::CombinatorialId; +use zeitgeist_primitives::{traits::CombinatorialTokensFuel, types::CombinatorialId}; /// Will return `None` if and only if `parent_collection_id` is not a valid collection ID. pub(crate) fn get_collection_id( hash: CombinatorialId, parent_collection_id: Option, - force_max_work: bool, + fuel: Fuel, ) -> Option { - let mut u = decompress_hash(hash, force_max_work)?; + let mut u = decompress_hash(hash, fuel)?; if let Some(pci) = parent_collection_id { let v = decompress_collection_id(pci)?; @@ -57,17 +58,15 @@ pub(crate) fn get_collection_id( Some(bytes) } -const DECOMPRESS_HASH_MAX_ITERS: usize = 32; - /// Decompresses a collection ID `hash` to a point of `alt_bn128`. The amount of work done can be -/// forced to be independent of the input by setting the `force_max_work` flag. +/// controlled using the `fuel` parameter. /// /// We don't have mathematical proof that the points of `alt_bn128` are distributed so that the /// required number of iterations is below the specified limit of iterations, but there's good -/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. -/// -/// Provided the assumption above is correct, this function cannot return `None`. -fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option { +/// evidence that input hash requires more than `log_2(P) = 507.19338271000436` iterations. With a +/// `fuel.total` value of `32`, statistical evidence suggests a 1 in 500_000_000 chance that the +/// number of iterations will not be enough. +fn decompress_hash(hash: CombinatorialId, fuel: Fuel) -> Option { // Calculate `odd` first, then get congruent point `x` in `Fq`. As `hash` might represent a // larger big endian number than `field_modulus()`, the MSB of `x` might be different from the // MSB of `x_u256`. @@ -77,7 +76,7 @@ fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option Option, - #[values(false, true)] force_max_work: bool, + #[values(false, true)] consume_all: bool, #[case] expected: Option, ) { - assert_eq!(get_collection_id(hash, parent_collection_id, force_max_work), expected); + let actual = get_collection_id(hash, parent_collection_id, Fuel { total: 16, consume_all }); + assert_eq!(actual, expected); } diff --git a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs index 087498fce..54343f14e 100644 --- a/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs +++ b/zrml/combinatorial-tokens/src/types/cryptographic_id_manager/mod.rs @@ -29,8 +29,42 @@ use crate::traits::CombinatorialIdManager; use alloc::vec::Vec; use core::marker::PhantomData; use hash_tuple::{HashTuple, ToBytes}; -use parity_scale_codec::Encode; -use zeitgeist_primitives::types::{Asset, CombinatorialId}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use zeitgeist_primitives::{ + traits::CombinatorialTokensFuel, + types::{Asset, CombinatorialId}, +}; + +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct Fuel { + /// The maximum number of iterations to perform in the main loop of `get_collection_id`. + total: u32, + + /// Perform `self.total` of iterations in the main loop of `get_collection_id`. Useful for + /// benchmarking purposes and should probably not be used in production. + consume_all: bool, +} + +impl Fuel { + pub fn new(total: u32, consume_all: bool) -> Self { + Fuel { total, consume_all } + } + + pub fn consume_all(&self) -> bool { + self.consume_all + } +} + +impl CombinatorialTokensFuel for Fuel { + fn from_total(total: u32) -> Fuel { + Fuel { total, consume_all: true } + } + + fn total(&self) -> u32 { + self.total + } +} pub struct CryptographicIdManager(PhantomData<(MarketId, Hasher)>); @@ -42,16 +76,18 @@ where type Asset = Asset; type CombinatorialId = CombinatorialId; type MarketId = MarketId; + type Fuel = Fuel; fn get_collection_id( parent_collection_id: Option, market_id: Self::MarketId, index_set: Vec, - force_max_work: bool, + fuel: Self::Fuel, ) -> Option { let input = (market_id, index_set); let hash = Hasher::hash_tuple(input); - decompressor::get_collection_id(hash, parent_collection_id, force_max_work) + + decompressor::get_collection_id(hash, parent_collection_id, fuel) } fn get_position_id( @@ -59,6 +95,7 @@ where collection_id: Self::CombinatorialId, ) -> Self::CombinatorialId { let input = (collateral, collection_id); + Hasher::hash_tuple(input) } } diff --git a/zrml/combinatorial-tokens/src/types/mod.rs b/zrml/combinatorial-tokens/src/types/mod.rs index a9960976e..bb5cce9cf 100644 --- a/zrml/combinatorial-tokens/src/types/mod.rs +++ b/zrml/combinatorial-tokens/src/types/mod.rs @@ -19,6 +19,6 @@ pub(crate) mod cryptographic_id_manager; pub(crate) mod hash; mod transmutation_type; -pub use cryptographic_id_manager::CryptographicIdManager; +pub use cryptographic_id_manager::{CryptographicIdManager, Fuel}; pub(crate) use hash::Hash256; pub use transmutation_type::TransmutationType; diff --git a/zrml/combinatorial-tokens/src/weights.rs b/zrml/combinatorial-tokens/src/weights.rs index 1edf64a0a..f1991c6df 100644 --- a/zrml/combinatorial-tokens/src/weights.rs +++ b/zrml/combinatorial-tokens/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_combinatorial_tokens //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-30`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-12-05`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `Mac`, CPU: `` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=zrml_combinatorial_tokens // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -49,14 +49,14 @@ use frame_support::{traits::Get, weights::Weight}; /// Trait containing the required functions for weight retrival within /// zrml_combinatorial_tokens (automatically generated) pub trait WeightInfoZeitgeist { - fn split_position_vertical_sans_parent(n: u32) -> Weight; - fn split_position_vertical_with_parent(n: u32) -> Weight; - fn split_position_horizontal(n: u32) -> Weight; - fn merge_position_vertical_sans_parent(n: u32) -> Weight; - fn merge_position_vertical_with_parent(n: u32) -> Weight; - fn merge_position_horizontal(n: u32) -> Weight; - fn redeem_position_sans_parent(n: u32) -> Weight; - fn redeem_position_with_parent(n: u32) -> Weight; + fn split_position_vertical_sans_parent(n: u32, m: u32) -> Weight; + fn split_position_vertical_with_parent(n: u32, m: u32) -> Weight; + fn split_position_horizontal(n: u32, m: u32) -> Weight; + fn merge_position_vertical_sans_parent(n: u32, m: u32) -> Weight; + fn merge_position_vertical_with_parent(n: u32, m: u32) -> Weight; + fn merge_position_horizontal(n: u32, m: u32) -> Weight; + fn redeem_position_sans_parent(n: u32, m: u32) -> Weight; + fn redeem_position_with_parent(n: u32, m: u32) -> Weight; } /// Weight functions for zrml_combinatorial_tokens (automatically generated) @@ -71,14 +71,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:32 w:32) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_vertical_sans_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `441` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 6_978_416 nanoseconds. - Weight::from_parts(102_072_603, 4173) - // Standard Error: 4_544_660 - .saturating_add(Weight::from_parts(3_565_520_352, 0).saturating_mul(n.into())) + // Minimum execution time: 3_358_000 nanoseconds. + Weight::from_parts(3_358_000_000, 4173) + // Standard Error: 397_575_770 + .saturating_add(Weight::from_parts(1_183_275_893, 0).saturating_mul(n.into())) + // Standard Error: 191_113_523 + .saturating_add(Weight::from_parts(73_290_272, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -92,14 +95,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_vertical_with_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_vertical_with_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `671` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 8_316_377 nanoseconds. - Weight::from_parts(2_605_489, 4173) - // Standard Error: 3_965_121 - .saturating_add(Weight::from_parts(4_222_525_574, 0).saturating_mul(n.into())) + // Minimum execution time: 3_816_000 nanoseconds. + Weight::from_parts(3_816_000_000, 4173) + // Standard Error: 404_411_306 + .saturating_add(Weight::from_parts(1_418_273_449, 0).saturating_mul(n.into())) + // Standard Error: 194_399_346 + .saturating_add(Weight::from_parts(67_832_101, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -113,14 +119,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn split_position_horizontal(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn split_position_horizontal(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `633` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 10_369_759 nanoseconds. - Weight::from_parts(3_384_663_612, 4173) - // Standard Error: 3_858_637 - .saturating_add(Weight::from_parts(3_549_879_971, 0).saturating_mul(n.into())) + // Minimum execution time: 4_950_000 nanoseconds. + Weight::from_parts(4_950_000_000, 4173) + // Standard Error: 407_636_194 + .saturating_add(Weight::from_parts(1_190_982_890, 0).saturating_mul(n.into())) + // Standard Error: 195_949_540 + .saturating_add(Weight::from_parts(75_730_302, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -136,14 +145,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_vertical_sans_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `623 + n * (159 ±0)` + // Measured: `623 + n * (160 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 6_950_049 nanoseconds. - Weight::from_parts(7_032_940_000, 4173) - // Standard Error: 8_128_230 - .saturating_add(Weight::from_parts(3_221_583_105, 0).saturating_mul(n.into())) + // Minimum execution time: 3_357_000 nanoseconds. + Weight::from_parts(3_357_000_000, 4173) + // Standard Error: 398_813_800 + .saturating_add(Weight::from_parts(1_188_250_534, 0).saturating_mul(n.into())) + // Standard Error: 191_708_641 + .saturating_add(Weight::from_parts(73_478_154, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -157,14 +169,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_vertical_with_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_vertical_with_parent(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `515 + n * (160 ±0)` + // Measured: `520 + n * (159 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 8_233_017 nanoseconds. - Weight::from_parts(8_273_928_000, 4173) - // Standard Error: 9_495_570 - .saturating_add(Weight::from_parts(3_810_340_613, 0).saturating_mul(n.into())) + // Minimum execution time: 3_791_000 nanoseconds. + Weight::from_parts(3_791_000_000, 4173) + // Standard Error: 403_628_733 + .saturating_add(Weight::from_parts(1_426_263_672, 0).saturating_mul(n.into())) + // Standard Error: 194_023_165 + .saturating_add(Weight::from_parts(67_374_417, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -178,14 +193,17 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:33 w:33) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn merge_position_horizontal(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn merge_position_horizontal(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `478 + n * (160 ±0)` + // Measured: `481 + n * (159 ±0)` // Estimated: `4173 + n * (2612 ±0)` - // Minimum execution time: 10_361_313 nanoseconds. - Weight::from_parts(3_250_658_133, 4173) - // Standard Error: 4_095_907 - .saturating_add(Weight::from_parts(3_565_909_886, 0).saturating_mul(n.into())) + // Minimum execution time: 4_948_000 nanoseconds. + Weight::from_parts(4_948_000_000, 4173) + // Standard Error: 408_972_386 + .saturating_add(Weight::from_parts(1_174_151_237, 0).saturating_mul(n.into())) + // Standard Error: 196_591_844 + .saturating_add(Weight::from_parts(76_736_050, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2)) @@ -201,14 +219,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_sans_parent(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn redeem_position_sans_parent(n: u32, _m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `780` // Estimated: `4173` - // Minimum execution time: 3_501_697 nanoseconds. - Weight::from_parts(3_567_968_354, 4173) - // Standard Error: 114_392 - .saturating_add(Weight::from_parts(159_826, 0).saturating_mul(n.into())) + // Minimum execution time: 342_000 nanoseconds. + Weight::from_parts(298_833_333, 4173) + // Standard Error: 86_602 + .saturating_add(Weight::from_parts(22_083_333, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -219,12 +238,15 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:2 w:2) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 32]`. - fn redeem_position_with_parent(_n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn redeem_position_with_parent(n: u32, _m: u32) -> Weight { // Proof Size summary in bytes: // Measured: `674` // Estimated: `6214` - // Minimum execution time: 4_123_619 nanoseconds. - Weight::from_parts(4_201_426_623, 6214) + // Minimum execution time: 572_000 nanoseconds. + Weight::from_parts(549_299_999, 6214) + // Standard Error: 202_072 + .saturating_add(Weight::from_parts(21_850_000, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/zrml/futarchy/src/dispatchable_impls.rs b/zrml/futarchy/src/dispatchable_impls.rs index ea786f4ef..0fd409e11 100644 --- a/zrml/futarchy/src/dispatchable_impls.rs +++ b/zrml/futarchy/src/dispatchable_impls.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{types::Proposal, Config, Error, Event, Pallet, Proposals}; +use crate::{traits::ProposalStorage, types::Proposal, Config, Error, Event, Pallet}; use frame_support::{ensure, require_transactional, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{DispatchResult, Saturating}; @@ -31,12 +31,10 @@ impl Pallet { let now = frame_system::Pallet::::block_number(); let to_be_scheduled_at = now.saturating_add(duration); - let try_mutate_result = Proposals::::try_mutate(to_be_scheduled_at, |proposals| { - proposals.try_push(proposal.clone()).map_err(|_| Error::::CacheFull) - }); + as ProposalStorage>::add(to_be_scheduled_at, proposal.clone())?; Self::deposit_event(Event::::Submitted { duration, proposal }); - Ok(try_mutate_result?) + Ok(()) } } diff --git a/zrml/futarchy/src/lib.rs b/zrml/futarchy/src/lib.rs index 500c61457..fca677860 100644 --- a/zrml/futarchy/src/lib.rs +++ b/zrml/futarchy/src/lib.rs @@ -42,6 +42,7 @@ mod benchmarking; mod dispatchable_impls; pub mod mock; mod pallet_impls; +mod proposal_storage; mod tests; pub mod traits; pub mod types; @@ -51,21 +52,21 @@ pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::{types::Proposal, weights::WeightInfoZeitgeist}; + use crate::{traits::ProposalStorage, types::Proposal, weights::WeightInfoZeitgeist}; use alloc::fmt::Debug; use core::marker::PhantomData; use frame_support::{ - pallet_prelude::{EnsureOrigin, IsType, StorageMap, StorageVersion, ValueQuery, Weight}, + pallet_prelude::{IsType, StorageMap, StorageValue, StorageVersion, ValueQuery, Weight}, traits::{schedule::v3::Anon as ScheduleAnon, Bounded, Hooks, OriginTrait}, transactional, Blake2_128Concat, BoundedVec, }; - use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; + use frame_system::{ + ensure_root, + pallet_prelude::{BlockNumberFor, OriginFor}, + }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; - use sp_runtime::{ - traits::{ConstU32, Get}, - DispatchResult, - }; + use sp_runtime::{traits::Get, DispatchResult, SaturatedConversion}; use zeitgeist_primitives::traits::FutarchyOracle; #[cfg(feature = "runtime-benchmarks")] @@ -76,10 +77,13 @@ mod pallet { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper: FutarchyBenchmarkHelper; + /// The maximum number of proposals allowed to be in flight simultaneously. + type MaxProposals: Get; + type MinDuration: Get>; // The type used to define the oracle for each proposal. - type Oracle: FutarchyOracle + type Oracle: FutarchyOracle> + Clone + Debug + Decode @@ -94,9 +98,6 @@ mod pallet { /// Scheduler interface for executing proposals. type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf>; - /// The origin that is allowed to submit proposals. - type SubmitOrigin: EnsureOrigin; - type WeightInfo: WeightInfoZeitgeist; } @@ -104,23 +105,21 @@ mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); - pub(crate) type CacheSize = ConstU32<16>; pub(crate) type CallOf = ::RuntimeCall; pub(crate) type BoundedCallOf = Bounded>; pub(crate) type OracleOf = ::Oracle; pub(crate) type PalletsOriginOf = <::RuntimeOrigin as OriginTrait>::PalletsOrigin; + pub(crate) type ProposalsOf = BoundedVec, ::MaxProposals>; pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::storage] - pub type Proposals = StorageMap< - _, - Blake2_128Concat, - BlockNumberFor, - BoundedVec, CacheSize>, - ValueQuery, - >; + pub type Proposals = + StorageMap<_, Blake2_128Concat, BlockNumberFor, ProposalsOf, ValueQuery>; + + #[pallet::storage] + pub type ProposalCount = StorageValue<_, u32, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -148,6 +147,9 @@ mod pallet { /// The specified duration must be at least equal to `MinDuration`. DurationTooShort, + + /// This is a logic error. You shouldn't see this. + UnexpectedStorageFailure, } #[pallet::call] @@ -160,7 +162,7 @@ mod pallet { duration: BlockNumberFor, proposal: Proposal, ) -> DispatchResult { - T::SubmitOrigin::ensure_origin(origin)?; + ensure_root(origin)?; Self::do_submit_proposal(duration, proposal) } @@ -169,10 +171,34 @@ mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - let mut total_weight = Weight::zero(); // Add buffer. + let mut total_weight = Weight::zero(); + + // Update all oracles. + let mutate_all_result = + as ProposalStorage>::mutate_all(|p| p.oracle.update(now)); + if let Ok(block_to_weights) = mutate_all_result { + // We did one storage read per vector cached. Shouldn't saturate, but technically + // might. + let reads: u64 = block_to_weights.len().saturated_into(); + total_weight = total_weight.saturating_add(T::DbWeight::get().reads(reads)); + + for weights in block_to_weights.values() { + for &weight in weights.iter() { + total_weight = total_weight.saturating_add(weight); + } + } + } else { + // Unreachable! + return total_weight; + } - let proposals = Proposals::::take(now); + // total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + let proposals = if let Ok(proposals) = as ProposalStorage>::take(now) { + proposals + } else { + return total_weight; + }; for proposal in proposals.into_iter() { let weight = Self::maybe_schedule_proposal(proposal); diff --git a/zrml/futarchy/src/mock/runtime.rs b/zrml/futarchy/src/mock/runtime.rs index 87dc92f29..0fc2e69cf 100644 --- a/zrml/futarchy/src/mock/runtime.rs +++ b/zrml/futarchy/src/mock/runtime.rs @@ -21,7 +21,7 @@ use crate::{ weights::WeightInfo, }; use frame_support::{construct_runtime, parameter_types, traits::Everything}; -use frame_system::{mocking::MockBlock, EnsureRoot}; +use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; use zeitgeist_primitives::{ constants::mock::{BlockHashCount, ExistentialDeposit, MaxLocks, MaxReserves}, @@ -33,6 +33,7 @@ use crate::mock::types::MockBenchmarkHelper; parameter_types! { // zrml-futarchy + pub const MaxProposals: u32 = 16; pub const MinDuration: BlockNumber = 10; } @@ -89,10 +90,10 @@ impl pallet_balances::Config for Runtime { impl zrml_futarchy::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = MockBenchmarkHelper; + type MaxProposals = MaxProposals; type MinDuration = MinDuration; type Oracle = MockOracle; type RuntimeEvent = RuntimeEvent; type Scheduler = MockScheduler; - type SubmitOrigin = EnsureRoot<::AccountId>; type WeightInfo = WeightInfo; } diff --git a/zrml/futarchy/src/mock/types/oracle.rs b/zrml/futarchy/src/mock/types/oracle.rs index 4b8a7f7e4..92b5e6b79 100644 --- a/zrml/futarchy/src/mock/types/oracle.rs +++ b/zrml/futarchy/src/mock/types/oracle.rs @@ -19,7 +19,8 @@ use alloc::fmt::Debug; use frame_support::pallet_prelude::Weight; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use zeitgeist_primitives::traits::FutarchyOracle; +use sp_runtime::traits::Zero; +use zeitgeist_primitives::{traits::FutarchyOracle, types::BlockNumber}; #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct MockOracle { @@ -40,7 +41,13 @@ impl MockOracle { } impl FutarchyOracle for MockOracle { + type BlockNumber = BlockNumber; + fn evaluate(&self) -> (Weight, bool) { (self.weight, self.value) } + + fn update(&mut self, _: Self::BlockNumber) -> Weight { + Zero::zero() + } } diff --git a/zrml/futarchy/src/pallet_impls.rs b/zrml/futarchy/src/pallet_impls.rs index 9ab3fe5ce..c5019ad6d 100644 --- a/zrml/futarchy/src/pallet_impls.rs +++ b/zrml/futarchy/src/pallet_impls.rs @@ -23,6 +23,10 @@ use frame_support::{ }; use zeitgeist_primitives::traits::FutarchyOracle; +// Following Parity's implementation of pallet-democracy, we're using minimum priority for futarchy +// proposals. +const SCHEDULE_PRIORITY: u8 = 63; + impl Pallet { /// Evaluates `proposal` using the specified oracle and schedules the contained call if the /// oracle approves. @@ -33,7 +37,7 @@ impl Pallet { let result = T::Scheduler::schedule( DispatchTime::At(proposal.when), None, - 63, + SCHEDULE_PRIORITY, RawOrigin::Root.into(), proposal.call.clone(), ); diff --git a/zrml/futarchy/src/proposal_storage.rs b/zrml/futarchy/src/proposal_storage.rs new file mode 100644 index 000000000..6a0fc0a10 --- /dev/null +++ b/zrml/futarchy/src/proposal_storage.rs @@ -0,0 +1,88 @@ +use crate::{ + traits::ProposalStorage, types::Proposal, Config, Error, Pallet, ProposalCount, Proposals, + ProposalsOf, +}; +use alloc::{collections::BTreeMap, vec, vec::Vec}; +use frame_support::{ensure, require_transactional, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::{DispatchError, SaturatedConversion}; +use zeitgeist_primitives::math::checked_ops_res::{CheckedIncRes, CheckedSubRes}; + +impl ProposalStorage for Pallet +where + T: Config, +{ + fn count() -> u32 { + ProposalCount::::get() + } + + #[require_transactional] + fn add(block_number: BlockNumberFor, proposal: Proposal) -> Result<(), DispatchError> { + let proposal_count = ProposalCount::::get(); + ensure!(proposal_count < T::MaxProposals::get(), Error::::CacheFull); + + let new_proposal_count = proposal_count.checked_inc_res()?; + ProposalCount::::put(new_proposal_count); + + // Can't error unless state is invalid. + let mutate_result = Proposals::::try_mutate(block_number, |proposals| { + proposals.try_push(proposal).map_err(|_| Error::::CacheFull) + }); + + Ok(mutate_result?) + } + + /// Take all proposals scheduled at `block_number`. + fn take(block_number: BlockNumberFor) -> Result, DispatchError> { + let proposals = Proposals::::take(block_number); + + // Can't error unless state is invalid. + let proposal_count = ProposalCount::::get(); + let proposals_len: u32 = proposals.len().try_into().map_err(|_| Error::::CacheFull)?; + let new_proposal_count = proposal_count.checked_sub_res(&proposals_len)?; + ProposalCount::::put(new_proposal_count); + + Ok(proposals) + } + + /// Returns all proposals scheduled at `block_number`. + fn get(block_number: BlockNumberFor) -> ProposalsOf { + Proposals::::get(block_number) + } + + fn mutate_all( + mut mutator: F, + ) -> Result, Vec>, DispatchError> + where + F: FnMut(&mut Proposal) -> R, + { + // Collect keys to avoid iterating over the keys whilst modifying the map. Won't saturate + // unless `usize` has fewer bits than `u32` for some reason. + let keys: Vec<_> = + Proposals::::iter_keys().take(T::MaxProposals::get().saturated_into()).collect(); + + let mut result_map = BTreeMap::new(); + + for k in keys.into_iter() { + let proposals = Self::get(k); + + let mut results = vec![]; + + // If mutation goes out of bounds, we've clearly failed. + let proposals = proposals + .try_mutate(|v| { + for p in v.iter_mut() { + let r = mutator(p); + results.push(r); + } + }) + .ok_or(Error::::UnexpectedStorageFailure)?; + + result_map.insert(k, results); + + Proposals::::insert(k, proposals); + } + + Ok(result_map) + } +} diff --git a/zrml/futarchy/src/tests/mod.rs b/zrml/futarchy/src/tests/mod.rs index a9cdbd360..e3bc8d409 100644 --- a/zrml/futarchy/src/tests/mod.rs +++ b/zrml/futarchy/src/tests/mod.rs @@ -27,14 +27,14 @@ use crate::{ utility, }, types::Proposal, - CacheSize, Config, Error, Event, Proposals, + Config, Error, Event, Proposals, ProposalsOf, }; use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, traits::{schedule::DispatchTime, Bounded}, }; -use sp_runtime::{traits::Get, BoundedVec, DispatchError}; +use sp_runtime::DispatchError; /// Utility struct for managing test accounts. pub(crate) struct Account { diff --git a/zrml/futarchy/src/tests/submit_proposal.rs b/zrml/futarchy/src/tests/submit_proposal.rs index 3eabcfafe..df0ece080 100644 --- a/zrml/futarchy/src/tests/submit_proposal.rs +++ b/zrml/futarchy/src/tests/submit_proposal.rs @@ -134,9 +134,9 @@ fn submit_proposal_fails_if_cache_is_full() { // Mock up a full vector of proposals. let now = System::block_number(); let to_be_scheduled_at = now + duration; - let cache_size: u32 = >>::get().unwrap(); - let proposals_vec = vec![proposal.clone(); cache_size as usize]; - let proposals: BoundedVec<_, CacheSize> = proposals_vec.try_into().unwrap(); + let max_proposals: u32 = ::MaxProposals::get(); + let proposals_vec = vec![proposal.clone(); max_proposals as usize]; + let proposals: ProposalsOf = proposals_vec.try_into().unwrap(); Proposals::::insert(to_be_scheduled_at, proposals); assert_noop!( diff --git a/zrml/futarchy/src/traits/mod.rs b/zrml/futarchy/src/traits/mod.rs index 1032ee726..d995c8264 100644 --- a/zrml/futarchy/src/traits/mod.rs +++ b/zrml/futarchy/src/traits/mod.rs @@ -14,3 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . + +mod proposal_storage; + +pub(crate) use proposal_storage::ProposalStorage; diff --git a/zrml/futarchy/src/traits/proposal_storage.rs b/zrml/futarchy/src/traits/proposal_storage.rs new file mode 100644 index 000000000..f26220bc9 --- /dev/null +++ b/zrml/futarchy/src/traits/proposal_storage.rs @@ -0,0 +1,28 @@ +use crate::{types::Proposal, Config, ProposalsOf}; +use alloc::{collections::BTreeMap, vec::Vec}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::DispatchError; + +pub(crate) trait ProposalStorage +where + T: Config, +{ + /// Returns the number of proposals currently in flight. + #[allow(dead_code)] + fn count() -> u32; + + /// Schedule `proposal` for evaluation at `block_number`. + fn add(block_number: BlockNumberFor, proposal: Proposal) -> Result<(), DispatchError>; + + /// Take all proposals scheduled at `block_number`. + fn take(block_number: BlockNumberFor) -> Result, DispatchError>; + + /// Returns all proposals scheduled at `block_number`. + #[allow(dead_code)] + fn get(block_number: BlockNumberFor) -> ProposalsOf; + + /// Mutates all scheduled proposals. + fn mutate_all(mutator: F) -> Result, Vec>, DispatchError> + where + F: FnMut(&mut Proposal) -> R; +} diff --git a/zrml/futarchy/src/types/proposal.rs b/zrml/futarchy/src/types/proposal.rs index 84ddf6748..fa69b6401 100644 --- a/zrml/futarchy/src/types/proposal.rs +++ b/zrml/futarchy/src/types/proposal.rs @@ -21,7 +21,6 @@ use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -// TODO Make config a generic, keeps things simple. #[derive( CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/zrml/hybrid-router/src/mock.rs b/zrml/hybrid-router/src/mock.rs index 99d3bc72b..36f4dbd9b 100644 --- a/zrml/hybrid-router/src/mock.rs +++ b/zrml/hybrid-router/src/mock.rs @@ -59,7 +59,7 @@ use zeitgeist_primitives::{ MarketId, Moment, }, }; -use zrml_combinatorial_tokens::types::CryptographicIdManager; +use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; #[cfg(feature = "parachain")] use { @@ -281,6 +281,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; diff --git a/zrml/neo-swaps/src/benchmarking.rs b/zrml/neo-swaps/src/benchmarking.rs index cd0ccc29b..6a7886844 100644 --- a/zrml/neo-swaps/src/benchmarking.rs +++ b/zrml/neo-swaps/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; use crate::{ liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree}, traits::{LiquiditySharesManager, PoolOperations, PoolStorage}, - types::DecisionMarketOracle, + types::{DecisionMarketOracle, DecisionMarketOracleScoreboard}, AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE, }; use alloc::{vec, vec::Vec}; @@ -40,7 +40,7 @@ use sp_runtime::{ use zeitgeist_primitives::{ constants::{base_multiples::*, CENT}, math::fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, - traits::{CompleteSetOperationsApi, FutarchyOracle}, + traits::{CombinatorialTokensFuel, CompleteSetOperationsApi, FutarchyOracle}, types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, }; use zrml_market_commons::MarketCommonsPalletApi; @@ -523,7 +523,7 @@ mod benchmarks { amount, create_spot_prices::(asset_count), CENT.saturated_into(), - false, + FuelOf::::from_total(16), )); let pool_id = 0u8.into(); @@ -578,7 +578,7 @@ mod benchmarks { amount, create_spot_prices::(asset_count), CENT.saturated_into(), - false, + FuelOf::::from_total(16), )); let pool_id = 0u8.into(); @@ -623,8 +623,9 @@ mod benchmarks { } #[benchmark] - fn deploy_combinatorial_pool(n: Linear<1, 7>) { + fn deploy_combinatorial_pool(n: Linear<1, 7>, m: Linear<32, 64>) { let market_count = n; + let total = m; let alice: T::AccountId = whitelisted_caller(); let base_asset = Asset::Ztg; @@ -644,7 +645,15 @@ mod benchmarks { let swap_fee = CENT.saturated_into(); #[extrinsic_call] - _(RawOrigin::Signed(alice), asset_count, market_ids, amount, spot_prices, swap_fee, true); + _( + RawOrigin::Signed(alice), + asset_count, + market_ids, + amount, + spot_prices, + swap_fee, + FuelOf::::from_total(total), + ); } #[benchmark] @@ -662,7 +671,13 @@ mod benchmarks { let pool = Pools::::get(market_id).unwrap(); let assets = pool.assets(); - let oracle = DecisionMarketOracle::::new(market_id, assets[0], assets[1]); + let scoreboard = DecisionMarketOracleScoreboard::::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let oracle = DecisionMarketOracle::::new(market_id, assets[0], assets[1], scoreboard); #[block] { @@ -670,6 +685,36 @@ mod benchmarks { } } + #[benchmark] + fn decision_market_oracle_update() { + let alice = whitelisted_caller(); + let base_asset = Asset::Ztg; + let asset_count = 2; + let market_id = create_market_and_deploy_pool::( + alice, + base_asset, + asset_count, + _10.saturated_into(), + ); + + let pool = Pools::::get(market_id).unwrap(); + let assets = pool.assets(); + + let scoreboard = DecisionMarketOracleScoreboard::::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let mut oracle = + DecisionMarketOracle::::new(market_id, assets[0], assets[1], scoreboard); + + #[block] + { + let _ = oracle.update(1u8.into()); + } + } + impl_benchmark_test_suite!( NeoSwaps, crate::mock::ExtBuilder::default().build(), diff --git a/zrml/neo-swaps/src/lib.rs b/zrml/neo-swaps/src/lib.rs index 2fc47f05a..0a14806cc 100644 --- a/zrml/neo-swaps/src/lib.rs +++ b/zrml/neo-swaps/src/lib.rs @@ -17,8 +17,8 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::too_many_arguments)] // TODO Try to remove this later! -#![allow(clippy::type_complexity)] // TODO Try to remove this later! +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] extern crate alloc; @@ -86,8 +86,8 @@ mod pallet { fixed::{BaseProvider, FixedDiv, FixedMul, ZeitgeistBase}, }, traits::{ - CombinatorialTokensApi, CombinatorialTokensUnsafeApi, CompleteSetOperationsApi, - DeployPoolApi, DistributeFees, HybridRouterAmmApi, + CombinatorialTokensApi, CombinatorialTokensFuel, CombinatorialTokensUnsafeApi, + CompleteSetOperationsApi, DeployPoolApi, DistributeFees, HybridRouterAmmApi, }, types::{Asset, MarketStatus, ScoringRule}, }; @@ -119,6 +119,8 @@ mod pallet { pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; pub(crate) type AssetIndexType = u16; + pub(crate) type FuelOf = + <::CombinatorialTokens as CombinatorialTokensApi>::Fuel; pub(crate) type MarketIdOf = <::MarketCommons as MarketCommonsPalletApi>::MarketId; pub(crate) type LiquidityTreeOf = LiquidityTree::MaxLiquidityTreeDepth>; @@ -741,7 +743,10 @@ mod pallet { } #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool(asset_count.log_ceil().into()))] + #[pallet::weight(T::WeightInfo::deploy_combinatorial_pool( + asset_count.log_ceil().into(), + fuel.total(), + ))] #[transactional] pub fn deploy_combinatorial_pool( origin: OriginFor, @@ -750,7 +755,7 @@ mod pallet { amount: BalanceOf, spot_prices: Vec>, swap_fee: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -761,14 +766,7 @@ mod pallet { } ensure!(asset_count == real_asset_count, Error::::IncorrectAssetCount); - Self::do_deploy_combinatorial_pool( - who, - market_ids, - amount, - spot_prices, - swap_fee, - force_max_work, - ) + Self::do_deploy_combinatorial_pool(who, market_ids, amount, spot_prices, swap_fee, fuel) } } @@ -1008,10 +1006,7 @@ mod pallet { ) -> DispatchResult { ensure!(pool_shares_amount != Zero::zero(), Error::::ZeroAmount); - // FIXME Should this also be made part of the `PoolStorage` interface? - Pools::::try_mutate_exists(pool_id, |maybe_pool| { - let pool = - maybe_pool.as_mut().ok_or::(Error::::PoolNotFound.into())?; + ::try_mutate_exists(&pool_id, |pool| { let ratio = { let mut ratio = pool_shares_amount .bdiv_floor(pool.liquidity_shares_manager.total_shares()?)?; @@ -1047,13 +1042,14 @@ mod pallet { for asset in pool.assets().iter() { withdraw_remaining(asset)?; } - *maybe_pool = None; // Delete the storage map entry. Self::deposit_event(Event::::PoolDestroyed { who: who.clone(), pool_id, amounts_out, }); - // No need to clear `MarketIdToPoolId`. + + // Delete the pool. No need to clear `MarketIdToPoolId`. + Ok(((), true)) } else { let old_liquidity_parameter = pool.liquidity_parameter; let new_liquidity_parameter = old_liquidity_parameter @@ -1083,8 +1079,9 @@ mod pallet { amounts_out, new_liquidity_parameter, }); + + Ok(((), false)) } - Ok(()) }) } @@ -1200,13 +1197,13 @@ mod pallet { amount: BalanceOf, spot_prices: Vec>, swap_fee: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> DispatchResult { ensure!(swap_fee >= MIN_SWAP_FEE.saturated_into(), Error::::SwapFeeBelowMin); ensure!(swap_fee <= T::MaxSwapFee::get(), Error::::SwapFeeAboveMax); let (collection_ids, position_ids, collateral) = - Self::split_markets(who.clone(), market_ids.clone(), amount, force_max_work)?; + Self::split_markets(who.clone(), market_ids.clone(), amount, fuel)?; ensure!(spot_prices.len() == collection_ids.len(), Error::::IncorrectVecLen); ensure!( @@ -1326,6 +1323,8 @@ mod pallet { let amount_out = swap_amount_out.checked_add_res(&amount_in_minus_fees)?; ensure!(amount_out >= min_amount_out, Error::::AmountOutBelowMin); + // Using unsafe API to avoid doing work. This is perfectly safe as long as + // `pool.assets()` returns a "full set" of split tokens. T::CombinatorialTokensUnsafe::split_position_unsafe( who.clone(), pool.collateral, @@ -1456,6 +1455,8 @@ mod pallet { pool.increase_reserve(&asset, &amount_keep)?; } + // Using unsafe API to avoid doing work. This is perfectly safe as long as + // `pool.assets()` returns a "full set" of split tokens. T::CombinatorialTokensUnsafe::merge_position_unsafe( pool.account_id.clone(), pool.collateral, @@ -1568,7 +1569,7 @@ mod pallet { who: T::AccountId, market_ids: Vec>, amount: BalanceOf, - force_max_work: bool, + fuel: FuelOf, ) -> Result<(Vec, Vec>, AssetOf), DispatchError> { let markets = market_ids.iter().map(T::MarketCommons::market).collect::, _>>()?; @@ -1619,7 +1620,7 @@ mod pallet { *market_id, partition.clone(), amount, - force_max_work, + fuel.clone(), )?; collection_ids.extend_from_slice(&split_position_info.collection_ids); @@ -1641,7 +1642,7 @@ mod pallet { *market_id, partition.clone(), amount, - force_max_work, + fuel.clone(), )?; new_collection_ids.extend_from_slice(&split_position_info.collection_ids); diff --git a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs index 69148c51d..856c96202 100644 --- a/zrml/neo-swaps/src/math/traits/combo_math_ops.rs +++ b/zrml/neo-swaps/src/math/traits/combo_math_ops.rs @@ -23,6 +23,9 @@ pub(crate) trait ComboMathOps where T: Config, { + /// Calculates the amount swapped out of a pool with liquidity parameter `liquidity` when + /// swapping in `amount_in` units of assets whose reserves in the pool are `sell` and swapping + /// out assets whose reserves in the pool are `buy`. fn calculate_swap_amount_out_for_buy( buy: Vec>, sell: Vec>, @@ -30,6 +33,7 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + /// Calculates the amount eventually held by the user after equalizing holdings. #[allow(dead_code)] fn calculate_equalize_amount( buy: Vec>, @@ -39,6 +43,10 @@ where liquidity: BalanceOf, ) -> Result, DispatchError>; + /// Calculates the amount of each asset of a pool with liquidity parameter `liquidity` held + /// in the user's wallet after equalizing positions whose reserves in the pool are `buy`, `keep` + /// and `sell`, resp. The parameters `amount_buy` and `amount_keep` refer to the user's holdings + /// of `buy` and `keep`. fn calculate_swap_amount_out_for_sell( buy: Vec>, keep: Vec>, diff --git a/zrml/neo-swaps/src/math/types/combo_math.rs b/zrml/neo-swaps/src/math/types/combo_math.rs index bfdb4e62a..585464776 100644 --- a/zrml/neo-swaps/src/math/types/combo_math.rs +++ b/zrml/neo-swaps/src/math/types/combo_math.rs @@ -16,23 +16,12 @@ // along with Zeitgeist. If not, see . use crate::{ - math::{ - traits::ComboMathOps, - transcendental::{exp, ln}, - }, + math::{traits::ComboMathOps, transcendental::ln, types::common::FixedType}, BalanceOf, Config, Error, }; use alloc::vec::Vec; use core::marker::PhantomData; -use fixed::FixedU128; use sp_runtime::{traits::Zero, DispatchError, SaturatedConversion}; -use typenum::U80; - -type Fractional = U80; -type FixedType = FixedU128; - -/// The point at which `exp` values become too large, 32.44892769177272. -const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct ComboMath(PhantomData); @@ -111,14 +100,7 @@ where mod detail { use super::*; - use zeitgeist_primitives::{ - constants::DECIMALS, - math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, - }; - - fn to_fixed(value: u128) -> Option { - value.to_fixed_from_fixed_decimal(DECIMALS).ok() - } + use crate::math::types::common::{from_fixed, protected_exp, to_fixed}; /// Converts `Vec` of fixed decimal numbers to a `Vec` of fixed point numbers; /// returns `None` if any of them fail. @@ -126,19 +108,6 @@ mod detail { vec.into_iter().map(to_fixed).collect() } - fn from_fixed(value: FixedType) -> Option - where - B: Into + From, - { - value.to_fixed_decimal(DECIMALS).ok() - } - - /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical - /// boundaries. - fn protected_exp(value: FixedType, neg: bool) -> Option { - if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } - } - /// Returns `\sum_{r \in R} e^{-r/b}`, where `R` denotes `reserves` and `b` denotes `liquidity`. /// The result is `None` if and only if any of the `exp` calculations has failed. fn exp_sum(reserves: Vec, liquidity: FixedType) -> Option { @@ -272,7 +241,9 @@ mod detail { ) -> Option { // Ensure that either `keep` is empty and `amount_keep` is zero, or `keep` is non-empty and // `amount_keep` is non-zero. - if keep.is_empty() && !amount_keep.is_zero() || !keep.is_empty() && amount_keep.is_zero() { + if (keep.is_empty() && !amount_keep.is_zero()) + || (!keep.is_empty() && amount_keep.is_zero()) + { return None; } diff --git a/zrml/neo-swaps/src/math/types/common.rs b/zrml/neo-swaps/src/math/types/common.rs new file mode 100644 index 000000000..e336aea75 --- /dev/null +++ b/zrml/neo-swaps/src/math/types/common.rs @@ -0,0 +1,51 @@ +// 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::math::transcendental::exp; +use fixed::FixedU128; +use typenum::U80; +use zeitgeist_primitives::{ + constants::DECIMALS, + math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, +}; + +type Fractional = U80; +pub(crate) type FixedType = FixedU128; + +// 32.44892769177272 +pub(crate) const EXP_NUMERICAL_THRESHOLD: FixedType = + FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); + +pub(crate) fn to_fixed(value: B) -> Option +where + B: Into + From, +{ + value.to_fixed_from_fixed_decimal(DECIMALS).ok() +} + +pub(crate) fn from_fixed(value: FixedType) -> Option +where + B: Into + From, +{ + value.to_fixed_decimal(DECIMALS).ok() +} + +/// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical +/// boundaries. +pub(crate) fn protected_exp(value: FixedType, neg: bool) -> Option { + if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } +} diff --git a/zrml/neo-swaps/src/math/types/math.rs b/zrml/neo-swaps/src/math/types/math.rs index 68cc38ac6..e816da26e 100644 --- a/zrml/neo-swaps/src/math/types/math.rs +++ b/zrml/neo-swaps/src/math/types/math.rs @@ -18,24 +18,17 @@ use crate::{ math::{ traits::MathOps, - transcendental::{exp, ln}, + transcendental::ln, + types::common::{FixedType, EXP_NUMERICAL_THRESHOLD}, }, BalanceOf, Config, Error, }; use alloc::vec::Vec; use core::marker::PhantomData; -use fixed::FixedU128; use sp_runtime::{ traits::{One, Zero}, DispatchError, SaturatedConversion, }; -use typenum::U80; - -type Fractional = U80; -type FixedType = FixedU128; - -// 32.44892769177272 -const EXP_NUMERICAL_THRESHOLD: FixedType = FixedType::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); pub(crate) struct Math(PhantomData); @@ -136,10 +129,7 @@ where mod detail { use super::*; - use zeitgeist_primitives::{ - constants::DECIMALS, - math::fixed::{IntoFixedDecimal, IntoFixedFromDecimal}, - }; + use crate::math::types::common::{from_fixed, protected_exp, to_fixed}; /// Calculate b * ln( e^(x/b) − 1 + e^(−r_i/b) ) + r_i − x. pub(super) fn calculate_swap_amount_out_for_buy( @@ -228,26 +218,6 @@ mod detail { from_fixed(result_fixed) } - fn to_fixed(value: B) -> Option - where - B: Into + From, - { - value.to_fixed_from_fixed_decimal(DECIMALS).ok() - } - - fn from_fixed(value: FixedType) -> Option - where - B: Into + From, - { - value.to_fixed_decimal(DECIMALS).ok() - } - - /// Calculates `exp(value)` but returns `None` if `value` lies outside of the numerical - /// boundaries. - fn protected_exp(value: FixedType, neg: bool) -> Option { - if value < EXP_NUMERICAL_THRESHOLD { exp(value, neg).ok() } else { None } - } - fn calculate_swap_amount_out_for_buy_fixed( reserve: FixedType, amount_in: FixedType, @@ -317,7 +287,7 @@ mod detail { let exp_x_over_b: FixedType = protected_exp(amount_in.checked_div(liquidity)?, false)?; let r_over_b = reserve.checked_div(liquidity)?; let exp_neg_r_over_b = if r_over_b < EXP_NUMERICAL_THRESHOLD { - protected_exp(reserve.checked_div(liquidity)?, true)? + protected_exp(r_over_b, true)? } else { FixedType::checked_from_num(0)? // Underflow to zero. }; @@ -372,7 +342,9 @@ mod tests { #![allow(clippy::duplicated_attributes)] use super::*; - use crate::{mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE}; + use crate::{ + math::transcendental::exp, mock::Runtime as MockRuntime, MAX_SPOT_PRICE, MIN_SPOT_PRICE, + }; use alloc::str::FromStr; use frame_support::assert_err; use test_case::test_case; diff --git a/zrml/neo-swaps/src/math/types/mod.rs b/zrml/neo-swaps/src/math/types/mod.rs index 69628b16a..4c8a92ba6 100644 --- a/zrml/neo-swaps/src/math/types/mod.rs +++ b/zrml/neo-swaps/src/math/types/mod.rs @@ -16,6 +16,7 @@ // along with Zeitgeist. If not, see . mod combo_math; +mod common; mod math; pub(crate) use combo_math::ComboMath; diff --git a/zrml/neo-swaps/src/mock.rs b/zrml/neo-swaps/src/mock.rs index 627b7db6a..75add7a3a 100644 --- a/zrml/neo-swaps/src/mock.rs +++ b/zrml/neo-swaps/src/mock.rs @@ -64,7 +64,7 @@ use zeitgeist_primitives::{ MarketId, Moment, }, }; -use zrml_combinatorial_tokens::types::CryptographicIdManager; +use zrml_combinatorial_tokens::types::{CryptographicIdManager, Fuel}; use zrml_neo_swaps::BalanceOf; #[cfg(feature = "parachain")] @@ -268,6 +268,7 @@ impl zrml_combinatorial_tokens::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = NoopCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; + type Fuel = Fuel; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; diff --git a/zrml/neo-swaps/src/pool_storage.rs b/zrml/neo-swaps/src/pool_storage.rs index 603b4b8a2..bccf35e74 100644 --- a/zrml/neo-swaps/src/pool_storage.rs +++ b/zrml/neo-swaps/src/pool_storage.rs @@ -16,8 +16,9 @@ // along with Zeitgeist. If not, see . use crate::{traits::PoolStorage, Config, Error, Pallet, PoolCount, PoolOf, Pools}; +use frame_support::require_transactional; use sp_runtime::DispatchError; -use zeitgeist_primitives::math::checked_ops_res::CheckedAddRes; +use zeitgeist_primitives::math::checked_ops_res::CheckedIncRes; impl PoolStorage for Pallet where @@ -26,16 +27,16 @@ where type PoolId = T::PoolId; type Pool = PoolOf; - // TODO Make `PoolId` as u32. fn next_pool_id() -> T::PoolId { PoolCount::::get() } + #[require_transactional] fn add(pool: Self::Pool) -> Result { let pool_id = Self::next_pool_id(); Pools::::insert(pool_id, pool); - let pool_count = pool_id.checked_add_res(&1u8.into())?; // TODO Add CheckedInc. + let pool_count = pool_id.checked_inc_res()?; PoolCount::::set(pool_count); Ok(pool_id) @@ -47,10 +48,26 @@ where fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result where - F: FnMut(&mut PoolOf) -> Result, + F: FnMut(&mut Self::Pool) -> Result, { Pools::::try_mutate(pool_id, |maybe_pool| { maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator) }) } + + fn try_mutate_exists(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>, + { + Pools::::try_mutate_exists(pool_id, |maybe_pool| { + let (result, delete) = + maybe_pool.as_mut().ok_or(Error::::PoolNotFound.into()).and_then(mutator)?; + + if delete { + *maybe_pool = None; + } + + Ok(result) + }) + } } diff --git a/zrml/neo-swaps/src/tests/combo_buy.rs b/zrml/neo-swaps/src/tests/combo_buy.rs index df1185e45..7cdd2dd4f 100644 --- a/zrml/neo-swaps/src/tests/combo_buy.rs +++ b/zrml/neo-swaps/src/tests/combo_buy.rs @@ -362,27 +362,45 @@ fn combo_buy_fails_on_amount_out_below_min() { ALICE, BASE_ASSET, vec![MarketType::Categorical(2), MarketType::Scalar(0..=1)], - _10, + 100_000_000 * _100, // Massive liquidity to keep slippage low. vec![_1_4, _1_4, _1_4, _1_4], CENT, ); let pool = as PoolStorage>::get(pool_id).unwrap(); let assets = pool.assets(); - let amount_in = _1; + + let asset_count = 4; + let buy = assets[0..1].to_vec(); + let sell = assets[1..4].to_vec(); + // amount_in is _1 / 0.97 (i.e. _1 after deducting 3% fees - 1% trading fees, 1% external + // fees _for each market_) + let amount_in = 10_309_278_350; + assert_ok!(AssetManager::deposit(BASE_ASSET, &BOB, amount_in)); - // Buying 1 at price of .25 will return less than 4 outcomes due to slippage. + // Buying for 1 at a price of .25 will return less than 4 outcomes due to slippage. assert_noop!( NeoSwaps::combo_buy( RuntimeOrigin::signed(BOB), pool_id, - 4, - vec![assets[0]], - vec![assets[1]], + asset_count, + buy.clone(), + sell.clone(), amount_in, _4, ), Error::::AmountOutBelowMin, ); + + // Post OakSecurity audit: Show that the slippage limit is tight. + assert_ok!(NeoSwaps::combo_buy( + RuntimeOrigin::signed(BOB), + pool_id, + asset_count, + buy, + sell, + amount_in, + _4 - 33, + )); }); } diff --git a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs index e58ccc5a5..2037c2f3e 100644 --- a/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs +++ b/zrml/neo-swaps/src/tests/deploy_combinatorial_pool.rs @@ -235,7 +235,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_vec_len() { _10, vec![20 * CENT; 5], CENT, - false, + Fuel::new(16, false), ), Error::::IncorrectVecLen ); @@ -257,7 +257,7 @@ fn deploy_combinatorial_pool_fails_on_market_not_found() { _10, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -288,7 +288,7 @@ fn deploy_combinatorial_pool_fails_on_inactive_market(market_status: MarketStatu _100, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), Error::::MarketNotActive, ); @@ -310,7 +310,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_trading_mechanism() { _100, vec![10 * CENT; 10], CENT, - false, + Fuel::new(16, false), ), Error::::InvalidTradingMechanism ); @@ -348,7 +348,7 @@ fn deploy_combinatorial_pool_fails_on_max_splits_exceeded() { liquidity, spot_prices, CENT, - false, + Fuel::new(16, false), ), Error::::MaxSplitsExceeded ); @@ -369,7 +369,7 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_below_min() { liquidity, vec![_1_4, _3_4], MIN_SWAP_FEE - 1, - false, + Fuel::new(16, false), ), Error::::SwapFeeBelowMin ); @@ -390,7 +390,7 @@ fn deploy_combinatorial_pool_fails_on_swap_fee_above_max() { liquidity, vec![_1_4, _3_4], ::MaxSwapFee::get() + 1, - false, + Fuel::new(16, false), ), Error::::SwapFeeAboveMax ); @@ -412,7 +412,7 @@ fn deploy_combinatorial_pool_fails_on_invalid_spot_prices(spot_prices: Vec::InvalidSpotPrices ); @@ -434,7 +434,7 @@ fn deploy_combinatorial_pool_fails_on_spot_price_below_min() { liquidity, vec![spot_price, _1 - spot_price], CENT, - false, + Fuel::new(16, false), ), Error::::SpotPriceBelowMin ); @@ -456,7 +456,7 @@ fn deploy_combinatorial_pool_fails_on_spot_price_above_max() { liquidity, vec![spot_price, _1 - spot_price], CENT, - false, + Fuel::new(16, false), ), Error::::SpotPriceAboveMax ); @@ -483,7 +483,7 @@ fn deploy_combinatorial_pool_fails_on_insufficient_funds() { liquidity, vec![_3_4, _1_4], CENT, - false, + Fuel::new(16, false), ), expected_error ); @@ -504,7 +504,7 @@ fn deploy_combinatorial_pool_fails_on_liquidity_too_low() { amount, vec![_1_2, _1_2], CENT, - false, + Fuel::new(16, false), ), Error::::LiquidityTooLow ); @@ -528,7 +528,7 @@ fn deploy_combinatorial_pool_fails_on_incorrect_asset_count() { amount, vec![_1_2, _1_2], // Incorrect, but doesn't matter! CENT, - false, + Fuel::new(16, false), ), Error::::IncorrectAssetCount, ); diff --git a/zrml/neo-swaps/src/tests/mod.rs b/zrml/neo-swaps/src/tests/mod.rs index 15ba59d97..56493b141 100644 --- a/zrml/neo-swaps/src/tests/mod.rs +++ b/zrml/neo-swaps/src/tests/mod.rs @@ -41,6 +41,7 @@ use zeitgeist_primitives::{ MarketType, MultiHash, ScalarPosition, ScoringRule, }, }; +use zrml_combinatorial_tokens::types::Fuel; use zrml_market_commons::{MarketCommonsPalletApi, Markets}; #[cfg(not(feature = "parachain"))] @@ -128,7 +129,7 @@ fn create_markets_and_deploy_combinatorial_pool( amount, spot_prices.clone(), swap_fee, - false, + Fuel::new(16, false), )); (market_ids, pool_id) diff --git a/zrml/neo-swaps/src/traits/pool_storage.rs b/zrml/neo-swaps/src/traits/pool_storage.rs index 805e0d847..6553a39d7 100644 --- a/zrml/neo-swaps/src/traits/pool_storage.rs +++ b/zrml/neo-swaps/src/traits/pool_storage.rs @@ -17,6 +17,8 @@ use sp_runtime::DispatchError; +/// Slot map interface for pool storage. Undocumented functions behave like their counterparts in +/// substrate's `StorageMap`. pub(crate) trait PoolStorage { type PoolId; type Pool; @@ -30,4 +32,11 @@ pub(crate) trait PoolStorage { fn try_mutate_pool(pool_id: &Self::PoolId, mutator: F) -> Result where F: FnMut(&mut Self::Pool) -> Result; + + /// Mutate and maybe remove the pool indexed by `pool_id`. Unlike `try_mutate_exists` in + /// `StorageMap`, the `mutator` must return a `(R, bool)`. If and only if the pool is positive, + /// the pool is removed. + fn try_mutate_exists(pool_id: &Self::PoolId, mutator: F) -> Result + where + F: FnMut(&mut Self::Pool) -> Result<(R, bool), DispatchError>; } diff --git a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs index a0a4363af..a3841345a 100644 --- a/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs +++ b/zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs @@ -19,7 +19,7 @@ use crate::{ liquidity_tree::types::LiquidityTree, - types::{DecisionMarketOracle, Pool, PoolType}, + types::{DecisionMarketOracle, DecisionMarketOracleScoreboard, Pool, PoolType}, BalanceOf, Config, MarketIdOf, Pallet, Pools, }; use alloc::{collections::BTreeMap, vec}; @@ -58,6 +58,13 @@ where reserves.insert(negative_outcome, one); } + let scoreboard = DecisionMarketOracleScoreboard::new( + Zero::zero(), + Zero::zero(), + Zero::zero(), + Zero::zero(), + ); + let account_id: T::AccountId = Pallet::::pool_account_id(&pool_id); let pool = Pool { account_id: account_id.clone(), @@ -72,6 +79,6 @@ where Pools::::insert(pool_id, pool); - DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome) + DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome, scoreboard) } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle.rs b/zrml/neo-swaps/src/types/decision_market_oracle.rs index 2d27f247b..b7650326b 100644 --- a/zrml/neo-swaps/src/types/decision_market_oracle.rs +++ b/zrml/neo-swaps/src/types/decision_market_oracle.rs @@ -15,13 +15,21 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use crate::{traits::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, Pools}; +use crate::{ + traits::PoolOperations, types::DecisionMarketOracleScoreboard, weights::WeightInfoZeitgeist, + AssetOf, BalanceOf, Config, Error, Pools, +}; use frame_support::pallet_prelude::Weight; +use frame_system::pallet_prelude::BlockNumberFor; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::DispatchError; use zeitgeist_primitives::traits::FutarchyOracle; +/// Struct that implements `FutarchyOracle` using price measurements from liquidity pools. +/// +/// The oracle evaluates to `true` if and only if the `positive_outcome` is more valuable than the +/// `negative_outcome` in the liquidity pool specified by `pool_id`. #[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] pub struct DecisionMarketOracle where @@ -30,6 +38,7 @@ where pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, + scoreboard: DecisionMarketOracleScoreboard, } impl DecisionMarketOracle @@ -40,22 +49,19 @@ where pool_id: T::PoolId, positive_outcome: AssetOf, negative_outcome: AssetOf, + scoreboard: DecisionMarketOracleScoreboard, ) -> Self { - Self { pool_id, positive_outcome, negative_outcome } + Self { pool_id, positive_outcome, negative_outcome, scoreboard } } - // Utility implementation that uses the question mark operator to implement a fallible version - // of `evaluate`. - fn try_evaluate(&self) -> Result { + fn try_get_prices(&self) -> Result<(BalanceOf, BalanceOf), DispatchError> { let pool = Pools::::get(self.pool_id) .ok_or::(Error::::PoolNotFound.into())?; let positive_value = pool.calculate_spot_price(self.positive_outcome)?; let negative_value = pool.calculate_spot_price(self.negative_outcome)?; - let success = positive_value > negative_value; - - Ok(success) + Ok((positive_value, negative_value)) } } @@ -63,11 +69,21 @@ impl FutarchyOracle for DecisionMarketOracle where T: Config, { + type BlockNumber = BlockNumberFor; + fn evaluate(&self) -> (Weight, bool) { - // Err on the side of caution if the pool is not found or a calculation fails by not - // enacting the policy. - let value = self.try_evaluate().unwrap_or(false); + (T::WeightInfo::decision_market_oracle_evaluate(), self.scoreboard.evaluate()) + } + + fn update(&mut self, now: Self::BlockNumber) -> Weight { + if let Ok((positive_outcome_price, negative_outcome_price)) = self.try_get_prices() { + self.scoreboard.update(now, positive_outcome_price, negative_outcome_price); + } else { + // Err on the side of caution if the pool is not found or a calculation fails by not + // enacting the policy. + self.scoreboard.skip_update(now); + } - (T::WeightInfo::decision_market_oracle_evaluate(), value) + T::WeightInfo::decision_market_oracle_update() } } diff --git a/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs new file mode 100644 index 000000000..1f718a35c --- /dev/null +++ b/zrml/neo-swaps/src/types/decision_market_oracle_scoreboard.rs @@ -0,0 +1,100 @@ +use crate::{BalanceOf, Config}; +use frame_system::pallet_prelude::BlockNumberFor; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, Saturating}; +use zeitgeist_primitives::math::fixed::FixedDiv; + +/// Records until the end of time. +#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)] +pub struct DecisionMarketOracleScoreboard +where + T: Config, +{ + /// The block at which the oracle records its first tick. + start: BlockNumberFor, + + /// The number of ticks the positive outcome requires to have + victory_margin: u128, + + /// The absolute minimum difference in prices required for the positive outcome to receive a + /// point. + price_margin_abs: BalanceOf, + + /// The relative minimum difference in prices required for the positive outcome to receive a + /// point, specified as fractional (i.e. 0.1 represents 10%). + price_margin_rel: BalanceOf, + + /// The number of ticks for the positive outcome. + pass_score: u128, + + /// The number of ticks for the negative outcome. + reject_score: u128, +} + +impl DecisionMarketOracleScoreboard +where + T: Config, +{ + pub fn new( + start: BlockNumberFor, + victory_margin: u128, + price_margin_abs: BalanceOf, + price_margin_rel: BalanceOf, + ) -> DecisionMarketOracleScoreboard { + DecisionMarketOracleScoreboard { + start, + victory_margin, + price_margin_abs, + price_margin_rel, + pass_score: 0, + reject_score: 0, + } + } + + pub fn update( + &mut self, + now: BlockNumberFor, + positive_outcome_price: BalanceOf, + negative_outcome_price: BalanceOf, + ) { + if now < self.start { + return; + } + + // Saturation is fine as that just means that the negative outcome is more valuable than the + // positive outcome. + let margin_abs = positive_outcome_price.saturating_sub(negative_outcome_price); + // In case of error, we're using zero here as a defensive default value. + let margin_rel = margin_abs.bdiv(negative_outcome_price).unwrap_or(Zero::zero()); + + if margin_abs >= self.price_margin_abs && margin_rel >= self.price_margin_rel { + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.pass_score.saturating_inc(); + } else { + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.reject_score.saturating_inc(); + } + } + + pub fn evaluate(&self) -> bool { + // Saturating is fine as that just means that the `reject_score` is higher than `pass_score` + // anyways. + let score_margin = self.pass_score.saturating_sub(self.reject_score); + + score_margin >= self.victory_margin + } + + /// Skips update on this block and awards a point to the negative outcome. + pub fn skip_update(&mut self, now: BlockNumberFor) { + if now < self.start { + return; + } + + // Saturation is fine as that would mean the oracle has been collecting data for + // hundreds of years. + self.reject_score.saturating_inc(); + } +} diff --git a/zrml/neo-swaps/src/types/mod.rs b/zrml/neo-swaps/src/types/mod.rs index 057dcd204..5d7723ede 100644 --- a/zrml/neo-swaps/src/types/mod.rs +++ b/zrml/neo-swaps/src/types/mod.rs @@ -17,6 +17,7 @@ mod decision_market_benchmark_helper; mod decision_market_oracle; +mod decision_market_oracle_scoreboard; mod fee_distribution; mod max_assets; mod pool; @@ -25,6 +26,7 @@ mod pool_type; #[cfg(feature = "runtime-benchmarks")] pub use decision_market_benchmark_helper::*; pub use decision_market_oracle::*; +pub use decision_market_oracle_scoreboard::*; pub(crate) use fee_distribution::*; pub(crate) use max_assets::*; pub(crate) use pool::*; diff --git a/zrml/neo-swaps/src/utility.rs b/zrml/neo-swaps/src/utility.rs index 62103304b..58a0e8792 100644 --- a/zrml/neo-swaps/src/utility.rs +++ b/zrml/neo-swaps/src/utility.rs @@ -15,9 +15,13 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . -use sp_runtime::SaturatedConversion; +use sp_runtime::{ + traits::{One, Zero}, + SaturatedConversion, +}; pub(crate) trait LogCeil { + /// Calculates the ceil of the log with base 2 of `self`. fn log_ceil(&self) -> Self; } @@ -25,10 +29,45 @@ impl LogCeil for u16 { fn log_ceil(&self) -> Self { let x = *self; - let bits_minus_one = u16::MAX.saturating_sub(1); + if x.is_zero() { + return One::one(); + } + + let bits_minus_one: u16 = u16::BITS.saturating_sub(1).saturated_into(); let leading_zeros: u16 = x.leading_zeros().saturated_into(); let floor_log2 = bits_minus_one.saturating_sub(leading_zeros); if x.is_power_of_two() { floor_log2 } else { floor_log2.saturating_add(1) } } } + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(0, 1)] + #[test_case(1, 0)] + #[test_case(2, 1)] + #[test_case(3, 2)] + #[test_case(4, 2)] + #[test_case(5, 3)] + #[test_case(6, 3)] + #[test_case(7, 3)] + #[test_case(8, 3)] + #[test_case(9, 4)] + #[test_case(15, 4)] + #[test_case(16, 4)] + #[test_case(17, 5)] + #[test_case(1023, 10)] + #[test_case(1024, 10)] + #[test_case(1025, 11)] + #[test_case(32767, 15)] + #[test_case(32768, 15)] + #[test_case(32769, 16)] + #[test_case(65535, 16)] + fn log_ceil_works(value: u16, expected: u16) { + let actual = value.log_ceil(); + assert_eq!(actual, expected); + } +} diff --git a/zrml/neo-swaps/src/weights.rs b/zrml/neo-swaps/src/weights.rs index ef359b861..aabda3571 100644 --- a/zrml/neo-swaps/src/weights.rs +++ b/zrml/neo-swaps/src/weights.rs @@ -19,21 +19,21 @@ //! Autogenerated weights for zrml_neo_swaps //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: `2024-10-31`, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: `2024-12-10`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ztg-benchmark`, CPU: `AMD EPYC 7601 32-Core Processor` +//! HOSTNAME: `Mac`, CPU: `` //! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/zeitgeist +// ./target/release/zeitgeist // benchmark // pallet // --chain=dev -// --steps=50 -// --repeat=20 +// --steps=2 +// --repeat=0 // --pallet=zrml_neo_swaps // --extrinsic=* -// --execution=wasm +// --execution=native // --wasm-execution=compiled // --heap-pages=4096 // --template=./misc/weight_template.hbs @@ -59,8 +59,9 @@ pub trait WeightInfoZeitgeist { fn deploy_pool(n: u32) -> Weight; fn combo_buy(n: u32) -> Weight; fn combo_sell(n: u32) -> Weight; - fn deploy_combinatorial_pool(n: u32) -> Weight; + fn deploy_combinatorial_pool(n: u32, m: u32) -> Weight; fn decision_market_oracle_evaluate() -> Weight; + fn decision_market_oracle_update() -> Weight; } /// Weight functions for zrml_neo_swaps (automatically generated) @@ -77,19 +78,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn buy(n: u32) -> Weight { + fn buy(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1372 + n * (182 ±0)` - // Estimated: `156294 + n * (2612 ±0)` - // Minimum execution time: 402_539 nanoseconds. - Weight::from_parts(302_340_193, 156294) - // Standard Error: 92_254 - .saturating_add(Weight::from_parts(53_376_748, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + // Measured: `1335 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 231_000 nanoseconds. + Weight::from_parts(3_880_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -102,19 +98,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn sell(n: u32) -> Weight { + fn sell(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `1503 + n * (182 ±0)` - // Estimated: `156294 + n * (2612 ±0)` - // Minimum execution time: 337_007 nanoseconds. - Weight::from_parts(250_443_677, 156294) - // Standard Error: 116_699 - .saturating_add(Weight::from_parts(60_461_360, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + // Measured: `1466 + n * (183 ±0)` + // Estimated: `337938` + // Minimum execution time: 207_000 nanoseconds. + Weight::from_parts(4_459_000_000, 337938) + .saturating_add(T::DbWeight::get().reads(262)) + .saturating_add(T::DbWeight::get().writes(261)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -125,19 +116,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_in_place(n: u32) -> Weight { + fn join_in_place(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139400 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 396_540 nanoseconds. - Weight::from_parts(350_023_672, 156294) - // Standard Error: 157_275 - .saturating_add(Weight::from_parts(31_812_304, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139361 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 235_000 nanoseconds. + Weight::from_parts(2_362_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -148,19 +134,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_reassigned(n: u32) -> Weight { + fn join_reassigned(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139196 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 405_608 nanoseconds. - Weight::from_parts(376_463_342, 156294) - // Standard Error: 158_653 - .saturating_add(Weight::from_parts(32_337_731, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139157 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 230_000 nanoseconds. + Weight::from_parts(2_864_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -171,19 +152,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn join_leaf(n: u32) -> Weight { + fn join_leaf(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `138700 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 470_430 nanoseconds. - Weight::from_parts(404_406_469, 156294) - // Standard Error: 162_346 - .saturating_add(Weight::from_parts(31_654_906, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `138661 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 279_000 nanoseconds. + Weight::from_parts(2_348_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -194,19 +170,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn exit(n: u32) -> Weight { + fn exit(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `139297 + n * (216 ±0)` - // Estimated: `156294 + n * (5224 ±0)` - // Minimum execution time: 433_429 nanoseconds. - Weight::from_parts(445_783_938, 156294) - // Standard Error: 148_856 - .saturating_add(Weight::from_parts(31_235_322, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `139258 + n * (217 ±0)` + // Estimated: `669662` + // Minimum execution time: 235_000 nanoseconds. + Weight::from_parts(2_551_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(259)) + .saturating_add(T::DbWeight::get().writes(257)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -216,8 +187,8 @@ impl WeightInfoZeitgeist for WeightInfo { // Proof Size summary in bytes: // Measured: `137883` // Estimated: `156294` - // Minimum execution time: 319_797 nanoseconds. - Weight::from_parts(365_799_000, 156294) + // Minimum execution time: 183_000 nanoseconds. + Weight::from_parts(183_000_000, 156294) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -234,19 +205,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[2, 128]`. - fn deploy_pool(n: u32) -> Weight { + fn deploy_pool(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `611 + n * (81 ±0)` - // Estimated: `4173 + n * (5224 ±0)` - // Minimum execution time: 159_294 nanoseconds. - Weight::from_parts(100_340_149, 4173) - // Standard Error: 67_332 - .saturating_add(Weight::from_parts(33_452_544, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 5224).saturating_mul(n.into())) + // Measured: `593 + n * (81 ±0)` + // Estimated: `669662` + // Minimum execution time: 106_000 nanoseconds. + Weight::from_parts(3_041_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(260)) + .saturating_add(T::DbWeight::get().writes(260)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -259,19 +225,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_buy(n: u32) -> Weight { + fn combo_buy(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (2721 ±0)` - // Estimated: `156294 + n * (38153 ±999)` - // Minimum execution time: 462_410 nanoseconds. - Weight::from_parts(465_820_000, 156294) - // Standard Error: 23_261_249 - .saturating_add(Weight::from_parts(901_905_964, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(10)) - .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) + // Measured: `0 + n * (5912 ±0)` + // Estimated: `669662` + // Minimum execution time: 257_000 nanoseconds. + Weight::from_parts(8_850_000_000, 669662) + .saturating_add(T::DbWeight::get().reads(395)) + .saturating_add(T::DbWeight::get().writes(388)) } /// Storage: `NeoSwaps::Pools` (r:1 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) @@ -284,19 +245,14 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `Tokens::TotalIssuance` (r:128 w:128) /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn combo_sell(n: u32) -> Weight { + fn combo_sell(_n: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `0 + n * (3627 ±0)` - // Estimated: `156294 + n * (38153 ±484)` - // Minimum execution time: 523_942 nanoseconds. - Weight::from_parts(527_472_000, 156294) - // Standard Error: 38_920_367 - .saturating_add(Weight::from_parts(1_535_695_671, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().reads((23_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(9)) - .saturating_add(T::DbWeight::get().writes((22_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 38153).saturating_mul(n.into())) + // Measured: `0 + n * (7938 ±0)` + // Estimated: `667050` + // Minimum execution time: 298_000 nanoseconds. + Weight::from_parts(14_050_000_000, 667050) + .saturating_add(T::DbWeight::get().reads(394)) + .saturating_add(T::DbWeight::get().writes(387)) } /// Storage: `MarketCommons::Markets` (r:7 w:0) /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) @@ -311,27 +267,37 @@ impl WeightInfoZeitgeist for WeightInfo { /// Storage: `NeoSwaps::Pools` (r:0 w:1) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 7]`. - fn deploy_combinatorial_pool(n: u32) -> Weight { + /// The range of component `m` is `[32, 64]`. + fn deploy_combinatorial_pool(n: u32, m: u32) -> Weight { // Proof Size summary in bytes: - // Measured: `357 + n * (185 ±0)` - // Estimated: `11438 + n * (57229 ±969)` - // Minimum execution time: 7_103_129 nanoseconds. - Weight::from_parts(7_159_730_000, 11438) - // Standard Error: 1_623_272_081 - .saturating_add(Weight::from_parts(61_965_055_407, 0).saturating_mul(n.into())) + // Measured: `351 + n * (185 ±0)` + // Estimated: `11438 + n * (156551 ±6_414)` + // Minimum execution time: 3_344_000 nanoseconds. + Weight::from_parts(3_344_000_000, 11438) + // Standard Error: 16_685_962_537 + .saturating_add(Weight::from_parts(57_843_965_765, 0).saturating_mul(n.into())) + // Standard Error: 1_759_383_308 + .saturating_add(Weight::from_parts(108_277_083, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().reads((37_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((101_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(10)) - .saturating_add(T::DbWeight::get().writes((37_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 57229).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes((100_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 156551).saturating_mul(n.into())) + } + fn decision_market_oracle_evaluate() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0 nanoseconds. + Weight::from_parts(0, 0) } /// Storage: `NeoSwaps::Pools` (r:1 w:0) /// Proof: `NeoSwaps::Pools` (`max_values`: None, `max_size`: Some(152829), added: 155304, mode: `MaxEncodedLen`) - fn decision_market_oracle_evaluate() -> Weight { + fn decision_market_oracle_update() -> Weight { // Proof Size summary in bytes: // Measured: `492` // Estimated: `156294` - // Minimum execution time: 91_762 nanoseconds. - Weight::from_parts(93_152_000, 156294).saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 47_000 nanoseconds. + Weight::from_parts(47_000_000, 156294).saturating_add(T::DbWeight::get().reads(1)) } } diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index 8d924151e..5623cbd04 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -3063,7 +3063,6 @@ mod pallet { type MarketId = MarketIdOf; fn payout_vector(market_id: Self::MarketId) -> Option> { - // TODO Abstract into separate function so we don't have to litter this with ok() calls. let market = >::market(&market_id).ok()?; if market.status != MarketStatus::Resolved || !market.is_redeemable() {