Skip to content

Commit c4cf492

Browse files
committed
Fix OP Stack L1 data fee accounting
1 parent 39510fc commit c4cf492

19 files changed

Lines changed: 980 additions & 19 deletions

File tree

crates/solver-core/src/builder/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ mod tests {
953953
gas_limit: None,
954954
max_fee_per_gas: None,
955955
gas_price: None,
956+
extra_native_fee_wei: "0".to_string(),
956957
value_wei: "0".to_string(),
957958
},
958959
)));
@@ -985,6 +986,7 @@ mod tests {
985986
gas_limit: None,
986987
max_fee_per_gas: None,
987988
gas_price: None,
989+
extra_native_fee_wei: "0".to_string(),
988990
value_wei: "0".to_string(),
989991
},
990992
)));

crates/solver-core/src/bump/service.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,8 @@ mod tests {
13661366
gas_buffer,
13671367
settlement_fee: Decimal::ZERO,
13681368
settlement_fee_buffer: Decimal::ZERO,
1369+
l1_data_fee: Decimal::ZERO,
1370+
l1_data_fee_buffer: Decimal::ZERO,
13691371
rate_buffer: Decimal::ZERO,
13701372
base_price: Decimal::ZERO,
13711373
min_profit,

crates/solver-core/src/engine/cost_profit.rs

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ fn quote_hyperlane_message_gas_limit(payload_size: usize) -> U256 {
8484
U256::from(base_gas + (payload_size * gas_per_byte) + buffer)
8585
}
8686

87+
fn extra_native_fee_configured(config: &Config, chain_id: u64) -> bool {
88+
let chain_key = chain_id.to_string();
89+
config
90+
.delivery
91+
.implementations
92+
.values()
93+
.any(|implementation| {
94+
implementation
95+
.get("fee_policy")
96+
.and_then(|fee_policy| fee_policy.get("chains"))
97+
.and_then(|chains| chains.get(&chain_key))
98+
.and_then(|chain| chain.get("extra_native_fee"))
99+
.is_some()
100+
})
101+
}
102+
87103
#[allow(clippy::too_many_arguments)]
88104
fn encode_quote_hyperlane_fill_description(
89105
solver_identifier: [u8; 32],
@@ -888,6 +904,65 @@ impl CostProfitService {
888904
gas_units.post_fill_units = live_units;
889905
}
890906

907+
let mut l1_data_fee_wei = U256::ZERO;
908+
let mut l1_data_fee_buffer_wei = U256::ZERO;
909+
910+
match self
911+
.build_fill_tx_for_quote(
912+
request,
913+
context,
914+
&swap_amounts_with_info,
915+
config,
916+
solver_address,
917+
)
918+
.await
919+
{
920+
Ok(mut fill_tx) => {
921+
fill_tx.gas_limit = Some(gas_units.fill_units);
922+
let (raw, buffer) = self
923+
.estimate_extra_native_fee_wei(config, dest_chain_id, &fill_tx)
924+
.await?;
925+
l1_data_fee_wei = l1_data_fee_wei.saturating_add(raw);
926+
l1_data_fee_buffer_wei = l1_data_fee_buffer_wei.saturating_add(buffer);
927+
},
928+
Err(e) => {
929+
tracing::warn!(
930+
chain_id = dest_chain_id,
931+
error = %e,
932+
"Skipping fill extra native fee estimate: failed to build synthetic fill tx"
933+
);
934+
},
935+
}
936+
937+
match self
938+
.build_post_fill_tx_for_quote(
939+
request,
940+
context,
941+
&swap_amounts_with_info,
942+
config,
943+
solver_address,
944+
settlement_fee_wei,
945+
)
946+
.await
947+
{
948+
Ok(Some(mut post_fill)) => {
949+
post_fill.tx.gas_limit = Some(gas_units.post_fill_units);
950+
let (raw, buffer) = self
951+
.estimate_extra_native_fee_wei(config, dest_chain_id, &post_fill.tx)
952+
.await?;
953+
l1_data_fee_wei = l1_data_fee_wei.saturating_add(raw);
954+
l1_data_fee_buffer_wei = l1_data_fee_buffer_wei.saturating_add(buffer);
955+
},
956+
Ok(None) => {},
957+
Err(e) => {
958+
tracing::warn!(
959+
chain_id = dest_chain_id,
960+
error = %e,
961+
"Skipping post-fill extra native fee estimate: failed to build synthetic post-fill tx"
962+
);
963+
},
964+
}
965+
891966
// Parse inputs/outputs to proper types for cost calculation
892967
let mut parsed_inputs = Vec::new();
893968
for input in inputs {
@@ -925,6 +1000,8 @@ impl CostProfitService {
9251000
dest_chain_id,
9261001
&gas_units,
9271002
settlement_fee_wei,
1003+
l1_data_fee_wei,
1004+
l1_data_fee_buffer_wei,
9281005
)
9291006
.await?;
9301007

@@ -988,7 +1065,9 @@ impl CostProfitService {
9881065
cost_breakdown.gas_fill
9891066
+ cost_breakdown.gas_post_fill
9901067
+ cost_breakdown.settlement_fee
991-
+ cost_breakdown.settlement_fee_buffer,
1068+
+ cost_breakdown.settlement_fee_buffer
1069+
+ cost_breakdown.l1_data_fee
1070+
+ cost_breakdown.l1_data_fee_buffer,
9921071
);
9931072

9941073
// Calculate adjusted amounts (swap amounts +/- costs based on swap type)
@@ -1050,6 +1129,8 @@ impl CostProfitService {
10501129
dest_chain_id: u64,
10511130
gas_units: &GasUnits,
10521131
settlement_fee_wei: U256,
1132+
l1_data_fee_wei: U256,
1133+
l1_data_fee_buffer_wei: U256,
10531134
) -> Result<CostBreakdown, CostProfitError> {
10541135
// Read gas_buffer_bps from solver config (hot-reloadable)
10551136
let gas_buffer_bps_value = config.solver.gas_buffer_bps;
@@ -1118,6 +1199,10 @@ impl CostProfitService {
11181199
Decimal::new(config.solver.settlement_fee_buffer_bps as i64, 0);
11191200
let settlement_fee_buffer =
11201201
(settlement_fee * settlement_fee_buffer_bps) / Decimal::from(10000);
1202+
let (l1_data_fee, l1_data_fee_buffer) = tokio::try_join!(
1203+
self.wei_to_usd(&l1_data_fee_wei),
1204+
self.wei_to_usd(&l1_data_fee_buffer_wei),
1205+
)?;
11211206

11221207
// Input and output valuations do not depend on each other.
11231208
let (total_input_value_usd, total_output_value_usd) = tokio::try_join!(
@@ -1149,6 +1234,8 @@ impl CostProfitService {
11491234
+ gas_claim + gas_buffer
11501235
+ settlement_fee
11511236
+ settlement_fee_buffer
1237+
+ l1_data_fee
1238+
+ l1_data_fee_buffer
11521239
+ rate_buffer;
11531240

11541241
// Calculate subtotal (actual costs only, excluding profit)
@@ -1166,6 +1253,8 @@ impl CostProfitService {
11661253
gas_buffer,
11671254
settlement_fee,
11681255
settlement_fee_buffer,
1256+
l1_data_fee,
1257+
l1_data_fee_buffer,
11691258
rate_buffer,
11701259
base_price,
11711260
min_profit,
@@ -1405,6 +1494,15 @@ impl CostProfitService {
14051494
None => U256::ZERO,
14061495
};
14071496

1497+
let (l1_data_fee_wei, l1_data_fee_buffer_wei) = if let Some(fill_tx) = fill_tx {
1498+
let mut fill_tx = fill_tx.clone();
1499+
fill_tx.gas_limit = Some(gas_units.fill_units);
1500+
self.estimate_extra_native_fee_wei(config, dest_chain_id, &fill_tx)
1501+
.await?
1502+
} else {
1503+
(U256::ZERO, U256::ZERO)
1504+
};
1505+
14081506
// Use the unified cost calculation method
14091507
let cost_breakdown = self
14101508
.calculate_total_cost(
@@ -1415,6 +1513,8 @@ impl CostProfitService {
14151513
dest_chain_id,
14161514
&gas_units,
14171515
settlement_fee_wei,
1516+
l1_data_fee_wei,
1517+
l1_data_fee_buffer_wei,
14181518
)
14191519
.await?;
14201520

@@ -2537,6 +2637,35 @@ impl CostProfitService {
25372637
})
25382638
}
25392639

2640+
async fn estimate_extra_native_fee_wei(
2641+
&self,
2642+
config: &Config,
2643+
chain_id: u64,
2644+
tx: &Transaction,
2645+
) -> Result<(U256, U256), CostProfitError> {
2646+
if !extra_native_fee_configured(config, chain_id) {
2647+
return Ok((U256::ZERO, U256::ZERO));
2648+
}
2649+
2650+
let estimate = self
2651+
.delivery_service
2652+
.estimate_extra_native_fee(chain_id, tx)
2653+
.await
2654+
.map_err(|e| APIError::InternalServerError {
2655+
error_type: ApiErrorType::ServiceError,
2656+
message: format!("Failed to estimate extra native fee: {e}"),
2657+
})?;
2658+
let raw = U256::from_str(&estimate.raw_fee_wei).map_err(|e| {
2659+
CostProfitError::Calculation(format!("Failed to parse extra native raw fee wei: {e}"))
2660+
})?;
2661+
let buffer = U256::from_str(&estimate.buffer_wei).map_err(|e| {
2662+
CostProfitError::Calculation(format!(
2663+
"Failed to parse extra native fee buffer wei: {e}"
2664+
))
2665+
})?;
2666+
Ok((raw, buffer))
2667+
}
2668+
25402669
async fn wei_to_usd(&self, value_wei: &U256) -> Result<Decimal, CostProfitError> {
25412670
let usd_value = self
25422671
.pricing_service
@@ -3287,6 +3416,8 @@ mod tests {
32873416
gas_buffer: Decimal::from_str("0.004").unwrap(),
32883417
settlement_fee: Decimal::ZERO,
32893418
settlement_fee_buffer: Decimal::ZERO,
3419+
l1_data_fee: Decimal::ZERO,
3420+
l1_data_fee_buffer: Decimal::ZERO,
32903421
rate_buffer: Decimal::ZERO,
32913422
base_price: Decimal::ZERO,
32923423
min_profit: Decimal::from_str("5.00").unwrap(),
@@ -4133,11 +4264,11 @@ mod tests {
41334264
}
41344265

41354266
#[tokio::test]
4136-
async fn test_calculate_total_cost_includes_optional_settlement_gas() {
4267+
async fn test_calculate_total_cost_includes_optional_settlement_and_l1_data_fees() {
41374268
let mut mock_pricing = MockPricingInterface::new();
41384269
mock_pricing
41394270
.expect_wei_to_currency()
4140-
.times(6)
4271+
.times(8)
41414272
.returning(|wei, _| {
41424273
let wei = wei.to_string();
41434274
Box::pin(async move { Ok(wei) })
@@ -4205,6 +4336,8 @@ mod tests {
42054336
claim_units: 5,
42064337
},
42074338
U256::from(10u64),
4339+
U256::from(7u64),
4340+
U256::from(8u64),
42084341
)
42094342
.await
42104343
.unwrap();
@@ -4217,7 +4350,9 @@ mod tests {
42174350
assert_eq!(breakdown.gas_buffer, Decimal::from(60));
42184351
assert_eq!(breakdown.settlement_fee, Decimal::from(10));
42194352
assert_eq!(breakdown.settlement_fee_buffer, Decimal::from(1));
4220-
assert_eq!(breakdown.operational_cost, Decimal::from(671));
4353+
assert_eq!(breakdown.l1_data_fee, Decimal::from(7));
4354+
assert_eq!(breakdown.l1_data_fee_buffer, Decimal::from(8));
4355+
assert_eq!(breakdown.operational_cost, Decimal::from(686));
42214356
}
42224357

42234358
// ============================================================================
@@ -4318,6 +4453,8 @@ mod tests {
43184453
gas_buffer: Decimal::from_str("0.004").unwrap(),
43194454
settlement_fee: Decimal::ZERO,
43204455
settlement_fee_buffer: Decimal::ZERO,
4456+
l1_data_fee: Decimal::ZERO,
4457+
l1_data_fee_buffer: Decimal::ZERO,
43214458
rate_buffer: Decimal::ZERO,
43224459
base_price: Decimal::ZERO,
43234460
min_profit,

crates/solver-core/src/engine/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,7 @@ mod tests {
17701770
gas_limit: Some(21_000),
17711771
max_fee_per_gas: Some(1),
17721772
gas_price: None,
1773+
extra_native_fee_wei: "0".to_string(),
17731774
value_wei: "0".to_string(),
17741775
},
17751776
))

crates/solver-core/src/handlers/settlement.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,7 @@ mod tests {
911911
gas_limit: Some(21_000),
912912
max_fee_per_gas: Some(1),
913913
gas_price: None,
914+
extra_native_fee_wei: "0".to_string(),
914915
value_wei: "0".to_string(),
915916
},
916917
)))

0 commit comments

Comments
 (0)