diff --git a/Cargo.lock b/Cargo.lock index 1f1acee77..11ba9981d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1668,7 +1668,7 @@ dependencies = [ "linregress", "log", "parity-scale-codec", - "paste", + "paste 1.0.5", "sp-api", "sp-io", "sp-runtime", @@ -1754,7 +1754,7 @@ dependencies = [ "log", "once_cell", "parity-scale-codec", - "paste", + "paste 1.0.5", "serde", "smallvec", "sp-arithmetic", @@ -3313,6 +3313,24 @@ dependencies = [ "statrs", ] +[[package]] +name = "lite-json" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0460d985423a026b4d9b828a7c6eed1bcf606f476322f3f9b507529686a61715" +dependencies = [ + "lite-parser", +] + +[[package]] +name = "lite-parser" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c50092e40e0ccd1bf2015a10333fde0502ff95b832b0895dc1ca0d7ac6c52f6" +dependencies = [ + "paste 0.1.18", +] + [[package]] name = "lock_api" version = "0.3.4" @@ -3701,6 +3719,9 @@ dependencies = [ "chrono", "frame-support", "frame-system", + "hex", + "hex-literal", + "lite-json", "log", "module-primitives", "pallet-aura", @@ -3718,10 +3739,10 @@ dependencies = [ "pallet-treasury", "parity-scale-codec", "rand 0.8.4", - "serde", "sp-consensus-aura", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-std", "static_assertions", @@ -4831,12 +4852,31 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + [[package]] name = "paste" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "pbkdf2" version = "0.4.0" @@ -7212,7 +7252,7 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste", + "paste 1.0.5", ] [[package]] @@ -7682,7 +7722,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste", + "paste 1.0.5", "rand 0.7.3", "serde", "sp-application-crypto", @@ -8846,7 +8886,7 @@ dependencies = [ "lazy_static", "libc", "log", - "paste", + "paste 1.0.5", "psm", "region", "rustc-demangle", diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index bf2f066c0..1af986c47 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -922,6 +922,7 @@ fn testnet_genesis( rewards_allowance_dhx_daily: FIVE_THOUSAND, // 5000 DHX rewards_allowance_dhx_for_date_remaining: Default::default(), rewards_allowance_dhx_for_date_remaining_distributed: Default::default(), + rewards_allowance_dhx_for_miner_for_date_remaining_distributed: Default::default(), rewards_multiplier_paused: false, rewards_multiplier_reset: false, rewards_multiplier_default_change: 10u32, @@ -933,20 +934,37 @@ fn testnet_genesis( rewards_multiplier_current_period_days_remaining: Default::default(), rewards_multiplier_operation: 1u8, registered_dhx_miners: vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), + // get_account_id_from_seed::("Alice"), + // get_account_id_from_seed::("Bob"), + // get_account_id_from_seed::("Charlie"), + // Alice + vec![212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + // Bob + vec![142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72], ], + rewards_eligible_miners_for_date: Default::default(), rewards_aggregated_dhx_for_all_miners_for_date: Default::default(), rewards_accumulated_dhx_for_miner_for_date: Default::default(), min_bonded_dhx_daily: TEN, // 10 DHX min_bonded_dhx_daily_default: TEN, // 10 DHX - min_mpower_daily: 5u128, - min_mpower_daily_default: 5u128, - cooling_off_period_days: 7u32, - cooling_off_period_days_remaining: vec![ + min_mpower_daily: 1u128, + min_mpower_daily_default: 1u128, + challenge_period_days: 7u64, + cooling_down_period_days: 7u32, + cooling_down_period_days_remaining: vec![ ( - get_account_id_from_seed::("Alice"), + // get_account_id_from_seed::("Alice").encode(), + // Alice + vec![212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + ( + 0, + 7u32, + 0u32, + ), + ), + ( + // Bob + vec![142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72], ( 0, 7u32, @@ -1051,6 +1069,7 @@ fn mainnet_genesis( rewards_allowance_dhx_daily: FIVE_THOUSAND, // 5000 DHX rewards_allowance_dhx_for_date_remaining: Default::default(), rewards_allowance_dhx_for_date_remaining_distributed: Default::default(), + rewards_allowance_dhx_for_miner_for_date_remaining_distributed: Default::default(), rewards_multiplier_paused: false, rewards_multiplier_reset: false, rewards_multiplier_default_change: 10u32, @@ -1062,20 +1081,37 @@ fn mainnet_genesis( rewards_multiplier_current_period_days_remaining: Default::default(), rewards_multiplier_operation: 1u8, registered_dhx_miners: vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), + // get_account_id_from_seed::("Alice"), + // get_account_id_from_seed::("Bob"), + // get_account_id_from_seed::("Charlie"), + // Alice + vec![212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + // Bob + vec![142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72], ], + rewards_eligible_miners_for_date: Default::default(), rewards_aggregated_dhx_for_all_miners_for_date: Default::default(), rewards_accumulated_dhx_for_miner_for_date: Default::default(), min_bonded_dhx_daily: TEN, // 10 DHX min_bonded_dhx_daily_default: TEN, // 10 DHX - min_mpower_daily: 5u128, - min_mpower_daily_default: 5u128, - cooling_off_period_days: 7u32, - cooling_off_period_days_remaining: vec![ + min_mpower_daily: 1u128, + min_mpower_daily_default: 1u128, + challenge_period_days: 7u64, + cooling_down_period_days: 7u32, + cooling_down_period_days_remaining: vec![ ( - get_account_id_from_seed::("Alice"), + // get_account_id_from_seed::("Alice").encode(), + // Alice + vec![212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125], + ( + 0, + 7u32, + 0u32, + ), + ), + ( + // Bob + vec![142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72], ( 0, 7u32, diff --git a/pallets/mining/rewards-allowance/Cargo.toml b/pallets/mining/rewards-allowance/Cargo.toml index 025ba24b7..2a1124ff0 100644 --- a/pallets/mining/rewards-allowance/Cargo.toml +++ b/pallets/mining/rewards-allowance/Cargo.toml @@ -11,8 +11,11 @@ targets = ['x86_64-unknown-linux-gnu'] default = ['std'] std = [ 'chrono/std', + 'hex/std', + # 'hex-literal/std', + 'lite-json/std', 'log/std', - 'serde/std', + # 'serde/std', 'rand/std', 'substrate-fixed/std', 'codec/std', @@ -34,6 +37,7 @@ std = [ 'sp-consensus-aura/std', 'sp-core/std', 'sp-io/std', + 'sp-keystore/std', 'sp-runtime/std', 'sp-std/std', 'module-primitives/std', @@ -42,8 +46,11 @@ std = [ [dependencies] static_assertions = '1.1.0' chrono = { version = '0.4.19', default_features = false } +hex = { version = '0.4.3', default_features = false, features = ['alloc'] } +hex-literal = { version = '0.3.1', default_features = false } +lite-json = { version = "0.1", default-features = false } log = { version = '0.4.14', default-features = false } -serde = { version = '1.0.126', features = ['derive'] } +# serde = { version = '1.0.126', default-features = false, features = ['derive'] } rand = { version = '0.8.4', default-features = false } substrate-fixed = { git = "https://github.com/encointer/substrate-fixed", version = '0.5.6' } codec = { version = '2.2.0', package = 'parity-scale-codec', default-features = false, features = ['derive', 'max-encoded-len'] } @@ -65,6 +72,7 @@ pallet-treasury = { git = 'https://github.com/DataHighway-DHX/substrate', rev = sp-consensus-aura = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false } sp-core = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false } sp-io = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false } +sp-keystore = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false, optional = true } sp-runtime = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false } sp-std = { git = 'https://github.com/DataHighway-DHX/substrate', rev = 'f5dc02a8a491c149fba05a2a5a51c80ce1b3cead', default-features = false } module-primitives = { version = '3.0.6', default-features = false, path = '../../primitives' } diff --git a/pallets/mining/rewards-allowance/src/lib.rs b/pallets/mining/rewards-allowance/src/lib.rs index 931a3ee27..a44d4ee92 100644 --- a/pallets/mining/rewards-allowance/src/lib.rs +++ b/pallets/mining/rewards-allowance/src/lib.rs @@ -1,8 +1,117 @@ +//! +//! # Rewards Allowance with Offchain Worker Pallet +//! +//! TODO - add description +//! +//! Run `cargo doc --package rewards-allowance --open` to view this module's +//! documentation. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! +//! ## Overview +//! +//! Offchain Worker (OCW) will be triggered after every block, fetch the mPower of current +//! of registered DHX users and prepare either signed or unsigned transaction to feed the +//! result back on chain. +//! +//! Additional logic in OCW is put in place to prevent spamming the network with both signed +//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only +//! one unsigned transaction floating in the network. +//! +//! The on-chain logic will integrate their mPower values in the calculation of their +//! accumulated and aggregated rewards allowance each day. +//! +//! TODO - add further overview +//! #![cfg_attr(not(feature = "std"), no_std)] -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// +// We need this to allow use of `format!` in a no_std environment +#![crate_type = "dylib"] +#[macro_use] +extern crate alloc; + +use core::str; +use chrono::{ + NaiveDateTime, +}; +use codec::{ + Decode, + Encode, + Error, + Input, + Output, +}; +use frame_support::{ + dispatch::DispatchResult, + traits::{ + Currency, + ExistenceRequirement, + Get, + }, +}; +use frame_system::{ + self as system, + offchain::{ + AppCrypto, CreateSignedTransaction, SendUnsignedTransaction, Signer, SubmitTransaction, + }, +}; +use hex; // to use hex::encode("..."); +use hex_literal::{ // to use hex!("..."); + hex as write_hex, +}; +// use serde::{Deserialize, Serialize}; +use lite_json::json::JsonValue; +use log::{warn, info}; +use module_primitives::{ + types::{ + AccountId, + Balance, + Signature, + }, +}; +use pallet_balances::{BalanceLock}; +use rand::{seq::SliceRandom, Rng}; +use sp_core::{ + crypto::{KeyTypeId, Public}, + sr25519, +}; +use sp_runtime::{ + offchain::{ + http, + storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, + Duration, + }, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + traits::{ + IdentifyAccount, + One, + Verify, + Zero, + }, + RuntimeDebug, +}; +use sp_std::{ + convert::{ + TryFrom, + TryInto, + }, + // vec::Vec, + prelude::*, // Imports Vec +}; +use substrate_fixed::{ + types::{ + extra::U3, + U16F16, + U32F32, + U64F64, + }, + FixedU32, + FixedU128, +}; + pub use pallet::*; #[cfg(test)] @@ -14,57 +123,53 @@ mod tests; // #[cfg(feature = "runtime-benchmarks")] // mod benchmarking; +/// Defines application identifier for crypto keys of this module. +/// +/// Every module that deals with signatures needs to declare its unique identifier for +/// its crypto keys. +/// When offchain worker is signing transactions it's going to request keys of type +/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction. +/// The keys can be inserted manually via RPC (see `author_insertKey`). +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"mpow"); + +/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers. +/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment +/// the types with this pallet-specific identifier. +pub mod crypto { + use super::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + MultiSigner, + MultiSignature, + }; + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + // implemented for off-chain workers in runtime + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + // implemented for mock runtime in test + impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> + for TestAuthId + { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + #[frame_support::pallet] pub mod pallet { - use log::{warn, info}; - use chrono::{ - NaiveDateTime, - }; - use rand::{seq::SliceRandom, Rng}; - use substrate_fixed::{ - types::{ - extra::U3, - U16F16, - U32F32, - U64F64, - }, - FixedU128, - }; - use codec::{ - Decode, - Encode, - // MaxEncodedLen, - }; - use frame_support::{dispatch::DispatchResult, pallet_prelude::*, - traits::{ - Currency, - ExistenceRequirement, - }, - }; + use super::*; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_std::{ - convert::{ - TryFrom, - TryInto, - }, - prelude::*, // Imports Vec - }; - use sp_core::{ - sr25519, - }; - use sp_runtime::traits::{ - IdentifyAccount, - One, - Verify, - }; - use pallet_balances::{BalanceLock}; - use module_primitives::{ - types::{ - AccountId, - Balance, - Signature, - }, - }; // this is only a default for test purposes and fallback. // set this to 0u128 in production @@ -79,14 +184,96 @@ pub mod pallet { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config + pub trait Config: CreateSignedTransaction> + + frame_system::Config + pallet_democracy::Config + pallet_balances::Config + pallet_timestamp::Config + pallet_treasury::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; + + /// The overarching dispatch call type. + type Call: From>; + type Currency: Currency; + + // Configuration parameters + + /// A grace period after we send transaction. + /// + /// To avoid sending too many transactions, we only attempt to send one + /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate + /// sending between distinct runs of this offchain worker. + #[pallet::constant] + type GracePeriod: Get; + + /// Number of blocks of cooldown after unsigned transaction is included. + /// + /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval` + /// blocks. + #[pallet::constant] + type UnsignedInterval: Get; + + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple pallets send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + } + + #[derive(Debug)] + struct MPowerAccountData { + acct_id: U, + mpower: V, + } + + #[derive(Debug)] + struct MPowerJSONResponseData { + data: Vec>, + } + + /// Payload used to hold mPower data required to submit a transaction. + #[cfg_attr(feature = "std", derive(Debug))] + #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)] + pub struct MPowerPayload { + pub account_id_registered_dhx_miner: U, + pub mpower_registered_dhx_miner: V, + pub received_at_date: W, + pub received_at_block_number: X, + } + + #[cfg_attr(feature = "std", derive(Debug))] + #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)] + pub struct FetchedMPowerForAccountsForDatePayload { + pub start_of_requested_date_millis: U, + pub all_miner_public_keys_fetched: V, + pub total_mpower_fetched: W, + } + + type MPowerPayloadData = MPowerPayload< + Vec, // ::AccountId, + u128, + Date, + ::BlockNumber, + >; + + type FetchedMPowerForAccountsForDatePayloadData = FetchedMPowerForAccountsForDatePayload< + Date, + Vec>, + u128, + >; + + // type MPowerAccountDataType = MPowerAccountData< + // ::AccountId, + // u128, + // >; + + enum TransactionType { + Raw, + None, } #[pallet::pallet] @@ -104,21 +291,11 @@ pub mod pallet { pub(super) type BondedDHXForAccountForDate = StorageMap<_, Blake2_128Concat, ( Date, - T::AccountId, + Vec, // public key of AccountId ), BalanceOf, >; - #[pallet::storage] - #[pallet::getter(fn mpower_of_account_for_date)] - pub(super) type MPowerForAccountForDate = StorageMap<_, Blake2_128Concat, - ( - Date, - T::AccountId, - ), - u128, - >; - #[pallet::storage] #[pallet::getter(fn rewards_allowance_dhx_for_date_remaining)] pub(super) type RewardsAllowanceDHXForDateRemaining = StorageMap<_, Blake2_128Concat, @@ -126,6 +303,7 @@ pub mod pallet { BalanceOf >; + // checks if all eligible registered dhx miners have already received their reward for a specific date #[pallet::storage] #[pallet::getter(fn rewards_allowance_dhx_for_date_remaining_distributed)] pub(super) type RewardsAllowanceDHXForDateRemainingDistributed = StorageMap<_, Blake2_128Concat, @@ -133,6 +311,17 @@ pub mod pallet { bool >; + // checks if a specific registered dhx miner has already received their reward for a specific date + #[pallet::storage] + #[pallet::getter(fn rewards_allowance_dhx_for_miner_for_date_remaining_distributed)] + pub(super) type RewardsAllowanceDHXForMinerForDateRemainingDistributed = StorageMap<_, Blake2_128Concat, + ( + Date, + Vec, // public key of AccountId + ), + bool + >; + #[pallet::storage] #[pallet::getter(fn rewards_allowance_dhx_daily)] pub(super) type RewardsAllowanceDHXDaily = StorageValue<_, BalanceOf>; @@ -201,7 +390,7 @@ pub mod pallet { pub(super) type RewardsAccumulatedDHXForMinerForDate = StorageMap<_, Blake2_128Concat, ( Date, - T::AccountId, + Vec, ), BalanceOf, >; @@ -211,7 +400,14 @@ pub mod pallet { /// TWOX-NOTE: Safe, as increasing integer keys are safe. #[pallet::storage] #[pallet::getter(fn registered_dhx_miners)] - pub(super) type RegisteredDHXMiners = StorageValue<_, Vec>; + pub(super) type RegisteredDHXMiners = StorageValue<_, Vec>>; + + #[pallet::storage] + #[pallet::getter(fn rewards_eligible_miners_for_date)] + pub(super) type RewardsEligibleMinersForDate = StorageMap<_, Blake2_128Concat, + Date, + Vec>, + >; #[pallet::storage] #[pallet::getter(fn min_bonded_dhx_daily)] @@ -230,36 +426,71 @@ pub mod pallet { pub(super) type MinMPowerDailyDefault = StorageValue<_, u128>; #[pallet::storage] - #[pallet::getter(fn cooling_off_period_days)] - pub(super) type CoolingOffPeriodDays = StorageValue<_, u32>; + #[pallet::getter(fn challenge_period_days)] + pub(super) type ChallengePeriodDays = StorageValue<_, u64>; #[pallet::storage] - #[pallet::getter(fn cooling_off_period_days_remaining)] - pub(super) type CoolingOffPeriodDaysRemaining = StorageMap<_, Blake2_128Concat, - T::AccountId, + #[pallet::getter(fn cooling_down_period_days)] + pub(super) type CoolingDownPeriodDays = StorageValue<_, u32>; + + #[pallet::storage] + #[pallet::getter(fn cooling_down_period_days_remaining)] + pub(super) type CoolingDownPeriodDaysRemaining = StorageMap<_, Blake2_128Concat, + Vec, // public key of AccountId ( - // date when cooling off period started for a given miner, or the date when we last reduced their cooling off period. - // we do not reduce their cooling off period days remaining if we've already set this to a date that is the + // date when cooling down period started for a given miner, or the date when we last reduced their cooling down period. + // we do not reduce their cooling down period days remaining if we've already set this to a date that is the // current date for a miner (i.e. only reduce the days remaining once per day per miner) Date, u32, // days remaining - // 0: - // unbonded (i.e. never bonded, insufficient bonded, or finished cool-down period and no longer bonding) and - // insufficient mPower (i.e. less than min. mPower) - // 1: bonded/bonding/mPower (i.e. waiting in the cool-down period before start getting rewards or eligible for rewards) - // 2: unbonding (i.e. if they are bonding less than the threshold whilst getting rewards, - // or no longer have the min. mPower, then - // this unbonding starts and they must wait until it finishes, which is when this value - // would be set to 0u32, before bonding and then waiting for the cool-down period all over again) - u32, + u32, // 0: during cooldown period, 1: cooldown period finished (don't reset cooldown days remaining unless bond min. then unbond again) + ), + >; + + // Offchain workers + + /// Recently submitted mPower data. + #[pallet::storage] + #[pallet::getter(fn mpower_of_account_for_date)] + pub(super) type MPowerForAccountForDate = StorageMap<_, Blake2_128Concat, + ( + Date, // converted to start of date + Vec, // T::AccountId, + ), + u128, // mPower + >; + + /// Recently submitted finished fetching mPower data. + #[pallet::storage] + #[pallet::getter(fn finished_fetching_mpower_for_accounts_for_date)] + pub(super) type FinishedFetchingMPowerForAccountsForDate = StorageMap<_, Blake2_128Concat, + Date, // converted to start of date + ( + Vec>, + u128, // mPower ), >; + /// Defines the block when next unsigned transaction (specifically for fetching the mPower) will be accepted. + /// + /// To prevent spam of unsigned (and unpayed!) transactions on the network, + /// we only allow one transaction every `T::UnsignedInterval` blocks. + /// This storage entry defines when new transaction is going to be accepted. + #[pallet::storage] + #[pallet::getter(fn next_unsigned_at_for_fetched_mpower)] + pub(super) type NextUnsignedAtForFetchedMPower = StorageValue<_, T::BlockNumber, ValueQuery>; + + /// Defines the block when next unsigned transaction (specifically for finishing fetching the mPower) will be accepted. + #[pallet::storage] + #[pallet::getter(fn next_unsigned_at_for_finished_fetching_mpower)] + pub(super) type NextUnsignedAtForFinishedFetchingMPower = StorageValue<_, T::BlockNumber, ValueQuery>; + // The genesis config type. #[pallet::genesis_config] pub struct GenesisConfig { pub rewards_allowance_dhx_for_date_remaining: Vec<(Date, BalanceOf)>, pub rewards_allowance_dhx_for_date_remaining_distributed: Vec<(Date, bool)>, + pub rewards_allowance_dhx_for_miner_for_date_remaining_distributed: Vec<((Date, Vec), bool)>, pub rewards_allowance_dhx_daily: BalanceOf, pub rewards_multiplier_paused: bool, pub rewards_multiplier_reset: bool, @@ -272,14 +503,16 @@ pub mod pallet { pub rewards_multiplier_current_period_days_remaining: (Date, Date, u32, u32), pub rewards_multiplier_operation: u8, pub rewards_aggregated_dhx_for_all_miners_for_date: Vec<(Date, BalanceOf)>, - pub rewards_accumulated_dhx_for_miner_for_date: Vec<((Date, T::AccountId), BalanceOf)>, - pub registered_dhx_miners: Vec, + pub rewards_accumulated_dhx_for_miner_for_date: Vec<((Date, Vec), BalanceOf)>, + pub registered_dhx_miners: Vec>, + pub rewards_eligible_miners_for_date: Vec<(Date, Vec>)>, pub min_bonded_dhx_daily: BalanceOf, pub min_bonded_dhx_daily_default: BalanceOf, pub min_mpower_daily: u128, pub min_mpower_daily_default: u128, - pub cooling_off_period_days: u32, - pub cooling_off_period_days_remaining: Vec<(T::AccountId, (Date, u32, u32))>, + pub challenge_period_days: u64, + pub cooling_down_period_days: u32, + pub cooling_down_period_days_remaining: Vec<(Vec, (Date, u32, u32))>, } // The default value for the genesis config type. @@ -289,6 +522,7 @@ pub mod pallet { Self { rewards_allowance_dhx_for_date_remaining: Default::default(), rewards_allowance_dhx_for_date_remaining_distributed: Default::default(), + rewards_allowance_dhx_for_miner_for_date_remaining_distributed: Default::default(), // 5000 UNIT, where UNIT token has 18 decimal places rewards_allowance_dhx_daily: Default::default(), rewards_multiplier_paused: false, @@ -310,14 +544,16 @@ pub mod pallet { Default::default(), Default::default(), ], + rewards_eligible_miners_for_date: Default::default(), min_bonded_dhx_daily: Default::default(), min_bonded_dhx_daily_default: Default::default(), min_mpower_daily: 5u128, min_mpower_daily_default: 5u128, - cooling_off_period_days: Default::default(), + challenge_period_days: Default::default(), + cooling_down_period_days: Default::default(), // Note: this doesn't seem to work, even if it's just `vec![Default::default()]` it doesn't use - // the defaults in chain_spec.rs, so we set defaults later with `let mut cooling_off_period_days_remaining` - cooling_off_period_days_remaining: vec![ + // the defaults in chain_spec.rs, so we set defaults later with `let mut cooling_down_period_days_remaining` + cooling_down_period_days_remaining: vec![ ( Default::default(), ( @@ -341,6 +577,9 @@ pub mod pallet { for (a, b) in &self.rewards_allowance_dhx_for_date_remaining_distributed { >::insert(a, b); } + for ((a, b), c) in &self.rewards_allowance_dhx_for_miner_for_date_remaining_distributed { + >::insert((a, b), c); + } >::put(&self.rewards_allowance_dhx_daily); >::put(&self.rewards_multiplier_paused); >::put(&self.rewards_multiplier_reset); @@ -354,6 +593,9 @@ pub mod pallet { for (a) in &self.registered_dhx_miners { >::append(a); } + for (a, b) in &self.rewards_eligible_miners_for_date { + >::insert(a, b); + } >::put(&self.rewards_multiplier_operation); for (a, b) in &self.rewards_aggregated_dhx_for_all_miners_for_date { >::insert(a, b); @@ -365,9 +607,10 @@ pub mod pallet { >::put(&self.min_bonded_dhx_daily_default); >::put(&self.min_mpower_daily); >::put(&self.min_mpower_daily_default); - >::put(&self.cooling_off_period_days); - for (a, (b, c, d)) in &self.cooling_off_period_days_remaining { - >::insert(a, (b, c, d)); + >::put(&self.challenge_period_days); + >::put(&self.cooling_down_period_days); + for (a, (b, c, d)) in &self.cooling_down_period_days_remaining { + >::insert(a, (b, c, d)); } } } @@ -379,49 +622,50 @@ pub mod pallet { T::AccountId = "AccountId", BondedData = "BondedData", BalanceOf = "BalanceOf", - T::AccountId = "Date" + Date = "Date", + T::BlockNumber = "BlockNumber", )] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Storage of a sending account as a registered DHX miner - /// \[sender] - SetRegisteredDHXMiner(T::AccountId), + /// Storage of provided accounts as a registered DHX miners + /// \[sender, registered_dhx_miners] + SetRegisteredDHXMiners(Vec>), /// Storage of the minimum DHX that must be bonded by each registered DHX miner each day /// to be eligible for rewards - /// \[amount_dhx, sender\] - SetMinBondedDHXDailyStored(BalanceOf, T::AccountId), + /// \[amount_dhx\] + SetMinBondedDHXDailyStored(BalanceOf), /// Storage of the minimum mPower that must be held by each registered DHX miner each day /// to be eligible for rewards - /// \[amount_mpower, sender\] - SetMinMPowerDailyStored(u128, T::AccountId), + /// \[amount_mpower\] + SetMinMPowerDailyStored(u128), + + /// Storage of the default challenge period in days + /// \[challenge_period_days\] + SetChallengePeriodDaysStored(u64), - /// Storage of the default cooling off period in days - /// \[cooling_off_period_days\] - SetCoolingOffPeriodDaysStored(u32), + /// Storage of the default cooling down period in days + /// \[cooling_down_period_days\] + SetCoolingDownPeriodDaysStored(u32), /// Storage of the bonded DHX of an account on a specific date. /// \[date, amount_dhx_bonded, account_dhx_bonded\] - SetBondedDHXOfAccountForDateStored(Date, BalanceOf, T::AccountId), - - /// Storage of the mPower of an account on a specific date. - /// \[date, amount_mpower, account\] - SetMPowerOfAccountForDateStored(Date, u128, T::AccountId), + SetBondedDHXOfAccountForDateStored(Date, BalanceOf, Vec), /// Storage of the default daily reward allowance in DHX by an origin account. - /// \[amount_dhx, sender\] - SetRewardsAllowanceDHXDailyStored(BalanceOf, T::AccountId), + /// \[amount_dhx\] + SetRewardsAllowanceDHXDailyStored(BalanceOf), /// Change the stored reward allowance in DHX for a specific date by an origin account, and /// where change is 0 for an decrease or any other value like 1 for an increase to the remaining /// rewards allowance. - /// \[date, change_amount_dhx, sender, change\] - ChangedRewardsAllowanceDHXForDateRemainingStored(Date, BalanceOf, T::AccountId, u8), + /// \[date, change_amount_dhx, change\] + ChangedRewardsAllowanceDHXForDateRemainingStored(Date, BalanceOf, u8), /// Transferred a proportion of the daily DHX rewards allowance to a DHX Miner on a given date - /// \[date, miner_reward, remaining_rewards_allowance_today, miner_account_id\] - TransferredRewardsAllowanceDHXToMinerForDate(Date, BalanceOf, BalanceOf, T::AccountId), + /// \[date, miner_reward, remaining_rewards_allowance_today, miner_public_key\] + TransferredRewardsAllowanceDHXToMinerForDate(Date, BalanceOf, BalanceOf, Vec), /// Exhausted distributing all the daily DHX rewards allowance to DHX Miners on a given date. /// Note: There may be some leftover for the day so we record it here @@ -433,16 +677,16 @@ pub mod pallet { ChangedMinBondedDHXDailyUsingNewRewardsMultiplier(Date, BalanceOf, u32, u8, u32), /// Storage of a new reward operation (1u8: addition) by an origin account. - /// \[operation, sender\] - SetRewardsMultiplierOperationStored(u8, T::AccountId), + /// \[operation\] + SetRewardsMultiplierOperationStored(u8), /// Storage of a new reward multiplier default period in days (i.e. 90 for 3 months) by an origin account. - /// \[days, sender\] - SetRewardsMultiplierDefaultPeriodDaysStored(u32, T::AccountId), + /// \[days\] + SetRewardsMultiplierDefaultPeriodDaysStored(u32), /// Storage of a new reward multiplier next period in days (i.e. 90 for 3 months) by an origin account. - /// \[days, sender\] - SetRewardsMultiplierNextPeriodDaysStored(u32, T::AccountId), + /// \[days\] + SetRewardsMultiplierNextPeriodDaysStored(u32), /// Storage of new rewards multiplier paused status /// \[new_status] @@ -451,6 +695,16 @@ pub mod pallet { /// Storage of new rewards multiplier reset status /// \[new_status] ChangeRewardsMultiplierResetStatusStored(bool), + + // Off-chain workers + + /// Event generated when new mPower data is accepted to contribute to the rewards allowance. + /// \[start_date_requested, registered_dhx_miner_account_id, mpower\] + NewMPowerForAccountForDate(Date, Vec, u128), + + /// Event generated when new finished fetching mPower data is stored. + /// \[start_date_requested, all_miner_public_keys_fetched, total_mpower_fetched\] + NewFinishedFetchingMPowerForAccountsForDate(Date, Vec>, u128), } // Errors inform users that something went wrong should be descriptive and have helpful documentation @@ -468,30 +722,185 @@ pub mod pallet { // Pallet implements [`Hooks`] trait to define some logic to execute in some context. #[pallet::hooks] impl Hooks> for Pallet { + /// Offchain Worker entry point. + /// + /// By implementing `fn offchain_worker` you declare a new offchain worker. + /// This function will be called when the node is fully synced and a new best block is + /// succesfuly imported. + /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might + /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs), + /// so the code should be able to handle that. + /// You can use `Local Storage` API to coordinate runs of the worker. + /// + /// https://paritytech.github.io/substrate/master/frame_support/traits/trait.OffchainWorker.html + /// https://paritytech.github.io/substrate/master/frame_support/traits/trait.Hooks.html + /// + /// Implementing this trait on a module allows you to perform long-running tasks that make + /// (by default) validators generate transactions that feed results of those long-running + /// computations back on chain. + /// NOTE: This function runs off-chain, so it can access the block state, but cannot preform + /// any alterations. More specifically alterations are not forbidden, but they are not persisted + /// in any way after the worker has finished. + fn offchain_worker(block_number: T::BlockNumber) { + log::info!("offchain_worker: {:?}", block_number.clone()); + println!("offchain_worker: {:?}", block_number.clone()); + // Note that having logs compiled to WASM may cause the size of the blob to increase + // significantly. You can use `RuntimeDebug` custom derive to hide details of the types + // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable + // all logging and thus, remove any logging from the WASM. + + // Since off-chain workers are just part of the runtime code, they have direct access + // to the storage and other included pallets. + // + // We can easily import `frame_system` and retrieve a block hash of the parent block. + // let parent_hash = >::block_hash(block_number - 1u32.into()); + // log::debug!("offchain_workers current block: {:?} (parent hash: {:?})", block_number, parent_hash); + + let should_process_block = Self::should_process_block(block_number.clone()); + if should_process_block == false { + return; + } + + // TODO - this timestamp section is duplicated in the on_initialize function, + // so move it into its owh private function + + // Anything that needs to be done at the start of the block. + let timestamp: ::Moment = >::get(); + log::info!("offchain_worker - block_number: {:?}", block_number.clone()); + log::info!("offchain_worker - timestamp: {:?}", timestamp.clone()); + let requested_date_as_u64; + let _requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone()); + match _requested_date_as_u64 { + Err(_e) => { + log::error!("offchain_worker - Unable to convert Moment to u64 in millis for timestamp"); + return; + }, + Ok(ref x) => { + requested_date_as_u64 = x; + } + } + log::info!("offchain_worker - requested_date_as_u64: {:?}", requested_date_as_u64.clone()); + println!("offchain_worker - requested_date_as_u64: {:?}", requested_date_as_u64.clone()); + + // do not run when block number is 1, which is when timestamp is 0 because this + // timestamp corresponds to 1970-01-01 + if requested_date_as_u64.clone() == 0u64 { + return; + } + + let start_of_requested_date_millis; + let _start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(requested_date_as_u64.clone()); + match _start_of_requested_date_millis { + Err(_e) => { + log::error!("offchain_worker - Unable to convert u64 in milliseconds to start_of_requested_date_millis"); + return; + }, + Ok(ref x) => { + start_of_requested_date_millis = x; + } + } + log::info!("offchain_worker - start_of_requested_date_millis: {:?}", start_of_requested_date_millis.clone()); + println!("offchain_worker - start_of_requested_date_millis: {:?}", start_of_requested_date_millis.clone()); + + // after fetching the mpower values store by sending an unsigned transactions + let should_send = Self::choose_transaction_type(block_number.clone()); + let mut mpower_data_vec = vec![]; + match should_send { + TransactionType::Raw => { + let _mpower_res = Self::fetch_mpower_process(block_number.clone(), start_of_requested_date_millis.clone()); + match _mpower_res.clone() { + Err(e) => { + log::error!("offchain_worker - offchain_workers error fetching mpower: {}", e); + return; + }, + Ok(x) => { + mpower_data_vec = x; + } + } + }, + TransactionType::None => { + log::error!("offchain_worker - offchain_workers error unknown transaction type"); + return; + }, + }; + + // TODO - remove all the fetched accounts from vector that aren't in the list of + // reg_dhx_miners, since we add the list of registered dhx miners from a signed account to + // and use it incase the API endpoint is compromised by hackers and fake accounts and mpower data are provided + + // TODO - change reg_dhx_miners functionality so it may be stored via an extrinsic function + // and voted on by governance, and so it just includes a list of account addresses that we check against. + // if the list needs to be changed then just call it and add all of them again once approved. + + // TODO - verify these fetched public keys correspond to registered dhx miners, + // and if not we should only store mpower data corresponding to those that are + let mut all_miner_public_keys_fetched = vec![]; + let mut total_mpower_fetched = 0u128; + for (index, mpower_data_item) in mpower_data_vec.iter().enumerate() { + log::info!("offchain_worker - mpower_data_vec {:?}, {:?}", index.clone(), mpower_data_item.account_id_registered_dhx_miner.clone()); + log::info!("offchain_worker - mpower_data_vec {:?}, {:?}", index.clone(), mpower_data_item.mpower_registered_dhx_miner.clone()); + all_miner_public_keys_fetched.push(mpower_data_item.account_id_registered_dhx_miner.clone()); + + let new_total_mpower_fetched = total_mpower_fetched.clone(); + let _total_mpower_fetched = + new_total_mpower_fetched.checked_add(mpower_data_item.mpower_registered_dhx_miner.clone()); + match _total_mpower_fetched { + None => { + log::error!("Unable to add mpower_data_item.mpower_registered_dhx_miner with total_mpower_fetched due to StorageOverflow"); + return; + }, + Some(x) => { + total_mpower_fetched = x; + } + } + } + log::info!("offchain_worker - total_mpower_fetched {:?}, {:?}", total_mpower_fetched.clone(), block_number.clone()); + + let fetched_mpower_data: FetchedMPowerForAccountsForDatePayloadData = FetchedMPowerForAccountsForDatePayload { + start_of_requested_date_millis: start_of_requested_date_millis.clone(), + all_miner_public_keys_fetched: all_miner_public_keys_fetched.clone(), + total_mpower_fetched: total_mpower_fetched.clone(), + }; + + let res_store_mpower = Self::store_mpower_raw_unsigned( + block_number.clone(), + start_of_requested_date_millis.clone(), + mpower_data_vec.clone() + ); + log::info!("offchain_worker - finished store_mpower_raw_unsigned at block: {:?}", block_number.clone()); + if let Err(e) = res_store_mpower { + log::error!("offchain_worker - offchain_workers error storing mpower: {}", e); + } + + let res_store_finished = Self::store_finished_fetching_mpower_raw_unsigned( + block_number.clone(), + fetched_mpower_data.clone() + ); + log::info!("offchain_worker - finished store_finished_fetching_mpower_raw_unsigned at block: {:?}", block_number.clone()); + if let Err(e) = res_store_finished { + log::error!("offchain_worker - offchain_workers error storing finished fetching mpower: {}", e); + } + + return; + } + // `on_initialize` is executed at the beginning of the block before any extrinsic are // dispatched. // // This function must return the weight consumed by `on_initialize` and `on_finalize`. // TODO - update with the weight consumed - fn on_initialize(_n: T::BlockNumber) -> Weight { - let block_one = 1u32; - let block_one_as_block; - if let Some(_block_one) = TryInto::::try_into(block_one).ok() { - block_one_as_block = _block_one; - } else { - log::error!("Unable to convert u32 to BlockNumber"); - return 0; - } - - // skip block #1 since timestamp is 0 in blocks before block #2 - if _n == block_one_as_block.clone() { + fn on_initialize(block_number: T::BlockNumber) -> Weight { + let should_process_block = Self::should_process_block(block_number.clone()); + if should_process_block == false { return 0; } // Anything that needs to be done at the start of the block. let timestamp: ::Moment = >::get(); - log::info!("_n: {:?}", _n.clone()); + log::info!("block_number: {:?}", block_number.clone()); + println!("block_number: {:?}", block_number.clone()); log::info!("timestamp: {:?}", timestamp.clone()); + println!("timestamp: {:?}", timestamp.clone()); let requested_date_as_u64; let _requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone()); match _requested_date_as_u64 { @@ -504,13 +913,14 @@ pub mod pallet { } } log::info!("requested_date_as_u64: {:?}", requested_date_as_u64.clone()); - // println!("requested_date_as_u64: {:?}", requested_date_as_u64.clone()); + println!("requested_date_as_u64: {:?}", requested_date_as_u64.clone()); // do not run when block number is 1, which is when timestamp is 0 because this // timestamp corresponds to 1970-01-01 if requested_date_as_u64.clone() == 0u64 { return 0; } + let start_of_requested_date_millis; let _start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(requested_date_as_u64.clone()); match _start_of_requested_date_millis { @@ -523,7 +933,7 @@ pub mod pallet { } } log::info!("start_of_requested_date_millis: {:?}", start_of_requested_date_millis.clone()); - // println!("start_of_requested_date_millis: {:?}", start_of_requested_date_millis.clone()); + println!("start_of_requested_date_millis: {:?}", start_of_requested_date_millis.clone()); // https://substrate.dev/rustdocs/latest/frame_support/storage/trait.StorageMap.html let contains_key = >::contains_key(&start_of_requested_date_millis); @@ -609,7 +1019,7 @@ pub mod pallet { min_bonded_dhx_daily_default_u128 = x.1; } } - // println!("min_bonded_dhx_daily_default_u128: {:?}", min_bonded_dhx_daily_default_u128.clone()); + println!("min_bonded_dhx_daily_default_u128: {:?}", min_bonded_dhx_daily_default_u128.clone()); let mut min_mpower_daily_default: u128 = 5u128; if let Some(_min_mpower_daily_default) = >::get() { @@ -617,7 +1027,7 @@ pub mod pallet { } else { log::info!("Unable to get min_mpower_daily_default"); } - // println!("min_mpower_daily_default {:?}", min_mpower_daily_default); + println!("min_mpower_daily_default {:?}", min_mpower_daily_default); let mut rm_current_period_days_remaining = ( 0.into(), @@ -640,14 +1050,14 @@ pub mod pallet { log::info!("rm_next_period_days: {:?}", &rm_next_period_days); log::info!("rm_current_period_days_remaining: {:?}", &rm_current_period_days_remaining); - // println!("rm_paused: {:?}", &rm_paused); - // println!("rm_reset: {:?}", &rm_reset); - // println!("rm_default_change: {:?}", &rm_default_change); - // println!("rm_current_change: {:?}", &rm_current_change); - // println!("rm_next_change: {:?}", &rm_next_change); - // println!("rm_default_period_days: {:?}", &rm_default_period_days); - // println!("rm_next_period_days: {:?}", &rm_next_period_days); - // println!("rm_current_period_days_remaining: {:?}", &rm_current_period_days_remaining); + println!("rm_paused: {:?}", &rm_paused); + println!("rm_reset: {:?}", &rm_reset); + println!("rm_default_change: {:?}", &rm_default_change); + println!("rm_current_change: {:?}", &rm_current_change); + println!("rm_next_change: {:?}", &rm_next_change); + println!("rm_default_period_days: {:?}", &rm_default_period_days); + println!("rm_next_period_days: {:?}", &rm_next_period_days); + println!("rm_current_period_days_remaining: {:?}", &rm_current_period_days_remaining); // pause the process of automatically changing to the next period change and next period day // until unpaused again by governance @@ -661,17 +1071,10 @@ pub mod pallet { >::put(min_mpower_daily_default.clone()); } - let block_two = 2u32; - let block_two_as_block; - if let Some(_block_two) = TryInto::::try_into(block_two).ok() { - block_two_as_block = _block_two; - } else { - log::error!("Unable to convert u32 to BlockNumber"); - return 0; - } + let is_block_two = Self::is_block_two(block_number.clone()); // start on block #2 since timestamp is 0 in blocks before that - if _n == block_two_as_block.clone() { + if is_block_two.clone() == true { // initialise values in storage that cannot be set in genesis and apply to local variables // incase its just after genesis when values are not yet set in storage >::put( @@ -696,7 +1099,7 @@ pub mod pallet { if rm_current_period_days_remaining.1 != start_of_requested_date_millis.clone() { // if there are still days remaining in the countdown if rm_current_period_days_remaining.3 > 0u32 { - // println!("[reducing_multiplier_days] block: {:#?}, date_start: {:#?} remain_days: {:#?}", _n, rm_current_period_days_remaining.0, rm_current_period_days_remaining.3); + println!("[reducing_multiplier_days] block: {:#?}, date_start: {:#?} remain_days: {:#?}", block_number, rm_current_period_days_remaining.0, rm_current_period_days_remaining.3); let old_rm_current_period_days_remaining = rm_current_period_days_remaining.3.clone(); // Subtract, handling overflow @@ -725,7 +1128,7 @@ pub mod pallet { log::info!("Reduced RewardsMultiplierCurrentPeriodDaysRemaining {:?} {:?}", start_of_requested_date_millis.clone(), new_rm_current_period_days_remaining.clone()); } else { // if no more days remaining - // println!("[reducing_multiplier_days] no more remaining days"); + println!("[reducing_multiplier_days] no more remaining days"); // run an operation with the the next change and the current min bonded dhx daily to determine the // new min. bonded dhx daily for the next period @@ -743,7 +1146,7 @@ pub mod pallet { min_bonded_dhx_daily_u128 = x.1; } } - // println!("min_bonded_dhx_daily_u128: {:?}", min_bonded_dhx_daily_u128.clone()); + println!("min_bonded_dhx_daily_u128: {:?}", min_bonded_dhx_daily_u128.clone()); let rewards_multipler_operation; if let Some(_rewards_multipler_operation) = >::get() { @@ -755,7 +1158,7 @@ pub mod pallet { let mut new_min_bonded_dhx_daily_u128 = 0u128; // initialize - // println!("rewards_multipler_operation: {:?}", rewards_multipler_operation.clone()); + println!("rewards_multipler_operation: {:?}", rewards_multipler_operation.clone()); // prepare for 'add' operation @@ -780,10 +1183,10 @@ pub mod pallet { rm_next_change_as_fixedu128 = x; } } - // println!("rm_next_change_as_fixedu128: {:?}", rm_next_change_as_fixedu128.clone()); + println!("rm_next_change_as_fixedu128: {:?}", rm_next_change_as_fixedu128.clone()); // round down the fixed point number to the nearest integer of type u128 let rm_next_change_u128: u128 = rm_next_change_as_fixedu128.floor().to_num::(); - // println!("rm_next_change_u128: {:?}", rm_next_change_as_fixedu128.clone()); + println!("rm_next_change_u128: {:?}", rm_next_change_as_fixedu128.clone()); // case of addition if rewards_multipler_operation == 1u8 { @@ -808,7 +1211,7 @@ pub mod pallet { return 0; } - // println!("new_min_bonded_dhx_daily_u128 {:?}", new_min_bonded_dhx_daily_u128); + println!("new_min_bonded_dhx_daily_u128 {:?}", new_min_bonded_dhx_daily_u128); let new_min_bonded_dhx_daily; let _new_min_bonded_dhx_daily = Self::convert_u128_to_balance(new_min_bonded_dhx_daily_u128.clone()); @@ -822,11 +1225,11 @@ pub mod pallet { } } log::info!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); - // println!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); + println!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); >::put(new_min_bonded_dhx_daily.clone()); log::info!("New MinBondedDHXDaily {:?} {:?}", start_of_requested_date_millis.clone(), new_min_bonded_dhx_daily_u128.clone()); - // println!("New MinBondedDHXDaily {:?} {:?}", start_of_requested_date_millis.clone(), new_min_bonded_dhx_daily_u128.clone()); + println!("New MinBondedDHXDaily {:?} {:?}", start_of_requested_date_millis.clone(), new_min_bonded_dhx_daily_u128.clone()); // FIXME - can we automatically change the next period days value to (~90 days depending on days in included months 28, 29, 30, or 31) // depending on the date? and do this from genesis too? @@ -874,31 +1277,53 @@ pub mod pallet { } } log::info!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); - // println!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); + println!("new_min_bonded_dhx_daily: {:?}", new_min_bonded_dhx_daily.clone()); } } } } - // we only check accounts that have registered that they want to participate in DHX Mining let reg_dhx_miners; if let Some(_reg_dhx_miners) = >::get() { reg_dhx_miners = _reg_dhx_miners; } else { log::error!("Unable to retrieve any registered DHX Miners"); + println!("Unable to retrieve any registered DHX Miners"); return 0; } if reg_dhx_miners.len() == 0 { log::error!("Registered DHX Miners has no elements"); + println!("Unable to retrieve any registered DHX Miners"); return 0; }; + + // TODO - uncomment this + + // // only continue with aggregating and accumulating rewards and using mPower data that + // // was fetched offchain when the unsigned tx + // // from offchain_workers that indicates we have finished fetching mpower data for the + // // day has been finished + // let data_finished_fetching_for_date = + // >::get(start_of_requested_date_millis.clone()); + + // match data_finished_fetching_for_date { + // None => { + // log::warn!("Skipping this block as we have not yet finished fetching mpower data for today from offchain workers {:?}", start_of_requested_date_millis.clone()); + // return 0; + // }, + // Some(x) => { + // } + // } + let mut miner_count = 0; - for (index, miner) in reg_dhx_miners.iter().enumerate() { + for (index, miner_public_key) in reg_dhx_miners.iter().enumerate() { miner_count += 1; log::info!("miner_count {:#?}", miner_count); - log::info!("miner {:#?}", miner); - // let locks_until_block_for_account = >::locks(miner.clone()); + println!("miner_count {:#?}", miner_count); + log::info!("miner_public_key {:?}", miner_public_key); + println!("miner_public_key {:?}", miner_public_key); + // let locks_until_block_for_account = >::locks(miner_public_key.clone()); // // NOTE - I fixed the following error by using `.into_inner()` after asking the community here and getting a // // response in Substrate Builders weekly meeting https://matrix.to/#/!HzySYSaIhtyWrwiwEV:matrix.org/$163243681163543vyfkW:matrix.org?via=matrix.parity.io&via=matrix.org&via=corepaper.org // // @@ -923,11 +1348,26 @@ pub mod pallet { // initialise so they have no locks and are ineligible for rewards let mut locks_first_amount_as_u128 = 0u128.clone(); - let locked_vec = >::locks(miner.clone()).into_inner(); + let miner_account_id: T::AccountId; + + // convert type public key Vec to type T::AccountId + let _miner_account_id = Decode::decode(&mut miner_public_key.as_slice().clone()); + match _miner_account_id.clone() { + Err(_e) => { + log::error!("Unable to decode miner_public_key"); + println!("Unable to decode miner_public_key"); + return 0; + }, + Ok(x) => { + miner_account_id = x; + } + } + + let locked_vec = >::locks(miner_account_id.clone()).into_inner(); if locked_vec.len() != 0 { - // println!("locked_vec: {:?}", locked_vec); + println!("locked_vec: {:?}", locked_vec); let locks_first_amount: ::Balance = - >::locks(miner.clone()).into_inner().clone()[0].amount; + >::locks(miner_account_id.clone()).into_inner().clone()[0].amount; let _locks_first_amount_as_u128 = Self::convert_balance_to_u128_from_pallet_balance(locks_first_amount.clone()); match _locks_first_amount_as_u128.clone() { @@ -941,7 +1381,7 @@ pub mod pallet { } } log::info!("locks_first_amount_as_u128: {:?}", locks_first_amount_as_u128.clone()); - // println!("locks_first_amount_as_u128 {:#?}", locks_first_amount_as_u128); + println!("locks_first_amount_as_u128 {:#?}", locks_first_amount_as_u128); // Example output below of vote with 9.9999 tokens on a referendum associated with a proposal // that was seconded @@ -961,9 +1401,13 @@ pub mod pallet { // reasons: Reasons::Misc, // }, + // let miner_public_key = miner.clone().encode(); + log::info!("Public key {:?}", miner_public_key); + println!("Public key {:?}", miner_public_key); + let bonded_dhx_current_u128; let _bonded_dhx_current_u128 = Self::set_bonded_dhx_of_account_for_date( - miner.clone(), + miner_public_key.clone(), locks_first_amount_as_u128.clone() ); match _bonded_dhx_current_u128 { @@ -994,25 +1438,25 @@ pub mod pallet { if locks_first_amount_as_u128 >= min_bonded_dhx_daily_u128 { is_bonding_min_dhx = true; } - log::info!("is_bonding_min_dhx: {:?} {:?}", is_bonding_min_dhx.clone(), miner.clone()); - // println!("is_bonding_min_dhx {:#?}", is_bonding_min_dhx); - // println!("min_bonded_dhx_daily_u128 {:#?}", min_bonded_dhx_daily_u128); - - // TODO - move this into off-chain workers function - let mut min_mpower_daily_u128: u128 = 5u128; + log::info!("is_bonding_min_dhx: {:?} {:?}", is_bonding_min_dhx.clone(), miner_public_key.clone()); + println!("is_bonding_min_dhx {:#?}", is_bonding_min_dhx); + println!("min_bonded_dhx_daily_u128 {:#?}", min_bonded_dhx_daily_u128); + + // the cooling-off period of 7 days and DHX Mining starts when they start bonding at least 10 DHX, + // but they don't necessarily have to have the min. 1 mPower yet, however you will only start earning + // dailing mining rewards when you have at least the min. 1 mPower (i.e. by locking at least 1 MXC or similar task) + // (so they could be DHX Mining and not getting any rewards until they do so) + let mut min_mpower_daily_u128: u128 = 1u128; if let Some(_min_mpower_daily_u128) = >::get() { min_mpower_daily_u128 = _min_mpower_daily_u128; } else { log::error!("Unable to retrieve min. mPower daily as u128"); } - // println!("min_mpower_daily_u128 {:#?}", min_mpower_daily_u128); + println!("min_mpower_daily_u128 {:#?}", min_mpower_daily_u128); - // TODO - integrate this with functionality of off-chain workers function where we - // fetch the mpower from off-chain and store it with `set_mpower_of_account_for_date` - // TODO - fetch the mPower of the miner currently being iterated to check if it's greater than the min. - // mPower that is required + // fetch the mPower of the miner currently being iterated to check if it's greater than the min. mPower that is required let mut mpower_current_u128: u128 = 0u128; - let _mpower_current_u128 = >::get((start_of_requested_date_millis.clone(), miner.clone())); + let _mpower_current_u128 = >::get((start_of_requested_date_millis.clone(), miner_public_key.clone())); match _mpower_current_u128 { None => { log::error!("Unable to get_mpower_of_account_for_date {:?}", start_of_requested_date_millis.clone()); @@ -1022,15 +1466,34 @@ pub mod pallet { mpower_current_u128 = x; } } + // Note: this was the hard-code mPower data that may be used incase we just want to mock + // the storage value of mpower for the current miner being iterated if we retrieved it from offchain workers + // and stored their mpower on-chain using an unsigned transaction + + // let _mpower_data = ( + // Some(0u128), + // start_of_requested_date_millis.clone(), + // 1u64, + // ); + // match _mpower_data.0 { + // None => { + // log::error!("Unable to get_mpower_of_account_for_date {:?}", start_of_requested_date_millis.clone()); + // println!("Unable to get_mpower_of_account_for_date {:?}", start_of_requested_date_millis.clone()); + // }, + // Some(x) => { + // mpower_current_u128 = x; + // } + // } log::info!("mpower_current_u128 {:#?}, {:?}", mpower_current_u128, start_of_requested_date_millis.clone()); - // println!("mpower_current_u128 {:#?}, {:?}", mpower_current_u128, start_of_requested_date_millis.clone()); + println!("mpower_current_u128 {:#?}, {:?}", mpower_current_u128, start_of_requested_date_millis.clone()); let mut has_min_mpower_daily = false; - if mpower_current_u128 >= min_mpower_daily_u128 { + // min. mpower daily must be greater than or equal to 1 otherwise they don't get any rewards + if mpower_current_u128 >= min_mpower_daily_u128 && min_mpower_daily_u128 >= 1u128 { has_min_mpower_daily = true; } - log::info!("has_min_mpower_daily: {:?} {:?}", has_min_mpower_daily.clone(), miner.clone()); - // println!("has_min_mpower_daily {:#?}", has_min_mpower_daily); + log::info!("has_min_mpower_daily: {:?} {:?}", has_min_mpower_daily.clone(), miner_public_key.clone()); + println!("has_min_mpower_daily {:#?}", has_min_mpower_daily); // TODO - after fetching their mPower from the off-chain workers function where we iterate through // the registered DHX miners too, we need to incorporate it @@ -1039,359 +1502,556 @@ pub mod pallet { // See Dhx-pop-mining-automatic.md in https://mxc.atlassian.net/browse/MMD-717 that explains off-chain worker // aspects - let cooling_off_period_days; - if let Some(_cooling_off_period_days) = >::get() { - cooling_off_period_days = _cooling_off_period_days; + let cooling_down_period_days; + if let Some(_cooling_down_period_days) = >::get() { + cooling_down_period_days = _cooling_down_period_days; } else { - log::error!("Unable to retrieve cooling off period days"); + log::error!("Unable to retrieve cooling down period days"); + println!("Unable to retrieve cooling down period days"); return 0; } - let mut cooling_off_period_days_remaining = ( + // fallback incase not already set + let mut cooling_down_period_days_remaining = ( start_of_requested_date_millis.clone(), - 7u32, + 0u32, 0u32, ); - if let Some(_cooling_off_period_days_remaining) = >::get(miner.clone()) { - // we do not change cooling_off_period_days_remaining.0 to the default value in the chain_spec.rs of 0, + if let Some(_cooling_down_period_days_remaining) = >::get(miner_public_key.clone()) { + // we do not change cooling_down_period_days_remaining.0 to the default value in the chain_spec.rs of 0, // instead we want to use today's date `start_of_requested_date_millis.clone()` by default, as we did above. - if _cooling_off_period_days_remaining.0 != 0 { - cooling_off_period_days_remaining.0 = _cooling_off_period_days_remaining.0; + if _cooling_down_period_days_remaining.0 != 0 { + cooling_down_period_days_remaining.0 = _cooling_down_period_days_remaining.0; } - cooling_off_period_days_remaining.1 = _cooling_off_period_days_remaining.1; - cooling_off_period_days_remaining.2 = _cooling_off_period_days_remaining.2; + cooling_down_period_days_remaining.1 = _cooling_down_period_days_remaining.1; + cooling_down_period_days_remaining.2 = _cooling_down_period_days_remaining.2; } else { - log::info!("Unable to retrieve cooling off period days remaining for given miner, using default {:?}", miner.clone()); + log::info!("Unable to retrieve cooling down period days remaining for given miner, using default {:?}", miner_public_key.clone()); + println!("Unable to retrieve cooling down period days remaining for given miner, using default {:?}", miner_public_key.clone()); } - log::info!("cooling_off_period_days_remaining {:?} {:?} {:?}", start_of_requested_date_millis.clone(), cooling_off_period_days_remaining, miner.clone()); - // if cooling_off_period_days_remaining.2 is 0u32, it means we haven't recognised they that have the min. bonded yet (or unbonded), - // they aren't currently bonding, they haven't started cooling off to start bonding, - // or have already finished cooling down after bonding. - // so if we detect they are now bonding above the min. or have above the min. mPower then we should start at max. remaining days - // before starting to decrement on subsequent blocks + + log::info!("cooling_down_period_days_remaining {:?} {:?} {:?}", start_of_requested_date_millis.clone(), cooling_down_period_days_remaining, miner_public_key.clone()); + println!("cooling_down_period_days_remaining {:?} {:?} {:?}", start_of_requested_date_millis.clone(), cooling_down_period_days_remaining, miner_public_key.clone()); + + // if they stop bonding the min dhx, and + // if cooling_down_period_days_remaining.1 is Some(0) + // so since we detected they are no longer bonding above the min. + // then we should start at max. remaining days before starting to decrement on subsequent blocks if - cooling_off_period_days_remaining.2 == 0u32 && - is_bonding_min_dhx == true && - has_min_mpower_daily == true + cooling_down_period_days_remaining.1 == 0u32 && + is_bonding_min_dhx == false + { - >::insert( - miner.clone(), - ( - start_of_requested_date_millis.clone(), - cooling_off_period_days.clone(), - 1u32, // they are bonded again, waiting to start getting rewards - ), - ); - log::info!("Added CoolingOffPeriodDaysRemaining for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner.clone(), cooling_off_period_days.clone()); - // if cooling_off_period_days_remaining.0 is not the start of the current date - // (since if they just started with min. bonded dhx and min. mPower and we just set days remaining to 7, or we already decremented + // case 1: just finished unbonding period + if cooling_down_period_days_remaining.2 == 1u32 { + >::insert( + miner_public_key.clone(), + ( + start_of_requested_date_millis.clone(), + 0u32, + 0u32, + ), + ); + } + + // // case 2: after finished unbonding period, and where .2 set to 2u32 already + // if cooling_down_period_days_remaining.2 == 2u32 { + // >::insert( + // miner_public_key.clone(), + // ( + // start_of_requested_date_millis.clone(), + // cooling_down_period_days_remaining.1, + // 0u32, + // ), + // ); + // } + // // case 1: just started unbonding after being + // if cooling_down_period_days_remaining.2 == 0u32 { + // >::insert( + // miner_public_key.clone(), + // ( + // start_of_requested_date_millis.clone(), + // cooling_down_period_days.clone(), + // 1u32, + // ), + // ); + // } + // // case 2: midway through unbonding + // if cooling_down_period_days_remaining.2 == 1u32 { + // >::insert( + // miner_public_key.clone(), + // ( + // start_of_requested_date_millis.clone(), + // cooling_down_period_days_remaining.1, + // 1u32, + // ), + // ); + // // case 3: just finished unbonding + // } else if cooling_down_period_days_remaining.2 == 0u32 { + // >::insert( + // miner_public_key.clone(), + // ( + // start_of_requested_date_millis.clone(), + // cooling_down_period_days_remaining.1, + // 0u32, + // ), + // ); + // } + // // case 4: finished unbonding period + + + // if cooling_down_period_days_remaining.0 is not the start of the current date + // (since if they just started un-bonding + // and we just set days remaining to 7, or we already decremented // a miner's days remaining for the current date, then we want to wait until the next day until we // decrement another day). - // if cooling_off_period_days_remaining.1 is Some(above 0), then decrement, but not eligible yet for rewards. + // if cooling_down_period_days_remaining.1 is Some(above 0), then decrement, + // but not yet completely unbonded so cannot claim rewards yet } else if - cooling_off_period_days_remaining.0 != start_of_requested_date_millis.clone() && - cooling_off_period_days_remaining.1 > 0u32 && - is_bonding_min_dhx == true && - has_min_mpower_daily == true + cooling_down_period_days_remaining.0 != start_of_requested_date_millis.clone() && + cooling_down_period_days_remaining.1 > 0u32 + // && + // is_bonding_min_dhx == false { - // println!("[reducing_days] block: {:#?}, miner: {:#?}, date_start: {:#?} remain_days: {:#?}", _n, miner_count, start_of_requested_date_millis, cooling_off_period_days_remaining); - let old_cooling_off_period_days_remaining = cooling_off_period_days_remaining.1.clone(); - - // we cannot do this because of error: cannot use the `?` operator in a method that returns `()` - // let new_cooling_off_period_days_remaining = - // old_cooling_off_period_days_remaining.checked_sub(One::one()).ok_or(Error::::StorageOverflow)?; + let old_cooling_down_period_days_remaining = cooling_down_period_days_remaining.1.clone(); // Subtract, handling overflow - let new_cooling_off_period_days_remaining; - let _new_cooling_off_period_days_remaining = - old_cooling_off_period_days_remaining.checked_sub(One::one()); - match _new_cooling_off_period_days_remaining { + let new_cooling_down_period_days_remaining; + let _new_cooling_down_period_days_remaining = + old_cooling_down_period_days_remaining.checked_sub(One::one()); + match _new_cooling_down_period_days_remaining { None => { - log::error!("Unable to subtract one from cooling_off_period_days_remaining due to StorageOverflow"); + log::error!("Unable to subtract one from cooling_down_period_days_remaining due to StorageOverflow"); + println!("Unable to subtract one from cooling_down_period_days_remaining due to StorageOverflow"); + return 0; }, Some(x) => { - new_cooling_off_period_days_remaining = x; + new_cooling_down_period_days_remaining = x; } } // Write the new value to storage - >::insert( - miner.clone(), - ( - start_of_requested_date_millis.clone(), - new_cooling_off_period_days_remaining.clone(), - 1u32, // they are bonded again, waiting to start getting rewards - ), - ); - log::info!("Reduced CoolingOffPeriodDaysRemaining for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner.clone(), new_cooling_off_period_days_remaining.clone()); - // if cooling_off_period_days_remaining.0 is not the start of the current date - // (since if we decremented days remaining from 1 to 0 days left for a miner - // then we want to wait until the next day before we distribute the rewards to them) - // if cooling_off_period_days_remaining.1 is Some(0), - // and if cooling_off_period_days_remaining.2 is 1 - // and then no more cooling off days, but don't decrement, - // and say they are eligible for reward payments - } else if - cooling_off_period_days_remaining.0 != start_of_requested_date_millis.clone() && - cooling_off_period_days_remaining.1 == 0u32 && - cooling_off_period_days_remaining.2 == 1u32 && - is_bonding_min_dhx == true && - has_min_mpower_daily == true - { - // println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} remain_days: {:#?}", _n, miner_count, start_of_requested_date_millis, cooling_off_period_days_remaining); - - // we need to add that they are eligible for rewards on the current date too - >::insert( - miner.clone(), + >::insert( + miner_public_key.clone(), ( start_of_requested_date_millis.clone(), - 0u32, + new_cooling_down_period_days_remaining.clone(), 1u32, ), ); + println!("[reduce] block: {:#?}, miner: {:#?}, date_start: {:#?} new_cooling_down_period_days_remaining: {:#?}", block_number, miner_count, start_of_requested_date_millis, new_cooling_down_period_days_remaining); + log::info!("Unbonded miner. Reducing cooling down period dates remaining {:?} {:?}", miner_public_key.clone(), new_cooling_down_period_days_remaining.clone()); + } + + // to calculate eligible rewards allowance each day then just need to be: + // - bonding the min. dhx + // - not unbonded at any block on the day being processed (since otherwise they have to wait the unbonding period) + if + cooling_down_period_days_remaining.1 == 0u32 && + is_bonding_min_dhx == true + { + log::info!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} remain_days: {:#?}", block_number, miner_count, start_of_requested_date_millis, cooling_down_period_days_remaining); + println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} remain_days: {:#?}", block_number, miner_count, start_of_requested_date_millis, cooling_down_period_days_remaining); + // only accumulate the DHX reward for each registered miner once per day // https://substrate.dev/rustdocs/latest/frame_support/storage/trait.StorageMap.html if >::contains_key( ( start_of_requested_date_millis.clone(), - miner.clone(), + miner_public_key.clone(), ) ) == true { continue; } - // TODO - calculate the daily reward for the miner in DHX based on their mPower - // and add that to the new_rewards_aggregated_dhx_daily_as_u128 (which currently only - // includes the proportion of their reward based on their bonded DHX tokens) of all - // miner's for that day, and also add that to the accumulated rewards for that specific - // miner on that day. - - // calculate the daily reward for the miner in DHX based on their bonded DHX. - // it should be a proportion taking other eligible miner's who are eligible for - // daily rewards into account since we want to split them fairly. - // - // assuming min_bonded_dhx_daily is 10u128, and they have that minimum of 10 DHX bonded (10u128) for - // the locks_first_amount_as_u128 value, then they are eligible for 1 DHX reward - // - // Divide, handling overflow - let mut daily_reward_for_miner_as_u128 = 0u128; - // note: this rounds down to the nearest integer - let _daily_reward_for_miner_as_u128 = locks_first_amount_as_u128.clone().checked_div(min_bonded_dhx_daily_u128.clone()); - match _daily_reward_for_miner_as_u128 { - None => { - log::error!("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); - return 0; - }, - Some(x) => { - daily_reward_for_miner_as_u128 = x; - } - } - log::info!("daily_reward_for_miner_as_u128: {:?}", daily_reward_for_miner_as_u128.clone()); - // println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} daily_reward_for_miner_as_u128: {:#?}", _n, miner_count, start_of_requested_date_millis, daily_reward_for_miner_as_u128); - - // if we have a rewards_aggregated_dhx_daily of 25.133 k DHX, then after the above manipulation - // since we're dealing with a mixture of u128 and BalanceOf so the values are more readable in the UI. - // the reward will be represented as 2.5130 f DHX (where f is femto 10^-18, i.e. 0.000_000_000_000_002_513) - // so we need to multiply it by 1_000_000_000_000_000_000 to be represented in DHX instead of femto DHX - // before storing the value. we need to do the same for the rewards accumulated value before it is stored. - // daily_reward_for_miner_as_u128 = daily_reward_for_miner_as_u128; - if let Some(_daily_reward_for_miner_as_u128) = daily_reward_for_miner_as_u128.clone().checked_mul(1_000_000_000_000_000_000u128) { - daily_reward_for_miner_as_u128 = _daily_reward_for_miner_as_u128; - } else { - log::error!("Unable to multiply daily_reward_for_miner_as_u128"); - } - - let mut daily_reward_for_miner; - let _daily_reward_for_miner = Self::convert_u128_to_balance(daily_reward_for_miner_as_u128.clone()); - match _daily_reward_for_miner { + let _result_calculate_rewards_eligibility = Self::calculate_rewards_eligibility_of_miner_for_day( + miner_public_key.clone(), + start_of_requested_date_millis.clone(), + locks_first_amount_as_u128.clone(), + rewards_allowance_dhx_daily.clone(), + min_bonded_dhx_daily_u128.clone(), + mpower_current_u128.clone(), + block_number.clone(), + miner_count.clone(), + reg_dhx_miners.clone(), + ); + match _result_calculate_rewards_eligibility { Err(_e) => { - log::error!("Unable to convert u128 to balance for daily_reward_for_miner"); return 0; }, Ok(ref x) => { - daily_reward_for_miner = x; } } - log::info!("daily_reward_for_miner: {:?}", daily_reward_for_miner.clone()); - let mut rewards_aggregated_dhx_daily: BalanceOf = 0u32.into(); // initialize - if let Some(_rewards_aggregated_dhx_daily) = >::get(&start_of_requested_date_millis) { - rewards_aggregated_dhx_daily = _rewards_aggregated_dhx_daily; - } else { - log::error!("Unable to retrieve balance for rewards_aggregated_dhx_daily"); - } + log::info!("Finished calculating eligible rewards date_start: {:#?}", start_of_requested_date_millis); + println!("Finished calculating eligible rewards date_start: {:#?}", start_of_requested_date_millis); + } + } - let rewards_aggregated_dhx_daily_as_u128; - let _rewards_aggregated_dhx_daily_as_u128 = Self::convert_balance_to_u128(rewards_aggregated_dhx_daily.clone()); - match _rewards_aggregated_dhx_daily_as_u128.clone() { - Err(_e) => { - log::error!("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); - return 0; - }, - Ok(x) => { - rewards_aggregated_dhx_daily_as_u128 = x; - } - } + log::info!("Finished loop of registered miners for block"); + println!("Finished loop of registered miners for block"); - // Add, handling overflow - let mut new_rewards_aggregated_dhx_daily_as_u128; - let _new_rewards_aggregated_dhx_daily_as_u128 = - rewards_aggregated_dhx_daily_as_u128.clone().checked_add(daily_reward_for_miner_as_u128.clone()); - match _new_rewards_aggregated_dhx_daily_as_u128 { - None => { - log::error!("Unable to add daily_reward_for_miner to rewards_aggregated_dhx_daily due to StorageOverflow"); - return 0; - }, - Some(x) => { - new_rewards_aggregated_dhx_daily_as_u128 = x; - } - } + return 0; + } - log::info!("new_rewards_aggregated_dhx_daily_as_u128: {:?}", new_rewards_aggregated_dhx_daily_as_u128.clone()); - // println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} new_rewards_aggregated_dhx_daily_as_u128: {:#?}", _n, miner_count, start_of_requested_date_millis, new_rewards_aggregated_dhx_daily_as_u128); + // `on_finalize` is executed at the end of block after all extrinsic are dispatched. + fn on_finalize(block_number: T::BlockNumber) { + // Perform necessary data/state clean up here. + } + } - let new_rewards_aggregated_dhx_daily; - let _new_rewards_aggregated_dhx_daily = Self::convert_u128_to_balance(new_rewards_aggregated_dhx_daily_as_u128.clone()); - match _new_rewards_aggregated_dhx_daily { - Err(_e) => { - log::error!("Unable to convert u128 to balance for new_rewards_aggregated_dhx_daily"); - return 0; - }, - Ok(ref x) => { - new_rewards_aggregated_dhx_daily = x; - } - } + // Dispatchable functions allows users to interact with the pallet and invoke state changes. + // These functions materialize as "extrinsics", which are often compared to transactions. + // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_registered_dhx_miners(origin: OriginFor, register_dhx_miners: Vec>) -> DispatchResult { + let _sender = ensure_root(origin)?; - // add to storage item that accumulates total rewards for all registered miners for the day - >::insert( - start_of_requested_date_millis.clone(), - new_rewards_aggregated_dhx_daily.clone(), - ); - log::info!("Added RewardsAggregatedDHXForAllMinersForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner.clone(), new_rewards_aggregated_dhx_daily.clone()); + for miner_public_key in register_dhx_miners.iter().rev() { + // log::info!("{:?}", miner); - // add to storage item that maps the date to the registered miner and the calculated reward - // (prior to possibly reducing it so they get a proportion of the daily rewards that are available) - >::insert( - ( - start_of_requested_date_millis.clone(), - miner.clone(), - ), - daily_reward_for_miner.clone(), - ); - log::info!("Added RewardsAccumulatedDHXForMinerForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner.clone(), daily_reward_for_miner.clone()); + >::append(miner_public_key.clone()); + log::info!("set_registered_dhx_miners: {:?}", &_sender); + } - // println!("date: {:?}, miner_count: {:?}, reg_dhx_miners.len: {:?}", start_of_requested_date_millis.clone(), miner_count.clone(), reg_dhx_miners.len()); - // if last miner being iterated then reset for next day - if reg_dhx_miners.len() == miner_count { - // println!("date: {:?}, rewards_allowance_dhx_daily: {:?}", start_of_requested_date_millis.clone(), rewards_allowance_dhx_daily.clone()); + Self::deposit_event(Event::SetRegisteredDHXMiners( + register_dhx_miners.clone(), + )); - // reset to latest set by governance - >::insert(start_of_requested_date_millis.clone(), rewards_allowance_dhx_daily.clone()); - }; + Ok(()) + } - // if they stop bonding the min dhx or stop having min. mPower, and - // if cooling_off_period_days_remaining.1 is Some(0), - // and if cooling_off_period_days_remaining.2 is 1 (where they had just been bonding and getting rewards) - // so since we detected they are no longer bonding above the min. or have less than min. mPower - // then we should start at max. remaining days before starting to decrement on subsequent blocks - } else if - cooling_off_period_days_remaining.1 == 0u32 && - cooling_off_period_days_remaining.2 == 1u32 && - (is_bonding_min_dhx == false || has_min_mpower_daily == false) - { - // Write the new value to storage - >::insert( - miner.clone(), - ( - start_of_requested_date_millis.clone(), - cooling_off_period_days.clone(), - 2u32, // they have unbonded again, waiting to finish cooling down period - ), - ); + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_min_bonded_dhx_daily(origin: OriginFor, min_bonded_dhx_daily: BalanceOf) -> DispatchResult { + let _sender = ensure_root(origin)?; - log::info!("Unbonding detected for miner. Starting cooling down period {:?} {:?}", miner.clone(), cooling_off_period_days.clone()); + >::put(&min_bonded_dhx_daily.clone()); + log::info!("set_min_bonded_dhx_daily: {:?}", &min_bonded_dhx_daily); - // if cooling_off_period_days_remaining.0 is not the start of the current date - // (since if they just started un-bonding or just had less than min. mPower - // and we just set days remaining to 7, or we already decremented - // a miner's days remaining for the current date, then we want to wait until the next day until we - // decrement another day). - // if cooling_off_period_days_remaining.1 is Some(above 0), then decrement, - // but not yet completely unbonded so cannot withdraw yet - // note: we don't care if they stop bonding below the min. dhx or have less than min. mPower - // during the cooling off period, - // as the user needs to learn that they should always been bonding the min. and have min. mPower to - // maintain rewards, otherwise they have to wait for entire cooling down period and - // then the cooling off period again. - // - } else if - cooling_off_period_days_remaining.0 != start_of_requested_date_millis.clone() && - cooling_off_period_days_remaining.1 > 0u32 && - cooling_off_period_days_remaining.2 == 2u32 - // && is_bonding_min_dhx == false - { - let old_cooling_off_period_days_remaining = cooling_off_period_days_remaining.1.clone(); + Self::deposit_event(Event::SetMinBondedDHXDailyStored( + min_bonded_dhx_daily.clone(), + )); - // Subtract, handling overflow - let new_cooling_off_period_days_remaining; - let _new_cooling_off_period_days_remaining = - old_cooling_off_period_days_remaining.checked_sub(One::one()); - match _new_cooling_off_period_days_remaining { - None => { - log::error!("Unable to subtract one from cooling_off_period_days_remaining due to StorageOverflow"); - return 0; - }, - Some(x) => { - new_cooling_off_period_days_remaining = x; - } + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_min_mpower_daily(origin: OriginFor, min_mpower_daily: u128) -> DispatchResult { + let _sender = ensure_root(origin)?; + + >::put(&min_mpower_daily.clone()); + log::info!("set_min_mpower_daily: {:?}", &min_mpower_daily); + + Self::deposit_event(Event::SetMinMPowerDailyStored( + min_mpower_daily.clone(), + )); + + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_challenge_period_days(origin: OriginFor, challenge_period_days: u64) -> DispatchResult { + let _sender = ensure_root(origin)?; + + >::put(&challenge_period_days.clone()); + log::info!("challenge_period_days: {:?}", &challenge_period_days); + + Self::deposit_event(Event::SetChallengePeriodDaysStored( + challenge_period_days.clone(), + )); + + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_cooling_down_period_days(origin: OriginFor, cooling_down_period_days: u32) -> DispatchResult { + let _sender = ensure_root(origin)?; + + >::put(&cooling_down_period_days.clone()); + log::info!("cooling_down_period_days: {:?}", &cooling_down_period_days); + + Self::deposit_event(Event::SetCoolingDownPeriodDaysStored( + cooling_down_period_days.clone(), + )); + + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_rewards_allowance_dhx_daily(origin: OriginFor, rewards_allowance: BalanceOf) -> DispatchResult { + let _who = ensure_root(origin)?; + // Update storage + >::put(&rewards_allowance.clone()); + log::info!("set_rewards_allowance_dhx_daily - rewards_allowance: {:?}", &rewards_allowance); + + // Emit an event. + Self::deposit_event(Event::SetRewardsAllowanceDHXDailyStored( + rewards_allowance.clone(), + )); + + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + // customised by governance at any time. this function allows us to change it each year + // https://docs.google.com/spreadsheets/d/1W2AzOH9Cs9oCR8UYfYCbpmd9X7hp-USbYXL7AuwMY_Q/edit#gid=970997021 + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_rewards_allowance_dhx_for_date_remaining(origin: OriginFor, rewards_allowance: BalanceOf, timestamp: u64) -> DispatchResult { + let _who = ensure_root(origin)?; + + // Note: we do not need the following as we're not using the current timestamp, rather the function parameter. + // let current_date = >::get(); + // let requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone())?; + // log::info!("requested_date_as_u64: {:?}", requested_date_as_u64.clone()); + + // Note: to get current timestamp `>::get()` + // convert the requested date/time to the start of that day date/time to signify that date for lookup + // i.e. 21 Apr @ 1420 -> 21 Apr @ 0000 + let start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(timestamp.clone())?; + + // Update storage. Override the default that may have been set in on_initialize + >::insert(start_of_requested_date_millis.clone(), &rewards_allowance); + log::info!("set_rewards_allowance_dhx_for_date_remaining - rewards_allowance: {:?}", &rewards_allowance); + + // Emit an event. + Self::deposit_event(Event::ChangedRewardsAllowanceDHXForDateRemainingStored( + start_of_requested_date_millis.clone(), + rewards_allowance.clone(), + 1u8, // increment + )); + + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + // only modifiable by governance as root rather than just any user + // parameter `change: u8` value may be 0 or 1 (or any other value) to represent that we want to make a + // corresponding decrease or increase to the remaining dhx rewards allowance for a given date. + pub fn change_rewards_allowance_dhx_for_date_remaining(origin: OriginFor, daily_rewards: BalanceOf, timestamp: u64, change: u8) -> DispatchResult { + let _who = ensure_root(origin)?; + + let start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(timestamp.clone())?; + + // https://substrate.dev/rustdocs/latest/frame_support/storage/trait.StorageMap.html + ensure!(>::contains_key(&start_of_requested_date_millis), DispatchError::Other("Date key must exist to reduce allowance.")); + + // Validate inputs so the daily_rewards is less or equal to the existing_allowance + let existing_allowance_as_u128; + if let Some(_existing_allowance) = >::get(&start_of_requested_date_millis) { + existing_allowance_as_u128 = Self::convert_balance_to_u128(_existing_allowance.clone())?; + log::info!("change_rewards_allowance_dhx_for_date_remaining - existing_allowance_as_u128: {:?}", existing_allowance_as_u128.clone()); + } else { + return Err(DispatchError::Other("Unable to retrieve balance from value provided")); + } + + let daily_rewards_as_u128; + daily_rewards_as_u128 = Self::convert_balance_to_u128(daily_rewards.clone())?; + log::info!("change_rewards_allowance_dhx_for_date_remaining - daily_rewards_as_u128: {:?}", daily_rewards_as_u128.clone()); + + ensure!(daily_rewards_as_u128 > 0u128, DispatchError::Other("Daily rewards must be greater than zero")); + ensure!(existing_allowance_as_u128 >= daily_rewards_as_u128, DispatchError::Other("Daily rewards cannot exceed current remaining allowance")); + + let new_remaining_allowance_as_balance; + if change == 0 { + // Decrementing the value will error in the event of underflow. + let new_remaining_allowance_as_u128 = existing_allowance_as_u128.checked_sub(daily_rewards_as_u128).ok_or(Error::::StorageUnderflow)?; + new_remaining_allowance_as_balance = Self::convert_u128_to_balance(new_remaining_allowance_as_u128.clone())?; + log::info!("change_rewards_allowance_dhx_for_date_remaining - Decreasing rewards_allowance_dhx_for_date_remaining at Date: {:?}", &start_of_requested_date_millis); + } else { + // Incrementing the value will error in the event of overflow. + let new_remaining_allowance_as_u128 = existing_allowance_as_u128.checked_add(daily_rewards_as_u128).ok_or(Error::::StorageOverflow)?; + new_remaining_allowance_as_balance = Self::convert_u128_to_balance(new_remaining_allowance_as_u128.clone())?; + log::info!("change_rewards_allowance_dhx_for_date_remaining - Increasing rewards_allowance_dhx_for_date_remaining at Date: {:?}", &start_of_requested_date_millis); + } + + // Update storage + >::mutate( + &start_of_requested_date_millis, + |allowance| { + if let Some(_allowance) = allowance { + *_allowance = new_remaining_allowance_as_balance.clone(); } + }, + ); - // Write the new value to storage - >::insert( - miner.clone(), - ( - start_of_requested_date_millis.clone(), - new_cooling_off_period_days_remaining.clone(), - 2u32, // they have unbonded or have less than min. mPower again, waiting to finish cooling down period - ), - ); + // Emit an event. + Self::deposit_event(Event::ChangedRewardsAllowanceDHXForDateRemainingStored( + start_of_requested_date_millis.clone(), + new_remaining_allowance_as_balance.clone(), + change.clone(), + )); - // println!("[reduce] block: {:#?}, miner: {:#?}, date_start: {:#?} new_cooling_off_period_days_remaining: {:#?}", _n, miner_count, start_of_requested_date_millis, new_cooling_off_period_days_remaining); - log::info!("Unbonded miner. Reducing cooling down period dates remaining {:?} {:?}", miner.clone(), new_cooling_off_period_days_remaining.clone()); + // Return a successful DispatchResultWithPostInfo + Ok(()) + } - // if cooling_off_period_days_remaining.0 is not the start of the current date - // (since if we decremented days remaining to from 1 to 0 days left for a miner - // then we want to wait until the next day before we set cooling_off_period_days_remaining.2 to 0u32 - // to allow them to be completely unbonded and withdraw). - // if cooling_off_period_days_remaining.1 is Some(0), do not subtract anymore, they are - // completely unbonded so can withdraw - } else if - cooling_off_period_days_remaining.0 != start_of_requested_date_millis.clone() && - cooling_off_period_days_remaining.1 == 0u32 && - cooling_off_period_days_remaining.2 == 2u32 - // && is_bonding_min_dhx == false - { - // Write the new value to storage - >::insert( - miner.clone(), - ( - start_of_requested_date_millis.clone(), - 0u32, - 0u32, // they are completely unbonded again - ), - ); + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_rewards_multiplier_operation(origin: OriginFor, operation: u8) -> DispatchResult { + let _who = ensure_root(origin)?; + >::put(&operation.clone()); + log::info!("set_rewards_multiplier_operation - operation: {:?}", &operation); + + // Emit an event. + Self::deposit_event(Event::SetRewardsMultiplierOperationStored( + operation.clone(), + )); + + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_rewards_multiplier_default_period_days(origin: OriginFor, days: u32) -> DispatchResult { + let _who = ensure_root(origin)?; + >::put(&days.clone()); + log::info!("set_rewards_multiplier_default_period_days - days: {:?}", &days); + + // Emit an event. + Self::deposit_event(Event::SetRewardsMultiplierDefaultPeriodDaysStored( + days.clone(), + )); + + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + // only modifiable by governance as root rather than just any user + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_rewards_multiplier_next_period_days(origin: OriginFor, days: u32) -> DispatchResult { + let _who = ensure_root(origin)?; + >::put(&days.clone()); + log::info!("set_rewards_multiplier_next_period_days - days: {:?}", &days); + + // Emit an event. + Self::deposit_event(Event::SetRewardsMultiplierNextPeriodDaysStored( + days.clone(), + )); + + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + // extrinsic that governance may choose to call to set the mpower of an account for a date + // if it needs to be corrected in future before they claim + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn change_mpower_of_account_for_date(origin: OriginFor, account_id: Vec, start_of_requested_date_millis: Date, mpower: u128) -> DispatchResult { + let _who = ensure_root(origin)?; + + let mpower_current_u128 = mpower.clone(); + + log::info!("change_mpower_of_account_for_date {:?} {:?} {:?}", + account_id.clone(), + start_of_requested_date_millis.clone(), + mpower_current_u128.clone(), + ); - log::info!("Unbonded miner. Cooling down period finished so allow them to withdraw {:?}", miner.clone()); + let _set_mpower = Self::set_mpower_of_account_for_date( + account_id.clone(), + start_of_requested_date_millis.clone(), + mpower_current_u128.clone(), + ); + match _set_mpower.clone() { + Err(_e) => { + log::warn!("Unable to change mpower using set_mpower_of_account_for_date"); + return Err(DispatchError::Other("Unable to change mpower using set_mpower_of_account_for_date")); + }, + Ok(x) => { + log::info!("changed mpower using set_mpower_of_account_for_date to: {:?} {:?}", account_id.clone(), x); } } - log::info!("Finished initial loop of registered miners"); - // println!("Finished initial loop of registered miners"); + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + // TODO - add change_finished_fetching_mpower_for_accounts_for_date + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn claim_rewards_of_account_for_date( + origin: OriginFor, + miner_public_key: Vec, // we need to ensure this is the same account as the origin + start_of_requested_date_millis: Date + ) -> DispatchResult { + let _sender = ensure_signed(origin)?; + + // FIXME - these need to be the same, or ideally we need to find out how to get the public key associated with the origin + // and not need to pass the miner_public_key as 2nd parameter at all. + // + // https://github.com/paritytech/subport/issues/290 + // assert_eq!(_sender.clone(), miner_public_key.clone()); + + log::info!("claim_rewards_of_account_for_date - miner_public_key: {:?} {:?}", miner_public_key.clone(), start_of_requested_date_millis.clone()); + println!("claim_rewards_of_account_for_date - miner_public_key: {:?} {:?}", miner_public_key.clone(), start_of_requested_date_millis.clone()); + + let block_number = >::block_number(); + + // check that the current date start_of_current_date_millis is at least 7 days after the provided start_of_requested_date_millis + // so there is sufficient time for the community to audit the reward eligibility. + // we could use `CoolingDownPeriodDays` for this, but probably better to call it a `ChallengePeriodDays` + // since it serves a different purpose (`CoolingDownPeriodDays` gives rewards for first 7 days after they + // start bonding in bulk on the 8th day, and then the next day after each day after that, whereas + // `ChallengePeriodDays` you can only claim each daily rewards manually 7 days after it, even for the first + // 7 days after they start bonding) + + let current_timestamp = >::get(); + let current_timestamp_as_u64 = Self::convert_moment_to_u64_in_milliseconds(current_timestamp.clone())?; + log::info!("current_timestamp_as_u64: {:?}", current_timestamp_as_u64.clone()); + println!("current_timestamp_as_u64: {:?}", current_timestamp_as_u64.clone()); + // convert the current timestamp to the start of that day date/time + // i.e. 21 Apr @ 1420 -> 21 Apr @ 0000 + let start_of_current_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(current_timestamp_as_u64.clone())?; + + // // Prevent them from claiming during their unbonding period + // let mut is_still_unbonding = false; + // if let Some(_cooling_down_period_days_remaining) = >::get(miner_public_key.clone()) { + // if _cooling_down_period_days_remaining.2 == 1 { + // is_still_unbonding = true; + // } + // } else { + // log::info!("Unable to retrieve cooling down period days remaining for given miner {:?}", miner_public_key.clone()); + // println!("Unable to retrieve cooling down period days remaining for given miner {:?}", miner_public_key.clone()); + // return Err(DispatchError::Other("Unable to retrieve cooling down period days remaining for given miner")); + // } + // if is_still_unbonding == true { + // log::info!("Unbonding still in progress for given miner {:?}", miner_public_key.clone()); + // println!("Unbonding still in progress for given miner {:?}", miner_public_key.clone()); + // return Err(DispatchError::Other("Unbonding still in progress for given miner")); + // } + + // where there are 86400000 milliseconds in a day + // there are 7 * 86400000 = 604800000 milliseconds in 7 days + // so we want to make sure `start_of_current_date_millis` - `start_of_requested_date_millis` > 604800000 + + let mut is_more_than_challenge_period = false; + let _is_more_than_challenge_period = Self::is_more_than_challenge_period(start_of_requested_date_millis.clone()); + match _is_more_than_challenge_period.clone() { + Err(_e) => { + log::error!("Unable to claim until after waiting the challenge period {:?}", _e); + println!("Unable to claim until after waiting the challenge period {:?}", _e); + + return Err(_e); + }, + Ok(x) => { + log::info!("Proceeding since waited at least the challenge period"); + println!("Proceeding since waited at least the challenge period"); + is_more_than_challenge_period = true; + } + } - // TODO - consider the miner's mPower that we have fetched. it should have been added earlier above - // to the aggregated (all miners for that day) and accumulated (specific miner for a day) rewards + // // TODO - consider the miner's mPower that we have fetched. it should have been added earlier above + // // to the aggregated (all miners for that day) and accumulated (specific miner for a day) rewards // fetch accumulated total rewards for all registered miners for the day // TODO - we've done this twice, create a function to fetch it @@ -1399,16 +2059,18 @@ pub mod pallet { if let Some(_rewards_aggregated_dhx_daily) = >::get(&start_of_requested_date_millis) { rewards_aggregated_dhx_daily = _rewards_aggregated_dhx_daily; } else { - log::error!("Unable to retrieve balance for rewards_aggregated_dhx_daily. Cooling off period may not be finished yet"); + log::error!("Unable to retrieve balance for rewards_aggregated_dhx_daily. cooling down period or challenge period may not be finished yet"); + println!("Unable to retrieve balance for rewards_aggregated_dhx_daily. cooling down period or challenge period may not be finished yet"); // Note: it would be an issue if we got past the first loop of looping through the registered miners // and still hadn't added to the aggregated rewards for the day - return 0; + return Err(DispatchError::Other("Unable to retrieve balance for rewards_aggregated_dhx_daily. cooling down period or challenge period may not be finished yet")); } - // println!("[multiplier] block: {:#?}, miner: {:#?}, date_start: {:#?} rewards_aggregated_dhx_daily: {:#?}", _n, miner_count, start_of_requested_date_millis, rewards_aggregated_dhx_daily); + println!("[multiplier] block: {:#?}, date_start: {:#?} rewards_aggregated_dhx_daily: {:#?}", block_number, start_of_requested_date_millis, rewards_aggregated_dhx_daily); if rewards_aggregated_dhx_daily == 0u32.into() { log::error!("rewards_aggregated_dhx_daily must be greater than 0 to distribute rewards"); - return 0; + println!("rewards_aggregated_dhx_daily must be greater than 0 to distribute rewards"); + return Err(DispatchError::Other("rewards_aggregated_dhx_daily must be greater than 0 to distribute rewards")); } let rewards_aggregated_dhx_daily_as_u128; @@ -1416,13 +2078,15 @@ pub mod pallet { match _rewards_aggregated_dhx_daily_as_u128.clone() { Err(_e) => { log::error!("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); - return 0; + println!("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); + return Err(DispatchError::Other("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128")); }, Ok(x) => { rewards_aggregated_dhx_daily_as_u128 = x; } } log::info!("rewards_aggregated_dhx_daily_as_u128: {:?}", rewards_aggregated_dhx_daily_as_u128.clone()); + println!("rewards_aggregated_dhx_daily_as_u128: {:?}", rewards_aggregated_dhx_daily_as_u128.clone()); // TODO - we've done this twice, create a function to fetch it let rewards_allowance_dhx_daily; @@ -1430,7 +2094,8 @@ pub mod pallet { rewards_allowance_dhx_daily = _rewards_allowance_dhx_daily; } else { log::error!("Unable to get rewards_allowance_dhx_daily"); - return 0; + println!("Unable to get rewards_allowance_dhx_daily"); + return Err(DispatchError::Other("Unable to get rewards_allowance_dhx_daily")); } let rewards_allowance_dhx_daily_u128; @@ -1438,16 +2103,21 @@ pub mod pallet { match _rewards_allowance_dhx_daily_u128.clone() { Err(_e) => { log::error!("Unable to convert balance to u128 for rewards_allowance_dhx_daily_u128"); - return 0; + println!("Unable to convert balance to u128 for rewards_allowance_dhx_daily_u128"); + return Err(DispatchError::Other("Unable to convert balance to u128 for rewards_allowance_dhx_daily_u128")); }, Ok(x) => { rewards_allowance_dhx_daily_u128 = x; } } + log::info!("rewards_allowance_dhx_daily_u128: {:?}", rewards_allowance_dhx_daily_u128.clone()); + println!("rewards_allowance_dhx_daily_u128: {:?}", rewards_allowance_dhx_daily_u128.clone()); + if rewards_allowance_dhx_daily_u128 == 0u128 { log::error!("rewards_allowance_dhx_daily must be greater than 0 to distribute rewards"); - return 0; + println!("rewards_allowance_dhx_daily must be greater than 0 to distribute rewards"); + return Err(DispatchError::Other("rewards_allowance_dhx_daily must be greater than 0 to distribute rewards")); } // previously when we looped through all the registered dhx miners we calculated the @@ -1471,6 +2141,8 @@ pub mod pallet { let mut distribution_multiplier_for_day_fixed128 = FixedU128::from_num(1); // initialize if rewards_aggregated_dhx_daily_as_u128.clone() > rewards_allowance_dhx_daily_u128.clone() { + log::info!("rewards_aggregated_dhx_daily > rewards_allowance_dhx_daily"); + println!("rewards_aggregated_dhx_daily > rewards_allowance_dhx_daily"); // Divide, handling overflow // Note: If the rewards_allowance_dhx_daily_u128 is 5000 DHX, its 5000000000000000000000, @@ -1485,8 +2157,11 @@ pub mod pallet { manageable_rewards_allowance_dhx_daily_u128 = _manageable_rewards_allowance_dhx_daily_u128; } else { log::error!("Unable to divide rewards_allowance_dhx_daily_u128 to make it smaller"); - return 0; + println!("Unable to divide rewards_allowance_dhx_daily_u128 to make it smaller"); + return Err(DispatchError::Other("Unable to divide rewards_allowance_dhx_daily_u128 to make it smaller")); } + log::info!("manageable_rewards_allowance_dhx_daily_u128: {:?}", manageable_rewards_allowance_dhx_daily_u128.clone()); + println!("manageable_rewards_allowance_dhx_daily_u128: {:?}", manageable_rewards_allowance_dhx_daily_u128.clone()); let mut rewards_allowance_dhx_daily_u64 = 0u64; if let Some(_rewards_allowance_dhx_daily_u64) = @@ -1494,16 +2169,22 @@ pub mod pallet { rewards_allowance_dhx_daily_u64 = _rewards_allowance_dhx_daily_u64; } else { log::error!("Unable to convert u128 to u64 for rewards_allowance_dhx_daily_u128"); - return 0; + println!("Unable to convert u128 to u64 for rewards_allowance_dhx_daily_u128"); + return Err(DispatchError::Other("Unable to convert u128 to u64 for rewards_allowance_dhx_daily_u128")); } + log::info!("rewards_allowance_dhx_daily_u64: {:?}", rewards_allowance_dhx_daily_u64.clone()); + println!("rewards_allowance_dhx_daily_u64: {:?}", rewards_allowance_dhx_daily_u64.clone()); let mut manageable_rewards_aggregated_dhx_daily_as_u128 = 0u128; if let Some(_manageable_rewards_aggregated_dhx_daily_as_u128) = rewards_aggregated_dhx_daily_as_u128.clone().checked_div(1000000000000000000u128) { manageable_rewards_aggregated_dhx_daily_as_u128 = _manageable_rewards_aggregated_dhx_daily_as_u128; } else { log::error!("Unable to divide manageable_rewards_aggregated_dhx_daily_as_u128 to make it smaller"); - return 0; + println!("Unable to divide manageable_rewards_aggregated_dhx_daily_as_u128 to make it smaller"); + return Err(DispatchError::Other("Unable to divide manageable_rewards_aggregated_dhx_daily_as_u128 to make it smaller")); } + log::info!("manageable_rewards_aggregated_dhx_daily_as_u128: {:?}", manageable_rewards_aggregated_dhx_daily_as_u128.clone()); + println!("manageable_rewards_aggregated_dhx_daily_as_u128: {:?}", manageable_rewards_aggregated_dhx_daily_as_u128.clone()); let mut rewards_aggregated_dhx_daily_as_u64 = 0u64; if let Some(_rewards_aggregated_dhx_daily_as_u64) = @@ -1511,8 +2192,11 @@ pub mod pallet { rewards_aggregated_dhx_daily_as_u64 = _rewards_aggregated_dhx_daily_as_u64; } else { log::error!("Unable to convert u128 to u64 for rewards_aggregated_dhx_daily_as_u128"); - return 0; + println!("Unable to convert u128 to u64 for rewards_aggregated_dhx_daily_as_u128"); + return Err(DispatchError::Other("Unable to convert u128 to u64 for rewards_aggregated_dhx_daily_as_u128")); } + log::info!("rewards_aggregated_dhx_daily_as_u64: {:?}", rewards_aggregated_dhx_daily_as_u64.clone()); + println!("rewards_aggregated_dhx_daily_as_u64: {:?}", rewards_aggregated_dhx_daily_as_u64.clone()); // See https://github.com/ltfschoen/substrate-node-template/pull/6/commits/175ef4805d07093042431c5814dda52da1ebde18 let _fraction_distribution_multiplier_for_day_fixed128 = @@ -1522,240 +2206,301 @@ pub mod pallet { match _distribution_multiplier_for_day_fixed128 { None => { log::error!("Unable to divide rewards_allowance_dhx_daily_u128 due to StorageOverflow by rewards_aggregated_dhx_daily_as_u128"); - return 0; + println!("Unable to divide rewards_allowance_dhx_daily_u128 due to StorageOverflow by rewards_aggregated_dhx_daily_as_u128"); + return Err(DispatchError::Other("Unable to divide rewards_allowance_dhx_daily_u128 due to StorageOverflow by rewards_aggregated_dhx_daily_as_u128")); }, Some(x) => { distribution_multiplier_for_day_fixed128 = x; } } + } else { + log::info!("rewards_aggregated_dhx_daily <= rewards_allowance_dhx_daily"); + println!("rewards_aggregated_dhx_daily <= rewards_allowance_dhx_daily"); } log::info!("distribution_multiplier_for_day_fixed128 {:#?}", distribution_multiplier_for_day_fixed128); - // println!("[multiplier] block: {:#?}, miner: {:#?}, date_start: {:#?} distribution_multiplier_for_day_fixed128: {:#?}", _n, miner_count, start_of_requested_date_millis, distribution_multiplier_for_day_fixed128); + println!("distribution_multiplier_for_day_fixed128 {:#?}", distribution_multiplier_for_day_fixed128); + + log::info!("[multiplier] block: {:#?}, date_start: {:#?} distribution_multiplier_for_day_fixed128: {:#?}", block_number, start_of_requested_date_millis, distribution_multiplier_for_day_fixed128); + println!("[multiplier] block: {:#?}, date_start: {:#?} distribution_multiplier_for_day_fixed128: {:#?}", block_number, start_of_requested_date_millis, distribution_multiplier_for_day_fixed128); // Initialise outside the loop as we need this value after the loop after we finish iterating through all the miners let mut rewards_allowance_dhx_remaining_today_as_u128 = 0u128; - miner_count = 0; - for (index, miner) in reg_dhx_miners.iter().enumerate() { - miner_count += 1; - log::info!("rewards loop - miner_count {:#?}", miner_count); - log::info!("rewards loop - miner {:#?}", miner); - - // only run the following once per day per miner until rewards_allowance_dhx_for_date_remaining is exhausted - // but since we're giving each registered miner a proportion of the daily reward allowance - // (if their aggregated rewards is above daily allowance) each proportion is rounded down, - // it shouldn't become exhausted anyway - let is_already_distributed = >::get(start_of_requested_date_millis.clone()); - if is_already_distributed == Some(true) { - log::error!("Unable to distribute further rewards allowance today"); - return 0; - } + // only run the following once per day per miner until rewards_allowance_dhx_for_date_remaining is exhausted + // but since we're giving each registered miner a proportion of the daily reward allowance + // (if their aggregated rewards is above daily allowance) each proportion is rounded down, + // it shouldn't become exhausted anyway + // let is_already_distributed = >::get(start_of_requested_date_millis.clone()); + // if is_already_distributed == Some(true) { + // log::error!("Unable to distribute further rewards allowance today"); + // return 0; + // } + + let is_already_distributed_to_miner = >::get( + ( + start_of_requested_date_millis.clone(), + miner_public_key.clone(), + ) + ); + log::info!("is_already_distributed_to_miner {:#?}", is_already_distributed_to_miner); + println!("is_already_distributed_to_miner {:#?}", is_already_distributed_to_miner); - let daily_reward_for_miner_as_u128; - let daily_reward_for_miner_to_try = >::get( - ( - start_of_requested_date_millis.clone(), - miner.clone(), - ), - ); - if let Some(_daily_reward_for_miner_to_try) = daily_reward_for_miner_to_try.clone() { - let _daily_reward_for_miner_as_u128 = Self::convert_balance_to_u128(_daily_reward_for_miner_to_try.clone()); - match _daily_reward_for_miner_as_u128.clone() { - Err(_e) => { - log::error!("Unable to convert balance to u128 for daily_reward_for_miner_as_u128"); - return 0; - }, - Ok(x) => { - daily_reward_for_miner_as_u128 = x; - } - } - } else { - // If any of the miner's don't have a reward, we won't waste storing that, - // so we want to move to the next miner in the loop - log::error!("Unable to retrieve reward balance for daily_reward_for_miner {:?}", miner.clone()); - continue; - } - log::info!("daily_reward_for_miner_as_u128: {:?}", daily_reward_for_miner_as_u128.clone()); - - let mut manageable_daily_reward_for_miner_as_u128 = 0u128; - if let Some(_manageable_daily_reward_for_miner_as_u128) = - daily_reward_for_miner_as_u128.clone().checked_div(1000000000000000000u128) { - manageable_daily_reward_for_miner_as_u128 = _manageable_daily_reward_for_miner_as_u128; - } else { - log::error!("Unable to divide daily_reward_for_miner_as_u128 to make it smaller"); - return 0; - } + if is_already_distributed_to_miner == Some(true) { + log::error!("Unable to distribute further rewards allowance to the registered dhx miner for this day"); + println!("Unable to distribute further rewards allowance to the registered dhx miner for this day"); + return Err(DispatchError::Other("Unable to distribute further rewards allowance to the registered dhx miner for this day")); + } - // Multiply, handling overflow - // TODO - probably have to initialise below proportion_of_daily_reward_for_miner_fixed128 to 0u128, - // and convert distribution_multiplier_for_day_fixed128 to u64, - // and convert daily_reward_for_miner_as_u128 to u64 too, like i did earlier. - // but it works so this doesn't seem necessary. - let proportion_of_daily_reward_for_miner_fixed128; - let _proportion_of_daily_reward_for_miner_fixed128 = - U64F64::from_num(distribution_multiplier_for_day_fixed128.clone()).checked_mul(U64F64::from_num(manageable_daily_reward_for_miner_as_u128.clone())); - match _proportion_of_daily_reward_for_miner_fixed128 { - None => { - log::error!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 with daily_reward_for_miner_as_u128 due to StorageOverflow"); - return 0; + let daily_reward_for_miner_as_u128; + let daily_reward_for_miner_to_try = >::get( + ( + start_of_requested_date_millis.clone(), + miner_public_key.clone(), + ), + ); + if let Some(_daily_reward_for_miner_to_try) = daily_reward_for_miner_to_try.clone() { + let _daily_reward_for_miner_as_u128 = Self::convert_balance_to_u128(_daily_reward_for_miner_to_try.clone()); + match _daily_reward_for_miner_as_u128.clone() { + Err(_e) => { + log::error!("Unable to convert balance to u128 for daily_reward_for_miner_as_u128"); + println!("Unable to distribute further rewards allowance to the registered dhx miner for this day"); + return Err(DispatchError::Other("Unable to convert balance to u128 for daily_reward_for_miner_as_u128")); }, - Some(x) => { - proportion_of_daily_reward_for_miner_fixed128 = x; + Ok(x) => { + daily_reward_for_miner_as_u128 = x; } } - log::info!("proportion_of_daily_reward_for_miner_fixed128: {:?}", proportion_of_daily_reward_for_miner_fixed128.clone()); - - // round down to nearest integer. we need to round down, because if we round up then if there are - // 3x registered miners with 5000 DHX rewards allowance per day then they would each get 1667 rewards, - // but there would only be 1666 remaining after the first two, so the last one would miss out. - // so if we round down they each get 1666 DHX and there is 2 DHX from the daily allocation that doesn't get distributed at all. - let proportion_of_daily_reward_for_miner_u128: u128 = proportion_of_daily_reward_for_miner_fixed128.floor().to_num::(); - - // we lose some accuracy doing this conversion, but at least we split the bulk of the rewards proportionally and fairly - let mut restored_proportion_of_daily_reward_for_miner_u128 = 0u128; - if let Some(_restored_proportion_of_daily_reward_for_miner_u128) = - proportion_of_daily_reward_for_miner_u128.clone().checked_mul(1000000000000000000u128) { - restored_proportion_of_daily_reward_for_miner_u128 = _restored_proportion_of_daily_reward_for_miner_u128; - } else { - log::error!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 to restore it larger again"); - return 0; + } else { + // If any of the miner's don't have a reward, we won't waste storing that, + // so we want to move to the next miner in the loop + log::error!("Unable to retrieve reward balance for daily_reward_for_miner {:?}", miner_public_key.clone()); + println!("Unable to retrieve reward balance for daily_reward_for_miner {:?}", miner_public_key.clone()); + return Err(DispatchError::Other("Unable to retrieve reward balance for daily_reward_for_miner")); + } + log::info!("daily_reward_for_miner_as_u128: {:?}", daily_reward_for_miner_as_u128.clone()); + println!("daily_reward_for_miner_as_u128: {:?}", daily_reward_for_miner_as_u128.clone()); + + let mut manageable_daily_reward_for_miner_as_u128 = 0u128; + if let Some(_manageable_daily_reward_for_miner_as_u128) = + daily_reward_for_miner_as_u128.clone().checked_div(1000000000000000000u128) { + manageable_daily_reward_for_miner_as_u128 = _manageable_daily_reward_for_miner_as_u128; + } else { + log::error!("Unable to divide daily_reward_for_miner_as_u128 to make it smaller"); + println!("Unable to divide daily_reward_for_miner_as_u128 to make it smaller"); + return Err(DispatchError::Other("Unable to divide daily_reward_for_miner_as_u128 to make it smaller")); + } + log::info!("manageable_daily_reward_for_miner_as_u128 {:#?}", manageable_daily_reward_for_miner_as_u128); + println!("manageable_daily_reward_for_miner_as_u128 {:#?}", manageable_daily_reward_for_miner_as_u128); + + // Multiply, handling overflow + // TODO - probably have to initialise below proportion_of_daily_reward_for_miner_fixed128 to 0u128, + // and convert distribution_multiplier_for_day_fixed128 to u64, + // and convert daily_reward_for_miner_as_u128 to u64 too, like i did earlier. + // but it works so this doesn't seem necessary. + let proportion_of_daily_reward_for_miner_fixed128; + let _proportion_of_daily_reward_for_miner_fixed128 = + U64F64::from_num(distribution_multiplier_for_day_fixed128.clone()).checked_mul(U64F64::from_num(manageable_daily_reward_for_miner_as_u128.clone())); + match _proportion_of_daily_reward_for_miner_fixed128 { + None => { + log::error!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 with daily_reward_for_miner_as_u128 due to StorageOverflow"); + println!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 with daily_reward_for_miner_as_u128 due to StorageOverflow"); + return Err(DispatchError::Other("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 with daily_reward_for_miner_as_u128 due to StorageOverflow")); + }, + Some(x) => { + proportion_of_daily_reward_for_miner_fixed128 = x; } + } + log::info!("proportion_of_daily_reward_for_miner_fixed128: {:?}", proportion_of_daily_reward_for_miner_fixed128.clone()); + println!("proportion_of_daily_reward_for_miner_fixed128: {:?}", proportion_of_daily_reward_for_miner_fixed128.clone()); + + // round down to nearest integer. we need to round down, because if we round up then if there are + // 3x registered miners with 5000 DHX rewards allowance per day then they would each get 1667 rewards, + // but there would only be 1666 remaining after the first two, so the last one would miss out. + // so if we round down they each get 1666 DHX and there is 2 DHX from the daily allocation that doesn't get distributed at all. + let proportion_of_daily_reward_for_miner_u128: u128 = proportion_of_daily_reward_for_miner_fixed128.floor().to_num::(); + log::info!("proportion_of_daily_reward_for_miner_u128: {:?}", proportion_of_daily_reward_for_miner_u128.clone()); + println!("proportion_of_daily_reward_for_miner_u128: {:?}", proportion_of_daily_reward_for_miner_u128.clone()); + + + // we lose some accuracy doing this conversion, but at least we split the bulk of the rewards proportionally and fairly + let mut restored_proportion_of_daily_reward_for_miner_u128 = 0u128; + if let Some(_restored_proportion_of_daily_reward_for_miner_u128) = + proportion_of_daily_reward_for_miner_u128.clone().checked_mul(1000000000000000000u128) { + restored_proportion_of_daily_reward_for_miner_u128 = _restored_proportion_of_daily_reward_for_miner_u128; + } else { + log::error!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 to restore it larger again"); + println!("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 to restore it larger again"); + return Err(DispatchError::Other("Unable to multiply proportion_of_daily_reward_for_miner_fixed128 to restore it larger again")); + } + log::info!("restored_proportion_of_daily_reward_for_miner_u128: {:?}", restored_proportion_of_daily_reward_for_miner_u128.clone()); + println!("restored_proportion_of_daily_reward_for_miner_u128: {:?}", restored_proportion_of_daily_reward_for_miner_u128.clone()); + + log::info!("[rewards] block: {:#?}, date_start: {:#?} restored_proportion_of_daily_reward_for_miner_u128: {:#?}", block_number, start_of_requested_date_millis, restored_proportion_of_daily_reward_for_miner_u128); + println!("[rewards] block: {:#?}, date_start: {:#?} restored_proportion_of_daily_reward_for_miner_u128: {:#?}", block_number, start_of_requested_date_millis, restored_proportion_of_daily_reward_for_miner_u128); + + let treasury_account_id: T::AccountId = >::account_id(); + let max_payout = pallet_balances::Pallet::::usable_balance(treasury_account_id.clone()); + log::info!("Treasury account id: {:?}", treasury_account_id.clone()); + log::info!("Miner to receive reward: {:?}", miner_public_key.clone()); + log::info!("Treasury balance max payout: {:?}", max_payout.clone()); + println!("Treasury account id: {:?}", treasury_account_id.clone()); + println!("Miner to receive reward: {:?}", miner_public_key.clone()); + println!("Treasury balance max payout: {:?}", max_payout.clone()); + + let proportion_of_daily_reward_for_miner; + let _proportion_of_daily_reward_for_miner = Self::convert_u128_to_balance(restored_proportion_of_daily_reward_for_miner_u128.clone()); + match _proportion_of_daily_reward_for_miner { + Err(_e) => { + log::error!("Unable to convert u128 to balance for proportion_of_daily_reward_for_miner"); + println!("Unable to convert u128 to balance for proportion_of_daily_reward_for_miner"); + return Err(DispatchError::Other("Unable to convert u128 to balance for proportion_of_daily_reward_for_miner")); + }, + Ok(ref x) => { + proportion_of_daily_reward_for_miner = x; + } + } + log::info!("proportion_of_daily_reward_for_miner: {:?}", proportion_of_daily_reward_for_miner.clone()); + println!("proportion_of_daily_reward_for_miner: {:?}", proportion_of_daily_reward_for_miner.clone()); - // println!("[rewards] block: {:#?}, miner: {:#?}, date_start: {:#?} restored_proportion_of_daily_reward_for_miner_u128: {:#?}", _n, miner_count, start_of_requested_date_millis, restored_proportion_of_daily_reward_for_miner_u128); + let max_payout_as_u128; + if let Some(_max_payout_as_u128) = TryInto::::try_into(max_payout).ok() { + max_payout_as_u128 = _max_payout_as_u128; + } else { + log::error!("Unable to convert Balance to u128 for max_payout"); + println!("Unable to convert Balance to u128 for max_payout"); + return Err(DispatchError::Other("Unable to convert Balance to u128 for max_payout")); + } + log::info!("max_payout_as_u128: {:?}", max_payout_as_u128.clone()); + println!("max_payout_as_u128: {:?}", max_payout_as_u128.clone()); - let treasury_account_id: T::AccountId = >::account_id(); - let max_payout = pallet_balances::Pallet::::usable_balance(treasury_account_id.clone()); - log::info!("Treasury account id: {:?}", treasury_account_id.clone()); - log::info!("Miner to receive reward: {:?}", miner.clone()); - log::info!("Treasury balance max payout: {:?}", max_payout.clone()); + // TODO - rename `rewards_allowance_dhx_remaining_today` to + // `rewards_allowance_dhx_remaining_for_date_rewards_being_claimed` - let proportion_of_daily_reward_for_miner; - let _proportion_of_daily_reward_for_miner = Self::convert_u128_to_balance(restored_proportion_of_daily_reward_for_miner_u128.clone()); - match _proportion_of_daily_reward_for_miner { + // Store output `rewards_allowance_dhx_remaining_today_as_u128` outside the loop + // Validate inputs so the daily_rewards is less or equal to the existing_allowance + if let Some(_rewards_allowance_dhx_remaining_today) = >::get(&start_of_requested_date_millis) { + let _rewards_allowance_dhx_remaining_today_as_u128 = Self::convert_balance_to_u128(_rewards_allowance_dhx_remaining_today.clone()); + match _rewards_allowance_dhx_remaining_today_as_u128.clone() { Err(_e) => { - log::error!("Unable to convert u128 to balance for proportion_of_daily_reward_for_miner"); - return 0; + log::error!("Unable to convert balance to u128"); + println!("Unable to convert balance to u128"); + return Err(DispatchError::Other("Unable to convert balance to u128")); }, - Ok(ref x) => { - proportion_of_daily_reward_for_miner = x; + Ok(x) => { + rewards_allowance_dhx_remaining_today_as_u128 = x; } } + log::info!("rewards_allowance_dhx_remaining_today_as_u128: {:?}", rewards_allowance_dhx_remaining_today_as_u128.clone()); + println!("rewards_allowance_dhx_remaining_today_as_u128: {:?}", rewards_allowance_dhx_remaining_today_as_u128.clone()); + } else { + log::error!("Unable to retrieve balance from value provided."); + println!("Unable to retrieve balance from value provided."); + return Err(DispatchError::Other("Unable to retrieve balance from value provided.")); + } - let max_payout_as_u128; - if let Some(_max_payout_as_u128) = TryInto::::try_into(max_payout).ok() { - max_payout_as_u128 = _max_payout_as_u128; - } else { - log::error!("Unable to convert Balance to u128 for max_payout"); - return 0; - } - log::info!("max_payout_as_u128: {:?}", max_payout_as_u128.clone()); - - // Store output `rewards_allowance_dhx_remaining_today_as_u128` outside the loop - // Validate inputs so the daily_rewards is less or equal to the existing_allowance - if let Some(_rewards_allowance_dhx_remaining_today) = >::get(&start_of_requested_date_millis) { - let _rewards_allowance_dhx_remaining_today_as_u128 = Self::convert_balance_to_u128(_rewards_allowance_dhx_remaining_today.clone()); - match _rewards_allowance_dhx_remaining_today_as_u128.clone() { - Err(_e) => { - log::error!("Unable to convert balance to u128"); - return 0; - }, - Ok(x) => { - rewards_allowance_dhx_remaining_today_as_u128 = x; - } + log::info!("[prepared-for-payment] block: {:#?}, date_start: {:#?} max payout: {:#?}, rewards remaining today {:?}, restored_proportion_of_daily_reward_for_miner_u128 {:?}", block_number, start_of_requested_date_millis, max_payout_as_u128, rewards_allowance_dhx_remaining_today_as_u128, restored_proportion_of_daily_reward_for_miner_u128); + println!("[prepared-for-payment] block: {:#?}, date_start: {:#?} max payout: {:#?}, rewards remaining today {:?}, restored_proportion_of_daily_reward_for_miner_u128 {:?}", block_number, start_of_requested_date_millis, max_payout_as_u128, rewards_allowance_dhx_remaining_today_as_u128, restored_proportion_of_daily_reward_for_miner_u128); + + // check if miner's reward is less than or equal to: rewards_allowance_dhx_daily_remaining + if restored_proportion_of_daily_reward_for_miner_u128.clone() > 0u128 && + rewards_allowance_dhx_remaining_today_as_u128.clone() >= restored_proportion_of_daily_reward_for_miner_u128.clone() && + max_payout_as_u128.clone() >= restored_proportion_of_daily_reward_for_miner_u128.clone() + { + // pay the miner their daily reward + info!("Paying the miner a proportion of the remaining daily reward allowance"); + println!("Paying the miner a proportion of the remaining daily reward allowance"); + + let tx_result; + let _tx_result = ::Currency::transfer( + &treasury_account_id, + &_sender.clone(), + proportion_of_daily_reward_for_miner.clone(), + ExistenceRequirement::KeepAlive + ); + match _tx_result { + Err(_e) => { + log::error!("Unable to transfer from treasury to miner {:?}", miner_public_key.clone()); + return Err(DispatchError::Other("Unable to transfer from treasury to miner")); + }, + Ok(ref x) => { + tx_result = x; } - log::info!("rewards_allowance_dhx_remaining_today_as_u128: {:?}", rewards_allowance_dhx_remaining_today_as_u128.clone()); - } else { - log::error!("Unable to retrieve balance from value provided."); - return 0; } + info!("Transfer to the miner tx_result: {:?}", tx_result.clone()); + println!("Transfer to the miner tx_result: {:?}", tx_result.clone()); - // println!("[prepared-for-payment] block: {:#?}, miner: {:#?}, date_start: {:#?} max payout: {:#?}, rewards remaining today {:?}, restored_proportion_of_daily_reward_for_miner_u128 {:?}", _n, miner_count, start_of_requested_date_millis, max_payout_as_u128, rewards_allowance_dhx_remaining_today_as_u128, restored_proportion_of_daily_reward_for_miner_u128); - - // check if miner's reward is less than or equal to: rewards_allowance_dhx_daily_remaining - if restored_proportion_of_daily_reward_for_miner_u128.clone() > 0u128 && - rewards_allowance_dhx_remaining_today_as_u128.clone() >= restored_proportion_of_daily_reward_for_miner_u128.clone() && - max_payout_as_u128.clone() >= restored_proportion_of_daily_reward_for_miner_u128.clone() - { - // pay the miner their daily reward - info!("Paying the miner a proportion of the remaining daily reward allowance"); - - let tx_result; - let _tx_result = ::Currency::transfer( - &treasury_account_id, - &miner.clone(), - proportion_of_daily_reward_for_miner.clone(), - ExistenceRequirement::KeepAlive - ); - match _tx_result { - Err(_e) => { - log::error!("Unable to transfer from treasury to miner {:?}", miner.clone()); - return 0; - }, - Ok(ref x) => { - tx_result = x; - } - } - info!("Transfer to the miner tx_result: {:?}", tx_result.clone()); + info!("Success paying the reward to the miner: {:?}", restored_proportion_of_daily_reward_for_miner_u128.clone()); + println!("Success paying the reward to the miner: {:?}", restored_proportion_of_daily_reward_for_miner_u128.clone()); - info!("Success paying the reward to the miner: {:?}", restored_proportion_of_daily_reward_for_miner_u128.clone()); + // TODO - move into function `reduce_rewards_allowance_dhx_for_date_remaining`? - // TODO - move into function `reduce_rewards_allowance_dhx_for_date_remaining`? - - // Subtract, handling overflow - let new_rewards_allowance_dhx_remaining_today_as_u128; - let _new_rewards_allowance_dhx_remaining_today_as_u128 = - rewards_allowance_dhx_remaining_today_as_u128.clone().checked_sub(restored_proportion_of_daily_reward_for_miner_u128.clone()); - match _new_rewards_allowance_dhx_remaining_today_as_u128 { - None => { - log::error!("Unable to subtract restored_proportion_of_daily_reward_for_miner_u128 from rewards_allowance_dhx_remaining_today due to StorageOverflow"); - return 0; - }, - Some(x) => { - new_rewards_allowance_dhx_remaining_today_as_u128 = x; - } + // Subtract, handling overflow + let new_rewards_allowance_dhx_remaining_today_as_u128; + let _new_rewards_allowance_dhx_remaining_today_as_u128 = + rewards_allowance_dhx_remaining_today_as_u128.clone().checked_sub(restored_proportion_of_daily_reward_for_miner_u128.clone()); + match _new_rewards_allowance_dhx_remaining_today_as_u128 { + None => { + log::error!("Unable to subtract restored_proportion_of_daily_reward_for_miner_u128 from rewards_allowance_dhx_remaining_today due to StorageOverflow"); + println!("Unable to subtract restored_proportion_of_daily_reward_for_miner_u128 from rewards_allowance_dhx_remaining_today due to StorageOverflow"); + return Err(DispatchError::Other("Unable to subtract restored_proportion_of_daily_reward_for_miner_u128 from rewards_allowance_dhx_remaining_today due to StorageOverflow")); + }, + Some(x) => { + new_rewards_allowance_dhx_remaining_today_as_u128 = x; } + } - let new_rewards_allowance_dhx_remaining_today; - let _new_rewards_allowance_dhx_remaining_today = Self::convert_u128_to_balance(new_rewards_allowance_dhx_remaining_today_as_u128.clone()); - match _new_rewards_allowance_dhx_remaining_today { - Err(_e) => { - log::error!("Unable to convert u128 to balance for new_rewards_allowance_dhx_remaining_today"); - return 0; - }, - Ok(ref x) => { - new_rewards_allowance_dhx_remaining_today = x; - } + let new_rewards_allowance_dhx_remaining_today; + let _new_rewards_allowance_dhx_remaining_today = Self::convert_u128_to_balance(new_rewards_allowance_dhx_remaining_today_as_u128.clone()); + match _new_rewards_allowance_dhx_remaining_today { + Err(_e) => { + log::error!("Unable to convert u128 to balance for new_rewards_allowance_dhx_remaining_today"); + println!("Unable to convert u128 to balance for new_rewards_allowance_dhx_remaining_today"); + return Err(DispatchError::Other("Unable to convert u128 to balance for new_rewards_allowance_dhx_remaining_today")); + }, + Ok(ref x) => { + new_rewards_allowance_dhx_remaining_today = x; } + } - // Write the new value to storage - >::insert( - start_of_requested_date_millis.clone(), - new_rewards_allowance_dhx_remaining_today.clone(), - ); - - // println!("[paid] block: {:#?}, miner: {:#?}, date_start: {:#?} new_rewards_allowance_dhx_remaining_today: {:#?}", _n, miner_count, start_of_requested_date_millis, new_rewards_allowance_dhx_remaining_today); + // Write the new value to storage + >::insert( + start_of_requested_date_millis.clone(), + new_rewards_allowance_dhx_remaining_today.clone(), + ); - // emit event with reward payment history rather than bloating storage - Self::deposit_event(Event::TransferredRewardsAllowanceDHXToMinerForDate( - start_of_requested_date_millis.clone(), - proportion_of_daily_reward_for_miner.clone(), - new_rewards_allowance_dhx_remaining_today.clone(), - miner.clone(), - )); + log::info!("[paid] block: {:#?}, date_start: {:#?} new_rewards_allowance_dhx_remaining_today: {:#?}", block_number, start_of_requested_date_millis, new_rewards_allowance_dhx_remaining_today); + println!("[paid] block: {:#?}, date_start: {:#?} new_rewards_allowance_dhx_remaining_today: {:#?}", block_number, start_of_requested_date_millis, new_rewards_allowance_dhx_remaining_today); - log::info!("TransferredRewardsAllowanceDHXToMinerForDate {:?} {:?} {:?} {:?}", + >::insert( + ( start_of_requested_date_millis.clone(), - proportion_of_daily_reward_for_miner.clone(), - new_rewards_allowance_dhx_remaining_today.clone(), - miner.clone(), - ); + miner_public_key.clone(), + ), + true, + ); - continue; - } else { - log::error!("Insufficient remaining rewards allowance to pay daily reward to miner"); + // emit event with reward payment history rather than bloating storage + Self::deposit_event(Event::TransferredRewardsAllowanceDHXToMinerForDate( + start_of_requested_date_millis.clone(), + proportion_of_daily_reward_for_miner.clone(), + new_rewards_allowance_dhx_remaining_today.clone(), + miner_public_key.clone(), + )); - break; - } + log::info!("TransferredRewardsAllowanceDHXToMinerForDate {:?} {:?} {:?} {:?}", + start_of_requested_date_millis.clone(), + proportion_of_daily_reward_for_miner.clone(), + new_rewards_allowance_dhx_remaining_today.clone(), + miner_public_key.clone(), + ); + // do not split this across multiple lines otherwise not as easy to find/replace `println` with `println` when + // switching between running tests with `println` and building the code with `cargo build --release` that doesn't + // work with println + println!("TransferredRewardsAllowanceDHXToMinerForDate {:?} {:?} {:?} {:?}", start_of_requested_date_millis.clone(), proportion_of_daily_reward_for_miner.clone(), new_rewards_allowance_dhx_remaining_today.clone(), miner_public_key.clone()); + } else { + log::error!("Insufficient remaining rewards allowance to pay daily reward to miner"); + println!("Insufficient remaining rewards allowance to pay daily reward to miner"); + return Err(DispatchError::Other("Insufficient remaining rewards allowance to pay daily reward to miner")); } let rewards_allowance_dhx_remaining_today; @@ -1763,262 +2508,170 @@ pub mod pallet { match _rewards_allowance_dhx_remaining_today { Err(_e) => { log::error!("Unable to convert u128 to balance for rewards_allowance_dhx_remaining_today"); - return 0; + println!("Unable to convert u128 to balance for rewards_allowance_dhx_remaining_today"); + return Err(DispatchError::Other("Unable to convert u128 to balance for rewards_allowance_dhx_remaining_today")); }, Ok(ref x) => { rewards_allowance_dhx_remaining_today = x; } } + info!("rewards_allowance_dhx_remaining_today: {:?}", rewards_allowance_dhx_remaining_today.clone()); + println!("rewards_allowance_dhx_remaining_today: {:?}", rewards_allowance_dhx_remaining_today.clone()); - >::insert( - start_of_requested_date_millis.clone(), - true - ); - - // println!("[distributed] block: {:#?}, miner: {:#?}, date_start: {:#?} ", _n, miner_count, start_of_requested_date_millis); - - Self::deposit_event(Event::DistributedRewardsAllowanceDHXForDateRemaining( - start_of_requested_date_millis.clone(), - rewards_allowance_dhx_remaining_today.clone(), - )); - - return 0; - } - - // `on_finalize` is executed at the end of block after all extrinsic are dispatched. - fn on_finalize(_n: T::BlockNumber) { - // Perform necessary data/state clean up here. - } - } - - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. - #[pallet::call] - impl Pallet { - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_registered_dhx_miner(origin: OriginFor) -> DispatchResult { - let _sender: T::AccountId = ensure_signed(origin)?; - - >::append(_sender.clone()); - log::info!("register_dhx_miner - account_id: {:?}", &_sender); - - Self::deposit_event(Event::SetRegisteredDHXMiner( - _sender.clone(), - )); - - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_min_bonded_dhx_daily(origin: OriginFor, min_bonded_dhx_daily: BalanceOf) -> DispatchResult { - let _sender: T::AccountId = ensure_signed(origin)?; - - >::put(&min_bonded_dhx_daily.clone()); - log::info!("set_min_bonded_dhx_daily: {:?}", &min_bonded_dhx_daily); - - Self::deposit_event(Event::SetMinBondedDHXDailyStored( - min_bonded_dhx_daily.clone(), - _sender.clone(), - )); - - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_min_mpower_daily(origin: OriginFor, min_mpower_daily: u128) -> DispatchResult { - let _sender: T::AccountId = ensure_signed(origin)?; - - >::put(&min_mpower_daily.clone()); - log::info!("set_min_mpower_daily: {:?}", &min_mpower_daily); - - Self::deposit_event(Event::SetMinMPowerDailyStored( - min_mpower_daily.clone(), - _sender.clone(), - )); - - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_cooling_off_period_days(origin: OriginFor, cooling_off_period_days: u32) -> DispatchResult { - let _sender: T::AccountId = ensure_signed(origin)?; - - >::put(&cooling_off_period_days.clone()); - log::info!("cooling_off_period_days: {:?}", &cooling_off_period_days); - - Self::deposit_event(Event::SetCoolingOffPeriodDaysStored( - cooling_off_period_days.clone(), - )); - - Ok(()) - } - - // TODO: we need to change this in future so it is only modifiable by governance, - // rather than just any user - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_rewards_allowance_dhx_daily(origin: OriginFor, rewards_allowance: BalanceOf) -> DispatchResult { - let _who = ensure_signed(origin)?; - // Update storage - >::put(&rewards_allowance.clone()); - log::info!("set_rewards_allowance_dhx_daily - rewards_allowance: {:?}", &rewards_allowance); - - // Emit an event. - Self::deposit_event(Event::SetRewardsAllowanceDHXDailyStored( - rewards_allowance.clone(), - _who.clone() - )); - - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - // customised by governance at any time. this function allows us to change it each year - // https://docs.google.com/spreadsheets/d/1W2AzOH9Cs9oCR8UYfYCbpmd9X7hp-USbYXL7AuwMY_Q/edit#gid=970997021 - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_rewards_allowance_dhx_for_date_remaining(origin: OriginFor, rewards_allowance: BalanceOf, timestamp: u64) -> DispatchResult { - let _who = ensure_signed(origin)?; - - // Note: we do not need the following as we're not using the current timestamp, rather the function parameter. - // let current_date = >::get(); - // let requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone())?; - // log::info!("requested_date_as_u64: {:?}", requested_date_as_u64.clone()); - - // Note: to get current timestamp `>::get()` - // convert the requested date/time to the start of that day date/time to signify that date for lookup - // i.e. 21 Apr @ 1420 -> 21 Apr @ 0000 - let start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(timestamp.clone())?; - - // Update storage. Override the default that may have been set in on_initialize - >::insert(start_of_requested_date_millis.clone(), &rewards_allowance); - log::info!("set_rewards_allowance_dhx_for_date_remaining - rewards_allowance: {:?}", &rewards_allowance); - - // Emit an event. - Self::deposit_event(Event::ChangedRewardsAllowanceDHXForDateRemainingStored( - start_of_requested_date_millis.clone(), - rewards_allowance.clone(), - _who.clone(), - 1u8, // increment - )); - - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - // parameter `change: u8` value may be 0 or 1 (or any other value) to represent that we want to make a - // corresponding decrease or increase to the remaining dhx rewards allowance for a given date. - pub fn change_rewards_allowance_dhx_for_date_remaining(origin: OriginFor, daily_rewards: BalanceOf, timestamp: u64, change: u8) -> DispatchResult { - let _who = ensure_signed(origin)?; - - let start_of_requested_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(timestamp.clone())?; - - // https://substrate.dev/rustdocs/latest/frame_support/storage/trait.StorageMap.html - ensure!(>::contains_key(&start_of_requested_date_millis), DispatchError::Other("Date key must exist to reduce allowance.")); - - // Validate inputs so the daily_rewards is less or equal to the existing_allowance - let existing_allowance_as_u128; - if let Some(_existing_allowance) = >::get(&start_of_requested_date_millis) { - existing_allowance_as_u128 = Self::convert_balance_to_u128(_existing_allowance.clone())?; - log::info!("change_rewards_allowance_dhx_for_date_remaining - existing_allowance_as_u128: {:?}", existing_allowance_as_u128.clone()); - } else { - return Err(DispatchError::Other("Unable to retrieve balance from value provided")); - } - - let daily_rewards_as_u128; - daily_rewards_as_u128 = Self::convert_balance_to_u128(daily_rewards.clone())?; - log::info!("change_rewards_allowance_dhx_for_date_remaining - daily_rewards_as_u128: {:?}", daily_rewards_as_u128.clone()); - - ensure!(daily_rewards_as_u128 > 0u128, DispatchError::Other("Daily rewards must be greater than zero")); - ensure!(existing_allowance_as_u128 >= daily_rewards_as_u128, DispatchError::Other("Daily rewards cannot exceed current remaining allowance")); - - let new_remaining_allowance_as_balance; - if change == 0 { - // Decrementing the value will error in the event of underflow. - let new_remaining_allowance_as_u128 = existing_allowance_as_u128.checked_sub(daily_rewards_as_u128).ok_or(Error::::StorageUnderflow)?; - new_remaining_allowance_as_balance = Self::convert_u128_to_balance(new_remaining_allowance_as_u128.clone())?; - log::info!("change_rewards_allowance_dhx_for_date_remaining - Decreasing rewards_allowance_dhx_for_date_remaining at Date: {:?}", &start_of_requested_date_millis); - } else { - // Incrementing the value will error in the event of overflow. - let new_remaining_allowance_as_u128 = existing_allowance_as_u128.checked_add(daily_rewards_as_u128).ok_or(Error::::StorageOverflow)?; - new_remaining_allowance_as_balance = Self::convert_u128_to_balance(new_remaining_allowance_as_u128.clone())?; - log::info!("change_rewards_allowance_dhx_for_date_remaining - Increasing rewards_allowance_dhx_for_date_remaining at Date: {:?}", &start_of_requested_date_millis); - } + log::info!("[distributed] block: {:#?}, date_start: {:#?} ", block_number, start_of_requested_date_millis); + println!("[distributed] block: {:#?}, date_start: {:#?} ", block_number, start_of_requested_date_millis); - // Update storage - >::mutate( - &start_of_requested_date_millis, - |allowance| { - if let Some(_allowance) = allowance { - *_allowance = new_remaining_allowance_as_balance.clone(); - } - }, - ); - - // Emit an event. - Self::deposit_event(Event::ChangedRewardsAllowanceDHXForDateRemainingStored( - start_of_requested_date_millis.clone(), - new_remaining_allowance_as_balance.clone(), - _who.clone(), - change.clone(), - )); - - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_rewards_multiplier_operation(origin: OriginFor, operation: u8) -> DispatchResult { - let _who = ensure_signed(origin)?; - >::put(&operation.clone()); - log::info!("set_rewards_multiplier_operation - operation: {:?}", &operation); - - // Emit an event. - Self::deposit_event(Event::SetRewardsMultiplierOperationStored( - operation.clone(), - _who.clone() - )); - - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_rewards_multiplier_default_period_days(origin: OriginFor, days: u32) -> DispatchResult { - let _who = ensure_signed(origin)?; - >::put(&days.clone()); - log::info!("set_rewards_multiplier_default_period_days - days: {:?}", &days); - - // Emit an event. - Self::deposit_event(Event::SetRewardsMultiplierDefaultPeriodDaysStored( - days.clone(), - _who.clone() - )); - - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn set_rewards_multiplier_next_period_days(origin: OriginFor, days: u32) -> DispatchResult { - let _who = ensure_signed(origin)?; - >::put(&days.clone()); - log::info!("set_rewards_multiplier_next_period_days - days: {:?}", &days); - - // Emit an event. - Self::deposit_event(Event::SetRewardsMultiplierNextPeriodDaysStored( - days.clone(), - _who.clone() + Self::deposit_event(Event::DistributedRewardsAllowanceDHXForDateRemaining( + start_of_requested_date_millis.clone(), + rewards_allowance_dhx_remaining_today.clone(), )); + // FIXME - previously when we distributed all rewards for a given date + // we used `RewardsAllowanceDHXForDateRemainingDistributed` to record whether all the rewards for + // that date had been distributed. + // + // but now registered_dhx_miners claim their rewards for a given date + + // we should only run `RewardsAllowanceDHXForDateRemainingDistributed` after all + // the registered_dhx_miners that were stored under + // a given date as the key for RewardsAccumulatedDHXForMinerForDate have been flagged as having been paid. + // so since there may be some leftover rewards that don't get distributed for a given day due to rounding, + // we can't say its all distributed when `rewards_allowance_dhx_remaining_today` is 0. + // we now have a StorageMap RewardsAllowanceDHXForMinerForDateRemainingDistributed, that uses the same + // date and miner_public_key as the key, and stores a flag of whether they have been paid for that date + // already as a value, so they can't claim more than once for the same day + + // TODO - we won't use this yet. we could use it but we'll need to use `RewardsEligibleMinersForDate`, + // which is where we store the registered_dhx_miners that are deemed eligible for rewards on a given date, + // and also record which of those registered miners we've already distributed rewards to + // using `RewardsAllowanceDHXForMinerForDateRemainingDistributed` and when there are 0 left to distribute to, + // then we'd run this, but it doesn't seem that important to know + // >::insert( + // start_of_requested_date_millis.clone(), + // true + // ); + // Return a successful DispatchResultWithPostInfo Ok(()) } + + // Off-chain workers + + /// Submit new mPower data on-chain via unsigned transaction. + /// + /// Works exactly like the `submit_mpower` function, but since we allow sending the + /// transaction without a signature, and hence without paying any fees, + /// we need a way to make sure that only some transactions are accepted. + /// This function can be called only once every `T::UnsignedInterval` blocks. + /// Transactions that call that function are de-duplicated on the pool level + /// via `validate_unsigned` implementation and also are rendered invalid if + /// the function has already been called in current "session". + /// + /// TODO - verify the provided mPower to check that it is meaningful data + /// TODO - replace u32 with data structured that contains the account id of each + /// registered DHX miner and their mPower data for a date + /// TODO - specify `weight` for unsigned calls as well, because even though + /// they don't charge fees, we still don't want a single block to contain unlimited + /// number of such transactions. + #[pallet::weight(0)] + pub fn submit_fetched_mpower_unsigned( + origin: OriginFor, + _block_number: T::BlockNumber, + start_of_requested_date_millis: Date, + mpower_payload_vec: Vec>, + ) -> DispatchResultWithPostInfo { + log::info!("submit_fetched_mpower_unsigned"); + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the mpower vec on-chain, but mark it as coming from an empty address. + Self::add_mpower(Default::default(), start_of_requested_date_millis.clone(), mpower_payload_vec.clone()); + log::info!("finished submit_fetched_mpower_unsigned"); + // now increment the block number at which we expect next unsigned transaction + // that is specifically for fetching mpower. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) + } + + #[pallet::weight(0)] + pub fn submit_finished_fetching_mpower_unsigned( + origin: OriginFor, + _block_number: T::BlockNumber, + fetched_mpower_data: FetchedMPowerForAccountsForDatePayloadData, + ) -> DispatchResultWithPostInfo { + log::info!("submit_finished_fetching_mpower_unsigned"); + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the data on-chain, but mark it as coming from an empty address. + Self::add_finished_fetching_mpower(Default::default(), fetched_mpower_data.clone()); + // now increment the block number at which we expect next unsigned transaction + // that is specifically for having "finished" fetching mpower. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + /// Validate unsigned call to this module. + /// + /// By default unsigned transactions are disallowed, but implementing the validator + /// here we make sure that some particular calls (the ones produced by offchain worker) + /// are being whitelisted and marked as valid. + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_fetched_mpower_unsigned(block_number, start_of_requested_date_millis, new_mpower_data_vec) = call { + log::info!("validate_unsigned submit_fetched_mpower_unsigned"); + return Self::validate_transaction_parameters_for_fetched_mpower(block_number, start_of_requested_date_millis, new_mpower_data_vec); + } else if let Call::submit_finished_fetching_mpower_unsigned(block_number, fetched_mpower_data) = call { + log::info!("validate_unsigned validate_transaction_parameters_for_fetched_mpower"); + return Self::validate_transaction_parameters_for_finished_fetching_mpower(block_number, fetched_mpower_data); + } else { + log::info!("validate_unsigned invalid"); + return InvalidTransaction::Call.into(); + } + } } // Private functions impl Pallet { + fn should_process_block(block_number: T::BlockNumber) -> bool { + let block_one = 1u32; + let block_one_as_block; + if let Some(_block_one) = TryInto::::try_into(block_one).ok() { + block_one_as_block = _block_one; + } else { + log::error!("Unable to convert u32 to BlockNumber"); + return false; + } + + // skip block #1 since timestamp is 0 in blocks before block #2 + if block_number == block_one_as_block.clone() { + return false; + } else { + return true; + } + } + + fn is_block_two(block_number: T::BlockNumber) -> bool { + let block_two = 2u32; + let block_two_as_block; + if let Some(_block_two) = TryInto::::try_into(block_two).ok() { + block_two_as_block = _block_two; + } else { + log::error!("Unable to convert u32 to BlockNumber"); + return false; + } + if block_number.clone() == block_two_as_block.clone() { + return true; + } else { + return false; + } + } + fn convert_moment_to_u64_in_milliseconds(date: T::Moment) -> Result { let date_as_u64_millis; if let Some(_date_as_u64) = TryInto::::try_into(date).ok() { @@ -2103,7 +2756,75 @@ pub mod pallet { return Ok(blocknumber_u64); } - fn set_bonded_dhx_of_account_for_date(account_id: T::AccountId, bonded_dhx: u128) -> Result { + // fn convert_vec_u8_to_u128(data: &[u8]) -> Result { + // Ok(core::str::from_utf8(data)?.parse()?) + // } + + // Convert a Vec that we received from an API endpoint that represents the mPower + // associated with an account id into a u128 value. + // ascii table: https://aws1.discourse-cdn.com/business5/uploads/rust_lang/original/3X/9/0/909baa7e3d9569489b07c791ca76f2223bd7bac2.webp + pub fn convert_vec_u8_to_u128(data: &[u8]) -> Result { + let mut out = 0u128; + let mut multiplier = 1; + + for &val in data.iter().rev() { + // log::info!("{:?}", val); + + let mut digit = 0u128; + match val { + 48u8 => { + digit = 0u128; + }, + 49u8 => { + digit = 1u128; + }, + 50u8 => { + digit = 2u128; + }, + 51u8 => { + digit = 3u128; + }, + 52u8 => { + digit = 4u128; + }, + 53u8 => { + digit = 5u128; + }, + 54u8 => { + digit = 6u128; + }, + 55u8 => { + digit = 7u128; + }, + 56u8 => { + digit = 8u128; + }, + 57u8 => { + digit = 9u128; + }, + _ => { + log::error!("Non-digit ASCII char in input"); + return Err(DispatchError::Other("Non-digit ASCII char in input")); + }, + } + + if digit != 0u128 && out != 0u128 { + multiplier *= 10; + } else if digit != 0u128 && out == 0u128 { + multiplier *= 1; + } else if digit == 0u128 && out != 0u128 { + multiplier *= 10; + } else if digit == 0u128 && out == 0u128 { + multiplier *= 10; + } + + out += multiplier * digit; + } + + Ok(out) + } + + fn set_bonded_dhx_of_account_for_date(account_public_key: Vec, bonded_dhx: u128) -> Result { // Note: we DO need the following as we're using the current timestamp, rather than a function parameter. let timestamp: ::Moment = >::get(); let requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone())?; @@ -2131,60 +2852,114 @@ pub mod pallet { >::insert( ( start_of_requested_date_millis.clone(), - account_id.clone(), + account_public_key.clone(), ), bonded_dhx_current.clone(), ); - log::info!("set_bonded_dhx_of_account_for_date - account_id: {:?}", &account_id); + log::info!("set_bonded_dhx_of_account_for_date - account_public_key: {:?}", &account_public_key); log::info!("set_bonded_dhx_of_account_for_date - bonded_dhx_current: {:?}", &bonded_dhx_current); // Emit an event. Self::deposit_event(Event::SetBondedDHXOfAccountForDateStored( start_of_requested_date_millis.clone(), bonded_dhx_current.clone(), - account_id.clone(), + account_public_key.clone(), )); // Return a successful DispatchResultWithPostInfo Ok(bonded_dhx_current_u128.clone()) } - // we need to set the mPower for the next start date so it's available from off-chain in time - pub fn set_mpower_of_account_for_date(account_id: T::AccountId, mpower: u128, next_start_date: Date) -> Result { - // // Note: we DO need the following as we're using the current timestamp, rather than a function parameter. - // let timestamp: ::Moment = >::get(); - // let requested_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone())?; - // log::info!("set_mpower_of_account_for_date - requested_date_as_u64: {:?}", requested_date_as_u64.clone()); - - // convert the requested date/time to the start of that day date/time to signify that date for lookup - // i.e. 21 Apr @ 1420 -> 21 Apr @ 0000 - let start_of_next_start_date_millis = Self::convert_i64_in_milliseconds_to_start_of_date(next_start_date.clone())?; - + pub fn set_mpower_of_account_for_date(account_public_key: Vec, start_of_requested_date_millis: Date, mpower: u128) -> Result { + log::info!("set_mpower_of_account_for_date: {:?} {:?} {:?}", account_public_key.clone(), start_of_requested_date_millis.clone(), mpower.clone()); let mpower_current_u128 = mpower.clone(); + // check if the new mpower value differs from the value that is already in storage + // for the given key, and only insert if it is different + let mpower_for_account_for_date = >::get( + ( + start_of_requested_date_millis.clone(), + account_public_key.clone(), + ) + ); + log::info!("existing mpower_for_account_for_date {:?}", mpower_for_account_for_date.clone()); + match mpower_for_account_for_date { + None => { + log::info!("Adding new storage value of mPower for account for date of data retrieved from API {:?}", mpower_current_u128.clone()); + }, + Some(x) => { + log::warn!("Existing storage value of mPower for account for date of data retrieved from API {:?}", x); + return Err(DispatchError::Other("Existing storage value of mPower for account for date of data retrieved from API")); + } + } + // Update storage. Override the default that may have been set in on_initialize >::insert( ( - start_of_next_start_date_millis.clone(), - account_id.clone(), + start_of_requested_date_millis.clone(), + account_public_key.clone(), ), mpower_current_u128.clone(), ); - log::info!("set_mpower_of_account_for_date - start_of_next_start_date_millis: {:?}", &start_of_next_start_date_millis); - log::info!("set_mpower_of_account_for_date - account_id: {:?}", &account_id); - log::info!("set_mpower_of_account_for_date - mpower_current: {:?}", &mpower_current_u128); + + log::info!("Added MPowerForAccountForDate {:?} {:?} {:?}", + start_of_requested_date_millis.clone(), + account_public_key.clone(), + mpower_current_u128.clone(), + ); // Emit an event. - Self::deposit_event(Event::SetMPowerOfAccountForDateStored( - start_of_next_start_date_millis.clone(), + Self::deposit_event(Event::NewMPowerForAccountForDate( + start_of_requested_date_millis.clone(), + account_public_key.clone(), mpower_current_u128.clone(), - account_id.clone(), )); // Return a successful DispatchResultWithPostInfo Ok(mpower_current_u128.clone()) } + pub fn set_finished_fetching_mpower_for_accounts_for_date(all_miner_public_keys_fetched: Vec>, start_of_requested_date_millis: Date, total_mpower_fetched: u128) -> Result { + log::info!("set_finished_fetching_mpower_for_accounts_for_date"); + // check if the new total_mpower_fetched value differs from the value that is already in storage + // for the given key, and only insert if it is different + let data_finished_fetching_for_date = >::get(start_of_requested_date_millis.clone()); + + match data_finished_fetching_for_date { + None => { + }, + Some(x) => { + log::warn!("Existing storage value of finished fetching for date of data retrieved from API"); + return Err(DispatchError::Other("Existing storage value of finished fetching for date of data retrieved from API")); + } + } + + // Update storage. Override the default that may have been set in on_initialize + >::insert( + start_of_requested_date_millis.clone(), + ( + all_miner_public_keys_fetched.clone(), + total_mpower_fetched.clone(), + ) + ); + + log::info!("Added FinishedFetchingMPowerForAccountsForDate {:?} {:?} {:?}", + start_of_requested_date_millis.clone(), + all_miner_public_keys_fetched.clone(), + total_mpower_fetched.clone(), + ); + + // Emit an event. + Self::deposit_event(Event::NewFinishedFetchingMPowerForAccountsForDate( + start_of_requested_date_millis.clone(), + all_miner_public_keys_fetched.clone(), + total_mpower_fetched.clone(), + )); + + // Return a successful DispatchResultWithPostInfo + Ok(total_mpower_fetched.clone()) + } + fn get_min_bonded_dhx_daily() -> Result<(BalanceOf, u128), DispatchError> { let mut min_bonded_dhx_daily: BalanceOf = 10u32.into(); // initialize let mut min_bonded_dhx_daily_u128: u128 = TEN; @@ -2218,7 +2993,7 @@ pub mod pallet { min_bonded_dhx_daily_u128 = x.1; } } - // println!("Reset to the min. bonded DHX daily default"); + println!("Reset to the min. bonded DHX daily default"); } // Return a successful DispatchResultWithPostInfo Ok( @@ -2278,5 +3053,719 @@ pub mod pallet { // Return a successful DispatchResultWithPostInfo Ok(new_status.clone()) } + + pub fn calculate_rewards_eligibility_of_miner_for_day( + miner_public_key: Vec, + start_of_requested_date_millis: i64, + locks_first_amount_as_u128: u128, + rewards_allowance_dhx_daily: BalanceOf, + min_bonded_dhx_daily_u128: u128, + mpower_current_u128: u128, + block_number: T::BlockNumber, + miner_count: usize, + reg_dhx_miners: Vec>, + ) -> Result<(), &'static str> { + log::info!("calculate_rewards_eligibility_of_miner_for_day"); + println!("calculate_rewards_eligibility_of_miner_for_day"); + + // TODO - calculate the daily reward for the miner in DHX based on their mPower + // and add that to the new_rewards_aggregated_dhx_daily_as_u128 (which currently only + // includes the proportion of their reward based on their bonded DHX tokens) of all + // miner's for that day, and also add that to the accumulated rewards for that specific + // miner on that day. + + // calculate the daily reward for the miner in DHX based on their bonded DHX. + // it should be a proportion taking other eligible miner's who are eligible for + // daily rewards into account since we want to split them fairly. + // + // assuming min_bonded_dhx_daily is 10u128, and they have that minimum of 10 DHX bonded (10u128) for + // the locks_first_amount_as_u128 value, then they are eligible for 1 DHX reward if they have 1 mPower + // + + // Divide, handling overflow + let mut div_bonded_as_u128 = 0u128; + // note: this rounds down to the nearest integer + let _div_bonded_as_u128 = locks_first_amount_as_u128.clone().checked_div(min_bonded_dhx_daily_u128.clone()); + match _div_bonded_as_u128 { + None => { + log::error!("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + println!("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + return Err("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + }, + Some(x) => { + div_bonded_as_u128 = x; + } + } + log::info!("div_bonded_as_u128: {:?}", div_bonded_as_u128.clone()); + + // Multiply, handling overflow + let mut daily_reward_for_miner_as_u128 = 0u128; + // note: this rounds down to the nearest integer + let _daily_reward_for_miner_as_u128 = mpower_current_u128.clone().checked_mul(div_bonded_as_u128.clone()); + match _daily_reward_for_miner_as_u128 { + None => { + log::error!("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + println!("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + return Err("Unable to divide min_bonded_dhx_daily from locks_first_amount_as_u128 due to StorageOverflow"); + }, + Some(x) => { + daily_reward_for_miner_as_u128 = x; + } + } + log::info!("daily_reward_for_miner_as_u128: {:?}", daily_reward_for_miner_as_u128.clone()); + println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} daily_reward_for_miner_as_u128: {:#?}", block_number, miner_count, start_of_requested_date_millis, daily_reward_for_miner_as_u128); + + // if we have a rewards_aggregated_dhx_daily of 25.133 k DHX, then after the above manipulation + // since we're dealing with a mixture of u128 and BalanceOf so the values are more readable in the UI. + // the reward will be represented as 2.5130 f DHX (where f is femto 10^-18, i.e. 0.000_000_000_000_002_513) + // so we need to multiply it by 1_000_000_000_000_000_000 to be represented in DHX instead of femto DHX + // before storing the value. we need to do the same for the rewards accumulated value before it is stored. + // daily_reward_for_miner_as_u128 = daily_reward_for_miner_as_u128; + if let Some(_daily_reward_for_miner_as_u128) = daily_reward_for_miner_as_u128.clone().checked_mul(1_000_000_000_000_000_000u128) { + daily_reward_for_miner_as_u128 = _daily_reward_for_miner_as_u128; + } else { + log::error!("Unable to multiply daily_reward_for_miner_as_u128"); + } + + let mut daily_reward_for_miner; + let _daily_reward_for_miner = Self::convert_u128_to_balance(daily_reward_for_miner_as_u128.clone()); + match _daily_reward_for_miner { + Err(_e) => { + log::error!("Unable to convert u128 to balance for daily_reward_for_miner"); + println!("Unable to convert u128 to balance for daily_reward_for_miner"); + return Err("Unable to convert u128 to balance for daily_reward_for_miner"); + }, + Ok(ref x) => { + daily_reward_for_miner = x; + } + } + log::info!("daily_reward_for_miner: {:?}", daily_reward_for_miner.clone()); + + let mut rewards_aggregated_dhx_daily: BalanceOf = 0u32.into(); // initialize + if let Some(_rewards_aggregated_dhx_daily) = >::get(&start_of_requested_date_millis) { + rewards_aggregated_dhx_daily = _rewards_aggregated_dhx_daily; + } else { + log::error!("Unable to retrieve balance for rewards_aggregated_dhx_daily"); + } + + let rewards_aggregated_dhx_daily_as_u128; + let _rewards_aggregated_dhx_daily_as_u128 = Self::convert_balance_to_u128(rewards_aggregated_dhx_daily.clone()); + match _rewards_aggregated_dhx_daily_as_u128.clone() { + Err(_e) => { + log::error!("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); + println!("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); + return Err("Unable to convert balance to u128 for rewards_aggregated_dhx_daily_as_u128"); + }, + Ok(x) => { + rewards_aggregated_dhx_daily_as_u128 = x; + } + } + + // Add, handling overflow + let mut new_rewards_aggregated_dhx_daily_as_u128; + let _new_rewards_aggregated_dhx_daily_as_u128 = + rewards_aggregated_dhx_daily_as_u128.clone().checked_add(daily_reward_for_miner_as_u128.clone()); + match _new_rewards_aggregated_dhx_daily_as_u128 { + None => { + log::error!("Unable to add daily_reward_for_miner to rewards_aggregated_dhx_daily due to StorageOverflow"); + println!("Unable to add daily_reward_for_miner to rewards_aggregated_dhx_daily due to StorageOverflow"); + return Err("Unable to add daily_reward_for_miner to rewards_aggregated_dhx_daily due to StorageOverflow"); + }, + Some(x) => { + new_rewards_aggregated_dhx_daily_as_u128 = x; + } + } + + log::info!("new_rewards_aggregated_dhx_daily_as_u128: {:?}", new_rewards_aggregated_dhx_daily_as_u128.clone()); + println!("[eligible] block: {:#?}, miner: {:#?}, date_start: {:#?} new_rewards_aggregated_dhx_daily_as_u128: {:#?}", block_number, miner_count, start_of_requested_date_millis, new_rewards_aggregated_dhx_daily_as_u128); + + let new_rewards_aggregated_dhx_daily; + let _new_rewards_aggregated_dhx_daily = Self::convert_u128_to_balance(new_rewards_aggregated_dhx_daily_as_u128.clone()); + match _new_rewards_aggregated_dhx_daily { + Err(_e) => { + log::error!("Unable to convert u128 to balance for new_rewards_aggregated_dhx_daily"); + println!("Unable to convert u128 to balance for new_rewards_aggregated_dhx_daily"); + return Err("Unable to convert u128 to balance for new_rewards_aggregated_dhx_daily"); + }, + Ok(ref x) => { + new_rewards_aggregated_dhx_daily = x; + } + } + + // add to storage item that accumulates total rewards for all registered miners for the day + >::insert( + start_of_requested_date_millis.clone(), + new_rewards_aggregated_dhx_daily.clone(), + ); + log::info!("Added RewardsAggregatedDHXForAllMinersForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone(), new_rewards_aggregated_dhx_daily.clone()); + println!("Added RewardsAggregatedDHXForAllMinersForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone(), new_rewards_aggregated_dhx_daily.clone()); + + // add to storage item that maps the date to the registered miner and the calculated reward + // (prior to possibly reducing it so they get a proportion of the daily rewards that are available) + >::insert( + ( + start_of_requested_date_millis.clone(), + miner_public_key.clone(), + ), + daily_reward_for_miner.clone(), + ); + log::info!("Added RewardsAccumulatedDHXForMinerForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone(), daily_reward_for_miner.clone()); + println!("Added RewardsAccumulatedDHXForMinerForDate for miner {:?} {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone(), daily_reward_for_miner.clone()); + + // Store a list of all the the registered_dhx_miners that are eligible for rewards on a given date + // so we know they have been used as keys for other storage maps like `RewardsAccumulatedDHXForMinerForDate` + // for future querying + let mut rewards_eligible_miners_for_date: Vec> = vec![]; // initialize + if let Some(_rewards_eligible_miners_for_date) = >::get(start_of_requested_date_millis.clone()) { + rewards_eligible_miners_for_date = _rewards_eligible_miners_for_date; + } else { + log::warn!("Unable to retrieve rewards_eligible_miners_for_date"); + println!("Unable to retrieve rewards_eligible_miners_for_date"); + } + log::info!("Retrieved existing rewards_eligible_miners_for_date {:?} {:?}", start_of_requested_date_millis.clone(), rewards_eligible_miners_for_date.clone()); + println!("Retrieved existing rewards_eligible_miners_for_date {:?} {:?}", start_of_requested_date_millis.clone(), rewards_eligible_miners_for_date.clone()); + + log::warn!("Updating rewards_eligible_miners_for_date with miner {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone()); + println!("Updating retrieve rewards_eligible_miners_for_date with miner {:?} {:?}", start_of_requested_date_millis.clone(), miner_public_key.clone()); + + rewards_eligible_miners_for_date.push(miner_public_key.clone()); + >::insert( + start_of_requested_date_millis.clone(), + rewards_eligible_miners_for_date.clone(), + ); + log::info!("date: {:?}, miner_count: {:?}, reg_dhx_miners.len: {:?}", start_of_requested_date_millis.clone(), miner_count.clone(), reg_dhx_miners.len()); + println!("date: {:?}, miner_count: {:?}, reg_dhx_miners.len: {:?}", start_of_requested_date_millis.clone(), miner_count.clone(), reg_dhx_miners.len()); + // if last miner being iterated then reset for next day + if reg_dhx_miners.len() == miner_count { + log::info!("date: {:?}, rewards_allowance_dhx_daily: {:?}", start_of_requested_date_millis.clone(), rewards_allowance_dhx_daily.clone()); + println!("date: {:?}, rewards_allowance_dhx_daily: {:?}", start_of_requested_date_millis.clone(), rewards_allowance_dhx_daily.clone()); + + // reset to latest set by governance + >::insert(start_of_requested_date_millis.clone(), rewards_allowance_dhx_daily.clone()); + }; + + Ok(()) + } + + // Offchain workers + + /// Chooses which transaction type to send. + /// + /// This function serves mostly to showcase `StorageValue` helper + /// and local storage usage. + /// + /// Returns a type of transaction that should be produced in current run. + /// + /// TODO - figure out how to effectively use Local Storage and whether to use + /// signed or unsigned transactions + fn choose_transaction_type(block_number: T::BlockNumber) -> TransactionType { + /// A friendlier name for the error that is going to be returned in case we are in the grace + /// period. + const RECENTLY_SENT: () = (); + + // Start off by creating a reference to Local Storage value. + // Since the local storage is common for all offchain workers, it's a good practice + // to prepend your entry with the module name. + let val = StorageValueRef::persistent(b"mpow_ocw::last_send"); + // The Local Storage is persisted and shared between runs of the offchain workers, + // and offchain workers may run concurrently. We can use the `mutate` function, to + // write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set` + // low-level method of local storage API, which means that only one worker + // will be able to "acquire a lock" and send a transaction if multiple workers + // happen to be executed concurrently. + let res = val.mutate(|last_send: Result, StorageRetrievalError>| { + match last_send { + // If we already have a value in storage and the block number is recent enough + // we avoid sending another transaction at this time. + Ok(Some(block)) if block_number < block + T::GracePeriod::get() => + Err(RECENTLY_SENT), + // In every other case we attempt to acquire the lock and send a transaction. + _ => Ok(block_number), + } + }); + + // The result of `mutate` call will give us a nested `Result` type. + // The first one matches the return of the closure passed to `mutate`, i.e. + // if we return `Err` from the closure, we get an `Err` here. + // In case we return `Ok`, here we will have another (inner) `Result` that indicates + // if the value has been set to the storage correctly - i.e. if it wasn't + // written to in the meantime. + match res { + // The value has been set correctly, which means we can safely send a transaction now. + Ok(block_number) => { + TransactionType::Raw + }, + // We are in the grace period, we should not send a transaction this time. + Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None, + // We wanted to send a transaction, but failed to write the block number (acquire a + // lock). This indicates that another offchain worker that was running concurrently + // most likely executed the same logic and succeeded at writing to storage. + // Thus we don't really want to send the transaction, knowing that the other run + // already did. + Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None, + } + } + + /// A helper function to fetch the mpower + fn fetch_mpower_process(block_number: T::BlockNumber, start_of_requested_date_millis: Date) -> Result>, &'static str> { + // Make sure we don't fetch the mpower if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at_for_fetched_mpower = >::get(); + if next_unsigned_at_for_fetched_mpower > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current mpower. + // Note this call will block until response is received. + let mpower_data_vec: Vec> = Self::fetch_mpower(block_number.clone(), start_of_requested_date_millis.clone()).map_err(|_| "Failed to fetch mpower data vec")?; + + Ok(mpower_data_vec.clone()) + } + + /// A helper function to send a raw unsigned transaction to store the mpower data. + fn store_mpower_raw_unsigned(block_number: T::BlockNumber, start_of_requested_date_millis: Date, mpower_data_vec: Vec>) -> Result<(), &'static str> { + log::info!("offchain_worker - store_mpower_raw_unsigned"); + // Received mpower data is wrapped into a call to `submit_fetched_mpower_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `mpower_data_vec` as an argument. + let call = Call::submit_fetched_mpower_unsigned(block_number.clone(), start_of_requested_date_millis.clone(), mpower_data_vec.clone()); + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // + // TODO - By default unsigned transactions are disallowed, so we need to whitelist this case + // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefuly + // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // attack vectors. See validation logic docs for more details. + // + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction for submit_fetched_mpower.")?; + + Ok(()) + } + + /// A helper function to send a raw unsigned transaction to store finished fetching mpower data for use in on_initialize + fn store_finished_fetching_mpower_raw_unsigned(block_number: T::BlockNumber, fetched_mpower_data: FetchedMPowerForAccountsForDatePayloadData) -> Result<(), &'static str> { + log::info!("offchain_worker - store_finished_fetching_mpower_raw_unsigned"); + // Received mpower data is wrapped into a call to `submit_finished_fetching_mpower_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `fetched_mpower_data` as an argument. + let call = Call::submit_finished_fetching_mpower_unsigned(block_number.clone(), fetched_mpower_data.clone()); + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // + // TODO - By default unsigned transactions are disallowed, so we need to whitelist this case + // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefuly + // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // attack vectors. See validation logic docs for more details. + // + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction for submit_finished_fetching_mpower.")?; + + Ok(()) + } + + /// Fetch current mPower and return the result. + fn fetch_mpower(block_number: T::BlockNumber, start_of_requested_date_millis: Date) -> Result>, http::Error> { + log::info!("offchain_worker - fetch_mpower"); + + // TODO - below is temporarily commented out whilst we are using mock API data + + // // We want to keep the offchain worker execution time reasonable, so we set a hard-coded + // // deadline to 2s to complete the external call. + // // You can also wait idefinitely for the response, however you may still get a timeout + // // coming from the host machine. + + // // Initiate an external HTTP GET request. + // // This is using high-level wrappers from `sp_runtime`, for the low-level calls that + // // you can find in `sp_io`. The API is trying to be similar to `reqwest`, but + // // since we are running in a custom WASM execution environment we can't simply + // // import the library here. + + // // Example from Substrate + // let request = + // http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD"); + + // // Example of request we may use + // // let start_of_requested_date_millis = 1630195200000i64; + // // let url = format!("https://api.datahighway.com/data/mpower-for-date?start_of_requested_date_millis={}", start_of_requested_date_millis); + // // log::info!("Request URL: {}", url.clone()); + // // let request = + // // http::Request::get(&url); + + // // We set the deadline for sending of the request, note that awaiting response can + // // have a separate deadline. Next we send the request, before that it's also possible + // // to alter request headers or stream body content in case of non-GET requests. + // let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + + // // The request is already being processed by the host, we are free to do anything + // // else in the worker (we can send multiple concurrent requests too). + // // At some point however we probably want to check the response though, + // // so we can block current thread and wait for it to finish. + // // Note that since the request is being driven by the host, we don't have to wait + // // for the request to have it complete, we will just not read the response. + // let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + + // // Let's check the status code before we proceed to reading the response. + // if response.code != 200 { + // log::warn!("offchain_worker - Unexpected status code: {}", response.code); + // return Err(http::Error::Unknown) + // } + + // // Next we want to fully read the response body and collect it to a vector of bytes. + // // Note that the return object allows you to read the body in chunks as well + // // with a way to control the deadline. + // let body = response.body().collect::>(); + + // // Create a str slice from the body. + // let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + // log::warn!("offchain_worker - No UTF8 body"); + // http::Error::Unknown + // })?; + + // log::info!("offchain_worker - Received HTTP Body: {}", body_str.clone()); + + // FIXME - replace the below hard-coded example in future with use of the response body + // Alice public key 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d + // Bob public key 0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48 + // Charlie public key 0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22 + // + // Note: we're not currently using the `start_of_requested_date_millis` that is in the hard-coded data below, + // and instead we're using the current date temporarily using + // `received_at_date: received_date_as_millis.clone(),` + let mpower_data = r#"{ + "data": [ + { "acct_id": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", "mpower": "11", "start_of_requested_date_millis": "1630195200000" }, + { "acct_id": "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", "mpower": "12", "start_of_requested_date_millis": "1630195200000" }, + { "acct_id": "90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22", "mpower": "13", "start_of_requested_date_millis": "1630195200000" } + ] + }"#; + + let mpower_data_vec = match Self::parse_mpower_data(mpower_data, block_number.clone()) { + Some(data) => Ok(data), + None => { + log::warn!("offchain_worker - Unable to extract mpower data from the response"); + Err(http::Error::Unknown) + }, + }?; + + // log::info!("offchain_worker - Parsed mpower_data_vec: {:?}", mpower_data_vec); + + Ok(mpower_data_vec) + } + + /// Parse the mPower from the given JSON string using `lite-json`. + /// + /// Returns `None` when parsing failed or `Some(mpower_data)` when parsing is successful. + fn parse_mpower_data(mpower_data_str: &str, block_number: T::BlockNumber) -> Option>> { + // checking it works using serde_json, but cannot use in substrate as it uses std: + // https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=09eee43b3354f2a798ca4394838fdef7 + + let timestamp = >::get(); + let received_date_as_u64 = Self::convert_moment_to_u64_in_milliseconds(timestamp.clone()).ok()?; + log::info!("offchain_worker - received_date_as_u64: {:?}", received_date_as_u64.clone()); + // TODO - parse for mPower data and replace hard-coded response with output + let received_date_as_millis = Self::convert_u64_in_milliseconds_to_start_of_date(received_date_as_u64.clone()).ok()?; + + let mpower_json_data = match lite_json::parse_json(mpower_data_str) { + Err(e) => { + log::error!("Couldn't parse JSON {:?}", e); + println!("Couldn't parse JSON {:?}", e); + return None; + }, + Ok(data) => data, + }; + + // let mpower_json_data: MPowerJSONResponseData = match serde_json::from_str(mpower_data_str) { + // Err(e) => { + // println!("Couldn't parse JSON :( {:?}", e); + // return None; + // }, + // Ok(data) => data, + // }; + // log::info!("offchain_worker - mpower_json_data{:#?}", mpower_json_data); + + let mut mpower_data_vec: Vec> = vec![]; + let mpower_array = match mpower_json_data { + JsonValue::Object(obj) => { + let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("data".chars()))?; + match v { + JsonValue::Array(vec) => vec, + _ => return None, + } + }, + _ => return None, + }; + + for (i, obj) in mpower_array.into_iter().enumerate() { + let obj_acct_id = match obj.clone() { + JsonValue::Object(obj_data) => { + let (_, v) = obj_data.into_iter().find(|(k, _)| k.iter().copied().eq("acct_id".chars()))?; + match v { + JsonValue::String(val) => val, + _ => return None, + } + }, + _ => return None, + }; + + let obj_mpower = match obj.clone() { + JsonValue::Object(obj_data) => { + let (_, v) = obj_data.into_iter().find(|(k, _)| k.iter().copied().eq("mpower".chars()))?; + match v { + JsonValue::String(val) => val, + _ => return None, + } + }, + _ => return None, + }; + + log::info!("offchain_worker - obj_mpower char {:?} {:?}", i, obj_mpower.clone()); + + // Convert from `Vec` to `Vec` since we do not use String in the runtime + // e.g. converts from `['1', '2', '3']` to `123` + let obj_acct_id_str_hex: Vec = obj_acct_id.iter().map(|c| *c as u8).collect::>(); + let obj_mpower_str_hex: Vec = obj_mpower.iter().map(|c| *c as u8).collect::>(); + log::info!("offchain_worker - obj_mpower_str_hex {:?} {:?}", i, obj_mpower_str_hex.clone()); + + // Decode from hex ascii format + let obj_acct_id_str = hex::decode(obj_acct_id_str_hex.clone()).ok()?; + log::info!("offchain_worker - Decoded acct_id i public key hex as Vec {:?} {:?}", i, obj_acct_id_str.clone()); + + let mpower_u128 = Self::convert_vec_u8_to_u128(&obj_mpower_str_hex).ok()?; + log::info!("offchain_worker - mpower_u128 {:?} {:?}", i, mpower_u128.clone()); + + // let mut obj_mpower_as_u128 = 0u128; // initialize + // if let Some(_obj_mpower_as_u128) = TryInto::::try_into(obj_mpower_str.clone()).ok() { + // obj_mpower_as_u128 = _obj_mpower_as_u128; + // } else { + // log::error!("offchain_worker - Unable to convert Vec into u128"); + // return None; + // } + // log::info!("offchain_worker - obj_mpower_as_u128 {:?} {:?}", i, obj_mpower_as_u128.clone()); + + // Example only: + // Alice public key 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d + // + // Note: do not do `hex!["..."].encode(), since that will just encoding a vec, + // which will include a length prefix, but we don't want that. + // let example_acct_id_str: Vec = write_hex!["d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"].into(); + // log::info!("offchain_worker - example_acct_id_str {:?}", example_acct_id_str); + + // let mpower_as_u128: u128 = obj_mpower.clone().parse().unwrap(); // convert from string to number + + // Example of how to access the Vec string representation of the account's public key hex + // let reg_dhx_miners; + // if let Some(_reg_dhx_miners) = >::get() { + // reg_dhx_miners = _reg_dhx_miners; + // } else { + // log::error!("offchain_worker - Unable to retrieve any registered DHX Miners"); + // return None; + // } + // let first_reg_dhx_miner = ®_dhx_miners[0]; + // log::info!("offchain_worker - first_reg_dhx_miner {:?}", first_reg_dhx_miner.encode()); + + let mpower_data_elem: MPowerPayloadData = MPowerPayload { + account_id_registered_dhx_miner: obj_acct_id_str.clone(), + mpower_registered_dhx_miner: mpower_u128.clone(), + received_at_date: received_date_as_millis.clone(), + received_at_block_number: block_number.clone(), + }; + + mpower_data_vec.push(mpower_data_elem); + } + + // log::info!("offchain_worker - mpower_data_vec {:?}", mpower_data_vec); + + Some(mpower_data_vec) + } + + /// Add new mPower on-chain. + fn add_mpower(account_id: T::AccountId, start_of_requested_date_millis: Date, mpower_data_vec: Vec>) -> Option>> { + // note: AccountId as Vec is [0, 0, ... 0] since its an unsigned transaction + log::info!("offchain_worker - Processing mPower for account for date into storage: {:?}", start_of_requested_date_millis.clone()); + + for (index, mpower_data_item) in mpower_data_vec.iter().enumerate() { + log::info!("offchain_worker - Processing mPower for account for date into storage - loop: {:?} {:?} {:?} {:?}", index.clone(), mpower_data_item.account_id_registered_dhx_miner.clone(), start_of_requested_date_millis.clone(), mpower_data_item.mpower_registered_dhx_miner.clone()); + + let _set_mpower = Self::set_mpower_of_account_for_date( + mpower_data_item.account_id_registered_dhx_miner.clone(), + start_of_requested_date_millis.clone(), + mpower_data_item.mpower_registered_dhx_miner.clone(), + ); + match _set_mpower.clone() { + Err(_e) => { + log::warn!("Unable to set_mpower_of_account_for_date"); + return None; + }, + Ok(x) => { + log::info!("set set_mpower_of_account_for_date to: {:?} {:?}", mpower_data_item.account_id_registered_dhx_miner.clone(), x); + continue; + } + } + } + + Some(mpower_data_vec.clone()) + } + + /// Add new finished fetching mPower on-chain. + fn add_finished_fetching_mpower(account_id: T::AccountId, fetched_mpower_data: FetchedMPowerForAccountsForDatePayloadData) -> Option { + // note: AccountId as Vec is [0, 0, ... 0] since its an unsigned transaction + log::info!("offchain_worker - Processing finished fetching mPower for account for date into storage: {:?}", fetched_mpower_data.start_of_requested_date_millis.clone()); + + Self::set_finished_fetching_mpower_for_accounts_for_date( + fetched_mpower_data.all_miner_public_keys_fetched.clone(), + fetched_mpower_data.start_of_requested_date_millis.clone(), + fetched_mpower_data.total_mpower_fetched.clone(), + ); + + Some(fetched_mpower_data.clone()) + } + + fn validate_transaction_parameters_for_finished_fetching_mpower( + block_number: &T::BlockNumber, + fetched_mpower_data: &FetchedMPowerForAccountsForDatePayloadData, + ) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at_for_finished_fetching_mpower = >::get(); + log::info!("offchain_worker - validate_transaction_parameters_for_finished_fetching_mpower - block_number {:?}", block_number.clone()); + log::info!("offchain_worker - validate_transaction_parameters_for_finished_fetching_mpower - next_unsigned_at_for_finished_fetching_mpower {:?}", next_unsigned_at_for_finished_fetching_mpower.clone()); + // TODO - do we need to use a modified version of this at all? + if &next_unsigned_at_for_finished_fetching_mpower > block_number { + return InvalidTransaction::Stale.into() + } + // Let's make sure to reject transactions from the future. + let current_block = >::block_number(); + log::info!("offchain_worker - validate_transaction_parameters_for_finished_fetching_mpower - current_block {:?}", current_block.clone()); + if ¤t_block < block_number { + return InvalidTransaction::Future.into() + } + + // See Substrate client/transaction-pool for configuration details + ValidTransaction::with_tag_prefix("OffchainWorkerFinishedFetchingMPower") + .priority(T::UnsignedPriority::get().saturating_add(2u128 as _)) + .and_provides(next_unsigned_at_for_finished_fetching_mpower) + // TODO - do we need to use .and_requires() and provide the `previous_unsigned_at` + // to signify that we need the fetched_mpower unsigned tx to happen beforehand? + .longevity(5) + .propagate(true) + .build() + } + + fn validate_transaction_parameters_for_fetched_mpower( + block_number: &T::BlockNumber, + start_of_requested_date_millis: &Date, + new_mpower_data: &Vec>, + ) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at_for_fetched_mpower = >::get(); + log::info!("offchain_worker - validate_transaction_parameters_for_fetched_mpower - block_number {:?}", block_number.clone()); + log::info!("offchain_worker - validate_transaction_parameters_for_fetched_mpower - next_unsigned_at_for_fetched_mpower {:?}", next_unsigned_at_for_fetched_mpower.clone()); + if &next_unsigned_at_for_fetched_mpower > block_number { + return InvalidTransaction::Stale.into() + } + // Let's make sure to reject transactions from the future. + let current_block = >::block_number(); + log::info!("offchain_worker - validate_transaction_parameters_for_fetched_mpower - current_block {:?}", current_block.clone()); + if ¤t_block < block_number { + return InvalidTransaction::Future.into() + } + + // See Substrate client/transaction-pool for configuration details + ValidTransaction::with_tag_prefix("OffchainWorkerFetchedMPower") + // Set base priority and hope it's included before any other + // transactions in the pool + .priority(T::UnsignedPriority::get().saturating_add(1u128 as _)) + // This transaction does not require anything else to go before into the pool. + // In theory we could require `previous_unsigned_at` transaction to go first, + // but it's not necessary in our case. + //.and_requires() + // We set the `provides` tag to be the same as `next_unsigned_at_for_fetched_mpower`. This makes + // sure only one transaction produced after `next_unsigned_at_for_fetched_mpower` will ever + // get to the transaction pool and will end up in the block. + // We can still have multiple transactions compete for the same "spot", + // and the one with higher priority will replace other one in the pool. + .and_provides(next_unsigned_at_for_fetched_mpower) + // The transaction is only valid for next 5 blocks. After that it's + // going to be revalidated by the pool. + .longevity(5) + // It's fine to propagate that transaction to other peers, which means it can be + // created even by nodes that don't produce blocks. + // Note that sometimes it's better to keep it for yourself (if you are the block + // producer), since for instance in some schemes others may copy your solution and + // claim a reward. + .propagate(true) + .build() + } + + // check that the current date start_of_current_date_millis is at least 7 days after the provided + // start_of_requested_date_millis so there is sufficient time for the community to audit the reward eligibility, + // where the start_of_requested_date_millis refers to a past date the claimant believes they became eligible for rewards on. + // + // `CoolingDownPeriodDays` notifies when to give rewards in bulk (previously automatically, now by claiming) on the 8th day in bulk to cover the + // first say x (i.e. 7) days after they start bonding, and then on the next day after each day after that, and + // it is also used to track the unbonding period where if they stop bonding obviously they cannot be eligible + // for rewards and they have wait x (i.e. 7) days after unbonding before they can access the locked DHX tokens + // that they were bonding with, whereas: + // + // `ChallengePeriodDays` is used only to ensure you can only claim each daily rewards manually 7 days after being found eligible, + // even for the first 7 days after they start bonding) + pub fn is_more_than_challenge_period(start_of_requested_date_millis: i64) -> Result<(), DispatchError> { + let current_timestamp = >::get(); + let current_timestamp_as_u64 = Self::convert_moment_to_u64_in_milliseconds(current_timestamp.clone())?; + log::info!("current_timestamp_as_u64: {:?}", current_timestamp_as_u64.clone()); + // convert the current timestamp to the start of that day date/time + // i.e. 21 Apr @ 1420 -> 21 Apr @ 0000 + let start_of_current_date_millis = Self::convert_u64_in_milliseconds_to_start_of_date(current_timestamp_as_u64.clone())?; + + // where there are 86400000 milliseconds in a day then challenge_period_millis is + // 7 * 86400000 = 604800000 milliseconds if challenge_period_days is 7 days + // so we want to make sure `start_of_current_date_millis` - `start_of_requested_date_millis` > 604800000 + + let mut is_more_than_challenge_period = false; + if let Some(_period_millis) = start_of_current_date_millis.clone().checked_sub(start_of_requested_date_millis.clone()) { + let millis_per_day = 86400000u64; + let mut challenge_period_days = 0u64; + if let Some(_challenge_period_days) = >::get() { + challenge_period_days = _challenge_period_days; + log::info!("existing challenge_period_days: {:?}", challenge_period_days.clone()); + println!("existing challenge_period_days: {:?}", challenge_period_days.clone()); + } else { + log::error!("Unable to get challenge_period_days"); + return Err(DispatchError::Other("Unable to get challenge_period_days"));; + } + let mut challenge_period_millis = 0u64; + if let Some(_challenge_period_millis) = challenge_period_days.clone().checked_mul(millis_per_day.clone()) { + challenge_period_millis = _challenge_period_millis; + } else { + log::error!("Unable to multiply to determine challenge_period_millis"); + return Err(DispatchError::Other("Unable to multiply to determine challenge_period_millis")); + } + + let challenge_period_millis = challenge_period_days.clone() * millis_per_day.clone(); + + let mut period_millis_u64 = 0u64; // initialize + if let Some(_period_millis_u64) = TryInto::::try_into(_period_millis.clone()).ok() { + period_millis_u64 = _period_millis_u64; + } else { + log::error!("Unable to convert i32 to u64 for period_millis"); + return Err(DispatchError::Other("Unable to convert i32 to u64 for period_millis")); + } + + // log::info!("period_millis_u64: {:?}", period_millis_u64.clone()); + // println!("period_millis_u64: {:?}", period_millis_u64.clone()); + if (period_millis_u64 >= challenge_period_millis.clone()) { + is_more_than_challenge_period = true; + println!("is_more_than_challenge_period: {:?}", is_more_than_challenge_period.clone()); + return Ok(()); + } else { + return Err(DispatchError::Other("Not more than challenge period")); + } + } else { + log::error!("Unable to subtract to determine if challenge period is satisfied"); + return Err(DispatchError::Other("Unable to subtract to determine if challenge period is satisfied")); + } + } } } diff --git a/pallets/mining/rewards-allowance/src/mock.rs b/pallets/mining/rewards-allowance/src/mock.rs index b9a2ec4b4..592bdc09f 100644 --- a/pallets/mining/rewards-allowance/src/mock.rs +++ b/pallets/mining/rewards-allowance/src/mock.rs @@ -10,6 +10,7 @@ use frame_support::{ GenesisBuild, LockIdentifier, SortedMembers, + StorageMapShim, }, weights::{ IdentityFee, @@ -24,6 +25,7 @@ use frame_system::{ use pallet_democracy::{self, Conviction, Vote}; use sp_core::{ H256, + sr25519::Signature, u32_trait::{ _1, _2, @@ -37,10 +39,13 @@ use codec::{ }; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_runtime::{ - testing::Header, + testing::{Header, TestSignature, TestXt, UintAuthorityId}, traits::{ BlakeTwo256, + Extrinsic as ExtrinsicT, + IdentifyAccount, IdentityLookup, + Verify, }, Perbill, Percent, @@ -99,7 +104,7 @@ frame_support::construct_runtime!( ); // Override primitives -pub type AccountId = u128; +pub type AccountId = u64; // pub type SysEvent = frame_system::Event; pub const MILLISECS_PER_BLOCK: Moment = 4320; @@ -108,6 +113,7 @@ pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a c pub const DOLLARS: Balance = 100 * CENTS; pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; +pub const SECONDS: BlockNumber = MINUTES / 60; pub const DAYS: BlockNumber = HOURS * 24; // from Substrate pallet_democracy tests @@ -137,7 +143,7 @@ impl frame_system::Config for Test { type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -157,7 +163,7 @@ impl pallet_scheduler::Config for Test { type PalletsOrigin = OriginCaller; type Call = Call; type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; + type ScheduleOrigin = EnsureRoot; type MaxScheduledPerBlock = MaxScheduledPerBlock; type WeightInfo = (); } @@ -188,7 +194,8 @@ impl pallet_balances::Config for Test { type DustRemoval = (); type Event = (); type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; + type AccountStore = StorageMapShim, frame_system::Provider, u64, pallet_balances::AccountData>; + // type AccountStore = frame_system::Pallet; type WeightInfo = (); } parameter_types! { @@ -293,16 +300,16 @@ impl pallet_membership::Config for Test { } thread_local! { - static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); + static TEN_TO_FOURTEEN: RefCell> = RefCell::new(vec![10,11,12,13,14]); } pub struct TenToFourteen; -impl SortedMembers for TenToFourteen { - fn sorted_members() -> Vec { +impl SortedMembers for TenToFourteen { + fn sorted_members() -> Vec { TEN_TO_FOURTEEN.with(|v| v.borrow().clone()) } #[cfg(feature = "runtime-benchmarks")] - fn add(new: &u128) { + fn add(new: &u64) { TEN_TO_FOURTEEN.with(|v| { let mut members = v.borrow_mut(); members.push(*new); @@ -342,8 +349,8 @@ parameter_types! { impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; type Currency = Balances; - type ApproveOrigin = EnsureRoot; - type RejectOrigin = EnsureRoot; + type ApproveOrigin = EnsureRoot; + type RejectOrigin = EnsureRoot; type Event = (); type OnSlash = (); type ProposalBond = ProposalBond; @@ -438,9 +445,52 @@ impl pallet_democracy::Config for Test { type MaxProposals = MaxProposals; } +type Extrinsic = TestXt; +// type AccountId = <::Signer as IdentifyAccount>::AccountId; + +impl frame_system::offchain::SigningTypes for Test { + type Public = UintAuthorityId; // ::Signer; + // type Public = u64; + type Signature = TestSignature; // Signature; +} + +impl frame_system::offchain::SendTransactionTypes for Test +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + +impl frame_system::offchain::CreateSignedTransaction for Test +where + Call: From, +{ + fn create_transaction>( + call: Call, + _public: UintAuthorityId, + // _public: ::Signer, + // _public: u64, + _account: AccountId, + nonce: Index, + ) -> Option<(Call, ::SignaturePayload)> { + Some((call, (nonce.into(), ()))) + } +} + +parameter_types! { + pub const GracePeriod: BlockNumber = 10 * SECONDS; // 1 * MINUTES; + pub const UnsignedInterval: BlockNumber = 10 * SECONDS; // 1 * MINUTES; + pub const UnsignedPriority: BlockNumber = 10 * SECONDS; // 1 * MINUTES; +} + impl MiningRewardsAllowanceConfig for Test { - type Event = (); + type Call = Call; type Currency = Balances; + type Event = (); + type GracePeriod = GracePeriod; + type UnsignedInterval = UnsignedInterval; + type UnsignedPriority = UnsignedPriority; } pub type SysEvent = frame_system::Event; @@ -478,6 +528,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { rewards_allowance_dhx_daily: FIVE_THOUSAND_DHX, // 5000 DHX rewards_allowance_dhx_for_date_remaining: Default::default(), rewards_allowance_dhx_for_date_remaining_distributed: Default::default(), + rewards_allowance_dhx_for_miner_for_date_remaining_distributed: Default::default(), rewards_multiplier_paused: false, rewards_multiplier_reset: false, rewards_multiplier_default_change: 10u32, @@ -495,15 +546,17 @@ pub fn new_test_ext() -> sp_io::TestExternalities { // get_account_id_from_seed::("Charlie"), // ], registered_dhx_miners: Default::default(), + rewards_eligible_miners_for_date: Default::default(), rewards_aggregated_dhx_for_all_miners_for_date: Default::default(), rewards_accumulated_dhx_for_miner_for_date: Default::default(), min_bonded_dhx_daily: TEN_DHX, // 10 DHX min_bonded_dhx_daily_default: TEN_DHX, // 10 DHX - min_mpower_daily: 5u128, - min_mpower_daily_default: 5u128, - cooling_off_period_days: 7u32, + min_mpower_daily: 1u128, + min_mpower_daily_default: 1u128, + challenge_period_days: 7u64, + cooling_down_period_days: 7u32, // Note: i'm not sure how to mock Alice, just set in implementation at genesis - // cooling_off_period_days_remaining: vec![ + // cooling_down_period_days_remaining: vec![ // ( // get_account_id_from_seed::("Alice"), // ( @@ -513,7 +566,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { // ), // ), // ], - cooling_off_period_days_remaining: Default::default(), + cooling_down_period_days_remaining: Default::default(), }, &mut t ) diff --git a/pallets/mining/rewards-allowance/src/tests.rs b/pallets/mining/rewards-allowance/src/tests.rs index 70f0f0e4a..b23f303e9 100644 --- a/pallets/mining/rewards-allowance/src/tests.rs +++ b/pallets/mining/rewards-allowance/src/tests.rs @@ -1,10 +1,13 @@ use super::{Call, Event, *}; use crate::{mock::*, Error}; pub use mock::{INIT_DAO_BALANCE_DHX, TOTAL_SUPPLY_DHX, TEN_DHX, FIVE_THOUSAND_DHX}; -use codec::Encode; +use codec::{ + Decode, + Encode, +}; use frame_support::{assert_noop, assert_ok, weights::{DispatchClass, DispatchInfo, GetDispatchInfo}, - traits::{OnFinalize, OnInitialize}, + traits::{OnFinalize, OnInitialize, OffchainWorker}, }; use frame_system::{self, AccountInfo, EventRecord, Phase, RawOrigin}; use pallet_balances::{self, BalanceLock, Reasons}; @@ -14,6 +17,7 @@ use sp_core::{ Hasher, // so we may use BlakeTwo256::hash }; use sp_runtime::{ + DispatchError, traits::{BlakeTwo256}, }; @@ -25,64 +29,105 @@ const THIRTY_DHX: u128 = 30_000_000_000_000_000_000_u128; // 30 const TWENTY_DHX: u128 = 20_000_000_000_000_000_000_u128; // 20 const TWO_DHX: u128 = 2_000_000_000_000_000_000_u128; // 2 +// TODO - try doing the following if necessary https://stackoverflow.com/a/58009990/3208553 +// Note: we have to use `&[u8] = &` instead of `Vec = vec!` otherwise we get error `allocations are not allowed in constants` +const ALICE_PUBLIC_KEY: &[u8] = &[212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125]; +const BOB_PUBLIC_KEY: &[u8] = &[142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147, 201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72]; +const CHARLIE_PUBLIC_KEY: &[u8] = &[144, 181, 171, 32, 92, 105, 116, 201, 234, 132, 27, 230, 136, 134, 70, 51, 220, 156, 168, 163, 87, 132, 62, 234, 207, 35, 20, 100, 153, 101, 254, 34]; + #[test] -// ignore this test until the FIXME is resolved -#[ignore] fn it_sets_rewards_allowance_with_genesis_defaults_automatically_in_on_finalize_if_not_already_set_for_today() { new_test_ext().execute_with(|| { - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(1))); - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(2))); - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(3))); + assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miners( + Origin::root(), + vec![CHARLIE_PUBLIC_KEY.into(), BOB_PUBLIC_KEY.into(), ALICE_PUBLIC_KEY.into()], + )); // 27th August 2021 @ ~7am is 1630049371000 // where milliseconds/day 86400000 // 27th August 2021 @ 12am is 1630022400000 (start of day) Timestamp::set_timestamp(1630049371000u64); - MiningRewardsAllowanceTestModule::on_initialize(1); + // Note: we start at block 2 since we early exit from block 1 because the timestamp is yet + MiningRewardsAllowanceTestModule::on_initialize(2); + // MiningRewardsAllowanceTestModule::offchain_worker(2); - // FIXME - why doesn't this work and use the defaults that we have set in the genesis config? - // i've had to add a function `set_rewards_allowance_dhx_daily` to set this instead + // This wasn't using the defaults set in genesis config previously because we weren't starting at block 2 assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_daily(), Some(FIVE_THOUSAND_DHX)); }) } #[test] -// Note: if we remove `cooling_off_period_days_remaining.0 != start_of_requested_date_millis.clone() &&` +// Note: if we remove `cooling_down_period_days_remaining.0 != start_of_requested_date_millis.clone() &&` // four times from the implementation, then all this happens on the same day so we'd need to use the // same timestamp for all the blocks and tests below. -fn it_distributes_rewards_automatically_in_on_finalize_for_default_amount() { +fn it_calcs_rewards_allowance_using_on_initialize_with_claim_using_extrinsic_for_default_amount() { new_test_ext().execute_with(|| { let amount_mpower_each_miner = 5u128; - let min_mpower_daily = 5u128; + let min_mpower_daily = 1u128; setup_min_mpower_daily(min_mpower_daily); - let r = setup_bonding(NORMAL_AMOUNT, TEN_DHX); + let referendum_index = setup_bonding(NORMAL_AMOUNT, TEN_DHX); setup_treasury_balance(); - - setup_multiplier(); - - distribute_rewards(NORMAL_AMOUNT, amount_mpower_each_miner, r); + setup_multiplier(90u32); + setup_registered_dhx_miners(vec![CHARLIE_PUBLIC_KEY.clone().into(), BOB_PUBLIC_KEY.clone().into(), ALICE_PUBLIC_KEY.clone().into()]); + // TODO - don't need this unless testing unbonding period + // setup_cooling_down_period_days(7_u32); + setup_rewards_allowance_dhx_daily(FIVE_THOUSAND_DHX); + claim_eligible_rewards_after_challenge_period_if_suffient_bonded(NORMAL_AMOUNT, amount_mpower_each_miner.clone()); }) } #[test] -fn it_distributes_rewards_automatically_in_on_finalize_for_large_amount() { +fn it_calcs_rewards_allowance_using_on_initialize_with_claim_using_extrinsic_for_large_amount() { new_test_ext().execute_with(|| { let amount_mpower_each_miner = 5u128; - let min_mpower_daily = 5u128; + let min_mpower_daily = 1u128; setup_min_mpower_daily(min_mpower_daily); - let r = setup_bonding(LARGE_AMOUNT_DHX, TEN_DHX); + let referendum_index = setup_bonding(LARGE_AMOUNT_DHX, TEN_DHX); setup_treasury_balance(); + setup_multiplier(90u32); + setup_registered_dhx_miners(vec![CHARLIE_PUBLIC_KEY.clone().into(), BOB_PUBLIC_KEY.clone().into(), ALICE_PUBLIC_KEY.clone().into()]); + // TODO - don't need this unless testing unbonding period + // setup_cooling_down_period_days(7_u32); + setup_rewards_allowance_dhx_daily(FIVE_THOUSAND_DHX); + claim_eligible_rewards_after_challenge_period_if_suffient_bonded(LARGE_AMOUNT_DHX, amount_mpower_each_miner.clone()); + }) +} + +#[test] +fn it_changes_rewards_multiplier_every_period_day_and_resets_remaining_days() { + new_test_ext().execute_with(|| { + // TODO - do we need all these setup to run this test? + let amount_mpower_each_miner = 5u128; + let min_mpower_daily = 1u128; - setup_multiplier(); + setup_min_mpower_daily(min_mpower_daily); - distribute_rewards(LARGE_AMOUNT_DHX, amount_mpower_each_miner, r); + let referendum_index = setup_bonding(LARGE_AMOUNT_DHX, TEN_DHX); + + setup_treasury_balance(); + setup_multiplier(2u32); + // TODO - this function doesn't exist, do we need this function? + // setup_multiplier_period_days_remaining(2u32); + setup_registered_dhx_miners(vec![CHARLIE_PUBLIC_KEY.clone().into(), BOB_PUBLIC_KEY.clone().into(), ALICE_PUBLIC_KEY.clone().into()]); + // TODO - this function doesn't exist, do we need this function? + // setup_cooling_down_period_days_remaining(0_u32); + setup_rewards_allowance_dhx_daily(FIVE_THOUSAND_DHX); + + // check that rewards multiplier increases by multiplier every period days and that days total and remaining are reset + check_rewards_double_each_multiplier_period(amount_mpower_each_miner.clone()); + + setup_cooling_down_period_days(2_u32); + // check that after the multiplier doubles, they are no longer eligible to receive the rewards + // if they have the same amount bonded (since they’d then need twice the amount bonded as ratio changes from 10:1 to 20:1), + // even if they have sufficient mpower + check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_bonded(LARGE_AMOUNT_DHX, amount_mpower_each_miner.clone(), referendum_index.clone()); }) } @@ -97,12 +142,12 @@ fn it_sets_rewards_allowance_with_timestamp() { Timestamp::set_timestamp(1630049371000u64); assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_allowance_dhx_daily( - Origin::signed(0), + Origin::root(), FIVE_THOUSAND_DHX )); assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_allowance_dhx_for_date_remaining( - Origin::signed(0), + Origin::root(), FIVE_THOUSAND_DHX, 1630049371000 )); @@ -113,7 +158,7 @@ fn it_sets_rewards_allowance_with_timestamp() { assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630022400000), Some(FIVE_THOUSAND_DHX)); assert_ok!(MiningRewardsAllowanceTestModule::change_rewards_allowance_dhx_for_date_remaining( - Origin::signed(0), + Origin::root(), FIVE_HUNDRED_DHX, 1630049371000, 0 @@ -125,7 +170,7 @@ fn it_sets_rewards_allowance_with_timestamp() { assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630022400000), Some(4_500_000_000_000_000_000_000u128)); assert_ok!(MiningRewardsAllowanceTestModule::change_rewards_allowance_dhx_for_date_remaining( - Origin::signed(0), + Origin::root(), TWO_THOUSAND_DHX, 1630049371000, 1 @@ -148,9 +193,10 @@ fn setup_preimage() { assert_eq!(Balances::free_balance(1), 1_000_000_000_000_000); // register pre-image for upcoming proposal let encoded_proposal_preimage = vec![0; 500]; + // TODO - should this be account_1_account_id instead of `1`, and likewise in the rest of this test? match Democracy::note_preimage(Origin::signed(1), encoded_proposal_preimage.clone()) { Ok(_) => (), - Err(x) if x == Error::::DuplicatePreimage.into() => (), + // Err(x) if x == Error::::DuplicatePreimage.into() => (), Err(x) => panic!("Democracy::note_preimage error {:?}", x), } System::set_block_number(1); @@ -275,82 +321,99 @@ fn setup_preimage() { fn it_sets_min_mpower_daily() { new_test_ext().execute_with(|| { assert_ok!(MiningRewardsAllowanceTestModule::set_min_mpower_daily( - Origin::signed(1), - 5u128, + Origin::root(), + 1u128, )); }); } #[test] -#[ignore] fn it_allows_us_to_retrieve_genesis_value_for_min_mpower_daily() { new_test_ext().execute_with(|| { - // FIXME - why doesn't it set the values we added in the chain_spec.rs at genesis - // https://matrix.to/#/!HzySYSaIhtyWrwiwEV:matrix.org/$163424903366086IiiUH:matrix.org?via=matrix.parity.io&via=corepaper.org&via=matrix.org - MiningRewardsAllowanceTestModule::on_initialize(1); - assert_eq!(MiningRewardsAllowanceTestModule::min_mpower_daily(), Some(5u128)); + // Note: we start at block 2 since we early exit from block 1 because the timestamp is yet + MiningRewardsAllowanceTestModule::on_initialize(2); + assert_eq!(MiningRewardsAllowanceTestModule::min_mpower_daily(), Some(1u128)); }); } -fn distribute_rewards(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(1))); - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(2))); - assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miner(Origin::signed(3))); +#[test] +fn it_converts_vec_u8_to_u128() { + new_test_ext().execute_with(|| { + // my snippet: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=69915086c8faa9de69301ee86e914bed + let hex_literal = vec![48, 51, 48, 48, 49, 50, 48, 51, 57, 48, 48]; + assert_eq!(MiningRewardsAllowanceTestModule::convert_vec_u8_to_u128(&hex_literal), Ok(3001203900u128)); + }); +} - assert_ok!(MiningRewardsAllowanceTestModule::set_cooling_off_period_days( - Origin::signed(1), - 1_u32, // debug quickly for testing - )); - assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_allowance_dhx_daily( - Origin::signed(1), - FIVE_THOUSAND_DHX, - )); +#[test] +// note: we're using a challenge period of 7 days in this test +fn it_checks_if_is_more_than_challenge_period() { + new_test_ext().execute_with(|| { + // where milliseconds/day 86400000 - assert_eq!(MiningRewardsAllowanceTestModule::registered_dhx_miners(), Some(vec![1, 2, 3])); - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days(), Some(1)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_daily(), Some(FIVE_THOUSAND_DHX)); + assert_ok!(MiningRewardsAllowanceTestModule::set_challenge_period_days( + Origin::root(), + 7u64, + )); - check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone()); + // 1st Dec 2021 @ 12am is 1638316800000 (start of day) + let start_of_requested_date_millis: i64 = 1638316800000i64; - // check that rewards multiplier increases by multiplier every period days and that days total and remaining are reset - check_rewards_double_each_multiplier_period(amount_mpower_each_miner.clone()); + // 7th Dec 2021 @ 12am is 1638835200000 (start of day) + let current_timestamp_6_days_later = 1638835200000u64; + Timestamp::set_timestamp(current_timestamp_6_days_later); + assert_eq!(MiningRewardsAllowanceTestModule::is_more_than_challenge_period(start_of_requested_date_millis), Err(DispatchError::Other("Not more than challenge period"))); - // check that after the multiplier doubles, they are no longer eligible to receive the rewards - // if they have the same amount bonded (since they’d then need twice the amount bonded as ratio changes from 10:1 to 20:1), - // even if they have sufficient mpower - check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_bonded(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); + // // 8th Dec 2021 @ 12am is 1638921600000 (start of day) + // let current_timestamp_7_days_later = 1638921600000u64; + // Timestamp::set_timestamp(current_timestamp_7_days_later); + // assert_eq!(MiningRewardsAllowanceTestModule::is_more_than_challenge_period(start_of_requested_date_millis), Ok(())); + }); } fn setup_min_mpower_daily(min_mpower_daily: u128) { assert_ok!(MiningRewardsAllowanceTestModule::set_min_mpower_daily( - Origin::signed(1), + Origin::root(), min_mpower_daily.clone(), )); assert_eq!(MiningRewardsAllowanceTestModule::min_mpower_daily(), Some(min_mpower_daily.clone())); } // we have to get their mpower the day before we check if they are eligible incase there are delays in getting the off-chain data -fn change_mpower_for_each_miner(amount_mpower_each_miner: u128, next_start_date: i64) { - assert_ok!(MiningRewardsAllowanceTestModule::set_mpower_of_account_for_date(1, amount_mpower_each_miner.clone(), next_start_date)); - assert_ok!(MiningRewardsAllowanceTestModule::set_mpower_of_account_for_date(2, amount_mpower_each_miner.clone(), next_start_date)); - assert_ok!(MiningRewardsAllowanceTestModule::set_mpower_of_account_for_date(3, amount_mpower_each_miner.clone(), next_start_date)); +fn change_mpower_for_each_miner(amount_mpower_each_miner: u128, start_date: i64) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + // https://aws1.discourse-cdn.com/business5/uploads/rust_lang/original/3X/9/0/909baa7e3d9569489b07c791ca76f2223bd7bac2.webp + assert_ok!(MiningRewardsAllowanceTestModule::change_mpower_of_account_for_date(Origin::root(), account_1_public_key.clone(), start_date.clone(), amount_mpower_each_miner.clone())); + assert_ok!(MiningRewardsAllowanceTestModule::change_mpower_of_account_for_date(Origin::root(), account_2_public_key.clone(), start_date.clone(), amount_mpower_each_miner.clone())); + assert_ok!(MiningRewardsAllowanceTestModule::change_mpower_of_account_for_date(Origin::root(), account_3_public_key.clone(), start_date.clone(), amount_mpower_each_miner.clone())); assert_eq!( - MiningRewardsAllowanceTestModule::mpower_of_account_for_date((next_start_date, 1)), + MiningRewardsAllowanceTestModule::mpower_of_account_for_date((start_date, account_1_public_key.clone())), Some(amount_mpower_each_miner.clone()) ); assert_eq!( - MiningRewardsAllowanceTestModule::mpower_of_account_for_date((next_start_date, 2)), + MiningRewardsAllowanceTestModule::mpower_of_account_for_date((start_date, account_2_public_key.clone())), Some(amount_mpower_each_miner.clone()) ); assert_eq!( - MiningRewardsAllowanceTestModule::mpower_of_account_for_date((next_start_date, 3)), + MiningRewardsAllowanceTestModule::mpower_of_account_for_date((start_date, account_3_public_key.clone())), Some(amount_mpower_each_miner.clone()) ); } fn setup_bonding(amount_bonded_each_miner: u128, min_bonding_dhx_daily: u128) -> u32 { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + let account_1_account_id: u64 = Decode::decode(&mut account_1_public_key.as_slice().clone()).ok().unwrap(); + let account_2_account_id: u64 = Decode::decode(&mut account_2_public_key.as_slice().clone()).ok().unwrap(); + let account_3_account_id: u64 = Decode::decode(&mut account_3_public_key.as_slice().clone()).ok().unwrap(); + assert_ok!(MiningRewardsAllowanceTestModule::set_min_bonded_dhx_daily( - Origin::signed(1), + Origin::root(), min_bonding_dhx_daily.clone(), )); assert_eq!(MiningRewardsAllowanceTestModule::min_bonded_dhx_daily(), Some(min_bonding_dhx_daily.clone())); @@ -363,15 +426,15 @@ fn setup_bonding(amount_bonded_each_miner: u128, min_bonding_dhx_daily: u128) -> // in this test we'll test that it distributes rewards when each of their account balances are very large // (i.e. a third of the total supply) ONE_THIRD_OF_TOTAL_SUPPLY_DHX - assert_ok!(Balances::set_balance(Origin::root(), 1, amount_bonded_each_miner, 0)); - assert_ok!(Balances::set_balance(Origin::root(), 2, amount_bonded_each_miner, 0)); - assert_ok!(Balances::set_balance(Origin::root(), 3, amount_bonded_each_miner, 0)); + assert_ok!(Balances::set_balance(Origin::root(), account_1_account_id.clone(), amount_bonded_each_miner, 0)); + assert_ok!(Balances::set_balance(Origin::root(), account_2_account_id.clone(), amount_bonded_each_miner, 0)); + assert_ok!(Balances::set_balance(Origin::root(), account_3_account_id.clone(), amount_bonded_each_miner, 0)); - assert_eq!(Balances::free_balance(&1), amount_bonded_each_miner); - assert_eq!(Balances::free_balance(&2), amount_bonded_each_miner); - assert_eq!(Balances::free_balance(&3), amount_bonded_each_miner); + assert_eq!(Balances::free_balance(&account_1_account_id.clone()), amount_bonded_each_miner); + assert_eq!(Balances::free_balance(&account_2_account_id.clone()), amount_bonded_each_miner); + assert_eq!(Balances::free_balance(&account_3_account_id.clone()), amount_bonded_each_miner); - assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(Balances::reserved_balance(&account_1_account_id.clone()), 0); let pre_image_hash = BlakeTwo256::hash(b"test"); // params: end block, proposal hash, threshold, delay @@ -382,25 +445,57 @@ fn setup_bonding(amount_bonded_each_miner: u128, min_bonding_dhx_daily: u128) -> return r; } -fn setup_multiplier() { +fn setup_multiplier(days: u32) { assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_multiplier_operation( - Origin::signed(0), + Origin::root(), 1u8, )); // in the tests we want the period between each 10:1, 20:1 cycle to be just 2 days instead of 90 days // since we don't want to wait so long to check that it changes each cycle in the tests assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_multiplier_default_period_days( - Origin::signed(0), - 2u32, + Origin::root(), + days.clone(), )); assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_multiplier_next_period_days( - Origin::signed(0), - 2u32, + Origin::root(), + days.clone(), )); } +fn setup_registered_dhx_miners(registered_dhx_miners: Vec>) { + assert_ok!(MiningRewardsAllowanceTestModule::set_registered_dhx_miners( + Origin::root(), + registered_dhx_miners.clone(), + )); + + assert_eq!( + MiningRewardsAllowanceTestModule::registered_dhx_miners(), + Some( + vec![ALICE_PUBLIC_KEY.clone().into(), BOB_PUBLIC_KEY.clone().into(), CHARLIE_PUBLIC_KEY.clone().into()] + ), + ); +} + +fn setup_cooling_down_period_days(cooling_down_period_days: u32) { + assert_ok!(MiningRewardsAllowanceTestModule::set_cooling_down_period_days( + Origin::root(), + cooling_down_period_days.clone(), + )); + + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days(), Some(cooling_down_period_days.clone())); +} + +fn setup_rewards_allowance_dhx_daily(rewards_allowance_dhx_daily: u128) { + assert_ok!(MiningRewardsAllowanceTestModule::set_rewards_allowance_dhx_daily( + Origin::root(), + rewards_allowance_dhx_daily.clone(), + )); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_daily(), Some(rewards_allowance_dhx_daily.clone())); +} + fn setup_treasury_balance() { // set the balance of the treasury so it distributes rewards Balances::set_balance(Origin::root(), Treasury::account_id(), INIT_DAO_BALANCE_DHX, 0); @@ -408,10 +503,18 @@ fn setup_treasury_balance() { } fn bond_each_miner_by_voting_for_referendum(amount_bonded_each_miner: u128, referendum_index: u32) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + let account_1_account_id: u64 = Decode::decode(&mut account_1_public_key.as_slice().clone()).ok().unwrap(); + let account_2_account_id: u64 = Decode::decode(&mut account_2_public_key.as_slice().clone()).ok().unwrap(); + let account_3_account_id: u64 = Decode::decode(&mut account_3_public_key.as_slice().clone()).ok().unwrap(); + // we're actually bonding with their entire account balance - let b1 = Balances::free_balance(&1); - let b2 = Balances::free_balance(&2); - let b3 = Balances::free_balance(&3); + let b1 = Balances::free_balance(&account_1_account_id.clone()); + let b2 = Balances::free_balance(&account_2_account_id.clone()); + let b3 = Balances::free_balance(&account_3_account_id.clone()); // lock the whole balance of account 1, 2, and 3 in voting let v1a1 = AccountVote::Standard { vote: AYE, balance: b1.clone() }; @@ -419,25 +522,25 @@ fn bond_each_miner_by_voting_for_referendum(amount_bonded_each_miner: u128, refe let v1a3 = AccountVote::Standard { vote: AYE, balance: b3.clone() }; // vote on referenda using time-lock voting with a conviction to scale the vote power // note: second parameter is the referendum index being voted on - assert_ok!(Democracy::vote(Origin::signed(1), referendum_index, v1a1)); - assert_ok!(Democracy::vote(Origin::signed(2), referendum_index, v1a2)); - assert_ok!(Democracy::vote(Origin::signed(3), referendum_index, v1a3)); + assert_ok!(Democracy::vote(Origin::signed(account_1_account_id.clone()), referendum_index, v1a1)); + assert_ok!(Democracy::vote(Origin::signed(account_2_account_id.clone()), referendum_index, v1a2)); + assert_ok!(Democracy::vote(Origin::signed(account_3_account_id.clone()), referendum_index, v1a3)); - assert_eq!(Balances::locks(1)[0], + assert_eq!(Balances::locks(account_1_account_id.clone())[0], BalanceLock { id: [100, 101, 109, 111, 99, 114, 97, 99], amount: b1.clone(), reasons: Reasons::Misc } ); - assert_eq!(Balances::locks(2)[0], + assert_eq!(Balances::locks(account_2_account_id.clone())[0], BalanceLock { id: [100, 101, 109, 111, 99, 114, 97, 99], amount: b2.clone(), reasons: Reasons::Misc } ); - assert_eq!(Balances::locks(3)[0], + assert_eq!(Balances::locks(account_3_account_id.clone())[0], BalanceLock { id: [100, 101, 109, 111, 99, 114, 97, 99], amount: b3.clone(), @@ -447,27 +550,46 @@ fn bond_each_miner_by_voting_for_referendum(amount_bonded_each_miner: u128, refe } fn unbond_each_miner_by_removing_their_referendum_vote(referendum_index: u32) { - // remove the votes and then unlock for each account + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + let account_1_account_id: u64 = Decode::decode(&mut account_1_public_key.as_slice().clone()).ok().unwrap(); + let account_2_account_id: u64 = Decode::decode(&mut account_2_public_key.as_slice().clone()).ok().unwrap(); + let account_3_account_id: u64 = Decode::decode(&mut account_3_public_key.as_slice().clone()).ok().unwrap(); + + // remove the votes and then unlock for each account // note: `remove_vote` must be done before `unlock` - assert_ok!(Democracy::remove_vote(Origin::signed(1), referendum_index)); - assert_ok!(Democracy::remove_vote(Origin::signed(2), referendum_index)); - assert_ok!(Democracy::remove_vote(Origin::signed(3), referendum_index)); + assert_ok!(Democracy::remove_vote(Origin::signed(account_1_account_id.clone()), referendum_index)); + assert_ok!(Democracy::remove_vote(Origin::signed(account_2_account_id.clone()), referendum_index)); + assert_ok!(Democracy::remove_vote(Origin::signed(account_3_account_id.clone()), referendum_index)); // we removed their votes assert_eq!(Democracy::referendum_status(referendum_index).unwrap().tally, Tally { ayes: 0, nays: 0, turnout: 0 }); - assert_ok!(Democracy::unlock(Origin::signed(1), 1)); - assert_ok!(Democracy::unlock(Origin::signed(2), 2)); - assert_ok!(Democracy::unlock(Origin::signed(3), 3)); + assert_ok!(Democracy::unlock(Origin::signed(account_1_account_id.clone()), account_1_account_id.clone())); + assert_ok!(Democracy::unlock(Origin::signed(account_2_account_id.clone()), account_2_account_id.clone())); + assert_ok!(Democracy::unlock(Origin::signed(account_3_account_id.clone()), account_3_account_id.clone())); // check that all accounts are unlocked - assert_eq!(Balances::locks(1), vec![]); - assert_eq!(Balances::locks(2), vec![]); - assert_eq!(Balances::locks(3), vec![]); + assert_eq!(Balances::locks(account_1_account_id.clone()), vec![]); + assert_eq!(Balances::locks(account_2_account_id.clone()), vec![]); + assert_eq!(Balances::locks(account_3_account_id.clone()), vec![]); } -fn check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128) { +fn claim_eligible_rewards_after_challenge_period_if_suffient_bonded(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + let account_1_account_id: u64 = Decode::decode(&mut account_1_public_key.as_slice().clone()).ok().unwrap(); + let account_2_account_id: u64 = Decode::decode(&mut account_2_public_key.as_slice().clone()).ok().unwrap(); + let account_3_account_id: u64 = Decode::decode(&mut account_3_public_key.as_slice().clone()).ok().unwrap(); + // since the timestamp is 0 (corresponds to 1970-01-01) at block number #1, we early exit from on_initialize in // that block in the implementation and do not set any storage values associated with the date until block #2. // in the tests we could set the timestamp before we run on_initialize(1), but that wouldn't reflect reality. + + // Note: we early exit from on_initialize and on_finalize in the the implementation since timestamp is 0 + // Timestamp::set_timestamp(0u64); MiningRewardsAllowanceTestModule::on_initialize(1); // IMPORTANT: if we don't set the mpower for each miner for the current date beforehand, we won't be able to accumulate their rewards @@ -480,7 +602,7 @@ fn check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount // 27th August 2021 @ 12am is 1630022400000 (start of day) Timestamp::set_timestamp(1630049371000u64); MiningRewardsAllowanceTestModule::on_initialize(2); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1630022400000, 2u32, 2u32))); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1630022400000, 90u32, 90u32))); // System::on_initialize(2); // System::on_finalize(2); // System::set_block_number(2); @@ -488,6 +610,29 @@ fn check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630022400000), Some(FIVE_THOUSAND_DHX)); assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630022400000), Some(false)); + // they have at least the min. bonded so they should be eligible for accumulating and aggregating rewards each day + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630022400000, account_1_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630022400000, account_2_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630022400000, account_3_public_key.clone())), Some(amount_bonded_each_miner)); + + // i.e. for example, if locked is 25_133_000_000_000_000_000_000u128 (NORMAL_AMOUNT), which is 25,133 DHX, + // then with 10:1 each of the 3x accounts get 2513.3 DHX, which is ~7538.9 DHX combined + // or 33_333_333_333_000_000_000_000_000u128 (LARGE_AMOUNT_DHX), + // but the results are rounded to the nearest integer so it would be 2513 DHX, not 2513.3 DHX + if amount_bonded_each_miner.clone() == NORMAL_AMOUNT { + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630022400000), Some(37_695_000_000_000_000_000_000u128)); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_1_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_2_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_3_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + } else if amount_bonded_each_miner.clone() == LARGE_AMOUNT_DHX { + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630022400000), Some(49_999_995_000_000_000_000_000_000u128)); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_1_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_2_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630022400000, account_3_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + } + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1635379200000i64); // https://www.epochconverter.com/ @@ -496,16 +641,34 @@ fn check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount // 28th August 2021 @ 12am is 1635379200000 (start of day) Timestamp::set_timestamp(1635406274000u64); MiningRewardsAllowanceTestModule::on_initialize(3); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1635379200000, 2u32, 1u32))); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1635379200000, 90u32, 89u32))); // check that on_initialize has populated this storage value automatically for the start of the current date - // still cooling off so no rewards distributed on this date + // still cooling down so no rewards distributed on this date assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1635379200000), Some(FIVE_THOUSAND_DHX)); assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1635379200000), Some(false)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, 1)), Some(amount_bonded_each_miner)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, 2)), Some(amount_bonded_each_miner)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, 3)), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, account_1_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, account_2_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1635379200000, account_3_public_key.clone())), Some(amount_bonded_each_miner)); + + // i.e. for example, if locked is 25_133_000_000_000_000_000_000u128 (NORMAL_AMOUNT), which is 25,133 DHX, + // then with 10:1 each of the 3x accounts get 2513.3 DHX, which is ~7538.9 DHX combined + // or 33_333_333_333_000_000_000_000_000u128 (LARGE_AMOUNT_DHX), + // but the results are rounded to the nearest integer so it would be 2513 DHX, not 2513.3 DHX + if amount_bonded_each_miner.clone() == NORMAL_AMOUNT { + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1635379200000), Some(37_695_000_000_000_000_000_000u128)); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_1_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_2_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_3_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + } else if amount_bonded_each_miner.clone() == LARGE_AMOUNT_DHX { + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1635379200000), Some(49_999_995_000_000_000_000_000_000u128)); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_1_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_2_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1635379200000, account_3_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + } change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630195200000i64); @@ -515,79 +678,161 @@ fn check_eligible_for_rewards_after_cooling_off_period_if_suffient_bonded(amount MiningRewardsAllowanceTestModule::on_initialize(4); // a day before we start the new multiplier period and change from 10:1 to 20:1 since no more days remaining - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1630195200000, 2u32, 0u32))); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630022400000, 1630195200000, 90u32, 88u32))); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, 1)), Some(amount_bonded_each_miner)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, 2)), Some(amount_bonded_each_miner)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, 3)), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, account_1_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, account_2_public_key.clone())), Some(amount_bonded_each_miner)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630195200000, account_3_public_key.clone())), Some(amount_bonded_each_miner)); // i.e. for example, if locked is 25_133_000_000_000_000_000_000u128 (NORMAL_AMOUNT), which is 25,133 DHX, // then with 10:1 each of the 3x accounts get 2513.3 DHX, which is ~7538.9 DHX combined // or 33_333_333_333_000_000_000_000_000u128 (LARGE_AMOUNT_DHX), // but the results are rounded to the nearest integer so it would be 2513 DHX, not 2513.3 DHX if amount_bonded_each_miner.clone() == NORMAL_AMOUNT { - assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630195200000), Some(7_539_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630195200000), Some(37_695_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 1)), Some(2_513_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 2)), Some(2_513_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 3)), Some(2_513_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_1_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_2_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_3_public_key.clone())), Some(12_565_000_000_000_000_000_000u128)); } else if amount_bonded_each_miner.clone() == LARGE_AMOUNT_DHX { - assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630195200000), Some(9_999_999_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630195200000), Some(49_999_995_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 1)), Some(3_333_333_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 2)), Some(3_333_333_000_000_000_000_000_000u128)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, 3)), Some(3_333_333_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_1_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_2_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630195200000, account_3_public_key.clone())), Some(16_666_665_000_000_000_000_000_000u128)); } - assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630195200000), Some(TWO_DHX)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630195200000), Some(true)); - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630195200000, 0, 1))); - change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630281600000i64); - // 30th August 2021 @ ~7am is 1630306800000 - // 30th August 2021 @ 12am is 1630281600000 (start of day) - Timestamp::set_timestamp(1630306800000u64); + // 30th Aug 2021 @ 12am is 1630281600000 (start of day) + Timestamp::set_timestamp(1630281600000u64); MiningRewardsAllowanceTestModule::on_initialize(5); - // we have finished the cooling off period and should now be distributing rewards each day unless they reduce their bonded - // amount below the min. bonded DHX daily amount - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630281600000, 0, 1))); - // check that the min_bonded_dhx_daily doubled after 3 months from 10 DHX to 20 DHX - assert_eq!(MiningRewardsAllowanceTestModule::min_bonded_dhx_daily(), Some(TWENTY_DHX)); - // the change between each multiplier period is 10 unless a user sets it to a different value - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_change(), Some(10u32)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_next_change(), Some(10u32)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_next_period_days(), Some(2u32)); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_total(), Some(2u32)); - // start of new multiplier period - assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630281600000, 1630281600000, 2u32, 2u32))); + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630368000000i64); + + // 31th Aug 2021 @ 12am is 1630368000000 (start of day) + Timestamp::set_timestamp(1630368000000u64); + MiningRewardsAllowanceTestModule::on_initialize(6); + + // we can only claim the rewards for a date that was at least the challenge period (i.e. 7 days) prior to the current date + // when a user submits a request to claim rewards that were calculated that they were eligible for on that prior date + + assert_ok!(MiningRewardsAllowanceTestModule::set_challenge_period_days( + Origin::root(), + 2u64, + )); + + // try to claim rewards they were NOT eligible for yet on the 31th Aug because it is less than the challenge period ago + // i'll just try claiming the reward for one of the miners + assert_eq!( + MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + Origin::signed(account_1_account_id.clone()), + account_1_public_key.clone(), + 1630368000000 + ), + Err(DispatchError::Other("Not more than challenge period")) + ); + + // try to claim rewards they were deemed eligible for back on the 29th Aug (more than challenge period prior) + + assert_ok!(MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + Origin::signed(account_1_account_id.clone()), + account_1_public_key.clone(), + 1630195200000 + )); + + // change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630713600000i64); + // // 4th September 2021 @ 12am is 1630713600000 (start of day) + // Timestamp::set_timestamp(1630713600000u64); - // Note - these are just notes. no further action required - // Note - why is this 2u128 instead of reset back to say 5000u128 DHX (unless set do different value?? - // this should be reset after rewards aggregated/accumulated each day - // since distribution/claiming may not be done by a user each day - // Update: it gets reset but difficult to add a test, have to run the logs with only one test running to see it gets accumulated/aggregated - // to all miners each day over a few days + // change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630886400000i64); + // // 6th September 2021 @ 12am is 1630886400000 (start of day) + // Timestamp::set_timestamp(1630886400000u64); + + + // // // added this so logs appear so i can debug + // // // assert_eq!(1, 0); + + // assert_ok!(MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + // Origin::signed(account_2_account_id.clone()), + // account_2_public_key.clone(), + // 1630195200000 + // )); + + // assert_ok!(MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + // Origin::signed(account_3_account_id.clone()), + // account_3_public_key.clone(), + // 1630195200000 + // )); + + // // after all the registered dhx miners have claimed their rewards this is the amount that should be remaining from the allocated dhx for the date + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630195200000), Some(TWO_DHX)); + // // // TODO - each registered dhx miner is claiming rewards now instead of the rewards being automatically distributed, + // // // see notes in the implementation lib.rs + // // // assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630195200000), Some(true)); + + + + // TODO - move the below into a separate test + + // assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630195200000, 0, 0))); + + // change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630281600000i64); + + // // 30th August 2021 @ ~7am is 1630306800000 + // // 30th August 2021 @ 12am is 1630281600000 (start of day) + // Timestamp::set_timestamp(1630306800000u64); + // MiningRewardsAllowanceTestModule::on_initialize(5); + + // // we have finished the cooling down period and should now be distributing rewards each day unless they reduce their bonded + // // amount below the min. bonded DHX daily amount + // assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630281600000, 0, 0))); + // // check that the min_bonded_dhx_daily doubled after 3 months from 10 DHX to 20 DHX + // assert_eq!(MiningRewardsAllowanceTestModule::min_bonded_dhx_daily(), Some(TWENTY_DHX)); + // // the change between each multiplier period is 10 unless a user sets it to a different value + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_change(), Some(10u32)); + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_next_change(), Some(10u32)); + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_next_period_days(), Some(2u32)); + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_total(), Some(2u32)); + // // start of new multiplier period + // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630281600000, 1630281600000, 2u32, 2u32))); + + // // Note - these are just notes. no further action required + // // Note - why is this 2u128 instead of reset back to say 5000u128 DHX (unless set do different value?? + // // this should be reset after rewards aggregated/accumulated each day + // // since distribution/claiming may not be done by a user each day + // // Update: it gets reset but difficult to add a test, have to run the logs with only one test running to see it gets accumulated/aggregated + // // to all miners each day over a few days // assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630281600000), Some(FIVE_THOUSAND_DHX)); - // Note - why is this 'true' when the day just started and nothing has been distributed? - // Update: difficult to add a test, check it works using the logs. they will be claiming rewards anyway, so we need separate tests for that - // assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630281600000), Some(false)); - // Note - why haven't these been reset to 0u128 for the next day? - // Update: difficult to add a test to check they are reset before next day, just check it works using the logs. - // assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630281600000), Some(0u128)); - // assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630281600000, 1)), Some(0u128)); + // // TODO - see other notes about status of using `rewards_allowance_dhx_for_date_remaining_distributed` in future. + // // assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630281600000), Some(false)); } fn check_rewards_double_each_multiplier_period(amount_mpower_each_miner: u128) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + + // 30th August 2021 @ ~7am is 1630306800000 + // 30th August 2021 @ 12am is 1630281600000 (start of day) + Timestamp::set_timestamp(1630306800000u64); + MiningRewardsAllowanceTestModule::on_initialize(5); + + assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630281600000, 1630281600000, 2u32, 2u32))); + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630368000000i64); // 31th August 2021 @ ~7am is 1630393200000 // 31th August 2021 @ 12am is 1630368000000 (start of day) Timestamp::set_timestamp(1630393200000u64); MiningRewardsAllowanceTestModule::on_initialize(6); - // cooling off period doesn't change again unless they unbond - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630368000000, 0, 1))); + // TODO - we don't need to use `cooling_down_period_days_remaining` to test the multiplier period works, + // but it should be set to the value below, so we might need an extrinsic like + // `set_cooling_down_period_days_remaining` to set it so we can test it is the + // value below + // + // // cooling down period must be 0 otherwise they are unbonding + // assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630368000000, 0, 0))); assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630281600000, 1630368000000, 2u32, 1u32))); assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_change(), Some(10u32)); @@ -597,7 +842,7 @@ fn check_rewards_double_each_multiplier_period(amount_mpower_each_miner: u128) { // 1st Sept 2021 @ 12am is 1630454400000 (start of day) Timestamp::set_timestamp(1630479600000u64); MiningRewardsAllowanceTestModule::on_initialize(7); - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630454400000, 0, 1))); + // assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630454400000, 0, 0))); assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630281600000, 1630454400000, 2u32, 0u32))); assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_change(), Some(10u32)); @@ -606,21 +851,26 @@ fn check_rewards_double_each_multiplier_period(amount_mpower_each_miner: u128) { // 2nd Sept 2021 @ ~7am is 1630566000000 // 2nd Sept 2021 @ 12am is 1630540800000 (start of day) Timestamp::set_timestamp(1630566000000u64); - MiningRewardsAllowanceTestModule::on_initialize(7); - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630540800000, 0, 1))); - // start of new multiplier period + MiningRewardsAllowanceTestModule::on_initialize(8); + // assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630540800000, 0, 0))); + + // check that it resets for a start of new multiplier period assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1630540800000, 1630540800000, 2u32, 2u32))); // check that the min_bonded_dhx_daily doubled after 3 months (we're only doing it after 2 days in the tests though) from 20 DHX to 30 DHX assert_eq!(MiningRewardsAllowanceTestModule::min_bonded_dhx_daily(), Some(THIRTY_DHX)); } fn check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_bonded(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_2_public_key: Vec = BOB_PUBLIC_KEY.clone().into(); + let account_3_public_key: Vec = CHARLIE_PUBLIC_KEY.clone().into(); + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630627200000i64); // 3rd Sept 2021 @ ~7am is 1630652400000 // 3rd Sept 2021 @ 12am is 1630627200000 (start of day) Timestamp::set_timestamp(1630652400000u64); - MiningRewardsAllowanceTestModule::on_initialize(8); + MiningRewardsAllowanceTestModule::on_initialize(9); // the below works to unbond each of the accounts @@ -635,46 +885,45 @@ fn check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_b // 4th Sept 2021 @ ~7am is 1630738800000 // 4th Sept 2021 @ 12am is 1630713600000 (start of day) Timestamp::set_timestamp(1630738800000u64); - MiningRewardsAllowanceTestModule::on_initialize(9); + MiningRewardsAllowanceTestModule::on_initialize(10); // IMPORTANT NOTE: The min. DHX bonded has increased from 10 (10:1) to 20 (20:1) in order to be eligible // for rewards, however none of the miner's increased their bonded DHX amount proportionally to still remain // eligible for rewards, so since having insufficient bonded DHX is the same as unbonding, we expect the - // cooling off period days remaining to change so they are now going through the unbonding cool down period, - // (which we also count using `cooling_off_period_days_remaining`) - // where they aren't eligble for rewards until they bond the new min. DHX so cooling off period starts and - // then they'd be eligible for rewards after waiting that period, but also note that if they don't bond the new min. - // DHX and wait until the end of the cool down period then they'll be able to withdraw the amount they had bonded. + // cooling down period days remaining to change so they are now going through the unbonding cool down period, + // (which we also count using `cooling_down_period_days_remaining`) + // where they aren't eligble for rewards during the unbonding whole unbonding period, + // then when they bond the new min. DHX then after the unbonding period the cooling down period starts and + // then they'd be eligible for rewards after waiting the challenge period. // // but in the tests the initial bonded amounts were much more than the min. DHX bonded, so even after it increases // from 10 (10:1) to 20 (20:1) they are still eligible for rewards. // so in the tests we've just decided to remove their vote and `unlock` their bonded DHX so they don't have a lock // and so don't satisfy the min. DHX bonded - // params: start of date, days remaining, bonding status - // note: since they don't have the min. DHX bonded their bonding status changes to `2`, which is unbonding - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630713600000, 1, 2))); + // params: start of date, days remaining + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630713600000, 2, 1))); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, 1)), Some(0u128)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, 2)), Some(0u128)); - assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, 3)), Some(0u128)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, account_1_public_key.clone())), Some(0u128)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, account_2_public_key.clone())), Some(0u128)); + assert_eq!(MiningRewardsAllowanceTestModule::bonded_dhx_of_account_for_date((1630713600000, account_3_public_key.clone())), Some(0u128)); // check they are not eligible for rewards due to insufficient bonded amount assert_eq!(MiningRewardsAllowanceTestModule::rewards_aggregated_dhx_for_all_miners_for_date(1630713600000), None); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, 1)), None); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, 2)), None); - assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, 3)), None); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, account_1_public_key.clone())), None); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, account_2_public_key.clone())), None); + assert_eq!(MiningRewardsAllowanceTestModule::rewards_accumulated_dhx_for_miner_for_date((1630713600000, account_3_public_key.clone())), None); assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining(1630713600000), Some(FIVE_THOUSAND_DHX)); assert_eq!(MiningRewardsAllowanceTestModule::rewards_allowance_dhx_for_date_remaining_distributed(1630713600000), Some(false)); - check_cooling_off_period_starts_again_if_sufficient_bonded_again(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); + check_cooling_down_period_resets_and_eligible_for_rewards_if_sufficient_bonded_again(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); } -fn check_cooling_off_period_starts_again_if_sufficient_bonded_again(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { - - bond_each_miner_by_voting_for_referendum(amount_bonded_each_miner, referendum_index); +fn check_cooling_down_period_resets_and_eligible_for_rewards_if_sufficient_bonded_again(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + let account_1_account_id: u64 = Decode::decode(&mut account_1_public_key.as_slice().clone()).ok().unwrap(); change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630800000000i64); @@ -682,23 +931,104 @@ fn check_cooling_off_period_starts_again_if_sufficient_bonded_again(amount_bonde // 5th Sept 2021 @ ~7am is 1630825200000 // 5th Sept 2021 @ 12am is 1630800000000 (start of day) Timestamp::set_timestamp(1630825200000u64); - MiningRewardsAllowanceTestModule::on_initialize(10); + MiningRewardsAllowanceTestModule::on_initialize(11); // params: start of date, days remaining, bonding status // note: since they have the min. DHX bonded again their bonding status changes to `1`, which is bonding - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630800000000, 0, 1))); + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630800000000, 1, 1))); + + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630886400000i64); + + // 6th Sept 2021 @ ~7am is 1630911600000 + // 6th Sept 2021 @ 12am is 1630886400000 (start of day) + Timestamp::set_timestamp(1630911600000u64); + MiningRewardsAllowanceTestModule::on_initialize(12); + + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630886400000, 0, 1))); + + // // temporarily set the challenge period really short so we can test that they weren't eligible for any rewards during the cooling down period + // assert_ok!(MiningRewardsAllowanceTestModule::set_challenge_period_days( + // Origin::root(), + // 1u64, + // )); + + // // try to claim rewards they were NOT eligible for yet on the 5th Sept since they were in the unbonding period + // assert_eq!( + // MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + // Origin::signed(account_1_account_id.clone()), + // account_1_public_key.clone(), + // 1630800000000 + // ), + // Err(DispatchError::Other("Unable to retrieve balance for rewards_aggregated_dhx_daily. cooling down period or challenge period may not be finished yet")) + // ); + + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1630972800000i64); + + // 7th Sept 2021 @ ~7am is 1630998000000 + // 7th Sept 2021 @ 12am is 1630972800000 (start of day) + Timestamp::set_timestamp(1630998000000u64); + MiningRewardsAllowanceTestModule::on_initialize(13); + + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630972800000, 0, 0))); + + change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1631059200000i64); + + // 8th Sept 2021 @ ~7am is 1631084400000 + // 8th Sept 2021 @ 12am is 1631059200000 (start of day) + Timestamp::set_timestamp(1631084400000u64); + MiningRewardsAllowanceTestModule::on_initialize(14); + + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630972800000, 0, 0))); + + // try to claim rewards they were NOT eligible for yet on the 7th Sept since they were in the unbonding period + assert_eq!( + MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + Origin::signed(account_1_account_id.clone()), + account_1_public_key.clone(), + 1630972800000 + ), + Err(DispatchError::Other("Unable to retrieve balance for rewards_aggregated_dhx_daily. cooling down period or challenge period may not be finished yet")) + ); + + + + + // restore again + assert_ok!(MiningRewardsAllowanceTestModule::set_challenge_period_days( + Origin::root(), + 1u64, + )); + + - check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_mpower(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); + + // // try to claim rewards they were deemed eligible for back on the 29th Aug + + // // we'll get all three of the registered dhx miners to claim their rewards + // assert_ok!(MiningRewardsAllowanceTestModule::claim_rewards_of_account_for_date( + // Origin::signed(account_1_account_id.clone()), + // account_1_public_key.clone(), + // 1630195200000 + // )); + + + + + // bond_each_miner_by_voting_for_referendum(amount_bonded_each_miner, referendum_index); + + // check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_mpower(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); } fn check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_mpower(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + // no mpower to check they'll be ineligible for rewards change_mpower_for_each_miner(0u128, 1630886400000i64); // 6th Sept 2021 @ ~7am is 1630911600000 // 6th Sept 2021 @ 12am is 1630886400000 (start of day) Timestamp::set_timestamp(1630911600000u64); - MiningRewardsAllowanceTestModule::on_initialize(11); + MiningRewardsAllowanceTestModule::on_initialize(12); // no mpower to check they'll be ineligible for rewards change_mpower_for_each_miner(0u128, 1630972800000i64); @@ -706,27 +1036,29 @@ fn check_ineligible_for_rewards_and_cooling_down_period_starts_if_insufficient_m // 7th Sept 2021 @ ~7am is 1630998000000 // 7th Sept 2021 @ 12am is 1630972800000 (start of day) Timestamp::set_timestamp(1630998000000u64); - MiningRewardsAllowanceTestModule::on_initialize(12); + MiningRewardsAllowanceTestModule::on_initialize(13); // params: start of date, days remaining, bonding status // note: since they don't have min. mPower their bonding status changes to `2`, which is unbonding - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1630972800000, 0, 2))); + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1630972800000, 0, 0))); - check_cooling_off_period_starts_again_if_sufficient_mpower_again(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); + check_cooling_down_period_starts_again_if_sufficient_mpower_again(amount_bonded_each_miner.clone(), amount_mpower_each_miner.clone(), referendum_index.clone()); } -fn check_cooling_off_period_starts_again_if_sufficient_mpower_again(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { +fn check_cooling_down_period_starts_again_if_sufficient_mpower_again(amount_bonded_each_miner: u128, amount_mpower_each_miner: u128, referendum_index: u32) { + let account_1_public_key: Vec = ALICE_PUBLIC_KEY.clone().into(); + // reset mpower to what it was change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1631059200000i64); // 8th Sept 2021 @ ~7am is 1631084400000 // 8th Sept 2021 @ 12am is 1631059200000 (start of day) Timestamp::set_timestamp(1631084400000u64); - MiningRewardsAllowanceTestModule::on_initialize(13); + MiningRewardsAllowanceTestModule::on_initialize(14); // params: start of date, days remaining, bonding status // note: they have min. mPower again so their bonding status changes to `0`, which is unbonded - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1631059200000, 0, 0))); + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1631059200000, 0, 0))); // use original mpower change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1631145600000i64); @@ -734,11 +1066,11 @@ fn check_cooling_off_period_starts_again_if_sufficient_mpower_again(amount_bonde // 9th Sept 2021 @ ~7am is 1631170800000 // 9th Sept 2021 @ 12am is 1631145600000 (start of day) Timestamp::set_timestamp(1631170800000u64); - MiningRewardsAllowanceTestModule::on_initialize(14); + MiningRewardsAllowanceTestModule::on_initialize(15); // params: start of date, days remaining, bonding status // note: they have min. mPower again so their bonding status changes to `1`, which means they are bonded again - assert_eq!(MiningRewardsAllowanceTestModule::cooling_off_period_days_remaining(1), Some((1631145600000, 1, 1))); + assert_eq!(MiningRewardsAllowanceTestModule::cooling_down_period_days_remaining(account_1_public_key.clone()), Some((1631145600000, 1, 0))); // params: total days, days remaining assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1631059200000, 1631145600000, 2u32, 1u32))); @@ -760,7 +1092,7 @@ fn check_pause_and_reset_rewards_multiplier_works(amount_bonded_each_miner: u128 // 10th Sept 2021 @ ~7am is 1631257200000 // 10th Sept 2021 @ 12am is 1631232000000 (start of day) Timestamp::set_timestamp(1631257200000u64); - MiningRewardsAllowanceTestModule::on_initialize(15); + MiningRewardsAllowanceTestModule::on_initialize(16); // use original mpower change_mpower_for_each_miner(amount_mpower_each_miner.clone(), 1631318400000i64); @@ -768,7 +1100,7 @@ fn check_pause_and_reset_rewards_multiplier_works(amount_bonded_each_miner: u128 // 11th Sept 2021 @ ~7am is 1631343600000 // 11th Sept 2021 @ 12am is 1631318400000 (start of day) Timestamp::set_timestamp(1631343600000u64); - MiningRewardsAllowanceTestModule::on_initialize(16); + MiningRewardsAllowanceTestModule::on_initialize(17); // params: total days, days remaining // assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1631318400000, 1631318400000, 2u32, 2u32))); @@ -789,7 +1121,7 @@ fn check_pause_and_reset_rewards_multiplier_works(amount_bonded_each_miner: u128 // 12th Sept 2021 @ ~7am is 1631430000000 // 12th Sept 2021 @ 12am is 1631404800000 (start of day) Timestamp::set_timestamp(1631430000000u64); - MiningRewardsAllowanceTestModule::on_initialize(17); + MiningRewardsAllowanceTestModule::on_initialize(18); // this starts reducing again since we unpaused it assert_eq!(MiningRewardsAllowanceTestModule::rewards_multiplier_current_period_days_remaining(), Some((1631059200000, 1631404800000, 2u32, 0u32))); diff --git a/pallets/primitives/src/constants/mod.rs b/pallets/primitives/src/constants/mod.rs index 558d11052..f7211058c 100644 --- a/pallets/primitives/src/constants/mod.rs +++ b/pallets/primitives/src/constants/mod.rs @@ -32,6 +32,7 @@ pub mod time { pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; pub const DAYS: BlockNumber = HOURS * 24; + pub const SECONDS: BlockNumber = MINUTES / 60; // 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 92063b768..4c2e3247c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -7,6 +7,11 @@ use codec::{ Decode, Encode, }; +use mining_rewards_allowance::{ + crypto::{ + TestAuthId, + }, +}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use pallet_grandpa::{ fg_primitives, @@ -183,6 +188,7 @@ pub use module_primitives::{ MILLISECS_PER_BLOCK, MINUTES, PRIMARY_PROBABILITY, + SECONDS, SLOT_DURATION, }, types::*, @@ -1146,9 +1152,35 @@ impl mining_execution_token::Config for Runtime { type MiningExecutionTokenIndex = u64; } +// pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); + +// pub mod sr25519 { +// mod app_sr25519 { +// use super::super::TEST_KEY_TYPE_ID; +// use app_crypto::{app_crypto, sr25519}; +// app_crypto!(sr25519, TEST_KEY_TYPE_ID); +// } + +// pub type AuthorityId = app_sr25519::Public; +// } + +parameter_types! { + // Note: if this is set to a longer period of 1 * MINUTE + // then it will cause an error in the logs: + // `offchain_workers error fetching mpower: Too early to send unsigned transaction ` or + // `offchain_workers error unknown transaction type` + pub const GracePeriod: BlockNumber = 10 * SECONDS; // 1 * MINUTES; + pub const UnsignedInterval: BlockNumber = 10 * SECONDS; // 1 * MINUTES; + pub const UnsignedPriority: BlockNumber = 10 * SECONDS; // 1 * MINUTES; +} + impl mining_rewards_allowance::Config for Runtime { - type Event = Event; + type Call = Call; type Currency = Balances; + type Event = Event; + type GracePeriod = GracePeriod; + type UnsignedInterval = UnsignedInterval; + type UnsignedPriority = UnsignedPriority; } impl exchange_rate::Config for Runtime { @@ -1222,7 +1254,7 @@ construct_runtime!( MiningSettingHardware: mining_setting_hardware::{Pallet, Call, Storage, Event}, MiningRatesToken: mining_rates_token::{Pallet, Call, Storage, Event}, MiningRatesHardware: mining_rates_hardware::{Pallet, Call, Storage, Event}, - MiningRewardsAllowance: mining_rewards_allowance::{Pallet, Call, Storage, Config, Event}, + MiningRewardsAllowance: mining_rewards_allowance::{Pallet, Call, Storage, Config, Event, ValidateUnsigned}, MiningSamplingToken: mining_sampling_token::{Pallet, Call, Storage, Event}, MiningSamplingHardware: mining_sampling_hardware::{Pallet, Call, Storage, Event}, MiningEligibilityToken: mining_eligibility_token::{Pallet, Call, Storage, Event},