From c4ba598d01d646ca02e07554b168805db6f701a3 Mon Sep 17 00:00:00 2001 From: jasl Date: Sun, 28 May 2023 19:37:51 +0800 Subject: [PATCH] =?UTF-8?q?Sync=20Phala=20pallets=20to=20f87192b64d90b06d1?= =?UTF-8?q?d8415fb47a1adffea43cc45=E2=80=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- pallets/phala/src/compute/base_pool.rs | 7 +- pallets/phala/src/compute/computation.rs | 20 +++- pallets/phala/src/compute/stake_pool_v2.rs | 100 +++++++++++------- pallets/phala/src/compute/wrapped_balances.rs | 50 +++++++-- pallets/phala/src/mock.rs | 9 +- .../phala/src/phat_tokenomic/tests/mock.rs | 4 +- pallets/phala/src/test.rs | 66 +++++++++++- 8 files changed, 196 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49ea4ad2..b7a75308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13693,7 +13693,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.6", - "rand 0.7.3", + "rand 0.8.5", "static_assertions", ] diff --git a/pallets/phala/src/compute/base_pool.rs b/pallets/phala/src/compute/base_pool.rs index 6e5da8ac..94fa1ba7 100644 --- a/pallets/phala/src/compute/base_pool.rs +++ b/pallets/phala/src/compute/base_pool.rs @@ -72,7 +72,6 @@ pub mod pallet { { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type MigrationAccountId: Get; - type WPhaMinBalance: Get>; } #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] @@ -1032,7 +1031,8 @@ pub mod pallet { None => return false, }; let current_balance = bmul(nft.shares, &price); - if current_balance > T::WPhaMinBalance::get() { + let wpha_min = wrapped_balances::Pallet::::min_balance(); + if current_balance > wpha_min { return false; } pool_info.total_shares -= nft.shares; @@ -1068,7 +1068,8 @@ pub mod pallet { None => return, }; - while pool_info.get_free_stakes::() > T::WPhaMinBalance::get() { + let wpha_min = wrapped_balances::Pallet::::min_balance(); + while pool_info.get_free_stakes::() > wpha_min { if let Some(withdraw) = pool_info.withdraw_queue.front().cloned() { // Must clear the pending reward before any stake change let mut withdraw_nft_guard = diff --git a/pallets/phala/src/compute/computation.rs b/pallets/phala/src/compute/computation.rs index 002a6136..63a2f7e9 100644 --- a/pallets/phala/src/compute/computation.rs +++ b/pallets/phala/src/compute/computation.rs @@ -210,6 +210,16 @@ pub mod pallet { type UpdateTokenomicOrigin: EnsureOrigin; type SetBudgetOrigins: EnsureOrigin; type SetContractRootOrigins: EnsureOrigin; + + /// Enable worker register time checking when trying to add it to a pool. + /// + /// The chain requires that workers must be registered after the Gatekeeper is launched + /// in order to be added to a stakepool. This makes sure the Gatekeeper tracks all events + /// for all workers being computing. However, on Khala network, workers registered earlier + /// didn't record there register time, so we need to disable this checking for Khala. + /// + /// DISABLE THIS FOR KHALA ONLY. + type CheckWorkerRegisterTime: Get; } const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); @@ -871,10 +881,12 @@ pub mod pallet { pub fn bind(session: T::AccountId, pubkey: WorkerPublicKey) -> DispatchResult { let worker = registry::Workers::::get(&pubkey).ok_or(Error::::WorkerNotRegistered)?; - ensure!( - registry::Pallet::::is_worker_registered_after_gk_launched(&pubkey), - Error::::WorkerReregisterNeeded - ); + if T::CheckWorkerRegisterTime::get() { + ensure!( + registry::Pallet::::is_worker_registered_after_gk_launched(&pubkey), + Error::::WorkerReregisterNeeded + ); + } // Check the worker has finished the benchmark ensure!(worker.initial_score != None, Error::::BenchmarkMissing); // Check worker and worker not bound diff --git a/pallets/phala/src/compute/stake_pool_v2.rs b/pallets/phala/src/compute/stake_pool_v2.rs index 45150410..da949e8a 100644 --- a/pallets/phala/src/compute/stake_pool_v2.rs +++ b/pallets/phala/src/compute/stake_pool_v2.rs @@ -227,6 +227,16 @@ pub mod pallet { worker: WorkerPublicKey, amount: BalanceOf, }, + + /// Some to-distribute reward is dismissed because the amount is too tiny (dust) + /// + /// There's no affected state. + RewardToOwnerDismissedDust { pid: u64, amount: BalanceOf }, + + /// Some to-distribute reward is dismissed because the amount is too tiny (dust) + /// + /// There's no affected state. + RewardToDistributionDismissedDust { pid: u64, amount: BalanceOf }, } #[pallet::error] @@ -1074,37 +1084,56 @@ pub mod pallet { pool_info: &mut StakePool>, rewards: BalanceOf, ) { - if rewards > Zero::zero() { - computation::Pallet::::withdraw_subsidy_pool( - &::WrappedBalancesAccountId::get(), - rewards, - ) - .expect("this should not happen"); - if base_pool::balance_close_to_zero(pool_info.basepool.total_shares) { - Self::deposit_event(Event::::RewardDismissedNoShare { - pid: pool_info.basepool.pid, - amount: rewards, - }); - return; - } - let commission = pool_info.payout_commission.unwrap_or_default() * rewards; - - wrapped_balances::Pallet::::mint_into( - &pool_info.owner_reward_account, - commission, - ) - .expect("mint into should be success"); - let to_distribute = rewards - commission; - wrapped_balances::Pallet::::mint_into( - &pool_info.basepool.pool_account_id, - to_distribute, - ) - .expect("mint into should be success"); - let distributed = if base_pool::is_nondust_balance(to_distribute) { + if rewards == Zero::zero() { + return; + } + // Dismiss if the reward is dust + if base_pool::balance_close_to_zero(rewards) { + Self::deposit_event(Event::::RewardDismissedDust { + pid: pool_info.basepool.pid, + amount: rewards, + }); + return; + } + // Dismiss if the share is dust (pool is frozen) + if base_pool::balance_close_to_zero(pool_info.basepool.total_shares) { + Self::deposit_event(Event::::RewardDismissedNoShare { + pid: pool_info.basepool.pid, + amount: rewards, + }); + return; + } + computation::Pallet::::withdraw_subsidy_pool( + &::WrappedBalancesAccountId::get(), + rewards, + ) + .expect("withdrawal from the subsidy pool should always success; qed."); + // Handle the owner commission. Be careful about minting as it may fail (dust) + let commission = pool_info.payout_commission.unwrap_or_default() * rewards; + let owner_minted = wrapped_balances::Pallet::::mint_into( + &pool_info.owner_reward_account, + commission, + ) + .expect("mint owner reward should succeed; qed."); + if !owner_minted { + Self::deposit_event(Event::::RewardToOwnerDismissedDust { + pid: pool_info.basepool.pid, + amount: commission, + }); + } + // Handle the to-distribute commission. Be careful about minting as it may fail (dust). + let to_distribute = rewards - commission; + let to_distribute_minted = wrapped_balances::Pallet::::mint_into( + &pool_info.basepool.pool_account_id, + to_distribute, + ) + .expect("mint to_distribute should succeed; qed."); + let distributed = + if to_distribute_minted && base_pool::is_nondust_balance(to_distribute) { pool_info.basepool.distribute_reward::(to_distribute); true } else if to_distribute > Zero::zero() { - Self::deposit_event(Event::::RewardDismissedDust { + Self::deposit_event(Event::::RewardToDistributionDismissedDust { pid: pool_info.basepool.pid, amount: to_distribute, }); @@ -1112,13 +1141,12 @@ pub mod pallet { } else { false }; - if distributed || commission > Zero::zero() { - Self::deposit_event(Event::::RewardReceived { - pid: pool_info.basepool.pid, - to_owner: commission, - to_stakers: to_distribute, - }); - } + if distributed || owner_minted { + Self::deposit_event(Event::::RewardReceived { + pid: pool_info.basepool.pid, + to_owner: commission, + to_stakers: to_distribute, + }); } } @@ -1195,7 +1223,7 @@ pub mod pallet { worker: info.pubkey, amount: reward, }); - return; + continue; } }; let mut pool_info = diff --git a/pallets/phala/src/compute/wrapped_balances.rs b/pallets/phala/src/compute/wrapped_balances.rs index 838fc57a..a2d551d4 100644 --- a/pallets/phala/src/compute/wrapped_balances.rs +++ b/pallets/phala/src/compute/wrapped_balances.rs @@ -9,6 +9,7 @@ pub mod pallet { use crate::registry; use crate::vault; use crate::{BalanceOf, NegativeImbalanceOf, PhalaConfig}; + use frame_support::traits::tokens::{Fortitude, Precision}; use frame_support::{ pallet_prelude::*, traits::{ @@ -19,7 +20,6 @@ pub mod pallet { OnUnbalanced, StorageVersion, }, }; - use frame_support::traits::tokens::{Fortitude, Precision}; use frame_system::{pallet_prelude::*, RawOrigin}; use pallet_democracy::{AccountVote, ReferendumIndex, ReferendumInfo}; pub use rmrk_traits::primitives::{CollectionId, NftId}; @@ -85,10 +85,14 @@ pub mod pallet { /// Mapping for users to their asset status proxys #[pallet::storage] - #[pallet::getter(fn staker_account)] pub type StakerAccounts = StorageMap<_, Twox64Concat, T::AccountId, FinanceAccount>>; + /// Collect the unmintable dust + // TODO: since this is the imbalance, consider to mint it in the future. + #[pallet::storage] + pub type UnmintableDust = StorageValue<_, BalanceOf, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -379,9 +383,14 @@ pub mod pallet { pub fn remove_dust(who: &T::AccountId, dust: BalanceOf) { debug_assert!(dust != Zero::zero()); if dust != Zero::zero() { - let actual_removed = - pallet_assets::Pallet::::burn_from(T::WPhaAssetId::get(), who, dust, Precision::BestEffort, Fortitude::Force) - .expect("slash should success with correct amount: qed."); + let actual_removed = pallet_assets::Pallet::::burn_from( + T::WPhaAssetId::get(), + who, + dust, + Precision::BestEffort, + Fortitude::Force, + ) + .expect("slash should success with correct amount: qed."); let (imbalance, _remaining) = ::Currency::slash( &>::account_id(), dust, @@ -394,10 +403,19 @@ pub mod pallet { } } - /// Mints some W-PHA - pub fn mint_into(target: &T::AccountId, amount: BalanceOf) -> DispatchResult { - pallet_assets::Pallet::::mint_into(T::WPhaAssetId::get(), target, amount)?; - Ok(()) + /// Mints some W-PHA. If the amount is below ED, it returns Ok(false) and adds the dust + /// to `UnmintableDust`. + pub fn mint_into( + target: &T::AccountId, + amount: BalanceOf, + ) -> Result { + let wpha = T::WPhaAssetId::get(); + let result = pallet_assets::Pallet::::mint_into(wpha, target, amount); + if result == Err(sp_runtime::TokenError::BelowMinimum.into()) { + UnmintableDust::::mutate(|value| *value += amount); + return Ok(false); + } + result.and(Ok(true)) } /// Burns some W-PHA @@ -407,7 +425,7 @@ pub mod pallet { target, amount, Precision::BestEffort, - Fortitude::Force + Fortitude::Force, )?; Ok(()) } @@ -499,5 +517,17 @@ pub mod pallet { let vote_info = pallet_democracy::Pallet::::referendum_info(vote_id); matches!(vote_info, Some(ReferendumInfo::Ongoing(_))) } + + /// Returns the minimum balance of WPHA + pub fn min_balance() -> BalanceOf { + if ! as Inspect>::asset_exists( + T::WPhaAssetId::get(), + ) { + panic!("WPHA does not exist"); + } + as Inspect>::minimum_balance( + T::WPhaAssetId::get(), + ) + } } } diff --git a/pallets/phala/src/mock.rs b/pallets/phala/src/mock.rs index 7eb11b27..e2fbde8c 100644 --- a/pallets/phala/src/mock.rs +++ b/pallets/phala/src/mock.rs @@ -76,6 +76,7 @@ parameter_types! { pub const NoneAttestationEnabled: bool = true; pub const VerifyPRuntime: bool = false; pub const VerifyRelaychainGenesisBlockHash: bool = true; + pub const CheckWorkerRegisterTime: bool = true; } impl system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; @@ -129,8 +130,8 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type HoldIdentifier = (); type FreezeIdentifier = (); - type MaxHolds = (); - type MaxFreezes = (); + type MaxHolds = ConstU32<1>; + type MaxFreezes = ConstU32<1>; } impl pallet_timestamp::Config for Test { @@ -257,6 +258,7 @@ impl computation::Config for Test { type UpdateTokenomicOrigin = EnsureRoot; type SetBudgetOrigins = EnsureSignedBy; type SetContractRootOrigins = EnsureRoot; + type CheckWorkerRegisterTime = CheckWorkerRegisterTime; } parameter_types! { @@ -402,7 +404,6 @@ impl vault::Config for Test { impl base_pool::Config for Test { type RuntimeEvent = RuntimeEvent; type MigrationAccountId = ConstU64<1234>; - type WPhaMinBalance = WPhaMinBalance; } impl stake_pool::Config for Test { @@ -473,14 +474,12 @@ pub fn take_events() -> Vec { .into_iter() .map(|evt| evt.event) .collect::>(); - println!("event(): {evt:?}"); System::reset_events(); evt } pub fn take_messages() -> Vec { let messages = PhalaMq::messages(); - println!("messages(): {messages:?}"); mq::OutboundMessages::::kill(); messages } diff --git a/pallets/phala/src/phat_tokenomic/tests/mock.rs b/pallets/phala/src/phat_tokenomic/tests/mock.rs index 4ad29af7..e9a36b35 100644 --- a/pallets/phala/src/phat_tokenomic/tests/mock.rs +++ b/pallets/phala/src/phat_tokenomic/tests/mock.rs @@ -80,8 +80,8 @@ impl pallet_balances::Config for Test { type ReserveIdentifier = [u8; 8]; type HoldIdentifier = (); type FreezeIdentifier = (); - type MaxHolds = (); - type MaxFreezes = (); + type MaxHolds = ConstU32<1>; + type MaxFreezes = ConstU32<1>; } impl pallet_timestamp::Config for Test { diff --git a/pallets/phala/src/test.rs b/pallets/phala/src/test.rs index 28d4737a..0a9559e5 100644 --- a/pallets/phala/src/test.rs +++ b/pallets/phala/src/test.rs @@ -5,6 +5,7 @@ use crate::stake_pool_v2; use crate::vault; use crate::wrapped_balances; use fixed::types::U64F64 as FixedPoint; +use fixed_macro::types::U64F64 as fp; use frame_support::{ assert_noop, assert_ok, pallet_prelude::Get, @@ -18,7 +19,8 @@ use sp_runtime::AccountId32; use crate::mock::{ ecdh_pubkey, elapse_cool_down, elapse_seconds, new_test_ext, set_block_1, setup_workers, - setup_workers_linked_operators, worker_pubkey, Balance, RuntimeOrigin, Test, DOLLARS, + setup_workers_linked_operators, take_events, worker_pubkey, Balance, RuntimeEvent, + RuntimeOrigin, Test, DOLLARS, }; // Pallets use crate::mock::{ @@ -895,6 +897,7 @@ fn test_add_worker() { ); }); } + #[test] fn test_start_computing() { new_test_ext().execute_with(|| { @@ -1283,6 +1286,67 @@ fn test_for_cdworkers() { }); } +#[test] +fn test_on_reward_dust() { + use crate::computation::pallet::OnReward; + new_test_ext().execute_with(|| { + mock_asset_id(); + assert_ok!(PhalaWrappedBalances::wrap( + RuntimeOrigin::signed(1), + 500 * DOLLARS + )); + set_block_1(); + setup_workers(1); + setup_stake_pool_with_workers(1, &[1]); + assert_ok!(PhalaStakePoolv2::set_payout_pref( + RuntimeOrigin::signed(1), + 0, + Some(Permill::from_percent(50)) + )); + assert_ok!(PhalaStakePoolv2::contribute( + RuntimeOrigin::signed(1), + 0, + 100 * DOLLARS, + None + )); + assert_ok!(PhalaStakePoolv2::start_computing( + RuntimeOrigin::signed(1), + 0, + worker_pubkey(1), + 100 * DOLLARS + )); + let _ = take_events(); + PhalaStakePoolv2::on_reward(&[SettleInfo { + pubkey: worker_pubkey(1), + v: FixedPoint::from_num(1u32).to_bits(), + payout: fp!(0.000010000000).to_bits(), // below 1e8 + treasury: 0, + }]); + let events = take_events(); + assert_eq!( + events[1], + RuntimeEvent::PhalaStakePoolv2( + stake_pool_v2::Event::::RewardToOwnerDismissedDust { + pid: 0, + amount: 4999999 + } + ) + ); + assert_eq!( + events[2], + RuntimeEvent::PhalaStakePoolv2( + stake_pool_v2::Event::::RewardToDistributionDismissedDust { + pid: 0, + amount: 5000000 + } + ) + ); + let pool = ensure_stake_pool::(0).unwrap(); + assert_eq!(get_balance(pool.owner_reward_account), 0); + assert_eq!(get_balance(pool.basepool.pool_account_id), 0); + }); +} + #[test] fn test_on_reward_for_vault() { use crate::computation::pallet::OnReward;