Skip to content

Commit f4ee42e

Browse files
committed
feat(staking): migration to rewards by timestamp
1 parent 324e872 commit f4ee42e

5 files changed

Lines changed: 121 additions & 8 deletions

File tree

src/flow_test/flow_ideas.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,9 @@ more ideas:
5555
- update_rewards for blocks in same epoch - same rewards, then advance epoch, different rewards, update rewards for blocks in same epoch - same rewards.
5656
- update rewards is not called every block, still rewards is updated correctly (miss block, miss first block in epoch, miss epoch)
5757
- set block time config and test rewards after
58+
59+
## rewards by timestamp - migration
60+
- set_consensus_rewards to future epoch, call update_rewards before consensus epoch and after, test rewards.
61+
- set_consensus_rewards to future epoch, call update_rewards only after consensus epoch, test rewards.
62+
- set_consensus_rewards to curr_epoch + 2. test rewards before and after. tets avg block time is update correctly.
63+
- set_consensus_rewards, update_rewards, then set_consensus_rewards to later epoch, update_rewards, then set_consensus_rewards to earlier epoch, update_rewards, test avg block time.

src/flow_test/test.cairo

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ use staking::staking::interface::{
1818
};
1919
use staking::staking::utils::{BTC_WEIGHT_FACTOR, STAKING_POWER_BASE_VALUE, STRK_WEIGHT_FACTOR};
2020
use staking::test_utils::constants::{
21-
BTC_18D_CONFIG, BTC_5D_CONFIG, BTC_8D_CONFIG, PUBLIC_KEY, STRK_BASE_VALUE,
21+
AVG_BLOCK_DURATION, BTC_18D_CONFIG, BTC_5D_CONFIG, BTC_8D_CONFIG, PUBLIC_KEY, STRK_BASE_VALUE,
2222
TEST_MIN_BTC_FOR_REWARDS,
2323
};
2424
use staking::test_utils::{
25-
StakingInitConfig, calculate_staker_btc_pool_rewards_v2,
25+
StakingInitConfig, advance_blocks, calculate_staker_btc_pool_rewards_v2,
2626
calculate_staker_strk_rewards_with_balances_v2, calculate_staker_strk_rewards_with_balances_v3,
2727
calculate_strk_pool_rewards_with_pool_balance_v2, compute_rewards_per_unit,
2828
custom_decimals_token, deploy_mock_erc20_decimals_contract,
@@ -2830,7 +2830,7 @@ fn update_rewards_disable_rewards_consensus_rewards_flow_test() {
28302830
assert_panic_with_error(
28312831
:result, expected_error: StakingError::REWARDS_ALREADY_UPDATED.describe(),
28322832
);
2833-
advance_block_number_global(blocks: 1);
2833+
advance_blocks(blocks: 1, block_duration: AVG_BLOCK_DURATION);
28342834

28352835
// Disable rewards = false before consensus epoch - no rewards
28362836
system.update_rewards(:staker, disable_rewards: false);
@@ -2860,7 +2860,7 @@ fn update_rewards_disable_rewards_consensus_rewards_flow_test() {
28602860
assert_panic_with_error(
28612861
:result, expected_error: StakingError::REWARDS_ALREADY_UPDATED.describe(),
28622862
);
2863-
advance_block_number_global(blocks: 1);
2863+
advance_blocks(blocks: 1, block_duration: AVG_BLOCK_DURATION);
28642864

28652865
// Disable rewards = false with consensus on - rewards
28662866
system.update_rewards(:staker, disable_rewards: false);

src/staking/staking.cairo

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,8 +1278,22 @@ pub mod Staking {
12781278

12791279
fn set_consensus_rewards_first_epoch(ref self: ContractState, epoch_id: Epoch) {
12801280
self.roles.only_app_governor();
1281-
assert!(epoch_id >= self.get_current_epoch() + 2, "{}", Error::INVALID_EPOCH);
1281+
let curr_epoch = self.get_current_epoch();
1282+
assert!(epoch_id >= curr_epoch + 2, "{}", Error::INVALID_EPOCH);
12821283
assert!(self.is_pre_consensus(), "{}", Error::CONSENSUS_REWARDS_IS_ACTIVE);
1284+
// If it's the first time setting the consensus rewards first epoch, initialize block
1285+
// rewards.
1286+
// We use `curr_epoch + 1` to ensure the average is computed only after completing
1287+
// at least one full epoch. This prevents calculating the average over too few blocks,
1288+
// since we are currently mid-epoch. The next calculation will therefore occur after
1289+
// at least one complete epoch (the next epoch) has passed.
1290+
if self.consensus_rewards_first_epoch.read().is_zero() {
1291+
self
1292+
.calculate_block_rewards(
1293+
reward_supplier_dispatcher: self.reward_supplier_dispatcher.read(),
1294+
curr_epoch: curr_epoch + 1,
1295+
);
1296+
}
12831297
self.consensus_rewards_first_epoch.write(epoch_id);
12841298
self
12851299
.emit(
@@ -1496,9 +1510,9 @@ pub mod Staking {
14961510
/// Calculates the rewards for a block in the current epoch (for STRK and BTC).
14971511
///
14981512
/// Precondition: `curr_epoch` must be `get_current_epoch()`, it's passed as a param to save
1499-
/// storage reads.
1513+
/// storage reads. This precondition is only violated during the migration to consensus
1514+
/// rewards.
15001515
// TODO: Consider view?
1501-
// TODO: Migration.
15021516
fn calculate_block_rewards(
15031517
ref self: ContractState,
15041518
reward_supplier_dispatcher: IRewardSupplierDispatcher,

src/staking/tests/test.cairo

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ use staking::staking::staking::Staking::{
6767
DEFAULT_EXIT_WAIT_WINDOW, MAX_EXIT_WAIT_WINDOW, V3_PREV_CONTRACT_VERSION,
6868
};
6969
use staking::staking::utils::STRK_WEIGHT_FACTOR;
70-
use staking::types::{Epoch, InternalStakerInfoLatest, VecIndex};
70+
use staking::types::{Amount, Epoch, InternalStakerInfoLatest, VecIndex};
7171
use staking::{event_test_utils, test_utils};
7272
use starknet::class_hash::ClassHash;
7373
use starknet::{ContractAddress, Store, get_block_number};
@@ -3441,6 +3441,7 @@ fn test_update_rewards_from_attestation_contract_assertions() {
34413441
let attestation_contract = cfg.test_info.attestation_contract;
34423442
let staking_dispatcher = IStakingDispatcher { contract_address: staking_contract };
34433443
let staking_config_dispatcher = IStakingConfigDispatcher { contract_address: staking_contract };
3444+
advance_epoch_global();
34443445
stake_for_testing_using_dispatcher(:cfg);
34453446

34463447
// Catch CALLER_IS_NOT_ATTESTATION_CONTRACT.
@@ -3492,6 +3493,7 @@ fn test_update_rewards_only_staker() {
34923493
};
34933494
let staking_config_dispatcher = IStakingConfigDispatcher { contract_address: staking_contract };
34943495
let minting_curve_contract = cfg.reward_supplier.minting_curve_contract;
3496+
advance_epoch_global();
34953497
let current_epoch = staking_dispatcher.get_current_epoch();
34963498
cheat_caller_address_once(
34973499
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -3533,6 +3535,7 @@ fn test_update_rewards_miss_blocks() {
35333535
};
35343536
let staking_config_dispatcher = IStakingConfigDispatcher { contract_address: staking_contract };
35353537
let minting_curve_contract = cfg.reward_supplier.minting_curve_contract;
3538+
advance_epoch_global();
35363539
let current_epoch = staking_dispatcher.get_current_epoch();
35373540
cheat_caller_address_once(
35383541
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -3570,6 +3573,7 @@ fn test_update_rewards_with_strk_pool() {
35703573
let token = cfg.test_info.strk_token;
35713574
let token_address = token.contract_address();
35723575
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };
3576+
advance_epoch_global();
35733577
let current_epoch = staking_dispatcher.get_current_epoch();
35743578
cheat_caller_address_once(
35753579
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -3658,6 +3662,7 @@ fn test_update_rewards_with_both_strk_and_btc() {
36583662
let btc_token = cfg.test_info.btc_token;
36593663
let btc_token_address = btc_token.contract_address();
36603664
let token_dispatcher = IERC20Dispatcher { contract_address: token_address };
3665+
advance_epoch_global();
36613666
let current_epoch = staking_dispatcher.get_current_epoch();
36623667
cheat_caller_address_once(
36633668
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -3910,6 +3915,7 @@ fn test_update_rewards_assertions_already_consensus() {
39103915
};
39113916
let staking_config_dispatcher = IStakingConfigDispatcher { contract_address: staking_contract };
39123917
let staker_address = cfg.test_info.staker_address;
3918+
advance_epoch_global();
39133919
let current_epoch = staking_dispatcher.get_current_epoch();
39143920
cheat_caller_address_once(
39153921
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -4566,6 +4572,9 @@ fn test_set_consensus_rewards_first_epoch() {
45664572
let staking_safe_config_dispatcher = IStakingConfigSafeDispatcher {
45674573
contract_address: staking_contract,
45684574
};
4575+
let minting_curve_contract = cfg.reward_supplier.minting_curve_contract;
4576+
stake_for_testing_using_dispatcher(:cfg);
4577+
advance_k_epochs_global();
45694578
let current_epoch = staking_dispatcher.get_current_epoch();
45704579
let mut spy = snforge_std::spy_events();
45714580
cheat_caller_address_once(
@@ -4578,6 +4587,31 @@ fn test_set_consensus_rewards_first_epoch() {
45784587
.try_into()
45794588
.unwrap();
45804589
assert!(consensus_rewards_first_epoch == current_epoch + 2);
4590+
// Test that block rewards are initialized.
4591+
let block_rewards = snforge_std::load(
4592+
target: staking_contract,
4593+
storage_address: selector!("block_rewards"),
4594+
size: Store::<(Amount, Amount)>::size().into(),
4595+
)
4596+
.span();
4597+
let strk_block_rewards = (*block_rewards.at(0)).try_into().unwrap();
4598+
let btc_block_rewards = (*block_rewards.at(1)).try_into().unwrap();
4599+
let (expected_strk_block_rewards, expected_btc_block_rewards) =
4600+
calculate_current_block_rewards_v3(
4601+
:minting_curve_contract,
4602+
);
4603+
assert!(expected_strk_block_rewards.is_non_zero());
4604+
assert!(expected_btc_block_rewards.is_non_zero());
4605+
assert!(strk_block_rewards == expected_strk_block_rewards);
4606+
assert!(btc_block_rewards == expected_btc_block_rewards);
4607+
let last_calculated_epoch = load_one_felt(
4608+
target: staking_contract, storage_address: selector!("last_calculated_epoch"),
4609+
)
4610+
.try_into()
4611+
.unwrap();
4612+
let expected_last_calculated_epoch = current_epoch + 1;
4613+
assert!(last_calculated_epoch == expected_last_calculated_epoch);
4614+
// Test that event is emitted.
45814615
let events = spy.get_events().emitted_by(contract_address: staking_contract).events;
45824616
assert_number_of_events(
45834617
actual: events.len(), expected: 1, message: "set_consensus_rewards_first_epoch",
@@ -4597,6 +4631,23 @@ fn test_set_consensus_rewards_first_epoch() {
45974631
.try_into()
45984632
.unwrap();
45994633
assert!(consensus_rewards_first_epoch == current_epoch + 4);
4634+
// Test that block rewards are initialized.
4635+
let block_rewards = snforge_std::load(
4636+
target: staking_contract,
4637+
storage_address: selector!("block_rewards"),
4638+
size: Store::<(Amount, Amount)>::size().into(),
4639+
)
4640+
.span();
4641+
let strk_block_rewards = (*block_rewards.at(0)).try_into().unwrap();
4642+
let btc_block_rewards = (*block_rewards.at(1)).try_into().unwrap();
4643+
assert!(strk_block_rewards == expected_strk_block_rewards);
4644+
assert!(btc_block_rewards == expected_btc_block_rewards);
4645+
let last_calculated_epoch = load_one_felt(
4646+
target: staking_contract, storage_address: selector!("last_calculated_epoch"),
4647+
)
4648+
.try_into()
4649+
.unwrap();
4650+
assert!(last_calculated_epoch == expected_last_calculated_epoch);
46004651
// Set consensus rewards epoch again with earlier epoch.
46014652
cheat_caller_address_once(
46024653
contract_address: staking_contract, caller_address: cfg.test_info.app_governor,
@@ -4608,6 +4659,23 @@ fn test_set_consensus_rewards_first_epoch() {
46084659
.try_into()
46094660
.unwrap();
46104661
assert!(consensus_rewards_first_epoch == current_epoch + 3);
4662+
// Test that block rewards are initialized.
4663+
let block_rewards = snforge_std::load(
4664+
target: staking_contract,
4665+
storage_address: selector!("block_rewards"),
4666+
size: Store::<(Amount, Amount)>::size().into(),
4667+
)
4668+
.span();
4669+
let strk_block_rewards = (*block_rewards.at(0)).try_into().unwrap();
4670+
let btc_block_rewards = (*block_rewards.at(1)).try_into().unwrap();
4671+
assert!(strk_block_rewards == expected_strk_block_rewards);
4672+
assert!(btc_block_rewards == expected_btc_block_rewards);
4673+
let last_calculated_epoch = load_one_felt(
4674+
target: staking_contract, storage_address: selector!("last_calculated_epoch"),
4675+
)
4676+
.try_into()
4677+
.unwrap();
4678+
assert!(last_calculated_epoch == expected_last_calculated_epoch);
46114679
// Set consensus rewards epoch again when currently set to current epoch + 1.
46124680
advance_k_epochs_global();
46134681
let current_epoch = staking_dispatcher.get_current_epoch();
@@ -4621,6 +4689,23 @@ fn test_set_consensus_rewards_first_epoch() {
46214689
.try_into()
46224690
.unwrap();
46234691
assert!(consensus_rewards_first_epoch == current_epoch + 2);
4692+
// Test that block rewards are initialized.
4693+
let block_rewards = snforge_std::load(
4694+
target: staking_contract,
4695+
storage_address: selector!("block_rewards"),
4696+
size: Store::<(Amount, Amount)>::size().into(),
4697+
)
4698+
.span();
4699+
let strk_block_rewards = (*block_rewards.at(0)).try_into().unwrap();
4700+
let btc_block_rewards = (*block_rewards.at(1)).try_into().unwrap();
4701+
assert!(strk_block_rewards == expected_strk_block_rewards);
4702+
assert!(btc_block_rewards == expected_btc_block_rewards);
4703+
let last_calculated_epoch = load_one_felt(
4704+
target: staking_contract, storage_address: selector!("last_calculated_epoch"),
4705+
)
4706+
.try_into()
4707+
.unwrap();
4708+
assert!(last_calculated_epoch == expected_last_calculated_epoch);
46244709
// Advance to consensus rewards and test that consensus rewards epoch cannot be changed.
46254710
advance_k_epochs_global();
46264711
let current_epoch = staking_dispatcher.get_current_epoch();
@@ -4643,6 +4728,7 @@ fn test_set_consensus_rewards_first_epoch_assertions() {
46434728
let staking_safe_config_dispatcher = IStakingConfigSafeDispatcher {
46444729
contract_address: staking_contract,
46454730
};
4731+
advance_epoch_global();
46464732
let current_epoch = staking_dispatcher.get_current_epoch();
46474733

46484734
// Catch ONLY_APP_GOVERNOR.

src/test_utils.cairo

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,13 @@ pub(crate) fn advance_epoch_global_custom_time(block_time: TimeDelta) {
938938
advance_time_global(:time);
939939
}
940940

941+
/// Advance the block number by the given `blocks` and the timestamp by the given `block_duration`
942+
/// in seconds * `blocks`.
943+
pub(crate) fn advance_blocks(blocks: u64, block_duration: Seconds) {
944+
advance_block_number_global(:blocks);
945+
advance_time_global(time: TimeDelta { seconds: block_duration * blocks });
946+
}
947+
941948
// ---- Calculate Rewards - V0 (index based) -----
942949

943950
/// Update rewards for STRK pool.

0 commit comments

Comments
 (0)