Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/capacity-staking-rewards-im…
Browse files Browse the repository at this point in the history
…pl' into audit-2024
  • Loading branch information
wilwade committed Aug 7, 2024
2 parents 3e38906 + abe5066 commit 9ef5818
Show file tree
Hide file tree
Showing 22 changed files with 284 additions and 210 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ serde_json = { version = "1.0.86", default-features = false }
tokio = { version = "1.25.0", default-features = false }
unicode-normalization = { version = "0.1.22", default-features = false }
clap = { version = "4.2.5", features = ["derive"] }
static_assertions = { version = "1.1.0", default-features = false }

frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.10.0" }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.10.0" }
Expand Down
2 changes: 1 addition & 1 deletion designdocs/provider_boosting_implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub trait Config: frame_system::Config {
type MaxRetargetsPerRewardEra: Get<u32>;

/// The fixed size of the reward pool in each Reward Era.
type RewardPoolEachEra: Get<BalanceOf<Self>>;
type RewardPoolPerEra: Get<BalanceOf<Self>>;

/// the percentage cap per era of an individual Provider Boost reward
type RewardPercentCap: Get<Permill>;
Expand Down
6 changes: 3 additions & 3 deletions e2e/capacity/list_unclaimed_rewards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ describe('Capacity: list_unclaimed_rewards', function () {
});

it('returns correct rewards after enough eras have passed', async function () {
if (isTestnet()) {
this.skip();
}
// this will be too long if run against testnet
if (isTestnet()) this.skip();

const [_provider, booster] = await setUpForBoosting('booster2', 'provider2');
console.debug(`Booster pubkey: ${booster.address}`);

Expand Down
9 changes: 7 additions & 2 deletions e2e/capacity/provider_boost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import {
DOLLARS,
createAndFundKeypair,
boostProvider,
stakeToProvider,
} from '../scaffolding/helpers';

const fundingSource = getFundingSource('capacity-provider-boost');
const tokenMinStake: bigint = 1n * CENTS;

describe('Capacity: provider_boost extrinsic', function () {
const providerBalance = 2n * DOLLARS;
Expand All @@ -25,14 +27,17 @@ describe('Capacity: provider_boost extrinsic', function () {
it('fails when staker is a Maximized Capacity staker', async function () {
const stakeKeys = createKeys('booster');
const provider = await createMsaAndProvider(fundingSource, stakeKeys, 'Provider1', providerBalance);
await assert.rejects(boostProvider(fundingSource, stakeKeys, provider, 1n * DOLLARS), {name: "CannotChangeStakingType"});
await assert.doesNotReject(stakeToProvider(fundingSource, stakeKeys, provider, tokenMinStake));
await assert.rejects(boostProvider(fundingSource, stakeKeys, provider, tokenMinStake), {
name: 'CannotChangeStakingType',
});
});

it("fails when staker doesn't have enough token", async function () {
const stakeKeys = createKeys('booster');
const provider = await createMsaAndProvider(fundingSource, stakeKeys, 'Provider1', providerBalance);
const booster = await createAndFundKeypair(fundingSource, 1n * DOLLARS, 'booster');
await assert.rejects(boostProvider(booster, booster, provider, 1n * DOLLARS), {name: "InsufficientCapacityBalance"});
await assert.rejects(boostProvider(booster, booster, provider, 1n * DOLLARS), { name: 'BalanceTooLowtoStake' });
});

it('staker can boost multiple times', async function () {
Expand Down
2 changes: 1 addition & 1 deletion e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions e2e/scaffolding/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,6 @@ export async function boostProvider(
const stakeOp = ExtrinsicHelper.providerBoost(keys, providerId, tokensToStake);
const { target: stakeEvent } = await stakeOp.fundAndSend(source);
assert.notEqual(stakeEvent, undefined, 'stakeToProvider: should have returned Stake event');

if (stakeEvent) {
const stakedCapacity = stakeEvent.data.capacity;

Expand Down Expand Up @@ -498,15 +497,14 @@ export async function getOrCreateGraphChangeSchema(source: KeyringPair): Promise
if (existingSchemaId) {
return new u16(ExtrinsicHelper.api.registry, existingSchemaId);
} else {
const op = ExtrinsicHelper.createSchemaV3(
const { target: createSchemaEvent, eventMap } = await ExtrinsicHelper.createSchemaV3(
source,
AVRO_GRAPH_CHANGE,
'AvroBinary',
'OnChain',
[],
'test.graphChangeSchema'
);
const { target: createSchemaEvent, eventMap } = await op.fundAndSend(source);
).fundAndSend(source);
assertExtrinsicSuccess(eventMap);
if (createSchemaEvent) {
return createSchemaEvent.data.schemaId;
Expand Down
2 changes: 1 addition & 1 deletion pallets/capacity/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ benchmarks! {
start_new_reward_era_if_needed {
let current_block: BlockNumberFor<T> = 1_209_600u32.into();
let history_limit: u32 = <T as Config>::ProviderBoostHistoryLimit::get();
let total_reward_pool: BalanceOf<T> = <T as Config>::RewardPoolEachEra::get();
let total_reward_pool: BalanceOf<T> = <T as Config>::RewardPoolPerEra::get();
let unclaimed_balance: BalanceOf<T> = 5_000u32.into();
let total_staked_token: BalanceOf<T> = 5_000u32.into();
let started_at: BlockNumberFor<T> = current_block.saturating_sub(<T as Config>::EraLength::get().into());
Expand Down
54 changes: 18 additions & 36 deletions pallets/capacity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub mod migration;
pub mod weights;
type BalanceOf<T> =
<<T as Config>::Currency as InspectFungible<<T as frame_system::Config>::AccountId>>::Balance;
type ChunkIndex = u32;

#[frame_support::pallet]
pub mod pallet {
Expand Down Expand Up @@ -127,7 +128,7 @@ pub mod pallet {
/// The maximum number of unlocking chunks a StakingAccountLedger can have.
/// It determines how many concurrent unstaked chunks may exist.
#[pallet::constant]
type MaxUnlockingChunks: Get<u32> + Clone;
type MaxUnlockingChunks: Get<u32>;

#[cfg(feature = "runtime-benchmarks")]
/// A set of helper functions for benchmarking.
Expand Down Expand Up @@ -178,14 +179,14 @@ pub mod pallet {

/// The fixed size of the reward pool in each Reward Era.
#[pallet::constant]
type RewardPoolEachEra: Get<BalanceOf<Self>>;
type RewardPoolPerEra: Get<BalanceOf<Self>>;

/// the percentage cap per era of an individual Provider Boost reward
#[pallet::constant]
type RewardPercentCap: Get<Permill>;

/// The number of chunks of Reward Pool history we expect to store
/// MUST be a divisor of [`Self::ProviderBoostHistoryLimit`]
/// Is a divisor of [`Self::ProviderBoostHistoryLimit`]
#[pallet::constant]
type RewardPoolChunkLength: Get<u32>;
}
Expand Down Expand Up @@ -257,7 +258,7 @@ pub mod pallet {
/// chunk number.
#[pallet::storage]
pub type ProviderBoostRewardPools<T: Config> =
StorageMap<_, Twox64Concat, u32, RewardPoolHistoryChunk<T>>;
StorageMap<_, Twox64Concat, ChunkIndex, RewardPoolHistoryChunk<T>>;

/// How much is staked this era
#[pallet::storage]
Expand All @@ -268,24 +269,6 @@ pub mod pallet {
pub type ProviderBoostHistories<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, ProviderBoostHistory<T>>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
/// Phantom type
#[serde(skip)]
pub _config: PhantomData<T>,
}

#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
CurrentEraInfo::<T>::set(RewardEraInfo {
era_index: 1u32.into(),
started_at: 0u32.into(),
});
CurrentEraProviderBoostTotal::<T>::set(0u32.into());
}
}

// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
// method.
#[pallet::pallet]
Expand Down Expand Up @@ -1096,12 +1079,14 @@ impl<T: Config> Pallet<T> {
let max_history: u32 = T::ProviderBoostHistoryLimit::get();

let start_era = current_era_info.era_index.saturating_sub((max_history).into());
let end_era = current_era_info.era_index.saturating_sub(One::one());
let end_era = current_era_info.era_index.saturating_sub(One::one()); // stop at previous era

// start with how much was staked in the era before the earliest for which there are eligible rewards.
let mut previous_amount: BalanceOf<T> =
staking_history.get_amount_staked_for_era(&(start_era.saturating_sub(1u32.into())));

let mut previous_amount: BalanceOf<T> = match start_era {
0 => 0u32.into(),
_ =>
staking_history.get_amount_staked_for_era(&(start_era.saturating_sub(1u32.into()))),
};
let mut unclaimed_rewards: BoundedVec<
UnclaimedRewardInfo<BalanceOf<T>, BlockNumberFor<T>>,
T::ProviderBoostHistoryLimit,
Expand All @@ -1117,7 +1102,7 @@ impl<T: Config> Pallet<T> {
let earned_amount = <T>::RewardsProvider::era_staking_reward(
eligible_amount,
total_for_era,
T::RewardPoolEachEra::get(),
T::RewardPoolPerEra::get(),
);
unclaimed_rewards
.try_push(UnclaimedRewardInfo {
Expand Down Expand Up @@ -1162,7 +1147,7 @@ impl<T: Config> Pallet<T> {
Error::<T>::EraOutOfRange
);

let chunk_idx: u32 = Self::get_chunk_index_for_era(reward_era);
let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(reward_era);
let reward_pool_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); // 1r
let total_for_era =
reward_pool_chunk.total_for_era(&reward_era).ok_or(Error::<T>::EraOutOfRange)?;
Expand All @@ -1183,7 +1168,7 @@ impl<T: Config> Pallet<T> {
let history_limit: u32 = T::ProviderBoostHistoryLimit::get();
let chunk_len = T::RewardPoolChunkLength::get();
// Remove one because eras are 1 indexed
let era_u32: u32 = era.saturating_sub(One::one()).into();
let era_u32: u32 = era;

// Add one chunk so that we always have the full history limit in our chunks
let cycle: u32 = era_u32 % history_limit.saturating_add(chunk_len);
Expand All @@ -1193,9 +1178,8 @@ impl<T: Config> Pallet<T> {
// This is where the reward pool gets updated.
pub(crate) fn update_provider_boost_reward_pool(era: RewardEra, boost_total: BalanceOf<T>) {
// Current era is this era
let chunk_idx: u32 = Self::get_chunk_index_for_era(era);
let mut new_chunk =
ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or(RewardPoolHistoryChunk::new()); // 1r
let chunk_idx: ChunkIndex = Self::get_chunk_index_for_era(era);
let mut new_chunk = ProviderBoostRewardPools::<T>::get(chunk_idx).unwrap_or_default(); // 1r

// If it is full we are resetting.
// This assumes that the chunk length is a divisor of the history limit
Expand All @@ -1205,6 +1189,7 @@ impl<T: Config> Pallet<T> {

if new_chunk.try_insert(era, boost_total).is_err() {
// Handle the error case that should never happen
log::warn!("could not insert a new chunk into provider boost reward pool")
}
ProviderBoostRewardPools::<T>::set(chunk_idx, Some(new_chunk)); // 1w
}
Expand Down Expand Up @@ -1315,13 +1300,10 @@ impl<T: Config> Replenishable for Pallet<T> {
}

impl<T: Config> ProviderBoostRewardsProvider<T> for Pallet<T> {
type AccountId = T::AccountId;
type RewardEra = common_primitives::capacity::RewardEra;
type Hash = T::Hash;
type Balance = BalanceOf<T>;

fn reward_pool_size(_total_staked: Self::Balance) -> Self::Balance {
T::RewardPoolEachEra::get()
T::RewardPoolPerEra::get()
}

/// Calculate the reward for a single era. We don't care about the era number,
Expand Down
8 changes: 4 additions & 4 deletions pallets/capacity/src/migration/provider_boost_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use frame_support::{
traits::{Get, OnRuntimeUpgrade},
};

use common_primitives::capacity::RewardEra;
#[cfg(feature = "try-runtime")]
use sp_std::vec::Vec;

Expand All @@ -15,9 +14,10 @@ impl<T: Config> OnRuntimeUpgrade for ProviderBoostInit<T> {
fn on_runtime_upgrade() -> Weight {
let current_era_info = CurrentEraInfo::<T>::get(); // 1r
if current_era_info.eq(&RewardEraInfo::default()) {
let current_block = frame_system::Pallet::<T>::block_number(); // Whitelisted
let era_index: RewardEra = 1u32.into();
CurrentEraInfo::<T>::set(RewardEraInfo { era_index, started_at: current_block }); // 1w
CurrentEraInfo::<T>::set(RewardEraInfo {
era_index: 0u32.into(),
started_at: frame_system::Pallet::<T>::block_number(),
}); // 1w
CurrentEraProviderBoostTotal::<T>::set(0u32.into()); // 1w
T::DbWeight::get().reads_writes(2, 1)
} else {
Expand Down
21 changes: 11 additions & 10 deletions pallets/capacity/src/tests/claim_staking_rewards_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ fn claim_staking_rewards_leaves_one_history_item_for_current_era() {

setup_provider(&account, &target, &amount, ProviderBoost);
run_to_block(31);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 4u32);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 3u32);

let current_history = ProviderBoostHistories::<Test>::get(account).unwrap();
assert_eq!(current_history.count(), 1usize);
let history_item = current_history.get_entry_for_era(&1u32).unwrap();
let history_item = current_history.get_entry_for_era(&0u32).unwrap();
assert_eq!(*history_item, amount);
})
}
Expand Down Expand Up @@ -57,27 +57,28 @@ fn claim_staking_rewards_mints_and_transfers_expected_total() {

setup_provider(&account, &target, &amount, ProviderBoost);
run_to_block(31);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 4u32);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 3u32);
assert_ok!(Capacity::claim_staking_rewards(RuntimeOrigin::signed(account)));
System::assert_last_event(
Event::<Test>::ProviderBoostRewardClaimed { account, reward_amount: 8u64 }.into(),
);

// should have 2 era's worth of payouts: 4 each for eras 2, 3
// should have 2 era's worth of payouts: 4 each for eras 1, 2
assert_eq!(get_balance::<Test>(&account), 10_008u64);

// the reward value is unlocked
assert_transferable::<Test>(&account, 8u64);

run_to_block(51);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 6u32);
run_to_block(41);
assert_eq!(CurrentEraInfo::<Test>::get().era_index, 4u32);
assert_ok!(Capacity::claim_staking_rewards(RuntimeOrigin::signed(account)));
// rewards available for one more era
System::assert_last_event(
ProviderBoostRewardClaimed { account, reward_amount: 8u64 }.into(),
ProviderBoostRewardClaimed { account, reward_amount: 4u64 }.into(),
);
// should have 4 for eras 2-5
assert_eq!(get_balance::<Test>(&account), 10_016u64);
assert_transferable::<Test>(&account, 16u64);
// should have 4 for eras 1-3
assert_eq!(get_balance::<Test>(&account), 10_012u64);
assert_transferable::<Test>(&account, 12u64);
})
}

Expand Down
Loading

0 comments on commit 9ef5818

Please sign in to comment.