@@ -70,7 +70,7 @@ use tokio::task::JoinHandle;
7070use tokio:: time:: sleep;
7171use tokio_util:: sync:: CancellationToken ;
7272use tokio_util:: task:: AbortOnDropHandle ;
73- use tracing:: { error, error_span, info, instrument, trace, warn, Instrument } ;
73+ use tracing:: { debug , error, error_span, info, instrument, trace, warn, Instrument } ;
7474
7575use crate :: build_proposal:: { build_proposal, BuildProposalError , ProposalBuildArguments } ;
7676use crate :: cende:: {
@@ -89,8 +89,21 @@ use crate::metrics::{
8989 record_validate_proposal_failure,
9090 register_metrics,
9191 CONSENSUS_L2_GAS_PRICE ,
92+ SNIP35_FEE_ACTUAL ,
93+ SNIP35_FEE_PROPOSAL ,
94+ SNIP35_FEE_TARGET ,
95+ SNIP35_STRK_USD_RATE ,
96+ } ;
97+ use crate :: snip35:: {
98+ compute_fee_actual,
99+ compute_fee_proposal,
100+ compute_fee_target,
101+ FEE_PROPOSAL_MARGIN_PPT ,
102+ FEE_PROPOSAL_WINDOW_SIZE ,
103+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
104+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
105+ TARGET_ATTO_USD_PER_L2_GAS ,
92106} ;
93- use crate :: snip35:: FEE_PROPOSAL_WINDOW_SIZE ;
94107use crate :: utils:: {
95108 convert_to_sn_api_block_info,
96109 make_gas_price_params,
@@ -352,6 +365,7 @@ impl SequencerConsensusContext {
352365 sequencer,
353366 timestamp : BlockTimestamp ( init. timestamp ) ,
354367 l1_da_mode : init. l1_da_mode ,
368+ fee_proposal : init. fee_proposal ,
355369 // TODO(guy.f): Figure out where/if to get the values below from and fill them.
356370 ..Default :: default ( )
357371 } ;
@@ -370,16 +384,64 @@ impl SequencerConsensusContext {
370384 /// Returns the next L2 gas price without mutating context. Used when building the fin and when
371385 /// updating at decision time.
372386 fn calculate_next_l2_gas_price ( & self , height : BlockNumber , l2_gas_used : GasAmount ) -> GasPrice {
387+ let fee_actual = self . compute_fee_actual ( ) ;
373388 calculate_next_l2_gas_price_for_fin (
374389 self . l2_gas_price ,
375390 height,
376391 l2_gas_used,
377392 self . config . dynamic_config . override_l2_gas_price_fri ,
378393 & self . config . dynamic_config . min_l2_gas_price_per_height ,
379- None ,
394+ fee_actual ,
380395 )
381396 }
382397
398+ /// SNIP-35: compute fee_actual from the sliding window of fee_proposals.
399+ fn compute_fee_actual ( & self ) -> Option < GasPrice > {
400+ let proposals: Vec < GasPrice > = self . fee_proposals_window . iter ( ) . copied ( ) . collect ( ) ;
401+ compute_fee_actual ( & proposals, FEE_PROPOSAL_WINDOW_SIZE )
402+ }
403+
404+ /// SNIP-35: compute the fee_proposal this node should publish.
405+ async fn compute_snip35_fee_proposal ( & self , timestamp : u64 ) -> GasPrice {
406+ // compute_fee_actual returns None for zero median or insufficient data, falling back to
407+ // the current l2_gas_price. This ensures proposer and validator use the same fallback.
408+ let fee_actual = self . compute_fee_actual ( ) . unwrap_or ( self . l2_gas_price ) ;
409+ SNIP35_FEE_ACTUAL . set_lossy ( fee_actual. 0 ) ;
410+
411+ // The oracle reuses PriceOracleClientTrait configured with a STRK/USD endpoint.
412+ let fee_target = match & self . deps . strk_price_oracle {
413+ Some ( oracle) => match oracle. eth_to_fri_rate ( timestamp) . await {
414+ Ok ( rate) if rate > 0 => {
415+ SNIP35_STRK_USD_RATE . set_lossy ( rate) ;
416+ let target = compute_fee_target (
417+ TARGET_ATTO_USD_PER_L2_GAS ,
418+ rate,
419+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
420+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
421+ ) ;
422+ SNIP35_FEE_TARGET . set_lossy ( target. 0 ) ;
423+ Some ( target)
424+ }
425+ Ok ( _) => {
426+ warn ! ( "STRK/USD oracle returned zero rate, freezing fee_proposal" ) ;
427+ None
428+ }
429+ Err ( e) => {
430+ warn ! ( "STRK/USD oracle error: {e:?}, freezing fee_proposal" ) ;
431+ None
432+ }
433+ } ,
434+ None => {
435+ debug ! ( "No STRK/USD oracle configured, freezing fee_proposal" ) ;
436+ None
437+ }
438+ } ;
439+
440+ let proposal = compute_fee_proposal ( fee_target, fee_actual, FEE_PROPOSAL_MARGIN_PPT ) ;
441+ SNIP35_FEE_PROPOSAL . set_lossy ( proposal. 0 ) ;
442+ proposal
443+ }
444+
383445 fn update_l2_gas_price ( & mut self , height : BlockNumber , l2_gas_used : GasAmount ) {
384446 self . l2_gas_price = self . calculate_next_l2_gas_price ( height, l2_gas_used) ;
385447 let gas_price_u64 = u64:: try_from ( self . l2_gas_price . 0 ) . unwrap_or ( u64:: MAX ) ;
@@ -610,6 +672,7 @@ impl ConsensusContext for SequencerConsensusContext {
610672 BehaviorMode :: Echonet => true ,
611673 BehaviorMode :: Starknet => false ,
612674 } ;
675+ let fee_proposal = self . compute_snip35_fee_proposal ( self . deps . clock . unix_now ( ) ) . await ;
613676 let round = build_param. round ;
614677 let args = ProposalBuildArguments {
615678 deps : self . deps . clone ( ) ,
@@ -643,6 +706,8 @@ impl ConsensusContext for SequencerConsensusContext {
643706 . config
644707 . dynamic_config
645708 . compare_retrospective_block_hash ,
709+ fee_proposal,
710+ fee_actual : self . compute_fee_actual ( ) ,
646711 } ;
647712
648713 let handle = tokio:: spawn (
0 commit comments