Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let markets open their pools when they start #711

Merged
merged 40 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d37ad94
Add `PoolStatus::Open`
maltekliemann Jul 3, 2022
494c5fc
Fix benchmark
maltekliemann Jul 4, 2022
7dcbaad
Add new pool status `Initialized`
maltekliemann Jul 6, 2022
66cde95
Fix benchmark
maltekliemann Jul 6, 2022
1ca7260
Fix `types.json`
maltekliemann Jul 7, 2022
1b5303c
Merge branch 'main' into open-pool
maltekliemann Jul 12, 2022
2a5dbe0
Update zrml/swaps/src/lib.rs
maltekliemann Jul 13, 2022
876c854
Update zrml/swaps/src/tests.rs
maltekliemann Jul 13, 2022
54204c9
Extend pool parameters checks
maltekliemann Jul 13, 2022
95a7add
Fix status errors
maltekliemann Jul 14, 2022
02fa027
Add scaffold for auto-opening markets
maltekliemann Jul 15, 2022
3e75147
Add tests for auto open
maltekliemann Jul 15, 2022
7ab133d
Fix tests, implement changes
maltekliemann Jul 15, 2022
cce7312
Reorganize hooks
maltekliemann Jul 16, 2022
e70b095
Fix chain stall and reorganize tests
maltekliemann Jul 16, 2022
8fba728
Merge branch 'main' into mkl-open-market
maltekliemann Jul 16, 2022
a64da93
Merge branch 'main' into mkl-open-market
maltekliemann Jul 16, 2022
6172084
Fix tests
maltekliemann Jul 16, 2022
10e733b
Compensate timestamp lag in `on_initialize`
maltekliemann Jul 17, 2022
db42671
Update changelog
maltekliemann Jul 17, 2022
3d3a474
Merge branch 'open-pool' into mkl-open-market
maltekliemann Jul 17, 2022
a949b91
Replace integer arithmetic with saturating ops
maltekliemann Jul 17, 2022
b97126c
Merge branch 'main' into mkl-open-market
maltekliemann Jul 18, 2022
7cc3c31
Add basic storage migration
maltekliemann Jul 19, 2022
29e6225
Add basic storage migration
maltekliemann Jul 19, 2022
dbc1faa
Merge branch 'mkl-open-market' of github.com:zeitgeistpm/zeitgeist in…
maltekliemann Jul 19, 2022
8c4a40b
Add missing file
maltekliemann Jul 19, 2022
3890b2d
Remove TODO
maltekliemann Jul 19, 2022
717d530
`cargo fmt`
maltekliemann Jul 19, 2022
e116892
Move block check outside of `with_transaction`
maltekliemann Jul 19, 2022
fc0555e
Limit bounded vector size
maltekliemann Jul 19, 2022
9644a79
Reduce size of close caches
maltekliemann Jul 19, 2022
b380356
Abstract open/close manager into status manager
maltekliemann Jul 19, 2022
ebbe906
Remove `on_market_open`
maltekliemann Jul 19, 2022
079720b
Reduce size of report/dispute caches
maltekliemann Jul 19, 2022
761e8c7
Apply suggestions from code review
maltekliemann Jul 26, 2022
3bccf0a
Fix `total_weight` calculation
maltekliemann Jul 26, 2022
973afc8
Add `CacheSize` abstraction
maltekliemann Jul 26, 2022
ff241d8
Improve code quality using `filter`
maltekliemann Jul 28, 2022
5daa4ac
Reduce indentation
maltekliemann Jul 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ type Executive = frame_executive::Executive<
frame_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
zrml_court::migrations::JurorsCountedStorageMapMigration<Runtime>,
(
zrml_court::migrations::JurorsCountedStorageMapMigration<Runtime>,
zrml_prediction_markets::migrations::MigrateMarketPoolsBeforeOpen<Runtime>,
),
>;

type Header = generic::Header<BlockNumber, BlakeTwo256>;
Expand Down
197 changes: 162 additions & 35 deletions zrml/prediction-markets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
extern crate alloc;

