Skip to content

Commit 5832f0b

Browse files
committed
Implement DecisionMarketOracle using scoreboard
1 parent e684d47 commit 5832f0b

File tree

8 files changed

+153
-26
lines changed

8 files changed

+153
-26
lines changed

runtime/common/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2287,7 +2287,7 @@ macro_rules! create_common_tests {
22872287
};
22882288
use zrml_futarchy::types::Proposal;
22892289
use zrml_market_commons::types::MarketBuilder;
2290-
use zrml_neo_swaps::types::DecisionMarketOracle;
2290+
use zrml_neo_swaps::types::{DecisionMarketOracle, DecisionMarketOracleScoreboard};
22912291

22922292
#[test]
22932293
fn futarchy_schedules_and_executes_call() {
@@ -2361,10 +2361,13 @@ macro_rules! create_common_tests {
23612361
};
23622362
let call =
23632363
Preimage::bound(RuntimeCall::from(remark_dispatched_as)).unwrap();
2364+
let scoreboard =
2365+
DecisionMarketOracleScoreboard::new(40_000, 10_000, one / 7, one);
23642366
let oracle = DecisionMarketOracle::new(
23652367
market_id,
23662368
Asset::CategoricalOutcome(market_id, 0),
23672369
Asset::CategoricalOutcome(market_id, 1),
2370+
scoreboard,
23682371
);
23692372
let when = duration + 10;
23702373
let proposal = Proposal { when, call, oracle };

zrml/futarchy/src/mock/types/oracle.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ use frame_support::pallet_prelude::Weight;
2020
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
2121
use scale_info::TypeInfo;
2222
use sp_runtime::traits::Zero;
23-
use zeitgeist_primitives::traits::FutarchyOracle;
24-
use zeitgeist_primitives::types::BlockNumber;
23+
use zeitgeist_primitives::{traits::FutarchyOracle, types::BlockNumber};
2524

2625
#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)]
2726
pub struct MockOracle {

zrml/futarchy/src/proposal_storage.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ use crate::{
22
traits::ProposalStorage, types::Proposal, Config, Error, Pallet, ProposalCount, Proposals,
33
ProposalsOf,
44
};
5-
use alloc::{collections::BTreeMap, vec::Vec};
5+
use alloc::{collections::BTreeMap, vec, vec::Vec};
66
use frame_support::{ensure, require_transactional, traits::Get};
77
use frame_system::pallet_prelude::BlockNumberFor;
88
use sp_runtime::{DispatchError, SaturatedConversion};
99
use zeitgeist_primitives::math::checked_ops_res::{CheckedIncRes, CheckedSubRes};
10-
use alloc::vec;
1110

1211
impl<T> ProposalStorage<T> for Pallet<T>
1312
where
@@ -51,7 +50,9 @@ where
5150
Proposals::<T>::get(block_number)
5251
}
5352

54-
fn mutate_all<R, F>(mut mutator: F) -> Result<BTreeMap<BlockNumberFor<T>, Vec<R>>, DispatchError>
53+
fn mutate_all<R, F>(
54+
mut mutator: F,
55+
) -> Result<BTreeMap<BlockNumberFor<T>, Vec<R>>, DispatchError>
5556
where
5657
F: FnMut(&mut Proposal<T>) -> R,
5758
{

zrml/neo-swaps/src/benchmarking.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use super::*;
2121
use crate::{
2222
liquidity_tree::{traits::LiquidityTreeHelper, types::LiquidityTree},
2323
traits::{LiquiditySharesManager, PoolOperations, PoolStorage},
24-
types::DecisionMarketOracle,
24+
types::{DecisionMarketOracle, DecisionMarketOracleScoreboard},
2525
AssetOf, BalanceOf, MarketIdOf, Pallet as NeoSwaps, Pools, MIN_SPOT_PRICE,
2626
};
2727
use alloc::{vec, vec::Vec};
@@ -671,7 +671,13 @@ mod benchmarks {
671671
let pool = Pools::<T>::get(market_id).unwrap();
672672
let assets = pool.assets();
673673

674-
let oracle = DecisionMarketOracle::<T>::new(market_id, assets[0], assets[1]);
674+
let scoreboard = DecisionMarketOracleScoreboard::<T>::new(
675+
Zero::zero(),
676+
Zero::zero(),
677+
Zero::zero(),
678+
Zero::zero(),
679+
);
680+
let oracle = DecisionMarketOracle::<T>::new(market_id, assets[0], assets[1], scoreboard);
675681

676682
#[block]
677683
{

zrml/neo-swaps/src/types/decision_market_benchmark_helper.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
use crate::{
2121
liquidity_tree::types::LiquidityTree,
22-
types::{DecisionMarketOracle, Pool, PoolType},
22+
types::{DecisionMarketOracle, DecisionMarketOracleScoreboard, Pool, PoolType},
2323
BalanceOf, Config, MarketIdOf, Pallet, Pools,
2424
};
2525
use alloc::{collections::BTreeMap, vec};
@@ -58,6 +58,13 @@ where
5858
reserves.insert(negative_outcome, one);
5959
}
6060

61+
let scoreboard = DecisionMarketOracleScoreboard::new(
62+
Zero::zero(),
63+
Zero::zero(),
64+
Zero::zero(),
65+
Zero::zero(),
66+
);
67+
6168
let account_id: T::AccountId = Pallet::<T>::pool_account_id(&pool_id);
6269
let pool = Pool {
6370
account_id: account_id.clone(),
@@ -72,6 +79,6 @@ where
7279

7380
Pools::<T>::insert(pool_id, pool);
7481

75-
DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome)
82+
DecisionMarketOracle::new(pool_id, positive_outcome, negative_outcome, scoreboard)
7683
}
7784
}

zrml/neo-swaps/src/types/decision_market_oracle.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@
1515
// You should have received a copy of the GNU General Public License
1616
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>.
1717

18-
use crate::{traits::PoolOperations, weights::WeightInfoZeitgeist, AssetOf, Config, Error, Pools};
18+
use crate::{
19+
traits::PoolOperations, types::DecisionMarketOracleScoreboard, weights::WeightInfoZeitgeist,
20+
AssetOf, BalanceOf, Config, Error, Pools,
21+
};
1922
use frame_support::pallet_prelude::Weight;
2023
use frame_system::pallet_prelude::BlockNumberFor;
2124
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
2225
use scale_info::TypeInfo;
23-
use sp_runtime::{traits::Zero, DispatchError};
26+
use sp_runtime::DispatchError;
2427
use zeitgeist_primitives::traits::FutarchyOracle;
2528

29+
/// Struct that implements `FutarchyOracle` using price measurements from liquidity pools.
30+
///
31+
/// The oracle evaluates to `true` if and only if the `positive_outcome` is more valuable than the
32+
/// `negative_outcome` in the liquidity pool specified by `pool_id`.
2633
#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)]
2734
pub struct DecisionMarketOracle<T>
2835
where
@@ -31,6 +38,7 @@ where
3138
pool_id: T::PoolId,
3239
positive_outcome: AssetOf<T>,
3340
negative_outcome: AssetOf<T>,
41+
scoreboard: DecisionMarketOracleScoreboard<T>,
3442
}
3543

