Skip to content

Commit 6fda82c

Browse files
committed
feat(staking): update_current_epoch_block_rewards
1 parent 53bea2b commit 6fda82c

File tree

10 files changed

+299
-25
lines changed

10 files changed

+299
-25
lines changed

docs/spec.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
- [L2 Reward supplier contract](#l2-reward-supplier-contract)
120120
- [Functions](#functions-2)
121121
- [calculate\_current\_epoch\_rewards](#calculate_current_epoch_rewards)
122+
- [update\_current\_epoch\_block\_rewards](#update_current_epoch_block_rewards)
122123
- [update\_unclaimed\_rewards\_from\_staking\_contract](#update_unclaimed_rewards_from_staking_contract)
123124
- [claim\_rewards](#claim_rewards-2)
124125
- [contract\_parameters\_v1](#contract_parameters_v1-2)
@@ -395,6 +396,7 @@ classDiagram
395396
erc20_dispatcher,
396397
l1_reward_supplier,
397398
calculate_current_epoch_rewards()
399+
update_current_epoch_block_rewards()
398400
update_unclaimed_rewards_from_staking_contract()
399401
claim_rewards()
400402
on_receive()
@@ -2215,7 +2217,8 @@ Only staking contract can execute.
22152217
fn calculate_current_epoch_rewards(self: @TContractState) -> (Amount, Amount)
22162218
```
22172219
#### description <!-- omit from toc -->
2218-
Return the amount of rewards for the current epoch (for STRK and BTC).
2220+
Returns the amount of rewards for the current epoch (for STRK and BTC).
2221+
Used only before the consensus rewards mechanism is activated.
22192222
#### return <!-- omit from toc -->
22202223
rewards: ([Amount](#amount), [Amount](#amount)) - the rewards for the current epoch, in FRI, for STRK and BTC (respectively).
22212224
#### emits <!-- omit from toc -->
@@ -2229,6 +2232,27 @@ rewards: ([Amount](#amount), [Amount](#amount)) - the rewards for the current ep
22292232
#### access control <!-- omit from toc -->
22302233
Any address can execute.
22312234

2235+
### update_current_epoch_block_rewards
2236+
```rust
2237+
fn update_current_epoch_block_rewards(ref self: TContractState) -> (Amount, Amount);
2238+
```
2239+
#### description <!-- omit from toc -->
2240+
Returns the amount of block rewards for the current epoch (for STRK and BTC).
2241+
Used after the consensus rewards mechanism is activated.
2242+
#### return <!-- omit from toc -->
2243+
rewards: ([Amount](#amount), [Amount](#amount)) - the block rewards for the current epoch, in FRI, for STRK and BTC (respectively).
2244+
#### emits <!-- omit from toc -->
2245+
#### errors <!-- omit from toc -->
2246+
1. [CALLER\_IS\_NOT\_STAKING\_CONTRACT](#caller_is_not_staking_contract)
2247+
#### logic <!-- omit from toc -->
2248+
1. Update average block duration.
2249+
2. Invoke the Minting Curve's [yearly_mint](#yearly-mint) to receive the theoretic yearly amount of rewards.
2250+
2. Calculate block total rewards according to the yearly mint and average block duration.
2251+
3. Calculate the fraction of the rewards dedicated to BTC pools.
2252+
4. Subtract the BTC rewards from the total to get the STRK rewards.
2253+
#### access control <!-- omit from toc -->
2254+
Only staking contract.
2255+
22322256
### update_unclaimed_rewards_from_staking_contract
22332257
```rust
22342258
fn update_unclaimed_rewards_from_staking_contract(ref self: TContractState, rewards: Amount)

src/constants.cairo

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ pub(crate) const K: u8 = 2;
1616
pub(crate) const ALPHA: u128 = 25;
1717
/// Denominator used to scale `ALPHA` when computing BTC and STRK weights.
1818
pub(crate) const ALPHA_DENOMINATOR: u128 = 100;
19+
20+
/// Number of seconds in one year.
21+
pub(crate) const SECONDS_IN_YEAR: u64 = 365 * 24 * 60 * 60;

src/errors.cairo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ pub(crate) enum InternalError {
5757
UNEXPECTED_INTERNAL_MEMBER_INFO_VERSION,
5858
INVALID_REWARDS_TRACE_IDX,
5959
MISSING_CLASS_HASH,
60+
INVALID_BLOCK_NUMBER,
61+
INVALID_BLOCK_TIMESTAMP,
6062
}
6163

6264
impl DescribableInternalError of Describable<InternalError> {
@@ -73,6 +75,8 @@ impl DescribableInternalError of Describable<InternalError> {
7375
InternalError::UNEXPECTED_INTERNAL_MEMBER_INFO_VERSION => "Unexpected VInternalPoolMemberInfo version",
7476
InternalError::INVALID_REWARDS_TRACE_IDX => "Invalid cumulative rewards trace idx",
7577
InternalError::MISSING_CLASS_HASH => "Missing class hash",
78+
InternalError::INVALID_BLOCK_NUMBER => "Invalid block number",
79+
InternalError::INVALID_BLOCK_TIMESTAMP => "Invalid block timestamp",
7680
}
7781
}
7882
}

src/flow_test/utils.cairo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use staking::staking::tests::interface_v1::{
5656
IStakingV1ForTestsDispatcher, IStakingV1ForTestsDispatcherTrait,
5757
};
5858
use staking::test_utils::constants::{
59-
AVG_BLOCK_TIME, BTC_18D_CONFIG, BTC_DECIMALS_18, BTC_TOKEN_NAME, BTC_TOKEN_NAME_2,
59+
AVG_BLOCK_DURATION, BTC_18D_CONFIG, BTC_DECIMALS_18, BTC_TOKEN_NAME, BTC_TOKEN_NAME_2,
6060
EPOCH_DURATION, EPOCH_LENGTH, EPOCH_STARTING_BLOCK, INITIAL_SUPPLY, OWNER_ADDRESS,
6161
STARTING_BLOCK_OFFSET, TESTING_C_NUM, TEST_BTC_DECIMALS, UPGRADE_GOVERNOR,
6262
};
@@ -1166,7 +1166,7 @@ pub(crate) impl SystemImpl of SystemTrait {
11661166
if current_block < EPOCH_STARTING_BLOCK {
11671167
let blocks = EPOCH_STARTING_BLOCK - current_block;
11681168
advance_block_number_global(:blocks);
1169-
self.advance_time(time: TimeDelta { seconds: blocks * AVG_BLOCK_TIME });
1169+
self.advance_time(time: TimeDelta { seconds: blocks * AVG_BLOCK_DURATION });
11701170
} else {
11711171
let epoch_info = self.staking.get_epoch_info();
11721172
/// Note: This calculation of the next epoch's starting block may be incorrect
@@ -1175,7 +1175,7 @@ pub(crate) impl SystemImpl of SystemTrait {
11751175
+ epoch_info.epoch_len_in_blocks().into();
11761176
let blocks = next_epoch_starting_block - current_block;
11771177
advance_block_number_global(:blocks);
1178-
self.advance_time(time: TimeDelta { seconds: blocks * AVG_BLOCK_TIME });
1178+
self.advance_time(time: TimeDelta { seconds: blocks * AVG_BLOCK_DURATION });
11791179
}
11801180
}
11811181

src/reward_supplier/errors.cairo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use starkware_utils::errors::{Describable, ErrorDisplay};
44
pub enum Error {
55
ON_RECEIVE_NOT_FROM_STARKGATE,
66
UNEXPECTED_TOKEN,
7+
BLOCK_DURATION_OVERFLOW,
78
}
89

910
impl DescribableError of Describable<Error> {
1011
fn describe(self: @Error) -> ByteArray {
1112
match self {
1213
Error::ON_RECEIVE_NOT_FROM_STARKGATE => "Only StarkGate can call on_receive",
1314
Error::UNEXPECTED_TOKEN => "Unexpected token",
15+
Error::BLOCK_DURATION_OVERFLOW => "Block duration calculation overflow",
1416
}
1517
}
1618
}

src/reward_supplier/interface.cairo

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,30 @@ use starknet::{ContractAddress, EthAddress};
44
#[starknet::interface]
55
pub trait IRewardSupplier<TContractState> {
66
/// Returns ([`Amount`](staking::types::Amount), [`Amount`](staking::types::Amount)) of rewards
7-
/// for the current epoch, for STRK and BTC respectively.
7+
/// for the current epoch, for STRK and BTC respectively (in FRI).
8+
/// Used for attestation rewards.
89
///
910
/// #### Internal calls:
1011
/// - [`minting_curve::minting_curve::interface::IMintingCurve::yearly_mint`]
1112
/// - [`staking::staking::interface::IStaking::get_epoch_info`]
1213
fn calculate_current_epoch_rewards(self: @TContractState) -> (Amount, Amount);
14+
/// Returns ([`Amount`](staking::types::Amount), [`Amount`](staking::types::Amount)) of rewards
15+
/// for block in the current epoch, for STRK and BTC respectively (in FRI).
16+
/// Used for the consensus rewards.
17+
///
18+
/// This function is called once per epoch. It updates `avg_block_duration` and returns (STRK,
19+
/// BTC) block rewards for the current epoch.
20+
///
21+
/// #### Errors:
22+
/// -
23+
/// [`CALLER_IS_NOT_STAKING_CONTRACT`](staking::errors::GenericError::CALLER_IS_NOT_STAKING_CONTRACT)
24+
///
25+
/// #### Access control:
26+
/// Only staking contract.
27+
///
28+
/// #### Internal calls:
29+
/// - [`minting_curve::minting_curve::interface::IMintingCurve::yearly_mint`]
30+
fn update_current_epoch_block_rewards(ref self: TContractState) -> (Amount, Amount);
1331
/// Updates the unclaimed rewards from the staking contract.
1432
///
1533
/// #### Emits:

src/reward_supplier/reward_supplier.cairo

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ pub mod RewardSupplier {
66
use openzeppelin::access::accesscontrol::AccessControlComponent;
77
use openzeppelin::introspection::src5::SRC5Component;
88
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
9-
use staking::constants::{ALPHA, STRK_IN_FRIS, STRK_TOKEN_ADDRESS};
9+
use staking::constants::{ALPHA, SECONDS_IN_YEAR, STRK_IN_FRIS, STRK_TOKEN_ADDRESS};
1010
use staking::errors::{GenericError, InternalError};
1111
use staking::minting_curve::interface::{IMintingCurveDispatcher, IMintingCurveDispatcherTrait};
1212
use staking::reward_supplier::errors::Error;
1313
use staking::reward_supplier::interface::{Events, IRewardSupplier, RewardSupplierInfoV1};
1414
use staking::reward_supplier::utils::{calculate_btc_rewards, compute_threshold};
1515
use staking::staking::interface::{IStakingDispatcher, IStakingDispatcherTrait};
1616
use staking::staking::objects::EpochInfoTrait;
17-
use staking::types::Amount;
17+
use staking::types::{Amount, BlockNumber};
1818
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
1919
use starknet::syscalls::send_message_to_l1_syscall;
2020
use starknet::{
@@ -25,9 +25,14 @@ pub mod RewardSupplier {
2525
use starkware_utils::erc20::erc20_utils::CheckedIERC20DispatcherTrait;
2626
use starkware_utils::errors::OptionAuxTrait;
2727
use starkware_utils::interfaces::identity::Identity;
28-
use starkware_utils::math::utils::ceil_of_division;
28+
use starkware_utils::math::utils::{ceil_of_division, mul_wide_and_div};
29+
use starkware_utils::time::time::Timestamp;
2930
pub const CONTRACT_IDENTITY: felt252 = 'Reward Supplier';
3031
pub const CONTRACT_VERSION: felt252 = '3.0.0';
32+
/// Scale factor for block duration measurements. 100 implies granularity of 100th of second.
33+
pub(crate) const BLOCK_DURATION_SCALE: u64 = 100;
34+
/// Default avg block duration.
35+
pub(crate) const DEFAULT_AVG_BLOCK_DURATION: u64 = 3 * BLOCK_DURATION_SCALE;
3136

3237
component!(path: ReplaceabilityComponent, storage: replaceability, event: ReplaceabilityEvent);
3338
component!(path: RolesComponent, storage: roles, event: RolesEvent);
@@ -71,6 +76,14 @@ pub mod RewardSupplier {
7176
l1_reward_supplier: felt252,
7277
/// Token bridge address.
7378
starkgate_address: ContractAddress,
79+
/// Average block duration in units of 1 / BLOCK_TIME_SCALE seconds.
80+
// TODO: Initial in EIC.
81+
// TODO: Setter.
82+
// TODO: View?
83+
avg_block_duration: u64,
84+
/// The latest block data used for average block duration calculation.
85+
/// Updated at the start of each epoch.
86+
block_snapshot: (BlockNumber, Timestamp),
7487
}
7588

7689
#[event]
@@ -109,6 +122,7 @@ pub mod RewardSupplier {
109122
self.minting_curve_dispatcher.contract_address.write(minting_curve_contract);
110123
self.l1_reward_supplier.write(l1_reward_supplier);
111124
self.starkgate_address.write(starkgate_address);
125+
self.avg_block_duration.write(DEFAULT_AVG_BLOCK_DURATION);
112126
}
113127

114128
#[abi(embed_v0)]
@@ -139,6 +153,30 @@ pub mod RewardSupplier {
139153
(strk_rewards, btc_rewards)
140154
}
141155

156+
// TODO: Emit event?
157+
fn update_current_epoch_block_rewards(ref self: ContractState) -> (Amount, Amount) {
158+
let staking_contract = self.staking_contract.read();
159+
assert!(
160+
get_caller_address() == staking_contract,
161+
"{}",
162+
GenericError::CALLER_IS_NOT_STAKING_CONTRACT,
163+
);
164+
self.set_avg_block_duration();
165+
// Calculate block rewards for the current epoch.
166+
let minting_curve_dispatcher = self.minting_curve_dispatcher.read();
167+
let yearly_mint = minting_curve_dispatcher.yearly_mint();
168+
let avg_block_duration = self.avg_block_duration.read();
169+
let total_rewards = mul_wide_and_div(
170+
lhs: yearly_mint,
171+
rhs: avg_block_duration.into(),
172+
div: BLOCK_DURATION_SCALE.into() * SECONDS_IN_YEAR.into(),
173+
)
174+
.expect_with_err(err: InternalError::REWARDS_COMPUTATION_OVERFLOW);
175+
let btc_rewards = calculate_btc_rewards(:total_rewards);
176+
let strk_rewards = total_rewards - btc_rewards;
177+
(strk_rewards, btc_rewards)
178+
}
179+
142180
fn update_unclaimed_rewards_from_staking_contract(
143181
ref self: ContractState, rewards: Amount,
144182
) {
@@ -259,5 +297,43 @@ pub mod RewardSupplier {
259297
let to_address = self.l1_reward_supplier.read();
260298
send_message_to_l1_syscall(:to_address, :payload).unwrap_syscall();
261299
}
300+
301+
fn set_avg_block_duration(ref self: ContractState) {
302+
let current_block_number = starknet::get_block_number();
303+
let current_timestamp = starknet::get_block_timestamp();
304+
let (snapshot_block_number, snapshot_timestamp) = self.block_snapshot.read();
305+
// Sanity asserts.
306+
assert!(
307+
current_block_number > snapshot_block_number,
308+
"{}",
309+
InternalError::INVALID_BLOCK_NUMBER,
310+
);
311+
assert!(
312+
current_timestamp > snapshot_timestamp.into(),
313+
"{}",
314+
InternalError::INVALID_BLOCK_TIMESTAMP,
315+
);
316+
self
317+
.block_snapshot
318+
.write((current_block_number, Timestamp { seconds: current_timestamp }));
319+
// If this is the first time we're setting the block snapshot, can't calculate avg block
320+
// time yet.
321+
if snapshot_block_number.is_zero() || snapshot_timestamp.is_zero() {
322+
return;
323+
}
324+
let time_delta = current_timestamp - snapshot_timestamp.into();
325+
// *Note*: `num_blocks` should match the epoch length in blocks. This calculation is
326+
// expected to run on the first block of each epoch, assuming `update_rewards` is called
327+
// every block.
328+
// We calculate `num_blocks` instead of using the configured value to keep the average
329+
// accurate even if some calls are missed.
330+
let num_blocks = current_block_number - snapshot_block_number;
331+
let calculated_block_duration = mul_wide_and_div(
332+
lhs: time_delta, rhs: BLOCK_DURATION_SCALE, div: num_blocks,
333+
)
334+
.expect_with_err(err: Error::BLOCK_DURATION_OVERFLOW);
335+
// TODO: Adjust calculated_block_duration with MIN_BLOCK_TIME and MAX_BLOCK_TIME.
336+
self.avg_block_duration.write(calculated_block_duration);
337+
}
262338
}
263339
}

0 commit comments

Comments
 (0)