mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
feat: ✨ Implement inflation mechanism for validator rewards (#304)
## Summary This PR introduces a configurable inflation system for validator rewards with an annual target rate and optional treasury allocation. ## Changes ### Inflation Mechanism - **Annual inflation rate runtime parameter**: Set to 5% default - **EraInflationProvider**: Calculates per-era inflation based on total issuance and annual rate - Formula: `per_era_inflation = (total_issuance × annual_rate) / eras_per_year` ### Treasury Allocation - **InflationTreasuryProportion parameter**: Set to 20% default - **ExternalRewardsInflationHandler**: Mints inflation and distributes between: - 80% to rewards account (for validator rewards) - 20% to treasury account - Treasury receives allocation via `mul_floor()`, with remainder going to rewards to ensure no tokens lost to rounding ### Runtime Integration - Configured across all three runtimes: mainnet, testnet, and stagenet - Consistent parameters across all environments ### Testing - Updated all tests to account for 80/20 split between rewards and treasury - Added precision tolerance (±1 unit) for Perbill rounding edge cases --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2cc1a4d3f0
commit
7f09949e64
13 changed files with 1453 additions and 28 deletions
|
|
@ -148,6 +148,8 @@ impl ExternalIndexProvider for TimestampProvider {
|
|||
parameter_types! {
|
||||
pub const RewardsEthereumSovereignAccount: u64
|
||||
= 0xffffffffffffffff;
|
||||
pub const TreasuryAccount: u64 = 999;
|
||||
pub const InflationTreasuryProportion: sp_runtime::Perbill = sp_runtime::Perbill::from_percent(20);
|
||||
pub EraInflationProvider: u128 = Mock::mock().era_inflation.unwrap_or(42);
|
||||
}
|
||||
|
||||
|
|
@ -172,17 +174,43 @@ impl pallet_external_validators_rewards::Config for Test {
|
|||
|
||||
pub struct InflationMinter;
|
||||
impl HandleInflation<u64> for InflationMinter {
|
||||
fn mint_inflation(account: &u64, amount: u128) -> sp_runtime::DispatchResult {
|
||||
if amount == 0 {
|
||||
fn mint_inflation(rewards_account: &u64, total_amount: u128) -> sp_runtime::DispatchResult {
|
||||
use sp_runtime::traits::Zero;
|
||||
|
||||
if total_amount.is_zero() {
|
||||
log::error!(target: "ext_validators_rewards", "No rewards to distribute");
|
||||
return Err(DispatchError::Other("No rewards to distribute"));
|
||||
}
|
||||
<Test as pallet_external_validators_rewards::Config>::Currency::mint_into(
|
||||
account,
|
||||
amount.into(),
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| DispatchError::Other("Failed to mint inflation"))
|
||||
|
||||
// Get treasury allocation proportion
|
||||
let treasury_proportion = InflationTreasuryProportion::get();
|
||||
|
||||
// Calculate amounts
|
||||
let treasury_amount = treasury_proportion.mul_floor(total_amount);
|
||||
let rewards_amount = total_amount.saturating_sub(treasury_amount);
|
||||
|
||||
// Mint rewards to the rewards account
|
||||
if !rewards_amount.is_zero() {
|
||||
<Test as pallet_external_validators_rewards::Config>::Currency::mint_into(
|
||||
rewards_account,
|
||||
rewards_amount,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| DispatchError::Other("Failed to mint rewards inflation"))?;
|
||||
}
|
||||
|
||||
// Mint treasury portion if non-zero
|
||||
if !treasury_amount.is_zero() {
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
<Test as pallet_external_validators_rewards::Config>::Currency::mint_into(
|
||||
&treasury_account,
|
||||
treasury_amount,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| DispatchError::Other("Failed to mint treasury inflation"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +273,18 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
|
|||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)];
|
||||
let balances = vec![
|
||||
(1, 100),
|
||||
(2, 100),
|
||||
(3, 100),
|
||||
(4, 100),
|
||||
(5, 100),
|
||||
(TreasuryAccount::get(), ExistentialDeposit::get().into()), // Treasury needs existential deposit
|
||||
(
|
||||
RewardsEthereumSovereignAccount::get(),
|
||||
ExistentialDeposit::get().into(),
|
||||
), // Rewards account needs existential deposit
|
||||
];
|
||||
pallet_balances::GenesisConfig::<Test> { balances }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use {
|
||||
crate::{self as pallet_external_validators_rewards, mock::*},
|
||||
frame_support::traits::fungible::Mutate,
|
||||
pallet_external_validators::traits::{ActiveEraInfo, OnEraEnd, OnEraStart},
|
||||
sp_std::collections::btree_map::BTreeMap,
|
||||
};
|
||||
|
|
@ -211,3 +212,550 @@ fn test_on_era_end_with_zero_points() {
|
|||
);
|
||||
})
|
||||
}
|
||||
#[test]
|
||||
fn test_inflation_minting() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
// Set inflation amount directly for this test
|
||||
mock.era_inflation = Some(10_000_000); // 10 million tokens per era
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let initial_rewards_balance = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Reward some validators to create reward points
|
||||
let points = vec![10u32, 30u32, 50u32];
|
||||
let accounts = vec![1u64, 3u64, 5u64];
|
||||
let accounts_points: Vec<(u64, crate::RewardPoints)> = accounts
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(points.iter().cloned())
|
||||
.collect();
|
||||
ExternalValidatorsRewards::reward_by_ids(accounts_points);
|
||||
|
||||
// Trigger era end which should mint inflation
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
// Verify inflation was minted (80% to rewards, 20% to treasury)
|
||||
let final_rewards_balance = Balances::free_balance(&rewards_account);
|
||||
let inflation_amount =
|
||||
<Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
|
||||
let rewards_amount = inflation_amount * 80 / 100; // 80% goes to rewards
|
||||
|
||||
assert_eq!(
|
||||
final_rewards_balance,
|
||||
initial_rewards_balance + rewards_amount,
|
||||
"Inflation should have been minted to rewards account"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inflation_calculation_with_different_rates() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
// Test with different inflation amounts
|
||||
for inflation_amount in [1_000_000u128, 5_000_000u128, 10_000_000u128] {
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(inflation_amount);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let initial_balance = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Add some reward points
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100)]);
|
||||
|
||||
// Trigger era end
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
// Verify correct amount was minted (80% to rewards, 20% to treasury)
|
||||
let final_balance = Balances::free_balance(&rewards_account);
|
||||
let rewards_amount = inflation_amount * 80 / 100;
|
||||
assert_eq!(
|
||||
final_balance - initial_balance,
|
||||
rewards_amount,
|
||||
"Incorrect inflation amount minted for rate {}",
|
||||
inflation_amount
|
||||
);
|
||||
|
||||
// Clean up for next iteration
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 2,
|
||||
start: None,
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_inflation_with_zero_points() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(10_000_000);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let initial_balance = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Don't add any reward points (or add zero points)
|
||||
// This should prevent inflation from being minted
|
||||
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
// Verify no inflation was minted because there were no reward points
|
||||
let final_balance = Balances::free_balance(&rewards_account);
|
||||
assert_eq!(
|
||||
final_balance, initial_balance,
|
||||
"No inflation should be minted when there are no reward points"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inflation_calculation_accuracy() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
// Test that the inflation calculation doesn't lose precision
|
||||
let expected_inflation = 12_345_678_901_234u128; // Large number with precision
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(expected_inflation);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let initial_balance = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Add reward points
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100), (2, 200)]);
|
||||
|
||||
// Trigger era end
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
// Verify amount was minted (80% to rewards, minor rounding acceptable)
|
||||
let final_balance = Balances::free_balance(&rewards_account);
|
||||
let rewards_amount = expected_inflation * 80 / 100;
|
||||
let actual_minted = final_balance - initial_balance;
|
||||
// Allow 1 unit difference due to Perbill rounding in treasury calculation
|
||||
assert!(
|
||||
actual_minted >= rewards_amount.saturating_sub(1) &&
|
||||
actual_minted <= rewards_amount + 1,
|
||||
"Inflation calculation should maintain precision (within 1 unit). Expected: {}, Got: {}",
|
||||
rewards_amount,
|
||||
actual_minted
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Treasury Allocation Tests
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn test_treasury_receives_20_percent_of_inflation() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
let base_inflation = 1_000_000u128;
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(base_inflation);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
|
||||
let initial_rewards = Balances::free_balance(&rewards_account);
|
||||
let initial_treasury = Balances::free_balance(&treasury_account);
|
||||
|
||||
// Add validators to trigger inflation
|
||||
ExternalValidatorsRewards::reward_by_ids([
|
||||
(1, 100),
|
||||
(2, 100),
|
||||
(3, 100),
|
||||
(4, 100),
|
||||
(5, 100),
|
||||
]);
|
||||
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let final_rewards = Balances::free_balance(&rewards_account);
|
||||
let final_treasury = Balances::free_balance(&treasury_account);
|
||||
|
||||
let rewards_received = final_rewards - initial_rewards;
|
||||
let treasury_received = final_treasury - initial_treasury;
|
||||
|
||||
// Treasury should receive 20% of total inflation
|
||||
let expected_treasury = base_inflation * 20 / 100;
|
||||
let expected_rewards = base_inflation * 80 / 100;
|
||||
|
||||
assert_eq!(
|
||||
treasury_received, expected_treasury,
|
||||
"Treasury should receive exactly 20% of inflation"
|
||||
);
|
||||
assert_eq!(
|
||||
rewards_received, expected_rewards,
|
||||
"Rewards account should receive exactly 80% of inflation"
|
||||
);
|
||||
assert_eq!(
|
||||
treasury_received + rewards_received,
|
||||
base_inflation,
|
||||
"Total minted should equal base inflation"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_treasury_allocation_with_different_amounts() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
|
||||
for (era, inflation) in [(1, 100_000u128), (2, 5_000_000u128), (3, 999_999_999u128)] {
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: era,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(inflation);
|
||||
});
|
||||
|
||||
let treasury_before = Balances::free_balance(&treasury_account);
|
||||
let rewards_before = Balances::free_balance(&rewards_account);
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100), (2, 100)]);
|
||||
ExternalValidatorsRewards::on_era_end(era);
|
||||
|
||||
let treasury_after = Balances::free_balance(&treasury_account);
|
||||
let rewards_after = Balances::free_balance(&rewards_account);
|
||||
|
||||
let treasury_increase = treasury_after - treasury_before;
|
||||
let rewards_increase = rewards_after - rewards_before;
|
||||
|
||||
// Treasury gets mul_floor of 20%, rewards gets the remainder
|
||||
// So treasury + rewards should equal total inflation
|
||||
assert_eq!(
|
||||
treasury_increase + rewards_increase,
|
||||
inflation,
|
||||
"Era {}: Treasury + Rewards should equal total inflation",
|
||||
era
|
||||
);
|
||||
|
||||
// Treasury should be approximately 20% (within 1 unit due to rounding)
|
||||
let expected_treasury = inflation * 20 / 100;
|
||||
assert!(
|
||||
treasury_increase >= expected_treasury.saturating_sub(1)
|
||||
&& treasury_increase <= expected_treasury + 1,
|
||||
"Era {}: Treasury should get approximately 20%",
|
||||
era
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_treasury_allocation_maintains_precision() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
// Use prime number that doesn't divide evenly by 5 (20%)
|
||||
let inflation = 1_234_567u128;
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(inflation);
|
||||
});
|
||||
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
|
||||
let treasury_before = Balances::free_balance(&treasury_account);
|
||||
let rewards_before = Balances::free_balance(&rewards_account);
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100)]);
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let treasury_after = Balances::free_balance(&treasury_account);
|
||||
let rewards_after = Balances::free_balance(&rewards_account);
|
||||
|
||||
let treasury_increase = treasury_after - treasury_before;
|
||||
let rewards_increase = rewards_after - rewards_before;
|
||||
let total_minted = treasury_increase + rewards_increase;
|
||||
|
||||
// Total minted should equal total inflation (no rounding loss to exceed inflation)
|
||||
assert!(
|
||||
total_minted <= inflation,
|
||||
"Total minted should not exceed inflation due to rounding"
|
||||
);
|
||||
|
||||
// But should be very close (within 1 token for rounding)
|
||||
assert!(
|
||||
inflation - total_minted < 100,
|
||||
"Rounding loss should be minimal"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Edge Case Tests
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn test_single_validator_network() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(1_000_000);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let initial_balance = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Only one validator participates
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100)]);
|
||||
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let final_balance = Balances::free_balance(&rewards_account);
|
||||
let inflation_received = final_balance - initial_balance;
|
||||
|
||||
// Single validator should still trigger full inflation (for rewards portion)
|
||||
assert!(
|
||||
inflation_received > 0,
|
||||
"Single validator should receive rewards"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_large_inflation_no_overflow() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
// Use close to u128::MAX to test overflow protection
|
||||
let large_inflation = u128::MAX / 2;
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(large_inflation);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
|
||||
let rewards_before = Balances::free_balance(&rewards_account);
|
||||
let treasury_before = Balances::free_balance(&treasury_account);
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100)]);
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let rewards_after = Balances::free_balance(&rewards_account);
|
||||
let treasury_after = Balances::free_balance(&treasury_account);
|
||||
|
||||
// Should not panic or overflow
|
||||
assert!(rewards_after >= rewards_before, "Rewards should increase");
|
||||
assert!(
|
||||
treasury_after >= treasury_before,
|
||||
"Treasury should increase"
|
||||
);
|
||||
|
||||
// Total should not exceed input
|
||||
let total_increase = (rewards_after - rewards_before) + (treasury_after - treasury_before);
|
||||
assert!(
|
||||
total_increase <= large_inflation,
|
||||
"Total minted should not exceed inflation amount"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_very_small_inflation_amounts() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
// Test with very small amounts
|
||||
for tiny_amount in [1u128, 2u128, 5u128, 10u128] {
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: tiny_amount as u32,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(tiny_amount);
|
||||
});
|
||||
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
|
||||
let rewards_before = Balances::free_balance(&rewards_account);
|
||||
let treasury_before = Balances::free_balance(&treasury_account);
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100)]);
|
||||
ExternalValidatorsRewards::on_era_end(tiny_amount as u32);
|
||||
|
||||
let rewards_after = Balances::free_balance(&rewards_account);
|
||||
let treasury_after = Balances::free_balance(&treasury_account);
|
||||
|
||||
let total_minted =
|
||||
(rewards_after - rewards_before) + (treasury_after - treasury_before);
|
||||
|
||||
// Should handle small amounts gracefully (may round to 0 for treasury)
|
||||
assert!(
|
||||
total_minted <= tiny_amount,
|
||||
"Amount {} should not exceed inflation",
|
||||
tiny_amount
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Integration and Regression Tests
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn test_consistent_inflation_across_eras() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
let base_inflation = 5_000_000u128;
|
||||
let rewards_account = RewardsEthereumSovereignAccount::get();
|
||||
|
||||
// Run multiple eras with identical conditions
|
||||
for era in 1..=5 {
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: era,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(base_inflation);
|
||||
});
|
||||
|
||||
let balance_before = Balances::free_balance(&rewards_account);
|
||||
|
||||
// Same participation every era
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100), (2, 100), (3, 100)]);
|
||||
|
||||
ExternalValidatorsRewards::on_era_end(era);
|
||||
|
||||
let balance_after = Balances::free_balance(&rewards_account);
|
||||
let inflation = balance_after - balance_before;
|
||||
|
||||
// Each era should mint the same amount given identical conditions
|
||||
let expected = base_inflation * 80 / 100; // 80% to rewards account
|
||||
assert_eq!(
|
||||
inflation, expected,
|
||||
"Era {}: Inflation should be consistent across eras",
|
||||
era
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_unexpected_balance_changes() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(1_000_000);
|
||||
});
|
||||
|
||||
// Check balances of non-participating accounts don't change
|
||||
let observer_account = 99u64;
|
||||
let _ = Balances::mint_into(&observer_account, 1000); // Give it some balance
|
||||
|
||||
let observer_balance_before = Balances::free_balance(&observer_account);
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([(1, 100), (2, 100)]);
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let observer_balance_after = Balances::free_balance(&observer_account);
|
||||
|
||||
assert_eq!(
|
||||
observer_balance_before, observer_balance_after,
|
||||
"Non-participating accounts should not be affected"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_total_issuance_increases_correctly() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1);
|
||||
|
||||
let inflation = 10_000_000u128;
|
||||
|
||||
Mock::mutate(|mock| {
|
||||
mock.active_era = Some(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
});
|
||||
mock.era_inflation = Some(inflation);
|
||||
});
|
||||
|
||||
let total_issuance_before = Balances::total_issuance();
|
||||
|
||||
ExternalValidatorsRewards::reward_by_ids([
|
||||
(1, 100),
|
||||
(2, 100),
|
||||
(3, 100),
|
||||
(4, 100),
|
||||
(5, 100),
|
||||
]);
|
||||
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
|
||||
let total_issuance_after = Balances::total_issuance();
|
||||
|
||||
// Total issuance should increase by exactly the inflation amount
|
||||
assert_eq!(
|
||||
total_issuance_after - total_issuance_before,
|
||||
inflation,
|
||||
"Total issuance should increase by inflation amount"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ pub mod time {
|
|||
pub const HOURS: BlockNumber = MINUTES * 60;
|
||||
pub const DAYS: BlockNumber = HOURS * 24;
|
||||
pub const WEEKS: BlockNumber = DAYS * 7;
|
||||
|
||||
/// Milliseconds per year (365.25 days to account for leap years)
|
||||
/// Used for inflation calculations
|
||||
pub const MILLISECONDS_PER_YEAR: u128 = 31_557_600_000;
|
||||
}
|
||||
|
||||
pub mod gas {
|
||||
|
|
|
|||
635
operator/runtime/common/src/inflation.rs
Normal file
635
operator/runtime/common/src/inflation.rs
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
// Copyright 2025 DataHaven
|
||||
// This file is part of DataHaven.
|
||||
//
|
||||
// DataHaven is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// DataHaven is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Common inflation handling utilities for validator rewards.
|
||||
//!
|
||||
//! This module provides reusable implementations for calculating and minting
|
||||
//! inflation-based rewards that can be shared across different runtime configurations.
|
||||
|
||||
use crate::constants::time::MILLISECONDS_PER_YEAR;
|
||||
use frame_support::traits::{fungible::Inspect, Get};
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
/// Generic era inflation provider that calculates per-era inflation based on annual inflation rate.
|
||||
///
|
||||
/// # Type Parameters
|
||||
/// * `R` - Runtime type that provides access to necessary pallets and configuration
|
||||
/// * `Balances` - Pallet implementing fungible::Inspect for total issuance
|
||||
/// * `AnnualRate` - Get<Perbill> providing the target annual inflation rate
|
||||
/// * `SessionsPerEra` - Get<u32> providing the number of sessions per era
|
||||
/// * `BlocksPerSession` - Get<u32> providing the number of blocks per session
|
||||
/// * `MillisecsPerBlock` - Get<u64> providing milliseconds per block
|
||||
///
|
||||
/// # Calculation
|
||||
/// 1. Gets total token issuance from Balances pallet
|
||||
/// 2. Retrieves annual inflation rate from runtime parameters
|
||||
/// 3. Calculates eras per year based on era duration
|
||||
/// 4. Divides annual inflation by eras per year to get per-era amount
|
||||
pub struct ExternalRewardsEraInflationProvider<
|
||||
Balances,
|
||||
AnnualRate,
|
||||
SessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
>(
|
||||
sp_std::marker::PhantomData<(
|
||||
Balances,
|
||||
AnnualRate,
|
||||
SessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<Balances, AnnualRate, SessionsPerEra, BlocksPerSession, MillisecsPerBlock> Get<u128>
|
||||
for ExternalRewardsEraInflationProvider<
|
||||
Balances,
|
||||
AnnualRate,
|
||||
SessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
>
|
||||
where
|
||||
Balances: Inspect<crate::AccountId, Balance = u128>,
|
||||
AnnualRate: Get<Perbill>,
|
||||
SessionsPerEra: Get<u32>,
|
||||
BlocksPerSession: Get<u32>,
|
||||
MillisecsPerBlock: Get<u64>,
|
||||
{
|
||||
fn get() -> u128 {
|
||||
use sp_runtime::traits::Zero;
|
||||
|
||||
let total_issuance = Balances::total_issuance();
|
||||
if total_issuance.is_zero() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let annual_inflation_rate = AnnualRate::get();
|
||||
|
||||
// Calculate eras per year
|
||||
// - SessionsPerEra: number of sessions in an era
|
||||
// - BlocksPerSession: number of blocks in an epoch (session)
|
||||
// - MillisecsPerBlock: milliseconds per block (6000ms = 6s)
|
||||
// - Year in milliseconds: 365.25 * 24 * 60 * 60 * 1000
|
||||
|
||||
let sessions_per_era = SessionsPerEra::get() as u128;
|
||||
let blocks_per_session = BlocksPerSession::get() as u128;
|
||||
let millisecs_per_block = MillisecsPerBlock::get() as u128;
|
||||
|
||||
let millisecs_per_era = sessions_per_era
|
||||
.saturating_mul(blocks_per_session)
|
||||
.saturating_mul(millisecs_per_block);
|
||||
|
||||
if millisecs_per_era.is_zero() {
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Invalid era duration configuration"
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let eras_per_year = MILLISECONDS_PER_YEAR.saturating_div(millisecs_per_era);
|
||||
if eras_per_year.is_zero() {
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Eras per year is zero, check configuration"
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate per-era inflation
|
||||
let annual_inflation = annual_inflation_rate.mul_floor(total_issuance);
|
||||
let per_era_inflation = annual_inflation.saturating_div(eras_per_year);
|
||||
|
||||
log::info!(
|
||||
target: "ext_validators_rewards",
|
||||
"Per-era inflation: {}",
|
||||
per_era_inflation
|
||||
);
|
||||
|
||||
per_era_inflation
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic implementation of inflation handler that mints tokens and splits between rewards and treasury.
|
||||
///
|
||||
/// # Type Parameters
|
||||
/// * `Balances` - Currency pallet implementing fungible::Mutate for minting
|
||||
/// * `TreasuryProportion` - Get<Perbill> providing the treasury allocation percentage
|
||||
/// * `TreasuryAccount` - Get<AccountId> providing the treasury account
|
||||
///
|
||||
/// # Functionality
|
||||
/// 1. Validates the total amount is non-zero
|
||||
/// 2. Calculates treasury allocation based on configured proportion
|
||||
/// 3. Mints rewards portion to the rewards account
|
||||
/// 4. Mints treasury portion to the treasury account
|
||||
///
|
||||
/// This struct provides a mint_inflation method that can be called from wrapper implementations
|
||||
/// in your runtime to avoid circular dependencies.
|
||||
pub struct ExternalRewardsInflationHandler<Balances, TreasuryProportion, TreasuryAccount>(
|
||||
sp_std::marker::PhantomData<(Balances, TreasuryProportion, TreasuryAccount)>,
|
||||
);
|
||||
|
||||
impl<Balances, TreasuryProportion, TreasuryAccount>
|
||||
ExternalRewardsInflationHandler<Balances, TreasuryProportion, TreasuryAccount>
|
||||
where
|
||||
Balances: frame_support::traits::fungible::Mutate<crate::AccountId, Balance = u128>,
|
||||
TreasuryProportion: Get<Perbill>,
|
||||
TreasuryAccount: Get<crate::AccountId>,
|
||||
{
|
||||
/// Mints inflation tokens and splits them between rewards and treasury accounts
|
||||
pub fn mint_inflation(
|
||||
rewards_account: &crate::AccountId,
|
||||
total_amount: u128,
|
||||
) -> sp_runtime::DispatchResult {
|
||||
use sp_runtime::traits::Zero;
|
||||
|
||||
if total_amount.is_zero() {
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Attempted to mint zero inflation"
|
||||
);
|
||||
return Err(sp_runtime::DispatchError::Other(
|
||||
"Cannot mint zero inflation",
|
||||
));
|
||||
}
|
||||
|
||||
// Get treasury allocation proportion
|
||||
let treasury_proportion = TreasuryProportion::get();
|
||||
|
||||
// Calculate amounts
|
||||
let treasury_amount = treasury_proportion.mul_floor(total_amount);
|
||||
let rewards_amount = total_amount.saturating_sub(treasury_amount);
|
||||
|
||||
log::debug!(
|
||||
target: "ext_validators_rewards",
|
||||
"Minting inflation: total={}, treasury={}, rewards={}",
|
||||
total_amount,
|
||||
treasury_amount,
|
||||
rewards_amount
|
||||
);
|
||||
|
||||
// Mint rewards to the rewards account
|
||||
if !rewards_amount.is_zero() {
|
||||
Balances::mint_into(rewards_account, rewards_amount).map_err(|e| {
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Failed to mint rewards inflation: {:?}",
|
||||
e
|
||||
);
|
||||
sp_runtime::DispatchError::Other("Failed to mint rewards inflation")
|
||||
})?;
|
||||
}
|
||||
|
||||
// Mint treasury portion if non-zero
|
||||
if !treasury_amount.is_zero() {
|
||||
let treasury_account = TreasuryAccount::get();
|
||||
Balances::mint_into(&treasury_account, treasury_amount).map_err(|e| {
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Failed to mint treasury inflation: {:?}",
|
||||
e
|
||||
);
|
||||
sp_runtime::DispatchError::Other("Failed to mint treasury inflation")
|
||||
})?;
|
||||
|
||||
log::info!(
|
||||
target: "ext_validators_rewards",
|
||||
"Successfully minted {} to treasury from inflation",
|
||||
treasury_amount
|
||||
);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: "ext_validators_rewards",
|
||||
"Successfully minted {} total inflation ({} to rewards, {} to treasury)",
|
||||
total_amount,
|
||||
rewards_amount,
|
||||
treasury_amount
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_support::{
|
||||
parameter_types,
|
||||
traits::fungible::{Inspect, Mutate, Unbalanced},
|
||||
};
|
||||
use sp_runtime::Perbill;
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Mock balances storage
|
||||
thread_local! {
|
||||
static TOTAL_ISSUANCE: RefCell<u128> = const { RefCell::new(0) };
|
||||
static BALANCES: RefCell<std::collections::HashMap<crate::AccountId, u128>> = RefCell::new(std::collections::HashMap::new());
|
||||
}
|
||||
|
||||
struct MockBalances;
|
||||
|
||||
impl Inspect<crate::AccountId> for MockBalances {
|
||||
type Balance = u128;
|
||||
|
||||
fn total_issuance() -> Self::Balance {
|
||||
TOTAL_ISSUANCE.with(|v| *v.borrow())
|
||||
}
|
||||
|
||||
fn minimum_balance() -> Self::Balance {
|
||||
0
|
||||
}
|
||||
|
||||
fn balance(_who: &crate::AccountId) -> Self::Balance {
|
||||
0
|
||||
}
|
||||
|
||||
fn total_balance(_who: &crate::AccountId) -> Self::Balance {
|
||||
0
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
_who: &crate::AccountId,
|
||||
_preservation: frame_support::traits::tokens::Preservation,
|
||||
_force: frame_support::traits::tokens::Fortitude,
|
||||
) -> Self::Balance {
|
||||
0
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
_who: &crate::AccountId,
|
||||
_amount: Self::Balance,
|
||||
_provenance: frame_support::traits::tokens::Provenance,
|
||||
) -> frame_support::traits::tokens::DepositConsequence {
|
||||
frame_support::traits::tokens::DepositConsequence::Success
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
_who: &crate::AccountId,
|
||||
_amount: Self::Balance,
|
||||
) -> frame_support::traits::tokens::WithdrawConsequence<Self::Balance> {
|
||||
frame_support::traits::tokens::WithdrawConsequence::Success
|
||||
}
|
||||
}
|
||||
|
||||
impl Unbalanced<crate::AccountId> for MockBalances {
|
||||
fn write_balance(
|
||||
_who: &crate::AccountId,
|
||||
_amount: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, sp_runtime::DispatchError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn set_total_issuance(amount: Self::Balance) -> () {
|
||||
TOTAL_ISSUANCE.with(|v| *v.borrow_mut() = amount);
|
||||
}
|
||||
|
||||
fn handle_dust(_dust: frame_support::traits::fungible::Dust<crate::AccountId, Self>) {
|
||||
// No-op for tests
|
||||
}
|
||||
}
|
||||
|
||||
impl Mutate<crate::AccountId> for MockBalances {
|
||||
fn mint_into(
|
||||
who: &crate::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Balance, sp_runtime::DispatchError> {
|
||||
BALANCES.with(|b| {
|
||||
let mut balances = b.borrow_mut();
|
||||
let balance = balances.entry(*who).or_insert(0);
|
||||
*balance = balance.saturating_add(amount);
|
||||
});
|
||||
TOTAL_ISSUANCE.with(|v| {
|
||||
let mut issuance = v.borrow_mut();
|
||||
*issuance = issuance.saturating_add(amount);
|
||||
});
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
fn burn_from(
|
||||
_who: &crate::AccountId,
|
||||
_amount: Self::Balance,
|
||||
_preservation: frame_support::traits::tokens::Preservation,
|
||||
_precision: frame_support::traits::tokens::Precision,
|
||||
_force: frame_support::traits::tokens::Fortitude,
|
||||
) -> Result<Self::Balance, sp_runtime::DispatchError> {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn treasury_account_id() -> crate::AccountId {
|
||||
crate::AccountId::from([1u8; 20])
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub TreasuryAccountId: crate::AccountId = treasury_account_id();
|
||||
}
|
||||
|
||||
fn reset_balances() {
|
||||
TOTAL_ISSUANCE.with(|v| *v.borrow_mut() = 0);
|
||||
BALANCES.with(|b| b.borrow_mut().clear());
|
||||
}
|
||||
|
||||
fn get_balance(who: &crate::AccountId) -> u128 {
|
||||
BALANCES.with(|b| *b.borrow().get(who).unwrap_or(&0))
|
||||
}
|
||||
|
||||
fn set_total_issuance(amount: u128) {
|
||||
TOTAL_ISSUANCE.with(|v| *v.borrow_mut() = amount);
|
||||
}
|
||||
|
||||
mod era_inflation_provider {
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub const AnnualRate5Percent: Perbill = Perbill::from_percent(5);
|
||||
pub const AnnualRate10Percent: Perbill = Perbill::from_percent(10);
|
||||
pub const SessionsPerEra: u32 = 6;
|
||||
pub const BlocksPerSession: u32 = 600;
|
||||
pub const MillisecsPerBlock: u64 = 6000;
|
||||
}
|
||||
|
||||
type TestInflationProvider = ExternalRewardsEraInflationProvider<
|
||||
MockBalances,
|
||||
AnnualRate5Percent,
|
||||
SessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
>;
|
||||
|
||||
type TestInflationProvider10Pct = ExternalRewardsEraInflationProvider<
|
||||
MockBalances,
|
||||
AnnualRate10Percent,
|
||||
SessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
>;
|
||||
|
||||
#[test]
|
||||
fn returns_zero_when_total_issuance_is_zero() {
|
||||
reset_balances();
|
||||
set_total_issuance(0);
|
||||
assert_eq!(TestInflationProvider::get(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_correct_per_era_inflation_5_percent() {
|
||||
reset_balances();
|
||||
// 1 billion tokens total issuance
|
||||
let total_issuance = 1_000_000_000_000_000_000u128;
|
||||
set_total_issuance(total_issuance);
|
||||
|
||||
// With 6 sessions per era, 600 blocks per session, 6000ms per block:
|
||||
// millisecs_per_era = 6 * 600 * 6000 = 21,600,000ms = 6 hours
|
||||
// eras_per_year = 31,557,600,000 / 21,600,000 = 1461 eras
|
||||
// annual_inflation = 5% of 1B = 50M
|
||||
// per_era_inflation = 50M / 1461 ≈ 34,223,312
|
||||
|
||||
let per_era_inflation = TestInflationProvider::get();
|
||||
|
||||
// Expected: 5% of total_issuance / number of eras per year
|
||||
// With the calculation: total_issuance * rate * millisecs_per_era / MILLISECONDS_PER_YEAR
|
||||
let expected = 34_223_134_839_151u128;
|
||||
assert_eq!(per_era_inflation, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calculates_correct_per_era_inflation_10_percent() {
|
||||
reset_balances();
|
||||
let total_issuance = 1_000_000_000_000_000_000u128;
|
||||
set_total_issuance(total_issuance);
|
||||
|
||||
let per_era_inflation = TestInflationProvider10Pct::get();
|
||||
|
||||
// Expected: 10% of total_issuance / number of eras per year
|
||||
let expected = 68_446_269_678_302u128;
|
||||
assert_eq!(per_era_inflation, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scales_with_total_issuance() {
|
||||
reset_balances();
|
||||
|
||||
// Test with 100M tokens
|
||||
set_total_issuance(100_000_000_000_000_000u128);
|
||||
let inflation_100m = TestInflationProvider::get();
|
||||
|
||||
// Test with 1B tokens (10x more)
|
||||
set_total_issuance(1_000_000_000_000_000_000u128);
|
||||
let inflation_1b = TestInflationProvider::get();
|
||||
|
||||
// Inflation should scale proportionally (allow 1 unit tolerance for rounding)
|
||||
let expected = inflation_100m * 10;
|
||||
assert!(
|
||||
inflation_1b >= expected.saturating_sub(1) && inflation_1b <= expected + 1,
|
||||
"Expected inflation to be ~{}, got {}",
|
||||
expected,
|
||||
inflation_1b
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_different_era_durations() {
|
||||
reset_balances();
|
||||
set_total_issuance(1_000_000_000_000_000_000u128);
|
||||
|
||||
parameter_types! {
|
||||
pub const LongEraSessionsPerEra: u32 = 12; // Double the sessions
|
||||
}
|
||||
|
||||
type LongEraProvider = ExternalRewardsEraInflationProvider<
|
||||
MockBalances,
|
||||
AnnualRate5Percent,
|
||||
LongEraSessionsPerEra,
|
||||
BlocksPerSession,
|
||||
MillisecsPerBlock,
|
||||
>;
|
||||
|
||||
let standard_era = TestInflationProvider::get();
|
||||
let long_era = LongEraProvider::get();
|
||||
|
||||
// Longer eras should have roughly 2x the inflation per era
|
||||
// (since there are half as many eras per year)
|
||||
assert!(long_era > standard_era * 19 / 10); // Allow some rounding tolerance
|
||||
assert!(long_era < standard_era * 21 / 10);
|
||||
}
|
||||
}
|
||||
|
||||
mod inflation_handler {
|
||||
use super::*;
|
||||
|
||||
parameter_types! {
|
||||
pub const TreasuryProportion20Pct: Perbill = Perbill::from_percent(20);
|
||||
pub const TreasuryProportion50Pct: Perbill = Perbill::from_percent(50);
|
||||
pub const TreasuryProportion0Pct: Perbill = Perbill::from_percent(0);
|
||||
pub const TreasuryProportion100Pct: Perbill = Perbill::from_percent(100);
|
||||
}
|
||||
|
||||
type TestHandler = ExternalRewardsInflationHandler<
|
||||
MockBalances,
|
||||
TreasuryProportion20Pct,
|
||||
TreasuryAccountId,
|
||||
>;
|
||||
|
||||
type TestHandler50Pct = ExternalRewardsInflationHandler<
|
||||
MockBalances,
|
||||
TreasuryProportion50Pct,
|
||||
TreasuryAccountId,
|
||||
>;
|
||||
|
||||
type TestHandler0Pct = ExternalRewardsInflationHandler<
|
||||
MockBalances,
|
||||
TreasuryProportion0Pct,
|
||||
TreasuryAccountId,
|
||||
>;
|
||||
|
||||
type TestHandler100Pct = ExternalRewardsInflationHandler<
|
||||
MockBalances,
|
||||
TreasuryProportion100Pct,
|
||||
TreasuryAccountId,
|
||||
>;
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_amount() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
|
||||
let result = TestHandler::mint_inflation(&rewards_account, 0);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_inflation_correctly_20_percent_treasury() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = 1_000_000u128;
|
||||
|
||||
let result = TestHandler::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// 20% to treasury, 80% to rewards
|
||||
assert_eq!(treasury_balance, 200_000);
|
||||
assert_eq!(rewards_balance, 800_000);
|
||||
assert_eq!(rewards_balance + treasury_balance, total_inflation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_inflation_correctly_50_percent_treasury() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = 1_000_000u128;
|
||||
|
||||
let result = TestHandler50Pct::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// 50% to treasury, 50% to rewards
|
||||
assert_eq!(treasury_balance, 500_000);
|
||||
assert_eq!(rewards_balance, 500_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_zero_percent_treasury() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = 1_000_000u128;
|
||||
|
||||
let result = TestHandler0Pct::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// 0% to treasury, 100% to rewards
|
||||
assert_eq!(treasury_balance, 0);
|
||||
assert_eq!(rewards_balance, total_inflation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_hundred_percent_treasury() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = 1_000_000u128;
|
||||
|
||||
let result = TestHandler100Pct::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// 100% to treasury, 0% to rewards
|
||||
assert_eq!(treasury_balance, total_inflation);
|
||||
assert_eq!(rewards_balance, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_total_issuance() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = 1_000_000u128;
|
||||
|
||||
let issuance_before = MockBalances::total_issuance();
|
||||
|
||||
let result = TestHandler::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let issuance_after = MockBalances::total_issuance();
|
||||
|
||||
assert_eq!(issuance_after, issuance_before + total_inflation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_large_amounts() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
let total_inflation = u128::MAX / 2; // Very large amount
|
||||
|
||||
let result = TestHandler::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// Verify proportions are maintained even with large numbers
|
||||
assert_eq!(rewards_balance + treasury_balance, total_inflation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_rounding_correctly() {
|
||||
reset_balances();
|
||||
let rewards_account = crate::AccountId::from([2u8; 20]);
|
||||
// Amount that will cause rounding: 100 with 20% treasury
|
||||
let total_inflation = 100u128;
|
||||
|
||||
let result = TestHandler::mint_inflation(&rewards_account, total_inflation);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let rewards_balance = get_balance(&rewards_account);
|
||||
let treasury_balance = get_balance(&TreasuryAccountId::get());
|
||||
|
||||
// 20% of 100 = 20, rewards = 80
|
||||
assert_eq!(treasury_balance, 20);
|
||||
assert_eq!(rewards_balance, 80);
|
||||
assert_eq!(rewards_balance + treasury_balance, total_inflation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ pub use constants::*;
|
|||
pub mod benchmarking;
|
||||
pub mod deal_with_fees;
|
||||
pub mod impl_on_charge_evm_transaction;
|
||||
pub mod inflation;
|
||||
pub mod migrations;
|
||||
pub use migrations::*;
|
||||
pub mod safe_mode;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use super::{
|
|||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
|
||||
/// A description of our proxy types.
|
||||
/// Proxy types are used to restrict the calls that can be made by a proxy account.
|
||||
|
|
@ -897,6 +897,14 @@ parameter_types! {
|
|||
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
|
||||
pub TreasuryAccount: AccountId = Treasury::account_id();
|
||||
pub const MaxSpendBalance: crate::Balance = crate::Balance::max_value();
|
||||
|
||||
/// PalletId for the External Validator Rewards account.
|
||||
/// This account receives minted inflation tokens before they are bridged to Ethereum
|
||||
/// for distribution to validators via EigenLayer.
|
||||
///
|
||||
/// Governance/Sudo can transfer funds using: pallet_balances::force_transfer
|
||||
pub const ExternalValidatorRewardsId: PalletId = PalletId(*b"dh/evrew");
|
||||
pub ExternalValidatorRewardsAccount: AccountId = ExternalValidatorRewardsId::get().into_account_truncating();
|
||||
}
|
||||
|
||||
type RootOrTreasuryCouncilOrigin = EitherOfDiverse<
|
||||
|
|
@ -1416,6 +1424,41 @@ impl Get<Vec<AccountId>> for GetWhitelistedValidators {
|
|||
}
|
||||
}
|
||||
|
||||
/// Type alias for the era inflation provider using common runtime implementation.
|
||||
///
|
||||
/// Calculates per-era inflation based on:
|
||||
/// - Total token issuance (from Balances pallet)
|
||||
/// - Annual inflation rate (from InflationTargetedAnnualRate dynamic parameter)
|
||||
/// - Era duration calculated from SessionsPerEra, EpochDurationInBlocks, and MILLISECS_PER_BLOCK
|
||||
pub type ExternalRewardsEraInflationProvider =
|
||||
datahaven_runtime_common::inflation::ExternalRewardsEraInflationProvider<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTargetedAnnualRate,
|
||||
SessionsPerEra,
|
||||
EpochDurationInBlocks,
|
||||
ConstU64<MILLISECS_PER_BLOCK>,
|
||||
>;
|
||||
|
||||
/// Wrapper struct for the inflation handler using common runtime implementation.
|
||||
///
|
||||
/// Handles minting of inflation tokens by:
|
||||
/// 1. Splitting total inflation between rewards and treasury based on InflationTreasuryProportion
|
||||
/// 2. Minting rewards portion to the rewards account
|
||||
/// 3. Minting treasury portion to the treasury account
|
||||
pub struct ExternalRewardsInflationHandler;
|
||||
|
||||
impl pallet_external_validators_rewards::types::HandleInflation<AccountId>
|
||||
for ExternalRewardsInflationHandler
|
||||
{
|
||||
fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult {
|
||||
datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion,
|
||||
TreasuryAccount,
|
||||
>::mint_inflation(who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Stub SendMessage implementation for rewards pallet
|
||||
pub struct RewardsSendAdapter;
|
||||
impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapter {
|
||||
|
|
@ -1479,16 +1522,21 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
type BackingPoints = ConstU32<20>;
|
||||
type DisputeStatementPoints = ConstU32<20>;
|
||||
type EraInflationProvider = ConstU128<0>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type Hashing = Keccak256;
|
||||
type Currency = Balances;
|
||||
type RewardsEthereumSovereignAccount = TreasuryAccount;
|
||||
type RewardsEthereumSovereignAccount = ExternalValidatorRewardsAccount;
|
||||
type SendMessage = RewardsSendAdapter;
|
||||
type HandleInflation = ();
|
||||
type HandleInflation = ExternalRewardsInflationHandler;
|
||||
type WeightInfo = mainnet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
|
|
|
|||
|
|
@ -339,6 +339,24 @@ pub mod dynamic_params {
|
|||
#[allow(non_upper_case_globals)]
|
||||
/// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests.
|
||||
pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0);
|
||||
|
||||
// ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗
|
||||
|
||||
#[codec(index = 37)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Targeted annual inflation rate.
|
||||
/// Default: 5% per annum
|
||||
/// This rate is divided across all eras in a year to calculate per-era inflation.
|
||||
pub static InflationTargetedAnnualRate: Perbill = Perbill::from_percent(5);
|
||||
|
||||
#[codec(index = 38)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Proportion of inflation rewards allocated to the treasury.
|
||||
/// Default: 20% of minted rewards go to treasury, 80% to validator rewards
|
||||
/// The treasury portion is minted separately and sent to the treasury account.
|
||||
pub static InflationTreasuryProportion: Perbill = Perbill::from_percent(20);
|
||||
|
||||
// ╚══════════════════════ Validator Rewards Inflation ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use super::{
|
|||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
|
||||
/// A description of our proxy types.
|
||||
/// Proxy types are used to restrict the calls that can be made by a proxy account.
|
||||
|
|
@ -900,6 +900,14 @@ parameter_types! {
|
|||
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
|
||||
pub TreasuryAccount: AccountId = Treasury::account_id();
|
||||
pub const MaxSpendBalance: crate::Balance = crate::Balance::max_value();
|
||||
|
||||
/// PalletId for the External Validator Rewards account.
|
||||
/// This account receives minted inflation tokens before they are bridged to Ethereum
|
||||
/// for distribution to validators via EigenLayer.
|
||||
///
|
||||
/// Governance/Sudo can transfer funds using: pallet_balances::force_transfer
|
||||
pub const ExternalValidatorRewardsId: PalletId = PalletId(*b"dh/evrew");
|
||||
pub ExternalValidatorRewardsAccount: AccountId = ExternalValidatorRewardsId::get().into_account_truncating();
|
||||
}
|
||||
|
||||
type RootOrTreasuryCouncilOrigin = EitherOfDiverse<
|
||||
|
|
@ -1421,6 +1429,41 @@ impl Get<Vec<AccountId>> for GetWhitelistedValidators {
|
|||
}
|
||||
}
|
||||
|
||||
/// Type alias for the era inflation provider using common runtime implementation.
|
||||
///
|
||||
/// Calculates per-era inflation based on:
|
||||
/// - Total token issuance (from Balances pallet)
|
||||
/// - Annual inflation rate (from InflationTargetedAnnualRate dynamic parameter)
|
||||
/// - Era duration calculated from SessionsPerEra, EpochDurationInBlocks, and MILLISECS_PER_BLOCK
|
||||
pub type ExternalRewardsEraInflationProvider =
|
||||
datahaven_runtime_common::inflation::ExternalRewardsEraInflationProvider<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTargetedAnnualRate,
|
||||
SessionsPerEra,
|
||||
EpochDurationInBlocks,
|
||||
ConstU64<MILLISECS_PER_BLOCK>,
|
||||
>;
|
||||
|
||||
/// Wrapper struct for the inflation handler using common runtime implementation.
|
||||
///
|
||||
/// Handles minting of inflation tokens by:
|
||||
/// 1. Splitting total inflation between rewards and treasury based on InflationTreasuryProportion
|
||||
/// 2. Minting rewards portion to the rewards account
|
||||
/// 3. Minting treasury portion to the treasury account
|
||||
pub struct ExternalRewardsInflationHandler;
|
||||
|
||||
impl pallet_external_validators_rewards::types::HandleInflation<AccountId>
|
||||
for ExternalRewardsInflationHandler
|
||||
{
|
||||
fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult {
|
||||
datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion,
|
||||
TreasuryAccount,
|
||||
>::mint_inflation(who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Stub SendMessage implementation for rewards pallet
|
||||
pub struct RewardsSendAdapter;
|
||||
impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapter {
|
||||
|
|
@ -1484,17 +1527,22 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
type BackingPoints = ConstU32<20>;
|
||||
type DisputeStatementPoints = ConstU32<20>;
|
||||
type EraInflationProvider = ConstU128<0>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type Hashing = Keccak256;
|
||||
type Currency = Balances;
|
||||
type RewardsEthereumSovereignAccount = TreasuryAccount;
|
||||
type RewardsEthereumSovereignAccount = ExternalValidatorRewardsAccount;
|
||||
type WeightInfo = stagenet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
type SendMessage = RewardsSendAdapter;
|
||||
type HandleInflation = ();
|
||||
type HandleInflation = ExternalRewardsInflationHandler;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,6 +344,24 @@ pub mod dynamic_params {
|
|||
#[allow(non_upper_case_globals)]
|
||||
/// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests.
|
||||
pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0);
|
||||
|
||||
// ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗
|
||||
|
||||
#[codec(index = 37)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Targeted annual inflation rate.
|
||||
/// Default: 5% per annum
|
||||
/// This rate is divided across all eras in a year to calculate per-era inflation.
|
||||
pub static InflationTargetedAnnualRate: Perbill = Perbill::from_percent(5);
|
||||
|
||||
#[codec(index = 38)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Proportion of inflation rewards allocated to the treasury.
|
||||
/// Default: 20% of minted rewards go to treasury, 80% to validator rewards
|
||||
/// The treasury portion is minted separately and sent to the treasury account.
|
||||
pub static InflationTreasuryProportion: Perbill = Perbill::from_percent(20);
|
||||
|
||||
// ╚══════════════════════ Validator Rewards Inflation ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use super::{
|
|||
};
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
|
||||
/// A description of our proxy types.
|
||||
/// Proxy types are used to restrict the calls that can be made by a proxy account.
|
||||
|
|
@ -903,6 +903,14 @@ parameter_types! {
|
|||
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
|
||||
pub TreasuryAccount: AccountId = Treasury::account_id();
|
||||
pub const MaxSpendBalance: crate::Balance = crate::Balance::max_value();
|
||||
|
||||
/// PalletId for the External Validator Rewards account.
|
||||
/// This account receives minted inflation tokens before they are bridged to Ethereum
|
||||
/// for distribution to validators via EigenLayer.
|
||||
///
|
||||
/// Governance/Sudo can transfer funds using: pallet_balances::force_transfer
|
||||
pub const ExternalValidatorRewardsId: PalletId = PalletId(*b"dh/evrew");
|
||||
pub ExternalValidatorRewardsAccount: AccountId = ExternalValidatorRewardsId::get().into_account_truncating();
|
||||
}
|
||||
|
||||
type RootOrTreasuryCouncilOrigin = EitherOfDiverse<
|
||||
|
|
@ -1422,6 +1430,41 @@ impl Get<Vec<AccountId>> for GetWhitelistedValidators {
|
|||
}
|
||||
}
|
||||
|
||||
/// Type alias for the era inflation provider using common runtime implementation.
|
||||
///
|
||||
/// Calculates per-era inflation based on:
|
||||
/// - Total token issuance (from Balances pallet)
|
||||
/// - Annual inflation rate (from InflationTargetedAnnualRate dynamic parameter)
|
||||
/// - Era duration calculated from SessionsPerEra, EpochDurationInBlocks, and MILLISECS_PER_BLOCK
|
||||
pub type ExternalRewardsEraInflationProvider =
|
||||
datahaven_runtime_common::inflation::ExternalRewardsEraInflationProvider<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTargetedAnnualRate,
|
||||
SessionsPerEra,
|
||||
EpochDurationInBlocks,
|
||||
ConstU64<MILLISECS_PER_BLOCK>,
|
||||
>;
|
||||
|
||||
/// Wrapper struct for the inflation handler using common runtime implementation.
|
||||
///
|
||||
/// Handles minting of inflation tokens by:
|
||||
/// 1. Splitting total inflation between rewards and treasury based on InflationTreasuryProportion
|
||||
/// 2. Minting rewards portion to the rewards account
|
||||
/// 3. Minting treasury portion to the treasury account
|
||||
pub struct ExternalRewardsInflationHandler;
|
||||
|
||||
impl pallet_external_validators_rewards::types::HandleInflation<AccountId>
|
||||
for ExternalRewardsInflationHandler
|
||||
{
|
||||
fn mint_inflation(who: &AccountId, amount: u128) -> sp_runtime::DispatchResult {
|
||||
datahaven_runtime_common::inflation::ExternalRewardsInflationHandler::<
|
||||
Balances,
|
||||
runtime_params::dynamic_params::runtime_config::InflationTreasuryProportion,
|
||||
TreasuryAccount,
|
||||
>::mint_inflation(who, amount)
|
||||
}
|
||||
}
|
||||
|
||||
// Stub SendMessage implementation for rewards pallet
|
||||
pub struct RewardsSendAdapter;
|
||||
impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapter {
|
||||
|
|
@ -1485,17 +1528,22 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
type BackingPoints = ConstU32<20>;
|
||||
type DisputeStatementPoints = ConstU32<20>;
|
||||
type EraInflationProvider = ConstU128<0>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type Hashing = Keccak256;
|
||||
type Currency = Balances;
|
||||
type RewardsEthereumSovereignAccount = TreasuryAccount;
|
||||
type RewardsEthereumSovereignAccount = ExternalValidatorRewardsAccount;
|
||||
type WeightInfo = testnet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
type SendMessage = RewardsSendAdapter;
|
||||
type HandleInflation = ();
|
||||
type HandleInflation = ExternalRewardsInflationHandler;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,6 +340,24 @@ pub mod dynamic_params {
|
|||
#[allow(non_upper_case_globals)]
|
||||
/// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests.
|
||||
pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0);
|
||||
|
||||
// ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗
|
||||
|
||||
#[codec(index = 37)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Targeted annual inflation rate.
|
||||
/// Default: 5% per annum
|
||||
/// This rate is divided across all eras in a year to calculate per-era inflation.
|
||||
pub static InflationTargetedAnnualRate: Perbill = Perbill::from_percent(5);
|
||||
|
||||
#[codec(index = 38)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Proportion of inflation rewards allocated to the treasury.
|
||||
/// Default: 20% of minted rewards go to treasury, 80% to validator rewards
|
||||
/// The treasury portion is minted separately and sent to the treasury account.
|
||||
pub static InflationTreasuryProportion: Perbill = Perbill::from_percent(20);
|
||||
|
||||
// ╚══════════════════════ Validator Rewards Inflation ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.1.0-autogenerated.13402936638329471124",
|
||||
"version": "0.1.0-autogenerated.427841660215020592",
|
||||
"name": "@polkadot-api/descriptors",
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue