Skip to content

Commit db1f928

Browse files
maltekliemannChralt98sea212
authored
Track reserved (non-dispute) bonds (#892)
* Add `bonds` field to `Market` * Add `bonds` field to `construct_market` * Abstract unreserving bonds * Abstract slashing of bonds * Fix clippy issues * Introduce `Bond` structure * Fix code duplication * Implement storage migration * Test storage migration * Fix doc string * Add `who` field to `Bond` * Fix runtime benchmarks * Fix macro invocations * Fix `market-commons` tests * Fix clippy issues * Fix formatting * Use `CurrencyOf` instead of `AssetManager` * Add test for `create_market` * Test that `on_resolution` settles bonds * Add migration to `Executive` * Remove unnecessary clone * Reorder blocks in `create_market` * Add `debug_asserts!` to catch logic errors * Consider markets with insufficient subsidy in migration * Improve trait bounds in `MaxEncodedLen` implementation * Apply suggestions from code review Co-authored-by: Chralt <chralt.developer@gmail.com> * Rename macro parameter for clarity * Update zrml/prediction-markets/src/migrations.rs Co-authored-by: Chralt <chralt.developer@gmail.com> * Add missing documentation * Add missing `return` statement * Add docs and order lexicographically * Make `slash_advisory_bond` private again * Use id-only approach for slashing/unreserving bonds * Apply suggestions from code review Co-authored-by: Harald Heckmann <mail@haraldheckmann.de> * Replace `advisory`/`validity` field with `creation` * Fix generic parameter names Co-authored-by: Chralt <chralt.developer@gmail.com> Co-authored-by: Harald Heckmann <mail@haraldheckmann.de>
1 parent 75bcae6 commit db1f928

File tree

19 files changed

+832
-193
lines changed

19 files changed

+832
-193
lines changed

docs/changelog_for_devs.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# v0.3.8
2+
3+
- Added the `bonds` field to the `Market` struct, which tracks the status of the
4+
advisory, oracle and validity bonds. Each of its members has type `Bond`,
5+
which has three fields: `who` (the account that reserved the bond), `value`
6+
(the amount reserved), `is_settled` (a flag which determines if the bond was
7+
already unreserved and/or (partially) slashed).
8+
19
# v0.3.7
210

311
- Added on-chain arbitrage. See
@@ -11,6 +19,7 @@
1119
`tokens.Deposited` and `tokens.Transfer` events are also emitted.
1220

1321
- Added new pallet: GlobalDisputes. Dispatchable calls are:
22+
1423
- `add_vote_outcome` - Add voting outcome to a global dispute in exchange for
1524
a constant fee. Errors if the voting outcome already exists or if the global
1625
dispute has not started or has already finished.
@@ -38,6 +47,7 @@
3847
Authorized MDM for resolving market.
3948

4049
- Properly configured reserve asset transfers via XCM:
50+
4151
- Added xTokens pallet to transfer tokens accross chains
4252
- Added AssetRegistry pallet to register foreign asset
4353
- Added UnknownTokens pallet to handle unknown foreign assets
@@ -62,7 +72,6 @@
6272
- `edit_market` extrinsic added, which enables creator of the market to edit
6373
market. It has same parameters as `create_market` except market_creation, on
6474
success it returns `MarketEdited` event.
65-
6675
- `get_spot_price()` RPC from Swaps support `with_fees` boolean parameter to
6776
include/exclude swap_fees in spot price, Currently this flag only works for
6877
CPMM.

primitives/src/constants/mock.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ parameter_types! {
5555
pub const ValidityBond: Balance = 50 * CENT;
5656
pub const MinDisputeDuration: BlockNumber = 2;
5757
pub const MinOracleDuration: BlockNumber = 2;
58-
pub const MaxDisputeDuration: BlockNumber = 5;
59-
pub const MaxGracePeriod: BlockNumber = 2;
60-
pub const MaxOracleDuration: BlockNumber = 3;
58+
pub const MaxDisputeDuration: BlockNumber = 50;
59+
pub const MaxGracePeriod: BlockNumber = 20;
60+
pub const MaxOracleDuration: BlockNumber = 30;
6161
pub const MaxEditReasonLen: u32 = 1024;
6262
pub const MaxRejectReasonLen: u32 = 1024;
6363
}

primitives/src/market.rs

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ use sp_runtime::RuntimeDebug;
2424

2525
/// Types
2626
///
27-
/// * `AI`: Account Id
28-
/// * `BN`: Block Number
29-
/// * `M`: Moment (Time moment)
27+
/// * `AI`: Account id
28+
/// * `BA`: Balance type for bonds
29+
/// * `BN`: Block number
30+
/// * `M`: Moment (time moment)
3031
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
31-
pub struct Market<AI, BN, M> {
32+
pub struct Market<AI, BA, BN, M> {
3233
/// Creator of this market.
3334
pub creator: AI,
3435
/// Creation type.
@@ -56,9 +57,53 @@ pub struct Market<AI, BN, M> {
5657
pub resolved_outcome: Option<OutcomeReport>,
5758
/// See [`MarketDisputeMechanism`].
5859
pub dispute_mechanism: MarketDisputeMechanism,
60+
/// The bonds reserved for this market.
61+
pub bonds: MarketBonds<AI, BA>,
62+
}
63+
64+
/// Tracks the status of a bond.
65+
#[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, Eq, RuntimeDebug, TypeInfo)]
66+
pub struct Bond<AI, BA> {
67+
/// The account that reserved the bond.
68+
pub who: AI,
69+
/// The amount reserved.
70+
pub value: BA,
71+
/// `true` if and only if the bond is unreserved and/or (partially) slashed.
72+
pub is_settled: bool,
73+
}
74+
75+
impl<AI, BA> Bond<AI, BA> {
76+
pub fn new(who: AI, value: BA) -> Bond<AI, BA> {
77+
Bond { who, value, is_settled: false }
78+
}
79+
}
80+
81+
/// Tracks bonds associated with a prediction market.
82+
#[derive(Clone, Decode, Encode, MaxEncodedLen, PartialEq, Eq, RuntimeDebug, TypeInfo)]
83+
pub struct MarketBonds<AI, BA> {
84+
pub creation: Option<Bond<AI, BA>>,
85+
pub oracle: Option<Bond<AI, BA>>,
5986
}
6087

61-
impl<AI, BN, M> Market<AI, BN, M> {
88+
impl<AI: Ord, BA: frame_support::traits::tokens::Balance> MarketBonds<AI, BA> {
89+
/// Return the combined value of the open bonds for `who`.
90+
pub fn total_amount_bonded(&self, who: &AI) -> BA {
91+
let value_or_default = |bond: &Option<Bond<AI, BA>>| match bond {
92+
Some(bond) if bond.who == *who => bond.value,
93+
_ => BA::zero(),
94+
};
95+
value_or_default(&self.creation).saturating_add(value_or_default(&self.oracle))
96+
}
97+
}
98+
99+
// Used primarily for testing purposes.
100+
impl<AI, BA> Default for MarketBonds<AI, BA> {
101+
fn default() -> Self {
102+
MarketBonds { creation: None, oracle: None }
103+
}
104+
}
105+
106+
impl<AI, BA, BN, M> Market<AI, BA, BN, M> {
62107
// Returns the number of outcomes for a market.
63108
pub fn outcomes(&self) -> u16 {
64109
match self.market_type {
@@ -84,9 +129,10 @@ impl<AI, BN, M> Market<AI, BN, M> {
84129
}
85130
}
86131

87-
impl<AI, BN, M> MaxEncodedLen for Market<AI, BN, M>
132+
impl<AI, BA, BN, M> MaxEncodedLen for Market<AI, BA, BN, M>
88133
where
89134
AI: MaxEncodedLen,
135+
BA: MaxEncodedLen,
90136
BN: MaxEncodedLen,
91137
M: MaxEncodedLen,
92138
{
@@ -105,6 +151,7 @@ where
105151
.saturating_add(<Option<Report<AI, BN>>>::max_encoded_len())
106152
.saturating_add(<Option<OutcomeReport>>::max_encoded_len())
107153
.saturating_add(<MarketDisputeMechanism>::max_encoded_len())
154+
.saturating_add(<MarketBonds<AI, BA>>::max_encoded_len())
108155
}
109156
}
110157

@@ -160,7 +207,9 @@ impl<BN: MaxEncodedLen, M: MaxEncodedLen> MaxEncodedLen for MarketPeriod<BN, M>
160207
}
161208

162209
/// Defines deadlines for market.
163-
#[derive(Clone, Copy, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
210+
#[derive(
211+
Clone, Copy, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo,
212+
)]
164213
pub struct Deadlines<BN> {
165214
pub grace_period: BN,
166215
pub oracle_duration: BN,
@@ -232,7 +281,7 @@ pub struct SubsidyUntil<BN, MO, MI> {
232281
mod tests {
233282
use crate::market::*;
234283
use test_case::test_case;
235-
type Market = crate::market::Market<u32, u32, u32>;
284+
type Market = crate::market::Market<u32, u32, u32, u32>;
236285

237286
#[test_case(
238287
MarketType::Categorical(6),
@@ -305,16 +354,19 @@ mod tests {
305354
report: None,
306355
resolved_outcome: None,
307356
dispute_mechanism: MarketDisputeMechanism::Authorized,
357+
bonds: MarketBonds::default(),
308358
};
309359
assert_eq!(market.matches_outcome_report(&outcome_report), expected);
310360
}
361+
311362
#[test]
312363
fn max_encoded_len_market_type() {
313364
// `MarketType::Scalar` is the largest enum variant.
314365
let market_type = MarketType::Scalar(1u128..=2);
315366
let len = parity_scale_codec::Encode::encode(&market_type).len();
316367
assert_eq!(MarketType::max_encoded_len(), len);
317368
}
369+
318370
#[test]
319371
fn max_encoded_len_market_period() {
320372
let market_period: MarketPeriod<u32, u32> = MarketPeriod::Block(Default::default());

primitives/src/traits/dispute_api.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ use crate::{market::MarketDispute, outcome_report::OutcomeReport, types::Market}
1919
use frame_support::dispatch::DispatchResult;
2020
use sp_runtime::DispatchError;
2121

22+
// Abstraction of the market type, which is not a part of `DisputeApi` because Rust doesn't support
23+
// type aliases in traits.
24+
type MarketOfDisputeApi<T> = Market<
25+
<T as DisputeApi>::AccountId,
26+
<T as DisputeApi>::Balance,
27+
<T as DisputeApi>::BlockNumber,
28+
<T as DisputeApi>::Moment,
29+
>;
30+
2231
pub trait DisputeApi {
2332
type AccountId;
2433
type Balance;
@@ -34,7 +43,7 @@ pub trait DisputeApi {
3443
fn on_dispute(
3544
previous_disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
3645
market_id: &Self::MarketId,
37-
market: &Market<Self::AccountId, Self::BlockNumber, Self::Moment>,
46+
market: &MarketOfDisputeApi<Self>,
3847
) -> DispatchResult;
3948

4049
/// Manage market resolution of a disputed market.
@@ -49,6 +58,6 @@ pub trait DisputeApi {
4958
fn on_resolution(
5059
disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
5160
market_id: &Self::MarketId,
52-
market: &Market<Self::AccountId, Self::BlockNumber, Self::Moment>,
61+
market: &MarketOfDisputeApi<Self>,
5362
) -> Result<Option<OutcomeReport>, DispatchError>;
5463
}

primitives/src/traits/market_commons_pallet_api.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,24 @@ use frame_support::{
2121
dispatch::{DispatchError, DispatchResult},
2222
pallet_prelude::{MaybeSerializeDeserialize, Member},
2323
storage::PrefixIterator,
24-
traits::NamedReservableCurrency,
24+
traits::{Currency, NamedReservableCurrency},
2525
Parameter,
2626
};
2727
use parity_scale_codec::MaxEncodedLen;
2828
use sp_runtime::traits::AtLeast32Bit;
2929

30-
/// Simple disputes - Pallet Api
30+
// Abstraction of the market type, which is not a part of `MarketCommonsPalletApi` because Rust
31+
// doesn't support type aliases in traits.
32+
type MarketOf<T> = Market<
33+
<T as MarketCommonsPalletApi>::AccountId,
34+
<<T as MarketCommonsPalletApi>::Currency as Currency<
35+
<T as MarketCommonsPalletApi>::AccountId,
36+
>>::Balance,
37+
<T as MarketCommonsPalletApi>::BlockNumber,
38+
<T as MarketCommonsPalletApi>::Moment,
39+
>;
40+
41+
/// Abstraction over storage operations for markets
3142
pub trait MarketCommonsPalletApi {
3243
type AccountId;
3344
type BlockNumber: AtLeast32Bit;
@@ -50,23 +61,18 @@ pub trait MarketCommonsPalletApi {
5061

5162
/// Return an iterator over the key-value pairs of markets. Altering market storage during
5263
/// iteration results in undefined behavior.
53-
fn market_iter()
54-
-> PrefixIterator<(Self::MarketId, Market<Self::AccountId, Self::BlockNumber, Self::Moment>)>;
64+
fn market_iter() -> PrefixIterator<(Self::MarketId, MarketOf<Self>)>;
5565

5666
/// Gets a market from the storage.
57-
fn market(
58-
market_id: &Self::MarketId,
59-
) -> Result<Market<Self::AccountId, Self::BlockNumber, Self::Moment>, DispatchError>;
67+
fn market(market_id: &Self::MarketId) -> Result<MarketOf<Self>, DispatchError>;
6068

6169
/// Mutates a given market storage
6270
fn mutate_market<F>(market_id: &Self::MarketId, cb: F) -> DispatchResult
6371
where
64-
F: FnOnce(&mut Market<Self::AccountId, Self::BlockNumber, Self::Moment>) -> DispatchResult;
72+
F: FnOnce(&mut MarketOf<Self>) -> DispatchResult;
6573

6674
/// Pushes a new market into the storage, returning its related auto-incremented ID.
67-
fn push_market(
68-
market: Market<Self::AccountId, Self::BlockNumber, Self::Moment>,
69-
) -> Result<Self::MarketId, DispatchError>;
75+
fn push_market(market: MarketOf<Self>) -> Result<Self::MarketId, DispatchError>;
7076

7177
/// Removes a market from the storage.
7278
fn remove_market(market_id: &Self::MarketId) -> DispatchResult;

runtime/common/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ macro_rules! decl_common_types {
5858
frame_system::ChainContext<Runtime>,
5959
Runtime,
6060
AllPalletsWithSystem,
61-
(),
61+
zrml_prediction_markets::migrations::RecordBonds<Runtime>,
6262
>;
6363

6464
#[cfg(not(feature = "parachain"))]
@@ -68,7 +68,7 @@ macro_rules! decl_common_types {
6868
frame_system::ChainContext<Runtime>,
6969
Runtime,
7070
AllPalletsWithSystem,
71-
(),
71+
zrml_prediction_markets::migrations::RecordBonds<Runtime>,
7272
>;
7373

7474
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;

zrml/authorized/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ mod pallet {
5959
pub(crate) type MarketIdOf<T> =
6060
<<T as Config>::MarketCommons as MarketCommonsPalletApi>::MarketId;
6161
pub(crate) type MomentOf<T> = <<T as Config>::MarketCommons as MarketCommonsPalletApi>::Moment;
62+
type MarketOf<T> = Market<
63+
<T as frame_system::Config>::AccountId,
64+
BalanceOf<T>,
65+
<T as frame_system::Config>::BlockNumber,
66+
MomentOf<T>,
67+
>;
6268

6369
#[pallet::call]
6470
impl<T: Config> Pallet<T> {
@@ -144,15 +150,15 @@ mod pallet {
144150
fn on_dispute(
145151
_: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
146152
_: &Self::MarketId,
147-
_: &Market<Self::AccountId, Self::BlockNumber, Self::Moment>,
153+
_: &MarketOf<T>,
148154
) -> DispatchResult {
149155
Ok(())
150156
}
151157

152158
fn on_resolution(
153159
_: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
154160
market_id: &Self::MarketId,
155-
_: &Market<Self::AccountId, Self::BlockNumber, MomentOf<T>>,
161+
_: &MarketOf<T>,
156162
) -> Result<Option<OutcomeReport>, DispatchError> {
157163
let result = AuthorizedOutcomeReports::<T>::get(market_id);
158164
if result.is_some() {
@@ -173,13 +179,13 @@ mod pallet {
173179

174180
#[cfg(any(feature = "runtime-benchmarks", test))]
175181
pub(crate) fn market_mock<T>()
176-
-> zeitgeist_primitives::types::Market<T::AccountId, T::BlockNumber, MomentOf<T>>
182+
-> zeitgeist_primitives::types::Market<T::AccountId, BalanceOf<T>, T::BlockNumber, MomentOf<T>>
177183
where
178184
T: crate::Config,
179185
{
180186
use frame_support::traits::Get;
181187
use sp_runtime::traits::AccountIdConversion;
182-
use zeitgeist_primitives::types::ScoringRule;
188+
use zeitgeist_primitives::types::{MarketBonds, ScoringRule};
183189

184190
zeitgeist_primitives::types::Market {
185191
creation: zeitgeist_primitives::types::MarketCreation::Permissionless,
@@ -199,5 +205,6 @@ where
199205
resolved_outcome: None,
200206
scoring_rule: ScoringRule::CPMM,
201207
status: zeitgeist_primitives::types::MarketStatus::Disputed,
208+
bonds: MarketBonds::default(),
202209
}
203210
}

zrml/court/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ mod pallet {
8888
pub(crate) type MarketIdOf<T> =
8989
<<T as Config>::MarketCommons as MarketCommonsPalletApi>::MarketId;
9090
pub(crate) type MomentOf<T> = <<T as Config>::MarketCommons as MarketCommonsPalletApi>::Moment;
91+
pub(crate) type MarketOf<T> = Market<
92+
<T as frame_system::Config>::AccountId,
93+
BalanceOf<T>,
94+
<T as frame_system::Config>::BlockNumber,
95+
MomentOf<T>,
96+
>;
9197

9298
#[pallet::call]
9399
impl<T: Config> Pallet<T> {
@@ -498,7 +504,7 @@ mod pallet {
498504
fn on_dispute(
499505
disputes: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
500506
market_id: &Self::MarketId,
501-
market: &Market<Self::AccountId, Self::BlockNumber, Self::Moment>,
507+
market: &MarketOf<T>,
502508
) -> DispatchResult {
503509
if market.dispute_mechanism != MarketDisputeMechanism::Court {
504510
return Err(Error::<T>::MarketDoesNotHaveCourtMechanism.into());
@@ -522,7 +528,7 @@ mod pallet {
522528
fn on_resolution(
523529
_: &[MarketDispute<Self::AccountId, Self::BlockNumber>],
524530
market_id: &Self::MarketId,
525-
market: &Market<Self::AccountId, Self::BlockNumber, MomentOf<T>>,
531+
market: &MarketOf<T>,
526532
) -> Result<Option<OutcomeReport>, DispatchError> {
527533
if market.dispute_mechanism != MarketDisputeMechanism::Court {
528534
return Err(Error::<T>::MarketDoesNotHaveCourtMechanism.into());

0 commit comments

Comments
 (0)