From 518d7e840da6f5c07f449dcd502566ea13169e88 Mon Sep 17 00:00:00 2001 From: Amiya Behera Date: Wed, 10 Jul 2024 17:48:11 +0530 Subject: [PATCH] positive externality reward --- Cargo.lock | 1 + .../runtime-templates/simple/src/lib.rs | 1 + .../positive-externality/src/lib.rs | 200 ++++++++++++++++-- .../positive-externality/src/mock.rs | 1 + .../positive-externality/src/tests.rs | 2 +- .../positive-externality/src/types.rs | 40 ++++ .../schelling-game-shared/src/score_game.rs | 14 +- .../schelling-game-shared/src/share_link.rs | 2 +- .../schelling-game-shared/src/tests.rs | 29 ++- traits/trait-schelling-game-shared/src/lib.rs | 2 +- traits/trait-shared-storage/Cargo.toml | 1 + traits/trait-shared-storage/src/lib.rs | 2 + 12 files changed, 259 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 854d911..acf6f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16508,6 +16508,7 @@ version = "0.1.0" dependencies = [ "frame-support", "parity-scale-codec", + "sp-std", ] [[package]] diff --git a/container-chains/runtime-templates/simple/src/lib.rs b/container-chains/runtime-templates/simple/src/lib.rs index 4330b8c..d9c2f20 100644 --- a/container-chains/runtime-templates/simple/src/lib.rs +++ b/container-chains/runtime-templates/simple/src/lib.rs @@ -682,6 +682,7 @@ impl pallet_positive_externality::Config for Runtime { type SharedStorageSource = SharedStorage; type Currency = Balances; type SchellingGameSharedSource = SchellingGameShared; + type Reward = (); } impl pallet_department_funding::Config for Runtime { diff --git a/custom-pallets/positive-externality/src/lib.rs b/custom-pallets/positive-externality/src/lib.rs index acaa646..5357bbf 100644 --- a/custom-pallets/positive-externality/src/lib.rs +++ b/custom-pallets/positive-externality/src/lib.rs @@ -26,21 +26,28 @@ use frame_support::sp_runtime::traits::Saturating; use frame_support::sp_runtime::SaturatedConversion; use frame_support::{dispatch::DispatchResult, ensure}; use frame_support::{ - traits::{Currency, ExistenceRequirement, Get, ReservableCurrency, WithdrawReasons}, + traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, ReservableCurrency, WithdrawReasons}, PalletId, }; use frame_system::pallet_prelude::*; -use pallet_schelling_game_shared::types::{Period, PhaseData, RangePoint, SchellingGameType}; +use pallet_schelling_game_shared::types::{ + JurorGameResult, Period, PhaseData, RangePoint, SchellingGameType, WinningDecision, +}; use pallet_sortition_sum_game::types::SumTreeName; use pallet_support::{ - ensure_content_is_valid, new_who_and_when, remove_from_vec, Content, PostId, WhoAndWhen, - WhoAndWhenOf, + ensure_content_is_valid, new_when_details, new_who_and_when, remove_from_vec, Content, PostId, + WhenDetails, WhenDetailsOf, WhoAndWhen, WhoAndWhenOf, }; +use types::{Incentives, IncentivesMetaData}; + use sp_std::prelude::*; use trait_schelling_game_shared::SchellingGameSharedLink; use trait_shared_storage::SharedStorageLink; type AccountIdOf = ::AccountId; type BalanceOf = <::Currency as Currency>>::Balance; +type PositiveImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::PositiveImbalance; pub type BlockNumberOf = BlockNumberFor; pub type SumTreeNameType = SumTreeName, BlockNumberOf>; @@ -72,8 +79,12 @@ pub mod pallet { RangePoint = RangePoint, Period = Period, PhaseData = PhaseData, + WinningDecision = WinningDecision, + JurorGameResult = JurorGameResult, >; type Currency: ReservableCurrency; + /// Handler for the unbalanced increment when rewarding (minting rewards) + type Reward: OnUnbalanced>; } // The pallet's runtime storage items. @@ -129,6 +140,21 @@ pub mod pallet { pub type ValidationBlock = StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberOf, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn incentives_count)] + pub type IncentiveCount = + StorageMap<_, Blake2_128Concat, T::AccountId, Incentives>; + + #[pallet::type_value] + pub fn IncentivesMetaValue() -> IncentivesMetaData { + IncentivesMetaData::default() + } + + #[pallet::storage] + #[pallet::getter(fn incentives_meta)] + pub type IncentivesMeta = + StorageValue<_, IncentivesMetaData, ValueQuery, IncentivesMetaValue>; + // Pallets use events to inform users when important changes are made. // https://docs.substrate.io/main-docs/build/events-errors/ #[pallet::event] @@ -151,6 +177,9 @@ pub mod pallet { LessThanMinStake, CannotStakeNow, ChoiceOutOfRange, + NotReachedMinimumDecision, + NoIncentiveCount, + AlreadyFunded, } // Dispatchable functions allows users to interact with the pallet and invoke state changes. @@ -390,30 +419,161 @@ pub mod pallet { #[pallet::call_index(9)] #[pallet::weight(0)] - pub fn get_incentives( + pub fn add_incentive_count( origin: OriginFor, user_to_calculate: T::AccountId, ) -> DispatchResult { - let _who = ensure_signed(origin)?; - let pe_block_number = >::get(user_to_calculate.clone()); + let who = ensure_signed(origin)?; + let block_number = >::get(user_to_calculate.clone()); let key = SumTreeName::PositiveExternality { - user_address: user_to_calculate.clone(), - block_number: pe_block_number.clone(), + user_address: user_to_calculate, + block_number: block_number.clone(), }; - - let phase_data = Self::get_phase_data(); - T::SchellingGameSharedSource::get_incentives_score_schelling_helper_link( - key.clone(), - phase_data, - RangePoint::ZeroToFive, - )?; - - let score = T::SchellingGameSharedSource::get_mean_value_link(key.clone())?; - // println!("Score {:?}", score); - T::SharedStorageSource::set_positive_externality_link(user_to_calculate, score)?; + let (juror_game_result, stake) = + T::SchellingGameSharedSource::get_result_of_juror_score( + key.clone(), + who.clone(), + RangePoint::ZeroToFive, + )?; + + T::SchellingGameSharedSource::add_to_incentives_count(key, who.clone())?; + let incentive_count_option = >::get(&who); + match incentive_count_option { + Some(mut incentive) => { + match juror_game_result { + JurorGameResult::Won => { + incentive.number_of_games += 1; + incentive.winner += 1; + incentive.total_stake += stake; + } + JurorGameResult::Lost => { + incentive.number_of_games += 1; + incentive.loser += 1; + incentive.total_stake += stake; + } + + JurorGameResult::Draw => { + incentive.number_of_games += 1; + incentive.total_stake += stake; + } + }; + >::mutate(&who, |incentive_option| { + *incentive_option = Some(incentive); + }); + } + None => { + let mut winner = 0; + let mut loser = 0; + match juror_game_result { + JurorGameResult::Won => { + winner = 1; + } + JurorGameResult::Lost => { + loser = 1; + } + JurorGameResult::Draw => {} + }; + let number_of_games = 1; + let new_incentives: Incentives = + Incentives::new(number_of_games, winner, loser, stake); + >::insert(&who, new_incentives); + } + } Ok(()) } + + + // Provide incentives + + #[pallet::call_index(10)] + #[pallet::weight(0)] + pub fn get_incentives(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let incentive_meta = >::get(); + let total_games_allowed = incentive_meta.total_number; + let incentive_count_option = >::get(&who); + match incentive_count_option { + Some(incentive) => { + let total_number_games = incentive.number_of_games; + if total_number_games >= total_games_allowed { + let new_incentives: Incentives = Incentives::new(0, 0, 0, 0); + >::mutate(&who, |incentive_option| { + *incentive_option = Some(new_incentives); + }); + + let total_win = incentive.winner; + let total_lost = incentive.loser; + + // Define multipliers + let win_multiplier = 10 * 100; + let lost_multiplier = incentive_meta.disincentive_times * 100; + + // Calculate total_win_incentives and total_lost_incentives + let total_win_incentives = total_win.checked_mul(win_multiplier); + let total_lost_incentives = total_lost.checked_mul(lost_multiplier); + + // Calculate total_incentives, handling overflow or negative errors + let total_incentives = match (total_win_incentives, total_lost_incentives) { + (Some(win), Some(lost)) => win.checked_sub(lost).unwrap_or(0), + _ => 0, // If multiplication overflowed, set total_incentives to 0 + }; + + let mut stake = incentive.total_stake; + // Deduct 1% of the stake if total_lost > total_win + if total_lost > total_win { + let stake_deduction = stake / 100; // 1% of the stake + stake = stake.checked_sub(stake_deduction).unwrap_or(stake); + // Safe subtraction + // println!("Stake deducted by 1%: {}", stake); + } + + let total_fund = stake.checked_add(total_incentives).unwrap_or(0); + + let balance = Self::u64_to_balance_saturated(total_fund); + + let r = + ::Currency::deposit_into_existing(&who, balance) + .ok() + .unwrap(); + ::Reward::on_unbalanced(r); + // Provide the incentives + } else { + Err(Error::::NotReachedMinimumDecision)? + } + } + None => Err(Error::::NoIncentiveCount)?, + } + Ok(()) + } + + // #[pallet::call_index(9)] + // #[pallet::weight(0)] + // pub fn get_incentives( + // origin: OriginFor, + // user_to_calculate: T::AccountId, + // ) -> DispatchResult { + // let _who = ensure_signed(origin)?; + // let pe_block_number = >::get(user_to_calculate.clone()); + + // let key = SumTreeName::PositiveExternality { + // user_address: user_to_calculate.clone(), + // block_number: pe_block_number.clone(), + // }; + + // let phase_data = Self::get_phase_data(); + // T::SchellingGameSharedSource::get_incentives_score_schelling_helper_link( + // key.clone(), + // phase_data, + // RangePoint::ZeroToFive, + // )?; + + // let score = T::SchellingGameSharedSource::get_mean_value_link(key.clone())?; + // // println!("Score {:?}", score); + // T::SharedStorageSource::set_positive_externality_link(user_to_calculate, score)?; + + // Ok(()) + // } } } diff --git a/custom-pallets/positive-externality/src/mock.rs b/custom-pallets/positive-externality/src/mock.rs index 9b1418b..a9e342a 100644 --- a/custom-pallets/positive-externality/src/mock.rs +++ b/custom-pallets/positive-externality/src/mock.rs @@ -106,6 +106,7 @@ impl pallet_template::Config for Test { type SharedStorageSource = SharedStorage; type Currency = Balances; // New code type SchellingGameSharedSource = SchellingGameShared; + type Reward = (); } // Build genesis storage according to the mock runtime. diff --git a/custom-pallets/positive-externality/src/tests.rs b/custom-pallets/positive-externality/src/tests.rs index 547ef04..7d402a3 100644 --- a/custom-pallets/positive-externality/src/tests.rs +++ b/custom-pallets/positive-externality/src/tests.rs @@ -338,6 +338,6 @@ fn test_commit_and_incentives_vote() { System::set_block_number(12980260); assert_ok!(TemplateModule::pass_period(RuntimeOrigin::signed(4), 1)); - assert_ok!(TemplateModule::get_incentives(RuntimeOrigin::signed(4), 1)); + // assert_ok!(TemplateModule::get_incentives(RuntimeOrigin::signed(4), 1)); }) } diff --git a/custom-pallets/positive-externality/src/types.rs b/custom-pallets/positive-externality/src/types.rs index 39ca8da..49ad6a1 100644 --- a/custom-pallets/positive-externality/src/types.rs +++ b/custom-pallets/positive-externality/src/types.rs @@ -33,3 +33,43 @@ pub struct PositiveExternalityPostUpdate { pub content: Option, pub hidden: Option, } + +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct Incentives { + pub number_of_games: u64, + pub winner: u64, + pub loser: u64, + pub total_stake: u64, + pub start: WhenDetailsOf, +} + +impl Incentives { + pub fn new(number_of_games: u64, winner: u64, loser: u64, stake: u64) -> Self { + Incentives { + number_of_games: number_of_games, + winner: winner, + loser: loser, + total_stake: stake, + start: new_when_details::(), + } + } +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct IncentivesMetaData { + pub total_number: u64, + pub disincentive_times: u64, + pub total_block: BlockNumberOf, +} + +impl Default for IncentivesMetaData { + fn default() -> Self { + Self { + total_number: 20, + disincentive_times: 15, // its 1.5 + total_block: 432000u64.saturated_into::>(), // 30 days = (24*60*60)/6 * 30 + } + } +} diff --git a/custom-pallets/schelling-game-shared/src/score_game.rs b/custom-pallets/schelling-game-shared/src/score_game.rs index 4ade3d8..a33c45c 100644 --- a/custom-pallets/schelling-game-shared/src/score_game.rs +++ b/custom-pallets/schelling-game-shared/src/score_game.rs @@ -173,7 +173,7 @@ impl Pallet { key: SumTreeNameType, who: AccountIdOf, range_point: RangePoint, - ) -> Result { + ) -> Result<(JurorGameResult, u64), DispatchError> { match >::get(&key) { Some(period) => { ensure!(period == Period::Execution, Error::::PeriodDontMatch); @@ -182,6 +182,14 @@ impl Pallet { } let new_mean = Self::get_mean_value(key.clone())?; + let drawn_juror = >::get(&key); + let mut stake = 0; + if let Ok(i) = drawn_juror.binary_search_by(|(c, _)| c.cmp(&who.clone())) { + stake = drawn_juror[i].1; + } else { + Err(Error::::StakeDoesNotExists)? + } + let incentives_range = Self::get_incentives_range(range_point); let reveal_votes = >::get(&key, &who); match reveal_votes { @@ -193,9 +201,9 @@ impl Pallet { && vote * 1000 <= new_mean.checked_add(incentives_range).unwrap() { // get incentives - Ok(JurorGameResult::Won) + Ok((JurorGameResult::Won, stake)) } else { - Ok(JurorGameResult::Lost) + Ok((JurorGameResult::Lost, stake)) } } None => Err(Error::::VoteNotRevealed)?, diff --git a/custom-pallets/schelling-game-shared/src/share_link.rs b/custom-pallets/schelling-game-shared/src/share_link.rs index 7ddc018..2625ea2 100644 --- a/custom-pallets/schelling-game-shared/src/share_link.rs +++ b/custom-pallets/schelling-game-shared/src/share_link.rs @@ -320,7 +320,7 @@ impl SchellingGameSharedLink for Pallet { key: Self::SumTreeName, who: Self::AccountId, range_point: Self::RangePoint, - ) -> Result { + ) -> Result<(JurorGameResult, u64), DispatchError> { Self::get_result_of_juror_score(key, who, range_point) } diff --git a/custom-pallets/schelling-game-shared/src/tests.rs b/custom-pallets/schelling-game-shared/src/tests.rs index e11cd52..3417393 100644 --- a/custom-pallets/schelling-game-shared/src/tests.rs +++ b/custom-pallets/schelling-game-shared/src/tests.rs @@ -1149,20 +1149,29 @@ fn score_schelling_game_value_test() { assert_ok!(TemplateModule::set_new_mean_value(key.clone())); let mean_values = TemplateModule::new_mean_reveal_score(key.clone()); assert_eq!(2000, mean_values.unwrap()); - let result = + let result_stake = TemplateModule::get_result_of_juror_score(key.clone(), 4, RangePoint::ZeroToTen); - assert_eq!(result.unwrap(), JurorGameResult::Won); - let result = + let (result, _) = result_stake.unwrap(); + assert_eq!(result, JurorGameResult::Won); + let result_stake = TemplateModule::get_result_of_juror_score(key.clone(), 7, RangePoint::ZeroToTen); - assert_eq!(result.unwrap(), JurorGameResult::Won); - let result = + let (result, _) = result_stake.unwrap(); + + assert_eq!(result, JurorGameResult::Won); + let result_stake = TemplateModule::get_result_of_juror_score(key.clone(), 13, RangePoint::ZeroToTen); - assert_eq!(result.unwrap(), JurorGameResult::Lost); - let result = + let (result, _) = result_stake.unwrap(); + + assert_eq!(result, JurorGameResult::Lost); + let result_stake = TemplateModule::get_result_of_juror_score(key.clone(), 14, RangePoint::ZeroToTen); - assert_eq!(result.unwrap(), JurorGameResult::Won); - let result = + let (result, _) = result_stake.unwrap(); + + assert_eq!(result, JurorGameResult::Won); + let result_stake = TemplateModule::get_result_of_juror_score(key.clone(), 15, RangePoint::ZeroToTen); - assert_eq!(result.unwrap(), JurorGameResult::Lost); + let (result, _) = result_stake.unwrap(); + + assert_eq!(result, JurorGameResult::Lost); }); } diff --git a/traits/trait-schelling-game-shared/src/lib.rs b/traits/trait-schelling-game-shared/src/lib.rs index f957584..35c3f80 100644 --- a/traits/trait-schelling-game-shared/src/lib.rs +++ b/traits/trait-schelling-game-shared/src/lib.rs @@ -157,7 +157,7 @@ pub trait SchellingGameSharedLink { key: Self::SumTreeName, who: Self::AccountId, range_point: Self::RangePoint, - ) -> Result; + ) -> Result<(Self::JurorGameResult, u64), DispatchError>; fn set_new_mean_value(key: Self::SumTreeName) -> DispatchResult; diff --git a/traits/trait-shared-storage/Cargo.toml b/traits/trait-shared-storage/Cargo.toml index 0423f3d..1792c27 100644 --- a/traits/trait-shared-storage/Cargo.toml +++ b/traits/trait-shared-storage/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] parity-scale-codec = { workspace = true } frame-support = { workspace = true} +sp-std= { workspace = true} diff --git a/traits/trait-shared-storage/src/lib.rs b/traits/trait-shared-storage/src/lib.rs index 2402b43..0187712 100644 --- a/traits/trait-shared-storage/src/lib.rs +++ b/traits/trait-shared-storage/src/lib.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::DispatchResult; +use sp_std::vec::Vec; + pub trait SharedStorageLink { type AccountId;