3644
impl<T> DecisionMarketOracle<T>
@@ -41,22 +49,19 @@ where
4149
pool_id: T::PoolId,
4250
positive_outcome: AssetOf<T>,
4351
negative_outcome: AssetOf<T>,
52+
scoreboard: DecisionMarketOracleScoreboard<T>,
4453
) -> Self {
45-
Self { pool_id, positive_outcome, negative_outcome }
54+
Self { pool_id, positive_outcome, negative_outcome, scoreboard }
4655
}
4756

48-
// Utility implementation that uses the question mark operator to implement a fallible version
49-
// of `evaluate`.
50-
fn try_evaluate(&self) -> Result<bool, DispatchError> {
57+
fn try_get_prices(&self) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
5158
let pool = Pools::<T>::get(self.pool_id)
5259
.ok_or::<DispatchError>(Error::<T>::PoolNotFound.into())?;
5360

5461
let positive_value = pool.calculate_spot_price(self.positive_outcome)?;
5562
let negative_value = pool.calculate_spot_price(self.negative_outcome)?;
5663

57-
let success = positive_value > negative_value;
58-
59-
Ok(success)
64+
Ok((positive_value, negative_value))
6065
}
6166
}
6267

@@ -67,14 +72,18 @@ where
6772
type BlockNumber = BlockNumberFor<T>;
6873

6974
fn evaluate(&self) -> (Weight, bool) {
70-
// Err on the side of caution if the pool is not found or a calculation fails by not
71-
// enacting the policy.
72-
let value = self.try_evaluate().unwrap_or(false);
73-
74-
(T::WeightInfo::decision_market_oracle_evaluate(), value)
75+
(T::WeightInfo::decision_market_oracle_evaluate(), self.scoreboard.evaluate())
7576
}
7677

