From 068206232518346dbd6ca15bbdabce16fd3980da Mon Sep 17 00:00:00 2001 From: Noa Wolfgor Date: Thu, 9 Oct 2025 13:39:57 +0300 Subject: [PATCH] feat: reward supplier eic --- src/flow_test/utils.cairo | 35 ++-- src/reward_supplier.cairo | 1 + src/reward_supplier/eic.cairo | 44 +++++ src/reward_supplier/reward_supplier.cairo | 2 - src/reward_supplier/test.cairo | 197 +++++++++++++++++++++- src/test_utils.cairo | 13 ++ 6 files changed, 278 insertions(+), 14 deletions(-) create mode 100644 src/reward_supplier/eic.cairo diff --git a/src/flow_test/utils.cairo b/src/flow_test/utils.cairo index cbe5131b..a5b42039 100644 --- a/src/flow_test/utils.cairo +++ b/src/flow_test/utils.cairo @@ -34,6 +34,9 @@ use staking::pool::interface_v0::{IPoolV0Dispatcher, IPoolV0DispatcherTrait, Poo use staking::reward_supplier::interface::{ IRewardSupplierDispatcher, IRewardSupplierDispatcherTrait, }; +use staking::reward_supplier::reward_supplier::RewardSupplier::{ + DEFAULT_AVG_BLOCK_DURATION, DEFAULT_BLOCK_DURATION_CONFIG, +}; use staking::staking::interface::{ CommissionCommitment, IStakingConfigDispatcher, IStakingConfigDispatcherTrait, IStakingConsensusDispatcher, IStakingConsensusSafeDispatcher, IStakingDispatcher, @@ -61,10 +64,10 @@ use staking::test_utils::constants::{ STARTING_BLOCK_OFFSET, TESTING_C_NUM, TEST_BTC_DECIMALS, UPGRADE_GOVERNOR, }; use staking::test_utils::{ - StakingInitConfig, approve, calculate_block_offset, custom_decimals_token, - declare_pool_contract, declare_pool_eic_contract, declare_staking_contract, - declare_staking_eic_contract, deploy_mock_erc20_decimals_contract, fund, load_from_simple_map, - upgrade_implementation, + StakingInitConfig, advance_blocks, approve, calculate_block_offset, custom_decimals_token, + declare_pool_contract, declare_pool_eic_contract, declare_reward_supplier_contract, + declare_reward_supplier_eic_contract, declare_staking_contract, declare_staking_eic_contract, + deploy_mock_erc20_decimals_contract, fund, load_from_simple_map, upgrade_implementation, }; use staking::types::{ Amount, BlockNumber, Commission, Epoch, Index, Inflation, InternalPoolMemberInfoLatest, @@ -1245,7 +1248,10 @@ pub(crate) impl SystemImpl of SystemTrait { let current_block = get_block_number(); let epoch_start_block = self.staking.get_epoch_info().current_epoch_starting_block(); let block_offset = block_offset_from_epoch_start - (current_block - epoch_start_block); - advance_block_number_global(blocks: block_offset + MIN_ATTESTATION_WINDOW.into()); + advance_blocks( + blocks: block_offset + MIN_ATTESTATION_WINDOW.into(), + block_duration: AVG_BLOCK_DURATION, + ); } fn deploy_second_btc_token(self: SystemState) -> Token { @@ -2056,10 +2062,21 @@ pub(crate) impl SystemReplaceabilityV3Impl of SystemReplaceabilityV3Trait { ); } - /// Upgrades the reward supplier contract in the system state with a local implementation. + /// Upgrades the staking contract in the system state with a local implementation. fn upgrade_reward_supplier_implementation_v3(self: SystemState) { + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: array![ + DEFAULT_AVG_BLOCK_DURATION.into(), + DEFAULT_BLOCK_DURATION_CONFIG.min_block_duration.into(), + DEFAULT_BLOCK_DURATION_CONFIG.max_block_duration.into(), + ] + .span(), + }; let implementation_data = ImplementationData { - impl_hash: declare_reward_supplier_contract(), eic_data: Option::None, final: false, + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, }; upgrade_implementation( contract_address: self.reward_supplier.address, @@ -2069,10 +2086,6 @@ pub(crate) impl SystemReplaceabilityV3Impl of SystemReplaceabilityV3Trait { } } -fn declare_reward_supplier_contract() -> ClassHash { - *snforge_std::declare("RewardSupplier").unwrap().contract_class().class_hash -} - fn declare_minting_curve_contract() -> ClassHash { *snforge_std::declare("MintingCurve").unwrap().contract_class().class_hash } diff --git a/src/reward_supplier.cairo b/src/reward_supplier.cairo index 99a7b2f5..367a185d 100644 --- a/src/reward_supplier.cairo +++ b/src/reward_supplier.cairo @@ -1,3 +1,4 @@ +mod eic; pub mod errors; pub mod interface; pub mod reward_supplier; diff --git a/src/reward_supplier/eic.cairo b/src/reward_supplier/eic.cairo new file mode 100644 index 00000000..9313fe1b --- /dev/null +++ b/src/reward_supplier/eic.cairo @@ -0,0 +1,44 @@ +/// An External Initializer Contract to upgrade a reward supplier contract. +/// This EIC is used to upgrade the reward supplier contract from V2 (BTC) to V3. +#[starknet::contract] +mod RewardSupplierEIC { + use staking::reward_supplier::errors::Error; + use staking::reward_supplier::interface::BlockDurationConfig; + use starknet::storage::StoragePointerWriteAccess; + use starkware_utils::components::replaceability::interface::IEICInitializable; + + #[storage] + struct Storage { + // --- New fields --- + /// Average block duration in units of 1 / BLOCK_DURATION_SCALE seconds. + avg_block_duration: u64, + /// Configuration for block duration calculation. + block_duration_config: BlockDurationConfig, + } + + /// Expected data : [avg_block_duration, min_block_duration, max_block_duration] + #[abi(embed_v0)] + impl EICInitializable of IEICInitializable { + fn eic_initialize(ref self: ContractState, eic_init_data: Span) { + assert(eic_init_data.len() == 3, 'EXPECTED_DATA_LENGTH_3'); + let avg_block_duration: u64 = (*eic_init_data[0]).try_into().unwrap(); + let min_block_duration: u64 = (*eic_init_data[1]).try_into().unwrap(); + let max_block_duration: u64 = (*eic_init_data[2]).try_into().unwrap(); + + // Validate values. + assert!( + min_block_duration <= avg_block_duration + && avg_block_duration <= max_block_duration, + "{}", + 'INVALID_AVG_BLOCK_DURATION', + ); + assert!(min_block_duration > 0, "{}", Error::INVALID_MIN_MAX_BLOCK_DURATION); + + // Set values. + self.avg_block_duration.write(avg_block_duration); + self + .block_duration_config + .write(BlockDurationConfig { min_block_duration, max_block_duration }); + } + } +} diff --git a/src/reward_supplier/reward_supplier.cairo b/src/reward_supplier/reward_supplier.cairo index 3817e4ad..98039122 100644 --- a/src/reward_supplier/reward_supplier.cairo +++ b/src/reward_supplier/reward_supplier.cairo @@ -84,7 +84,6 @@ pub mod RewardSupplier { /// Token bridge address. starkgate_address: ContractAddress, /// Average block duration in units of 1 / BLOCK_DURATION_SCALE seconds. - // TODO: Initial in EIC. // TODO: Setter. // TODO: View? avg_block_duration: u64, @@ -92,7 +91,6 @@ pub mod RewardSupplier { /// Updated at the start of each epoch. block_snapshot: (BlockNumber, Timestamp), /// Configuration for block duration calculation. - // TODO: Initial in EIC. block_duration_config: BlockDurationConfig, } diff --git a/src/reward_supplier/test.cairo b/src/reward_supplier/test.cairo index 41f5f971..33135d67 100644 --- a/src/reward_supplier/test.cairo +++ b/src/reward_supplier/test.cairo @@ -32,6 +32,7 @@ use staking::test_utils::constants::{ }; use staking::types::{Amount, BlockNumber}; use starknet::{ContractAddress, Store}; +use starkware_utils::components::replaceability::interface::{EICData, ImplementationData}; use starkware_utils::errors::{Describable, OptionAuxTrait}; use starkware_utils::math::utils::{ceil_of_division, mul_wide_and_div}; use starkware_utils::time::time::{Time, TimeDelta, Timestamp}; @@ -41,8 +42,10 @@ use starkware_utils_testing::test_utils::{ }; use test_utils::{ StakingInitConfig, advance_epoch_global, advance_epoch_global_custom_time, - advance_k_epochs_global, advance_time_global, fund, general_contract_system_deployment, + advance_k_epochs_global, advance_time_global, declare_reward_supplier_contract, + declare_reward_supplier_eic_contract, fund, general_contract_system_deployment, initialize_reward_supplier_state_from_cfg, load_one_felt, stake_for_testing_using_dispatcher, + upgrade_implementation, }; #[test] @@ -688,3 +691,195 @@ fn test_update_current_epoch_block_rewards_with_adjustments() { assert!(strk_rewards == expected_strk_rewards); assert!(btc_rewards == expected_btc_rewards); } + +#[test] +fn test_reward_supplier_eic() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let reward_supplier_dispatcher = IRewardSupplierDispatcher { + contract_address: reward_supplier, + }; + let upgrade_governor = cfg.test_info.upgrade_governor; + let avg_block_duration = load_one_felt( + target: reward_supplier, storage_address: selector!("avg_block_duration"), + ) + .try_into() + .unwrap(); + assert!(avg_block_duration == DEFAULT_AVG_BLOCK_DURATION); + assert!( + reward_supplier_dispatcher.get_block_duration_config() == DEFAULT_BLOCK_DURATION_CONFIG, + ); + + // Upgrade. + let avg_block_duration = DEFAULT_AVG_BLOCK_DURATION - 10; + let min_block_duration = DEFAULT_BLOCK_DURATION_CONFIG.min_block_duration - 10; + let max_block_duration = DEFAULT_BLOCK_DURATION_CONFIG.max_block_duration - 10; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: [ + avg_block_duration.into(), min_block_duration.into(), max_block_duration.into(), + ] + .span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); + + // Test. + let new_avg_block_duration = load_one_felt( + target: reward_supplier, storage_address: selector!("avg_block_duration"), + ) + .try_into() + .unwrap(); + assert!(new_avg_block_duration == avg_block_duration); + assert!( + reward_supplier_dispatcher + .get_block_duration_config() == BlockDurationConfig { + min_block_duration, max_block_duration, + }, + ); +} + +#[test] +#[should_panic(expected: "EIC_LIB_CALL_FAILED")] +fn test_reward_supplier_eic_with_wrong_number_of_data_elements() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let upgrade_governor = cfg.test_info.upgrade_governor; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), eic_init_data: [].span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); +} + +#[test] +#[should_panic(expected: "EIC_LIB_CALL_FAILED")] +fn test_reward_supplier_eic_invalid_avg_block_duration_less_than_min() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let upgrade_governor = cfg.test_info.upgrade_governor; + let avg_block_duration = 4; + let min_block_time = 5; + let max_block_time = 10; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: [avg_block_duration.into(), min_block_time.into(), max_block_time.into()] + .span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); +} + +#[test] +#[should_panic(expected: "EIC_LIB_CALL_FAILED")] +fn test_reward_supplier_eic_invalid_avg_block_duration_greater_than_max() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let upgrade_governor = cfg.test_info.upgrade_governor; + let avg_block_duration = 14; + let min_block_time = 5; + let max_block_time = 10; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: [avg_block_duration.into(), min_block_time.into(), max_block_time.into()] + .span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); +} + +#[test] +#[should_panic(expected: "EIC_LIB_CALL_FAILED")] +fn test_reward_supplier_eic_invalid_min_zero() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let upgrade_governor = cfg.test_info.upgrade_governor; + let avg_block_duration = 5; + let min_block_time = 0; + let max_block_time = 10; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: [avg_block_duration.into(), min_block_time.into(), max_block_time.into()] + .span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); +} + +#[test] +#[should_panic(expected: "EIC_LIB_CALL_FAILED")] +fn test_reward_supplier_eic_invalid_min_max() { + let mut cfg: StakingInitConfig = Default::default(); + general_contract_system_deployment(ref :cfg); + let reward_supplier = cfg.staking_contract_info.reward_supplier; + let upgrade_governor = cfg.test_info.upgrade_governor; + let avg_block_duration = 5; + let min_block_time = 10; + let max_block_time = 9; + let eic_data = EICData { + eic_hash: declare_reward_supplier_eic_contract(), + eic_init_data: [avg_block_duration.into(), min_block_time.into(), max_block_time.into()] + .span(), + }; + let implementation_data = ImplementationData { + impl_hash: declare_reward_supplier_contract(), + eic_data: Option::Some(eic_data), + final: false, + }; + start_cheat_block_timestamp_global( + block_timestamp: Time::now().add(delta: Time::days(count: 1)).into(), + ); + upgrade_implementation( + contract_address: reward_supplier, :implementation_data, :upgrade_governor, + ); +} diff --git a/src/test_utils.cairo b/src/test_utils.cairo index 7368109c..95876491 100644 --- a/src/test_utils.cairo +++ b/src/test_utils.cairo @@ -393,6 +393,11 @@ pub(crate) fn deploy_reward_supplier_contract(cfg: StakingInitConfig) -> Contrac account: cfg.test_info.app_governor, app_role_admin: cfg.test_info.app_role_admin, ); + set_account_as_upgrade_governor( + contract: reward_supplier_contract_address, + account: cfg.test_info.upgrade_governor, + governance_admin: cfg.test_info.governance_admin, + ); reward_supplier_contract_address } @@ -424,6 +429,10 @@ pub(crate) fn declare_pool_contract() -> ClassHash { *snforge_std::declare("Pool").unwrap().contract_class().class_hash } +pub(crate) fn declare_reward_supplier_contract() -> ClassHash { + *snforge_std::declare("RewardSupplier").unwrap().contract_class().class_hash +} + pub(crate) fn declare_staking_eic_contract() -> ClassHash { *snforge_std::declare("StakingEIC").unwrap().contract_class().class_hash } @@ -432,6 +441,10 @@ pub(crate) fn declare_pool_eic_contract() -> ClassHash { *snforge_std::declare("PoolEIC").unwrap().contract_class().class_hash } +pub(crate) fn declare_reward_supplier_eic_contract() -> ClassHash { + *snforge_std::declare("RewardSupplierEIC").unwrap().contract_class().class_hash +} + /// Upgrades implementation of the given contract. pub(crate) fn upgrade_implementation( contract_address: ContractAddress,