@@ -26,7 +26,11 @@ use apollo_l1_gas_price_types::errors::{
2626 L1GasPriceClientError ,
2727 L1GasPriceProviderError ,
2828} ;
29- use apollo_l1_gas_price_types:: { MockL1GasPriceProviderClient , PriceInfo } ;
29+ use apollo_l1_gas_price_types:: {
30+ MockExchangeRateOracleClientTrait ,
31+ MockL1GasPriceProviderClient ,
32+ PriceInfo ,
33+ } ;
3034use apollo_protobuf:: consensus:: {
3135 BuildParam ,
3236 CommitmentParts ,
@@ -89,6 +93,15 @@ fn expected_l2_gas_info_for_build_proposal_defaults() -> L2GasInfo {
8993 l2_gas_used : GasAmount ( 0 ) ,
9094 }
9195}
96+
97+ use crate :: snip35:: {
98+ compute_fee_proposal,
99+ compute_fee_target,
100+ FEE_PROPOSAL_MARGIN_PPT ,
101+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
102+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
103+ TARGET_ATTO_USD_PER_L2_GAS ,
104+ } ;
92105use crate :: utils:: { apply_fee_transformations, make_gas_price_params} ;
93106
94107static TEST_PROPOSAL_COMMITMENT : LazyLock < ProposalCommitment > = LazyLock :: new ( || {
@@ -1544,3 +1557,57 @@ async fn test_first_height_keeps_sync_provided_l2_gas_price() {
15441557 expected_price, context. l2_gas_price. 0
15451558 ) ;
15461559}
1560+
1561+ /// E2E test: SNIP-35 oracle → fee_proposal → ProposalInit → validate → decision_reached.
1562+ /// Verifies the full flow from STRK/USD oracle price to a fee_proposal that appears in the
1563+ /// built proposal and passes validation.
1564+ #[ tokio:: test]
1565+ async fn snip35_fee_proposal_e2e_flow ( ) {
1566+ // STRK/USD rate: $0.50 with 18 decimals.
1567+ const STRK_USD_RATE : u128 = 500_000_000_000_000_000 ;
1568+
1569+ let ( mut deps, mut network) = create_test_and_network_deps ( ) ;
1570+ deps. setup_deps_for_build ( SetupDepsArgs :: default ( ) ) ;
1571+
1572+ // Create a mock oracle that returns the known STRK/USD rate.
1573+ let mut mock_oracle = MockExchangeRateOracleClientTrait :: new ( ) ;
1574+ mock_oracle. expect_eth_to_fri_rate ( ) . returning ( |_| Ok ( STRK_USD_RATE ) ) ;
1575+ deps. strk_exchange_rate_oracle = Some ( Arc :: new ( mock_oracle) ) ;
1576+
1577+ // Build context (SNIP-35 is always enabled with hardcoded constants).
1578+ let mut context = deps. build_context ( ) ;
1579+
1580+ // Build a proposal.
1581+ context. set_height_and_round ( BlockNumber ( 0 ) , 0 ) . await . unwrap ( ) ;
1582+ let _fin_receiver = context. build_proposal ( BuildParam :: default ( ) , TIMEOUT ) . await . unwrap ( ) ;
1583+
1584+ // Extract ProposalInit from the network.
1585+ let ( _, mut receiver) = network. outbound_proposal_receiver . next ( ) . await . unwrap ( ) ;
1586+ let ProposalPart :: Init ( init) = receiver. next ( ) . await . unwrap ( ) else {
1587+ panic ! ( "Expected ProposalPart::Init" ) ;
1588+ } ;
1589+
1590+ // Compute the expected fee_proposal.
1591+ // During initiation (< 10 blocks), fee_actual falls back to l2_gas_price (min_gas_price).
1592+ let min_gas_price = VersionedConstants :: latest_constants ( ) . min_gas_price ;
1593+ let fee_target = compute_fee_target (
1594+ TARGET_ATTO_USD_PER_L2_GAS ,
1595+ STRK_USD_RATE ,
1596+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
1597+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
1598+ ) ;
1599+ let expected_fee_proposal =
1600+ compute_fee_proposal ( Some ( fee_target) , min_gas_price, FEE_PROPOSAL_MARGIN_PPT ) ;
1601+
1602+ assert_eq ! (
1603+ init. fee_proposal_fri,
1604+ Some ( expected_fee_proposal) ,
1605+ "fee_proposal in ProposalInit should match: fee_target={fee_target:?}, \
1606+ min_gas_price={min_gas_price:?}, expected={expected_fee_proposal:?}"
1607+ ) ;
1608+ // Sanity: fee_proposal should be nonzero.
1609+ assert ! (
1610+ init. fee_proposal_fri. expect( "fee_proposal must be Some at V0_14_3+" ) . 0 > 0 ,
1611+ "fee_proposal should be nonzero when oracle is active"
1612+ ) ;
1613+ }
0 commit comments