77-
fn update(&mut self, _: Self::BlockNumber) -> Weight {
78-
Zero::zero()
78+
fn update(&mut self, now: Self::BlockNumber) -> Weight {
79+
if let Ok((positive_outcome_price, negative_outcome_price)) = self.try_get_prices() {
80+
self.scoreboard.update(now, positive_outcome_price, negative_outcome_price);
81+
} else {
82+
// Err on the side of caution if the pool is not found or a calculation fails by not
83+
// enacting the policy.
84+
self.scoreboard.skip_update(now);
85+
}
86+
87+
Weight::zero() // TODO
7988
}
8089
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::{BalanceOf, Config};
2+
use frame_system::pallet_prelude::BlockNumberFor;
3+
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
4+
use scale_info::TypeInfo;
5+
use sp_runtime::{traits::Zero, Saturating};
6+
use zeitgeist_primitives::math::fixed::FixedDiv;
7+
8+
/// Records until the end of time.
9+
#[derive(Clone, Debug, Decode, Encode, Eq, MaxEncodedLen, PartialEq, TypeInfo)]
10+
pub struct DecisionMarketOracleScoreboard<T>
11+
where
12+
T: Config,
13+
{
14+
/// The block at which the oracle records its first tick.
15+
start: BlockNumberFor<T>,
16+
17+
/// The number of ticks the positive outcome requires to have
18+
victory_margin: u128,
19+
20+
/// The absolute minimum difference in prices required for the positive outcome to receive a
21+
/// point.
22+
price_margin_abs: BalanceOf<T>,
23+
24+
/// The relative minimum difference in prices required for the positive outcome to receive a
25+
/// point, specified as fractional (i.e. 0.1 represents 10%).
26+
price_margin_rel: BalanceOf<T>,
27+
28+
/// The number of ticks for the positive outcome.
29+
pass_score: u128,
30+
31+
/// The number of ticks for the negative outcome.
32+
reject_score: u128,
33+
}
34+
35+
impl<T> DecisionMarketOracleScoreboard<T>
36+
where
37+
T: Config,
38+
{
39+
pub fn new(
40+
start: BlockNumberFor<T>,
41+
victory_margin: u128,
42+
price_margin_abs: BalanceOf<T>,
43+
price_margin_rel: BalanceOf<T>,
44+
) -> DecisionMarketOracleScoreboard<T> {
45+
DecisionMarketOracleScoreboard {
46+
start,
47+
victory_margin,
48+
price_margin_abs,
49+
price_margin_rel,
50+
pass_score: 0,
51+
reject_score: 0,
52+
}
53+
}
54+
55+
pub fn update(
56+
&mut self,
57+
now: BlockNumberFor<T>,
58+
positive_outcome_price: BalanceOf<T>,
59+
negative_outcome_price: BalanceOf<T>,
60+
) {
61+
if now < self.start {
62+
return;
63+
}
64+
65+
// Saturation is fine as that just means that the negative outcome is more valuable than the
66+
// positive outcome.
67+
let margin_abs = positive_outcome_price.saturating_sub(negative_outcome_price);
68+
// In case of error, we're using zero here as a defensive default value.
69+
let margin_rel = margin_abs.bdiv(negative_outcome_price).unwrap_or(Zero::zero());
70+
71+
if margin_abs >= self.price_margin_abs && margin_rel >= self.price_margin_rel {
72+
// Saturation is fine as that would mean the oracle has been collecting data for
73+
// hundreds of years.
74+
self.pass_score.saturating_inc();
75+
} else {
76+
// Saturation is fine as that would mean the oracle has been collecting data for
77+
// hundreds of years.
78+
self.reject_score.saturating_inc();
79+
}
80+
}
81+
82+
pub fn evaluate(&self) -> bool {
83+
// Saturating is fine as that just means that the `reject_score` is higher than `pass_score`
84+
// anyways.
85+
let score_margin = self.pass_score.saturating_sub(self.reject_score);
86+
87+
score_margin >= self.victory_margin
88+
}
89+
90+
/// Skips update on this block and awards a point to the negative outcome.
91+
pub fn skip_update(&mut self, now: BlockNumberFor<T>) {
92+
if now < self.start {
93+
return;
94+
}
95+
96+
// Saturation is fine as that would mean the oracle has been collecting data for
97+
// hundreds of years.
98+
self.reject_score.saturating_inc();
99+
}
100+
}

zrml/neo-swaps/src/types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
mod decision_market_benchmark_helper;
1919
mod decision_market_oracle;
20+
mod decision_market_oracle_scoreboard;
2021
mod fee_distribution;
2122
mod max_assets;
2223
mod pool;
@@ -25,6 +26,7 @@ mod pool_type;
2526
#[cfg(feature = "runtime-benchmarks")]
2627
pub use decision_market_benchmark_helper::*;
2728
pub use decision_market_oracle::*;
29+
pub use decision_market_oracle_scoreboard::*;
2830
pub(crate) use fee_distribution::*;
2931
pub(crate) use max_assets::*;
3032
pub(crate) use pool::*;

0 commit comments

Comments
 (0)