mod benchmarks;
pub mod migrations;
pub mod mock;
mod tests;
pub mod weights;
Expand Down Expand Up @@ -108,7 +109,7 @@ mod pallet {
pub const RESERVE_ID: [u8; 8] = PmPalletId::get().0;

/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(3);

pub(crate) type BalanceOf<T> = <<T as Config>::AssetManager as MultiCurrency<
<T as frame_system::Config>::AccountId,
Expand All @@ -117,6 +118,7 @@ mod pallet {
pub(crate) type MarketIdOf<T> =
<<T as Config>::MarketCommons as MarketCommonsPalletApi>::MarketId;
pub(crate) type MomentOf<T> = <<T as Config>::MarketCommons as MarketCommonsPalletApi>::Moment;
type CacheSize = ConstU32<64>;

#[pallet::call]
impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -203,6 +205,7 @@ mod pallet {
T::MarketCommons::remove_market_pool(&market_id)?;
}

Self::clear_auto_open(&market_id)?;
Self::clear_auto_close(&market_id)?;
Chralt98 marked this conversation as resolved.
Show resolved Hide resolved
Self::clear_auto_resolve(&market_id)?;
T::MarketCommons::remove_market(&market_id)?;
Expand Down Expand Up @@ -246,6 +249,7 @@ mod pallet {
T::CloseOrigin::ensure_origin(origin)?;
let market = T::MarketCommons::market(&market_id)?;
Self::ensure_market_is_active(&market)?;
Self::clear_auto_open(&market_id)?;
Self::clear_auto_close(&market_id)?;
Self::close_market(&market_id)?;
Ok(())
Expand Down Expand Up @@ -672,7 +676,33 @@ mod pallet {
Some(amount),
Some(weights),
)?;
T::Swaps::open_pool(pool_id)?;

// Open the pool now or cache it for later
match market.period {
MarketPeriod::Block(ref range) => {
let current_block = <frame_system::Pallet<T>>::block_number();
let open_block = range.start;
if current_block < open_block {
MarketIdsPerOpenBlock::<T>::try_mutate(&open_block, |ids| {
ids.try_push(market_id).map_err(|_| <Error<T>>::StorageOverflow)
})?;
} else {
T::Swaps::open_pool(pool_id)?;
}
}
MarketPeriod::Timestamp(ref range) => {
let current_time_frame =
Self::calculate_time_frame_of_moment(T::MarketCommons::now());
let open_time_frame = Self::calculate_time_frame_of_moment(range.start);
if current_time_frame < open_time_frame {
MarketIdsPerOpenTimeFrame::<T>::try_mutate(&open_time_frame, |ids| {
ids.try_push(market_id).map_err(|_| <Error<T>>::StorageOverflow)
})?;
} else {
T::Swaps::open_pool(pool_id)?;
}
}
};

// This errors if a pool already exists!
T::MarketCommons::insert_market_pool(market_id, pool_id)?;
Expand Down Expand Up @@ -818,6 +848,7 @@ mod pallet {
) -> DispatchResult {
T::ApprovalOrigin::ensure_origin(origin)?;
let market = T::MarketCommons::market(&market_id)?;
Self::clear_auto_open(&market_id)?;
Self::clear_auto_close(&market_id)?;
Self::do_reject_market(&market_id, market)?;
Ok(())
Expand Down Expand Up @@ -1190,20 +1221,66 @@ mod pallet {
let mut total_weight: Weight =
Self::process_subsidy_collecting_markets(now, T::MarketCommons::now());

// If we are at genesis or the first block the timestamp is be undefined. No
// market needs to be opened or closed on blocks #0 or #1, so we skip the
// evaluation. Without this check, new chains starting from genesis will hang up,
// since the loops in the `market_status_manager` calls below will run over an interval
// of 0 to the current time frame.
if now <= 1u32.into() {
return total_weight;
}

// We add one to the count, because `pallet-timestamp` sets the timestamp _after_
// `on_initialize` is called, so calling `now()` during `on_initialize` gives us
// the timestamp of the previous block.
let current_time_frame =
Self::calculate_time_frame_of_moment(T::MarketCommons::now()).saturating_add(1);
maltekliemann marked this conversation as resolved.
Show resolved Hide resolved

// On first pass, we use current_time - 1 to ensure that the chain doesn't try to
// check all time frames since epoch.
let last_time_frame =
LastTimeFrame::<T>::get().unwrap_or_else(|| current_time_frame.saturating_sub(1));
Chralt98 marked this conversation as resolved.
Show resolved Hide resolved

let _ = with_transaction(|| {
let close = Self::market_close_manager(now, |market_id, market| {
let weight = Self::on_market_close(market_id, market)?;
total_weight = total_weight.saturating_add(weight);
Ok(())
});
let open = Self::market_status_manager::<
_,
MarketIdsPerOpenBlock<T>,
MarketIdsPerOpenTimeFrame<T>,
>(
now,
last_time_frame,
current_time_frame,
|market_id, _| {
let weight = Self::open_market(market_id)?;
Chralt98 marked this conversation as resolved.
Show resolved Hide resolved
total_weight = total_weight.saturating_add(weight);
Ok(())
},
);

let close = Self::market_status_manager::<
_,
MarketIdsPerCloseBlock<T>,
MarketIdsPerCloseTimeFrame<T>,
>(
now,
last_time_frame,
current_time_frame,
|market_id, market| {
let weight = Self::on_market_close(market_id, market)?;
total_weight = total_weight.saturating_add(weight);
Ok(())
},
);

let resolve = Self::resolution_manager(now, |market_id, market| {
let weight = Self::on_resolution(market_id, market)?;
total_weight = total_weight.saturating_add(weight);
Ok(())
});

match close.and(resolve) {
LastTimeFrame::<T>::set(Some(current_time_frame));

match open.and(close).and(resolve) {
Err(err) => {
Self::deposit_event(Event::BadOnInitialize);
log::error!("Block {:?} was not initialized. Error: {:?}", now, err);
Expand Down Expand Up @@ -1232,13 +1309,31 @@ mod pallet {
ValueQuery,
>;

#[pallet::storage]
pub type MarketIdsPerOpenBlock<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::BlockNumber,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

#[pallet::storage]
pub type MarketIdsPerOpenTimeFrame<T: Config> = StorageMap<
_,
Blake2_128Concat,
TimeFrame,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

/// A mapping of market identifiers to the block their market ends on.
#[pallet::storage]
pub type MarketIdsPerCloseBlock<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::BlockNumber,
BoundedVec<MarketIdOf<T>, ConstU32<1024>>,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

Expand All @@ -1248,7 +1343,7 @@ mod pallet {
_,
Blake2_128Concat,
TimeFrame,
BoundedVec<MarketIdOf<T>, ConstU32<1024>>,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

Expand All @@ -1263,7 +1358,7 @@ mod pallet {
_,
Twox64Concat,
T::BlockNumber,
BoundedVec<MarketIdOf<T>, ConstU32<1024>>,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

Expand All @@ -1273,7 +1368,7 @@ mod pallet {
_,
Twox64Concat,
T::BlockNumber,
BoundedVec<MarketIdOf<T>, ConstU32<1024>>,
BoundedVec<MarketIdOf<T>, CacheSize>,
ValueQuery,
>;

Expand Down Expand Up @@ -1339,6 +1434,32 @@ mod pallet {
Ok(())
}

// Manually remove market from cache for auto open.
fn clear_auto_open(market_id: &MarketIdOf<T>) -> DispatchResult {
let market = T::MarketCommons::market(market_id)?;

// No-op if market isn't cached for auto open according to its state.
match market.status {
MarketStatus::Active | MarketStatus::Proposed => (),
_ => return Ok(()),
};

match market.period {
MarketPeriod::Block(range) => {
MarketIdsPerOpenBlock::<T>::mutate(&range.start, |ids| {
remove_item::<MarketIdOf<T>, _>(ids, market_id);
});
}
MarketPeriod::Timestamp(range) => {
let time_frame = Self::calculate_time_frame_of_moment(range.start);
MarketIdsPerOpenTimeFrame::<T>::mutate(&time_frame, |ids| {
remove_item::<MarketIdOf<T>, _>(ids, market_id);
});
}
};
Ok(())
}

/// Clears this market from being stored for automatic resolution.
fn clear_auto_resolve(market_id: &MarketIdOf<T>) -> DispatchResult {
let market = T::MarketCommons::market(market_id)?;
Expand Down Expand Up @@ -1628,6 +1749,17 @@ mod pallet {
Ok([total_accounts, total_asset_accounts, total_categories])
}

pub(crate) fn open_market(market_id: &MarketIdOf<T>) -> Result<Weight, DispatchError> {
// Is no-op if market has no pool. This should never happen, but it's safer to not
// error in this case.
let mut total_weight = T::DbWeight::get().reads(1); // (For the `market_pool` read)
if let Ok(pool_id) = T::MarketCommons::market_pool(market_id) {
let open_pool_weight = T::Swaps::open_pool(pool_id)?;
total_weight = total_weight.saturating_add(open_pool_weight);
}
Ok(total_weight)
}

pub(crate) fn close_market(market_id: &MarketIdOf<T>) -> Result<Weight, DispatchError> {
T::MarketCommons::mutate_market(market_id, |market| {
ensure!(market.status == MarketStatus::Active, Error::<T>::InvalidMarketStatus);
Expand Down Expand Up @@ -2016,47 +2148,42 @@ mod pallet {
Ok(())
}

pub(crate) fn market_close_manager<F>(
now: T::BlockNumber,
pub(crate) fn market_status_manager<F, MarketIdsPerBlock, MarketIdsPerTimeFrame>(
block_number: T::BlockNumber,
last_time_frame: TimeFrame,
current_time_frame: TimeFrame,
mut mutation: F,
) -> DispatchResult
where
F: FnMut(
&MarketIdOf<T>,
Market<T::AccountId, T::BlockNumber, MomentOf<T>>,
) -> DispatchResult,
MarketIdsPerBlock: frame_support::StorageMap<
T::BlockNumber,
BoundedVec<MarketIdOf<T>, CacheSize>,
Query = BoundedVec<MarketIdOf<T>, CacheSize>,
>,
MarketIdsPerTimeFrame: frame_support::StorageMap<
TimeFrame,
BoundedVec<MarketIdOf<T>, CacheSize>,
Query = BoundedVec<MarketIdOf<T>, CacheSize>,
>,
{
for market_id in MarketIdsPerCloseBlock::<T>::get(&now).iter() {
for market_id in MarketIdsPerBlock::get(&block_number).iter() {
let market = T::MarketCommons::market(market_id)?;
mutation(market_id, market)?;
}

MarketIdsPerCloseBlock::<T>::remove(&now);

// If we are at genesis the timestamp is 0. No market can exist, we skip the evaluation.
// Without this check, new chains starting from genesis will hang up, since the loop
// below will run over an interval of 0 to the current time frame.
// We check the block number and the timestamp, since technically the timestamp is
// undefined at genesis.
let current_time_frame = Self::calculate_time_frame_of_moment(T::MarketCommons::now());
if current_time_frame == 0 || now == T::BlockNumber::zero() {
return Ok(());
}

// On first pass, we use current_time - 1 to ensure that the chain doesn't try to check
// all time frames since epoch.
let last_time_frame =
LastTimeFrame::<T>::get().unwrap_or_else(|| current_time_frame.saturating_sub(1));
MarketIdsPerBlock::remove(&block_number);

for time_frame in last_time_frame.saturating_add(1)..=current_time_frame {
for market_id in MarketIdsPerCloseTimeFrame::<T>::get(&time_frame).iter() {
for market_id in MarketIdsPerTimeFrame::get(&time_frame).iter() {
let market = T::MarketCommons::market(market_id)?;
mutation(market_id, market)?;
}
MarketIdsPerCloseTimeFrame::<T>::remove(&time_frame);
MarketIdsPerTimeFrame::remove(&time_frame);
}

LastTimeFrame::<T>::set(Some(current_time_frame));
Ok(())
}

Expand Down
Loading