Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/flow_test/flow_ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,4 @@
- Cover all ifs with migration from: V0, V1, V2.

## k=1 -> k=2 token
- enable token, update rewards, advance epoch, update rewards, advance epoch, update rewards - token does not get rewards until after 2 epochs
- same as above with disable (can be implemented together as one test)
- enable token A and disable token B, next epoch upgrade, test views and rewards.
183 changes: 181 additions & 2 deletions src/flow_test/test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ use staking::staking::interface::{
IStakingConsensusDispatcherTrait, IStakingDispatcherTrait,
IStakingRewardsManagerSafeDispatcherTrait,
};
use staking::staking::objects::NormalizedAmountTrait;
use staking::staking::utils::{BTC_WEIGHT_FACTOR, STAKING_POWER_BASE_VALUE, STRK_WEIGHT_FACTOR};
use staking::test_utils::constants::{
BTC_18D_CONFIG, BTC_5D_CONFIG, BTC_8D_CONFIG, PUBLIC_KEY, STRK_BASE_VALUE,
TEST_MIN_BTC_FOR_REWARDS,
};
use staking::test_utils::{
StakingInitConfig, calculate_staker_btc_pool_rewards_v2,
calculate_staker_strk_rewards_with_balances_v2, calculate_staker_strk_rewards_with_balances_v3,
StakingInitConfig, calculate_staker_btc_pool_rewards_v2, calculate_staker_btc_pool_rewards_v3,
calculate_staker_strk_rewards_v2, calculate_staker_strk_rewards_with_balances_v2,
calculate_staker_strk_rewards_with_balances_v3,
calculate_strk_pool_rewards_with_pool_balance_v2,
calculate_strk_pool_rewards_with_pool_balance_v3, compute_rewards_per_unit,
custom_decimals_token, deploy_mock_erc20_decimals_contract,
Expand Down Expand Up @@ -3688,3 +3690,180 @@ fn delegator_claim_rewards_after_change_balance_flow_test() {
// Allow small rounding errors.
assert!(wide_abs_diff(rewards, expected_rewards_v3 * 2) <= 1);
}

/// Flow:
/// Add tokens A and B
/// Enable token B
/// Advance K epochs
/// Enable token A, disable token B
/// Attest
/// Advance epoch - test rewards only for token B
/// Attest
/// Advance epoch - test rewards only for token B
/// Attest
/// Advance epoch - test rewards only for token A
/// Start consensus rewards
/// Enable token B, disable token A
/// update_rewards twice in the same epoch
/// Advance epoch - test rewards only for token A
/// update_rewards
/// Advance epoch - test rewards only for token A
/// update_rewards
/// Advance epoch - test rewards only for token B
#[test]
fn update_rewards_token_enable_disable_flow_test() {
let cfg: StakingInitConfig = Default::default();
let mut system = SystemConfigTrait::basic_stake_flow_cfg(:cfg).deploy();
let token_a_decimals = 8;
let token_b_decimals = 18;
let token_a = system.deploy_new_btc_token(name: "Token A", decimals: token_a_decimals);
let token_b = system.deploy_new_btc_token(name: "Token B", decimals: token_b_decimals);
let staking_contract = system.staking.address;
let minting_curve_contract = system.minting_curve.address;

// Setup tokens
system.staking.add_token(token_address: token_a.contract_address());
system.staking.add_token(token_address: token_b.contract_address());
system.staking.enable_token(token_address: token_b.contract_address());

// Stake and delegate
let stake_amount = system.staking.get_min_stake();
let delegation_amount_a = BTC_8D_CONFIG.min_for_rewards;
let delegation_amount_b = BTC_18D_CONFIG.min_for_rewards;
let staker = system.new_staker(amount: stake_amount);
let commission = 200;
system.stake(:staker, amount: stake_amount, pool_enabled: true, :commission);
let pool_a = system.set_open_for_delegation(:staker, token_address: token_a.contract_address());
let pool_b = system.set_open_for_delegation(:staker, token_address: token_b.contract_address());
let delegator_a = system.new_btc_delegator(amount: delegation_amount_a, token: token_a);
let delegator_b = system.new_btc_delegator(amount: delegation_amount_b, token: token_b);
system
.delegate_btc(
delegator: delegator_a, pool: pool_a, amount: delegation_amount_a, token: token_a,
);
system
.delegate_btc(
delegator: delegator_b, pool: pool_b, amount: delegation_amount_b, token: token_b,
);
system.advance_k_epochs();

// Enable token A, disable token B
system.staking.enable_token(token_address: token_a.contract_address());
system.staking.disable_token(token_address: token_b.contract_address());

// Attest
system.advance_block_into_attestation_window(:staker);
system.attest(:staker);
system.advance_epoch();

// Calculate rewards
let staker_info = system.staker_info_v1(:staker);
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
let (expected_staker_rewards, _) = calculate_staker_strk_rewards_v2(
:staker_info, :staking_contract, :minting_curve_contract,
);
assert!(expected_staker_rewards.is_non_zero());
let (commission_rewards, expected_delegator_rewards) = calculate_staker_btc_pool_rewards_v2(
pool_balance: delegation_amount_b,
:commission,
:staking_contract,
:minting_curve_contract,
token_address: token_b.contract_address(),
);
assert!(commission_rewards.is_non_zero());
assert!(expected_delegator_rewards.is_non_zero());

// Test rewards
assert!(staker_rewards == expected_staker_rewards + commission_rewards);
assert!(delegator_a_rewards.is_zero());
assert!(delegator_b_rewards == expected_delegator_rewards);

// Attest - test rewards only for token B
system.advance_block_into_attestation_window(:staker);
system.attest(:staker);
system.advance_epoch();
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
assert!(staker_rewards == expected_staker_rewards + commission_rewards);
assert!(delegator_a_rewards.is_zero());
// Same rewards because the delegation amount is the same (with different decimals).
assert!(delegator_b_rewards == expected_delegator_rewards);

// Attest - test rewards only for token A
system.advance_block_into_attestation_window(:staker);
system.attest(:staker);
system.advance_epoch();
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
assert!(staker_rewards == expected_staker_rewards + commission_rewards);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Staker commission uses wrong token's parameters for assertions

At line 3142, when token A becomes active (comment says "test rewards only for token A"), the staker_rewards assertion uses commission_rewards that was calculated for token B (lines 3109-3115). Similarly at line 3203, when token B becomes active, the assertion uses commission_rewards calculated for token A (lines 3162-3175). The staker commission should reflect the currently active token's pool, but the test reuses commission values calculated for the previously active token. This passes only due to the coincidental numerical equivalence after normalization.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment why its ok

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

assert!(delegator_a_rewards == expected_delegator_rewards);
assert!(delegator_b_rewards.is_zero());

// Start consensus rewards
system.start_consensus_rewards();

// Enable token B, disable token A
system.staking.enable_token(token_address: token_b.contract_address());
system.staking.disable_token(token_address: token_a.contract_address());

// Calculate rewards for consensus rewards - for single enabled token.
let (expected_staker_rewards, _) = calculate_staker_strk_rewards_with_balances_v3(
amount_own: stake_amount,
pool_amount: Zero::zero(),
:commission,
:staking_contract,
:minting_curve_contract,
);
assert!(expected_staker_rewards.is_non_zero());
let (commission_rewards, expected_delegator_rewards) = calculate_staker_btc_pool_rewards_v3(
normalized_pool_balance: NormalizedAmountTrait::from_native_amount(
amount: delegation_amount_a, decimals: token_a_decimals,
),
normalized_staker_total_btc_balance: NormalizedAmountTrait::from_native_amount(
amount: delegation_amount_a, decimals: token_a_decimals,
),
:commission,
:staking_contract,
:minting_curve_contract,
token_address: token_a.contract_address(),
);
assert!(commission_rewards.is_non_zero());
assert!(expected_delegator_rewards.is_non_zero());

// update_rewards - test rewards only for token A
system.update_rewards(:staker, disable_rewards: false);
advance_block_number_global(blocks: 1);
system.update_rewards(:staker, disable_rewards: false);
system.advance_epoch();
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
assert!(staker_rewards == expected_staker_rewards * 2 + commission_rewards * 2);
assert!(delegator_a_rewards == expected_delegator_rewards * 2);
assert!(delegator_b_rewards.is_zero());

// update_rewards - test rewards only for token A
system.update_rewards(:staker, disable_rewards: false);
system.advance_epoch();
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
assert!(staker_rewards == expected_staker_rewards + commission_rewards);
assert!(delegator_a_rewards == expected_delegator_rewards);
assert!(delegator_b_rewards.is_zero());

// update_rewards - test rewards only for token B
system.update_rewards(:staker, disable_rewards: false);
system.advance_epoch();
let staker_rewards = system.staker_claim_rewards(:staker);
let delegator_a_rewards = system.delegator_claim_rewards(delegator: delegator_a, pool: pool_a);
let delegator_b_rewards = system.delegator_claim_rewards(delegator: delegator_b, pool: pool_b);
assert!(staker_rewards == expected_staker_rewards + commission_rewards);
assert!(delegator_a_rewards.is_zero());
// Same rewards because the delegation amount is the same (with different decimals).
assert!(delegator_b_rewards == expected_delegator_rewards);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Wrong expected rewards used for token B validation

The test calculates expected_delegator_rewards using token A's parameters (delegation_amount_a, token_a_decimals, token_a.contract_address()) at lines 3163-3174, but then uses this value to validate token B's rewards at line 3206. Since token A has 8 decimals with min_for_rewards of 10³ and token B has 18 decimals with min_for_rewards of 10¹³, the expected rewards will be incorrect. The test needs to recalculate expected rewards using token B's configuration before the final assertion block.

Additional Locations (1)

Fix in Cursor Fix in Web

}