@@ -71,7 +71,7 @@ use tokio::task::JoinHandle;
7171use tokio:: time:: sleep;
7272use tokio_util:: sync:: CancellationToken ;
7373use tokio_util:: task:: AbortOnDropHandle ;
74- use tracing:: { error, error_span, info, instrument, trace, warn, Instrument } ;
74+ use tracing:: { debug , error, error_span, info, instrument, trace, warn, Instrument } ;
7575
7676use crate :: build_proposal:: { build_proposal, BuildProposalError , ProposalBuildArguments } ;
7777use crate :: cende:: {
@@ -90,8 +90,21 @@ use crate::metrics::{
9090 record_validate_proposal_failure,
9191 register_metrics,
9292 CONSENSUS_L2_GAS_PRICE ,
93+ SNIP35_FEE_ACTUAL ,
94+ SNIP35_FEE_PROPOSAL ,
95+ SNIP35_FEE_TARGET ,
96+ SNIP35_STRK_USD_RATE ,
97+ } ;
98+ use crate :: snip35:: {
99+ compute_fee_actual,
100+ compute_fee_proposal,
101+ compute_fee_target,
102+ FEE_PROPOSAL_MARGIN_PPT ,
103+ FEE_PROPOSAL_WINDOW_SIZE ,
104+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
105+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
106+ TARGET_ATTO_USD_PER_L2_GAS ,
93107} ;
94- use crate :: snip35:: FEE_PROPOSAL_WINDOW_SIZE ;
95108use crate :: utils:: {
96109 convert_to_sn_api_block_info,
97110 make_gas_price_params,
@@ -353,6 +366,7 @@ impl SequencerConsensusContext {
353366 sequencer,
354367 timestamp : BlockTimestamp ( init. timestamp ) ,
355368 l1_da_mode : init. l1_da_mode ,
369+ fee_proposal_fri : init. fee_proposal_fri ,
356370 // TODO(guy.f): Figure out where/if to get the values below from and fill them.
357371 ..Default :: default ( )
358372 } ;
@@ -371,16 +385,64 @@ impl SequencerConsensusContext {
371385 /// Returns the next L2 gas price without mutating context. Used when building the fin and when
372386 /// updating at decision time.
373387 fn calculate_next_l2_gas_price ( & self , height : BlockNumber , l2_gas_used : GasAmount ) -> GasPrice {
388+ let fee_actual = self . compute_fee_actual ( ) ;
374389 calculate_next_l2_gas_price_for_fin (
375390 self . l2_gas_price ,
376391 height,
377392 l2_gas_used,
378393 self . config . dynamic_config . override_l2_gas_price_fri ,
379394 & self . config . dynamic_config . min_l2_gas_price_per_height ,
380- None ,
395+ fee_actual ,
381396 )
382397 }
383398
399+ /// SNIP-35: compute fee_actual from the sliding window of fee_proposals.
400+ fn compute_fee_actual ( & self ) -> Option < GasPrice > {
401+ let proposals: Vec < GasPrice > = self . fee_proposals_window . iter ( ) . copied ( ) . collect ( ) ;
402+ compute_fee_actual ( & proposals, FEE_PROPOSAL_WINDOW_SIZE )
403+ }
404+
405+ /// SNIP-35: compute the fee_proposal this node should publish.
406+ async fn compute_snip35_fee_proposal ( & self , timestamp : u64 ) -> GasPrice {
407+ // compute_fee_actual returns None for zero median or insufficient data, falling back to
408+ // the current l2_gas_price. This ensures proposer and validator use the same fallback.
409+ let fee_actual = self . compute_fee_actual ( ) . unwrap_or ( self . l2_gas_price ) ;
410+ SNIP35_FEE_ACTUAL . set_lossy ( fee_actual. 0 ) ;
411+
412+ // The oracle reuses ExchangeRateOracleClientTrait configured with a STRK/USD endpoint.
413+ let fee_target = match & self . deps . strk_exchange_rate_oracle {
414+ Some ( oracle) => match oracle. eth_to_fri_rate ( timestamp) . await {
415+ Ok ( rate) if rate > 0 => {
416+ SNIP35_STRK_USD_RATE . set_lossy ( rate) ;
417+ let target = compute_fee_target (
418+ TARGET_ATTO_USD_PER_L2_GAS ,
419+ rate,
420+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
421+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
422+ ) ;
423+ SNIP35_FEE_TARGET . set_lossy ( target. 0 ) ;
424+ Some ( target)
425+ }
426+ Ok ( _) => {
427+ warn ! ( "STRK/USD oracle returned zero rate, freezing fee_proposal" ) ;
428+ None
429+ }
430+ Err ( e) => {
431+ warn ! ( "STRK/USD oracle error: {e:?}, freezing fee_proposal" ) ;
432+ None
433+ }
434+ } ,
435+ None => {
436+ debug ! ( "No STRK/USD oracle configured, freezing fee_proposal" ) ;
437+ None
438+ }
439+ } ;
440+
441+ let proposal = compute_fee_proposal ( fee_target, fee_actual, FEE_PROPOSAL_MARGIN_PPT ) ;
442+ SNIP35_FEE_PROPOSAL . set_lossy ( proposal. 0 ) ;
443+ proposal
444+ }
445+
384446 fn update_l2_gas_price ( & mut self , height : BlockNumber , l2_gas_used : GasAmount ) {
385447 self . l2_gas_price = self . calculate_next_l2_gas_price ( height, l2_gas_used) ;
386448 let gas_price_u64 = u64:: try_from ( self . l2_gas_price . 0 ) . unwrap_or ( u64:: MAX ) ;
@@ -613,6 +675,7 @@ impl ConsensusContext for SequencerConsensusContext {
613675 BehaviorMode :: Echonet => true ,
614676 BehaviorMode :: Starknet => false ,
615677 } ;
678+ let fee_proposal = self . compute_snip35_fee_proposal ( self . deps . clock . unix_now ( ) ) . await ;
616679 let round = build_param. round ;
617680 let args = ProposalBuildArguments {
618681 deps : self . deps . clone ( ) ,
@@ -646,6 +709,8 @@ impl ConsensusContext for SequencerConsensusContext {
646709 . config
647710 . dynamic_config
648711 . compare_retrospective_block_hash ,
712+ fee_proposal,
713+ fee_actual : self . compute_fee_actual ( ) ,
649714 } ;
650715
651716 let handle = tokio:: spawn (
0 commit comments