diff --git a/operator/pallets/external-validators-rewards/src/lib.rs b/operator/pallets/external-validators-rewards/src/lib.rs index d346c67b..376d3a55 100644 --- a/operator/pallets/external-validators-rewards/src/lib.rs +++ b/operator/pallets/external-validators-rewards/src/lib.rs @@ -625,10 +625,43 @@ pub mod pallet { impl OnEraEnd for Pallet { fn on_era_end(era_index: EraIndex) { // Calculate performance-scaled inflation based on blocks produced. - // This must be done first since we use it for both minting and the rewards message. let base_inflation = T::EraInflationProvider::get(); let scaled_inflation = Self::calculate_scaled_inflation(era_index, base_inflation); + // Check that there are reward points before minting. + // This prevents minting inflation when no validators have earned rewards. + let era_reward_points = RewardPointsForEra::::get(&era_index); + let total_points: u128 = era_reward_points + .individual + .values() + .map(|pts| *pts as u128) + .sum(); + + if total_points.is_zero() { + log::error!( + target: "ext_validators_rewards", + "No reward points in era {}, skipping inflation minting", + era_index + ); + return; + } + + let ethereum_sovereign_account = T::RewardsEthereumSovereignAccount::get(); + + // Mint scaled inflation tokens using the configurable handler. + // Returns an InflationMintResult with the rewards/treasury split. + let mint_result = match T::HandleInflation::mint_inflation( + ðereum_sovereign_account, + scaled_inflation, + ) { + Ok(result) => result, + Err(err) => { + log::error!(target: "ext_validators_rewards", "Failed to handle inflation: {err:?}"); + log::error!(target: "ext_validators_rewards", "Not sending message since there are no rewards to distribute"); + return; + } + }; + // Get era start timestamp from the active era (still the ending era at this point). // Convert from milliseconds to seconds for EigenLayer compatibility. let era_start_timestamp = T::EraIndexProvider::active_era() @@ -636,11 +669,11 @@ pub mod pallet { .map(|ms| (ms / 1000) as u32) .unwrap_or(0); - // Generate era rewards utils with the scaled inflation amount. - // This ensures the message to EigenLayer matches the actual minted amount. - let utils = match RewardPointsForEra::::get(&era_index).generate_era_rewards_utils( + // Generate era rewards utils with the actual rewards amount (post-treasury split). + // This ensures the message to EigenLayer matches the actual minted rewards. + let utils = match era_reward_points.generate_era_rewards_utils( era_index, - scaled_inflation, + mint_result.rewards_amount, era_start_timestamp, ) { Some(utils) => utils, @@ -654,17 +687,6 @@ pub mod pallet { } }; - let ethereum_sovereign_account = T::RewardsEthereumSovereignAccount::get(); - - // Mint scaled inflation tokens using the configurable handler - if let Err(err) = - T::HandleInflation::mint_inflation(ðereum_sovereign_account, scaled_inflation) - { - log::error!(target: "ext_validators_rewards", "Failed to handle inflation: {err:?}"); - log::error!(target: "ext_validators_rewards", "Not sending message since there are no rewards to distribute"); - return; - } - frame_system::Pallet::::register_extra_weight_unchecked( T::WeightInfo::on_era_end(), DispatchClass::Mandatory, @@ -675,7 +697,7 @@ pub mod pallet { message_id, era_index, total_points: utils.total_points, - inflation_amount: scaled_inflation, + inflation_amount: mint_result.rewards_amount, }); } } diff --git a/operator/pallets/external-validators-rewards/src/mock.rs b/operator/pallets/external-validators-rewards/src/mock.rs index 4d854751..3c892f35 100644 --- a/operator/pallets/external-validators-rewards/src/mock.rs +++ b/operator/pallets/external-validators-rewards/src/mock.rs @@ -230,7 +230,10 @@ impl pallet_external_validators_rewards::Config for Test { pub struct InflationMinter; impl HandleInflation for InflationMinter { - fn mint_inflation(rewards_account: &H160, total_amount: u128) -> sp_runtime::DispatchResult { + fn mint_inflation( + rewards_account: &H160, + total_amount: u128, + ) -> Result { use sp_runtime::traits::Zero; if total_amount.is_zero() { @@ -266,7 +269,10 @@ impl HandleInflation for InflationMinter { .map_err(|_| DispatchError::Other("Failed to mint treasury inflation"))?; } - Ok(()) + Ok(crate::types::InflationMintResult { + rewards_amount, + treasury_amount, + }) } } diff --git a/operator/pallets/external-validators-rewards/src/tests.rs b/operator/pallets/external-validators-rewards/src/tests.rs index 986c624c..1a66daa0 100644 --- a/operator/pallets/external-validators-rewards/src/tests.rs +++ b/operator/pallets/external-validators-rewards/src/tests.rs @@ -160,15 +160,19 @@ fn test_on_era_end() { let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::::get(1); let inflation = ::EraInflationProvider::get(); + // The event should contain the rewards amount (post-treasury split), not the full inflation. + // Treasury gets Perbill::from_percent(20).mul_floor(inflation), rewards gets the rest. + let treasury_amount = InflationTreasuryProportion::get().mul_floor(inflation); + let rewards_amount = inflation - treasury_amount; // Use 0 for era_start_timestamp in tests - let rewards_utils = era_rewards.generate_era_rewards_utils(1, inflation, 0); + let rewards_utils = era_rewards.generate_era_rewards_utils(1, rewards_amount, 0); assert!(rewards_utils.is_some()); System::assert_last_event(RuntimeEvent::ExternalValidatorsRewards( crate::Event::RewardsMessageSent { message_id: Default::default(), era_index: 1, total_points: total_points as u128, - inflation_amount: inflation, + inflation_amount: rewards_amount, }, )); }) diff --git a/operator/pallets/external-validators-rewards/src/types.rs b/operator/pallets/external-validators-rewards/src/types.rs index cb62aa47..540e6b94 100644 --- a/operator/pallets/external-validators-rewards/src/types.rs +++ b/operator/pallets/external-validators-rewards/src/types.rs @@ -39,14 +39,34 @@ pub trait SendMessage { fn deliver(ticket: Self::Ticket) -> Result; } -// Trait for handling inflation +/// Result of minting inflation tokens, detailing the split between rewards and treasury. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct InflationMintResult { + /// Amount minted to the rewards account (for distribution to validators via AVS). + pub rewards_amount: u128, + /// Amount minted to the treasury account. + pub treasury_amount: u128, +} + +/// Trait for handling inflation minting with a rewards/treasury split. pub trait HandleInflation { - fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult; + /// Mint inflation tokens, splitting between rewards and treasury. + /// Returns an `InflationMintResult` detailing the amounts minted to each destination. + fn mint_inflation( + who: &AccountId, + amount: u128, + ) -> Result; } impl HandleInflation for () { - fn mint_inflation(_: &AccountId, _: u128) -> sp_runtime::DispatchResult { - Ok(()) + fn mint_inflation( + _: &AccountId, + _: u128, + ) -> Result { + Ok(InflationMintResult { + rewards_amount: 0, + treasury_amount: 0, + }) } } diff --git a/operator/runtime/common/src/inflation.rs b/operator/runtime/common/src/inflation.rs index 90936b1b..b18332bc 100644 --- a/operator/runtime/common/src/inflation.rs +++ b/operator/runtime/common/src/inflation.rs @@ -165,11 +165,15 @@ where TreasuryProportion: Get, TreasuryAccount: Get, { - /// Mints inflation tokens and splits them between rewards and treasury accounts + /// Mints inflation tokens and splits them between rewards and treasury accounts. + /// Returns an `InflationMintResult` detailing the amounts minted to each destination. pub fn mint_inflation( rewards_account: &crate::AccountId, total_amount: u128, - ) -> sp_runtime::DispatchResult { + ) -> Result< + pallet_external_validators_rewards::types::InflationMintResult, + sp_runtime::DispatchError, + > { use sp_runtime::traits::Zero; if total_amount.is_zero() { @@ -236,7 +240,12 @@ where treasury_amount ); - Ok(()) + Ok( + pallet_external_validators_rewards::types::InflationMintResult { + rewards_amount, + treasury_amount, + }, + ) } } @@ -247,6 +256,7 @@ mod tests { parameter_types, traits::fungible::{Inspect, Mutate, Unbalanced}, }; + use pallet_external_validators_rewards::types::InflationMintResult; use sp_runtime::Perbill; use std::cell::RefCell; @@ -561,7 +571,13 @@ mod tests { let total_inflation = 1_000_000u128; let result = TestHandler::mint_inflation(&rewards_account, total_inflation); - assert!(result.is_ok()); + assert_eq!( + result, + Ok(InflationMintResult { + rewards_amount: 800_000, + treasury_amount: 200_000, + }) + ); let rewards_balance = get_balance(&rewards_account); let treasury_balance = get_balance(&TreasuryAccountId::get()); @@ -579,7 +595,13 @@ mod tests { let total_inflation = 1_000_000u128; let result = TestHandler50Pct::mint_inflation(&rewards_account, total_inflation); - assert!(result.is_ok()); + assert_eq!( + result, + Ok(InflationMintResult { + rewards_amount: 500_000, + treasury_amount: 500_000, + }) + ); let rewards_balance = get_balance(&rewards_account); let treasury_balance = get_balance(&TreasuryAccountId::get()); @@ -596,7 +618,13 @@ mod tests { let total_inflation = 1_000_000u128; let result = TestHandler0Pct::mint_inflation(&rewards_account, total_inflation); - assert!(result.is_ok()); + assert_eq!( + result, + Ok(InflationMintResult { + rewards_amount: total_inflation, + treasury_amount: 0, + }) + ); let rewards_balance = get_balance(&rewards_account); let treasury_balance = get_balance(&TreasuryAccountId::get()); @@ -613,7 +641,13 @@ mod tests { let total_inflation = 1_000_000u128; let result = TestHandler100Pct::mint_inflation(&rewards_account, total_inflation); - assert!(result.is_ok()); + assert_eq!( + result, + Ok(InflationMintResult { + rewards_amount: 0, + treasury_amount: total_inflation, + }) + ); let rewards_balance = get_balance(&rewards_account); let treasury_balance = get_balance(&TreasuryAccountId::get()); diff --git a/operator/runtime/mainnet/src/configs/mod.rs b/operator/runtime/mainnet/src/configs/mod.rs index fe152317..2d97fc46 100644 --- a/operator/runtime/mainnet/src/configs/mod.rs +++ b/operator/runtime/mainnet/src/configs/mod.rs @@ -1460,7 +1460,13 @@ pub struct ExternalRewardsInflationHandler; impl pallet_external_validators_rewards::types::HandleInflation for ExternalRewardsInflationHandler { - fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult { + fn mint_inflation( + who: &AccountId, + amount: u128, + ) -> Result< + pallet_external_validators_rewards::types::InflationMintResult, + sp_runtime::DispatchError, + > { datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::< Balances, runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion, diff --git a/operator/runtime/stagenet/src/configs/mod.rs b/operator/runtime/stagenet/src/configs/mod.rs index b07e9a5c..fb4cc4bb 100644 --- a/operator/runtime/stagenet/src/configs/mod.rs +++ b/operator/runtime/stagenet/src/configs/mod.rs @@ -1456,7 +1456,13 @@ pub struct ExternalRewardsInflationHandler; impl pallet_external_validators_rewards::types::HandleInflation for ExternalRewardsInflationHandler { - fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult { + fn mint_inflation( + who: &AccountId, + amount: u128, + ) -> Result< + pallet_external_validators_rewards::types::InflationMintResult, + sp_runtime::DispatchError, + > { datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::< Balances, runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion, diff --git a/operator/runtime/testnet/src/configs/mod.rs b/operator/runtime/testnet/src/configs/mod.rs index 2ea41697..5b9305a9 100644 --- a/operator/runtime/testnet/src/configs/mod.rs +++ b/operator/runtime/testnet/src/configs/mod.rs @@ -1460,7 +1460,13 @@ pub struct ExternalRewardsInflationHandler; impl pallet_external_validators_rewards::types::HandleInflation for ExternalRewardsInflationHandler { - fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult { + fn mint_inflation( + who: &AccountId, + amount: u128, + ) -> Result< + pallet_external_validators_rewards::types::InflationMintResult, + sp_runtime::DispatchError, + > { datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::< Balances, runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion,