Skip to content

Commit fb344f6

Browse files
committed
feat(staking): add block time config in reward supplier
1 parent f3267d6 commit fb344f6

6 files changed

Lines changed: 199 additions & 6 deletions

File tree

docs/spec.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
- [contract\_parameters\_v1](#contract_parameters_v1-2)
126126
- [on\_receive](#on_receive)
127127
- [get\_alpha](#get_alpha)
128+
- [get\_block\_duration\_config](#get_block_duration_config)
129+
- [set\_block\_duration\_config](#set_block_duration_config)
128130
- [Events](#events-2)
129131
- [Mint Request](#mint-request)
130132
- [Minting Curve Contract](#minting-curve-contract)
@@ -237,6 +239,7 @@
237239
- [CONSENSUS\_REWARDS\_IS\_ACTIVE](#consensus_rewards_is_active)
238240
- [INVALID\_STAKER](#invalid_staker)
239241
- [INVALID\_TOKEN\_DECIMALS](#invalid_token_decimals)
242+
- [INVALID\_MIN\_MAX\_BLOCK\_DURATION](#invalid_min_max_block_duration)
240243
- [Structs](#structs)
241244
- [StakerPoolInfoV1](#stakerpoolinfov1)
242245
- [StakerInfoV1](#stakerinfov1)
@@ -254,6 +257,7 @@
254257
- [AttestationInfo](#attestationinfo)
255258
- [EpochInfo](#epochinfo)
256259
- [MintingCurveContractInfo](#mintingcurvecontractinfo)
260+
- [BlockDurationConfig](#blockdurationconfig)
257261
- [Type aliases](#type-aliases)
258262
- [Amount](#amount)
259263
- [Commission](#commission)
@@ -2344,6 +2348,36 @@ Returns the alpha parameter, as percentage, used when computing BTC rewards.
23442348
#### access control <!-- omit from toc -->
23452349
Any address can execute.
23462350

2351+
### get_block_duration_config
2352+
```rust
2353+
fn get_block_duration_config(self: @TContractState) -> BlockdurationConfig;
2354+
```
2355+
#### description <!-- omit from toc -->
2356+
Returns [BlockdurationConfig](#blockdurationconfig).
2357+
#### emits <!-- omit from toc -->
2358+
#### errors <!-- omit from toc -->
2359+
#### pre-condition <!-- omit from toc -->
2360+
#### logic <!-- omit from toc -->
2361+
#### access control <!-- omit from toc -->
2362+
Any address can execute.
2363+
2364+
### get_block_duration_config
2365+
```rust
2366+
fn set_block_duration_config(ref self: TContractState, block_duration_config: BlockdurationConfig);
2367+
```
2368+
#### description <!-- omit from toc -->
2369+
Set the block duration configuration.
2370+
#### emits <!-- omit from toc -->
2371+
#### errors <!-- omit from toc -->
2372+
1. [ONLY\_APP\_GOVERNOR](#only_app_governor)
2373+
2. [INVALID\_MIN\_MAX\_BLOCK\_duration](#invalid_min_max_block_duration)
2374+
#### pre-condition <!-- omit from toc -->
2375+
1. 0 < `block_duration_config.weighted_avg_factor` <= 100
2376+
2. 0 < `block_duration_config.min_block_duration` <= `block_duration_config.max_block_duration`
2377+
#### logic <!-- omit from toc -->
2378+
#### access control <!-- omit from toc -->
2379+
Only app governor.
2380+
23472381
## Events
23482382
### Mint Request
23492383
| data | type | keyed |
@@ -2797,6 +2831,9 @@ Only token admin.
27972831
### INVALID_STAKER
27982832
"Staker is invalid for getting rewards"
27992833

2834+
### INVALID_MIN_MAX_BLOCK_DURATION
2835+
"Invalid min/max block duration"
2836+
28002837
# Structs
28012838
### StakerPoolInfoV1
28022839
| name | type |
@@ -2918,6 +2955,12 @@ Only token admin.
29182955
| c_num | Inflation |
29192956
| c_denom | Inflation |
29202957

2958+
### BlockDurationConfig
2959+
| name | type |
2960+
| ------------------- | ---- |
2961+
| min_block_duration | u64 |
2962+
| max_block_duration | u64 |
2963+
29212964
# Type aliases
29222965
### Amount
29232966
Amount: u128

src/reward_supplier/errors.cairo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub enum Error {
55
ON_RECEIVE_NOT_FROM_STARKGATE,
66
UNEXPECTED_TOKEN,
77
BLOCK_DURATION_OVERFLOW,
8+
INVALID_MIN_MAX_BLOCK_DURATION,
89
}
910

1011
impl DescribableError of Describable<Error> {
@@ -13,6 +14,7 @@ impl DescribableError of Describable<Error> {
1314
Error::ON_RECEIVE_NOT_FROM_STARKGATE => "Only StarkGate can call on_receive",
1415
Error::UNEXPECTED_TOKEN => "Unexpected token",
1516
Error::BLOCK_DURATION_OVERFLOW => "Block duration calculation overflow",
17+
Error::INVALID_MIN_MAX_BLOCK_DURATION => "Invalid min/max block duration",
1618
}
1719
}
1820
}

src/reward_supplier/interface.cairo

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ pub trait IRewardSupplier<TContractState> {
7878
fn contract_parameters_v1(self: @TContractState) -> RewardSupplierInfoV1;
7979
/// Returns the alpha parameter, as percentage, used when computing BTC rewards.
8080
fn get_alpha(self: @TContractState) -> u128;
81+
/// Returns the block duration configuration.
82+
fn get_block_duration_config(self: @TContractState) -> BlockDurationConfig;
83+
}
84+
85+
#[starknet::interface]
86+
pub trait IRewardSupplierConfig<TContractState> {
87+
/// Sets the block duration configuration.
88+
///
89+
/// #### Preconditions:
90+
/// - `block_duration_config.min_block_duration > 0`
91+
/// - `block_duration_config.min_block_duration <= block_duration_config.max_block_duration`
92+
///
93+
/// #### Errors:
94+
/// - [`ONLY_APP_GOVERNOR`](AccessErrors::ONLY_APP_GOVERNOR)
95+
/// -
96+
/// [`INVALID_MIN_MAX_BLOCK_DURATION`](staking::reward_supplier::errors::Error::INVALID_MIN_MAX_BLOCK_DURATION)
97+
///
98+
/// #### Access control:
99+
/// Only app governor.
100+
fn set_block_duration_config(
101+
ref self: TContractState, block_duration_config: BlockDurationConfig,
102+
);
81103
}
82104

83105
pub mod Events {
@@ -95,3 +117,12 @@ pub struct RewardSupplierInfoV1 {
95117
pub unclaimed_rewards: Amount,
96118
pub l1_pending_requested_amount: Amount,
97119
}
120+
121+
/// Configuration for block duration calculation.
122+
#[derive(Debug, Copy, Drop, Serde, PartialEq, starknet::Store)]
123+
pub struct BlockDurationConfig {
124+
/// Minimum block duration, in units of 1 / BLOCK_DURATION_SCALE seconds.
125+
pub min_block_duration: u64,
126+
/// Maximum block duration, in units of 1 / BLOCK_DURATION_SCALE seconds.
127+
pub max_block_duration: u64,
128+
}

src/reward_supplier/reward_supplier.cairo

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ pub mod RewardSupplier {
1010
use staking::errors::{GenericError, InternalError};
1111
use staking::minting_curve::interface::{IMintingCurveDispatcher, IMintingCurveDispatcherTrait};
1212
use staking::reward_supplier::errors::Error;
13-
use staking::reward_supplier::interface::{Events, IRewardSupplier, RewardSupplierInfoV1};
13+
use staking::reward_supplier::interface::{
14+
BlockDurationConfig, Events, IRewardSupplier, IRewardSupplierConfig, RewardSupplierInfoV1,
15+
};
1416
use staking::reward_supplier::utils::{calculate_btc_rewards, compute_threshold};
1517
use staking::staking::interface::{IStakingDispatcher, IStakingDispatcherTrait};
1618
use staking::staking::objects::EpochInfoTrait;
@@ -33,6 +35,10 @@ pub mod RewardSupplier {
3335
pub(crate) const BLOCK_DURATION_SCALE: u64 = 100;
3436
/// Default avg block duration.
3537
pub(crate) const DEFAULT_AVG_BLOCK_DURATION: u64 = 3 * BLOCK_DURATION_SCALE;
38+
/// Default block duration configuration.
39+
pub(crate) const DEFAULT_BLOCK_DURATION_CONFIG: BlockDurationConfig = BlockDurationConfig {
40+
min_block_duration: 2 * BLOCK_DURATION_SCALE, max_block_duration: 5 * BLOCK_DURATION_SCALE,
41+
};
3642

3743
component!(path: ReplaceabilityComponent, storage: replaceability, event: ReplaceabilityEvent);
3844
component!(path: RolesComponent, storage: roles, event: RolesEvent);
@@ -76,14 +82,17 @@ pub mod RewardSupplier {
7682
l1_reward_supplier: felt252,
7783
/// Token bridge address.
7884
starkgate_address: ContractAddress,
79-
/// Average block duration in units of 1 / BLOCK_TIME_SCALE seconds.
85+
/// Average block duration in units of 1 / BLOCK_DURATION_SCALE seconds.
8086
// TODO: Initial in EIC.
8187
// TODO: Setter.
8288
// TODO: View?
8389
avg_block_duration: u64,
8490
/// The latest block data used for average block duration calculation.
8591
/// Updated at the start of each epoch.
8692
block_snapshot: (BlockNumber, Timestamp),
93+
/// Configuration for block duration calculation.
94+
// TODO: Initial in EIC.
95+
block_duration_config: BlockDurationConfig,
8796
}
8897

8998
#[event]
@@ -123,6 +132,7 @@ pub mod RewardSupplier {
123132
self.l1_reward_supplier.write(l1_reward_supplier);
124133
self.starkgate_address.write(starkgate_address);
125134
self.avg_block_duration.write(DEFAULT_AVG_BLOCK_DURATION);
135+
self.block_duration_config.write(DEFAULT_BLOCK_DURATION_CONFIG);
126136
}
127137

128138
#[abi(embed_v0)]
@@ -255,6 +265,35 @@ pub mod RewardSupplier {
255265
fn get_alpha(self: @ContractState) -> u128 {
256266
ALPHA
257267
}
268+
269+
fn get_block_duration_config(self: @ContractState) -> BlockDurationConfig {
270+
self.block_duration_config.read()
271+
}
272+
}
273+
274+
#[abi(embed_v0)]
275+
impl RewardSupplierConfigImpl of IRewardSupplierConfig<ContractState> {
276+
fn set_block_duration_config(
277+
ref self: ContractState, block_duration_config: BlockDurationConfig,
278+
) {
279+
self.roles.only_app_governor();
280+
// TODO: Emit event?
281+
// Assert that block_time_config is valid.
282+
// TODO: More validations?
283+
assert!(
284+
block_duration_config.min_block_duration > 0,
285+
"{}",
286+
Error::INVALID_MIN_MAX_BLOCK_DURATION,
287+
);
288+
assert!(
289+
block_duration_config
290+
.min_block_duration <= block_duration_config
291+
.max_block_duration,
292+
"{}",
293+
Error::INVALID_MIN_MAX_BLOCK_DURATION,
294+
);
295+
self.block_duration_config.write(block_duration_config);
296+
}
258297
}
259298

260299
#[generate_trait]

src/reward_supplier/test.cairo

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ use snforge_std::{
1111
use staking::constants::{ALPHA, ALPHA_DENOMINATOR, SECONDS_IN_YEAR, STRK_IN_FRIS};
1212
use staking::errors::{GenericError, InternalError};
1313
use staking::minting_curve::interface::{IMintingCurveDispatcher, IMintingCurveDispatcherTrait};
14+
use staking::reward_supplier::errors::Error;
1415
use staking::reward_supplier::interface::{
15-
IRewardSupplier, IRewardSupplierDispatcher, IRewardSupplierDispatcherTrait,
16-
IRewardSupplierSafeDispatcher, IRewardSupplierSafeDispatcherTrait, RewardSupplierInfoV1,
16+
BlockDurationConfig, IRewardSupplier, IRewardSupplierConfigDispatcher,
17+
IRewardSupplierConfigDispatcherTrait, IRewardSupplierConfigSafeDispatcher,
18+
IRewardSupplierConfigSafeDispatcherTrait, IRewardSupplierDispatcher,
19+
IRewardSupplierDispatcherTrait, IRewardSupplierSafeDispatcher,
20+
IRewardSupplierSafeDispatcherTrait, RewardSupplierInfoV1,
1721
};
1822
use staking::reward_supplier::reward_supplier::RewardSupplier;
1923
use staking::reward_supplier::reward_supplier::RewardSupplier::{
20-
BLOCK_DURATION_SCALE, DEFAULT_AVG_BLOCK_DURATION,
24+
BLOCK_DURATION_SCALE, DEFAULT_AVG_BLOCK_DURATION, DEFAULT_BLOCK_DURATION_CONFIG,
2125
};
2226
use staking::reward_supplier::utils::compute_threshold;
2327
use staking::staking::interface::{IStakingDispatcher, IStakingDispatcherTrait};
@@ -41,7 +45,6 @@ use test_utils::{
4145
stake_for_testing_using_dispatcher,
4246
};
4347

44-
4548
#[test]
4649
fn test_identity() {
4750
assert!(reward_supplier_identity == 'Reward Supplier');
@@ -534,3 +537,68 @@ fn test_update_current_epoch_block_rewards_assertions() {
534537
:result, expected_error: InternalError::INVALID_BLOCK_TIMESTAMP.describe(),
535538
);
536539
}
540+
541+
#[test]
542+
fn test_get_block_duration_config() {
543+
let mut cfg: StakingInitConfig = Default::default();
544+
general_contract_system_deployment(ref :cfg);
545+
let reward_supplier = cfg.staking_contract_info.reward_supplier;
546+
let reward_supplier_dispatcher = IRewardSupplierDispatcher {
547+
contract_address: reward_supplier,
548+
};
549+
let block_duration_config = reward_supplier_dispatcher.get_block_duration_config();
550+
assert!(block_duration_config == DEFAULT_BLOCK_DURATION_CONFIG);
551+
}
552+
553+
#[test]
554+
fn test_set_block_duration_config() {
555+
let mut cfg: StakingInitConfig = Default::default();
556+
general_contract_system_deployment(ref :cfg);
557+
let reward_supplier = cfg.staking_contract_info.reward_supplier;
558+
let reward_supplier_dispatcher = IRewardSupplierDispatcher {
559+
contract_address: reward_supplier,
560+
};
561+
let reward_supplier_config_dispatcher = IRewardSupplierConfigDispatcher {
562+
contract_address: reward_supplier,
563+
};
564+
let app_governor = cfg.test_info.app_governor;
565+
let block_duration_config = BlockDurationConfig {
566+
min_block_duration: 90, max_block_duration: 350,
567+
};
568+
assert!(reward_supplier_dispatcher.get_block_duration_config() != block_duration_config);
569+
cheat_caller_address_once(contract_address: reward_supplier, caller_address: app_governor);
570+
reward_supplier_config_dispatcher.set_block_duration_config(:block_duration_config);
571+
assert!(reward_supplier_dispatcher.get_block_duration_config() == block_duration_config);
572+
}
573+
574+
#[test]
575+
#[feature("safe_dispatcher")]
576+
fn test_set_block_duration_config_assertions() {
577+
let mut cfg: StakingInitConfig = Default::default();
578+
general_contract_system_deployment(ref :cfg);
579+
let reward_supplier = cfg.staking_contract_info.reward_supplier;
580+
let reward_supplier_config_safe_dispatcher = IRewardSupplierConfigSafeDispatcher {
581+
contract_address: reward_supplier,
582+
};
583+
let app_governor = cfg.test_info.app_governor;
584+
let mut block_duration_config = DEFAULT_BLOCK_DURATION_CONFIG;
585+
// Catch ONLY_APP_GOVERNOR.
586+
let result = reward_supplier_config_safe_dispatcher
587+
.set_block_duration_config(:block_duration_config);
588+
assert_panic_with_error(:result, expected_error: "ONLY_APP_GOVERNOR");
589+
// Catch INVALID_MIN_MAX_BLOCK_DURATION.
590+
block_duration_config.min_block_duration = block_duration_config.max_block_duration + 1;
591+
cheat_caller_address_once(contract_address: reward_supplier, caller_address: app_governor);
592+
let result = reward_supplier_config_safe_dispatcher
593+
.set_block_duration_config(:block_duration_config);
594+
assert_panic_with_error(
595+
:result, expected_error: Error::INVALID_MIN_MAX_BLOCK_DURATION.describe(),
596+
);
597+
block_duration_config.min_block_duration = 0;
598+
cheat_caller_address_once(contract_address: reward_supplier, caller_address: app_governor);
599+
let result = reward_supplier_config_safe_dispatcher
600+
.set_block_duration_config(:block_duration_config);
601+
assert_panic_with_error(
602+
:result, expected_error: Error::INVALID_MIN_MAX_BLOCK_DURATION.describe(),
603+
);
604+
}

src/test_utils.cairo

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,16 @@ pub(crate) fn deploy_reward_supplier_contract(cfg: StakingInitConfig) -> Contrac
383383
cfg.test_info.governance_admin.serialize(ref calldata);
384384
let reward_supplier_contract = snforge_std::declare("RewardSupplier").unwrap().contract_class();
385385
let (reward_supplier_contract_address, _) = reward_supplier_contract.deploy(@calldata).unwrap();
386+
set_account_as_app_role_admin(
387+
contract: reward_supplier_contract_address,
388+
account: cfg.test_info.app_role_admin,
389+
governance_admin: cfg.test_info.governance_admin,
390+
);
391+
set_account_as_app_governor(
392+
contract: reward_supplier_contract_address,
393+
account: cfg.test_info.app_governor,
394+
app_role_admin: cfg.test_info.app_role_admin,
395+
);
386396
reward_supplier_contract_address
387397
}
388398

0 commit comments

Comments
 (0)