diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 4ed5639d6..5ad12e800 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +mod combinatorial_tokens_benchmark_helper; mod complete_set_operations_api; mod deploy_pool_api; mod dispute_api; @@ -31,6 +32,7 @@ mod payout_api; mod swaps; mod zeitgeist_asset; +pub use combinatorial_tokens_benchmark_helper::*; pub use complete_set_operations_api::*; pub use deploy_pool_api::*; pub use dispute_api::*; diff --git a/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs new file mode 100644 index 000000000..267d20b56 --- /dev/null +++ b/primitives/src/traits/combinatorial_tokens_benchmark_helper.rs @@ -0,0 +1,13 @@ +use alloc::vec::Vec; +use sp_runtime::DispatchResult; + +pub trait CombinatorialTokensBenchmarkHelper { + type Balance; + type MarketId; + + /// Prepares the market with the specified `market_id` to have a particular `payout`. + fn setup_payout_vector( + market_id: Self::MarketId, + payout: Option>, + ) -> DispatchResult; +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 05b85a417..ea6a299c1 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -93,6 +93,9 @@ macro_rules! decl_common_types { #[cfg(feature = "runtime-benchmarks")] use zrml_neo_swaps::types::DecisionMarketBenchmarkHelper; + #[cfg(feature = "runtime-benchmarks")] + use zrml_prediction_markets::types::PredictionMarketsCombinatorialTokensBenchmarkHelper; + pub type Block = generic::Block; type Address = sp_runtime::MultiAddress; @@ -1154,12 +1157,15 @@ macro_rules! impl_config_traits { } impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PredictionMarketsCombinatorialTokensBenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = AssetManager; type Payout = PredictionMarkets; type RuntimeEvent = RuntimeEvent; type PalletId = CombinatorialTokensPalletId; + type WeightInfo = zrml_combinatorial_tokens::weights::WeightInfo; } impl zrml_court::Config for Runtime { @@ -1453,6 +1459,7 @@ macro_rules! create_runtime_api { list_benchmark!(list, extra, pallet_vesting, Vesting); list_benchmark!(list, extra, zrml_swaps, Swaps); list_benchmark!(list, extra, zrml_authorized, Authorized); + list_benchmark!(list, extra, zrml_combinatorial_tokens, CombinatorialTokens); list_benchmark!(list, extra, zrml_court, Court); list_benchmark!(list, extra, zrml_futarchy, Futarchy); list_benchmark!(list, extra, zrml_global_disputes, GlobalDisputes); @@ -1543,6 +1550,7 @@ macro_rules! create_runtime_api { add_benchmark!(params, batches, pallet_vesting, Vesting); add_benchmark!(params, batches, zrml_swaps, Swaps); add_benchmark!(params, batches, zrml_authorized, Authorized); + add_benchmark!(params, batches, zrml_combinatorial_tokens, CombinatorialTokens); add_benchmark!(params, batches, zrml_court, Court); add_benchmark!(params, batches, zrml_futarchy, Futarchy); add_benchmark!(params, batches, zrml_global_disputes, GlobalDisputes); diff --git a/scripts/benchmarks/configuration.sh b/scripts/benchmarks/configuration.sh index 41ec1f9f9..41409c06a 100644 --- a/scripts/benchmarks/configuration.sh +++ b/scripts/benchmarks/configuration.sh @@ -26,8 +26,9 @@ export ORML_PALLETS_STEPS="${ORML_PALLETS_STEPS:-50}" export ORML_WEIGHT_TEMPLATE="./misc/orml_weight_template.hbs" export ZEITGEIST_PALLETS=( - zrml_authorized zrml_court zrml_futarchy zrml_global_disputes zrml_hybrid_router \ - zrml_neo_swaps zrml_orderbook zrml_parimutuel zrml_prediction_markets zrml_swaps zrml_styx \ + zrml_authorized zrml_combinatorial_tokens zrml_court zrml_futarchy zrml_global_disputes \ + zrml_hybrid_router zrml_neo_swaps zrml_orderbook zrml_parimutuel zrml_prediction_markets \ + zrml_swaps zrml_styx \ ) export ZEITGEIST_PALLETS_RUNS="${ZEITGEIST_PALLETS_RUNS:-20}" export ZEITGEIST_PALLETS_STEPS="${ZEITGEIST_PALLETS_STEPS:-50}" diff --git a/zrml/combinatorial-tokens/src/benchmarking.rs b/zrml/combinatorial-tokens/src/benchmarking.rs new file mode 100644 index 000000000..901821fb1 --- /dev/null +++ b/zrml/combinatorial-tokens/src/benchmarking.rs @@ -0,0 +1,594 @@ +// Copyright 2024 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// Zeitgeist is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{BalanceOf, Call, Config, Event, MarketIdOf, Pallet}; +use alloc::{vec, vec::Vec}; +use frame_benchmarking::v2::*; +use frame_support::dispatch::RawOrigin; +use frame_system::Pallet as System; +use orml_traits::MultiCurrency; +use sp_runtime::{traits::Zero, Perbill}; +use zeitgeist_primitives::{ + math::fixed::{BaseProvider, ZeitgeistBase}, + traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + types::{Asset, Market, MarketCreation, MarketPeriod, MarketStatus, MarketType, ScoringRule}, +}; + +fn create_market(caller: T::AccountId, asset_count: u16) -> MarketIdOf { + let market = Market { + market_id: Default::default(), + base_asset: Asset::Ztg, + creation: MarketCreation::Permissionless, + creator_fee: Perbill::zero(), + creator: caller.clone(), + oracle: caller, + metadata: Default::default(), + market_type: MarketType::Categorical(asset_count), + period: MarketPeriod::Block(0u32.into()..1u32.into()), + deadlines: Default::default(), + scoring_rule: ScoringRule::AmmCdaHybrid, + status: MarketStatus::Active, + report: None, + resolved_outcome: None, + dispute_mechanism: None, + bonds: Default::default(), + early_close: None, + }; + T::MarketCommons::push_market(market).unwrap() +} + +fn create_payout_vector(asset_count: u16) -> Vec> { + let mut result = vec![Zero::zero(); asset_count as usize]; + result[0] = ZeitgeistBase::get().unwrap(); + + result +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn split_position_vertical_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + T::MultiCurrency::deposit(Asset::Ztg, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id, + market_id, + partition, + asset_in: Asset::Ztg, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn split_position_vertical_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let parent_market_id = create_market::(alice.clone(), 2); + + // The collection/position that we're merging into. + let cid_01 = Pallet::::collection_id_from_parent_collection( + parent_collection_id, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + T::MultiCurrency::deposit(pos_01, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + Some(cid_01), + child_market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(child_market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + partition, + asset_in: pos_01, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn split_position_horizontal(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + let asset_count = position_count + 1; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...010. Doesn't contain 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; asset_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + // Add 1...10 to Alice's account. + let mut asset_in_index_set = vec![true; asset_count]; + *asset_in_index_set.last_mut().unwrap() = false; + let asset_in = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + asset_in_index_set, + false, + ) + .unwrap(); + T::MultiCurrency::deposit(asset_in, &alice, amount).unwrap(); + + #[extrinsic_call] + split_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let collection_ids: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::collection_id_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + let assets_out: Vec<_> = collection_ids + .iter() + .cloned() + .map(|collection_id| { + Pallet::::position_from_collection_id(market_id, collection_id).unwrap() + }) + .collect(); + let expected_event = ::RuntimeEvent::from(Event::::TokenSplit { + who: alice, + parent_collection_id, + market_id, + partition, + asset_in, + assets_out, + collection_ids, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_vertical_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + T::MultiCurrency::deposit(Asset::Ztg, &Pallet::::account_id(), amount).unwrap(); + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id, + market_id, + partition, + asset_out: Asset::Ztg, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_vertical_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + + let parent_collection_id = None; + let parent_market_id = create_market::(alice.clone(), 2); + + // The collection/position that we're merging into. + let cid_01 = Pallet::::collection_id_from_parent_collection( + parent_collection_id, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), position_count.try_into().unwrap()); + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; position_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + Some(cid_01), + child_market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + partition.clone(), + amount, + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + partition, + asset_out: pos_01, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn merge_position_horizontal(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let position_count: usize = n.try_into().unwrap(); + let asset_count = position_count + 1; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count.try_into().unwrap()); + // Partition is 10...0, 010...0, ..., 0...010. Doesn't contain 0...01. + let partition: Vec<_> = (0..position_count) + .map(|index| { + let mut index_set = vec![false; asset_count]; + index_set[index] = true; + + index_set + }) + .collect(); + let amount = ZeitgeistBase::get().unwrap(); + + let assets_in: Vec<_> = partition + .iter() + .cloned() + .map(|index_set| { + Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set, + false, + ) + .unwrap() + }) + .collect(); + + for &asset in assets_in.iter() { + T::MultiCurrency::deposit(asset, &alice, amount).unwrap(); + } + + #[extrinsic_call] + merge_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + partition.clone(), + amount, + true, + ); + + let mut asset_out_index_set = vec![true; asset_count]; + *asset_out_index_set.last_mut().unwrap() = false; + let asset_out = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + asset_out_index_set, + false, + ) + .unwrap(); + let expected_event = ::RuntimeEvent::from(Event::::TokenMerged { + who: alice, + parent_collection_id, + market_id, + partition, + asset_out, + assets_in, + amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn redeem_position_sans_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let n_u16: u16 = n.try_into().unwrap(); + let asset_count = n_u16 + 1; + + // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` + // loop in `redeem_position`. + let mut index_set = vec![true; asset_count as usize]; + *index_set.last_mut().unwrap() = false; + + let parent_collection_id = None; + let market_id = create_market::(alice.clone(), asset_count); + + let payout_vector = create_payout_vector::(asset_count); + T::BenchmarkHelper::setup_payout_vector(market_id, Some(payout_vector)).unwrap(); + + // Deposit tokens for Alice and the pallet account. + let position = Pallet::::position_from_parent_collection( + parent_collection_id, + market_id, + index_set.clone(), + false, + ) + .unwrap(); + let amount = ZeitgeistBase::get().unwrap(); + T::MultiCurrency::deposit(position, &alice, amount).unwrap(); + T::MultiCurrency::deposit(Asset::Ztg, &Pallet::::account_id(), amount).unwrap(); + + #[extrinsic_call] + redeem_position( + RawOrigin::Signed(alice.clone()), + parent_collection_id, + market_id, + index_set.clone(), + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { + who: alice, + parent_collection_id, + market_id, + index_set, + asset_in: position, + amount_in: amount, + asset_out: Asset::Ztg, + amount_out: amount, + }); + System::::assert_last_event(expected_event.into()); + } + + #[benchmark] + fn redeem_position_with_parent(n: Linear<2, 32>) { + let alice: T::AccountId = whitelisted_caller(); + + let n_u16: u16 = n.try_into().unwrap(); + let asset_count = n_u16 + 1; + + // `index_set` has `n` entries that are `true`, which results in `n` iterations in the `for` + // loop in `redeem_position`. + let mut index_set = vec![true; asset_count as usize]; + *index_set.last_mut().unwrap() = false; + + let parent_market_id = create_market::(alice.clone(), 2); + let cid_01 = Pallet::::collection_id_from_parent_collection( + None, + parent_market_id, + vec![false, true], + false, + ) + .unwrap(); + let pos_01 = Pallet::::position_from_collection_id(parent_market_id, cid_01).unwrap(); + + let child_market_id = create_market::(alice.clone(), asset_count); + let pos_01_10 = Pallet::::position_from_parent_collection( + Some(cid_01), + child_market_id, + index_set.clone(), + false, + ) + .unwrap(); + let amount = ZeitgeistBase::get().unwrap(); + T::MultiCurrency::deposit(pos_01_10, &alice, amount).unwrap(); + + let payout_vector = create_payout_vector::(asset_count); + T::BenchmarkHelper::setup_payout_vector(child_market_id, Some(payout_vector)).unwrap(); + + #[extrinsic_call] + redeem_position( + RawOrigin::Signed(alice.clone()), + Some(cid_01), + child_market_id, + index_set.clone(), + true, + ); + + let expected_event = ::RuntimeEvent::from(Event::::TokenRedeemed { + who: alice, + parent_collection_id: Some(cid_01), + market_id: child_market_id, + index_set, + asset_in: pos_01_10, + amount_in: amount, + asset_out: pos_01, + amount_out: amount, + }); + System::::assert_last_event(expected_event.into()); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::ext_builder::ExtBuilder::build(), + crate::mock::runtime::Runtime + ); +} diff --git a/zrml/combinatorial-tokens/src/lib.rs b/zrml/combinatorial-tokens/src/lib.rs index 6f4a34c9c..0706ca56a 100644 --- a/zrml/combinatorial-tokens/src/lib.rs +++ b/zrml/combinatorial-tokens/src/lib.rs @@ -27,21 +27,23 @@ extern crate alloc; +mod benchmarking; pub mod mock; mod tests; mod traits; pub mod types; +pub mod weights; pub use pallet::*; #[frame_support::pallet] mod pallet { - use crate::traits::CombinatorialIdManager; + use crate::{traits::CombinatorialIdManager, weights::WeightInfoZeitgeist}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use frame_support::{ ensure, - pallet_prelude::{IsType, StorageVersion}, + pallet_prelude::{DispatchResultWithPostInfo, IsType, StorageVersion}, require_transactional, transactional, PalletId, }; use frame_system::{ @@ -51,7 +53,7 @@ mod pallet { use orml_traits::MultiCurrency; use sp_runtime::{ traits::{AccountIdConversion, Get, Zero}, - DispatchError, DispatchResult, + DispatchError, SaturatedConversion, }; use zeitgeist_primitives::{ math::{checked_ops_res::CheckedAddRes, fixed::FixedMul}, @@ -59,8 +61,17 @@ mod pallet { types::{Asset, CombinatorialId}, }; + #[cfg(feature = "runtime-benchmarks")] + use zeitgeist_primitives::traits::CombinatorialTokensBenchmarkHelper; + #[pallet::config] pub trait Config: frame_system::Config { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: CombinatorialTokensBenchmarkHelper< + Balance = BalanceOf, + MarketId = MarketIdOf, + >; + type CombinatorialIdManager: CombinatorialIdManager< Asset = AssetOf, MarketId = MarketIdOf, @@ -77,6 +88,8 @@ mod pallet { #[pallet::constant] type PalletId: Get; + + type WeightInfo: WeightInfoZeitgeist; } #[pallet::pallet] @@ -103,8 +116,9 @@ mod pallet { /// User `who` has split `amount` units of token `asset_in` into the same amount of each /// token in `assets_out` using `partition`. The ith element of `partition` matches the ith /// element of `assets_out`, so `assets_out[i]` is the outcome represented by the specified - /// `parent_collection_id` together with `partition` in `market_id`. - /// TODO The second sentence is confusing. + /// `parent_collection_id` when split using `partition[i]` in `market_id`. The same goes for + /// the `collection_ids` vector, the ith element of which specifies the collection ID of + /// `assets_out[i]`. TokenSplit { who: AccountIdOf, parent_collection_id: Option, @@ -117,13 +131,35 @@ mod pallet { }, /// User `who` has merged `amount` units of each of the tokens in `assets_in` into the same - /// amount of `asset_out`. + /// amount of `asset_out`. The ith element of the `partition` matches the ith element of + /// `assets_in`, so `assets_in[i]` is the outcome represented by the specified + /// `parent_collection_id` when split using `partition[i]` in `market_id`. Note that the + /// `parent_collection_id` is equal to the collection ID of the position `asset_out`; if + /// `asset_out` is the collateral token, then `parent_collection_id` is `None`. TokenMerged { who: AccountIdOf, + parent_collection_id: Option, + market_id: MarketIdOf, + partition: Vec>, asset_out: AssetOf, assets_in: Vec>, amount: BalanceOf, }, + + /// User `who` has redeemed `amount_in` units of `asset_in` for `amount_out` units of + /// `asset_out` using the report for the market specified by `market_id`. The + /// `parent_collection_id` specifies the collection ID of the `asset_out`; it is `None` if + /// the `asset_out` is the collateral token. + TokenRedeemed { + who: AccountIdOf, + parent_collection_id: Option, + market_id: MarketIdOf, + index_set: Vec, + asset_in: AssetOf, + amount_in: BalanceOf, + asset_out: AssetOf, + amount_out: BalanceOf, + }, } #[pallet::error] @@ -154,7 +190,13 @@ mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight({0})] // TODO + #[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())) + )] #[transactional] pub fn split_position( origin: OriginFor, @@ -163,13 +205,27 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_split_position(who, parent_collection_id, market_id, partition, amount) + Self::do_split_position( + who, + parent_collection_id, + market_id, + partition, + amount, + force_max_work, + ) } #[pallet::call_index(1)] - #[pallet::weight({0})] // TODO + #[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())) + )] #[transactional] pub fn merge_position( origin: OriginFor, @@ -177,22 +233,40 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_merge_position(who, parent_collection_id, market_id, partition, amount) + Self::do_merge_position( + who, + parent_collection_id, + market_id, + partition, + amount, + force_max_work, + ) } #[pallet::call_index(2)] - #[pallet::weight({0})] // TODO + #[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())) + )] #[transactional] pub fn redeem_position( origin: OriginFor, parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_redeem_position(who, parent_collection_id, market_id, index_set) + Self::do_redeem_position( + who, + parent_collection_id, + market_id, + index_set, + force_max_work, + ) } } @@ -204,14 +278,15 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; let free_index_set = Self::free_index_set(market_id, &partition)?; // Destroy/store the tokens to be split. - let split_asset = if !free_index_set.iter().any(|&i| i) { + let (weight, split_asset) = if !free_index_set.iter().any(|&i| i) { // Vertical split. if let Some(pci) = parent_collection_id { // Split combinatorial token into higher level position. Destroy the tokens. @@ -224,7 +299,11 @@ mod pallet { T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - position + let weight = T::WeightInfo::split_position_vertical_with_parent( + partition.len().saturated_into(), + ); + + (weight, position) } else { // Split collateral into first level position. Store the collateral in the // pallet account. This is the legacy `buy_complete_set`. @@ -236,7 +315,11 @@ mod pallet { amount, )?; - collateral_token + let weight = T::WeightInfo::split_position_vertical_sans_parent( + partition.len().saturated_into(), + ); + + (weight, collateral_token) } } else { // Horizontal split. @@ -245,11 +328,15 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, + force_max_work, )?; T::MultiCurrency::ensure_can_withdraw(position, &who, amount)?; T::MultiCurrency::withdraw(position, &who, amount)?; - position + let weight = + T::WeightInfo::split_position_horizontal(partition.len().saturated_into()); + + (weight, position) }; // Deposit the new tokens. @@ -261,6 +348,7 @@ mod pallet { parent_collection_id, market_id, index_set, + force_max_work, ) }) .collect::, _>>()?; @@ -269,6 +357,8 @@ mod pallet { .cloned() .map(|collection_id| Self::position_from_collection_id(market_id, collection_id)) .collect::, _>>()?; + // Security note: Safe as iterations are limited to the number of assets in the market + // thanks to the `ensure!` invocations in `Self::free_index_set`. for &position in positions.iter() { T::MultiCurrency::deposit(position, &who, amount)?; } @@ -284,7 +374,7 @@ mod pallet { amount, }); - Ok(()) + Ok(Some(weight).into()) } #[require_transactional] @@ -294,13 +384,14 @@ mod pallet { market_id: MarketIdOf, partition: Vec>, amount: BalanceOf, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let market = T::MarketCommons::market(&market_id)?; let collateral_token = market.base_asset; let free_index_set = Self::free_index_set(market_id, &partition)?; - // Destory the old tokens. + // Destroy the old tokens. let positions = partition .iter() .cloned() @@ -309,15 +400,18 @@ mod pallet { parent_collection_id, market_id, index_set, + force_max_work, ) }) .collect::, _>>()?; + // Security note: Safe as iterations are limited to the number of assets in the market + // thanks to the `ensure!` invocations in `Self::free_index_set`. for &position in positions.iter() { T::MultiCurrency::withdraw(position, &who, amount)?; } // Destroy/store the tokens to be split. - let merged_token = if !free_index_set.iter().any(|&i| i) { + let (weight, merged_token) = if !free_index_set.iter().any(|&i| i) { // Vertical merge. if let Some(pci) = parent_collection_id { // Merge combinatorial token into higher level position. Destroy the tokens. @@ -326,7 +420,11 @@ mod pallet { let position = Asset::CombinatorialToken(position_id); T::MultiCurrency::deposit(position, &who, amount)?; - position + let weight = T::WeightInfo::merge_position_vertical_with_parent( + partition.len().saturated_into(), + ); + + (weight, position) } else { // Merge first-level tokens into collateral. Move collateral from the pallet // account to the user's wallet. This is the legacy `sell_complete_set`. @@ -337,7 +435,11 @@ mod pallet { amount, )?; - collateral_token + let weight = T::WeightInfo::merge_position_vertical_sans_parent( + partition.len().saturated_into(), + ); + + (weight, collateral_token) } } else { // Horizontal merge. @@ -346,20 +448,27 @@ mod pallet { parent_collection_id, market_id, remaining_index_set, + force_max_work, )?; T::MultiCurrency::deposit(position, &who, amount)?; - position + let weight = + T::WeightInfo::merge_position_horizontal(partition.len().saturated_into()); + + (weight, position) }; Self::deposit_event(Event::::TokenMerged { who, + parent_collection_id, + market_id, + partition, asset_out: merged_token, assets_in: positions, amount, }); - Ok(()) + Ok(Some(weight).into()) } fn do_redeem_position( @@ -367,7 +476,8 @@ mod pallet { parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, - ) -> DispatchResult { + force_max_work: bool, + ) -> DispatchResultWithPostInfo { let payout_vector = T::Payout::payout_vector(market_id).ok_or(Error::::PayoutVectorNotFound)?; @@ -381,6 +491,8 @@ mod pallet { // Add up values of each outcome. let mut total_stake: BalanceOf = Zero::zero(); + // Security note: Safe because `zip` will limit this loop to `payout_vector.len()` + // iterations. for (&index, value) in index_set.iter().zip(payout_vector.iter()) { if index { total_stake = total_stake.checked_add_res(value)?; @@ -389,19 +501,28 @@ mod pallet { ensure!(!total_stake.is_zero(), Error::::TokenHasNoValue); - let position = - Self::position_from_parent_collection(parent_collection_id, market_id, index_set)?; + let position = Self::position_from_parent_collection( + parent_collection_id, + market_id, + index_set.clone(), + force_max_work, + )?; let amount = T::MultiCurrency::free_balance(position, &who); ensure!(!amount.is_zero(), Error::::NoTokensFound); T::MultiCurrency::withdraw(position, &who, amount)?; let total_payout = total_stake.bmul(amount)?; - if let Some(pci) = parent_collection_id { + let (weight, asset_out) = if let Some(pci) = parent_collection_id { // Merge combinatorial token into higher level position. Destroy the tokens. let position_id = T::CombinatorialIdManager::get_position_id(collateral_token, pci); 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()); + + (weight, position) } else { T::MultiCurrency::transfer( collateral_token, @@ -409,16 +530,32 @@ mod pallet { &who, total_payout, )?; - } - Ok(()) + let weight = + T::WeightInfo::redeem_position_sans_parent(index_set.len().saturated_into()); + + (weight, collateral_token) + }; + + Self::deposit_event(Event::::TokenRedeemed { + who, + parent_collection_id, + market_id, + index_set, + asset_in: position, + amount_in: amount, + asset_out, + amount_out: total_payout, + }); + + Ok(Some(weight).into()) } pub(crate) fn account_id() -> T::AccountId { T::PalletId::get().into_account_truncating() } - fn free_index_set( + pub(crate) fn free_index_set( market_id: MarketIdOf, partition: &[Vec], ) -> Result, DispatchError> { @@ -447,21 +584,22 @@ mod pallet { Ok(free_index_set) } - fn collection_id_from_parent_collection( + pub(crate) fn collection_id_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, + force_max_work: bool, ) -> Result, DispatchError> { T::CombinatorialIdManager::get_collection_id( parent_collection_id, market_id, index_set, - false, // TODO Expose this parameter! + force_max_work, ) .ok_or(Error::::InvalidCollectionId.into()) } - fn position_from_collection_id( + pub(crate) fn position_from_collection_id( market_id: MarketIdOf, collection_id: CombinatorialIdOf, ) -> Result, DispatchError> { @@ -475,15 +613,17 @@ mod pallet { Ok(asset) } - fn position_from_parent_collection( + pub(crate) fn position_from_parent_collection( parent_collection_id: Option>, market_id: MarketIdOf, index_set: Vec, + force_max_work: bool, ) -> Result, DispatchError> { let collection_id = Self::collection_id_from_parent_collection( parent_collection_id, market_id, index_set, + force_max_work, )?; Self::position_from_collection_id(market_id, collection_id) diff --git a/zrml/combinatorial-tokens/src/mock/runtime.rs b/zrml/combinatorial-tokens/src/mock/runtime.rs index 157841ca7..ede07e829 100644 --- a/zrml/combinatorial-tokens/src/mock/runtime.rs +++ b/zrml/combinatorial-tokens/src/mock/runtime.rs @@ -16,7 +16,7 @@ // along with Zeitgeist. If not, see . use crate as zrml_combinatorial_tokens; -use crate::{mock::types::MockPayout, types::CryptographicIdManager}; +use crate::{mock::types::MockPayout, types::CryptographicIdManager, weights::WeightInfo}; use frame_support::{construct_runtime, traits::Everything, Blake2_256}; use frame_system::mocking::MockBlock; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -30,6 +30,9 @@ use zeitgeist_primitives::{ }, }; +#[cfg(feature = "runtime-benchmarks")] +use crate::mock::types::BenchmarkHelper; + construct_runtime! { pub enum Runtime { CombinatorialTokens: zrml_combinatorial_tokens, @@ -43,12 +46,15 @@ construct_runtime! { } impl zrml_combinatorial_tokens::Config for Runtime { + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = BenchmarkHelper; type CombinatorialIdManager = CryptographicIdManager; type MarketCommons = MarketCommons; type MultiCurrency = Currencies; type Payout = MockPayout; type RuntimeEvent = RuntimeEvent; type PalletId = CombinatorialTokensPalletId; + type WeightInfo = WeightInfo; } impl orml_currencies::Config for Runtime { diff --git a/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs new file mode 100644 index 000000000..36d1438fa --- /dev/null +++ b/zrml/combinatorial-tokens/src/mock/types/benchmark_helper.rs @@ -0,0 +1,25 @@ +use crate::{ + mock::{runtime::Runtime, types::MockPayout}, + BalanceOf, MarketIdOf, +}; +use alloc::vec::Vec; +use sp_runtime::DispatchResult; +use zeitgeist_primitives::traits::CombinatorialTokensBenchmarkHelper; + +pub struct BenchmarkHelper; + +impl CombinatorialTokensBenchmarkHelper for BenchmarkHelper { + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + /// A bit of a messy implementation as this sets the return value of the next `payout_vector` + /// call, regardless of what `_market_id` is. + fn setup_payout_vector( + _market_id: Self::MarketId, + payout: Option>, + ) -> DispatchResult { + MockPayout::set_return_value(payout); + + Ok(()) + } +} diff --git a/zrml/combinatorial-tokens/src/mock/types/mod.rs b/zrml/combinatorial-tokens/src/mock/types/mod.rs index 03136bcda..6f3afbeaf 100644 --- a/zrml/combinatorial-tokens/src/mock/types/mod.rs +++ b/zrml/combinatorial-tokens/src/mock/types/mod.rs @@ -15,6 +15,10 @@ // You should have received a copy of the GNU General Public License // along with Zeitgeist. If not, see . +#[cfg(feature = "runtime-benchmarks")] +mod benchmark_helper; mod payout; -pub use payout::MockPayout; +#[cfg(feature = "runtime-benchmarks")] +pub(crate) use benchmark_helper::BenchmarkHelper; +pub(crate) use payout::MockPayout; diff --git a/zrml/combinatorial-tokens/src/tests/integration.rs b/zrml/combinatorial-tokens/src/tests/integration.rs index ced2f9ae1..83a5162bd 100644 --- a/zrml/combinatorial-tokens/src/tests/integration.rs +++ b/zrml/combinatorial-tokens/src/tests/integration.rs @@ -42,6 +42,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(Asset::Ztg), _99); assert_eq!(alice.free_balance(ct_001), _1); @@ -54,6 +55,7 @@ fn split_followed_by_merge_vertical_no_parent() { market_id, partition, amount, + false, )); assert_eq!(alice.free_balance(Asset::Ztg), _100); assert_eq!(alice.free_balance(ct_001), 0); @@ -94,6 +96,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition.clone(), parent_amount, + false, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -110,6 +113,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition.clone(), child_amount, + false, )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -124,6 +128,7 @@ fn split_followed_by_merge_vertical_with_parent() { child_market_id, child_partition, child_amount, + false, )); assert_eq!(alice.free_balance(ct_001), parent_amount); assert_eq!(alice.free_balance(ct_110), parent_amount); @@ -138,6 +143,7 @@ fn split_followed_by_merge_vertical_with_parent() { parent_market_id, parent_partition, parent_amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); @@ -219,6 +225,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, + false, )); // Split C into C&(U|V) and C&(W|X). @@ -228,6 +235,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, + false, )); // Split A|B into into (A|B)&(U|V) and (A|B)&(W|X). @@ -237,6 +245,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -256,6 +265,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -275,6 +285,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_0, partition_0, amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -294,6 +305,7 @@ fn split_followed_by_merge_vertical_with_parent_in_opposite_order() { market_1, partition_1, amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); @@ -325,6 +337,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -332,6 +345,7 @@ fn split_vertical_followed_by_horizontal_split_no_parent() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + false, )); let ct_001 = CombinatorialToken([ @@ -358,6 +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, )); assert_eq!(alice.free_balance(ct_001), 2 * amount); @@ -383,6 +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, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -425,6 +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, )); assert_ok!(CombinatorialTokens::split_position( alice.signed(), @@ -432,6 +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, )); assert_eq!(alice.free_balance(ct_001), parent_amount - child_amount_first_pass); @@ -451,6 +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, )); let total_child_amount = child_amount_first_pass + child_amount_second_pass; @@ -488,6 +507,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::split_position( @@ -496,6 +516,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + false, )); assert_ok!(CombinatorialTokens::merge_position( @@ -504,6 +525,7 @@ fn split_horizontal_followed_by_merge_horizontal() { market_id, vec![vec![B1, B0, B0], vec![B0, B1, B0]], amount, + 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 b724e57c9..077685f29 100644 --- a/zrml/combinatorial-tokens/src/tests/merge_position.rs +++ b/zrml/combinatorial-tokens/src/tests/merge_position.rs @@ -41,20 +41,35 @@ fn merge_position_works_no_parent( let pallet = Account::new(Pallet::::account_id()).deposit(collateral, amount).unwrap(); + let parent_collection_id = None; let market_id = create_market(collateral, MarketType::Categorical(3)); - + let partition = vec![vec![B0, B0, B1], vec![B1, B1, B0]]; assert_ok!(CombinatorialTokens::merge_position( alice.signed(), - None, + parent_collection_id, market_id, - vec![vec![B0, B0, B1], vec![B1, B1, B0]], + partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), 0); assert_eq!(alice.free_balance(ct_110), 0); assert_eq!(alice.free_balance(collateral), _100); assert_eq!(pallet.free_balance(collateral), 0); + + System::assert_last_event( + Event::::TokenMerged { + who: alice.id, + parent_collection_id, + market_id, + partition, + assets_in: vec![ct_001, ct_110], + asset_out: collateral, + amount, + } + .into(), + ); }); } @@ -82,25 +97,39 @@ fn merge_position_works_parent() { .unwrap(); let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); - let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); // Collection ID of [0, 0, 1]. - let parent_collection_id = [ + let parent_collection_id = Some([ 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, - ]; - + ]); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let partition = vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]]; assert_ok!(CombinatorialTokens::merge_position( alice.signed(), - Some(parent_collection_id), + parent_collection_id, market_id, - vec![vec![B0, B1, B0, B1], vec![B1, B0, B1, B0]], + partition.clone(), amount, + false, )); assert_eq!(alice.free_balance(ct_001), amount); assert_eq!(alice.free_balance(ct_001_0101), 0); assert_eq!(alice.free_balance(ct_001_1010), 0); + + System::assert_last_event( + Event::::TokenMerged { + who: alice.id, + parent_collection_id, + market_id, + partition, + assets_in: vec![ct_001_0101, ct_001_1010], + asset_out: ct_001, + amount, + } + .into(), + ); }); } @@ -131,6 +160,7 @@ fn merge_position_horizontal_works() { market_id, vec![vec![B0, B1, B0], vec![B1, B0, B0]], amount, + false, )); assert_eq!(alice.free_balance(ct_110), amount); @@ -151,6 +181,7 @@ fn merge_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, + false, ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -167,7 +198,14 @@ fn merge_position_fails_on_invalid_partition_length() { let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -183,7 +221,14 @@ fn merge_position_fails_on_trivial_partition_member() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -199,7 +244,14 @@ fn merge_position_fails_on_overlapping_partition_members() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; assert_noop!( - CombinatorialTokens::merge_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::merge_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -220,6 +272,7 @@ fn merge_position_fails_on_insufficient_funds() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); @@ -241,6 +294,7 @@ fn merge_position_fails_on_insufficient_funds_foreign_token() { market_id, vec![vec![B1, B0, B1], vec![B0, B1, B0]], _100, + false, ), orml_tokens::Error::::BalanceTooLow ); diff --git a/zrml/combinatorial-tokens/src/tests/redeem_position.rs b/zrml/combinatorial-tokens/src/tests/redeem_position.rs index a58381206..64a0d9944 100644 --- a/zrml/combinatorial-tokens/src/tests/redeem_position.rs +++ b/zrml/combinatorial-tokens/src/tests/redeem_position.rs @@ -22,11 +22,13 @@ use test_case::test_case; fn redeem_position_fails_on_no_payout_vector() { ExtBuilder::build().execute_with(|| { let alice = Account::new(0).deposit(Asset::Ztg, _100).unwrap(); + let market_id = 0; MockPayout::set_return_value(None); assert_noop!( - CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![]), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, vec![], false), Error::::PayoutVectorNotFound ); + assert!(MockPayout::called_once_with(market_id)); }); } @@ -36,7 +38,7 @@ 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![]), + CombinatorialTokens::redeem_position(alice.signed(), None, 0, vec![], false), zrml_market_commons::Error::::MarketDoesNotExist ); }); @@ -51,7 +53,7 @@ fn redeem_position_fails_on_incorrect_index_set(index_set: Vec) { 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), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::InvalidIndexSet ); }); @@ -65,7 +67,7 @@ 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), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::TokenHasNoValue ); }); @@ -79,7 +81,7 @@ 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), + CombinatorialTokens::redeem_position(alice.signed(), None, market_id, index_set, false), Error::::NoTokensFound, ); }); @@ -93,22 +95,43 @@ fn redeem_position_works_sans_parent() { 225, 102, 57, 241, 199, 18, 226, 137, 68, 3, 219, 131, ]); let alice = Account::new(0).deposit(ct_110, _3).unwrap(); - let pallet = Account::new(Pallet::::account_id()).deposit(Asset::Ztg, _3).unwrap(); + let amount_in = _3; + let pallet = + Account::new(Pallet::::account_id()).deposit(Asset::Ztg, amount_in).unwrap(); MockPayout::set_return_value(Some(vec![_1_4, _1_2, _1_4])); + let parent_collection_id = None; let market_id = create_market(Asset::Ztg, MarketType::Categorical(3)); let index_set = vec![B1, B1, B0]; assert_ok!(CombinatorialTokens::redeem_position( alice.signed(), - None, + parent_collection_id, market_id, - index_set + index_set.clone(), + false, )); assert_eq!(alice.free_balance(ct_110), 0); - assert_eq!(alice.free_balance(Asset::Ztg), _2 + _1_4); + let amount_out = _2 + _1_4; + assert_eq!(alice.free_balance(Asset::Ztg), amount_out); assert_eq!(pallet.free_balance(Asset::Ztg), _3_4); + + System::assert_last_event( + Event::::TokenRedeemed { + who: alice.id, + parent_collection_id, + market_id, + index_set, + asset_in: ct_110, + amount_in, + asset_out: Asset::Ztg, + amount_out, + } + .into(), + ); + + assert!(MockPayout::called_once_with(market_id)); }); } @@ -124,27 +147,46 @@ fn redeem_position_works_with_parent() { 225, 211, 72, 142, 210, 98, 202, 168, 193, 245, 217, 239, 28, ]); - let alice = Account::new(0).deposit(ct_001_0101, _7).unwrap(); + let amount_in = _7; + let alice = Account::new(0).deposit(ct_001_0101, amount_in).unwrap(); MockPayout::set_return_value(Some(vec![_1_4, 0, _1_2, _1_4])); let _ = create_market(Asset::Ztg, MarketType::Categorical(3)); - let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); + let market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); // Collection ID of [0, 0, 1]. - let parent_collection_id = [ + let parent_collection_id = Some([ 6, 44, 173, 50, 122, 106, 144, 185, 253, 19, 252, 218, 215, 241, 218, 37, 196, 112, 45, 133, 165, 48, 231, 189, 87, 123, 131, 18, 190, 5, 110, 93, - ]; + ]); let index_set = vec![B0, B1, B0, B1]; assert_ok!(CombinatorialTokens::redeem_position( alice.signed(), - Some(parent_collection_id), - child_market_id, - index_set + parent_collection_id, + market_id, + index_set.clone(), + false, )); assert_eq!(alice.free_balance(ct_001_0101), 0); - assert_eq!(alice.free_balance(ct_001), _1 + _3_4); + let amount_out = _1 + _3_4; + assert_eq!(alice.free_balance(ct_001), amount_out); + + System::assert_last_event( + Event::::TokenRedeemed { + who: alice.id, + parent_collection_id, + market_id, + index_set, + asset_in: ct_001_0101, + amount_in, + asset_out: ct_001, + amount_out, + } + .into(), + ); + + assert!(MockPayout::called_once_with(market_id)); }); } diff --git a/zrml/combinatorial-tokens/src/tests/split_position.rs b/zrml/combinatorial-tokens/src/tests/split_position.rs index 4a1460931..88873e02b 100644 --- a/zrml/combinatorial-tokens/src/tests/split_position.rs +++ b/zrml/combinatorial-tokens/src/tests/split_position.rs @@ -34,6 +34,7 @@ fn split_position_works_vertical_no_parent() { market_id, partition.clone(), amount, + false, )); let ct_001 = CombinatorialToken([ @@ -89,6 +90,7 @@ fn split_position_works_vertical_with_parent() { parent_market_id, vec![vec![B0, B0, B1], vec![B1, B1, B0]], parent_amount, + false, )); let child_market_id = create_market(Asset::Ztg, MarketType::Categorical(4)); @@ -105,6 +107,7 @@ fn split_position_works_vertical_with_parent() { child_market_id, partition.clone(), child_amount, + false, )); // Alice is left with 1 unit of [0, 0, 1], 2 units of [1, 1, 0] and one unit of each of the @@ -180,6 +183,7 @@ fn split_position_fails_if_market_not_found() { 0, vec![vec![B0, B0, B1], vec![B1, B1, B0]], 1, + false, ), zrml_market_commons::Error::::MarketDoesNotExist, ); @@ -196,7 +200,14 @@ fn split_position_fails_on_invalid_partition_length() { let partition = vec![vec![B1, B0, B1], vec![B0, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false, + ), Error::::InvalidPartition ); }); @@ -212,7 +223,14 @@ fn split_position_fails_on_empty_partition_member() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B0]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1,), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -228,7 +246,14 @@ fn split_position_fails_on_overlapping_partition_members() { let partition = vec![vec![B1, B0, B1], vec![B0, B0, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false, + ), Error::::InvalidPartition ); }); @@ -243,7 +268,14 @@ fn split_position_fails_on_trivial_partition() { let partition = vec![vec![B1, B1, B1]]; assert_noop!( - CombinatorialTokens::split_position(alice.signed(), None, market_id, partition, _1), + CombinatorialTokens::split_position( + alice.signed(), + None, + market_id, + partition, + _1, + false + ), Error::::InvalidPartition ); }); @@ -264,6 +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, ), orml_currencies::Error::::BalanceTooLow ); @@ -285,6 +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, ), orml_currencies::Error::::BalanceTooLow ); @@ -317,6 +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, ), orml_tokens::Error::::BalanceTooLow ); @@ -328,6 +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, )); }); } @@ -352,6 +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, ), orml_tokens::Error::::BalanceTooLow ); @@ -363,6 +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, )); }); } 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 4a437d956..f5f5d0a6f 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 @@ -57,15 +57,14 @@ pub(crate) fn get_collection_id( Some(bytes) } -const DECOMPRESS_HASH_MAX_ITERS: usize = 1_000; +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. /// /// 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. We -/// will use `1_000` iterations as maximum for now. +/// 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 { @@ -107,7 +106,7 @@ fn decompress_hash(hash: CombinatorialId, force_max_work: bool) -> Option. + +//! Autogenerated weights for zrml_combinatorial_tokens +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: `2024-10-24`, STEPS: `2`, REPEAT: `0`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `blackbird`, CPU: `` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/zeitgeist +// benchmark +// pallet +// --chain=dev +// --steps=2 +// --repeat=0 +// --pallet=zrml_combinatorial_tokens +// --extrinsic=* +// --execution=native +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./misc/weight_template.hbs +// --header=./HEADER_GPL3 +// --output=./zrml/combinatorial-tokens/src/weights.rs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use core::marker::PhantomData; +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; +} + +/// Weight functions for zrml_combinatorial_tokens (automatically generated) +pub struct WeightInfo(PhantomData); +impl WeightInfoZeitgeist for WeightInfo { + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(132), added: 2607, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:32 w:32) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `441` + // Estimated: `84574` + // Minimum execution time: 1_923_000 nanoseconds. + Weight::from_parts(29_365_000_000, 84574) + .saturating_add(T::DbWeight::get().reads(66)) + .saturating_add(T::DbWeight::get().writes(65)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `671` + // Estimated: `87186` + // Minimum execution time: 2_353_000 nanoseconds. + Weight::from_parts(37_193_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `633` + // Estimated: `87186` + // Minimum execution time: 2_773_000 nanoseconds. + Weight::from_parts(30_303_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:32 w:32) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:32 w:32) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `624 + n * (160 ±0)` + // Estimated: `84574` + // Minimum execution time: 1_889_000 nanoseconds. + Weight::from_parts(29_394_000_000, 84574) + .saturating_add(T::DbWeight::get().reads(66)) + .saturating_add(T::DbWeight::get().writes(65)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `518 + n * (160 ±0)` + // Estimated: `87186` + // Minimum execution time: 2_376_000 nanoseconds. + Weight::from_parts(37_564_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:33 w:33) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `480 + n * (160 ±0)` + // Estimated: `87186` + // Minimum execution time: 2_760_000 nanoseconds. + Weight::from_parts(30_589_000_000, 87186) + .saturating_add(T::DbWeight::get().reads(67)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:1 w:1) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `780` + // Estimated: `4173` + // Minimum execution time: 979_000 nanoseconds. + Weight::from_parts(986_000_000, 4173) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `MarketCommons::Markets` (r:1 w:0) + /// Proof: `MarketCommons::Markets` (`max_values`: None, `max_size`: Some(708), added: 3183, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:2 w:2) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// 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 { + // Proof Size summary in bytes: + // Measured: `674` + // Estimated: `6214` + // Minimum execution time: 1_193_000 nanoseconds. + Weight::from_parts(1_215_000_000, 6214) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/zrml/prediction-markets/src/lib.rs b/zrml/prediction-markets/src/lib.rs index abaf617df..8d924151e 100644 --- a/zrml/prediction-markets/src/lib.rs +++ b/zrml/prediction-markets/src/lib.rs @@ -27,6 +27,7 @@ mod benchmarks; pub mod migrations; pub mod mock; mod tests; +pub mod types; pub mod weights; pub use pallet::*; diff --git a/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs new file mode 100644 index 000000000..1a8019122 --- /dev/null +++ b/zrml/prediction-markets/src/types/combinatorial_tokens_benchmark_helper.rs @@ -0,0 +1,40 @@ +use crate::{BalanceOf, Config, MarketIdOf}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use sp_runtime::{traits::Zero, DispatchResult}; +use zeitgeist_primitives::{ + traits::{CombinatorialTokensBenchmarkHelper, MarketCommonsPalletApi}, + types::{MarketStatus, OutcomeReport}, +}; + +pub struct PredictionMarketsCombinatorialTokensBenchmarkHelper(PhantomData); + +impl CombinatorialTokensBenchmarkHelper + for PredictionMarketsCombinatorialTokensBenchmarkHelper +where + T: Config, +{ + type Balance = BalanceOf; + type MarketId = MarketIdOf; + + /// Aggressively modifies the market specified by `market_id` to be resolved. The payout vector + /// must contain exactly one non-zero entry. Does absolutely no error management. + fn setup_payout_vector( + market_id: Self::MarketId, + payout_vector: Option>, + ) -> DispatchResult { + let payout_vector = payout_vector.unwrap(); + let index = payout_vector.iter().position(|&value| !value.is_zero()).unwrap(); + + as MarketCommonsPalletApi>::mutate_market( + &market_id, + |market| { + market.resolved_outcome = + Some(OutcomeReport::Categorical(index.try_into().unwrap())); + market.status = MarketStatus::Resolved; + + Ok(()) + }, + ) + } +} diff --git a/zrml/prediction-markets/src/types/mod.rs b/zrml/prediction-markets/src/types/mod.rs new file mode 100644 index 000000000..04d4ef801 --- /dev/null +++ b/zrml/prediction-markets/src/types/mod.rs @@ -0,0 +1,3 @@ +mod combinatorial_tokens_benchmark_helper; + +pub use combinatorial_tokens_benchmark_helper::PredictionMarketsCombinatorialTokensBenchmarkHelper;